• Storing an object link and a mat link in a hyperfile

    2023 python
    3
    1
    0 Votes
    3 Posts
    664 Views
    i_mazlovI
    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)
  • How to create ToolBox in C++.

    s26 c++ windows
    3
    2
    0 Votes
    3 Posts
    744 Views
    P
    @ferdinand Thank you
  • TreeView DropDown Menu

    3
    0 Votes
    3 Posts
    920 Views
    ferdinandF
    Hello @simonator420, Thank you for reaching out to us. As announced here, Maxon is currently conducting a company meeting. Please understand that our capability to answer questions is therefore limited at the moment. I am slightly confused about the nature of your questions, especially in the context of the reply from @mogh. TreeViewFunctions.GetDropDownMenu lets you define the content of drop down menus in a TreeView, and the slightly ill named SetDropDownMenu lets you react to an item being selected in such menu. The 'problem' with your code snippet is that you do not differentiate the drop down gadgets which are set in GetDropDownMenu. Like many methods of TreeViewFunctions it is called for each cell in the tree view table, where lColumn denotes the column as defined in your TreeViewCustomGui.SetLayout call, and obj denotes an item in your root, so sort of the row in the tree. lColumn becomes meaningless when your tree view has only one column of type LV_DROPDOWN. How to make sense of obj, depends on the shape of the data you passed as root. When root root is just a list[object], you could for example alternate between even and odd rows like this. def GetDropDownMenu( self, root: list[object], userdata: any, obj: object, lColumn: int, menuInfo: dict): """Simple example for defining the menu content based on the position of #obj in #root. """ index: int = root.index(obj) if index % 2 == 0: menuInfo["menu"][1000] = "Even row first option" menuInfo["menu"][1001] = "Even row second option" else: menuInfo["menu"][1000] = "Odd row first option" menuInfo["menu"][1001] = "Odd row second option" menuInfo["state"] = int(menuInfo["state"]) In practice, the content of a drop down is more likely to be determined based on the fields of obj (e.g., if obj.a == "foo" then Menu1 else Menu2)rather than its relative position in root (be it a list-like or tree-like data structure). Cheers, Ferdinand
  • Cancel Option for Progress Bar Dialog?

    2023
    4
    0 Votes
    4 Posts
    998 Views
    ferdinandF
    Hey @bentraje, I would recommend using the example I posted above, only that you replace the MessageData instance with a GeDialog instance. The other thread looks overly specific, a bit overengineered with the decorator, to be a good basic example. I do not have the time to write a full working example right now, but in pseudo code it would look as shown at the end of the posting. I cannot make much out of random error messages you give me. An AttributeError means that an object does not have an attribute, be it a field/property (myObject._data, myObject.Data) or a function (myObject.SendMessage), so it means that you have not declared MyThread.SendMessage and yet try to call it. As always, we also cannot debug your code for you. Cheers, Ferdinand Code: This is untested 'pseudo-code', I wrote this 'blind'. It demonstrates a pattern and is not meant to be executable code. """Provides an example for a thread executing multiple tasks and expressing the execution state to the outside world. """ import c4d import typing class WorkerThread (c4d.threading.C4DThread): """Wraps the execution of multiple tasks expressed by a set of data in a thread. The thread exposes the total amount of tasks, already done tasks, and their results to outside observers. """ def __init__(self, data: typing.Collection) -> None: """Initializes the worker. """ self._data : typing.Collection = data # Stuff to do. self._results: list[any] = [] # Results of stuff that has been done. self._taskCount: int = len(data) # Number of things to do in total. self._finishedTaskCount: int = 0 # Number of things that already have been done. def Main(self) -> None: """Carries out the tasks. """ for item in self._data: self._results.append(self.Compute(item)) # this takes a long time to do. self._finishedTaskCount += 1 def Compute(self, *args) -> any: """Represents a computation step. """ return 0 # Read-only properties to access the data of the thread from another thread. You could also just # let the other thread directly access members (thread._results) or also use fancier things like # locks/semaphores to make this "nicer". This is a question of taste, read-only properties are # somewhat a middle ground. @property def Results(self) -> tuple[any]: return tuple(self._results) @property def TaskCount(self) -> int: return self._taskCount @property def FinishedTaskCount(self) -> int: return self._finishedTaskCount class MyTaskDialog (c4d.gui.GeDialog): """Realizes a dialog that runs a thread wrapping multiple tasks. """ ID_NEW_THREAD: int = 1000 # Id of a gadget which invokes adding a new thread. def __init__(self) -> None: """ """ # The worker thread of the dialog, could also be multiple threads as in the other example, # I kept it simple here. self._workerThread: WorkerThread | None = None super().__init__() def Command(self, mid: int, msg: c4d.BaseContainer) -> bool: """Called by Cinema 4D on GUI interactions. """ # Something with ID_NEW_THREAD has been pressed, we start try to start a new thread with the # dummy data [1, 2, 3]. if mid == MyTaskDialog.ID_NEW_THREAD and not self.StartWorkerThread([1, 2, 3]): # Do something on failure. pass return super().Command(mid, msg) def StartWorkerThread(self, data: typing.Collection, evalFrequency: int = 250) -> bool: """Starts a new worker thread and sets the evaluation frequency. """ # There is already an ongoing thread. if isinstance(self._workerThread, WorkerThread): return False # Create and start the new thread. self._workerThread = WorkerThread(data) self._workerThread.Start() self.SetTimer(max(100, evalFrequency)) return True def Timer(self, msg: c4d.BaseContainer) -> None: """Called by Cinema 4D for each timer tick. """ # Should never happen and (most) GUI functions do this own their own, more a formality. if not c4d.threading.GeIsMainThreadAndNoDrawThread(): return t: WorkerThread = self._workerThread # The thread is still running, just update the UI with the status of the thread. if t.IsRunning(): c4d.StatusSetSpin() c4d.StatusSetText(f"Running tasks: {t.FinishedTaskCount}/{t.TaskCount}") # The thread has finished, do something with the result, shut off the timer, and clear out # the UI. else: results: any = t.Results print(results) t.End() self._workerThread = None self.SetTimer(0) c4d.StatusClear()
  • Create User Data with Data Type Spline

    python 2023
    4
    0 Votes
    4 Posts
    953 Views
    ferdinandF
    Hey @pim, It is great to hear that your problem is solved. And to be clear: You can use ChatGPT and Co. to write your code; I would simply ask you to disclose it if you post code (partially) generated by a bot. We understand and agree that chat bots can be a useful tool especially for beginners. But they also have a tendency to write code that is just made up gibberish. When we know that the code has been written by a bot, we can say "that is just noise/garbage". If we do not, we have to point out all problems individually, not only to not offend the author by calling his or her code "garbage", but to also to help the user to understand his or her mistakes. Cheers, Ferdinand
  • wordwrapping difference between r23 and r2023.

    python r23 2023
    3
    0 Votes
    3 Posts
    816 Views
    D
    hi, thank you - but it has become unnecessary. i did not realize that there is a checkbox in the preferences, called "Script Word Wrap", which was activated while i was working in r23 (probably since the first installation of r23, back in the day), but deactivated (i guess, that's the default setting) in my fresh installation of r2023. it's fine now. i'll mark this one as solved.
  • Getting the spline from the SplineCustomGui

    python 2023
    2
    1
    0 Votes
    2 Posts
    524 Views
    ferdinandF
    Hello @pim, Thank you for reaching out to us. As announced here, Maxon is currently conducting a company meeting. Please understand that our capability to answer questions is therefore limited at the moment. The custom GUI for SplineData has its own dedicated GUI type, c4d.gui.SplineCustomGui as pointed out by yourself. So, you must use SplineCustomGui.GetSplineData instead of the generic BaseCustomGui.GetData. Cheers, Ferdinand
  • set only the value of a dialog gadget

    python
    6
    0 Votes
    6 Posts
    1k Views
    D
    huge thanks again @m_adam! the trick with storing the data in a dictionary was super helpful. now i can jump between documents and the dialog updates properly. cheers, sebastian
  • how to track if the document has changed?

    python
    4
    1 Votes
    4 Posts
    872 Views
    M
    Hi correct, if you look at my code I do oldDoc = self.activeDoc This means oldDoc will call the activeDoc property getter. And within it I already check for IsAlive and return None if the saved document is not alive. @property def activeDoc(self): doc = getattr(self, '_activeDoc', None) if doc is None: return None if not doc.IsAlive(): return None return doc So for my use case it's fine enough, but you are right, if you store somewhere this oldDoc variable and use it latter then yes you need to check for IsAlive. Cheers, Maxime.
  • Connect Multiply Object with saving Position/Rotation/Scale

    s26 python
    3
    0 Votes
    3 Posts
    624 Views
    i_mazlovI
    Hello @DjNikMax, Welcome to the Plugin Café forum and the Cinema 4D development 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 Guidelines, as they line out details about the Maxon SDK Group support procedures. Of special importance are: Support Procedures: Scope of Support: Lines out the things we will do and what we will not do. Support Procedures: Confidential Data: Most questions should be accompanied by code but code cannot always be shared publicly. This section explains how to share code confidentially with Maxon. Forum Structure and Features: Lines out how the forum works. Structure of a Question: Lines out how to ask a good technical question. It is not mandatory to follow this exactly, but you follow the idea of keeping things short and mentioning your primary question in a clear manner. About your First Question The goal you're trying to achieve is a unclear and the proposed steps look redundant. There're a couple of topics I'd like to mention regarding your provided code: Using doc.GetActiveObjects(c4d.GETACTIVEOBJECTFLAGS_CHILDREN) doesn't make any sense in this context, because children are removed when calling "Connect Objects + Delete" command Using c4d.CallCommand(16768) applies this command to the currently selected objects (rather than to the obj on the current loop iteration) If you process objects one-by-one there's no need to group it beforehand, the "Connect Objects + Delete" command can be run directly on the object itself. This would bake rotation and scale into the mesh itself, the position would remain exactly the same as in the original object (without any extra step, no need to instantiate the object and copy it's coordinates back to the baked mesh) If you want to zero-out object position after baking it, you might want to look into freeze transform functionality: c4d.ID_BASEOBJECT_FREEZE_P If you anyways want to deal with transformations, you can check the thread about matrices, mentioned in above. @mogh, thank you for sharing! Let me know if you still have any questions. Cheers, Ilia
  • Setting mouse position

    4
    1
    0 Votes
    4 Posts
    781 Views
    WickedPW
    @i_mazlov thanks for posting the link to the icon resource page listing. Have bookmarked it. Thanks @ferdinand, I noticed the CSS change. It's made scrolling through what were large pages more digestible and quicker. Hopefully I haven't upset too many others. But appreciated from my end. Post edit: I know this topic was about setting the mouse position, but seems like that's an OS level thing for now. So have marked topic as solved.
  • Using / copying object properties dialog

    python 2023
    5
    1
    0 Votes
    5 Posts
    771 Views
    P
    Thanks for the clear answer. I know the difference between description and dialog, but I was hoping I could re-use already defined description in a dialog. But I follow your advise and recreate the settings. Regards, Pim
  • description.SetParameter() Question

    r23 2023 python windows
    11
    0 Votes
    11 Posts
    2k Views
    ThomasBT
    @ferdinand At first I wanted to do it exactly like you recommended, but then I thought why, if you save 36 IDs, it's certainly more memory-efficient. And when you just rename it, you've already learned something again. Actually, I wanted to do it with separate IDs. But then I recognized that I need the exact same 3 parameters, just with other Labels. but since renaming was faster, I decided to do it. It's an update of the previous version of the plugin and it was easiest to just change the labels. I'll think about it, I've got time. Thank you because my English is not always the best, I sometimes find it difficult to read your texts. They are already very extensive in some cases. Good evening!
  • Defer Modifying Menus to the Main Thread

    python s26
    6
    0 Votes
    6 Posts
    2k Views
    alexandre.djA
    @ferdinand Just a quick update that using an event message to force update the view after creating the menus is working great. c4d.EventAdd(c4d.EVMSG_CHANGE)
  • Plugin Update use "disklevel" or new PluginID

    r23 python windows 2023
    3
    0 Votes
    3 Posts
    550 Views
    ThomasBT
    @ferdinand thank you very much
  • How to create Preference hierarchy child tree item

    python r23
    3
    1
    0 Votes
    3 Posts
    569 Views
    mikeudinM
    @ferdinand Thank you! I'll check it!
  • Quicktabs in dynamic prefs description

    5
    1
    0 Votes
    5 Posts
    996 Views
    a_blockA
    No worries. Thanks for trying.
  • 0 Votes
    2 Posts
    454 Views
    ferdinandF
    Hello @fkenned1, Welcome to the Plugin Café forum and the Cinema 4D development 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 Guidelines, as they line out details about the Maxon SDK Group support procedures. Of special importance are: Support Procedures: Scope of Support: Lines out the things we will do and what we will not do. Support Procedures: Confidential Data: Most questions should be accompanied by code but code cannot always be shared publicly. This section explains how to share code confidentially with Maxon. Forum Structure and Features: Lines out how the forum works. Structure of a Question: Lines out how to ask a good technical question. It is not mandatory to follow this exactly, but you follow the idea of keeping things short and mentioning your primary question in a clear manner. About your First Question Your question is a little bit ambiguous, and I am not quite sure what you want to be done here. To get something simple out of the way: Cinema 4D C4DAtom instances, the atomic type all classic API scene elements derive from, have parameters which are indexed by c4d.DescID keys. Cinema 4D does not have properties in its classic API model. I understood what you meant, just as an FYI. This is represented by the methods C4DAtom.GetParameter and .SetParameter. The methods GeListNode.__getitem__ and .__setitem__, i.e., the bracket syntax in Cube[c4d.PRIM_CUBE_SUBY], are just convenience wrappers around the C4DAtom methods. The parameter keys passed to these methods are internally always c4d.DescID, it is only the convenience methods __get/setitem__ which allow you to pass integers instead, e.g., write Cube[c4d.PRIM_CUBE_LEN,c4d.VECTOR_X] or Cube[1100, 1000] (both do the same). I went over the subject of parameter IDs in more detail here. The drag and drop behavior of the Python console is proprietary and not exposed, I also do not quite understand what you want to do with it inside a dialog. If you want to let a user reference an object in a dialog, as for example many objects do in Cinema 4D, you must use a BaseLink parameter. In dialogs you can do this with the CUSTOMGUI_LINKBOX custom gui. I have shown here how to do this. Letting a user reference a parameter in this manner is not possible. You could technically unpack parameter drag data (at least in C++, likely not in Python, have not checked though) to get hold of a dragged BaseList2D and traverse its description (possible in C++ and Python) of a node to figure out to which DescID for example the string "c4d.PRIM_CUBE_LEN, c4d.VECTOR_X" corresponds to. But I do not quite understand the purpose of all this? I am sure there is a less complicated way to achieve what you want to do. I would recommend sharing your code and explaining what you want to achieve on a high level, as the workflow you have in mind is either impossible or at least very labor intensive to implement. Cheers, Ferdinand
  • Python version suddenly reported as 3.1 in 2023.2

    sdk python 2023
    3
    1
    0 Votes
    3 Posts
    666 Views
    M
    Ok, thanks Ferdinand your clarification shed some light on my problem. I convert the version to a float which is the culprit ... import sys if sys.version_info < (3, 2): print("This Plugin requires at least Python 3.2") thank you
  • Can get Point-Index under the mouse pointer? (Py)

    python sdk
    3
    1
    0 Votes
    3 Posts
    689 Views
    ymoonY
    The mouse position is obtained as follows. msg[c4d.BFM_INPUT_X] --> GetEditorWindow() --> editor_x = win.Global2Local() --> mouse_x-abs(Editor_x) The problem of overlapping selection in GetNearestPoint() was solved by making a list of selected object. Thank you.