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

    Remove Shortcut failed

    Cinema 4D SDK
    2
    3
    435
    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.
    • DunhouD
      Dunhou
      last edited by

      Hello :

      In Short :
      Failed to remove a shortcut with python like : remove Shift + 1 for move camera and add it to my own tool .

      More Descriptions :
      I am trying to remove a C4D shortcut and add it to my own tools , but it didn't work in my plugins :
      8b38a501-d142-4eef-b69b-3953302f2cdb-image.png
      I want my tool has 4 shortcut , like :

      1. Ctrl + Shift + Alt + 1 : Do A
      2. Ctrl + Shift + 1 : Do B
      3. Shift + 1 : Do C
      4. Ctrl + 1 : Do D
        But some of them like Ctrl + 1 has employed . Here is little codes :
      #--------------------------------------------------------------------
      # Plugin Class
      #--------------------------------------------------------------------
      ###  ==========  Execute  ==========  ###
      #? 缓存灯光
      class CacheLight(LightSolo):
          def __init__(self) -> None:
              pass
          # Override - Called when the plugin is selected by the user.
          def Execute(self, doc=c4d.documents.BaseDocument):
              self.CacheState()
              return True
      
      #? 重置灯光   
      class ResetLight(LightSolo):
          def __init__(self) -> None:
              pass
          # Override - Called when the plugin is selected by the user.
          def Execute(self, doc=c4d.documents.BaseDocument):
              alllights = self.getAllLights()
              for light in alllights:
                  self.ResetState(light) # 重置灯光
              return True
      
      #? 灯光组solo  
      class SoloLightGroup(LightSolo):
          def __init__(self) -> None:
              pass
          # Override - Called when the plugin is selected by the user.
          def Execute(self, doc=c4d.documents.BaseDocument):
              self.SoloLightGroup() # 灯光组solo
              return True
      
      #? 灯光solo   
      class SoloLights(LightSolo):
          def __init__(self) -> None:
              pass
          # Override - Called when the plugin is selected by the user.
          def Execute(self, doc=c4d.documents.BaseDocument):
              self.SoloLights() # 灯光solo
              return True
      #--------------------------------------------------------------------
      # Shortcut.
      #--------------------------------------------------------------------  
      def RemoveShortcut(qualifier,key):
          """
          Remove Shortcut by given qualifier and key    
              
          Args:
              qualifier (int): modifier key 
              key (int): ascii code of key
          """
          for x in range(c4d.gui.GetShortcutCount()):
              shortcutBc = c4d.gui.GetShortcut(x)
              # Check if shortcut is stored in the basecontainer.
              if shortcutBc[0] == qualifier and shortcutBc[1] == key:
                  index = x    
          try:
              c4d.gui.RemoveShortcut(index)
          except:
              print ("Shortcut {} remove failed".format(c4d.gui.Shortcut2String(qualifier, key)))
              return False
          
      def AddShortCut(qualifier,key,pluginID):
          """
          Add Shortcut by given qualifier and key to given ID  
              
          Args:
              qualifier (int): modifier key 
              key (int): ascii code of key
              pluginID (int): plugin ID
          """
          for x in range(c4d.gui.GetShortcutCount()):
              shortcutBc = c4d.gui.GetShortcut(x)
              # Check if shortcut is stored in the basecontainer.        
              if shortcutBc[0] == qualifier and shortcutBc[1] == key:
                  if shortcutBc[c4d.SHORTCUT_PLUGINID] == pluginID:
                      print ("Shortcut {} is already Used for Command ID: {}".format(c4d.gui.Shortcut2String(qualifier, key), shortcutBc[c4d.SHORTCUT_PLUGINID]))
                      return
              
          # Define shortcut container
          bc = c4d.BaseContainer()
          bc.SetInt32(c4d.SHORTCUT_PLUGINID, pluginID)
          bc.SetLong(c4d.SHORTCUT_ADDRESS, 0)
          bc.SetLong(c4d.SHORTCUT_OPTIONMODE, 0)
          # User defined key
          bc.SetLong(0, qualifier)
          bc.SetLong(1, key)
          return c4d.gui.AddShortcut(bc)
      
      ###  ==========  Register Plugin  ==========  ###
      if __name__ == "__main__":
                  
          key = 49 # num 1 next ~
          # remove shortcut reg with Cinema 4D for [move camera]
          RemoveShortcut(7,key)
          RemoveShortcut(4,key)
          RemoveShortcut(1,key)
          RemoveShortcut(3,key)    
          # Add own shortcut
          AddShortCut(7,key,SUB_PLUNIGID_CACHE)
          AddShortCut(4,key,SUB_PLUNIGID_SOLOG)
          AddShortCut(1,key,PLUNIGID)
          AddShortCut(3,key,SUB_PLUNIGID_RESET)
          c4d.EventAdd()
          # Plugins Register
          iconfile = c4d.bitmaps.BaseBitmap()
          iconfile.InitWith(ICONPATH)
          c4d.plugins.RegisterCommandPlugin(
              id = SUB_PLUNIGID_CACHE,
              str = PLUNGINNAME + "Cache State",
              info = c4d.PLUGINFLAG_HIDEPLUGINMENU, # hide
              help = "Cache All Light Objects State",
              dat = CacheLight(),
              icon = iconfile
         )
          c4d.plugins.RegisterCommandPlugin(
              id = SUB_PLUNIGID_RESET,
              str = PLUNGINNAME + "Reset State",
              info = c4d.PLUGINFLAG_HIDEPLUGINMENU, # hide
              help = "Reset All Light Objects State",
              dat = ResetLight(),
              icon = iconfile
         )
          c4d.plugins.RegisterCommandPlugin(
              id = SUB_PLUNIGID_SOLOG,
              str = PLUNGINNAME + "Solo Group",
              info = 0, # c4d.PLUGINFLAG_COMMAND_STICKY,
              help = "Solo Selected Light Objects in Group",
              dat = SoloLightGroup(),
              icon = iconfile
         )
          c4d.plugins.RegisterCommandPlugin(
              id = PLUNIGID,
              str = TITLE,
              info = c4d.PLUGINFLAG_HIDEPLUGINMENU, # hide
              help = INFO,
              dat = SoloLights(),
              icon = iconfile
         )   
      

      https://boghma.com
      https://github.com/DunHouGo

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

        Hello @dunhou,

        Thank you for reaching out to us, and thank you for the clear posting, much appreciated. There are multiple reasons why removing shortcuts does not work in your example.

        1. The major reason is how you handle your index variable in RemoveShortcut(int, int).
                if shortcutBc[0] == qualifier and shortcutBc[1] == key:
                    index = x  # You define #index only here.
            try:
                # Will raise an AttributeError for 'index' when the condition above never ran.
                c4d.gui.RemoveShortcut(index) 
            except:
                ...
        
        1. You did also not respect some of the intricacies of the shortcut containers, which is the triggering factor for your condition never being met.

        Find a commented example of your RemoveShortcut at the end of the posting. I turned off the actual removal of shortcuts in the example so that running it will not break your Cinema 4D. We might also add a small example and some other documentation to our SDK for shortcuts, because currently the makeup of a shortcut container is not too well documented.

        Cheers,
        Ferdinand

        The output:

        RemoveShortcut([c4d.QUALIFIER_SHIFT, '1']) = 4
        RemoveShortcut([c4d.QUALIFIER_CTRL, c4d.QUALIFIER_SHIFT, '1']) = 434
        RemoveShortcut(['M', 'S']) = 194
        RemoveShortcut([c4d.QUALIFIER_CTRL, 'A']) = 53
        RemoveShortcut([c4d.QUALIFIER_CTRL, 'A'], managerId=nodeEditorManager) = 390
        RemoveShortcut([c4d.QUALIFIER_CTRL, 'A'], pluginId=nodeEditorSelectAllNodes) = 390
        

        The code:

        """Demonstrates how to find and remove shortcuts in Cinema 4D.
        
        Shortcuts are tied to how Cinema 4D internally handles sequences of key strokes (called input
        events in Cinema 4D). The example showcases besides the general data structures with which these
        events are communicated ways to disambiguate shortcuts for different input contexts, e.g., pressing
        'CTRL + A' in the object manager and 'CTRL + A' in the node editor.
        
        I have commented out the actual removing of shortcuts int the example, so that the example can be
        run without 'bricking' a Cinema 4D installation.
        
        The example is also unable to handle mouse key shortcuts, e.g., SHIFT + ALT + LMB. It would be
        entirely possible to do this, but I have fashioned RemoveShortcut() and how it interprets its
        #keySequence argument in such way that this is not possible, because it distinguishes qualifier 
        inputs from value inputs by type. One would have to simply make the #keySequence data structure
        a bit more complex to also support mouse buttons.
        """
        
        import typing
        import c4d
        
        def RemoveShortcut(keySequence: list[typing.Union[int, str]], 
                           managerId: typing.Optional[int] = None,
                           pluginId: typing.Optional[int] = None) -> bool:
            """
            Finds a shortcut index by the given #keySequence and optionally #managerId and/or #pluginId.
        
            I have commented out the actual removing of shortcuts int the example, so that the example can be
            run without 'bricking' a Cinema 4D installation.
                
            Args:
                keySequence: A sequence of keyboard inputs, e.g., [c4d.QUALIFIER_SHIFT, '1'].
                managerId (optional): The manager context of the shortcut to find or remove.
                pluginId (optional): The plugin ID of the plugin invoked by the shortcut.
            
            Returns:
                The success of the removal operation.
        
            Raises:
                RuntimeError: On illegal key symbols.
                RuntimeError: On non-existing shortcut key sequences.
            """
            # The input data #keySequence is fashioned in a user friendly way, e.g.,
            #
            #   [c4d.QUALIFIER_SHIFT, c4d.QUALIFIER_ALT, "S", "T"]
            #
            # for the shortcut SHIFT + ALT + S ~ T. We must bring this into a form which what aligns how
            # such data is handled internally.
            #
            #   1. All qualifiers are ORed together.
            #   2. There can be multiple successive stroke events, e.g., M ~ S.
        
            # The raw values of the input are,
            #
            #   [1, 4, "S", "T"]
            #
            # which are being transformed into this:
            #
            #  [  (5, 83),    # (qualifier = 1 | 4 = 5, key = ASCII_VALUE("S"))
            #     (0, 84)     # (qualifier = 0        , key = ASCII_VALUE("T")
            #  ]
        
            # The list of key stroke modifier-key tuples.
            strokeData: list[tuple[int, int]] = []
            # A variable to OR together the qualifiers for the current key stroke.
            currentModifiers: int = 0
        
            for key in keySequence:
                # Extend a modifier key sequence, e.g., SHIFT + ALT + CTRL
                if isinstance(key, (int, float)):
                    currentModifiers |= key
                # A character key was found, append an input event.
                elif isinstance(key, str) and len(key) == 1:
                    strokeData.append((currentModifiers, ord(key.upper())))
                    currentModifiers = 0
                # Something else was found, yikes :)
                else:
                    raise RuntimeError(f"Found illegal key symbol: {key}")
        
            # Now we can iterate over all shortcuts in Cinema 4D.
            for index in range(c4d.gui.GetShortcutCount()):
                # Get the shortcut at #index.
                bc: c4d.BaseContainer = c4d.gui.GetShortcut(index)
        
                # The shortcut container #bc is structured as follows:
                #
                #   Container Access             ID     Description
                #   bc[0]                          0    Qualifier sequence of the first key stroke.
                #   bc[1]                          1    ASCII value of the first key stroke.
                #   bc[10]                        10    Qualifier sequence of the second key stroke (optional).
                #   bc[11]                        11    ASCII value of the second key stroke (optional).
                #   [...]
                #   bc[990]                      990    Qualifier sequence of the 99th key stroke (optional).
                #   bc[991]                      991    ASCII value of the 99th key stroke (optional).
                #   bc[c4d.SHORTCUT_PLUGINID]   1000    The plugin id of the thing triggered by the shortcut. 
                #   bc[c4d.SHORTCUT_ADDRESS]    1001    The the manager context of the shortcut.           
                #   bc[c4d.SHORTCUT_OPTIONMODE] 1002    If the shortcut opens the plugins options dialog.
                #
                # So, for multi keystroke events, e.g., A ~ B, the strokes are being placed with a stride
                # of 10 between the container indices [0, 990]. Also placed in this stride is qualifier OR
                # sum for each stroke. Here is the content of two real life containers for clarity:
                #
                # A container for pressing the key "0"(48). There are no qualifiers here.
                #
                #   0: 0
                #   1: 48
                #   1000: 200000084
                #   1001: 0
                #   1002: 0
                #
                # A container for pressing "M"(77) and then "S"(83).  There are no qualifiers here.
                #
                #   0: 0
                #   1: 77
                #   10: 0
                #   11: 83
                #   1000: 431000015
                #   1001: 0
                #   1002: 0
        
                # We test if #strokeData matches #bc.
                isMatch: bool = True
                for i, (qualifier, key) in enumerate(strokeData):
                    idQualifier: int = i * 10 + 0
                    idKey: int = i * 10 + 1
                    # A qualifier + key stroke did not match, we break out.
                    if bc[idQualifier] != qualifier or bc[idKey] != key:
                        isMatch = False
                        break
                
                # Something in the key sequence did not match with #strokeData, so we try the next shortcut
                # container provided by the outer loop.
                if not isMatch:
                    continue
                
                # We could do here some additional tests, as shortcut key strokes do not have to be unique,
                # i.e., there could be two short-cuts "Shift + 1" bound to different manager contexts.
                if pluginId is not None and bc[c4d.SHORTCUT_PLUGINID] != pluginId:
                    continue
                if managerId is not None and bc[c4d.SHORTCUT_ADDRESS] != managerId:
                    continue
                
                # All tests succeeded, the shortcut at the current index should be removed, we instead just
                # return the index to make this example a bit less volatile.
        
                # return c4d.gui.RemoveShortcut(index)
                return index
        
            # All shortcuts have been traversed and no match was found, the user provided a key sequence
            # which is not a shortcut.
            raise RuntimeError(f"The shortcut sequence {keySequence} was not found.")
        
        def main() -> None:
            """Runs the example.
            """
            # Remove/find the shortcut index for "Shift + 1"
            print (f"{RemoveShortcut([c4d.QUALIFIER_SHIFT, '1']) = }")
            # Remove/find the shortcut index for "Shift + Alt + 1"
            print (f"{RemoveShortcut([c4d.QUALIFIER_CTRL, c4d.QUALIFIER_SHIFT, '1']) = }")
            # Remove/find the shortcut index for "M ~ S"
            print (f"{RemoveShortcut(['M', 'S']) = }")
        
            # To filter duplicate shortcuts, either the manager context or plugin ID must be used. Most plugin
            # IDs are public, but all most no manager IDs are public. The only way to figure them out is to
            # reverse engineer the GetShortcut() containers.
        
            nodeEditorManager: int = 465002211    # The ID of the node editor manager.
            nodeEditorSelectAllNodes: int = 465002309  # The plugin ID of the "Select All" (nodes) command.
        
            # Remove/find the shortcut index for "CTR + A". There are multiple shortcuts for this sequence,
            # this will return the first match.
            print (f"{RemoveShortcut([c4d.QUALIFIER_CTRL, 'A']) = }")
            # Remove/find the shortcut index for "CTR + A" in the Node Editor context. 
            print (f"{RemoveShortcut([c4d.QUALIFIER_CTRL, 'A'], managerId=nodeEditorManager) = }")
            # Remove/find the shortcut index for "CTR + A" which invokes the plugin with the ID 465002309.
            print (f"{RemoveShortcut([c4d.QUALIFIER_CTRL, 'A'], pluginId=nodeEditorSelectAllNodes) = }")
        
        if __name__ == "__main__":
            main()
        

        MAXON SDK Specialist
        developers.maxon.net

        DunhouD 1 Reply Last reply Reply Quote 0
        • DunhouD
          Dunhou @ferdinand
          last edited by

          @ferdinand
          Thank you for detailed explain . It's great to add a shortcut container document update and small examples , actually , more examples on sdk or Github is really helpful.

          After reading I try to re-write it and add some functions like add shortcut or check plugin's shortcut list, It's all worked as expected . Shortcut BaseContainer explain is that great to move on .

          By the way , from those examples , I learned and try to keep code style as easy to read and repair like yours at the same time . So much appreciated.👏

          https://boghma.com
          https://github.com/DunHouGo

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