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
[image: 1684233709032-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)