HowTo set keyframes for PosXYZ/RotXYZ/ScaleXYZ with a script
-
Hey,
How I can make this work for my current selection? No matter, how many objects I have selected...
The question is a little bit ambiguous because the current script will also work when multiple objects are selected, it will then take what Cinema 4D considers to be the primary selected object. I assume you mean that you want to run the script on all selected objects at once.
In this case you must manually retrieve all selected objects with BaseDocument.GetActiveObjects and iterate over them.
for op in doc.GetActiveObjects(c4d.GETACTIVEOBJECTFLAGS_CHILDREN): # Do something with the selected object #op ...
Cheers,
Ferdinand -
If I want to do something on the selected objects only (not the children), I use
for op in doc.GetActiveObjects(c4d.GETACTIVEOBJECTFLAGS_NONE):
# Do something with the selected objects #op
...Do I have to place it on top, before the definitions are done? Do I have to include everything or just where the key stuff starts?
-
Hey,
you must wrap most of the content of the
main()
function as you want to do stuff per object I assume. The only thing which can be done once is theEventAdd()
. See below for an example. Please understand that my ability to help you to learn Python or programming in general, as for example loops, will be limited, as this is out of scope of support.Cheers,
Ferdinand"""Sets the current relative position x-component of the selected object as a keyframe at the current time. Must be run as a Script Manager script. """ import c4d import typing doc: c4d.documents.BaseDocument # The active document. op: typing.Optional[c4d.BaseObject] # The active object, can be None. # The major work in writing key frames in the way you want to do it, lies in precisely defining the # DescID for these parameters. I wrote this example only for keying the relative position x-component, # but below I hint at how the remaining eight parameters must be addressed. # The description level for the relative position, rotation, and scale parameter, we are going to # reuse these when defining IDs for each component of them. DLV_REL_POS: c4d.DescLevel = c4d.DescLevel(c4d.ID_BASEOBJECT_REL_POSITION, c4d.DTYPE_VECTOR, 0) DLV_REL_ROT: c4d.DescLevel = c4d.DescLevel(c4d.ID_BASEOBJECT_REL_ROTATION, c4d.DTYPE_VECTOR, 0) DLV_REL_SCL: c4d.DescLevel = c4d.DescLevel(c4d.ID_BASEOBJECT_REL_SCALE, c4d.DTYPE_VECTOR, 0) # The description levels for the x, y, and z components of a vector parameter. DLV_VEC_X: c4d.DescLevel = c4d.DescLevel(c4d.VECTOR_X, c4d.DTYPE_REAL, 0) DLV_VEC_Y: c4d.DescLevel = c4d.DescLevel(c4d.VECTOR_Y, c4d.DTYPE_REAL, 0) DLV_VEC_Z: c4d.DescLevel = c4d.DescLevel(c4d.VECTOR_Z, c4d.DTYPE_REAL, 0) # Define the IDs for the x, y, and z relative position parameters. DID_POS_X: c4d.DescID = c4d.DescID(DLV_REL_POS, DLV_VEC_X) DID_POS_Y: c4d.DescID = c4d.DescID(DLV_REL_POS, DLV_VEC_Y) DID_POS_Z: c4d.DescID = c4d.DescID(DLV_REL_POS, DLV_VEC_Z) # Define the IDs for the x, y, and z relative rotation and scale parameters. DID_ROT_X: c4d.DescID = c4d.DescID(DLV_REL_ROT, DLV_VEC_X) # ... DID_SCL_X: c4d.DescID = c4d.DescID(DLV_REL_SCL, DLV_VEC_X) # ... def main() -> None: """Runs the example. """ # Iterate over all selected objects. It is better to not use #op as a loop variable to not # shadow the module attribute of the same name. I only suggested that so that you could copy and # paste the code. I am using here #node instead, so #node is what was #op before in the other # script, an instance of a selected BaseObject. But here we not only operate on the primary one, # but all of them. for node in doc.GetActiveObjects(c4d.GETACTIVEOBJECTFLAGS_CHILDREN): # Find the track for the parameter we are interested in creating, here the track for x # component of the relative position, or create a new track when there is none. track: c4d.CTrack = node.FindCTrack(DID_POS_X) if track is None: track = c4d.CTrack(node, DID_POS_X) if not track: raise MemoryError(f"{track = }") node.InsertTrackSorted(track) # Get the current time and the key in #track for this time. CCurve.AddKey will return an # existing key if there is one, so we do not have to search for potentially existing keys # first. t: c4d.BaseTime = doc.GetTime() curve: c4d.CCurve = track.GetCurve() keyData: dict = curve.AddKey(t) if keyData is None: raise RuntimeError("Could not add key frame.") # Set the value of the key to the current value of that parameter. key: c4d.CKey = keyData["key"] key.SetValue(curve, node[DID_POS_X]) # Set some of the key parameters. We could also use the more low-level and precise # C4DAtom.Get/SetParameter calls instead of GeListNode.__get/setitem__ calls (the bracket # syntax) used below. But that is more typing work and there is no need to be more precise # here. # Enable the "Lock Time" tick box for the key. key[c4d.ID_CKEY_LOCK_T] = True # Set "Fixed Weighted" as the key preset. key[c4d.ID_CKEY_PRESET] = c4d.ID_CKEY_PRESET_FIXED_OVERSHOOTWEIGHTED # Enable clamping for the key, just as in the Attribute Manager, this will implicitly change # the preset mode to "Custom" but we will maintain the changes made to other parameters when # we set it to "Fixed Weighted" before. key[c4d.ID_CKEY_CLAMP] = True # Push an update event to Cinema 4D. c4d.EventAdd() if __name__ == "__main__": main()
-
Thank you for the example.
Yes, I know. Sorry again.
Maybe ask the developers why it's not possible to assign hotkeys for keying SRT/X/Y/Z separately in vanilla C4D. This is a basic feature.I could show you how u could make C4D the best animation tool on earth in a 3 hours meeting.
-
Hey,
We in the SDK Group do not have the ability to deal with end user feature requests, it is only the end user support team which collects them. There might also be built-in animation features of Cinema 4D which do what you want to be done. We are primarily engineers and SDK experts here in the SDK Group, and not trainers and application experts. So, there is a good chance that user support knows tips and tricks I am not aware of, as I factually never use Cinema 4D to animate things in any substantial capacity.
You can reach end user support via our support portal.
Cheers,
Ferdinand -
Hello @ferdinand,
This is the final script for PositionX.
I made it a bit shorter for making it more easy to adapt this to PosYZ/RotXYZ/SclXYZ.
And I added a possibility to undo the complete action (e.g. if there was already a key, which got overridden and the user wants to revert this). I am not sure, whether the undo commands are in the right place, but after some research I placed the AddUndo before the stuff begins and it works so far. For all selected objects.Just wanted to post it, because maybe some else needs something like this.
"""Sets the current relative pos/rot/scl x/y/z-component of the selected object as a keyframe at the current time. """ import c4d import typing doc: c4d.documents.BaseDocument # The active document. op: typing.Optional[c4d.BaseObject] # The active object, can be None. # The description level for the relative position, rotation, and scale parameter, we are going to # reuse these when defining IDs for each component of them. # Possible: ID_BASEOBJECT_REL_POSITION // ID_BASEOBJECT_REL_ROTATION // ID_BASEOBJECT_REL_SCALE DLV_REL: c4d.DescLevel = c4d.DescLevel(c4d.ID_BASEOBJECT_REL_POSITION, c4d.DTYPE_VECTOR, 0) # The description levels for the x, y, and z components of a vector parameter. # Possible: VECTOR_X // VECTOR_Y // VECTOR_Z DLV_VEC: c4d.DescLevel = c4d.DescLevel(c4d.VECTOR_X, c4d.DTYPE_REAL, 0) # Define the IDs for the x/y/z relative SRT parameters. DID_SRT: c4d.DescID = c4d.DescID(DLV_REL, DLV_VEC) PRINT_MESSAGE = "Added Key PosX" def main() -> None: doc.StartUndo() for node in doc.GetActiveObjects(c4d.GETACTIVEOBJECTFLAGS_NONE): doc.AddUndo(c4d.UNDOTYPE_CHANGE, node) # Find the track for the parameter we are interested in creating, here the track for x # component of the relative position, or create a new track when there is none. track: c4d.CTrack = node.FindCTrack(DID_SRT) if track is None: track = c4d.CTrack(node, DID_SRT) if not track: raise MemoryError(f"{track = }") node.InsertTrackSorted(track) # Get the current time and the key in #track for this time. CCurve.AddKey will return an # existing key if there is one, so we do not have to search for potentially existing keys # first. t: c4d.BaseTime = doc.GetTime() curve: c4d.CCurve = track.GetCurve() keyData: dict = curve.AddKey(t) if keyData is None: raise RuntimeError("Could not add key frame.") # Set the value of the key to the current value of that parameter. key: c4d.CKey = keyData["key"] key.SetValue(curve, node[DID_SRT]) # Set different options for the key tangents key[c4d.ID_CKEY_PRESET] = c4d.ID_CKEY_PRESET_AUTO_OVERSHOOTWEIGHTED doc.EndUndo() # Push an update event to Cinema 4D. c4d.EventAdd() print (PRINT_MESSAGE) if __name__ == "__main__": main()
Is there a documentation how to make a PlugIn of my 9 scripts?
I mean, atm I just saved them into..\Maxon Cinema 4D 2023_0AF8E603\library\scripts
Maybe it's enough doing a nicely named subfolder there and to add the folder to search location. I don't know. I'll try it out. Never installed a plugin or had a look into a plugin structure yet.
Thank you very much again for your help, Sir!
Kind regards
Vannipo -
Hello @vannipo ,
What you need may not a plugin, scripts is enough. InCommand Manager
, search your script's name, then assign the shortcut you like. Assign a shortcut key to each of your nine scripts, You can even drag the item out of the list tree view and turn it into a button.
-
Hey @vannipo,
Great to hear that you succeeded.
And just to be clear, we are here to answer your questions, that is part of the responsibility of the SDK group. I just have to sometimes moderate a bit the expectations of newcomers (and long timers) regarding what we can do (provide help along the way) and what not (provide full solutions, help with learning a language itself). So, there is no need to thank me every step of the way, we are happy to answer questions, as long as we see that users do not try to use us as a "write my script on demand"-service.
Regarding bringing your script to the next level, there are countless ways you could take, @iplai already pointed out the most straight forward one, assigning them all a hotkey or placing them in a palette (you can directly drag and drop the script cion from the script manager to do that). But I would assume that you are aware of that and rather want to consolidate your nine scripts into less scripts, i.e., bring down the number of hotkeys you require.
- First of all, it is not possible to write a plugin or script that consolidates multiple hotkeys. So, you cannot have a plugin which reacts to all variations of
CTRL + C
, as for exampleCTRL + C + A
,SHIFT + CTRL + C
, etc., being pressed. - If you want a 'real' plugin, you could implement a
CommandData
plugin with aGeDialog
as an interface and thereby implement your own little GUI which determines what happens when you invoke the plugin, press its assigned shortcut. The GUI could then have a dropdown where you can select the transformation mode ("position", "rotation", "or "scale") and a dropdown where one can select the component ("x", "y", "z") and when the user has set them to "position" and "z", every invocation of the plugin would set a "position.z" keyframe. - Another way to reduce the number of shortcuts would be to use a popup menu as a very minmal GUI. You could do this both in a plugin and a script. Find a small example below.
Cheers,
FerdinandResult
Code
"""Sets a key for one of the relative position components for all selected objects. The component which is being set is being determined with a popup menu. This example must be run from the script manager. """ import c4d import typing doc: c4d.documents.BaseDocument # The active document. op: typing.Optional[c4d.BaseObject] # The active object, can be None. # The parameter IDs this script is targeting. DID_POS_X: c4d.DescID = c4d.DescID( c4d.DescLevel(c4d.ID_BASEOBJECT_REL_POSITION, c4d.DTYPE_VECTOR, 0), c4d.DescLevel(c4d.VECTOR_X, c4d.DTYPE_REAL, 0)) DID_POS_Y: c4d.DescID = c4d.DescID( c4d.DescLevel(c4d.ID_BASEOBJECT_REL_POSITION, c4d.DTYPE_VECTOR, 0), c4d.DescLevel(c4d.VECTOR_Y, c4d.DTYPE_REAL, 0)) DID_POS_Z: c4d.DescID = c4d.DescID( c4d.DescLevel(c4d.ID_BASEOBJECT_REL_POSITION, c4d.DTYPE_VECTOR, 0), c4d.DescLevel(c4d.VECTOR_Z, c4d.DTYPE_REAL, 0)) # The popup menu container for selecting the component which should be written, x, y, or z. I am using here # the string mini language to embed an icon in the name of an item, so 'X Channel&i12153&' means for # example, the label "X Channel" and the icon with the ID 12153. The mini language is documented under # ShowPopupDialog(). The icons I am using here are documented under: # https://developers.maxon.net/docs/py/2023_2/modules/c4d.bitmaps/RESOURCEIMAGE.html POPUP_MENU: c4d.BaseContainer = c4d.BaseContainer() POPUP_MENU.InsData(c4d.VECTOR_X, 'X Channel&i12153&') POPUP_MENU.InsData(c4d.VECTOR_Y, 'Y Channel&i12154&') POPUP_MENU.InsData(c4d.VECTOR_Z, 'Z Channel&i12155&') def main() -> None: """Runs the example. """ # Do not run the script at all when there is not object selected. selectedObjects: list[c4d.BaseObject] = doc.GetActiveObjects(c4d.GETACTIVEOBJECTFLAGS_CHILDREN) if len(selectedObjects) < 1: return # Determine which parameter (ID) to target with a little popup menu. targetParameter: typing.Optional[c4d.DescID] = None result: int = c4d.gui.ShowPopupDialog(cd=None, bc=POPUP_MENU, x=c4d.MOUSEPOS, y=c4d.MOUSEPOS, flags=c4d.POPUP_CENTERHORIZ|c4d.POPUP_CENTERHORIZ) if result == c4d.VECTOR_X: targetParameter = DID_POS_X elif result == c4d.VECTOR_Y: targetParameter = DID_POS_Y elif result == c4d.VECTOR_Z: targetParameter = DID_POS_Z # The user aborted the popup menu. if targetParameter is None: return # Your code ... doc.StartUndo() for node in selectedObjects: doc.AddUndo(c4d.UNDOTYPE_CHANGE, node) track: c4d.CTrack = node.FindCTrack(targetParameter) if track is None: track = c4d.CTrack(node, targetParameter) if not track: raise MemoryError(f"{track = }") node.InsertTrackSorted(track) t: c4d.BaseTime = doc.GetTime() curve: c4d.CCurve = track.GetCurve() keyData: dict = curve.AddKey(t) if keyData is None: raise RuntimeError("Could not add key frame.") key: c4d.CKey = keyData["key"] key.SetValue(curve, node[targetParameter]) key[c4d.ID_CKEY_PRESET] = c4d.ID_CKEY_PRESET_AUTO_OVERSHOOTWEIGHTED doc.EndUndo() c4d.EventAdd() if __name__ == "__main__": main()
- First of all, it is not possible to write a plugin or script that consolidates multiple hotkeys. So, you cannot have a plugin which reacts to all variations of
-
Hi @ferdinand @iplai ,
Yes, sure, I already assigned hotkeys to my 9 scripts and made a folded palette and rendered 9 super sweet icons
I was very disappointed as I found out, C4D does not support Numpad keys. So, the whole numpad keys are not useable. I mean, I can use them, but then I have to find new hotkeys for the commands I already use on the usual keys 1-9. Very sadThe most important point for an animator is to key the desired channel as fast as possible. I just got used to use the numpad keys 1-3 for position, 4-6 for rotation, 7-9 for scaling in other 3D tools. They perfectly ordered for those commands.
But, the pop up idea is very interesting and I will have a look. Concerning the GUI... yes, I already saw something like this. I think the GUI builder is inside C4D, isn't it? I am not sure, atm.
Btw, is it possible to set a "real name" for a script? atm, everywhere it just shows the filename.
Cheers,
Vannipo -
Hey @vannipo,
GUI builder is inside C4D, isn't it? I am not sure, atm.
There is no WYSIWYG GUI builder for dialogs in Cinema 4D anymore. A long time ago existed the classic API Resource Editor, but it is not published anymore by us. The Resource Editor you can find via
CTRL + C
in a modern Cinema 4D instance is an editor for maxon API resources, a different and newer GUI paradigm in Cinema 4D.When you want to implement a dialog, you will have to either write a resource file manually or use the methods of
GeDialog
to add elements at runtime, you could have a look at this posting where I recently lined out some basics. The Python GUI Manual is quite superficial at the moment, but we have some simple examples on GitHub. For learning dialog resource markup, I would recommend the Dialog Resource Manual.The most important point for an animator is to key the desired channel as fast as possible. [...] But, the pop-up idea is very interesting, and I will have a look [...]
Yeah, I understood that you were after a very streamlined setup. When you want to make zero compromises, nine shortcuts are probably the best solution. The solution I proposed with the popup menu is a compromise of the number of shortcut keys to allocate and the speed with which keys can be generated. The advantage is here that the popup menu will always open under your mouse cursor no matter where it is, which will minimize mouse travel distances. But when you overpopulate the menu with all nine entries (you can also add separators if you want to), selecting the right item will probably become slow. So, if you want a good compromise, you could use three scripts (translate/rotate/scale) which each provide a popup for x, y, or z.
Btw, is it possible to set a "real name" for a script? atm, everywhere it just shows the filename.
You can supply a different name from its filename by adding file docstring containing
Name-en-US: XXXX
where XXX is the name of the plugin. This can also be done for description that will be displayed when you hover the command withDescription-en-US: XXX
. If you need to support other language please refer to Plugin Structure Manual which specify the different language code. Finally you can find an example within GitHub - script_custom_name_description.pyCheers,
Ferdinand