Group Details Private

Global Moderators

Forum wide moderators

RE: Adding Layers to a MultipassBitmap

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)
7375d915-1e62-4b6b-b174-3a16d05a5e20-image.png

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

posted in Cinema 4D SDK
RE: Back on "message after tag delete" post

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.

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

posted in Cinema 4D SDK
RE: Rendering into Multipassbitmap a "Depth Matte" Layer

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'.

Cheers,
Ferdinand

posted in Cinema 4D SDK
RE: Render settings Multi-Pass flag

Hello @wickedp,

I have forked your questions as they both did constitute new topics, find them here:

Cheers,
Ferdinand

posted in Cinema 4D SDK
RE: Create RS Standard Material instead of RS Material

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.

Cheers,
Manuel

posted in Cinema 4D SDK
RE: FindNodesByName Not Working As Expected

i confirm that sometimes the function does not find the node. i need to investigate a bit more what is happening here.

Cheers,
Manuel

posted in Cinema 4D SDK
RE: Redshift constants

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

Cheers,
Manuel

posted in Cinema 4D SDK
RE: Back on "message after tag delete" post

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.

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 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.

So, let us take the TagData example from your previous question and look at the data by adding the following code.

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:

  • Even more than in C++, the memory location of nodes (id()) is in Python completely meaningless. Nodes are reallocated all the time.
  • But one can see patterns by looking at the MAXON_CREATOR_ID UUID of nodes.
    • Only the UUID for the actual node appears more than once per Init and Free.
    • The actual node in the scene is never freed (because I never deleted the tag).
    • It is the temporary Asset API preset nodes which are freed.

What should I do?

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:

  1. By having some sort of watch node W which is watching a to be removed node R. There is no magic here, you can for example have a tag T1 which checks on each execution if the tag T2 does still exist.
  2. A bit more elegant solution is using event notifications, a non-public concept in the Cinema 4D API.

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:

  1. You will not be notified when something up in the hierarchy chain is being removed and with it your node. So, you have a null object "Null.1" and attached to it your tag "MyTag" for which you registered for the remove event. Then "Null.1" is being removed, and with it your tag. You will not be notified, and instead only when the tag itself is being removed.
  2. There also other notify events, but for NOTIFY_EVENT_FREE you will for example need at least two nodes, one watcher and one watched, as you cannot notify yourself about being freed as you can for being removed. Some of the events will also be emitted for the dummy nodes.
  3. Event notification is private, and we cannot share details on demand here. You are more or less on your own.

One Important Last Thing

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 😉

Cheers,
Ferdinand

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()
posted in Cinema 4D SDK
RE: Inheritance created by Python script not getting saved

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.

Screenshot 2023-01-24 at 12.45.56.png

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.

Cheers,
Ferdinand

File: mg_effector_rig.c4d
Result: Screenshot 2023-01-24 at 13.03.29.png
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()
posted in Cinema 4D SDK
RE: Redshift constants

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

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)

0654afa5-4923-48bc-85fc-fdb2d5f65ab0-image.png

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.
5fb470e9-591b-4dc7-bc8a-bf0ce6771b3c-image.png

each entry that a string attached to it
f39e8ded-3bc6-412a-857c-756befb64288-image.png

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.
530b4310-25c7-4416-a026-ef1241dd5576-image.png
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.

74cba6ad-b06e-4076-92cc-57e30654baff-image.png

Cheers,
Manuel

posted in Cinema 4D SDK