Maxon Developers Maxon Developers
    • Documentation
      • Cinema 4D Python API
      • Cinema 4D C++ API
      • Cineware API
      • ZBrush GoZ API
      • Code Examples on Github
    • Forum
    • Downloads
    • Support
      • Support Procedures
      • Registered Developer Program
      • Plugin IDs
      • Contact Us
    • Categories
      • Overview
      • News & Information
      • Cinema 4D SDK Support
      • Cineware SDK Support
      • ZBrush 4D SDK Support
      • Bugs
      • General Talk
    • Unread
    • Recent
    • Tags
    • Users
    • Login

    Menu items without RegisterCommandPlugin?

    Cinema 4D SDK
    3
    11
    1.8k
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • lasselauchL
      lasselauch
      last edited by

      Hi John,

      you should be able to enhance the menu using a simple python-script (i.e. refresh command), but I've haven't tried it myself. Here's a small example on enhancing the menu:

      Enhance Menu Example

      From there you can list your local python-scripts, however you need a simple function to run() those "external" scripts.

      I'm currently using this function for one of my plugins which more or less does the same:

          def execute_script(self, filename):
              fl = open(filename, 'rb')
              code = compile(fl.read(), filename, 'exec')
              doc = c4d.documents.GetActiveDocument()
              scope = {
              '__file__': filename,
              '__name__': '__main__',
              'doc': doc,
              }
              exec code in scope
      

      There are some downsides to using this function but it might get you started...

      Cheers,
      Lasse

      1 Reply Last reply Reply Quote 0
      • M
        m_adam
        last edited by m_adam

        Hi @jcooper, as @lasselauch pointed you, usually to build menu its recommended to create a plugin that will react to the C4DPL_BUILDMENU message.
        However, if you want a one time action this is possible.

        Now regarding your question to have a custom folder, this is not properly supported by Cinema 4D so either you will have to use @lasselauch workaround but then I don't really see how to make dynamic CommandData for each entry since in Python there is no way to dynamically creates CommandData within the script manager since plugins can only be registered in a pyp file.

        Or you can put them in the Cinema 4D script folder located in:

        • Windows: %APPDATA%/MAXON/{cinemaversion}/library/scripts.
        • MacOS: ~/Library/Preferences/MAXON/{cinemaversion}/library/scripts.
          or defined by the enviroment variable C4D_SCRIPTS_DIR

        The benefice of the last decision is that Cinema 4D will load these scripts and create a CommandData for each of them dynamically. This way you can retrieve this commanddata and build the menu. Note that to retrieve the associated CommandData id, I search by plugin name, so be sure to have a unique name for your script.

        Here an example, that will build a menu ( I have 2 scripts, testScript and createMenu😞

        import c4d
        
        def GetScriptIdByName(name):
            pluginList = c4d.plugins.FilterPluginList(c4d.PLUGINTYPE_COMMAND, True)
            for plugin in pluginList:
                if plugin.GetName() == name:
                    return plugin.GetID()
        
            return None
        
        def GetMenuContainer(name):
            mainMenu = c4d.gui.GetMenuResource("M_EDITOR")
        
            customMenu = c4d.BaseContainer()
            for bcMenuId, bcMenu in mainMenu:
                if bcMenu[c4d.MENURESOURCE_SUBTITLE] == name:
                    customMenu = mainMenu.GetContainerInstance(bcMenuId)
                    break
        
            if customMenu is not None:
                customMenu.FlushAll()
        
            customMenu.InsData(c4d.MENURESOURCE_SUBTITLE, name)
        
            return customMenu
        
        def AddsMenuToC4DMenu(menu):
            mainMenu = c4d.gui.GetMenuResource("M_EDITOR")
            for bcMenuId, bcMenu in mainMenu:
                if bcMenu[c4d.MENURESOURCE_SUBTITLE] == menu[c4d.MENURESOURCE_SUBTITLE]:
                    return
        
            mainMenu = c4d.gui.GetMenuResource("M_EDITOR")
            mainMenu.InsData(c4d.MENURESOURCE_STRING, menu)
        
        def AddEntry(menuContainer, scriptName):
            scriptId = GetScriptIdByName(scriptName)
            if scriptId is None:
                return
        
            menuContainer.InsData(c4d.MENURESOURCE_COMMAND, "PLUGIN_CMD_{0}".format(scriptId))
        
        
        # Main function
        def main():
            menuName = "Custom Python menu"
            menuContainer = GetMenuContainer(menuName)
        
            AddEntry(menuContainer, "testScript")
            AddEntry(menuContainer, "createMenu")
        
            AddsMenuToC4DMenu(menuContainer)
        
            c4d.gui.UpdateMenus()
        
        # Execute main()
        if __name__=='__main__':
            main()
        

        Cheers,
        Maxime.

        MAXON SDK Specialist

        Development Blog, MAXON Registered Developer

        1 Reply Last reply Reply Quote 0
        • lasselauchL
          lasselauch
          last edited by

          Nice, thanks for the example @m_adam!
          I always thought you have to restart c4d to reload all the scripts located in the scripts folder..!?

          M 1 Reply Last reply Reply Quote 0
          • M
            m_adam @lasselauch
            last edited by

            @lasselauch Correct but the idea is to have all the scripts already loaded.

            MAXON SDK Specialist

            Development Blog, MAXON Registered Developer

            1 Reply Last reply Reply Quote 0
            • lasselauchL
              lasselauch
              last edited by lasselauch

              Well, one request was to "Refresh" the menu when scripts are added/deleted while the user is in C4D, this is sadly a limitation and not possible with your solution... am I right?!

              1 Reply Last reply Reply Quote 0
              • M
                m_adam
                last edited by

                @lasselauch correct.

                MAXON SDK Specialist

                Development Blog, MAXON Registered Developer

                1 Reply Last reply Reply Quote 0
                • jcooperJ
                  jcooper
                  last edited by

                  As @lasselauch points out, the issue is the functionality of the "Refresh" command.

                  I've added C4D support for our menu generation framework and it creates a hierarchical menu matching the python scripts it finds on disk just fine. Execution of those scripts works too.

                  The only issue is the inability of the Refresh menu item to rebuild the menu when it encounters a new script that didn't exist when C4D was launched.

                  I guess my only recourse is to pop up a MessageDialog telling the user that the Refresh command simply doesn't work in Cinema 4D.

                  JOHN COOPER I TECHNICAL DIRECTOR
                  PSYOP LA

                  1 Reply Last reply Reply Quote 0
                  • lasselauchL
                    lasselauch
                    last edited by

                    Hm, but it would be possible to Flush the whole Menu, rebuild it and reinitialize the updated Menu via c4d.gui.UpdateMenus(), wouldn't it!?

                    1 Reply Last reply Reply Quote 0
                    • jcooperJ
                      jcooper
                      last edited by

                      My menugen code is able to rebuild the menu.

                      However, when it tries to use RegisterCommandPlugin() for the new python scripts it finds on disk, C4D issues an error indicating it can't find a .pyp file. Apparently this is because RegisterCommandPlugin() can't be used at any time except at application launch. And without the ability to RegisterCommandPlugin() for the new scripts, I can't add them to the newly built menu as menu items.

                      JOHN COOPER I TECHNICAL DIRECTOR
                      PSYOP LA

                      1 Reply Last reply Reply Quote 1
                      • M
                        m_adam
                        last edited by m_adam

                        Hi, I'm sorry for the delay, but I can only confirm what you said.
                        In C++ it's possible to call RegisterCommandPlugin but not in Python at runtime.

                        So I guess the best approach is to have as you suggested a menu entry (a pre-registered c4d script or CommandData) that will then create a PopuDialog with a list of all scripts, and then it's up to you to execute them with the code @lasselauch provided.
                        So here an example of how to implement it.

                        import c4d
                        import os
                        
                        def main():
                            # Gets all python script of a folder
                            searchPath = r"%appdata%\Roaming\Maxon\Maxon Cinema 4D R21_115_XXXXXX\library\scripts"
                            pythonFiles = [os.path.join(folder, f) for f in os.listdir(folder) if os.path.isfile(os.path.join(folder, f)) and f.endswith(".py")]
                        
                            # Build the menu for all the entries
                            menu = c4d.BaseContainer()
                            for pythonFileId, pythonFile in enumerate(pythonFiles):
                                menuId = c4d.FIRST_POPUP_ID + pythonFileId
                                filename = os.path.basename(pythonFile)
                                menu.InsData(menuId, filename)
                        
                            # Example to also list regular command.
                            # Uses POPUP_EXECUTECOMMANDS in ShowPopupDialog flag so if its a command its executed directly
                            menu.InsData(c4d.Ocube, "CMD")
                        
                            # Display the PopupDialog
                            result = c4d.gui.ShowPopupDialog(cd=None, bc=menu, x=c4d.MOUSEPOS, y=c4d.MOUSEPOS, flags=c4d.POPUP_EXECUTECOMMANDS | c4d.POPUP_BELOW | c4d.POPUP_CENTERHORIZ)
                            
                            # If result is bigger than FIRST_POPUP_ID it means user selected something
                            if result >= c4d.FIRST_POPUP_ID:
                                
                                # Retrieves the selected python file
                                scriptId = result - c4d.FIRST_POPUP_ID
                                pythonFile = pythonFiles[scriptId]
                                
                                # Execute it and copy the global to it ( so doc, op are accessible as well)
                                fl = open(pythonFile, 'rb')
                                code = compile(fl.read(), pythonFile, 'exec')
                                exec(code, globals())
                        
                        # Execute main()
                        if __name__=='__main__':
                            main()
                        ```
                        
                        Cheers,
                        Maxime.

                        MAXON SDK Specialist

                        Development Blog, MAXON Registered Developer

                        1 Reply Last reply Reply Quote 2
                        • First post
                          Last post