administrators

Private

Posts

  • RE: How to compute Redshift MoGraph index ratios (RSMGIDRatioColor)?

    Hey Bruce, multiple people in your company have access. When you do not, reach out to us, so that we can set you up.

  • RE: set GvNode value via python

    Hey,

    so, I actually did get it right, I apparently just did not check properly when I tested the common case of second level IDs of dynamic properties starting off at 1000.

    Cheers,
    Ferdinand

    """Demonstrates how to set values on dynamic input ports of a Condition node in an XPresso tag.
    """
    
    import c4d
    
    from c4d.modules.graphview import GvNodeMaster, GvNode, GvPort, XPressoTag
    from mxutils import CheckType
    
    doc: c4d.documents.BaseDocument  # The currently active document.
    op: c4d.BaseObject | None  # The primary selected object in `doc`. Can be `None`.
    
    
    def main() -> None:
        """Called by Cinema 4D when the script is being executed.
        """
        # Create a new XPresso tag with a Condition node.
        null: c4d.BaseObject = CheckType(c4d.BaseObject(c4d.Onull))
        tag: XPressoTag = CheckType(null.MakeTag(c4d.Texpresso))
        master: GvNodeMaster = CheckType(tag.GetNodeMaster())
        doc.InsertObject(null)
    
        root: GvNode = CheckType(master.GetRoot())
        condNode: GvNode = CheckType(master.CreateNode(root, c4d.ID_OPERATOR_CONDITION, x=200, y=200))
    
        # Count the number of input ports on the Condition node. For our case, a freshly instantiated
        # node we do not really have to do this, since there will always be static "Switch" port and
        # two dynamic input ports. But when we are working on an existing graph, we have to count the
        # ports to know how many dynamic ports there are.
        inputPorts: list[GvPort] = condNode.GetInPorts()
        portCount: int = len(inputPorts)
    
        # There is no predefined symbol for this, the implementation counts just ports, so dynamic ports
        # start at 1001 because the input port GV_CONDITION_SWITCH has the ID 1000.
        idDynamicStart: int = 1001
    
        # Iterate over all dynamic input ports (-1 -> skipping the first static "Switch" port).
        for i in range(portCount - 1): 
            # Define the DescID for the dynamic input port.
            did: c4d.DescID = c4d.DescID(
                # The first desc level for variadic/dynamic ports is always a sub container. For a 
                # condition node where the variadic ports can only be inputs and always are of one type,
                # this is GV_CONDITION_INPUT. Other nodes which can have variadic ports of different 
                # types (like the Python node) have different sub container IDs for inputs and outputs, 
                # e.g., IN_REAL for all real inputs, IN_STRING for all string inputs, OUT_LONG for all 
                # long outputs, etc.
                c4d.DescLevel(c4d.GV_CONDITION_INPUT, c4d.DTYPE_SUBCONTAINER, condNode.GetType()),
                # And the second desc level is the dynamic port ID, starting at 1001 for the first dynamic
                # input port, 1002 for the second dynamic input port, and so on. It unfortunately not very
                # standardized, where these start.
                c4d.DescLevel(idDynamicStart + i, c4d.DTYPE_REAL, 0)
            )
            # Set the value of the dynamic input port to some float value.
            result: bool = condNode.SetParameter(did, float((i + 1) * 10), c4d.DESCFLAGS_SET_0)
            if not result:
                print("Failed to set value on port index:", i)
    
        # Or another common workflow, we want to set a dynamic port by port label (instead of knowing
        # the index in advance).
    
        # Find the first port that starts with "Input".
        targetLabel: str = "Input"
        targetPort: GvPort | None = next((port for port in inputPorts 
                                          if port.GetName(condNode).startswith(targetLabel)), None)
        
        # We found a port with that label.
        if targetPort is not None:
            # Unlike in the Nodes API, we cannot set the value on a port directly, we always have to go
            # through the node and use a DescID to identify the port. So we must find the index of the 
            # port in the input ports list to calculate the dynamic port ID.
            index: int = inputPorts.index(targetPort)
            result: bool = condNode.SetParameter(
                c4d.DescID(
                    c4d.DescLevel(c4d.GV_CONDITION_INPUT, c4d.DTYPE_SUBCONTAINER, condNode.GetType()),
                    c4d.DescLevel(idDynamicStart + (index - 1), c4d.DTYPE_REAL, 0)
                ),
                99.9, # Set the value to 99.9
                c4d.DESCFLAGS_SET_0
            )
            if not result:
                print("Failed to set value on port:", targetPort.GetName(condNode))
    
        c4d.EventAdd()
    
    if __name__ == '__main__':
        main()
    
  • RE: How to compute Redshift MoGraph index ratios (RSMGIDRatioColor)?

    Hi @BruceC,

    correct, the RSMGIDRatioColor is (tentatively) expected to behave as index / total_number.
    You can check the index in the Transform -> Display attribute of the cloner object (check the screenshot below).

    Cheers,
    Ilia

    0deba902-1d30-474c-b518-aafd6ce079dd-image.png

  • RE: How to compute Redshift MoGraph index ratios (RSMGIDRatioColor)?

    FYI: The RS team just told me that they agree that this is a bug and that they will fix this. Please reach out via our beta forums for details.

    So, the code I showed you has an expiration date.

  • RE: How to compute Redshift MoGraph index ratios (RSMGIDRatioColor)?

    Hey @BruceC,

    Thank you for your question. I just checked, and what RS calls the index ratio does not really have much to do with indices and is instead based on the uvw coordinate of a particle. This might be a bug, but you would have to talk with the Redshift team for that. Anyway, find below some Python code to emulate computing that 'index ratio'.

    Cheers,
    Ferdinand

    Result

    I emulated your setup:

    dfbeebb4-cce6-4ca1-bf31-76e78d3966c1-image.png

    Code

    import c4d
    import mxutils
    
    doc: c4d.documents.BaseDocument  # The currently active document.
    op: c4d.BaseObject | None  # The primary selected object in `doc`. Can be `None`.
    
    
    def main() -> None:
        """Called by Cinema 4D when the script is being executed.
        """
        data: c4d.modules.mograph.MoData = mxutils.CheckType(c4d.modules.mograph.GeGetMoData(op))
        uvw: list[c4d.Matrix] = data.GetArray(c4d.MODATA_UVW)
        for i in range(data.GetCount()):
            # That is how the 'index ratio' is computed, it is effectively just the shifted x-component
            # of the uvw coordinate of the particle. The color is then just (ratio, ratio, ratio). Not very
            # 'indexy'.
            ratio: float = 1 - uvw[i].x
            print(f"{i}: {ratio}")
    
    if __name__ == '__main__':
        main()
    
  • RE: Is it possible to hide the Basic Tab in the attribute viewer?

    Hey @ECHekman,

    it depends a bit on what you do in your description, which bases you include, and what you consider NodeData instances in your case. Are you actually talking about plain NodeData hooks or are you talking about derived types such as ObjectData?

    When we look for example into Ocube.res, we can see it includes Obase:

    CONTAINER Ocube
    {
    	NAME Ocube;
    	INCLUDE Obase;  // includes the description base for all objects Obase.res
    

    Which among other things contains the Obaselist group, which is the group you mean.
    e972d7aa-5434-40d2-b219-25703f65c2d8-image.png

    You could either not include the base description for your node type, or use a modified base which for example hides all elements in that group. But I personally would say that none of these approaches are really a good idea, as Cinema 4D is designed with the assumption that each basic scene element (object, tag, etc.) includes their base description.

    Falling to do so, will either make your plugin outright not work, or - when using more 'clever' approaches such as hiding all parameters, or hiding the whole group via GetDDescription or a modified base - severely impact how users can interact with your scene element.

    When you implement a plain NodeData hook, you should not see any extra tabs, as plain node hooks should not include any bases.

    Cheers,
    Ferdinand

  • RE: set GvNode value via python

    Hey @Dunhou,

    Thank you for reaching out to us. This is probably possible, but I'll have to check with a debugger myself. These are dynamically generated ports which tend to be a pain in the butt in Xpresso, as pretty much every node type handles them slightly differently, and even by looking at our source code you often do not get any wiser. My guess would have been that it is something like c4d.DescID(c4d.DescLevel(c4d.GV_CONDITION_INPUT, c4d.DTYPE_SUBCONTAINER, condNode.GetType()), c4d.DescLevel(0, c4d.DTYPE_REAL, 0)) or the same but other values for the second desc level such as 1000, 2000, etc. instead of 0. GV_CONDITION_INPUT is defined in the description as the input group.

    I will have to attach a debugger to see what is going on, but where I am currently working from, I have no compiled version of Cinema 4D. Will check in the next days from my Mac, where I do have a compiled version. You can check this thread for context, the function PythonNodeLayerShaderAccess goes into some details (a Python node also has a dynamic/variadic number of in- and output ports).

    Cheers,
    Ferdinand

    """My attempt in setting the dynamic ID_OPERATOR_CONDITION operator in ports (does not work). 
    """
    
    import c4d
    
    from c4d.modules.graphview import GvNodeMaster, GvNode, GvPort, XPressoTag
    from mxutils import CheckType
    
    doc: c4d.documents.BaseDocument  # The currently active document.
    op: c4d.BaseObject | None  # The primary selected object in `doc`. Can be `None`.
    
    
    def main() -> None:
        """Called by Cinema 4D when the script is being executed.
        """
        null: c4d.BaseObject = CheckType(c4d.BaseObject(c4d.Onull))
        tag: XPressoTag = CheckType(null.MakeTag(c4d.Texpresso))
        master: GvNodeMaster = CheckType(tag.GetNodeMaster())
        doc.InsertObject(null)
    
        root: GvNode = CheckType(master.GetRoot())
        condNode: GvNode = CheckType(master.CreateNode(root, c4d.ID_OPERATOR_CONDITION, x=200, y=200))
    
        # Set values:
        did_1: c4d.DescID = c4d.DescID(
            c4d.DescLevel(c4d.GV_CONDITION_INPUT, c4d.DTYPE_SUBCONTAINER, condNode.GetType()),
            c4d.DescLevel(0, c4d.DTYPE_REAL, 0)
        )
        did_2: c4d.DescID = c4d.DescID(
            c4d.DescLevel(c4d.GV_CONDITION_INPUT, c4d.DTYPE_SUBCONTAINER, condNode.GetType()),
            c4d.DescLevel(1, c4d.DTYPE_REAL, 0)
        )
    
        a = condNode.SetParameter(did_1, 10.0, c4d.DESCFLAGS_SET_0)
        b = condNode.SetParameter(did_2, 20.0, c4d.DESCFLAGS_SET_0)
        print(a, b)
    
        c4d.EventAdd()
    
    
    if __name__ == '__main__':
        main()
    
  • RE: Python tag or Python node in Xpresso crash Cinema 4D

    FYI, there has been a verified fix for the Booleans crash, it will be included in an upcoming release of Cinema 4D.