Hi,
The most important thing first: Using the description element names as identifiers is bound to go wrong at some point. You have been warned ;)
You are also talking about objects being in a hierarchical relationship, but if I am not mistaken, the objects in your screenshot are not in such relation? So I assume the objects are not actually hierarchically linked?
Aside from that: GvNode.AddPort
is still broken, which rules out problematically (oh, you, autocomplete !) programmatically modifying your GraphView
(Xpresso). But you could just use a Python Scripting Tag to write the attributes from the parent to all children. With the main "hurdle" being unpacking the attribute name, DescID
relationships. Here is an example on how you could do that. You will find more details in the module docstring.
Cheers,
zipit
"""Put this script into a Python Scripting Tag.
The tag will automatically add an InExludeData user data element called
"Attribute-Linked Objects" to the node it is attached to. Adding objects
to this list will link their attributes of matching name to the attributes
of the node of the tag.
The relevant functions would be get_userdata_symbols() (for extracting name
and descid pairs from a node) and set_node_attributes() (for writing the
attributes form the source to the target). The rest is more or less fluff.
"""
import c4d
def get_userdata_symbols(node):
"""Returns a hash map of the user data element name and element id pairs
for a node.
Args:
node (c4d.C4DAtom): The atom for which to inspect the description.
Returns:
dict[str: c4d.DescID]: The element_name, element_id pairs.
"""
# Get the description of the node.
description = node.GetDescription(c4d.DESCFLAGS_DESC_NONE)
# Build the hash map, we only keep elements where the first
# DescLevel of the DescID is the user data id.
return {bc[c4d.DESC_NAME]: descid
for bc, descid, _ in description
if descid[0].id == c4d.ID_USERDATA and descid.GetDepth() > 1}
def set_node_attributes(source, target):
"""Writes the user data from source to target.
For each user data element in target, with an element in source of
matching name, the value from source will be written to target.
Args:
source (c4d.BaseList2D): The node to read from.
target (c4d.BaseList2D): The node to write to.
"""
# Get the element name, element id dictionaries
source_symbols = get_userdata_symbols(source)
target_symbols = get_userdata_symbols(target)
# Go over all user data elements in the source and write them to the
# target when the name and type matches.
for name, source_id in source_symbols.items():
if name in target_symbols:
target_id = target_symbols[name]
# Filter out elements that have not the same type.
if isinstance(source[source_id], type(target[target_id])):
target[target_id] = source[source_id]
def get_linked_objects(node):
"""Returns the attribute linked objects in the user data of a node.
If the user data element "Attribute-Linked Objects" is not present, it
will be build.
Args:
node (c4d.BaseList2D): The node the Python Scripting Tag is attached to.
Returns:
list[c4d.BaseObject] or empty list: The attribute linked objects.
Raises:
TypeError: When the user data element with the name "Attribute-
Linked Objects" is not of type InExcludeData.
"""
def create_user_data(node):
"""Adds a InExcludeData user data element to the node.
"""
bc = c4d.GetCustomDataTypeDefault(c4d.CUSTOMDATATYPE_INEXCLUDE_LIST)
bc.SetString(c4d.DESC_NAME, "Attribute-Linked Objects")
bc.SetString(c4d.DESC_SHORT_NAME, "Attribute-Linked Objects")
node.AddUserData(bc)
return
# Check if there is the user data element present. If not, build it and
# return an empty list.
node_symbols = get_userdata_symbols(node)
if "Attribute-Linked Objects" not in get_userdata_symbols(node):
create_user_data(node)
return []
# Get the descid of the InExcludeData and retrieve the object.
descid_linked_objects = node_symbols["Attribute-Linked Objects"]
inexclude_data = node[descid_linked_objects]
# Catch "Attribute-Linked Objects" not being of type InExcludeData
if not isinstance(inexclude_data, c4d.InExcludeData):
msg = ("'Attribute-Linked Objects' is occupied by non-InExcludeData "
" user data element.")
raise TypeError(msg)
# Get the objects from the InExcludeData and return them as a list.
return [inexclude_data.ObjectFromIndex(doc, i)
for i in range(inexclude_data.GetObjectCount() - 1)]
def main():
"""Entry point.
"""
# The object the tag is sitting on.
node = op.GetObject()
# For each object in the InExcludeData write the attributes.
for target in get_linked_objects(node):
set_node_attributes(node, target)