Maxon Developers Maxon Developers
    • Documentation
      • Cinema 4D Python API
      • Cinema 4D C++ API
      • Cineware API
      • ZBrush GoZ API
      • Code Examples on Github
    • Forum
    • Downloads
    • Support
      • Support Procedures
      • Registered Developer Program
      • Plugin IDs
      • Contact Us
    • Categories
      • Overview
      • News & Information
      • Cinema 4D SDK Support
      • Cineware SDK Support
      • ZBrush 4D SDK Support
      • Bugs
      • General Talk
    • Unread
    • Recent
    • Tags
    • Users
    • Login

    Storing an object link and a mat link in a hyperfile

    Cinema 4D SDK
    2023 python
    3
    3
    583
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • P
      pim
      last edited by

      I have a commandplugin with two CUSTOMGUI_LINKBOX.
      One for an object link and one for a material link.

      How do I store these links in a hyperfile?

      b31a48f9-8baa-443e-a073-5eeb200c03f4-image.png

      1 Reply Last reply Reply Quote 0
      • fwilleke80F
        fwilleke80
        last edited by

        You can use HyperFile::WriteGeData() to store your GeData in a HyperFile.

        www.frankwilleke.de
        Only asking personal code questions here.

        1 Reply Last reply Reply Quote 0
        • i_mazlovI
          i_mazlov
          last edited by

          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)
          

          MAXON SDK Specialist
          developers.maxon.net

          1 Reply Last reply Reply Quote 1
          • First post
            Last post