Best practices for a plugin containing multiple commands
-
Hi there, I'm working on creating my first python plugin for c4d and wrapping my head around the intricacies of the api (so forgive me if my general knowledge is a bit lacklustre, and terminology a bit off ).
The plugin itself allows a custom way of navigating the editor/user selected camera based on custom created Command Data commands. For example, the user presses the 'A' key (arbitrary) to move the camera to one position, the 'B' key (also arbitrary) to move it to another, etc.
I'm curious as to what the recommended best practices are for creating these custom commands as I couldn't find many examples of Command Data plugins that create multiple commands in the sdk or forum examples. The immediately obvious way is to create and register multiple
c4d.plugins.CommandData
classes within the main .pyp file, as follows:import c4d PLUGIN_ID_1 = 1000000 #Test ID PLUGIN_ID_2 = 1000001 #Test ID #Create first command example class ExampleCameraCommandA(c4d.plugins.CommandData): def Execute (self, doc): #Execute camera code here... return True #Create second command example class ExampleCameraCommandB(c4d.plugins.CommandData): def Execute (self, doc): #Execute camera code here... return True if __name__ == "__main__": #Registers first custom command c4d.plugins.RegisterCommandPlugin ( id = PLUGIN_ID_1, str = "Example Command A", info = 0, #PLUGINFLAG_COMMAND_HOTKEY(?) help = "Moves camera to point A.", dat = ExampleCameraCommandA(), icon = None) #Registers second custom command c4d.plugins.RegisterCommandPlugin ( id = PLUGIN_ID_2, str = "Example Command B", info = 0, #PLUGINFLAG_COMMAND_HOTKEY(?) help = "Moves camera to point B.", dat = ExampleCameraCommandB(), icon = None)
Really my main issue with this, is that it seems a bit overkill to generate multiple pluginIDs for essentially one plugin (albeit one that will contain many custom commands) - is this really how it should be done?
The only other point of potential interest that I could see in the docs were the
CommandData.GetSubContainer()
,CommandData.ExecuteSubID()
andCommandData.ExecuteOptionID()
methods, but couldn't find many examples of their use and implementation to learn from...Any light shed on this topic would be very much appreciated, thanks in advance!
-
Hello @hexbob6,
thank you for reaching out to us. Please remember to add tags for the targeted operating system(s) and version(s) of Cinema 4D to your topics in the future. You can read more about our support procedures int the Forum Guidelines.
About your question: The answer to your topic title,
Best practices for a plugin containing multiple commands
, slightly reaches into the domain of "out of scope of support" (see Forum Guidelines for details), since such general design decision questions are often hard to answer. So, some stuff in bullet points:
CommandData.ExecuteOptionID()
is related to your plugin entry in menus and palettes being split into two buttons. One which will lead toCommandData.Execute()
and one which will lead toCommandData.ExecuteOptionID()
. This is then rendered out as your plugin icon and a cogwheel next to it. You are somewhat expected to provide an options dialog with the secondary button, but that is just convention and you can do inCommandData.ExecuteOptionID()
whatever you want. TheOptimize
-command of Cinema 4D makes use of that pattern for example:CommandData.ExecuteSubID()
is related to your CommandData being split into multiple menu entries without having to provide multiple plugin ids. You must define these sub-menu entries yourself withCommandData.GetSubContainer()
and also define the sub-ids for these sub-commands. ExecuteSubID() will then be called with these ids for the sub-commands you did define and therefore allow you to react to different 'buttons' the user did click on.- At the surface, option No. 2 will be undistinguishable for users from providing multiple CommandData plugins. But plugin ids are not a scarce commodity, we give you as many as you want ExecuteSubID() is a more complex approach, which at least I would not use when I just want 3, 4, 5, ..., n commands, where n is a fixed number and reasonably small. But it is up to you to make that decision.
On top of that there are several other topics which are tangential to you question of the 'best practice'. There are for once
ToolData
plugins, which at a quick glance seem more fitting for your task of multiple plugins/commands 'that move the camera around' - sinceToolData
does provide dedicated keyboard and mouse input methods. There is also the typeGeDialog
(which you will also need for ToolData), which represents these little dialog windows Cinema 4D does open all over the place. In them you can define your own GUI and with that also your own buttons which do their own stuff. GeDialog is commonly used in conjunction with CommandData. Lastly, there arec4d.gui.GetInputState()
andc4d.gui.GetInputEvent()
, with which you can poll yourself for keyboard and mouse events and by that branch out from a single plugin into multiple actions.I cannot really tell you what the best for you under the light of best practice would be, because that is a matter of taste and depends on what you want to do exactly. But for a beginner I would recommend writing multiple
CommandData
plugins and keeping it as simple as possible.I hope this helps and cheers,
Ferdinand -
@hexbob6 said in Best practices for a plugin containing multiple commands:
The plugin itself allows a custom way of navigating the editor/user selected camera based on custom created Command Data commands. For example, the user presses the 'A' key (arbitrary) to move the camera to one position, the 'B' key (also arbitrary) to move it to another, etc.
I'm curious as to what the recommended best practices are for creating these custom commands as I couldn't find many examples of Command Data plugins that create multiple commands in the sdk or forum examples. The immediately obvious way is to create and register multiple c4d.plugins.CommandData classes within the main .pyp fileWell, as you're asking for best practises, let me put forward a few thoughts.
As Ferdinand already said, it is indeed necessary to give every command its own ID. (And its own icon.) That allows the system to recognize that command everywhere (as you progress, you will find that the ID of a plugin can also be used to identify portions of the saved file, e.g. for an Object plugin) and to include it in the menu, as icon, or as a dialog that can be embedded in a layout and reopened at starting time.
However, the technical prerequisites do not necessarily mirror what's best for the user. Since you use that camera example: Sure, you can let the user press a specific key for each action (or a specific icon in the layout). But this will force the user to remember all these keys, and it will block these keys for any other functionality (regardless of what keys these are). (Or it will take up space in the GUI, if you use the icons.) That raises the question whether single commands really are the way to go for that usecase.
An alternative would be to have a single command, which then opens a menu with all available cameras and/or the possibility to switch to the next/prev camera. This would only need one registered ID with Maxon, since the menu IDs are an internal matter of the single plugin.
Or you can open a dialog instead, where you have buttons / lists for the cameras. You may even have input fields there to pass parameters to the plugin. Here too, all functions would be called through "plugin-local" IDs, and you only need one ID with Maxon (and you occupy only one icon in the layout, or one key on the keyboard... unless, of course, you want to embed that dialog in the layout, which naturally is the user's preference).
Maybe "camera" as example is not quite novel as C4D has the "Use Camera" icon already built in (which lists all scene cameras) so this is pretty much done already.
The other question is whether the functionality is really needed in praxis. I remember that I had the GRIP tools (at that time written in COFFEE if I remember correctly) which had commands to go to the next/previous camera. Later (after the demise of COFFEE) I thought I would miss these, and programmed equivalent scripts in Python. These were indeed separate scripts so you could put them on separate keys for immediate access.
Now, with the hundreds of functions to put on keys, I was at three-letter shortcuts already, so "next camera" would map to V-C-arrow right, and "previous camera" would map to V-C-arrow left (V as in view, C as in camera). You may imagine how many different shortcuts I had (and even though they are logically arranged, I struggle with Modeling Command shortcuts to this day).
And then I finally found that I don't even use "next/prev camera" commands all that often, because I rarely want to go to a camera that just happens to be the next in the object tree, but more often want to switch to a specific camera - for which the "Use Camera" pop-up icon would be fine. Duh!
On the other hand, I also wrote commands to skip in the timeline - 2, 5 or 10 frames forward/backward, or a second forward/backward - these are indeed all commands with separate IDs, and they are all icons in my timeslider! Plus I actually use them Thus, you see that it is not always "wrong" to go with single commands.
I am just mentioning that anecdote to underline the importance of usecase analysis and UX design. Whether you really need separate commands, or rather lump it all together under one menu / dialog, depends on how often you use that functionality, how quickly you want to access it with the mouse (layout/icons), how urgent keyboard shortcuts are for this, and how much you want to automate. So, there is no all-encompassing best practice, but rather a situational one:
-
If you need a function at your fingertips, or hop between similar functions quickly, a separate command makes much sense (like the timeline jumping I mentioned) which can be used as keyboard shortcut or as icon.
-
If you have a number of functions that are related (and may be expanded in the future), then you are often better off with a menu. This will only require one ID / one key / one icon to be called up. But on the other side the user will need two interactions (call the menu, select from the menu) to get to the actual function.
-
If you have functions that relate to dynamic data (e.g. call a specific camera), then you cannot go with single commands, as the number of commands is static with your source code. E.g. if you program two camera command slots, you have a command for camera A and a command for camera B, but a potential camera C would not have a slot left! (If you can use the word "slot" in your usecase description, it's a clear sign for this case )
In that case, you need to go with a dynamic menu or dialog that can catch any number of "slots". -
If you need to enter additional data, like a parameter, to execute the command, then a dialog is the way to go. E.g. you want to select certain polygons by their slope, then you need a number field where you can specify that slope. Depending on the command, you may want a modal or a non-modal dialog.
-
Finally, if your functionality is highly dynamic and interactive, you can think of doing a tool plugin, where user interactions directly lead to a visible and changeable result. This is advanced magic, though.
Go ahead with the camera commands for practice, but keep an eye on those pesky usecases and how you want the user to interact with the actual functionality; doing is still the best learning.
Learn more about Python for C4D scripting:
https://www.patreon.com/cairyn -
-
Wow, thank you both for such detailed and informative replies!
First of all, apologies @ferdinand for the tag issues, I was consulting the guidelines whilst typing my question but managed to miss the OS tag. Not sure if it matters too much for python dev, but I am on a Windows machine. I did however notice that the tag list doesn't seem to be updated to include R25 just yet (the version I'm currently working with), so it may need adding by the team. It doesn't look like I'm able to edit my original post past a certain time, but if you have the ability feel free to update
Useful to know about the distinction between
CommandData.ExecuteOptionID()
andCommandData.ExecuteSubID()
. The cogwheel icon present on some of the in-built commands was something that I'd noticed previously and been curious as to the implementation of - assumed it was just some complex code magic that added an additional button to the menu UI, ha!I guess that you're probably right in that I shouldn't try to run before I can walk, and if there's no limit to the amount of plugin IDs permitted (makes sense if there are hundreds of thousands of them going spare! ), then the keep-it-simple approach of registering multiple IDs would probably be best at this stage. For future reference though, if it is functionally the same as registering multiple IDs, do you know of any resources showing the implementation of
CommandData.ExecuteSubID()
, besides the docs; is this something that could be a candidate for an additional example in the SDK plugins repo?@Cairyn thanks for offering a more general insight into the situational decisions behind why something might be the 'best' choice (or not a choice at all!). I'm a designer first and foremost with an interest in coding, rather than the other way around, so considering the UX of a potential plugin and how it could fit into my own workflow is something I'm keen not to ignore
To be a little more specific about how the plugin will function: essentially it will be a set of commands allowing the user to rapidly navigate around the viewport in a way that's a bit more custom than the default 'front, back, left, right, etc' views, as well as the ability to toggle between perspective and parallel views, ideally while keeping the same projection matrix (the hard bit!). My intention is to bind these commands to separate shortcuts so that they're a 'function at your fingertips' Actually I do have some additional questions about the ability to set keyboard shortcuts via a plugin, instead of the command manager, and how Cinema then deals with shortcut conflicts, but I think that's one for another thread!
Definitely going to refer back to these suggestions as I continue my learning journey.
Thank you both again!