Adding Multiple Keyboard Shortcuts within a Python Script



  • I am new to Cinema, having come from a different 3D software, and have been playing around with the Python API to create various scripts replicating certain functionality in my original software. These scripts have been very simple, typically calling/toggling commands, and I have been binding them to different keyboard shortcuts using the 'Customise Commands' Manager.

    My problem is that there is a script I'd like to create that would ideally map different commands to individual key-presses from within the script. Short of creating multiple smaller scripts and binding each of them to individual keys, I was wondering if it was possible to assign shortcuts from within the script itself? Obviously (I'd assume?), this singular script would have to run at startup.

    For example:

    if  a_key is pressed:
        # do_something
    
    elif a_different_key is pressed:
        # do_something_else
    

    Any detailed answers would be appreciated as I'm still fairly new to Python.
    Thanks!


  • Global Moderator

    Hi @Hexbob6, first of all, welcome in the plugincafe community!

    I see multiple questions in your post. So first one how to read the input of the user?
    For this purpose the sdk offer 2 functions c4d.gui.GetInputState and c4d.gui.GetInputEvent:
    here you got an example with the difference beetwin all possibilities:

    import c4d
    
    # Main function
    def main():
        # Check for specific key pressed
        bc = c4d.BaseContainer()
        if c4d.gui.GetInputState(c4d.BFM_INPUT_KEYBOARD, c4d.KEY_F10,bc):
            if bc[c4d.BFM_INPUT_VALUE] == 1:
                print "F10 PRESSED"
            else:
                print "F10 NOT PRESSED"
    
        # Check only modifer Keys (ctrl / shift / alt)
        bc = c4d.BaseContainer()
        if c4d.gui.GetInputState(c4d.BFM_INPUT_KEYBOARD,c4d.BFM_INPUT_CHANNEL,bc):
            if bc[c4d.BFM_INPUT_QUALIFIER] & c4d.QSHIFT:
                print "SHIFT PRESSED"
            if bc[c4d.BFM_INPUT_QUALIFIER] & c4d.QCTRL:
                print "CONTROL PRESSED"
            if bc[c4d.BFM_INPUT_QUALIFIER] & c4d.QALT:
                print "ALT PRESSED"
    
        # Check for a any key pressed
        bc = c4d.BaseContainer()
        if c4d.gui.GetInputEvent(c4d.BFM_INPUT_KEYBOARD, bc):
            key = bc[c4d.BFM_INPUT_CHANNEL]
            if key == c4d.NOTOK:
                return
    
            print c4d.gui.Shortcut2String(bc[c4d.BFM_INPUT_QUALIFIER], bc[c4d.BFM_INPUT_CHANNEL])
    
    # Execute main()
    if __name__=='__main__':
        main()
    

    And your second question, is how to add a shortcut?
    For this purpose, you get the c4d.gui.AddShortcut function, and here an example which registers the current script (it must be saved somewhere) to CTRL+F11

    import c4d
    import os
    
    def getCurrentScriptId():
        fPath = __file__
        if __file__ == "scriptmanager" or not os.path.exists(fPath):
            return False
    
        plugins = c4d.plugins.FilterPluginList(c4d.PLUGINTYPE_COMMAND, True)
        for plugin in plugins:
            if plugin.GetName() == os.path.basename(os.path.splitext(fPath)[0]):
                return plugin.GetID()
    
        return False
    
    # Main function
    def main():
        scriptId = getCurrentScriptId()
        if not scriptId:
            print "unable to find script ID"
            return
    
        # We want to add this shortcut
        qualifier = c4d.QCTRL
        key = c4d.KEY_F11
    
        # Check if the shortCut is already use or not
        for x in xrange(c4d.gui.GetShortcutCount()):
            shortcutBc = c4d.gui.GetShortcut(x)
    
            # Check if shortcut is already define. Please read
            # https://developers.maxon.net/docs/Cinema4DPythonSDK/html/modules/c4d.gui/index.html?highlight=getshortcut#c4d.gui.AddShortcut
            # for more information about how qualifier and key are stored in the basecontainer.
            if shortcutBc[0] == qualifier and shortcutBc[1] == key:
                print "Shortcut {} is already used for command ID: {}".format(c4d.gui.Shortcut2String(qualifier, key), shortcutBc[c4d.SHORTCUT_PLUGINID])
                return
    
        # Add our shortcut
        bc = c4d.BaseContainer()
        bc.SetInt32(c4d.SHORTCUT_PLUGINID, scriptId)
        bc.SetLong(c4d.SHORTCUT_ADDRESS, 0)
        bc.SetLong(c4d.SHORTCUT_OPTIONMODE, 0)
    
        # Define our key, again please read
        # https://developers.maxon.net/docs/Cinema4DPythonSDK/html/modules/c4d.gui/index.html?highlight=getshortcut#c4d.gui.AddShortcut
        # for more information about how qualifier and key are stored in the basecontainer.
        bc.SetLong(0, qualifier);
        bc.SetLong(1, key);
        return c4d.gui.AddShortcut(bc)
    
    
    if __name__=='__main__':
        main()
    

    If you have any question please, let me know!
    Cheers,
    Maxime.



  • Hi @m_adam , thank you for the detailed reply! You're right there are a couple of parts to my question, so it might be helpful if I give a simplified example of what I'm trying to do.

    Essentially, I want to create a script that allows me to switch between different camera views (top, right, left, etc) by pressing the number keys. Whilst it would be easy to create a number of '.py' files using c4d.CallCommand() and then assign them all manually in the 'Customise Commands' manager, I was hoping I'd be able to create a script that runs for the duration that Cinema is open and reads the user's keypresses, returning c4d.CallCommand() when specified keys are pressed.

    Based on the first part of your answer I'd assume I'd need something along the lines of this... but I can't seem to get it to work.

    import c4d
    
    def main():
    
        bc = c4d.BaseContainer()
    
            # If the '7' key is pressed, enter 'Top' View
            if c4d.gui.GetInputState(c4d.BFM_INPUT_KEYBOARD, c4d.KEY_7, bc):
                if bc[c4d.BFM_INPUT_VALUE] == 1:
                    c4d.CallCommand(12083)  # Enters 'Top' View
    
            # If the '3' key is pressed, enter 'Right' View
            elif c4d.gui.GetInputState(c4d.BFM_INPUT_KEYBOARD, c4d.KEY_3, bc):
                if bc[c4d.BFM_INPUT_VALUE] == 1:
                     c4d.CallCommand(12080)  # Enters 'Right' View
    
    if __name__=='__main__':
        main()
    

    Any ideas? Thanks again, Maxime! :)


  • Global Moderator

    Hi,

    please let me mention one thing before we dive into the technical topic again. In general we discourage people from double posting into multiple forums. While we understand your desire to increase visibility and chances for input, please also see it from the opposite perspective. Chances increase drastically people are investing time even after the question has already been answered in another forum. Even more so, if there are no references to these other threads.
    And to be honest, in your case I was even more surprised to see an identical thread being opened in C4DCafe after Maxime provided you with an answer here. Even if his first answer didn't lead to the result you might have expected, somebody started working on your thread and also clearly expressed the will to go further and deeper.
    As I said, I hope you don't mind me addressing this.

    Back to your actual request:

    From your snippets it looks really as if you only want to do one CallCommand() per assigned key. As mentioned in C4DCafe, I'm wondering, why you are not using the Customize Commands dialog to directly assign those keys.

    But lets assume, you want to do more complex things via the assigned keys. Actually a script in Script Manager is the wrong way to do it. The thing is, a script is a fire and forget thing, it gets called, executes and that's it. There's no instance in a script, that could be listening to key presses.
    Technical background: Cinema 4D can be extended via multiple different types of plugins. One of these is a so called CommandData plugin, which is basically the way all those commands you see in Cinema 4D (in menus, on palette buttons or inside the Commander (Shift-C)) have been implemented. And scripts in Script Manager are actually a very simplified version of CommandData plugins, in a way that these are basically one command per script.

    Now, implementing a CommandData plugin is not much more complicated than implementing a script. The script code basically ends up in the CommandData's Execute() function. I recommend to take a look at our examples (also included in the Python SDK documentation archive, you have probably already downloaded). See for example Py-TextureBaker or Py-MemoryViewer. Don't let yourself confuse by the amount of code of the latter, rather start reading bottom up (RegisterCommandPlugin and the implementation of just the class derived from CommandData). A minimum CommandData is just that, register and have this class derived from CommandData with an Execute() function containing roughly your script's code.

    The thing is, opposite to a script and instead of implementing CommandData.Execute() you can also implement GetSubContainer() and ExecuteSubID(). By this you can have several commands in one single CommandData plugin, which can then be assigned with different keyboard shortcuts each, either in code or in Customize Commands manager..

    I hope, this helps and can get you on the right track. If you have mire questions, don't hesitate to ask. On different topics please open a new thread per topic, though.

    Cheers,
    Andreas


  • Global Moderator

    Hi @Hexbob6

    I'm wondering if your question has been answered. If so, please mark this thread as solved.

    Cheers,
    Maxime.