Get all BaseListLink parameters using specific BaseObject
-
Hey guys, I'm new to working in Cinema 4D and the Cinema 4D python SDK, I am trying to relink a BaseObject into DTYPE_BASELISTLINK parameters after running a join on a BaseObject. I'm wondering if there is a simple way to get all other objects a single BaseObject is linked within, something like a get all objects a BaseObject is referenced by. I have a code snippet below that hopefully demonstrates what I'm trying to achieve.
def join_object(obj : c4d.BaseObject): # Gets the objects that the obj is used in linked_objects = get_references(obj) # Join the object, this will remove the obj from all of its linked parameters of type DTYPE_BASELISTLINK new_obj = c4d.utils.SendModelingCommand(command=c4d.MCOMMAND_JOIN, list=[obj], doc=doc)[0] doc.InsertObject(new_obj, None, obj) obj.Remove() # Relink the new_obj into where the obj was used relink_object(linked_objects, obj) return new_obj
-
Hi @curtwart,
Welcome to the Maxon developers forum and its community, it is great to have you with us!
Getting Started
Before creating your next postings, we would recommend making yourself accustomed with our forum and support procedures. You did not do anything wrong, we point all new users to these rules.
- Forum Overview: Provides a broad overview of the fundamental structure and rules of this forum, such as the purpose of the different sub-forums or the fact that we will ban users who engage in hate speech or harassment.
- Support Procedures: Provides a more in detail overview of how we provide technical support for APIs here. This topic will tell you how to ask good questions and limits of our technical support.
- Forum Features: Provides an overview of the technical features of this forum, such as Markdown markup or file uploads.
It is strongly recommended to read the first two topics carefully, especially the section Support Procedures: Asking Questions.
About your First Question
Please excuse the delay. I'm currently a little busy trying to meet all the deadlines. I'll get back to your topic ASAP!
Could you please in the meantime clarify if you're trying to do something special (i.e. handle relinking with some custom logic) or you're more interested in a general approach of objects not losing their references?
The reason I'm asking is because keeping references up-to-date is a well-known challenge and there can be multiple ways of handling it. In Cinema C++ API there's a dedicated approach, which involves using AliasTrans class, that does the actual traversal and relinking automatically. However, AliasTrans doesn't have a python binding, hence one cannot use it in our Python API.
Cheers,
Ilia -
Hi @curtwart,
You need to manually search for the dependencies. You can do this by accessing object's description and searching for the type DTYPE_BASELISTLINK. I've pasted a very draft script below. I've stolen the tree traversal function from Ferdinand's answer here.
Please also note, that BaseLink is not the only way to keep the reference of the object, e.g. it can be addressed in the FieldList or in the InExcludeData.
Cheers,
IliaThe script traverses all the objects on the scene (you should think if checking objects only is enough), for each objects iterates over its descriptions and if DTYPE_BASELISTLINK dound, checks if it refers to our object. All these refs are stored in a list. Then the command is executed, and then the reference objects are revisited to updated the baselink value to a new object.
import c4d from typing import Iterator doc: c4d.documents.BaseDocument # The currently active document. op: c4d.BaseObject | None # The primary selected object in `doc`. Can be `None`. # Is stolen from: https://developers.maxon.net/forum/topic/15611/how-to-scale-all-objects-in-a-scene-at-once/2 def IterateTree(node: c4d.GeListNode, yieldSiblings: bool = False) -> Iterator[c4d.GeListNode]: """ Iterates over all descendants of the given #node and optionally its next siblings. """ def iterate(node: c4d.GeListNode) -> Iterator[c4d.GeListNode]: if not node: return yield node for child in node.GetChildren(): yield from iterate(child) while node: yield from iterate(node) node = node.GetNext() if yieldSiblings else None def iterateObjectBaselistLinks(op: c4d.BaseObject) -> Iterator[c4d.DescID]: """ Searches for all BaseListLinks in the object and returns the list of corresponding DescIDs. """ for description in op.GetDescription(c4d.DESCFLAGS_DESC_NONE): bc, paramId, groupId = description isBaselistLink: bool = False for descLevelIdx in range(paramId.GetDepth()): descLevel: c4d.DescLevel = paramId[descLevelIdx] if descLevel.dtype == c4d.DTYPE_BASELISTLINK: isBaselistLink = True break if not isBaselistLink: continue yield paramId def findAllObjectReferences(doc: c4d.documents.BaseDocument, op: c4d.BaseObject) -> Iterator[tuple[c4d.BaseObject, c4d.DescID]]: """ Iterates over all objects in the document and returns the objects that reference the given object. """ for rootObject in doc.GetObjects(): for obj in IterateTree(rootObject): if obj == op: # Skipping ourselves, but this might not be correct for some certain cases continue for linkID in iterateObjectBaselistLinks(obj): if obj[linkID] == op: yield obj, linkID def main() -> None: # FIXME: this only checks for a selected object, but you're likely interested in checking for all children as well linkedObjectsInfo: list[tuple[c4d.BaseObject, c4d.DescID]] = list(findAllObjectReferences(doc, op)) new_obj = c4d.utils.SendModelingCommand(command=c4d.MCOMMAND_JOIN, list=[op], doc=doc)[0] doc.InsertObject(new_obj, None, op) op.Remove() # Relink for obj, linkID in linkedObjectsInfo: obj[linkID] = new_obj if __name__ == '__main__': main() c4d.EventAdd()
-
Thank you for this write up, I started writing something similar, but this is a huge help understanding how to get and set the data for BaseLink. I'll need to add FieldList and InExcludeData as you mentioned, knowing the getting and adding the new object is a little more involved than just setting the property id directly. I'll reach out here again if I have any followups.