• 0 Votes
    2 Posts
    1k Views
    ferdinandF
    Hello @GillesKontrol, Thank you for reaching out to us. Jesus Christ, that is a lot of tags! While we do have quite a few topics on selections here on the forum, we have no dedicated Python code examples for selections on GitHub (we probably should change that). Selections are merged with BaseSelect.Merge. But there are some hoops to jump through here, which is why I have written a code example. Although my example is probably pretty close to what you want, I must remind you that we do not provide solutions in the SDK group. Any modification or improvement must be done by yourself. You should also show your code in the future, no matter how little your progress has been. Cheers, Ferdinand Scene: select.c4d Before: [image: 1707503136122-15ec7661-afbb-4fe0-889c-dd5ee6d3622b-image.png] After: [image: 1707503174138-cb1ed039-3e64-4da1-8e01-653ac47dc953-image.png] Code: """Consolidates material assignments on all selected objects. Run with at least one object selected. The script will merge all texture tags using a restriction, i.e., a selection tag, that reference the same material. The operation will be wrapped into one undo item. Note: As always, the SDK group does not provide finished solutions. If you want things to be changed, that would be up to you. Here are some short-comings. * I did not deal with the case that there is a material M referenced on an object both as a texture tag with and without a restriction. Does not make too much sense but I ignored it. * I also ignored differences in projections. The script assumes that all material assignments are just unmodified UVW projections. * Finally, I determine the "sameness" of materials by identity and not by (quasi)-equality. When there are two absolutely identical materials in a scene, or two materials which are only differentiated by their name, they and their tags will not be merged. """ import c4d import mxutils import time doc: c4d.documents.BaseDocument # The active document. def ConsolidateMaterialsOnObject(op: c4d.BaseObject) -> None: """Consolidates the texture tags and selection tags used by them on #op by reference of material. Note: In the API material tags are called texture tags. """ # The first thing we do, is build a map where we associate materials assigned to #op with the # texture tags of #op assigning them. I.e., we collect the texture tags which should be # consolidated as they are assigning the same material. # # NOTE: We use here a 2023.2 feature, C4DAtom.__hash__, to insert the materials as keys into the # dict. In earlier versions we would have to use node UUIDs for that. groups: dict[c4d.BaseMaterial, list[c4d.TextureTag]] = {} # Iterate over all texture tags in #op and store them under the material they assign. for tag in [t for t in op.GetTags() if t.CheckType(c4d.Ttexture)]: material: c4d.BaseMaterial = tag[c4d.TEXTURETAG_MATERIAL] groups[material] = groups.get(material, []) + [tag] # Now we iterate over our build mappings as pairs of a material and the texture tags which # assign it. for material, textureTags in groups.items(): # Step over materials which are assigned by less than two tags. if len(textureTags) < 2: continue # Now we pull all the selection tags of #op which are referenced in the texture tags. selectionTags: list[c4d.SelectionTag] = [] for tag in textureTags: # Since selection tags are referenced a bit weirdly as a string in texture tags, # we cannot just grab their base link and instead must search for them. restriction: str = tag[c4d.TEXTURETAG_RESTRICTION] candidates: list[c4d.SelectionTag] = [t for t in op.GetTags() if t.CheckType(c4d.Tpolygonselection) and t.GetName() == restriction] if len(candidates) != 1: raise RuntimeError( f"Found texture tag {tag} with ambiguous restriction '{restriction}'.") selectionTags.append(candidates[0]) # Now we have all the input data we need. The first thing we do is establish a unique name # for the new selection tag, the 'restriction' referenced in the texture tag, and then # create the selection tag. restriction: str = f"{material.GetName()}_{time.perf_counter_ns()}" selectionTag: c4d.SelectionTag = op.MakeTag(c4d.Tpolygonselection) selectionTag.SetName(restriction) doc.AddUndo(c4d.UNDOTYPE_NEWOBJ, selectionTag) # Now we get the BaseSelect for our new selection tag and copy over all the selection states # of the selection tags we have collected. selection: c4d.BaseSelect = selectionTag.GetBaseSelect() res: bool = all([selection.Merge(t.GetBaseSelect()) for t in selectionTags]) if not res: raise RuntimeError(f"Copying a selection tag for {material} failed.") # We do not need the old selection and texture tags anymore and therefore remove them. for t in (selectionTags + textureTags): doc.AddUndo(c4d.UNDOTYPE_DELETE, t) t.Remove() # We create a new texture tag and reference the current material from the loop and our new # selection tag in it. textureTag: c4d.TextureTag = op.MakeTag(c4d.Ttexture) textureTag[c4d.TEXTURETAG_MATERIAL] = material textureTag[c4d.TEXTURETAG_RESTRICTION] = restriction doc.AddUndo(c4d.UNDOTYPE_NEWOBJ, textureTag) def main() -> None: """ """ # Get all directly selected objects in the scene, bail when there is no selection. selection: list[c4d.BaseObject] = doc.GetActiveObjects(c4d.GETACTIVEOBJECTFLAGS_NONE) if not selection: print ("no objects selected to consolidate selection tags for.") return # Wrapped in an undo, iterate over them and compact the material assignments on them. doc.StartUndo() for obj in doc.GetActiveObjects(c4d.GETACTIVEOBJECTFLAGS_CHILDREN): ConsolidateMaterialsOnObject(obj) doc.EndUndo() # Push an update event. c4d.EventAdd() if __name__ == "__main__": main()
  • 0 Votes
    4 Posts
    867 Views
    S
    @i_mazlov I tried the GetRad() method. This is exactly what I need, thanks!
  • Material node plugin

    Cinema 4D SDK python
    2
    0 Votes
    2 Posts
    498 Views
    ferdinandF
    Hello @merkvilson, Thank you for reaching out to us. No, that is not possible and we are currently not planning to change that. You cannot implement materials in Python, you cannot implement GraphView (i.e., Xpresso) nodes, and you also cannot implement Nodes API nodes, i.e., Node Editor nodes, including the material nodes. But you can do all this in the C++ API Nodes API. But that is not nothing you do on the side, even as an experienced C++ developer. What you can do however, is implement a node using the Node Editor itself, i.e., you can just plug something together using existing nodes and the Resource Editor to give your node a more finished feeling. To ship that node, you would have to save it into an asset database and then ship that database. Cheers, Ferdinand
  • Getting radius of text during rendering

    Cinema 4D SDK 2024 python
    2
    0 Votes
    2 Posts
    570 Views
    i_mazlovI
    Hi @gsmetzer , Thank you for providing the scene that shows the problem! Your code in the Python generator should be scoped with functions and classes. The code outside of main() function is only run once. Please also make sure you're familiar with cinema threading restrictions: Threading Manual. Once you cover the code into a separate function it starts working (check the code below). Although, you are kind of misusing the python generator object (as it doesn't generate any cache). The Python Tag could probably be a better fit here. Another note is that although changing other object's attributes is somewhat acceptable, you can still fall into a trap of changes being delayed, because of lacking the recalculate pass after you've changed the value. It might be worth trying to store your text in a user data attribute of python generator. And for the text object just create an xpresso tag that drives the text value from the string in the user data attribute of python generator. This way you can fine tune the execution priority order: documentation. One more thing. I've also received the missing font error upon loading your test scene, make sure it is fixed to have the code working. Cheers, Ilia Code for your python generator: import c4d def foo(): children = op.GetChildren() widths = [] for i in range(len(children)): radius = children[i].GetRad() widths.append(round(radius.x)) return widths def main(): op[c4d.ID_USERDATA,1][c4d.PRIM_TEXT_TEXT] = str(foo()) return
  • 0 Votes
    4 Posts
    932 Views
    ferdinandF
    Hey @GordOrb, This is not possible. You are not really meant to distinguish the file names you are writing for. I also do not understand the purpose of doing what you want to do. You can already name the beauty file and the multipass file differently in the UI. And when you write out each pass on its own, Cinema 4D will already name them differently for you. There will be singular token call for all pass layers, and before that a call for the RGB/beauty layer. None of the calls for naming the file contains pass information. The inner logic of it seems to be that you first get the call for evaluating the token for a filename and after that the actual passes. The purpose of some fields in the passed data is probably more of internal than public nature, since you cannot really do much with that info due to how the token calls are coming in. In some contexts these fields might be useful when you use the Token System (C++ Manual) more manually. But at least for the passes case, this is not really true either, because when you start to render manually, you will have to fill in the pass data on your own when constructing a RenderPathData. So, long story short: this is not really possible, at least in the intended way. Upon having a closer look at the time stamps and my file output, I realized that you could try to rely on the rule 'the call after the call for the RGB pass', because that is at least how it works in my example. isMultipass: '1', pname: ' ', ptype: ' ', pid: ' -1', t: 9366346949848 // First call, irrelevant isMultipass: '1', pname: ' ', ptype: ' ', pid: ' -1', t: 9367113676312 // Time stamp of saved RGB/beauty. isMultipass: '1', pname: ' RGB', ptype: ' RGB', pid: ' -1', t: 9367113987757 // RGB/beauty pass isMultipass: '1', pname: ' ', ptype: ' ', pid: ' -1', t: 9367114252401 // Time stamp of all passes. ... If this works, this would be hack, and it would be then up to you establish if that holds true or not. To establish "what is the last call", you could have some global list into which you push things. Make sure not to store any Cinema 4D rendering data in that global list (BaseDocument, BaseContainer, RenderData) or you might end up in world of hurt. Cheers, Ferdinand Written Files: [image: 1707477127170-fc970227-90fb-437e-b5d7-b7a25a9e2da0-image-resized.png] My console dump, here I search for the time stamp 9367114252401 of my files, it is the fourth call, long before we get the calls for the passes. The only thing that comes before is a call for the RGB, i. e., the beauty pass. [image: 1707477097394-b5e92449-2ffa-4d4b-a0b7-de02968635d8-image-resized.png] Code: """ """ import c4d import mxutils import time def get_render_type(data: c4d.BaseContainer) -> str: """Dumps information about #data into the token and the console. """ # Get the render document #rdoc, and build a string for the UUID of rdoc as well as its memory # location and #data. Unsurprisingly, the mem locs are meaningless here in Python as everything # must be wrapped by Python objects in each call. In C++ you might be able to establish "sameness" # between calls by the location of #data (the rendering document which is used is always the # same as shown by its UUID). But I doubt that you will have luck with #data in C++, because it # is likely constructed for each call there too (and not reused). rdoc: c4d.documents.BaseDocument = mxutils.CheckType(data[0], c4d.documents.BaseDocument) uuid: bytes = bytes(rdoc.FindUniqueID(c4d.MAXON_CREATOR_ID)) ident: str = f"doc-uuid: {uuid}, doc-mem: {id(rdoc)}, bc-mem: {id(data)}" # Build the token. token: str = (f"isMultipass: '{rdoc.GetActiveRenderData()[c4d.RDATA_MULTIPASS_ENABLE]}', " f"pname: '{data[5]:>20}', ptype: '{data[6]:>10}', pid: '{data[7]:>3}', t: {time.perf_counter_ns()}") print (ident, token) return token if __name__ == "__main__": if "r_type" not in set(item["_token"] for item in c4d.modules.tokensystem.GetAllTokenEntries()): c4d.plugins.RegisterToken("r_type", "[Render] Type", "reg/multi", get_render_type)
  • Can't generate Plugin ID

    Moved General Talk python
    2
    0 Votes
    2 Posts
    731 Views
    ferdinandF
    Hello @merkvilson, Thank you for reaching out to us. This topic is not Cinema 4D SDK related I therefore moved it. But I can't log in to my account on the plugin cafe forum. I tried to change the password but it changed only the current account password. I assume this means that the reason for not being able to login is that you lost your password. But I am not quite sure what you mean by "current account" password. While this forum cloned the user settings and data from the old forum (Plugin Café), they are still two separate forums. So, if you want to reset your Plugin Café account, you would have to do it on https://plugincafe.maxon.net/. There is no need to keep passwords of both forums 'in sync'. I have forcibly logged you out from Plugin Café and manually sent you a password reset e-mail. I also sent you five plugin IDs you can use via PM. Cheers, Ferdinand
  • GUI Bitmap Button Properties

    Cinema 4D SDK python
    6
    1
    0 Votes
    6 Posts
    1k Views
    ferdinandF
    @merkvilson said in GUI Bitmap Button Properties: @ferdinand said in GUI Bitmap Button Properties: How to change BITMAPBUTTON_BACKCOLOR via the Command? I am not sure if I am understanding your question here correctly. You want to change the background color of a button after CreateLayout ran? That is not possible. You can flush layout groups and rebuild them at runtime (with buttons with a new color for example). I showed here once the pattern of a dynamic dialog. Is it possible to change the background color based on the toggle state? Thanks for your answers! It depends on how you define 'possible'. Dialogs in itself are static GUIs, you cannot change any of the settings you have set in CreateLayout after the method ran. It does not matter if it is the spacing of a group, the width of a text field, or the background color of a bitmap toggle button. What you can do in addition to defining the 2nd icon of the toggle button, is flush a layout group and then rebuild that group with a button which has the color you like. So, you wrap all your buttons in a group, and whenever a button is toggled on or off, you change a color in an array of button colors and then flush the buttons and rebuild them using your array of colors to set the background colors (and you also have to reapply the states then). The posting I linked to in my previous example lines out the details of how to do such dynamic dialog GUIs. Cheers, Ferdinand
  • 0 Votes
    3 Posts
    580 Views
    DunhouD
    Thanks @i_mazlov , The msg[c4d.BFM_ACTION_VALUE] is all I want, Thanks for that. Cheers~ DunHou
  • 0 Votes
    3 Posts
    807 Views
    F
    Thank you @i_mazlov It works perfectly Best regards, Tomasz
  • 0 Votes
    5 Posts
    1k Views
    B
    Hi @ferdinand Apologies for the late response. RE: You should show me how you access that key. My bad. But it's on the previous thread you answered by manually selecting a keyframe and storign it in a variable. https://developers.maxon.net/forum/topic/15344/get-selected-keyframes/3 RE: I just showed you above how to operate with percentage values in Cinema 4D. My bad. I missed it. I was looking immediately at the declaration. keyA: c4d.CKey keyB: c4d.CKey which I only want one key to be declared. === In summary, the GetNext() and GetPred() solved the problem in getting the Neighboring Keys from a Selected Key.
  • 0 Votes
    6 Posts
    2k Views
    T
    ok. I know not your department, but thats nuts. Thanks!
  • Crash from processing too many xrefs.

    Cinema 4D SDK r25 python macos
    6
    0 Votes
    6 Posts
    1k Views
    ferdinandF
    Hey @Visualride-0, you seem to run into some form of memory leak. That you open that many files in a row on one Cinema 4D instance is of course not really intended. This could either be a l smaller leak in our API that simply manifests more or only at all when you load that many files. Generally, you could separate out the file iteration into an external script, a shell script or a vanilla Python script if you want, and then simply run c4dpy with your script per file or for batches of 10 or 100 files. Starting and shutting down c4dpy 8000 times in a row will of course take its toll in time, so one would be incentivized to bundle up more files to bring down that number of starts and shutdowns. I had a look at your code, and apart from your redundant document insertion, I do not see much wrong with it, as it is basically just the loop on fileList. Cinema 4D should have caught your mistake there and prevented you from opening the same file twice, but I have not tested it. for f in filesList: # This line will load #f into a BaseDocument, insert it into the opened files, and make it the # active document. Unlike for BaseDocument.LoadDocument, the following lines are therefore # redundant. c4d.documents.LoadFile(f) # doc = c4d.documents.GetActiveDocument() # c4d.documents.InsertBaseDocument(doc) # c4d.documents.SetActiveDocument(doc) c4d.documents.SaveDocument(c4d.documents.GetActiveDocument(), f, c4d.SAVEDOCUMENTFLAGS_0, c4d.FORMAT_C4DEXPORT) # This does what it should do, close the active document, and create a new one. But to be # ultra safe, you could also call c4d.documents.CloseAllDocuments(). c4d.documents.KillDocument(doc) What escapes mea bit too, is the purpose of what we are doing here, since we just overwrite files with itself. But I guess this is minimized code, so it is only meant to demonstrate the problem. For my own understanding, the files in fileList are all *.c4d files, right? Fixing your code as shown above will likely not fix the problem, it seems more likely to me that there is a memory leak somewhere. For an intermediate solution, you will probably have to do what I proposed above, split this into batches of Cinema 4D or c4dpy instances to flush your memory by shutting down Cinema 4D. But we would like to have a look at the problem. Could you provide a sample of the documents, something between 10 or 100 files, so that we can test it with your data? You can send me a cloud storage link via a PM on the forum or via a mail to sdk_support(at)maxon(dot)net. Mail attachments will not work here due to the 50MB limitation. We strive for Cinema 4D being memory leak free, and if there is a severe leak we are not aware of, we should fix it. But this might take time. Cheers, Ferdinand
  • Setting the name of a maxon.GraphNode via Python

    Cinema 4D SDK 2023 python
    3
    0 Votes
    3 Posts
    614 Views
    PoliigonP
    Hey Maxime, thanks for the quick answer. Actually I thought, I had tried SetValue() with that ID. But either I did something wrong or I tried something else... anyway, thanks. Just checked: Yes, I did something wrong. I stupidly just passed a string instead of a maxon.String. With the latter it works, not that I doubted your proposal. Maybe would be good, if SetValue() could throw an error, if a wrong type gets passed instead of just doing nothing. Also thanks for the pointer to Dunhou's project, I was already aware. Nice project! And indeed I peeked into it regarding some Arnold peculiarities... Cheers, Andreas
  • Get Selected Keyframes?

    Cinema 4D SDK 2023 2024 python
    6
    0 Votes
    6 Posts
    1k Views
    ferdinandF
    Hey @bentraje, yes, we are aware that internal and private tags are something that plagues our documentation as they often have been abused by developers to skip documentation. But that is not so easy to fix. I for example did and still do not know the purpose NBIT_TLX_SELECT2 either. I was just experienced enough with the C4D API to poke in this place first. Physically fixing the docs, i.e., adding a blab here or there, is not the problem. The problem is to evaluate if the private tag in C++ (which then radiates into Python) is well founded or not, especially for ancient things like this. I would have to read a lot of code to make an assessment if this should be private or not, and even then would not be sure. And even an 'Expresses the selection state of f-curve keys. @markprivate' is problematic because for that I would have to be sure that it does not have a weird side effect. Cheers, Ferdinand
  • Xref object Make it Editable

    Cinema 4D SDK 2024 python
    3
    2
    0 Votes
    3 Posts
    730 Views
    chuanzhenC
    @ferdinand Thanks for your reply!
  • UserArea drag and drop example?

    Cinema 4D SDK windows 2024 python
    6
    0 Votes
    6 Posts
    2k Views
    K
    I tried using a timer to solve this problem, but I still want to know if there is a more direct way import c4d import threading from c4d.gui import GeUserArea, GeDialog GADGET_ID_GEUSERAREA = 10000 class DropArea(GeUserArea): def __init__(self): # Used to store all objects involved in the drag-and-drop operation self.currentDragObjects = [] # Flag to indicate whether a drag operation is in progress self.isDragging = False # Define a timer to delay the handling of the drag completion self.dragTimer = None def Message(self, msg, result): # Handle drag-and-drop messages if msg.GetId() == c4d.BFM_DRAGRECEIVE: # Check if the drag was lost or canceled if msg.GetInt32(c4d.BFM_DRAG_LOST) or msg.GetInt32(c4d.BFM_DRAG_ESC): self.isDragging = False return self.SetDragDestination(c4d.MOUSE_FORBIDDEN) # If the drag just started, clear the previous object list if not self.isDragging: self.currentDragObjects = [] # Initialize the storage list self.isDragging = True # Mark the beginning of the drag # Verify if it is a valid drop area if not self.CheckDropArea(msg, True, True): return self.SetDragDestination(c4d.MOUSE_FORBIDDEN) # Get the dragged file object dragInfo = self.GetDragObject(msg) if dragInfo is not None: dragObject = dragInfo['object'] # Check if the object already exists in the list to avoid duplicates if dragObject not in self.currentDragObjects: self.currentDragObjects.append(dragObject) # Reset the timer to delay the handling of drag completion if self.dragTimer is not None: self.dragTimer.cancel() # Set a short timer (e.g., 0.2 seconds) to determine if the drag operation is complete self.dragTimer = threading.Timer(0.2, self._finalize_drag) self.dragTimer.start() # Set the mouse cursor to a valid state return self.SetDragDestination(c4d.MOUSE_MOVE) # Call the base class Message() method to handle other messages return c4d.gui.GeUserArea.Message(self, msg, result) def _finalize_drag(self): # Delayed execution to ensure all dragged objects have been received self.isDragging = False if self.currentDragObjects: # Print all dropped files print(f"Dropped files: {self.currentDragObjects}") # Additional logic can be executed here, e.g., handling file paths or other content # Clear the object list for the next drag-and-drop operation self.currentDragObjects = [] # Redraw the user area (if UI update is needed) self.Redraw() class ExampleDialog(GeDialog): geUserArea = DropArea() def CreateLayout(self): self.SetTitle("Drag Area") if self.GroupBegin(0, c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, cols=1, rows=0, title="", groupflags=0, initw=100, inith=100): self.GroupBorderSpace(8, 8, 8, 8) self.GroupSpace(2, 2) # Add the user area gadget self.AddUserArea(GADGET_ID_GEUSERAREA, c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, 200, 200) # Attach the user area to the gadget self.AttachUserArea(self.geUserArea, GADGET_ID_GEUSERAREA) self.GroupEnd() return True if __name__ == "__main__": global dlg dlg = ExampleDialog() dlg.Open(dlgtype=c4d.DLG_TYPE_ASYNC, defaultw=200, defaulth=200)
  • Character Defenition Tag - maxon.Id?

    Cinema 4D SDK 2024 python
    3
    0 Votes
    3 Posts
    681 Views
    jochemdkJ
    Thx Maxim, so I'll have to wait until the next version..
  • Reset Tool in Interaction Tag

    Cinema 4D SDK 2024 python
    4
    2
    0 Votes
    4 Posts
    818 Views
    ferdinandF
    Hey @CJtheTiger, just as a clarification, it is obvious that you have put quite a bit of effort in your posting. So, that was not meant in the sense of "what a terrible posting". But especially for postings which contain a lot of detail, it is important to put the question at the very beginning so that it is clear what is the question. Regarding the axis behavior thing, I now understand how you mean that. The Interaction Tag (and Tooling) is not owned by the SDK group, so we would not be responsible for this case either, we only own all the "pure" Python stuff. What I thought was your request before, changing the general default value, had probably almost zero changes of being implemented. This request of yours sounds logical (I am not a big expert on the interaction tag) but given how niche that case is, and that it would require customization in tools just for that case, I do not see a high chance that this will ever be implemented either. But if you truly desire that feature, you should still submit the wish, because a lot of user requests for the same thing are something we cannot and will not ignore. Cheers, Ferdinand