• 0 Votes
    1 Posts
    5 Views
    No one has replied
  • Finding out the latest asset version number/string via python

    python 2026
    6
    0 Votes
    6 Posts
    61 Views
    ferdinandF
    Hey @MPB, there is no need to be sorry, sometimes one struggles even with very on the nose information. But the example is quite verbose, as it contains literally the words "version" and "timestamp" multiple times and explains how these concepts work. But it is pointless to endlessly discuss this, it is quicker for me to just write what you want. Here is an example doing exactly what you want to do. Please understand that it is an exception that I write a code example when there is already an example which already explains the subject sufficiently as it is here the case. Cheers, Ferdinand Result What we also learn from this, is that the asset team works at unholy hours . Latest version of asset file_266f97c45ea05f17 has version string '2.0.2 - 2022-07-30 02:29', version hash '4905fdf9a02aa951e0d18a6d07f244172a41205a4692b4ae065d6b856e5de7cb' and timestamp '2022-07-30 00:29:28'. Found 4 asset versions for asset id file_266f97c45ea05f17: Found asset version with version string '2.0.2 - 2022-07-30 02:29', version hash '4905fdf9a02aa951e0d18a6d07f244172a41205a4692b4ae065d6b856e5de7cb' and timestamp '2022-07-30 00:29:28'. Found asset version with version string '2.0.1 - 2022-02-03 23:45', version hash '3997955fa22c74286c9319509eeaa7a5dccd0230951b608fe338741bbce0d9f9' and timestamp '2022-02-03 22:45:45'. Found asset version with version string '2.0.0 - 2022-02-03 23:26', version hash '310c1a6684d8be9eafff1708225b64fe72bff46bd7fae8fa783c39fac7c70d43' and timestamp '2022-02-03 22:26:39'. Found asset version with version string '1.0.1 - 2022-02-02 03:02', version hash '5fafa425cbd3f951d4ce8859fc02f51857342094dd0c2fcac8eb4377b7cea9dd' and timestamp '2022-02-02 02:02:47'. Assets sorted by timestamp: file_266f97c45ea05f17/5fafa425cbd3f951d4ce8859fc02f51857342094dd0c2fcac8eb4377b7cea9dd (1.0.1 - 2022-02-02 03:02) file_266f97c45ea05f17/310c1a6684d8be9eafff1708225b64fe72bff46bd7fae8fa783c39fac7c70d43 (2.0.0 - 2022-02-03 23:26) file_266f97c45ea05f17/3997955fa22c74286c9319509eeaa7a5dccd0230951b608fe338741bbce0d9f9 (2.0.1 - 2022-02-03 23:45) file_266f97c45ea05f17/4905fdf9a02aa951e0d18a6d07f244172a41205a4692b4ae065d6b856e5de7cb (2.0.2 - 2022-07-30 02:29) Code #coding: utf-8 """Provides an example for sorting assets by their time stamp metadata. """ __version__ = "2026.X.X" import c4d import maxon def main() -> None: """Runs the example for reading asset metadata. """ # Get the user preferences repository. repo: maxon.AssetRepositoryRef = maxon.AssetInterface.GetUserPrefsRepository() if not repo: raise RuntimeError("Could not access the user preferences repository.") # The id of the "Stone 01" asset which naturally has four asset versions. aid: maxon.Id = maxon.Id("file_266f97c45ea05f17") # Find explicitly the latest version of the asset. asset: maxon.AssetDescription = repo.FindLatestAsset( maxon.AssetTypes.File(), aid, maxon.Id(), maxon.ASSET_FIND_MODE.LATEST) # Get the time stamp and version hash and string of the latest asset version. metadata: maxon.AssetMetaData = asset.GetMetaData() timestamp: str = metadata.Get(maxon.ASSETMETADATA.ASSET_TIMESTAMP) version_hash: str = asset.GetVersion() version_string: str = maxon.AssetInterface.GetVersionString(asset) print(f"Latest version of asset {aid} has version string '{version_string}', " f"version hash '{version_hash}' and timestamp '{timestamp}'.") # Now find all versions of assets with the id #aid and print their metadata. assets: list[maxon.AssetDescription] = repo.FindAssets( maxon.AssetTypes.File(), aid, maxon.Id(), maxon.ASSET_FIND_MODE.ALL) print(f"\nFound {len(assets)} asset versions for asset id {aid}:") for item in assets: metadata = item.GetMetaData() timestamp = metadata.Get(maxon.ASSETMETADATA.ASSET_TIMESTAMP) version_hash = item.GetVersion() version_string = maxon.AssetInterface.GetVersionString(item) print(f"Found asset version with version string '{version_string}', " f"version hash '{version_hash}' and timestamp '{timestamp}'.") # So, if we wanted to temporally sort #assets, we could do this. The asset version is naturally # a hash, and there is no grantee that there is a version string or that that string is parsable # into a numeric value which could be sorted. assets.sort(key=lambda a: a.GetMetaData().Get(maxon.ASSETMETADATA.ASSET_TIMESTAMP)) print("\nAssets sorted by timestamp:") for item in assets: print(f"{item} ({maxon.AssetInterface.GetVersionString(item)})") if __name__ == "__main__": main()
  • Getting some weird console output from GeDialog.Timer()

    python 2026
    3
    0 Votes
    3 Posts
    25 Views
    B
    Thank you @Dunhou ! That solved it!
  • Object-level "Show Help" (CMD+F1) for ObjectData plugins?

    macos 2025 2026 python
    2
    0 Votes
    2 Posts
    30 Views
    ferdinandF
    Hey @lasselauch, Thank you for reaching out to us. I am not 100% sure that I am understanding you correctly. You basically want to hook into this menu entry, right? [image: 1768249550647-650df545-8d6c-4444-a525-a4fe70bee750-image.png] That is not possible at the moment. Because what this thing does, is gather information from the description of the selected entity or the active dialog and with that data calls cinema::OpenHelpBrowser (at least the backend version of that function). This is not even a dedicated command, just a switch case within the abstracted dialog menu handling. So, this is custom built for help.maxon.net. It would not be impossible to isolate this so that there could be either a dedicated plugin hook for this or it somehow reusing the existing RegisterPluginHelpDelegate (the C++ variant of the Python hook you used). But that would be quite a bit of work, and you would also have to answer if that justifies the overhead of calling all hooks each time a user presses that button/menu entry (but you could also argue that the overhead of RegisterPluginHelpDelegate is even worse). I can see the allure of "Show Help" working for third parties, but I doubt many people would use it and the current system is very Maxon centric which are not good arguments for going for this. On top of this, in theory, it would have to support both NodeData entities and dialogs (because the menu entry works for both). We could only support nodes, but there I would just recommend the proven and tested workflow of including a base description at the end of your nodes, which places there a bitmap icon branding that is clickable or just a button. I talked a bit in the all new Licensing Manual videos and code about this workflow. edit: An alternative could be to offer a hook into OpenHelpBrowser but there you probably then run into problems with dialogs as the back end function splits into two signatures (which do not exist in the frontend). Also solvable but again extra work that can hardly be justified but the few users this will have. I am not strictly against adding such hook, but I currently do not see a good cost/effect ratio unless this thread is flooded with third party developers stating otherwise. Cheers, Ferdinand
  • How to access animation tracks for a GraphNode in a capsule

    2026 python
    3
    0 Votes
    3 Posts
    76 Views
    K
    Hi @ferdinand , Thank you for your detailed answer. I will look into the details and try to understand the concepts. For now the issue is solved. Thank you as always.
  • CommandData.Message() implementation and Message concepts

    python 2023
    5
    0 Votes
    5 Posts
    920 Views
    K
    @John_Do said in CommandData.Message() implementation and Message concepts: seasoned baselislienhanMO
  • 0 Votes
    3 Posts
    156 Views
    lasselauchL
    Thank you, Ferdinand. (Again!) That was exactly what I needed. It's working great now! Thanks for taking the time to answer this so thoroughly and quickly! Cheers, Lasse
  • How to change the Node spaces

    python 2025 windows
    2
    0 Votes
    2 Posts
    87 Views
    ferdinandF
    Hello @gelobui, Welcome to the Maxon developers forum and its 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 procedures. You did not do anything wrong, we point all new users to these rules. Forum Overview: Provides a broad overview of the fundamental structure and rules of this forum, such as the purpose of the different sub-forums or the fact that we will ban users who engage in hate speech or harassment. Support Procedures: Provides a more in detail overview of how we provide technical support for APIs here. This topic will tell you how to ask good questions and limits of our technical support. Forum Features: Provides an overview of the technical features of this forum, such as Markdown markup or file uploads. It is strongly recommended to read the first two topics carefully, especially the section Support Procedures: How to Ask Questions. About your First Question It depends a bit on how you mean your question. There is GetActiveNodeSpaceId which allows you to get the ID of the current node space. But there is no setting equivalent of that function. So, you cannot set a node space by its ID. What you can do, is call the command which switches node spaces. These are however dynamically assigned and can have a different meaning, depending on how many render engines are installed. You can just check the script log after changing the space. On this installation I have for example no extra render engines or node spaces installed, therefore Redshift is there 72000, 4. [image: 1765458881919-a0da1ed7-7add-456e-a8cc-63d8bd1ced2a-image.png] But on this machine I have the C++ SDK installed and therefore the Example nodes space, so Redshift is now 72000, 5: [image: 1765459007165-49b0838b-49d5-4f14-b638-811d8d26ada4-image.png] When you really want to do this in a fail safe manner, you would have to parse the menu of Cinema 4D to know with which sub-id to call CallCommand. Cheers, Ferdinand
  • Educational Licenses

    python windows 2025
    9
    1
    0 Votes
    9 Posts
    421 Views
    DunhouD
    @ferdinand Thanks for your great examples! Very helpful!
  • How to get edge 'island' selection

    python 2026
    6
    1
    0 Votes
    6 Posts
    207 Views
    ferdinandF
    Hey @BretBays, Let me answer a few things, I although I still have the feeling we are not at the bottom of things yet. So my idea(based off a maya tool used at work) is to be able to select those loops myself with edges, and have it run the interpolation on all the loops at once(well, at once as far as clicking apply once and it does each loop for you). You can of course implement a point, edge, or polygon loop or ring selection yourself. But my advice would be to use the builtin tool programmatically unless you really have to implement your own tool. Because while a simple loop selection is relatively trivial, a production level loop tool is then quite a bit of work, due to all the edge cases you have to handle. The issues I am running into is that it seems like working with edge selections is very cumbersome in Cinema. [...] I don't know I just am having a hard time wrapping my head around these concepts in Cinema. Is it possible to pass in an edge ID and work with edge ID's or do you have to go through the polygon info and all of that to get them? Cinema 4D does not store edges explicitly, as this would unnecessarily increase the size of a scene. One can sufficiently describe polygonal geometry as a set of points for the vertices, and a set of ordered quadruples of point indices for each polygon. This is common practice in 3D applications and called 'indexed face set'. You always have to go through the polygons and points to work with edges. Edges are effectively just a smoke and mirrors convenience feature for end users. One could argue how much front-end wrappers for edges an API requires to be easy to use, but I would say Cinema 4D is there at least okay. You can find helper functions for edges on PolygonObject and SendModelingCommand supports edge selections directly. In short, for each perceived user edge E_p, exist n 'real' or 'raw' edges for the indexed face set, where n is either 1 or 2 when the is mesh manifold (or larger when non-manifold). If n=1, then E_p is a boundary edge, otherwise it is an internal edge shared by two polygons. This is due to these two polygons having two have opposite winding orders for that shared edge when the polygons are meant to face into the same direction. The following diagram illustrates this (arrows indicate the winding order of the polygons): a- → -b b- → -e | | | | ↑ P ↓ ↑ Q ↓ | | | | d- ← -c c- ← -f Fig. 1: Two polygons P and Q sharing the user perceived edge E_p defined by the points b and c. The lower case labels denote unique point identifiers in the indexed face set, not a point order within the polygon. The polygon P is defined as (a, b, c, d) and the polygon Q as (b, e, f, c), i.e., a and b are the first vertex of each polygon respectively. The arrows describe the winding order of the polygons. The global raw edge index is defined as rawEdgeIndex = polygonIndex * 4 + localEdgeIndex. E.g., when P would have the polygon index 2 and Q the polygon index 6, then the user perceived edge E_p would correspond to the two raw edges indices p_bc = 2 * 4 + 1 = 8 (edge bc in P which is the second edge, i.e. local index 1) and q_cb = 6 * 4 + 3 = 27 (edge cb in Q which is the fourth edge, i.e. local index 3). Here are some code examples and forum posts about working with edges in Cinema 4D's Python API: geometry_polgyon_edges_2024: This is the official example script showing how to work with polygon edges in Cinema 4D 2024. It explains how to access and identify edges in a polygon object. Select Edges by Length: An example that shows how to select edges based on their length. Select Polygons Facing into the Same Direction: Not directly related to edges, but I talk here about the fundamental concept of a winding order, which is important when working with polygon edges. Cheers, Ferdinand
  • KeyFrame User Data from Xpresso driven User Data

    python
    3
    2
    0 Votes
    3 Posts
    94 Views
    ferdinandF
    Hey @JoelJohera, it is kind of hard to follow your question and solution, as the former lacks a scene for context and for the latter I am not quite sure what you fixed. But when it works for you I am happy Cheers, Ferdinand
  • BaseLink across documents

    c++
    4
    0 Votes
    4 Posts
    174 Views
    ferdinandF
    Hey @WickedP, As I hinted at above, markers are eternally persistent. I.e., you can unload, load, and modify a scene S or the non-cache* node N in it, the node N will always have the same marker. The flag you found has a very special purpose and also very misleading documentation (I just fixed that). Each node in a scene must have a unique marker, as otherwise not only BaseLink but also things like undo and more won't work properly when not each node has exactly one marker and each marker exactly one node (i.e., a bijective or 'one-to-one' relation). But there can be bad actors such as bugs in our codebase or third party plugins which violate that rule. To combat that, Cinema 4D checks the uniqueness of markers of nodes when a scene is being loaded. When it finds duplicate markers, it will open a question dialog, asking the user if he/she wants to repair that scene. When the user says 'yes', only the nodes which had duplicate markers will get a new marker each (so that they are in a bijective marker relation again). This flag you found will suppress this whole behavior, i.e., it will let you load corrupted scenes as is. I would not recommend using it. I am currently not 100% sure what happens when your LoadDocument call is not DIALOGSALLOWED, it looks a bit like that this check then always never runs (which sounds a bit dangerous). Cheers, Ferdinand edit: okay now I see it, the question dialog part has been commented out, so this runs always without asking the user (and with that also without DIALOGSALLOWED), unless you pass the flag NONEWMARKERS. [*] Object and other nodes in caches, i.e., the partial scene graph returned by BaseObject::GetCache and generated by ObjectData::GetVirtualObjects, are allocated each time the cache is being built and therefore also have a new marker each time. But you cannot (or better should not) try to build base links, undos, xrefs, etc. for object and other nodes in caches. TLDR: Markers do not work for caches.
  • Dynamically Add Massign to Material in Material Editor

    c++
    2
    0 Votes
    2 Posts
    83 Views
    ferdinandF
    Hey @ECHekman, I currently use INCLUDE Massign in my .res file. However i would like to do this dynamically so that I can change the order at which it is placed in the Material Editor and Attribute Editor. Is there a way to do this? There is no INCLUDE which you could call programmatically in GetDDescription, but there is Description::LoadDescription. When Cinema 4D is asking you to update a certain part of your description, you could try to call it on your description instance but not with its id, but Massign. This is however very likely to fail or cause crashes, because your description is already being processed, I never tried this myself though. Another approach could be to allocate a dummy description, then load your resource (e.g., Massign) into that dummy description, to then copy parameters bit by bit into your active/actual description passed into GetDDescription. But the whole approach of a 'dynamic include' is a bit backwards without context why it has to be this way. Because when you dynamically include something, this also means you have to remove existing data where it shall not be included anymore. Which is possible but unnecessary extra work. Also copying a whole partial description is not a great idea performance wise, GetDDescription is being called a lot. The better approach would be to just include your partial description in your res file in the a place where it might appear and then dynamically change the parent of that container. As this workflow would be the common dynamic description workflow. Additionally Also is there a way to hide settings from the Obaselist group or hide settings in the Material Editor but keep them in the Attribute editor? No, both managers are more or less just a DescriptionCustomGui, they will always show the same data, unless you have access to the implementation (which you do not). When you would implement your own material dialog, with your own DescriptionCustomGui in it, you could mangle what is shown in this particular description view to your liking, there are multiple methods to highlight, filter, and modify a description on that class. To operate this dialog you would then set this description GUI to the material, shader or whatever BaseList2D you want to display, and then either filter or outright modify the description which is being displayed. Cheers, Ferdinand
  • Adjust spline layers settings in Field Force Objects with Python?

    r25 python
    2
    0 Votes
    2 Posts
    129 Views
    ferdinandF
    Hey @Simon-Lucas, Welcome to the Maxon developers forum and its 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 procedures. You did not do anything wrong, we point all new users to these rules. Forum Overview: Provides a broad overview of the fundamental structure and rules of this forum, such as the purpose of the different sub-forums or the fact that we will ban users who engage in hate speech or harassment. Support Procedures: Provides a more in detail overview of how we provide technical support for APIs here. This topic will tell you how to ask good questions and limits of our technical support. Forum Features: Provides an overview of the technical features of this forum, such as Markdown markup or file uploads. It is strongly recommended to read the first two topics carefully, especially the section Support Procedures: How to Ask Questions. About your First Question Do you really mean you are on R25? Or do you mean you are on 2025? Anyway, please share an example scene and the code you have so far. Otherwise we won't be able to help you. Cheers, Ferdinand
  • VertexMap Display (Behavior Equivalent to Double-Click)

    2026 python windows
    3
    0 Votes
    3 Posts
    122 Views
    ymoonY
    @ferdinand Thank you. It works well. --> tag.Message(c4d.MSG_EDIT)
  • 0 Votes
    5 Posts
    244 Views
    chuanzhenC
    @ferdinand Thanks for reply Other channels(BOX,X-Ray....) also seem to be unfeasible, and currently it seems only can accept this limitation (affected by depth of field) Due to the current not to transfer to C++, some plugin ideas are indeed somewhat unusual only using some simple C4D drawings to generate Bitmaps, so I did not pay attention to the handling of Ocio. I will carefully read the manual on this aspect in the document.
  • How do I return a list of names and ids for a drop down menu?

    windows python 2025
    3
    0 Votes
    3 Posts
    125 Views
    ferdinandF
    Hey @lionlion44, yes, that is the correct answer. The subject comes up from time to time, here is an answer of mine which is about the very case of yours - discovering substance channels. Cheers, Ferdinand
  • How to get the fully resolved file path of an upcoming rendering?

    2
    0 Votes
    2 Posts
    129 Views
    ferdinandF
    Hey @BigRoy, Thank you for reaching out to us and your question. We do not allow for topics that are a collection of questions from one user. New questions mandate new topics, unless they are clear follow up questions on an existing topic. The reason for this is so that this forum remains a searchable database. Please create new topics on your own in the future. I have forked your topic. Your topic is also lacking an actual question. Here I can infer your question, but for more complex subjects I might not be able to. It is important to have a literal question close to the start of your topic. See Support Procedures: How to Ask Questions and the examples below for what makes a good technical question. From the context I am assuming here the question is: Is there an API function that returns the full physical file path of a rendered file for a given render setting and in particular the Save > Name format? [image: 1762787643631-34d82dba-bf2f-411a-8489-a8dccc5d9f77-image.png] The answer is unfortunately that no such function exists. When you need this feature, you have to hard code it yourself. Cheers, Ferdinand
  • Best place to do version conversion for scenes and assets

    c++
    2
    0 Votes
    2 Posts
    93 Views
    ferdinandF
    Hey @ECHekman, Thank you for your question. This is a tricky one. When NodeData::Read is emitted, Cinema 4D is currently reading a file and in the process of deserializing your node; so it indeed is not yet 'fully instantiated', as that is the whole point of the method. You are here not meant to manipulate the scene graph (the node is not yet attached), but read and store data on your plugin hook. I.e., you are supposed to do something with your MyMaterialData and not the GeListNode/BaseMaterial which is being passed in as the first argument of Read. The common workflow is to read data from the HyperFile which is passed in and store it on your NodeData instance. Passed in is also the important version number (which is called for some odd reason disklevel) of your HyperFile container. As always, you are bound to the main thread for scene graph manipulations. So, NodeData::Init is not a good candidate when wanting to poke around in the scene graph, as it can be called off main thread and on dettached dummy nodes. A good approach is to hook into NodeData::Message and listen for MSG_DOCUMENTINFO to catch a document just having been loaded. This is for your specific case of wanting to manipulate nodes which have been deserialized. If you want to manipulate node instantiation, MSG_MENUPREPARE is a god choice. But it is only called for nodes which have been instantiated from direct user interactions such as a menu click. So, MSG_MENUPREPARE is not being emitted for nodes loaded from a file. The drill to do what you want to do would be: [optional] Overwrite NodeData::Read/Write/CopyTo to read, write, and copy custom data, and possibly use/detect different disk levels to catch different versions of a node type being loaded. You would have to match this with incrementing the disk level in your RegisterMaterialPlugin call, so that there are old and new versions of your material type out there. Hook into the document being loaded, check the scene state, and possible data left behind for you by NodeData::Read and ::Init. Manipulate the scene graph to your liking. Cheers, Ferdinand
  • How to compute Redshift MoGraph index ratios (RSMGIDRatioColor)?

    2025
    8
    2
    0 Votes
    8 Posts
    312 Views
    B
    @ferdinand Thanks!