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()