• Categories
    • Overview
    • News & Information
    • Cinema 4D SDK Support
    • Cineware SDK Support
    • ZBrush 4D SDK Support
    • Bugs
    • General Talk
  • Unread
  • Recent
  • Tags
  • Users
  • Register
  • Login
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
  • Register
  • Login

Insert my own command in main render Tab

Cinema 4D SDK
python 2023
2
7
880
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.
  • P
    pim
    last edited by ferdinand Jun 5, 2023, 5:13 PM Jun 5, 2023, 3:58 PM

    I found where to insert my own command (plugin),
    but I get following message.

    Traceback (most recent call last):
      File "scriptmanager", line 22, in <module>
      File "scriptmanager", line 18, in main
    TypeError: argument 3 must be PyCapsule, not tuple
    

    Also I am not sure I insert my plugin correctly.
    I do not fully understand how to indicate my plugin; PLUGIN_CMD_5159, name or id?

    Here the code

    from typing import Optional
    import c4d
    
    doc: c4d.documents.BaseDocument  # The active document
    op: Optional[c4d.BaseObject]  # The active object, None if unselected
    
    def main() -> None:
        MainMenu = c4d.gui.GetMenuResource("M_EDITOR")
    
        for bcMenuId, bcMenu in MainMenu:
            #print (bcMenu[c4d.MENURESOURCE_SUBTITLE])
            if bcMenu[c4d.MENURESOURCE_SUBTITLE] == "IDS_EDITOR_RENDER":
                for sub in bcMenu:
                    if (sub[1] == "IDM_RENDERAUSSCHNITT"):
                        print ("*** Insert after this ***")
                    
                        # Add my plugin
                        bcMenu.InsDataAfter(c4d.MENURESOURCE_COMMAND, "PLUGIN_CMD_5159", sub)   
                        c4d.gui.UpdateMenus()             
            
    if __name__ == '__main__':
        main()
    
    F 1 Reply Last reply Jun 5, 2023, 5:11 PM Reply Quote 0
    • F
      ferdinand @pim
      last edited by ferdinand Jun 5, 2023, 5:13 PM Jun 5, 2023, 5:11 PM

      Hello @pim,

      Thank you for reaching out to us. BaseContainer.__iter__ yields a tuple[int, any] for the key-value pairs of the container. You call BaseContainer.__iter__ with for sub in bcMenu. sub is therefore a tuple[int, any]. Your bcMenu.InsDataAfter then complains that you pass this tuple.

      I would also recommend having a look at this thread.

      Cheers,
      Ferdinand

      MAXON SDK Specialist
      developers.maxon.net

      P 1 Reply Last reply Jun 6, 2023, 7:29 PM Reply Quote 0
      • P
        pim @ferdinand
        last edited by Jun 6, 2023, 7:29 PM

        @ferdinand
        Ok, I can now insert my command in the render Tab, but it is positioned at the end.
        This is, I guess, because I use InsData() instead of InsDataAfter()

        Refering to my first code, this was my thinking:
        When using InsDataAfter(), looking at the documentation, I thought that last(any) indicates the position after where to insert the command. That is why I used sub.

        So, how to indicate where to insert the command?

        BaseContainer.InsDataAfter(self, id, n, last)¶
        Inserts an arbitrary data at the specified id after last.
        
        Warning This function does not check if the ID already exists in the container!
        Parameters
        id (int) – The ID to insert at.
        n (any) – The data to insert.
        last (any) – The data to insert after.
        

        Working code that inserts the command at the end.

        from typing import Optional
        import c4d
        
        pluginId = "1052844"        # official plugin ID
        
        def main() -> None:
            MainMenu = c4d.gui.GetMenuResource("M_EDITOR")
        
            for bcMenuId, bcMenu in MainMenu:
                #print (bcMenu[c4d.MENURESOURCE_SUBTITLE])
                if bcMenu[c4d.MENURESOURCE_SUBTITLE] == "IDS_EDITOR_RENDER":
                    for sub in bcMenu:
                        if (sub[1] == "IDM_RENDERAUSSCHNITT"):
                            print ("*** Insert after this ***")
                        
                            # Add my plugin
                            #bcMenu.InsDataAfter(c4d.MENURESOURCE_COMMAND, "PLUGIN_CMD_5159", sub)
                            bcMenu.InsData(c4d.MENURESOURCE_COMMAND, "PLUGIN_CMD_" + pluginId)    
                            c4d.gui.UpdateMenus()             
                
        if __name__ == '__main__':
            main()
        
        F 1 Reply Last reply Jun 7, 2023, 10:20 AM Reply Quote 0
        • F
          ferdinand @pim
          last edited by ferdinand Jun 7, 2023, 10:30 AM Jun 7, 2023, 10:20 AM

          Hey @pim,

          Using BaseContainer.InsDataAfter() does not make too much sense in this case, as you cannot get hold of the PyCObject that is wrapping the data entries you are after. The method has its place in other cases, but for menus, it is pretty much meaningless in Python. A visualization of a menu container looks like this:

          {
              MENURESOURCE_SUBTITLE: "My Menu"
              MENURESOURCE_COMMAND: [
                    ID_COMMAND_1, 
                    ID_COMMAND_2, 
                    ID_COMMAND_3
              ]
              MENURESOURCE_SUBMENU: [ 
                  {
                      MENURESOURCE_SUBTITLE: "Weeh, I am a sub menu!"
                      MENURESOURCE_COMMAND: [ ID_FOO, ID_BAR ]
                  },
                  {
                      MENURESOURCE_SUBTITLE: "Can you please be quiet up there?"
                      MENURESOURCE_COMMAND: [ ID_BOB, ID_IS, ID_YOUR, ID_UNCLE ]
                  }
              ]
          }
          

          As you can see here and in your own code, all entries in a menu that are commands, are stored under the same ID, namely MENURESOURCE_COMMAND. So, you would have to get hold of for example the data pointer for ID_COMMAND_1 to insert something after it. But you cannot in Python, you can only get the PyCObject data pointers for whole entries with BaseContainer.GetDataPointer. What you must do in your case, is rebuilding the whole submenu. Find a code example below.

          ⚠ WE DO NOT CONDONE PLUGINS THAT REORDER CONTENT IN THE NATIVE MENUS OF CINEMA 4D. In general, plugins should either appear under the main menu entry "Extensions" or have their own main menu entry, e.g., "XParticles". They should not inject themselves into native main menu entries, e.g., "Render". When it is unavoidable to do this (which it never is), you should at least NOT REORDER things. This applies only for publicly shipped plugins/scripts. In a private or studio environment, you are free to remodel all menus to your liking.

          Cheers,
          Ferdinand

          Result:
          508a395f-d3ac-4d9f-a748-1996b1cea556-image.png

          Code:

          """Demonstrates how to insert an item into an existing menu.
          
          WE DO NOT CONDONE PLUGINS THAT REORDER CONTENT IN THE NATIVE MENUS OF CINEMA 4D. In general, 
          plugins should either appear under the main menu entry "Extensions" or have their own main menu 
          entry, e.g., "XParticles". They should not inject themselves into native main menu entries, e.g.,
          "Render". When it is unavoidable to do this (which it never is), you should at least NOT REORDER 
          things:
          
              NOT OK  - "Render": [a, myNewStuff, ..., n]
              OK-ish  - "Render": [a, ..., n, myNewStuff]
          
          This has less a technical and more a UX reason, as we want to avoid having plugins going around that
          break the UX habits of users, i.e., the muscle memory of clicking the item that is "that much" pixels
          below the root of "Render".
          
          This applies only for publicly shipped plugins/scripts. In a private or studio environment, you are 
          free to remodel all menus to your liking.
          """
          import c4d
          
          def main() -> None:
              """Runs the example.
              """
              for _, data in c4d.gui.GetMenuResource("M_EDITOR"):
                  if not (isinstance(data, c4d.BaseContainer) and 
                          data[c4d.MENURESOURCE_SUBTITLE] == "IDS_EDITOR_RENDER"):
                      continue
          
                  # To do what you want to do, you will have to flush the container. When you are not careful
                  # and remove menu items which Cinema 4D expects to be present, or change menu structures
                  # that Cinema 4D considers to be given, you can crash Cinema 4D.
          
                  # Get all items in 'M_EDITOR' as a list of key-value pairs and bail when our insertion point
                  # is not in there. "IDM_RENDERAUSSCHNITT" as an insertion point alone is ambiguous, as we
                  # do not care about a MENURESOURCE_SUBTITLE with that value for example.
                  insertionPoint: tuple[int, str] = (c4d.MENURESOURCE_COMMAND, "IDM_RENDERAUSSCHNITT")
                  items: list[tuple[int, str | c4d.BaseContainer]] = [item for item in data]
                  if insertionPoint not in items:
                      continue
                  
                  # Insert a new command for the cube object after that value.
                  menuEntry: tuple[int, str] = (c4d.MENURESOURCE_COMMAND, "PLUGIN_CMD_5159")
                  items.insert(items.index(insertionPoint) + 1, menuEntry)
          
                  # Flush the container and write the new data.
                  data.FlushAll()
                  for cid, value in items:
                      data.InsData(cid, value)
          
              # Make sure to update menus sparingly, your code did this with every iteration of the loop.
              c4d.gui.UpdateMenus()
                  
          if __name__ == '__main__':
              main()
          

          MAXON SDK Specialist
          developers.maxon.net

          P 1 Reply Last reply Jun 7, 2023, 11:33 AM Reply Quote 0
          • P
            pim @ferdinand
            last edited by Jun 7, 2023, 11:33 AM

            @ferdinand
            I run the script, but it hangs cinema 4d.
            After some debugging, I could see that the insertion point was found and that it was inserted in items.
            I am not sure what statement hangs cinema 4d.

            I am using R2023.2.1 on a Windows 10 Pro 22H2.
            d55cbe3b-bde3-460d-8c7f-d82e92fde5fc-image.png

            F 1 Reply Last reply Jun 7, 2023, 12:18 PM Reply Quote 0
            • F
              ferdinand @pim
              last edited by ferdinand Jun 7, 2023, 12:20 PM Jun 7, 2023, 12:18 PM

              Hey @pim,

              Well, as I stated in the code above, doing this can crash your Cinema 4D as what you want to do is not intended. For me this is not crashing, but I am not surprised that it does on another machine. You could try to be more careful by calling data.RemoveData() (instead of data.FlushAll()) to only clear out the things that you must remove, i.e., stuff in MENURESOURCE_COMMAND, but then you have also to fiddle the container indexing into order. You could also try to defer things to C4DPL_BUILDMENU, as less things rely there on a certain state of a menu. But be warned, I have also experienced crashes from there, when I was too drastic in my changes.

              I understand that people do not like this answer, but what you want to do is simply out of scope of support. I have already helped you here more than I probably should have.

              Cheers,
              Ferdinand

              MAXON SDK Specialist
              developers.maxon.net

              1 Reply Last reply Reply Quote 0
              • P
                pim
                last edited by Jun 7, 2023, 4:50 PM

                I really do appreciate all your effort and I will take your warning seriously and create my own menu Tab.
                Thanks again.

                Regards,
                Pim

                1 Reply Last reply Reply Quote 0
                • i_mazlovI i_mazlov referenced this topic on Jun 7, 2023, 4:55 PM
                1 out of 7
                • First post
                  Last post