• How to detect Global Illumination state via Python API?

    python 2024
    3
    0 Votes
    3 Posts
    31 Views
    mfersaouiM
    @ferdinand Hey @ferdinand, Thank you again for the clarification — checking the Video Post list was indeed the right direction. Just to share what worked on my side (Standard/Physical renderer), I check the presence of the GI Video Post and use its BIT_VPDISABLED state to determine whether it’s enabled. Here’s the small helper function I’m using: def is_gi_active(doc): rd = doc.GetActiveRenderData() if not rd: return False # GI Video Post IDs (verified on C4D 2024) GI_IDS = [ 1021096, # Global Illumination Video Post (C4D 2024) 300001038, # VPglobalillumination (older versions) ] vp = rd.GetFirstVideoPost() while vp: if vp.GetType() in GI_IDS: return not vp.GetBit(c4d.BIT_VPDISABLED) vp = vp.GetNext() return False Thanks again for your help. Best regards, Mustapha
  • 2025 SDKs missing files

    2025 c++ windows
    2
    0 Votes
    2 Posts
    11 Views
    ferdinandF
    Hello @atg, 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 I can see why would think that, but you mixed things there a bit up. The CMake SDK build system was introduced with 2025.2.0 and became the standard with 2026.0.0. Between 2025.2 and 2025.3 we supported both build systems as a grace period for developers to get accustomed. Chances are very good, that you can just copy the CMake setup, i.e., the cmake folder and files such as CMakeLists.txt, CMakePresets.json, and sdk_modules.txt to a 2025.0 folder and it will generate a correct build system for you. But the supported range is only 2025.2+. For older projects you would have to use the old project tool based build system. Since I know what you are trying to do, I would recommend trying copying before you get into the old build system of ours. 2025.2 Release Notes 2025.2 Build System Docs (which covered both the old Project Tool and CMake) Cheers, Ferdinand edit: You will only find the old project tool tooling in old extended SDKs which supported it, such as 2025.2 or 2025.0.1
  • Tile rendering with Cinema 4D

    python 2026
    5
    0 Votes
    5 Posts
    92 Views
    K
    Thanks @ferdinand . I was about to add to this forum post that the rendered images are bit darker than actual. Thanks for the code link. Here's my updated script with changes for OCIO and I was able to get it rendering correctly at least locally. Anything to avoid or missed here? import c4d import os doc: c4d.documents.BaseDocument op: c4d.BaseObject | None def EnsureIsOcioDocument(doc: c4d.documents.BaseDocument) -> None: if doc[c4d.DOCUMENT_COLOR_MANAGEMENT] is not c4d.DOCUMENT_COLOR_MANAGEMENT_OCIO: doc[c4d.DOCUMENT_COLOR_MANAGEMENT] = c4d.DOCUMENT_COLOR_MANAGEMENT_OCIO doc.UpdateOcioColorSpaces() if c4d.threading.GeIsMainThreadAndNoDrawThread(): c4d.EventAdd() def main() -> None: """Renders the scene as a grid of tiles, then reassembles into the final image.""" # --- Configure these --- tiles_x = 2 tiles_y = 2 output_dir = os.path.join(os.path.expanduser("~"), "Desktop", "tiles") final_path = os.path.join(output_dir, "final_assembled.png") # ------------------------ os.makedirs(output_dir, exist_ok=True) EnsureIsOcioDocument(doc) if not doc.GetDocumentPath(): c4d.gui.MessageDialog("Please save the document first.") return # Work directly on the document's render data, matching the reference pattern. renderData: c4d.documents.RenderData = doc.GetActiveRenderData() data: c4d.BaseContainer = renderData.GetDataInstance() requiresBaking: bool = data[c4d.RDATA_FORMATDEPTH] is c4d.RDATA_FORMATDEPTH_8 xRes: int = int(data[c4d.RDATA_XRES_VIRTUAL] or data[c4d.RDATA_XRES]) yRes: int = int(data[c4d.RDATA_YRES_VIRTUAL] or data[c4d.RDATA_YRES]) tile_w = xRes // tiles_x tile_h = yRes // tiles_y # Determine save bit flags based on format depth if data[c4d.RDATA_FORMATDEPTH] is c4d.RDATA_FORMATDEPTH_16: save_bits = c4d.SAVEBIT_16BITCHANNELS elif data[c4d.RDATA_FORMATDEPTH] is c4d.RDATA_FORMATDEPTH_32: save_bits = c4d.SAVEBIT_32BITCHANNELS else: save_bits = c4d.SAVEBIT_NONE # Save original render region state to restore later orig_region = data[c4d.RDATA_RENDERREGION] orig_region_left = data[c4d.RDATA_RENDERREGION_LEFT] orig_region_top = data[c4d.RDATA_RENDERREGION_TOP] orig_region_right = data[c4d.RDATA_RENDERREGION_RIGHT] orig_region_bottom = data[c4d.RDATA_RENDERREGION_BOTTOM] orig_bake_flag = data.GetBool(c4d.RDATA_BAKE_OCIO_VIEW_TRANSFORM_RENDER) if requiresBaking: data[c4d.RDATA_BAKE_OCIO_VIEW_TRANSFORM_RENDER] = False tile_bmps = {} for ty in range(tiles_y): for tx in range(tiles_x): left = tx * tile_w top_ = ty * tile_h right = left + tile_w bottom = top_ + tile_h data[c4d.RDATA_RENDERREGION] = True data[c4d.RDATA_RENDERREGION_LEFT] = left data[c4d.RDATA_RENDERREGION_TOP] = top_ data[c4d.RDATA_RENDERREGION_RIGHT] = right data[c4d.RDATA_RENDERREGION_BOTTOM] = bottom # Always render as 32-bit float bmp = c4d.bitmaps.MultipassBitmap(xRes, yRes, c4d.COLORMODE_RGBf) bmp.AddChannel(True, True) print(f"Rendering tile ({tx}, {ty}) region=({left},{top_})-({right},{bottom})") result = c4d.documents.RenderDocument( doc, data, bmp, c4d.RENDERFLAGS_EXTERNAL, ) if result != c4d.RENDERRESULT_OK: print(f"Tile ({tx}, {ty}) failed with code: {result}") # Restore original settings before returning data[c4d.RDATA_RENDERREGION] = orig_region data[c4d.RDATA_RENDERREGION_LEFT] = orig_region_left data[c4d.RDATA_RENDERREGION_TOP] = orig_region_top data[c4d.RDATA_RENDERREGION_RIGHT] = orig_region_right data[c4d.RDATA_RENDERREGION_BOTTOM] = orig_region_bottom data[c4d.RDATA_BAKE_OCIO_VIEW_TRANSFORM_RENDER] = orig_bake_flag return # Bake OCIO and null profiles — exactly as reference if requiresBaking: bmp = c4d.documents.BakeOcioViewToBitmap(bmp, data, c4d.SAVEBIT_NONE) or bmp bmp.SetColorProfile(c4d.bitmaps.ColorProfile(), c4d.COLORPROFILE_INDEX_DISPLAYSPACE) bmp.SetColorProfile(c4d.bitmaps.ColorProfile(), c4d.COLORPROFILE_INDEX_VIEW_TRANSFORM) # Crop tile preserving bit depth tile_bmp = bmp.GetClonePart(left, top_, tile_w, tile_h) if tile_bmp is None: print(f"Tile ({tx}, {ty}) crop failed") continue tile_path = os.path.join(output_dir, f"tile_{tx}_{ty}.png") tile_bmp.Save(tile_path, c4d.FILTER_PNG, c4d.BaseContainer(), save_bits) print(f"Saved {tile_path}") tile_bmps[(tx, ty)] = tile_bmp # Restore original render data settings data[c4d.RDATA_RENDERREGION] = orig_region data[c4d.RDATA_RENDERREGION_LEFT] = orig_region_left data[c4d.RDATA_RENDERREGION_TOP] = orig_region_top data[c4d.RDATA_RENDERREGION_RIGHT] = orig_region_right data[c4d.RDATA_RENDERREGION_BOTTOM] = orig_region_bottom data[c4d.RDATA_BAKE_OCIO_VIEW_TRANSFORM_RENDER] = orig_bake_flag # --- Reassemble using GetPixelCnt/SetPixelCnt to preserve full bit depth --- print("Assembling final image...") # Get bit depth from the first tile first_tile = tile_bmps.get((0, 0)) if first_tile is None: print("No tiles to assemble") return tile_bpp = first_tile.GetBt() bpc = tile_bpp // 3 if bpc == 32: color_mode = c4d.COLORMODE_RGBf inc = 12 elif bpc == 16: color_mode = c4d.COLORMODE_RGBw inc = 6 else: color_mode = c4d.COLORMODE_RGB inc = 3 final_bmp = c4d.bitmaps.BaseBitmap() final_bmp.Init(xRes, yRes, depth=tile_bpp) row_buffer = bytearray(tile_w * inc) row_view = memoryview(row_buffer) for ty in range(tiles_y): for tx in range(tiles_x): if (tx, ty) not in tile_bmps: continue tile_bmp = tile_bmps[(tx, ty)] dst_x = tx * tile_w dst_y = ty * tile_h for py in range(tile_h): tile_bmp.GetPixelCnt( 0, py, tile_w, row_view, inc, color_mode, c4d.PIXELCNT_0 ) final_bmp.SetPixelCnt( dst_x, dst_y + py, tile_w, row_view, inc, color_mode, c4d.PIXELCNT_0 ) final_bmp.Save(final_path, c4d.FILTER_PNG, c4d.BaseContainer(), save_bits) print(f"Saved assembled image to {final_path}") c4d.bitmaps.ShowBitmap(final_bmp) c4d.gui.MessageDialog(f"Done! {tiles_x * tiles_y} tiles rendered and assembled.\n{final_path}") if __name__ == "__main__": main()
  • CUSTOMGUI_QUICKTAB trigger twice when click

    sdk python 2023 windows
    7
    0 Votes
    7 Posts
    478 Views
    ferdinandF
    hey @Gene, So, you were just trying to add information for future readers? That is of course very welcome and explains my inability to extract a question from your posting. I would not have been that generous with the word bug either. Since this has never been fixed, chances are high that we came to the same conclusion internally. In general, you should not expect Command calling patterns to make perfect sense in every case. There are multiple cases where Command is triggered more often than one would think at first glance. I would also not try to interpret some deeper sense into all that. Cheers, Ferdinand
  • Advice on implementing undo/redo in a Tool

    c++ 2026
    3
    0 Votes
    3 Posts
    24 Views
    SteveHill3DS
    Thank you for the pointers. I will digest the examples.
  • C++ SDK Matrix object style distribution

    2026 c++
    3
    0 Votes
    3 Posts
    108 Views
    dexD
    Thank you so much Ferdinand! I have a way forward now for my plugin using the EffectorData ModifyPoints() method. Kind regards Fredrik
  • EventAdd doesn't work with a modal dialog in C4D 2026

    2026 python windows
    2
    0 Votes
    2 Posts
    80 Views
    ferdinandF
    Hey @aturtur, Thank you for reaching out to us. EventAdd will never really work in script manager scripts in the sense you mean it, unless you use hacks like dangling async dialogs (which as I always point out are a really bad idea). The reason is that Script Manager scripts are blocking, i.e., all scene and GUI execution is being halted until the script finishes. You can hack yourself around this with a dangling async dialog, i.e., a dialog that lives beyond the life time of its script. But that is not a good idea, you should implement some form of plugin to host your asnyc dialog, as you otherwise risk crashes. A modal dialog is just an extension of this. It is right in the name, it is modal, i.e., synchronous. All scene and GUI execution is being halted while this dialog is open and only resumes once it closes. When you want updates while your dialog is open, you need an async dialog (and a plugin which hosts it). Cheers, Ferdinand Since you also might misunderstand the nature of EventAdd() I am also putting here the C++ docs I updated a few weeks ago, to better reflect the nature of it (not yet live): /// @brief Enqueues an update event for the active document. /// @details Only must be called when modifying the active document and is without meaning for other documents. The typical example of using `EventAdd` is after adding or removing elements from the active document; and wanting these changes to be reflected in the UI. The function itself is technically thread-safe, but the vast majority of operations that require calling `EventAdd` are not thread-safe and must be called from the main thread (and therefore calling this function is usually main thread bound). The function also does not enqueue a dedicated event item, but rather sets a flag that is checked when the next update event is processed. Therefore, calling `EventAdd` multiple times in one function scope is unnecessary overhead which must be avoided. Because such multiple event flags cannot be consumed while a function on the main thread is still running, and instead the event will only be consumed after that function returns. /// @code /// Result<void> AddCubes() /// { /// CheckState(maxon::ThreadInterface::IsMainThread(), "AddCubes must be called from the main thread."_s); /// /// // EventAdd(); // We could also technically call it here with the same effect. The event /// // will only happen after this function returns. /// /// BaseDocument* doc = GetActiveDocument(); /// for (int i = 0; i < 10; ++i) /// { /// BaseObject* cube = BaseObject::Alloc(Ocube); /// if (!cube) /// return maxon::OutOfMemoryError(MAXON_SOURCE_LOCATION, "Failed to allocate cube object."_s); /// /// doc->InsertObject(cube); /// /// // Calling EventAdd here would have no extra effect, since this event cannot be consumed while /// // our main thread function is still running. And such extra calls on a large scale can cause /// // considerable overhead. /// } /// /// // Notify C4D that the active document has changed. The very end of a function or scope is the /// // canonical place to call EventAdd(). /// EventAdd(); /// } /// @endcode /// @see The article @link page_manual_coremessages Core Messages@endlink for more information. /// @param[in] eventflag The event to add: @enumerateEnum{EVENT}
  • Render Token Questions

    4
    0 Votes
    4 Posts
    92 Views
    ferdinandF
    @tommyf said in Render Token Questions: The frame padding I was referring to are in regards to the frame numbering that is written out when selecting a render pattern in the output settings that you would find under Render Settings > Save > Name where the one we typically use is Name.0000.TIF. Yes, that was already my hunch, as described above, and you cannot alter this numbering with render tokens, you can only add your own. When you want to change this numbering, you would have to write yourself a post processor as lined out above. Technically, you could also just run this outside of Cinema 4D, e.g., commandline.exe -someScene.c4d ... c4dpy.exe renameScript.py -someScene.c4d # Or just even just a CPython instance (or any other scripting language), the problem here is # that you could not access the the render settings of the scene and would have to rely on conventions. python renameScript.py -someScene.c4d Based on how you answered my questions it sounds like as long as I load the plugin script that contains the registration code that I could create custom tokens that way. I can also therefore create my own $frame token with my own frame padding (python str.zfill method) for frame numbers however there is no way to override the 4 digit frame padding that comes from the render settings name field. Yes. But you could not name it $frame, because that would collide with the builtin token of the same name. When the pattern my_render_frame-000001_001.tif works for you, I would definitely go for the render token. When you absolutely have to change the postfix set by Cinema 4D, that is possible too, but much more work. The work lies there not in actually renaming the files, that is somewhat trivial. The work lies in supporting all the ways a rendering can happen. When you are locked into using the commandline on your farm only, that would simplify things and drive down the costs of doing this (i.e., you would not support picture viewer, teams, editor, etc. renderings). Cheers, Ferdinand
  • 0 Votes
    3 Posts
    85 Views
    pislicesP
    Hi @ferdinand, I appreciate the reply! I didn't have a chance to update this post until now, but over the weekend I also found the Graph Descriptions Manual you've linked. The Scalar Ramp example in there was enough to help me figure out how to implement it with the Ramp node. Thank you for your response though, I'm sure it will make things easier if anyone else comes across this subject!
  • Set View Transform in Picture Viewer

    c++ 2026
    2
    0 Votes
    2 Posts
    79 Views
    ferdinandF
    Hey @ECHekman, Thank you for reaching out to us. The answer to your question is sort of yesn't. You can set the embedded view transform of a bitmap, which by default will be used by the Picture Viewer. The Picture Viewer like most managers in Cinema 4D is sealed, and we do not want to change that. So, you cannot change what view transform override is used by the Picture Viewer (when the user chooses to ignore the view transform embedded into an image). See Manage Bitmap OCIO Color Profiles for details. Cheers, Ferdinand
  • Access Node Material Path Redshift 2026

    2026 python windows
    2
    0 Votes
    2 Posts
    114 Views
    R
    hi there, I actually did sort this out in a very round about way with some "vibe coding". this was for a mapp creation project I am working where I am displaying various eras of map onto 20km grids (so as not to kill the viewport functionality). have a look at the below and let me know if this is a solid approach or if there was a better way: import c4d import maxon def main(): era = "INSERT_YOUR_ERA_HERE" basePath = "INSERT_YOUR_PATH_HERE" prefix = f"map_{era}_tile_20k_" extension = ".tif" doc = c4d.documents.GetActiveDocument() if doc is None: return nodeSpaceId = maxon.Id("com.redshift3d.redshift4c4d.class.nodespace") textureNodeId = maxon.Id("com.redshift3d.redshift4c4d.nodes.core.texturesampler") for mat_index in range(1, 9): mat_name = f"column_{mat_index:02d}" mat = doc.SearchMaterial(mat_name) if not mat: print(f"Material {mat_name} not found.") continue nodeMat = mat.GetNodeMaterialReference() if nodeMat is None: print(f"{mat_name} is not a node material.") continue graph = nodeMat.GetGraph(nodeSpaceId) if graph.IsNullValue(): print(f"No Redshift graph for {mat_name}.") continue textureNodes = [] maxon.GraphModelHelper.FindNodesByAssetId(graph, textureNodeId, False, textureNodes) with graph.BeginTransaction() as transaction: for node in textureNodes: node_name = node.GetValue(maxon.NODE.BASE.NAME) if not node_name: print(f"Unnamed node in {mat_name}, skipping.") continue node_name = str(node_name) try: local_index = int(node_name) except: print(f"Non-numeric node name '{node_name}' in {mat_name}, skipping.") continue global_index = (mat_index - 1) * 11 + local_index filename = f"{prefix}{global_index:03d}{extension}" full_path = basePath + filename tex0 = node.GetInputs().FindChild( "com.redshift3d.redshift4c4d.nodes.core.texturesampler.tex0" ) if tex0.IsNullValue(): print(f"No tex0 on node '{node_name}' in {mat_name}") continue pathPort = tex0.FindChild("path") if pathPort.IsNullValue(): print(f"No path port on node '{node_name}' in {mat_name}") continue pathPort.SetDefaultValue(maxon.Url(full_path)) print(f"{mat_name} → Node '{node_name}' set to {full_path}") transaction.Commit() c4d.EventAdd() if __name__ == "__main__": main()
  • We can update the Python API to align with the C++API

    windows python
    2
    0 Votes
    2 Posts
    116 Views
    ferdinandF
    Hey @Dunhou, Thank you for reaching out to us. We agree that this would be desirable. These methods are actually already wrapped but we hide them for now in the Python SDK. Find the reasons below. ExecuteJavascript: I just did remove the bindings of the Python API for that method in the current beta and also switched the C++ method to internal. The reason for that decision was is that we have security concerns about attackers being able to execute arbitrary JS in a web browser opened in Cinema 4D. SetWebMessageCallback: This is intended solution, i.e., the JS you want to execute must be already embedded into the HTML which is running in the HtmlView. On Windows/WebView2 it uses web messages, on MacOS/WebKit a custom solution emulating them. And SetURLCallback is then the way to get data back from the JS VM. For 2026.1 I already wrote examples for these methods, but on the last meters we discovered that something not only broke the Python bindings but the whole "execute JS" in the WebView2/WebKit bindings. My last info is that something broke there due to a project update, and that the two devs involved in it will have a look. I'll give them another bump and report here if there are any updates. Cheers, Ferdinand
  • Creating custom asset nodes via Python API

    2026 python windows
    4
    0 Votes
    4 Posts
    180 Views
    ferdinandF
    Good to hear that things worked out for you!
  • 0 Votes
    4 Posts
    239 Views
    ferdinandF
    Hey @vaishhg, As I tried to explain there are no nested dependencies. *.rs is a full blown scene file format which can express geometry, curves, simulation data, materials and more. When you save a Cinema 4D scene as *.rs al data in it is exported to that format, including the "nested" case where a *.c4d scene is referencing a *.c4d scene. So when you start out with this *.c4d scene: Scene.c4d +-- Objects +-- Cloner ( creates 5 instances) +-- Cube Generator +-- Cache +-- Polygon Object +-- Tags +-- Material Tag (references 'Red Material') +-- Materials +-- Red Material And then export it to Scene.rs, you get this (this is not an actual depiction of the file format, just a visualization of what happens, rs is not an open format). Scene.rs +-- Objects +-- Cube.0 [Red Material] +-- Cube.1 [Red Material] +-- Cube.2 [Red Material] +-- Cube.3 [Red Material] +-- Cube.4 [Red Material] +-- Materials +-- Red Material (contains Red Material definition) If you load that file back into Cinema 4D you get this. All data - that these are 5 separate cubes with a red material each - resides in the Redshift core only, we only see a proxy in Cinema 4D, hence the name "RS Proxy Object". It is the Redshift Core which will resolve the data in the RS file at render time. ReferencingScene.c4d +-- Objects +-- RS Proxy Object.0 (loads Scene.rs) +-- Cache (will be empty by default, there is literally no data in the c4d core, only when we set 'Preview' to 'Mesh' there will be a cache so that the viewport can display something) +-- Polygon Object (one blob representing all 5 cubes and no material information) +-- RS Proxy Object.1 (loads Scene.rs) +-- Cache +-- Polygon Object When we now export ReferencingScene.c4d to ReferencingScene.rs we get this. Because when the exporter runs, it will encounter the two RS Proxy Objects when flattening the c4d scene and do what you cannot do, grab the rs scene data from the referenced Scene.rs files and inline that into the new ReferencingScene.rs file. So we end up with 10 cubes in total, each with the red material assigned. ReferencingScene.rs +-- Objects +-- Cube.0 [Red Material] (from RS Proxy Object.0) +-- Cube.1 [Red Material] ... +-- Cube.2 [Red Material] ... +-- Cube.3 [Red Material] ... +-- Cube.4 [Red Material] ... +-- Cube.0 [Red Material] (from RS Proxy Object.1) +-- Cube.1 [Red Material] ... +-- Cube.2 [Red Material] ... +-- Cube.3 [Red Material] ... +-- Cube.4 [Red Material] ... +-- Materials +-- Red Material (contains Red Material definition) And when we load that back into Cinema 4D we get this: SecondGeneration.c4d +-- Objects +-- RS Proxy Object.0 (loads ReferencingScene.rs) +-- Cache +-- Polygon Object (one blob representing all 10 cubes and no material information) The TLDR is that the Redshift Core can read *.rs files and the Cinema API cannot, it can only write them or load them via an RS Proxy Object. And there is no 'resolving [...] the full proxy chain' as you put it. An *.rs scene file is just a discrete scene representation that contains does not know concepts such as generators or assets known to the Cinema API/Core. When export a *.c4d scene that references *.rs files all data is just flattened into a single *.rs file (again, what I showed under the *.rs formats above was just a visualization, not the actual file format). There is currently no way to do what you want to do, even if you would request access to the Redshift Core C++ SDK. Because the RS file format is a GPU scene file format and very deeply integrated into the core. Even the RS Core SDK does not expose functionalities to read RS files to CPU memory structures. Cheers, Ferdinand
  • how to detect obj selected in InExcludeData()?

    2025 python
    7
    0 Votes
    7 Posts
    227 Views
    chuanzhenC
    @ferdinand Thank you. hope this can be updated in the document
  • Finding out the latest asset version number/string via python

    python 2026
    7
    0 Votes
    7 Posts
    222 Views
    M
    Hi Ferdinant, Great! Thank you for your support - I really appreciate it! with... asset: maxon.AssetDescription = repo.FindLatestAsset( maxon.AssetTypes.File(), aid, maxon.Id(), maxon.ASSET_FIND_MODE.LATEST) version_string: str = maxon.AssetInterface.GetVersionString(asset) version_string = version_string[:-19].strip() #deleting the timestamp I could filter out the Version String. Thank you again for your help. Cheers, MPB
  • Getting some weird console output from GeDialog.Timer()

    python 2026
    3
    0 Votes
    3 Posts
    122 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
    123 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
    150 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
    1k Views
    K
    @John_Do said in CommandData.Message() implementation and Message concepts: seasoned baselislienhanMO