Maxon Developers Maxon Developers
    • Documentation
      • Cinema 4D Python API
      • Cinema 4D C++ API
      • Cineware API
      • ZBrush Python 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

    Simple plugin to dynamically list python scripts

    Cinema 4D SDK
    python
    2
    2
    373
    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.
    • D
      Dave
      last edited by

      Hi there,

      I'm trying to make my first ever simple plugin that will just list a collection of Python Scripts I've made over the years (for easy sharing as a plugin).

      With a little help from Chat GPT I've managed to get the plugin to show inside Cinema 4D Extensions but it fails to show the icon or list the scripts.

      Here's an image showing my file structure with all my python scripts in the 'res' folder.

      b39c92c1-321e-45f6-bfa4-62e31ca16e47-image.png

      And here's my code:

      import c4d
      import os
      import importlib
      import c4d.bitmaps
      
      class TestPlugin(c4d.plugins.CommandData):
          def Execute(self, doc):
              # Display a dialog with a list of available scripts
              selected_script = self.show_script_list_dialog()
      
              # Execute the selected script
              if selected_script:
                  self.run_script(selected_script)
      
              return True
      
          def show_script_list_dialog(self):
              # Get the path to the 'res' folder within the plugin directory
              plugin_folder = os.path.dirname(__file__)
              res_folder = os.path.join(plugin_folder, 'res')
      
              # List all Python scripts in the 'res' folder
              script_files = [f for f in os.listdir(res_folder) if f.endswith('.py')]
      
              # Create a dialog to display the script list
              dlg = c4d.gui.GeDialog()
              dlg.SetTitle('Select Script to Run')
      
              for script_file in script_files:
                  dlg.AddChild(1000, c4d.DTYPE_STATIC, '')  # Spacer
                  dlg.AddChild(1001, c4d.CID_FILEBUTTON, os.path.splitext(script_file)[0])
      
              # Show the dialog
              dlg.Open(c4d.DLG_TYPE_ASYNC, 1039792, -1, -1, 400, 150)
      
              # Handle dialog events
              result = dlg.DoWhile(c4d.DLGRESULT_OK)
              if result:
                  return os.path.join(res_folder, dlg.GetFilename(1001) + '.py')
              else:
                  return None
      
          def run_script(self, script_path):
              # Load and execute the selected script
              script_name, script_extension = os.path.splitext(os.path.basename(script_path))
              script_module = importlib.load_source(script_name, script_path)
              script_module.main()
      
      plugin_folder = os.path.dirname(__file__)  # Define the plugin folder variable
      
      if __name__ == "__main__":
          plugin = TestPlugin()  # Change the plugin name to TestPlugin
      
          icon_path = os.path.join(plugin_folder, "icon.png")
          icon_bitmap = c4d.bitmaps.BaseBitmap(icon_path)
      
          c4d.plugins.RegisterCommandPlugin(
              id=1009792,  # Use the specified ID
              str="TestPlugin",  # Change the plugin name to TestPlugin
              info=0,
              dat=plugin,
              icon=icon_bitmap,  # Pass the BaseBitmap object
              help="This is a description of the TestPlugin"  # Provide a description for your plugin
          )
      
      

      Any idea where I'm going wrong?

      Cheers

      ferdinandF 1 Reply Last reply Reply Quote 0
      • ferdinandF
        ferdinand @Dave
        last edited by ferdinand

        Hello @Dave ,

        Welcome to the Plugin Café forum and the Cinema 4D development community, it is great to have you with us!

        Getting Started

        Before creating your next postings, we would recommend making yourself accustomed with our Forum and Support Guidelines, as they line out details about the Maxon SDK Group support procedures. Of special importance are:

        • Support Procedures: Scope of Support: Lines out the things we will do and what we will not do.
        • Support Procedures: Confidential Data: Most questions should be accompanied by code but code cannot always be shared publicly. This section explains how to share code confidentially with Maxon.
        • Forum Structure and Features: Lines out how the forum works.
        • Structure of a Question: Lines out how to ask a good technical question. It is not mandatory to follow this exactly, but you should follow the idea of keeping things short and mentioning your primary question in a clear manner.

        About your First Question

        Your question is effectively out of scope of support because we do not debug code for users as lined out in our support guidelines. So, you cannot just post your code and then ask us to fix it, this especially applies when things like Chat GPT are involved.

        With that being said, there are two (three) major problems with your code:

        • show_script_list_dialog: This is pure nonsense regarding creating and running a dialog. Dialogs must be implemented, see here for our dialog examples.
        • run_script: This does not set the runtime environment, the globals, of the script to run. Which will cause many scripts to fail, as script manager scripts are prepopulated with doc and op.
        • Plugin ID: The plugin ID 1009792 you are using does not seem to be a valid ID, the current plugin ID counter is at 1062027. You can do whatever you want to do locally, but you cannot ship any plugins with such made up IDs.

        You also do not really need a full-blown dialog here, you could just get away with ShowPopupDialog. Find a simple version below. Please understand that we cannot support ChatGPT nonsense output in future postings of yours.

        Cheers,
        Ferdinand

        Result:
        Running the twice hello_doc.py script with the wrapper script run_scripts.py. And then for the lulz run the wrapper with the wrapper to run the hello_doc script.

        Code:

        """Demonstrates how to run Script Manager scripts from a Script Manager script.
        
        Must be run as a Script Manager script while setting #SCRIPT_DIRECTORY to a path where your scripts
        do lie.
        
        Note:
            This code has been written for Cinema 4D 2023.2 or higher, it uses the pipe operator in type
            hints supported only by Python 3.10 or higher. To run this code in older versions, simply remove
            the pipe operator and its second argument:
            
            def foo() -> str | None:    # 3.10 code
            def foo() -> str:           # legacy code  
        """
        
        import c4d
        import os
        import runpy
        
        # Make sure to use a raw string or to escape it when defining a windows path.
        SCRIPT_DIRECTORY: str = r"C:\Users\f_hoppe\AppData\Roaming\MAXON\2024.1.0_7142A741\library\scripts"
        
        doc: c4d.documents.BaseDocument # The active document
        op: c4d.BaseObject # The active object, can be `None`.
        
        def SelectScript(path: str) -> str | None:
            """Opens popup selection dialog to let a user select a Python file in the given #path.
            """
            # Sort out that #path is indeed an existing directory path and get all Python files in it.
            if not os.path.exists(path) or not os.path.isdir(path):
                raise OSError(f"'{path}' is not a valid directory path.")
        
            files: list[str] = [os.path.join(path, f) 
                                for f in os.listdir(path) 
                                if os.path.splitext(f)[1] == ".py"]
            if len(files) == 0:
                c4d.gui.MessageDialog(f"There are no Python scripts in the path: '{path}'.")
        
            # Build a popup menu for #files and display it under the cursor.
            menu: c4d.BaseContainer = c4d.BaseContainer()
            for i, item in enumerate(files):
                menu.InsData(c4d.FIRST_POPUP_ID + i, os.path.split(item)[1])
        
            index: int = c4d.gui.ShowPopupDialog(
                cd=None, bc=menu, x=c4d.MOUSEPOS, y=c4d.MOUSEPOS) - c4d.FIRST_POPUP_ID
        
            # ShowPopupDialog returns the index of the selected item, which we let start at #FIRST_POPUP_ID
            # and the  later subtracted that value again. So, index should now either be below 0 because
            # user aborted the dialog or an index for #files.
            if index < 0:
                print ("User aborted selection.")
                return
            if index > len(files):
                raise IndexError("Unexpected invalid index.")
        
            # Get the file path and be extra paranoid in checking if the file actually does exist (it
            # should since we retrieved only existing files above).
            file: str = files[index]
            print (file)
            if not os.path.exists(file):
                raise OSError(f"The file '{file}' does not exist.")
        
            return file
        
        
        def main() -> None:
            """Runs the example.
                """
            # Let the user select a Python script at #SCRIPT_DIRECTORY and run the script in an environment
            # that matches that of a Script Manager script - which primarily means defining #doc and #op.
            # Script Manager scripts also do some other stuff, but that is irrelevant for us here. If you
            # wanted to be ultra-precise, you could also pass #globals() as the second argument to
            # #run_path(). One should then however make sure that the globals are indeed clean, so we should
            # not expose #SCRIPT_DIRECTORY as a global then.
            file: str | None = SelectScript(SCRIPT_DIRECTORY)
            if isinstance(file, str):
                runpy.run_path(file, {"doc": doc, "op": op}, "__main__")
        
        
        if __name__ == "__main__":
            main()
        

        MAXON SDK Specialist
        developers.maxon.net

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