Hi @pim,
Thanks for reaching out to us. The links are document-specific so they only make any sense in the context of the document that they were created in. It means that once stored, restoring them back would only correspond to the exact same objects in the exact same document. If it's not your case, then you'd need to define the criteria of what you mean by two objects being the same (is it only a name or a combination of the name with any other properties, e.g. hierarchy?).
However, if the context of these objects stays the same, then you can use the same trick as @ferdinand mentioned in the point selection thread. Namely, using MAXON_CREATOR_ID for extracting the UUIDs of the objects. These UUIDs can be stored in the Hyperfile (just as simple strings). So whenever you need to restore them, you would need to traverse the document tree and compare the actual object UUID with the one you're restoring.
One can also use GetClassification() function to retrieve the type of the object, which would make traversing the document a little more efficient.
Please find the sample script implementing the explained approach below.
Let me know if you have any further questions.
Cheers,
Ilia
Cinema_4D_IfZOkEU8lw.gif
import c4d
LINK_OBJ_ID = 1001
LINK_MAT_ID = 1002
BTN_SAVE_ID = 1003
BTN_LOAD_ID = 1004
BTN_RESET_ID = 1005
HF_IDENT = 49545
PATH = 'd:\\_tmp\\lnkbox.bin'
class MainDialog(c4d.gui.GeDialog):
def __init__(self):
self.linkBoxes : dict[int, c4d.gui.BaseCustomGui] = {}
def CreateLayout(self):
self.GroupBegin(2001, c4d.BFH_FIT, cols=1)
self.linkBoxes[0] = self.AddCustomGui(LINK_OBJ_ID, c4d.CUSTOMGUI_LINKBOX, "Obj", c4d.BFH_SCALEFIT, 100, 4)
self.linkBoxes[1] = self.AddCustomGui(LINK_MAT_ID, c4d.CUSTOMGUI_LINKBOX, "Mat", c4d.BFH_SCALEFIT, 100, 4)
self.GroupEnd()
self.GroupBegin(2002, c4d.BFH_FIT, cols=3)
self._btnSave = self.AddButton(BTN_SAVE_ID, c4d.BFH_SCALEFIT, name="Store links")
self._btnLoad = self.AddButton(BTN_RESET_ID, c4d.BFH_SCALEFIT, name="Reset links")
self._btnSave = self.AddButton(BTN_LOAD_ID, c4d.BFH_SCALEFIT, name="Load links")
self.GroupEnd()
return True
def Command(self, id, msg):
if id == BTN_SAVE_ID:
self.save(PATH)
elif id == BTN_LOAD_ID:
self.load(PATH)
elif id == BTN_RESET_ID:
self.reset()
return True
@staticmethod
def GetUUID(node: c4d.C4DAtom) -> bytes:
"""Returns an UUID for #node which identifies it over reallocation boundaries"""
if not isinstance(node, c4d.C4DAtom):
raise TypeError(f"{node = }")
data: memoryview = node.FindUniqueID(c4d.MAXON_CREATOR_ID)
if not isinstance(data, memoryview):
raise RuntimeError(f"Could not access UUID for: {node}")
return bytes(data)
@staticmethod
def traverseSubtree(bl : c4d.BaseList2D):
"""Half-recursively iterates over baselist elements and its children"""
while bl:
yield bl
for child in MainDialog.traverseSubtree(bl.GetDown()):
yield child
bl = bl.GetNext()
@staticmethod
def traverseDocument(doc : c4d.documents.BaseDocument, callBack, classification):
"""Executes callback for each document element depending on classification"""
if doc is None:
raise ValueError("doc is None")
bl : c4d.BaseList2D = None
if classification == c4d.Obase:
bl = doc.GetFirstObject()
elif classification == c4d.Mbase:
bl = doc.GetFirstMaterial()
for op in MainDialog.traverseSubtree(bl):
if not callBack(op): # callback returns false if no further traversing needed
return
def reset(self):
"""Reset links in the gui"""
for lnkbox in self.linkBoxes.values():
lnkbox.SetLink(None)
def save(self, path):
"""Store links UUID and Classification in the Hyperfile"""
bcFile = c4d.BaseContainer()
hf = c4d.storage.HyperFile()
if hf.Open(ident=HF_IDENT, filename=path, mode=c4d.FILEOPEN_WRITE, error_dialog=c4d.FILEDIALOG_NONE):
for idx, lnkbox in self.linkBoxes.items():
lnk : c4d.BaseList2D = lnkbox.GetLink(c4d.documents.GetActiveDocument())
if lnk is None:
print("No link selected!")
continue
uuid : str = MainDialog.GetUUID(lnk).hex()
bc = c4d.BaseContainer()
bc[0], bc[1] = uuid, lnk.GetClassification()
bcFile.SetContainer(idx, bc)
hf.WriteContainer(bcFile)
else:
c4d.gui.MessageDialog("Couldn't open file for writing")
hf.Close()
def load(self, path):
"""Unpack UUIDS from Hyperfile and search for corresponding objects"""
uuid : str = None
obj : c4d.BaseObject = None
classification : int = 0
def process(op):
"""Callback lambda: store object once the correct one has been found"""
nonlocal obj
if op is not None and MainDialog.GetUUID(op).hex() == uuid:
obj = op
return False
return True
hf = c4d.storage.HyperFile()
if hf.Open(ident=HF_IDENT, filename=path, mode=c4d.FILEOPEN_READ, error_dialog=c4d.FILEDIALOG_NONE):
bcFile : c4d.BaseContainer = hf.ReadContainer()
for idx, lnkbox in self.linkBoxes.items():
bc : c4d.BaseContainer = bcFile.GetContainer(idx)
uuid, classification = bc[0], bc[1]
MainDialog.traverseDocument(c4d.documents.GetActiveDocument(), process, classification)
if uuid is not None and obj is not None:
lnkbox.SetLink(obj)
else:
c4d.gui.MessageDialog("Couldn't open file for reading")
hf.Close()
if __name__=='__main__':
dlg = MainDialog()
dlg.Open(c4d.DLG_TYPE_ASYNC, defaultw=256, xpos=-2, ypos=-2)