Python, tool attributes and hotkeys



  • Hello,

    Python beginner here, my goal is to write a script or a plugin to interact with some (active) tool attributes through a predefined set of hotkeys. In short, context-sensitive hotkeys. As a bonus goal, I would like to add some settings in the Preferences window to change the hotkeys.

    Until now I've managed to write simple python scripts ( but you can call it macros ), like these lines to change bevel mode f.e. :

        if c4d.IsCommandChecked(431000015) is True : 
            if tool()[c4d.MDATA_BEVEL_MASTER_MODE] == 0:
                tool()[c4d.MDATA_BEVEL_MASTER_MODE] = 1
            else:
                tool()[c4d.MDATA_BEVEL_MASTER_MODE] = 0
            
        c4d.EventAdd()
    

    It kind of works but it doesn't affect the active tool, only the UI.

    And that's all for the moment. The SDK isn't really useful since I don't know where to start and how implement this, plus I guess there may be some specific constraints due to the interactive part.

    So I would like to get some clues on the best way to tackle this. I've watch many C4D Python tutorials, look through the docs, the Python SDK and read few scripts but it doesn't make sense yet. Like baseContainer, what is this thing and why I have to call it to interact with a tool ? ( I'm really a beginner )

    From what I've read what I want to do is not possible with a simple script, I should rather write a plugin to have the code running in background to evaluate tool activation and keystrokes. Is that correct ? Is it why the script doesn't trigger the active tool ?

    Thanks,



  • I'm not quite sure what you are looking for... the code does change the settings in the tool, not just "the UI", provided you define the function tool() which the script log automatically does.
    If you expect the default tool settings to change: no, that is not what the script does.
    If you expect the script to trigger the tool (cause the tool to execute): no, the script only changes the settings, you still have to click Apply or New Transform.

    To execute the Bevel tool, you can use SendModelingCommand instead of the stuff the Script Log recommends:

    import c4d
    from c4d import gui, utils
    
    def main():
        doc.SetMode(c4d.Mpolygons)
        doc.StartUndo()
        doc.AddUndo(c4d.UNDOTYPE_CHANGE, op)
        settings = c4d.BaseContainer()      # Settings
        settings[c4d.MDATA_BEVEL_MASTER_MODE] = c4d.MDATA_BEVEL_MASTER_MODE_CHAMFER
        settings[c4d.MDATA_BEVEL_OFFSET_MODE] = c4d.MDATA_BEVEL_OFFSET_MODE_FIXED
        settings[c4d.MDATA_BEVEL_RADIUS]      = 10.0
    
        res = utils.SendModelingCommand(command = c4d.ID_XBEVELTOOL,
                                        list = [op],
                                        mode = c4d.MODELINGCOMMANDMODE_POLYGONSELECTION,
                                        bc = settings,
                                        doc = doc)
        doc.SetAction(c4d.ID_MODELING_MOVE)
        doc.EndUndo()
        c4d.EventAdd()
    
        if res is False:
            print "Something went wrong."
        elif res is True:
            print "Command successful."
        elif isinstance(res, list):
            print "Oops, you got new objects?"
    
    # Execute main()
    if __name__=='__main__':
        main()
    

    Regarding the hotkeys: I have a hunch that you want to use C4D in a way that is not really supported. A keyboard shortcut triggers a script or a tool or a command - hotkeys are global and depend on the command behind it. You can make them context sensitive by checking the current tool in the called script, and then react accordingly, but that still requires that you bind that key to that command, not the other way round.

    You may be able to catch key press events from the messaging system and evaluate them yourself, but that would require much programming experience and API knowledge, and you will probably break the shortcut system.

    If you just want to define your own hotkeys, you will need to program a command plugin with the flag PLUGINFLAG_COMMAND_HOTKEY set. That's not context sensitive, again, you would have to program any context dependencies yourself. At least you wouldn't need to have a preferences plugin to assign them; any script or plugin can have its keyboard shortcut assigned on the Customize Command window.

    Provided I even understand correctly what you want... if you could describe an actual usecase...?


    Learn more about Python for C4D scripting:
    https://www.patreon.com/cairyn



  • (It just came to mind that you may not mean "hotkey" but "keyboard shortcut". A hotkey is a special kind of keyboard shortcut that takes effect while it is pressed, so it switches on when you press and keep pressing, and switches off when released. The hotkeys are the ones that require the PLUGINFLAG_COMMAND_HOTKEY flag. For other keyboard shortcuts that trigger an action on release only you can use a simple script where applicable and don't necessarily require a command plugin.)



  • hi,

    welcome to our forum !!

    If i understood, you want to reproduce the behavior of a tool but with a shortcut. You are going to face some problem.
    First, the tool work that way, it does do the changes and add an undo step in the undo stack. Than, if you change the value of the bevel for example, it call the undo, and do another bevel with the new parameter.

    First i would suggest to use the cinema4D shortcut system. You will avoid conflict in your shortkeys.
    What you can do is having your own system to define those shortkey (replacing the shortkey manager)

    In my opinion you can have one commandData and assign one shortkey to that command Data. And that command data can react depending on what tool is activated. Or Create one CommandData per shortcut you want.

    As @Cairyn said you have to use SendModelingCommandData to apply those changes. You can create the basecontainer for you command from scratch or retrieve it from the tool itself.

    this example should give you some idea to do it. It's not ideal.

    import c4d
    from c4d import gui
    # Welcome to the world of Python
    
    
    def tool():
        return c4d.plugins.FindPlugin(doc.GetAction(), c4d.PLUGINTYPE_TOOL)
    
    # Main function
    def main():
        
        # Retrieves the active Object.
        op = doc.GetActiveObject()
        if op is None:
            return 
        
        # Check in the undo stack if there's already a step for that object
        # we are going to assume that if there's a UNDOTYPE_CHANGE step, it's because the bevel tool have been applied once
        lastState =  doc.FindUndoPtr(op, c4d.UNDOTYPE_CHANGE)
        # if there's no previous step, we just return doing nothing
        if lastState == None:
            return
        
        # we call DoUndo to undo the previous bevel
        doc.DoUndo()
        
        # As the DoUndo destroy the object and recreate it, we assign again the op variablt with the active object
        op = doc.GetActiveObject()    
        if op is None:
            return 
        
        # we change he parameter directly on the tool
        if c4d.IsCommandChecked(c4d.ID_XBEVELTOOL) is True : 
            if tool()[c4d.MDATA_BEVEL_MASTER_MODE] == 0:
                tool()[c4d.MDATA_BEVEL_MASTER_MODE] = 1
            else:
                tool()[c4d.MDATA_BEVEL_MASTER_MODE] = 0
    
        # retrieves the baseContainer of the activeTool
        bc = doc.GetActiveToolData()
        
        if bc is None:
            return
        
        # starts the undo block
        doc.StartUndo()
        # Adds an undo step in the stack 
        doc.AddUndo(c4d.UNDO_CHANGE, op)
        # send the modeling command
        c4d.utils.SendModelingCommand(c4d.ID_XBEVELTOOL, [op],c4d.MODELINGCOMMANDMODE_EDGESELECTION, bc)
        # close the undo block.
        doc.EndUndo()
     
        # Adds an event in the update stack of cinema4D.        
        c4d.EventAdd()
    
    # Execute main()
    if __name__=='__main__':
        main()
    

    Maybe you could create your own tooldata to drive all tools inside cinema4D. I'm not really sure how this could work, but that could simplify your life =)

    don't be afraid to ask questions if you got any, and to open a new thread for each :)

    Cheers,
    Manuel



  • Hello guys,

    Thank you for your detailed answers and sorry for the delay, I was on holiday.

    @Cairyn
    Sorry for the misunderstanding, in my mind hotkeys = shortcuts, I didn't want to make any particular distinction. What you described is also called Sticky Keys in some softwares.
    To answer your questions : I want to be able to modify tool settings / attributes while the tool is enabled AND in interactive mode. Since I don't want to bother with gazillions of shortcuts and multiple keyboards, it has to be context sensitive so I can use the same set of keys with many tools.

    But a picture is worth a thousand words, so I made a little demo of what I want to achieve in C4D : Modal Bevel Shortcuts demo

    As you can see, the shortcuts allows to modify tool settings in real time, without loosing the ability to enter values manually in the attributes fields. What you don't see is that I can use the same set of keys with many tools and in the same fashion ( one key to toggle an option, two other to add / sub values like segments, number of clones etc. ). This software is context-sensitive by design at many levels and allow this kind of customisation without scripting.

    @m_magalhaes
    Thank you very much for the detailed answer and the script example. I have to take a deeper look at it but it works, and I've made two other scripts from it to simulate what I show in the link above. Please take a look : Cinema 4D - Modal Bevel Shortcuts POC #1

    As you can see

    • the part with the shortcuts works : I can press keys and alter active tool settings as expected.
    • but the tool is no longer interactive, so when I want to alter the bevel radius, it doesn't work. I have to undo and restart the bevel.

    So these results are encouraging but there is room for improvements :

    • Is there is a way to alter the current tool settings values without loosing the tool interactivity ? An access to some kind of "tool buffer" ?
    • Let's say I want to add function for others tools in the same way. Wouldn't the script become too slow at some point if there is many if c4d.IsCommandChecked() commands ?
    • Is there is a way to gather all the commands in one file / script but output as many commands as shortcuts needed ? Right now I have one script per command, it's a bit clunky.
      I guess plugin and command registering is the answer ?

    Thanks,



  • @John_Do said in Python, tool attributes and hotkeys:

    @Cairyn
    Sorry for the misunderstanding, in my mind hotkeys = shortcuts, I didn't want to make any particular distinction. What you described is also called Sticky Keys in some softwares.

    Yeah, the nomenclature for keyboard shortcuts is unfortunately all over the place. I know Sticky Keys as the type of behaviour where you type a key and release it, but the key still registers as pressed, until it is pressed a second time. This is the case for e.g. Windows' "easy access" for qualifier keys. (No idea whether this is still called that; I'm on a German system.)

    But sadly every system, every application, and every user uses the terms as they just come to mind, so it's fairly worthless now without an explanatio every time. I was just pointing out the "hotkey" as Cinema 4D actually uses this term in its API.

    To answer your questions : I want to be able to modify tool settings / attributes while the tool is enabled AND in interactive mode. Since I don't want to bother with gazillions of shortcuts and multiple keyboards, it has to be context sensitive so I can use the same set of keys with many tools.

    But a picture is worth a thousand words, so I made a little demo of what I want to achieve in C4D : Modal Bevel Shortcuts demo

    I understand what you want to do... I suppose as long as you reserve the necessary keys for your scripts/tools, it's possible to get the effect. I was actually thinking along the lines "if you want these keys to work only when the tools are active, and have their original meaning if something else is active" which is a much more difficult goal - it would force you to remember the original function in some way, and jump there if your target tools are not active. I actually have no solution for that, as Cinema does not have an additional context sensitive layer for keyboard shortcuts that depends on tools. (I often wish for that.)

    I might offer the functions to swap out the keyboard shortcuts dynamically, but you probably don't want that and have found these already anyway ;-)

    • Is there is a way to gather all the commands in one file / script but output as many commands as shortcuts needed ? Right now I have one script per command, it's a bit clunky.
      I guess plugin and command registering is the answer ?

    You can create one file that registers several command plugins. You need a separate ID from the PluginCafé for each, but it's easy to achieve. The plugin needs to go into the plugin directory then, naturally.



  • hi

    can we considered this thread as resolved ?

    Cheers,
    Manuel



  • @m_magalhaes Yes please, and sorry for the delay :3


Log in to reply