Xpresso & Python: Update an object's user data from other object...



  • Hello people!

    I am working on "procedural" objects (3d models) using xprersso.
    Each object (built under a Null object that holds the xpresso tag) has its own parameters under their user data and these objects are stored in content browser and "merge" whichever I want intomy current project.

    I can use (insert / merge) as many objects as I need in a new project and very often these objects may have the "parent - child" relation ship, meaning that the "parent" object may overwrite the parameters(user data fields) of the "child". Of cource this is optional (to overwrite or not the "child" parameters).

    Here is the help i need.
    I provide an image with an example, containg 3 objects: Parent , Object_A and Object_B.
    I whould like to have the oportunity to define if the Object_A parameters ("width" & "depth") should be overwritten from the specific Parent object's parameters by dragging the Parent object (Null) into the A_Parent_Object field (link).
    Every object (except Parent) by default includes itself in that field.

    After all this long speech , in a few words I would like from the xpresso editor, Object_A to access the Parent->width value.
    Of course I need to do it by scripting and not just by dragging the Parent object inside the Object_A xpresso editor and get the parameters values (width & depth)

    Any help would be appreciatted!
    Thank in advance.

    PROJECT_IMAGE.png .



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


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


  • Zipit thanks for responding man!

    I will give it a shot and will come back to you.
    Yes you are right... The objects are not nested.
    So, instead of "Parent-Child" i could rephrase as a "Guide-Guided" relationship.

    As long as i can see in your scipt, you read the "Parent" userdata fields and check if userdata exists in the "Object_A". If not, the script creates them and fill their values... THE USERDATA EXIST IN ALL OBJECTS BY DEFAULT.
    Each object functions on its own. With its own parameters (userdata fields).
    And optionaly i can set in any of the GUIDED objects the "parent_obect" as the object that its parameters will overwrite the desired original parameter of the "guided" object (by dragging the Parent object into the Link field of the guided object). Then i would like to get the parent's parameter (for example "width", or "depth") and paste it into the Guided's parameter i want..
    Anyway, THANK YOU VERY MUCH!!!
    I will try to work this around and if i get stuck i will let you know!
    THANKS AGAIN



  • Hi,

    the script does not create any user data in the children, it only creates an InExcludeData on the parent, the object it is attached to, to let you express these relationships.

    The script also works the other way around. You place the tag on the parent, which then in turn drives the attributes of the nodes linked in the mentioned InExcludeData. You will have to adapt the example to your needs as it was exactly that: an example ;)

    I'am also seeing just now, that I somehow wrote problematically in my last posting where I actually meant programmatically, lol.

    Cheers,
    zipit



  • Zipit THANK YOU!!!

    I retrieved Parent's values (Width & Depth)
    Now, I dont know how to change Child's values...
    I send you the changes I made on your guiding script.

    It would be great if you could give me a "final push" so i could solve my weakness on Python.
    Anyway, you already HELPED ME A LOT!!!

    Thanks again man!!!

    import c4d
    #Welcome to the world of Python
    
    def get_userdata_symbols(node):
        
        # 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 get_Parent_object(node):
    
        node_symbols = get_userdata_symbols(node)
        print "Child= " , node
        # ---------> Parent Object
        descid_linked_objects = node_symbols["Parent_Object"]
        inexclude_data = node[descid_linked_objects]
        ParentNode_symbols = get_userdata_symbols(inexclude_data)
        # Parent Object's parameters
        if "Parameters" in get_userdata_symbols(inexclude_data):
            # get Width
            Wdthid_linked_objects = ParentNode_symbols["Width"]
            Winexclude_data = inexclude_data[Wdthid_linked_objects]
            print "Width= ", Winexclude_data
            # get Depth
            Dpthid_linked_objects = ParentNode_symbols["Depth"]
            Dinexclude_data = inexclude_data[Dpthid_linked_objects]
            print "Depth= ", Dinexclude_data
            
            return True
        else:
            return False
    
    
    def main():
        node = op.GetObject()  
        # Get Parent Object (type of Link)
        if get_Parent_object(node):
            # GOT THE PARENT's VALUES' OK... 
            print "now how to change Object_A values with Parent's values?"
        else:
            print "Error: Parameters of Parent object are not corect"
    


  • Hi,

    I would like to help you, but I am not quite sure what you are trying to achieve from looking at your code. If you want to pull the data to the node the tag is sitting on, instead of pushing it, you only have to switch the argument order of the call to set_node_attributes from node, target to target, node in my original example. I also just spotted a minor bug in my code.

    return [inexclude_data.ObjectFromIndex(doc, i)
                for i in range(inexclude_data.GetObjectCount() - 1)]
    

    should be:

    return [inexclude_data.ObjectFromIndex(doc, i)
                for i in range(inexclude_data.GetObjectCount())]
    

    Cheers,
    zipit



  • Hey, sorry for jumping so late on the topic @Tasos-Magkafas, unfortunately, I don't have a lot more to add than what @zipit said.
    So thanks a lot @zipit for the help provided in the topic and in general, it's greatly appreciated.

    @zipit do you have any example about the GvNode.AddPort still being broken? Because for me it's working nicely if I'm on the main thread of course.

    Cheers,
    Maxime



  • Thanks Maxime for jumbing on this topic.
    I would like to explain again what is that I am looking for.

    In c4d R21 , I have 2 objects (at least, there may be more…) that both are driven by UserData (“Parameters” group containig several fileds).

    The one object (OBJECT_A) has only 4 fields in its “Parameters” userdata group.
    The other (PARENT) may have 40, 50 or 60 fields in its “Parameters” userdata group…
    I want to be able while on OBJECT_A, to retrieve PARENT’s specific userdata field values and overwrite the OBJECT_A initial userdata values of specific fields.
    I have a Python tag on OBJECT_A and from that Python Tag I have to retrieve specific PARENT’s data (I need only 1 or 2 fields, lets say “Width” and “Depth”).
    (see screenshot)
    NEW-Object_List.png
    Zipit helped me and I have retrieved the data from PARENT. (THANKS ZIPIT)
    I just can’t proceed on passing these values to specific fields of OBJECT_A.

    That’s all… First time I use Python and any help would be appreciated.
    Thanks in advance!



  • @m_adam said in Xpresso & Python: Update an object's user data from other object...:

    @zipit do you have any example about the GvNode.AddPort still being broken? Because for me it's working nicely if I'm on the main thread of course.

    Hi,

    being broken might be putting it a bit harshly, but I was referring to this post of yours. Call it being in a only very conditionally usable state if you like ;) And if I am not mistaken, the same conditions do apply here.

    Cheers,
    zipit



  • @Tasos-Magkafas said in Xpresso & Python: Update an object's user data from other object...:

    Thanks Maxime for jumbing on this topic.
    I would like to explain again what is that I am looking for.

    In c4d R21 , I have 2 objects (at least, there may be more…) that both are driven by UserData (“Parameters” group containig several fileds).

    The one object (OBJECT_A) has only 4 fields in its “Parameters” userdata group.
    The other (PARENT) may have 40, 50 or 60 fields in its “Parameters” userdata group…
    I want to be able while on OBJECT_A, to retrieve PARENT’s specific userdata field values and overwrite the OBJECT_A initial userdata values of specific fields.
    I have a Python tag on OBJECT_A and from that Python Tag I have to retrieve specific PARENT’s data (I need only 1 or 2 fields, lets say “Width” and “Depth”).
    (see screenshot)
    NEW-Object_List.png
    Zipit helped me and I have retrieved the data from PARENT. (THANKS ZIPIT)
    I just can’t proceed on passing these values to specific fields of OBJECT_A.

    That’s all… First time I use Python and any help would be appreciated.
    Thanks in advance!

    Thanks for the additional explanation, this makes sense and what zipit said is correct, if you can't really trust that user data ID is the same, then you should rely on the name while it could fail in a case of multiples parameters named identically.

    So swapping node, target to target, node should be enough to do what you want.

    I rechecked the previous AddNode stuff and as said the xpresso was embedded within a generator, here the xpresso things are exposed within the scene so normally it should just work.

    Cheers,
    Maxime.



  • @m_adam
    Dear Maxime thanks for everything!
    But, unfortunally now that I almost finished this part I realized that all this scripting doen't give me the desired procedural result. It just changes the values once but if i change the values of the PARENT object the OBJECT_A (child) is not updated...
    Anyway I have to approach it in a different way.

    I think I should tag this thread as "SOLVED" and in case I need again help i create a new one.
    Please advise me Maxime.

    • And for one more time THANK YOU both , Maxime and Zipit

Log in to reply