Your browser does not seem to support JavaScript. As a result, your viewing experience will be diminished, and you have been placed in read-only mode.
Please download a browser that supports JavaScript, or enable it if it's disabled (i.e. NoScript).
Forum wide moderators
Hi, If you want the layer and folder to be displayed in the picture viewer you need to define the parameter MPBTYPE_SAVE to True. MPBTYPE_SHOW is defined by default to true when you add a layer. This parameter will just disable the layer as seen below. (the red eye)
MPBTYPE_SAVE
MPBTYPE_SHOW
This example will create a folder with a layer in it and another layer below the folder.
If it is working with pyhton, it should work with c++, you can share a bit of your code so we can reproduce it.
from typing import Optional import c4d doc: c4d.documents.BaseDocument # The active document op: Optional[c4d.BaseObject] # The active object, None if unselected def main() -> None: mpb = c4d.bitmaps.MultipassBitmap(640, 480, c4d.COLORMODE_ARGB) folder1 = mpb.AddFolder(None) folder1.SetParameter(c4d.MPBTYPE_NAME, "folder") folder1.SetParameter(c4d.MPBTYPE_SAVE, True) layer1 = folder1.AddLayer(None, c4d.COLORMODE_ARGB) layer1.SetParameter(c4d.MPBTYPE_NAME, "layer1") layer1.SetParameter(c4d.MPBTYPE_SAVE, True) layer2 = mpb.AddLayer(folder1, c4d.COLORMODE_ARGB) layer2.SetParameter(c4d.MPBTYPE_NAME, "layer2") layer2.SetParameter(c4d.MPBTYPE_SAVE, True) c4d.bitmaps.ShowBitmap(mpb) if __name__ == '__main__': main()
Cheers, Manuel
Hey @mocoloco,
thank you for your answer.
I was asking this because I want to reset some globals var when the TAG is removed - maybe there is a more elegant way to do, but until now I haven't found one yet.
I am not 100% sure, but your answer somewhat implies a misconception about the lifetime of plugin hooks. They are just as volatile as nodes. When we update our code like this:
@staticmethod def GetIdNodeString(node: c4d.GeListNode, hook: object) -> str: """Returns the memory location and MAXON_CREATOR_ID UUID of the node as a string. """ nodeMem: str = str(hex(id(node))).upper() hookMem: str = str(hex(id(hook))).upper() uuid: str = str(DynamicDescriptionTagData.GetNodeUuid(node)) return f"node-mem: {nodeMem}, hook-mem: {hookMem}, node-uuid: {uuid}" def Init(self, node: c4d.GeListNode) -> bool: """Called by Cinema 4D to initialize a tag instance. Args: node: The BaseTag instance representing this plugin object. """ print (f"Init: {DynamicDescriptionTagData.GetIdNodeString(node, self)}")
Which yield data similar to this:
# A call for the actual tag node to be added to the scene graph. """Init: node-mem: 0X25D5E702F40, hook-mem: 0X25DE98A2A90, node-uuid: b',\xf0]w\xccG\x17\x01\x1c|9C\xbc!\x00\x00'""" # ... # A call for the actual tag node to be added to the scene graph. Note that the call it NOT made to # the same plugin hook instance: # # 1: node-mem: 0X25D5E702F40, hook-mem: 0X25DE98A2A90, # node-uuid: b',\xf0]w\xccG\x17\x01\x1c|9C\xbc!\x00\x00' # 2: node-mem: 0X25D5E71A980, hook-mem: 0X25DE989A090, # node-uuid: b',\xf0]w\xccG\x17\x01\x1c|9C\xbc!\x00\x00' # """Init: node-mem: 0X25D5E71A980, hook-mem: 0X25DE989A090, node-uuid: b',\xf0]w\xccG\x17\x01\x1c|9C\xbc!\x00\x00'""" # ...
So, there is neither a persistent plugin hook nor a president node in the scene for your plugin. Using globals is fine as long as they are constants. But when you want to delete them, it implies that you want to treat them as variables which more often than not will lead into problems.
You could have a global dict where you store settings under node UUIDs and where you only put nodes whose UUID has appeared more than once in the NodeData.Init of yours. That will sort of work, but it would be better to attach the data to the plugin hook instance, i.e., self of MyTagData as you will side step a lot of global variable problems with that. That would mean that you have to init these values every time the node is initialized.
NodeData.Init
self
MyTagData
But that plugin hook instance approach is still non-ideal due to the volatility of hooks. The best way to handle this, is to add these values simply to the data container of the node. This way they will be serialized and deleted with the node.
Cheers, Ferdinand
Hello @WickedP,
Thank you for reaching out to us. It is great to hear that you did find your solution.
Regarding your request for adding a code example: I do not fully understand what you were trying to do?
For example; when I hit the render button, it renders with a depth matte and it displays in the picture viewer. But if I do it myself (C++) using the same render settings, I don't get any depth matte.
What does constitute 'do[ing] it myself' for you here? When one implements a renderer, one usually does not have to deal with render bitmaps and instead operates on render buffers. When using RenderDocument or the things like the render queue, one does not have to manage bitmaps either.
I assume you mean that you want to mimic the output of a renderer with a depth pass/channel. I am not totally against adding such an example, but what would be the content of it? Create an RGBA channel and an alpha channel for that? Recreating the whole structure of a multipass render output bitmap with all the layers unfortunately does not have a good relevance/effort ratio. So, I cannot do 'everything'.
Hello @wickedp,
I have forked your questions as they both did constitute new topics, find them here:
For an external user, NimbusRef can be seen as a hook to attach any GraphSystem to a BaseList2D. Most of the needed functionalities have been added to the BaseList2D but some remain. Most of the time you do not need it. If you use CreateDefaultGraph you do not need it for example.
i confirm that sometimes the function does not find the node. i need to investigate a bit more what is happening here.
hi,
oupss sorry, when you print a GraphNode, the path of the node is printed out. If you want its value, you must use GetDefaultValue().
Int the following path [email protected]$ZuPt9nFg_F5ugGNq<com.redshift3d.redshift4c4d.nodes.core.bumpmap.inputtype the symbol < correspond to the InputList
[email protected]$ZuPt9nFg_F5ugGNq<com.redshift3d.redshift4c4d.nodes.core.bumpmap.inputtype
<
InputList
Hello @mocoloco,
Thank you for reaching out to us. The behavior you are running into is caused by the Asset API. There are many facets to this problem of node reallocation.
Cinema 4D always has reallocated nodes much more frequently than it might appear to be the case for the user. But with the introduction of the Asset API, this behavior has increased significantly, which is why I then added this comment to NodeData::Init both in Python and C++:
NodeData::Init
NodeData.Init is being called frequently due to nodes being reallocated in the background by Cinema 4D. One should therefore be careful with carrying out computationally complex tasks in this function.
This also applies by extension to NodeData::Free. The underlying cause is that the Asset API is initializing and freeing nodes to manage presets. So, your plugin hook will not only be called for the actual node in a scene, but also all the throw-away instances allocated and freed by the asset API. With the help of the MAXON_CREATOR_ID unique node ID, one can establish in this firework of node reallocations which one are actually the same.
NodeData::Free
MAXON_CREATOR_ID
So, let us take the TagData example from your previous question and look at the data by adding the following code.
TagData
class DynamicDescriptionTagData(c4d.plugins.TagData): """Implements a tag hook that monitors the identity of the nodes representing the hook. """ @staticmethod def GetNodeUuid(node: c4d.GeListNode) -> bytes: """Identifies a node over reallocation and scene reloading boundaries. The returned UUID will express what a user would consider one node. When a user creates a document, material, shader, object, tag, etc., it will get such UUID assigned and it will stay the same even over reallocation and scene reloading boundaries. """ data: memoryview = node.FindUniqueID(c4d.MAXON_CREATOR_ID) return bytes(data or None) @staticmethod def GetIdNodeString(node: c4d.GeListNode) -> str: """Returns the memory location and MAXON_CREATOR_ID UUID of the node as a string. """ mem: str = str(hex(id(node))).upper() uuid: str = str(DynamicDescriptionTagData.GetNodeUuid(node)) return f"mem: {mem}, uuid: {uuid}" def Init(self, node: c4d.GeListNode) -> bool: """Called by Cinema 4D to initialize a tag instance. Args: node: The BaseTag instance representing this plugin object. """ print (f"Init: {DynamicDescriptionTagData.GetIdNodeString(node)}") self.InitAttr(node, float, c4d.ID_OFF_X) self.InitAttr(node, float, c4d.ID_OFF_Y) node[c4d.ID_OFF_X] = 0. node[c4d.ID_OFF_Y] = 0. return True def Free(self, node: c4d.GeListNode) -> None: """Called by Cinema 4D to free a tag instance. Args: node: The BaseTag instance representing this plugin object. """ print (f"Free: {DynamicDescriptionTagData.GetIdNodeString(node)}") # ...
When we run this example with one instance of that tag in the scene and interact with the tag a bit, we will come up with something like this:
# The actual tag in the scene is (re-)initialized. Init: mem: 0X7F95D530E3C0, uuid: b'\x14}\xda\xa4\xdb\xf2\x17\x01\x19\xc1\xf7\x00\x079\x00\x00' # An Asset API temp tag is being initialized and then freed right away. Init: mem: 0X7F95D5318500, uuid: b'\x14}\xda\xa4\xdb\xf2\x17\x01$\xc1\xf7\x00Q9\x00\x00' Free: mem: 0X7F95D5316640, uuid: b'\x14}\xda\xa4\xdb\xf2\x17\x01$\xc1\xf7\x00Q9\x00\x00' # The actual tag in the scene is (re-)initialized. Init: mem: 0X7F95D5314FC0, uuid: b'\x14}\xda\xa4\xdb\xf2\x17\x01\x19\xc1\xf7\x00\x079\x00\x00' # An Asset API temp tag is being initialized and then freed right away. Init: mem: 0X7F95D52E9E40, uuid: b'\x14}\xda\xa4\xdb\xf2\x17\x01p\xc9\xf7\x00\xc49\x00\x00' Free: mem: 0X7F95D52E30C0, uuid: b'\x14}\xda\xa4\xdb\xf2\x17\x01p\xc9\xf7\x00\xc49\x00\x00' # The actual tag in the scene is (re-)initialized. Init: mem: 0X7F95D52FB3C0, uuid: b'\x14}\xda\xa4\xdb\xf2\x17\x01\x19\xc1\xf7\x00\x079\x00\x00' # An Asset API temp tag is being initialized and then freed right away. Init: mem: 0X7F95D53096C0, uuid: b'\x14}\xda\xa4\xdb\xf2\x17\x01\xdd\xcd\xf7\x006:\x00\x00' Free: mem: 0X7F95D530A180, uuid: b'\x14}\xda\xa4\xdb\xf2\x17\x01\xdd\xcd\xf7\x006:\x00\x00' # The actual tag in the scene is (re-)initialized. Init: mem: 0X7F95D5309F00, uuid: b'\x14}\xda\xa4\xdb\xf2\x17\x01\x19\xc1\xf7\x00\x079\x00\x00' # An Asset API temp tag is being initialized and then freed right away. Init: mem: 0X7F95D52F9D00, uuid: b'\x14}\xda\xa4\xdb\xf2\x17\x01\x9c\xd9\xf7\x00\xaf:\x00\x00' Free: mem: 0X7F95D52FD980, uuid: b'\x14}\xda\xa4\xdb\xf2\x17\x01\x9c\xd9\xf7\x00\xaf:\x00\x00' # The actual tag in the scene is (re-)initialized. Init: mem: 0X7F95D52FD940, uuid: b'\x14}\xda\xa4\xdb\xf2\x17\x01\x19\xc1\xf7\x00\x079\x00\x00' # An Asset API temp tag is being initialized and then freed right away. Init: mem: 0X7F95D5317A80, uuid: b'\x14}\xda\xa4\xdb\xf2\x17\x01,\xdd\xf7\x00Q;\x00\x00' Free: mem: 0X7F95D5313EC0, uuid: b'\x14}\xda\xa4\xdb\xf2\x17\x01,\xdd\xf7\x00Q;\x00\x00'
So, with this we can see, that:
id()
Init
Free
There is no really good answer to this. Your underlying question is basically:
How can I distinguish a dummy node being passed to my node hook Init, Free, etc., from a 'real' node?
And the answer is currently: You cannot, even in C++. You can count UUIDs and exploit the patterns shown above, but that is quite unreliable. So, in the end you are unable to distinguish a dummy node being freed from a real one (at least from our current perspective).
But what you can do is detect a node being removed:
To implement (2.), you would do something like this:
def Execute(self, tag: c4d.BaseTag, doc: c4d.documents.BaseDocument, op: c4d.BaseObject, bt: c4d.threading.BaseThread, priority: int, flags: int) -> int: """Called when expressions are evaluated to let a tag modify a scene. Does nothing in this example. """ if not tag.FindEventNotification(doc, tag, c4d.NOTIFY_EVENT_REMOVE): tag.AddEventNotification(tag, c4d.NOTIFY_EVENT_REMOVE, c4d.NOTIFY_EVENT_FLAG_NONE, c4d.BaseContainer()) return c4d.EXECUTIONRESULT_OK def Message(self, node: c4d.GeListNode, mid: int, data: object) -> bool: """Called by Cinema 4D to convey events to the node. """ if mid == c4d.MSG_NOTIFY_EVENT: print (f"{node} is being removed.") return True
Which would then result in:
Init: mem: 0X7FDC359CCA00, uuid: b'\x14}\xda\xa4\xdb\xf2\x17\x01,`\x86\x01\xbc)\x00\x00' Init: mem: 0X7FDC359B6100, uuid: b'\x14}\xda\xa4\xdb\xf2\x17\x01:`\x86\x01\xf0)\x00\x00' Free: mem: 0X7FDC359B53C0, uuid: b'\x14}\xda\xa4\xdb\xf2\x17\x01:`\x86\x01\xf0)\x00\x00' Init: mem: 0X7FDC359C6700, uuid: b'\x14}\xda\xa4\xdb\xf2\x17\x01\x84z\x86\x01\xfa)\x00\x00' Free: mem: 0X7FDC359C6E00, uuid: b'\x14}\xda\xa4\xdb\xf2\x17\x01\x84z\x86\x01\xfa)\x00\x00' Init: mem: 0X7FDC359DA640, uuid: b'\x14}\xda\xa4\xdb\xf2\x17\x01,`\x86\x01\xbc)\x00\x00' Free: mem: 0X7FDC359D5B00, uuid: b'\x14}\xda\xa4\xdb\xf2\x17\x01,`\x86\x01\xbc)\x00\x00' Init: mem: 0X7FDC359D2580, uuid: b'\x14}\xda\xa4\xdb\xf2\x17\x01\x87\xa4\x86\x01n*\x00\x00' Free: mem: 0X7FDC359D2780, uuid: b'\x14}\xda\xa4\xdb\xf2\x17\x01\x87\xa4\x86\x01n*\x00\x00' Init: mem: 0X7FDC359DCBC0, uuid: b'\x14}\xda\xa4\xdb\xf2\x17\x01,`\x86\x01\xbc)\x00\x00' <c4d.BaseTag object called DynDescription Tag/DynDescription Tag with ID 1060463 at 140583769067328> is being removed. Free: mem: 0X7FDC359DCFC0, uuid: b'\x14}\xda\xa4\xdb\xf2\x17\x01,`\x86\x01\xbc)\x00\x00'
But here are also a few caveats to be aware of:
NOTIFY_EVENT_FREE
Your question was unintentionally quite board without telling us what you actually intended to do. In general, wanting to distinguish these events should not be necessary when one is using the plugin hooks how they are meant to be used and respecting their limits.
There are of course exceptions to this, and sometimes the classic API can get in your way. But I would urge you to try to rethink your problem, because that is often better than trying to go head first through a wall
Full Code:
"""Implements a tag which drives the position of a host object procedurally or via tracks. """ import c4d import typing class DynamicDescriptionTagData(c4d.plugins.TagData): """Implements a tag which drives the position of a host object procedurally or via tracks. """ # The plugin ID. ID_PLUGIN: int = 1060463 # What you dubbed "user data", a dictionary holding some description data to be injected at # runtime. I just changed things a bit up and include/identify things over their ID instead # of made-up strings, but nothing would prevent you from having both, it would just be a bit # more complicated to parse such data: # # INTERFACE_DATA = [ # { # "label": "Foo", # "id": c4d.DescId(...), # "data": [ # {"label": "Name", "id": c4d.DESC_NAME, "value": "Bar"}, # ... # ] # }, # ... # ] INTERFACE_DATA: list[tuple[c4d.DescID, dict[int, typing.Any]]] = [ (c4d.DescID(c4d.DescLevel(1000, c4d.DTYPE_REAL, 0)), { c4d.DESC_NAME: "foo.x", c4d.DESC_SHORT_NAME: "foo.x", c4d.DESC_MIN: c4d.utils.DegToRad(-45.), c4d.DESC_MAX: c4d.utils.DegToRad(45.), c4d.DESC_MINSLIDER: c4d.utils.DegToRad(-45.), c4d.DESC_MAXSLIDER: c4d.utils.DegToRad(45.), }), (c4d.DescID(c4d.DescLevel(1001, c4d.DTYPE_REAL, 0)), { c4d.DESC_NAME: "bar.y", c4d.DESC_SHORT_NAME: "bar.y", c4d.DESC_MIN: c4d.utils.DegToRad(-180.), c4d.DESC_MAX: c4d.utils.DegToRad(180.), c4d.DESC_MINSLIDER: c4d.utils.DegToRad(-180.), c4d.DESC_MAXSLIDER: c4d.utils.DegToRad(180.), }), ] @staticmethod def GetNodeUuid(node: c4d.GeListNode) -> bytes: """Identifies a node over reallocation and scene reloading boundaries. The returned UUID will express what a user would consider one node. When a user creates a document, material, shader, object, tag, etc., it will get such UUID assigned and it will stay the same even over reallocation and scene reloading boundaries. """ data: memoryview = node.FindUniqueID(c4d.MAXON_CREATOR_ID) return bytes(data or None) @staticmethod def GetIdNodeString(node: c4d.GeListNode) -> str: """Returns the memory location and MAXON_CREATOR_ID UUID of the node as a string. """ mem: str = str(hex(id(node))).upper() uuid: str = str(DynamicDescriptionTagData.GetNodeUuid(node)) return f"mem: {mem}, uuid: {uuid}" def Init(self, node: c4d.GeListNode) -> bool: """Called by Cinema 4D to initialize a tag instance. Args: node: The BaseTag instance representing this plugin object. """ print (f"Init: {DynamicDescriptionTagData.GetIdNodeString(node)}") self.InitAttr(node, float, c4d.ID_OFF_X) self.InitAttr(node, float, c4d.ID_OFF_Y) node[c4d.ID_OFF_X] = 0. node[c4d.ID_OFF_Y] = 0. return True def Free(self, node: c4d.GeListNode) -> None: """Called by Cinema 4D to free a tag instance. """ print (f"Free: {DynamicDescriptionTagData.GetIdNodeString(node)}") def Execute(self, tag: c4d.BaseTag, doc: c4d.documents.BaseDocument, op: c4d.BaseObject, bt: c4d.threading.BaseThread, priority: int, flags: int) -> int: """Called when expressions are evaluated to let a tag modify a scene. Does nothing in this example. """ if not tag.FindEventNotification(doc, tag, c4d.NOTIFY_EVENT_REMOVE): tag.AddEventNotification(tag, c4d.NOTIFY_EVENT_REMOVE, c4d.NOTIFY_EVENT_FLAG_NONE, c4d.BaseContainer()) return c4d.EXECUTIONRESULT_OK def Message(self, node: c4d.GeListNode, mid: int, data: object) -> bool: """Called by Cinema 4D to convey events to the node. """ if mid == c4d.MSG_NOTIFY_EVENT: print (f"{node} is being removed.") return True def GetDDescription(self, node: c4d.GeListNode, description: c4d.Description, flags: int) -> typing.Union[bool, tuple[bool, int]]: """Called by Cinema 4D when the description of a node is being evaluated to let the node dynamically modify its own description. """ if not description.LoadDescription(node.GetType()): return False, flags singleId: c4d.DescID = description.GetSingleDescID() for paramId, dataDictionary in DynamicDescriptionTagData.INTERFACE_DATA: if (singleId and not paramId.IsPartOf(singleId)): return True, flags data: c4d.BaseContainer = description.GetParameterI(paramId) if data is None: return True, flags for key, value in dataDictionary.items(): data[key] = value return True, flags | c4d.DESCFLAGS_DESC_LOADED def RegisterPlugins() -> None: """Registers the plugins contained in this file. """ if not c4d.plugins.RegisterTagPlugin( id=DynamicDescriptionTagData.ID_PLUGIN, str="DynDescription Tag", info=c4d.TAG_EXPRESSION | c4d.TAG_VISIBLE, g=DynamicDescriptionTagData, description="tdyndescription", icon=c4d.bitmaps.InitResourceBitmap(c4d.ID_MODELING_MOVE)): print("Warning: Failed to register 'DynDescription' tag plugin.") if __name__ == "__main__": RegisterPlugins()
Hello @joel,
so, I had a look at your file and your code. Your file seems broken beyond repair, I was not able to replicate your results there, the tracks themselves seem to be malformed.
The problem is here, that you do not show how you construct these track IDs returned by your find_DescID function, which then also leaves me a bit in the dark which parameter you want to keyframe. From the context I assumed that you want to animate the dynamically managed effector list strength values of a cloner.
find_DescID
Without that code, I cannot tell you if you do construct your IDs correctly. What also stands out, is that you do not send c4d. MSG_MENUPREPARE to your Mograph nodes, causing your effectors not to be respected in loaded files. This is the likely cause of your problems. Find below an example script which does work fine for me, both on execution and a saved file.
c4d. MSG_MENUPREPARE
File: mg_effector_rig.c4d Result: Code:
"""Creates a Mograph rig with a cloner influenced by effectors, then creates a key frame animation for the effector influences on the cloner. Must be run from the Script Manager. Your key problem was probably not sending MSG_MENUPREPARE to the effectors, possibly in combination with constructing the track IDs incorrectly. """ import c4d import typing doc: c4d.documents.BaseDocument # The active document. def CreateClonerRig(doc: c4d.documents.BaseDocument) -> c4d.BaseObject: """Creates a MoGraph cloner rig with an inheritance and a random effector. """ if not isinstance(doc, c4d.documents.BaseDocument): raise TypeError(f"{doc = }") # Instantiate the cloner, the cloned object, the two effectors and a motion source for the # inheritance effector. I am using here the Mograph symbols exposed with 2023.0, e.g., # Omgcloner. For prior versions one must define them manually. clonerObj: c4d.BaseObject = c4d.BaseObject(c4d.Omgcloner) cloneObj: c4d.BaseObject = c4d.BaseObject(c4d.Ocube) motionSourceObj: c4d.BaseObject = c4d.BaseObject(c4d.Osphere) inheritanceEffector: c4d.BaseObject = c4d.BaseObject(c4d.Omginheritance) randomEffector: c4d.BaseObject = c4d.BaseObject(c4d.Omgrandom) if None in (clonerObj, cloneObj, motionSourceObj, inheritanceEffector, randomEffector): raise MemoryError("Could not allocate cloner setup.") # Create the Phong and Vibrate tag on the motion source object. phongTag: c4d.BaseTag = motionSourceObj.MakeTag(c4d.Tphong) vibrateTag: c4d.BaseTag = motionSourceObj.MakeTag(c4d.Tvibrate) if None in (phongTag, vibrateTag): raise MemoryError("Could not allocate cloner setup.") # Insert the objects into the document. doc.InsertObject(clonerObj) doc.InsertObject(inheritanceEffector) doc.InsertObject(randomEffector) doc.InsertObject(motionSourceObj) cloneObj.InsertUnder(clonerObj) # Set the size of the cloned cube object, set a position vibration on the Vibrate tag, and # set the source object for the inheritance effector. cloneObj[c4d.PRIM_CUBE_LEN] = c4d.Vector(50) vibrateTag[c4d.VIBRATEEXPRESSION_POS_ENABLE] = True vibrateTag[c4d.VIBRATEEXPRESSION_POS_AMPLITUDE] = c4d.Vector(100) inheritanceEffector[c4d.MGINHERITANCEEFFECTOR_OBJECT] = motionSourceObj # Add both effectors to the cloner. effectorList: c4d.InExcludeData = clonerObj[c4d.ID_MG_MOTIONGENERATOR_EFFECTORLIST] effectorList.InsertObject(inheritanceEffector, 1) effectorList.InsertObject(randomEffector, 1) clonerObj[c4d.ID_MG_MOTIONGENERATOR_EFFECTORLIST] = effectorList # It is important to send this message to all Mograph objects on instantiation. for node in (clonerObj, inheritanceEffector, randomEffector): node.Message(c4d.MSG_MENUPREPARE, doc) c4d.EventAdd() return clonerObj def AnimateEffectorList(clonerObj: c4d.BaseObject) -> None: """Creates a keyframe animation for each effector strength in the effector list of the given cloner. """ # Make sure the passed cloner is indeed a cloner and attached to a document. if not isinstance(clonerObj, c4d.BaseObject) or clonerObj.GetType() != c4d.Omgcloner: raise TypeError(f"{clonerObj = }") doc: c4d.documents.BaseDocument = clonerObj.GetDocument() if not isinstance(doc, c4d.documents.BaseDocument): raise RuntimeError(f"{clonerObj} is not attached to a document.") # Define the three points in document time to insert keyframes for. fps: int = doc.GetFps() minTime: c4d.BaseTime = doc.GetMinTime() # 1st frame maxTime: c4d.BaseTime = doc.GetMaxTime() # last frame midTime: c4d.BaseTime = c4d.BaseTime( int((maxTime.GetFrame(fps) - minTime.GetFrame(fps)) / 2), fps) # 'middle' frame # Get the effectors which are linked in the cloner. Note that #linkedObjects can contain # None as ObjectFromIndex() can return None. This happens when a link cannot be resolved # anymore, i.e., an InExcludeData list is referencing something which does not exist anymore. effectorList: c4d.InExcludeData = clonerObj[c4d.ID_MG_MOTIONGENERATOR_EFFECTORLIST] linkedObjects: list[typing.Optional[c4d.BaseObject]] = [ effectorList.ObjectFromIndex(doc, i) for i in range(effectorList.GetObjectCount())] # Iterate over all objects in the InEx: for i, effector in enumerate(linkedObjects): # Step over dangling references. if effector is None: continue # Define the ID for the parameter. The parameters are dynamically generated and start of at # ID 1500 and then continue in the order and length of the InExcludeData. did: c4d.DescID = c4d.DescID(c4d.DescLevel(1500 + i, c4d.DTYPE_REAL, c4d.Omgcloner)) print (f"{did = }, {effector.GetName() = }") # Get or create a track for the parameter ID. track: typing.Optional[c4d.CTrack] = clonerObj.FindCTrack(did) if track is None: track = c4d.CTrack(clonerObj, did) clonerObj.InsertTrackSorted(track) # Get the curve of the track and start adding keyframes. curve: c4d.CCurve = track.GetCurve() for time, value in ((minTime, 0.), (midTime, .25), (maxTime, 1.)): key: c4d.CKey = curve.AddKey(time)["key"] key.SetValue(curve, value) print (f"Created key for {did} at '{time.Get()}' sec with the value '{value}'.") def main() -> None: """Runs the example. """ clonerObj: c4d.BaseObject = CreateClonerRig(doc) AnimateEffectorList(clonerObj) c4d.EventAdd() if __name__ == "__main__": main()
Hi,
Redshift IDs are not available as symbols. We have differents ways to find those IDs. One is to display them in the palette info. You can activate the option in the node editor preferences.
The best way to understand how the UI of is composed of is to use the Resource Editor. You can launch it through the Command Manager (shift + C) and type resou
resou
Once you are in the resource editor you can click this icon and start typing the ID of the node you are looking for. This will reduce the database to only the one that contain a node with this ID. (here typing bump)
bump
On the left you have the port list and you can find the port 'Input Map Type'. If you select it, you can find the enum list and the value waited.
each entry that a string attached to it
Another way of finding IDs and value, you can Copy a node and paste it in a text editor, the json version of the node will be pasted. The port's value will only be displayed if you changed the value. The last one is to add the value you want to define as an input port for the node and select it. Information will be displayed in the info palette like its ID, DataType, and value.