• Help with Attribute Manager History

    Cinema 4D SDK windows python 2025
    2
    0 Votes
    2 Posts
    401 Views
    i_mazlovI
    Hi @kimberlyhtown, 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: Asking Questions. About your First Question I suppose you're asking about the Attribute Manager history, which one can access by using the following arrows: [image: 1738944299742-bf4e829b-8ae1-4eae-a86b-c8dbc57c713d-image.png] Unfortunately, no. the Attribute Manager is a quite complex part of C4D (e.g. with multiple AM having each their own history), hence it is not transparently exposed to our public SDK and one's typically limited by the functionality listed in our C++ SDK the AOM part: Active Object Manager. Cheers, Ilia
  • Start IPR rendering in the viewport

    Cinema 4D SDK windows c++ 2025
    5
    0 Votes
    5 Posts
    805 Views
    F
    @ferdinand Thank you for answering ,I 'll try it.
  • Strange string addition crash

    Cinema 4D SDK r21 c++ windows
    3
    0 Votes
    3 Posts
    760 Views
    E
    Thanks for your response. We found the issue. Turns out we had to delay load our dlls for earlier versions of the plugin R21 etc
  • 0 Votes
    4 Posts
    1k Views
    ferdinandF
    Hello everyone, we have moved this topic to a mail discussion, when there are outcomes relevant for other developers, I will post them here. Cheers, Ferdinand
  • Get the random result of Cloner Object

    Cinema 4D SDK c++ 2025 windows
    9
    2
    0 Votes
    9 Posts
    1k Views
    F
    @ferdinand Thank you very much! The problem is solved and now I know the way of MODATA_CLONE used.
  • 0 Votes
    6 Posts
    1k Views
    ferdinandF
    Hello @uogygiuol, Thank you for the added details. Yes, reducing the complexity of questions is the right thing to do, thank you for doing tit. Essays are counterproductive, as we then tend to overlook things (q.e.d., I overlooked the fact that you wanted to mangle the scene file in this thread). In general, trying to mangle a file beforehand is not a good route, as you always risk invalidating the file. For your very specific scenario - very simple scene graph, just geometry, no materials, animations or other dependencies - it could make sense. I briefly talked with the owner of our GLTF-importer, and we do not do any sanity checking, e.g., comparing nodes with meshes. So, you could just 'clean up' the scene graph ("nodes") of the file, and Cinema's GLTF importer will then just ignore extra data in fields such as "meshes". How fruitful this will be, you will have to find out yourself. I already had the hunch that your are here surfing on the edge of what is sensbible, and GLTF JSON files which translate to gigabytes of memory are certainly an edge case, due to the fact that text-based file formats are usually a bad choice for such heavy data. Using Python to Read JSON My guesstimate would be that when you throw a GLTF JSON file at Python's JSON parser - which takes five minutes to load in Cinema 4D - to mangle it, you end up with a net-loss or tie, because you loose most or more than the won time in that Python JSON stage. Python's json module is mostly written in C to make it performant, but that is still a lot of JSON to deserialize, modify, and then serialize. One idea could be to use re, i.e., regular expressions, to find the "nodes" section in that file, just deserialize that from JSON, modify it, serialize back to a JSON string, and write it back in place, and by that sidestep having to deserialize that whole file. The problem with all that is that json.load allows you to pass a file object, allowing you to bypass the Python VM entirely and let the data reside in C until the parsing is done, while re does not allow you to regex a file object directly (AFAIK), you always must read the file object into lines or chunks to then pass these strings to the re module. I.e., you would have to load that whole file into a Python string first. What would come here out on top, I have no clue, but my hunch is that re might loose, as Python's string handling is not the fastest. Alternatives might be 3rd party libs such as isjon (a lazy JSON loader) but I do not know how performant it is. For this section it would make a huge difference if you could predict the position of "nodes" in the file, either exactly as a chunk offset, or in the form of 'I know that it is always very close to the end, so let's regex parse the file in reverse'. Using a Binary File Format But the fact remains that text-format file types, e.g., JSON GLTF, become extemely ineffcient once you pass the ~100 MB barrier. Using something like binary GLTF or another binary format such as FBX will likely speed up your Cinema 4D loading times quite a bit, no extra steps required. And to be clear, text-based file formats are always wildely ineffcient. It is just that below the ~100 MB barrier (adjust for the beefiness of your machine), you can drown that inefficency with pure computing power and have the nice advantage of a human-readble file format. Cheers, Ferdinand
  • 0 Votes
    3 Posts
    517 Views
    A
    That is perfect, thank you for being through and concise, lifesaver.
  • 0 Votes
    2 Posts
    465 Views
    i_mazlovI
    Hi @derudo, 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: Asking Questions. About your First Question There's no single known "thing" we're aware of that could lead to such behavior. In other words, it can be lots of different "things" that caused the symptoms you're describing. Hence, without any further details everything I'm pointing out here is highly speculative. I also don't think answering the question "What could have happened?" is by any means productive, so let's switch the point of view to "How one could diagnose it?". Since you haven't posted any console output in your question, I assume you haven't checked that. This would actually be the first thing to check. Please refer to our Python Console Manual and searching on the forum. Next, try to figure out if it's only UI that stopped appearing or your plugin isn't registered at all anymore. You can do this by checking (e.g. with some temporary print statements) if you plugin is actually functional. On the screenshot you've posted the structure is kind of strange, but we're not sure if it's just a visualization matter of your software. Namely, the plugin.pyp file is expected to reside in the root of the plugin folder. Essentially, the following hierarchy level is not supposed to be there: [image: 1737450594273-232c47cd-171e-4642-8c12-7661b51e6ce5-image.png] Please refer to the article Plugin Structure Manual: Directory Structure and double check your plugin in this regard. If the points above don't lead to any result, try debugging it step-by-step. Namely, strip out everything except the essentials (like plugin registration) and continue adding things piece-by-piece until it starts failing. By the way, one can easily achieve this by using any version control system (e.g. git), as they typically provide a lossless way to manage your code (i.e. remove and add parts of code, without being worried to lose any of them). This approach could also have prevented your scenario in first place, when something stopped working and there're no clues about the change that has lead to it. If this still doesn't help, you can share your plugin here (or send us via contact form, when confidential information is involved). However, I must warn you that our Support Procedures still fully apply, namely: We cannot debug your code for you and instead provide answers to specific problems. Cheers, Ilia
  • 0 Votes
    2 Posts
    646 Views
    ferdinandF
    Hello @myosis, 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: Asking Questions. About your First Question All the methods you list simply do not exist (neither in C++ nor in Python), see c4d.utils.Neighbor for the type overview. I therefore must assume that you are using an AI, like, for example, ChatGPT, which hallucinated these methods. Please note that we reserve the right to refuse support when confronted with undisclosed AI gibberish, especially for beginner content. Always state when you used an AI to generate code. Something such as an edge does not exist concretely in our API and many other APIs, i.e., other than for points and polygons, there is no explicit data type for edges which would be stored. Edges are defined implicitly by CPolygon. To filter a selection for edges of a certain length, you would have to convert edge indices to polygon and point indices and then measure the distance between the relevant points. Cheers, Ferdinand Result [image: 1737027755224-074e2ada-a9ce-45b4-800a-acf7e060941a-image-resized.png] Code """Deselects all edges in the edge selection of the active object whose edge length exceeds MAX_EDGE_LENGTH. Must be run as a Script Manager script with an editable polygon object selected. """ import c4d doc: c4d.documents.BaseDocument # The currently active document. op: c4d.BaseObject | None # The primary selected object in `doc`. Can be `None`. MAX_EDGE_LENGTH: float = 25.0 # The maximum length of an edge before to be considered too long. MAX_EDGE_LENGTH_SQUARED: float = MAX_EDGE_LENGTH ** 2 # The square of `MAX_EDGE_LENGTH`. def main() -> None: """Called by Cinema 4D when the script is being executed. """ if not op or not op.IsInstanceOf(c4d.Opolygon): raise ValueError("The selected object is not a polygon object.") # Get the edge selection of the object and turn it into a list of selected edges indices. Also, # get the points and polygons of the object. selection: c4d.BaseSelect = op.GetEdgeS() selectedEdges: list[int] = [i for i in range(op.GetEdgeCount()) if selection.IsSelected(i)] points: list[c4d.Vector] = op.GetAllPoints() polygons: list[c4d.CPolygon] = op.GetAllPolygons() def getPointByIndex(poly: c4d.CPolygon, index: int) -> c4d.Vector: """Returns the point of the polygon at the given index. CPolygon has no index access, so we fix that here. """ if index == 0: return points[poly.a] elif index == 1: return points[poly.b] elif index == 2: return points[poly.c] elif index == 3: return points[poly.d] # Iterate over the edges and find the one's that are longer than MAX_EDGE_LENGTH. An edge index # is defined as: # # "The edges are indexed by 4 * polygon + edge where polygon is the polygon index and edge is # the edge index between 0 and 3." # # So, we must revert that here, then measure the edge length, and collect all too long edges. tooLongEdges: list[int] = [] for edgeIndex in selectedEdges: polygonIndex: int = edgeIndex // 4 edgeInPolygonIndex: int = edgeIndex % 4 poly: c4d.CPolygon = polygons[polygonIndex] pointA: c4d.Vector = getPointByIndex(poly, edgeInPolygonIndex) pointB: c4d.Vector = getPointByIndex(poly, (edgeInPolygonIndex + 1) % 4) # Getting the length of a vector is quite expensive, so we compare the squared lengths. edgeLengthSq: float = (pointA - pointB).GetLengthSquared() if edgeLengthSq > MAX_EDGE_LENGTH_SQUARED: tooLongEdges.append(edgeIndex) # Print the indices of the edges that are too long. print("The following edges are too long:", tooLongEdges) # Deselect all edges in the object's edge selection that are too long. for edgeIndex in tooLongEdges: selection.Deselect(edgeIndex) # Push an update event to Cinema 4D to redraw the object. c4d.EventAdd() if __name__ == '__main__': main()
  • About Texture Paths in MergeDocument

    Cinema 4D SDK 2025 python windows
    3
    0 Votes
    3 Posts
    626 Views
    R
    @i_mazlov I get it, thanks for your reply.
  • python script change Redshift setting

    Cinema 4D SDK python windows 2024
    6
    0 Votes
    6 Posts
    1k Views
    R
    @Dunhou @i_mazlov Thanks a lot.
  • Automatically execute python scripte

    Cinema 4D SDK python 2024 windows
    2
    0 Votes
    2 Posts
    391 Views
    M
    Hi @serco, there is multiple way to execute a script automatically when Cinema 4D is opened. Use python_init.py, this force you to add your script into the temp folder. Bu it can be done for a particular instance of a Cinema 4D or it can also be applied to all Cinema 4D versions that use a given python version. Implement a Plugin and react to PluginMessage various event are sent to Python, and you can hook into them to execute your code. This require to have a Python plugin loaded by Cinema 4D. Depending of your needs there is c4dpy which act as a Python Interpreter, where you can pass directly your Python file as an argument. Then again depending of your need you may be able to start what you want to do next. Cheers, Maxime.
  • 0 Votes
    6 Posts
    1k Views
    C
    @m_adam Thank you very much for your help in the last days! This worked as well as expected
  • WebSocket usage in C++

    Cinema 4D SDK c++ windows macos s24
    6
    1
    0 Votes
    6 Posts
    1k Views
    C
    Hi @m_adam, following your typescript example it worked now to connect! Thank you very much for your help Merry Christmas and a Happy New Year!
  • 0 Votes
    2 Posts
    480 Views
    i_mazlovI
    Hi @shahir-zaman , 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: Asking Questions. About your First Question Assuming that what you're trying to achieve is to toggle the "UV Grid" option [image: 1734354534219-d09d5b8d-51a2-4a6a-9618-90b2dbcc6638-image.png] You effectively have two options: The easiest one is to execute CallCommand(170776) (you can find the command ID in the Script Log) The more involved but also more flexible way is to find the UV settings scenehook and go through all its branches (or may be not all of them, depending on what specifically you want to achieve). Please find the C++ and Python code snippets below. Cheers, Ilia C++ commanddata execute function code snippet: Bool MyCommandData::Execute(BaseDocument* doc, GeDialog* parentManager) { const Int32 ID_UV_SETTINGS_HOOK = 1053309; if (!doc) return true; BaseSceneHook* sceneHook = static_cast<BaseSceneHook*>(doc->FindSceneHook(ID_UV_SETTINGS_HOOK)); if (!sceneHook) return true; maxon::BufferedBaseArray<BranchInfo, 20> infos; sceneHook->GetBranchInfo(infos, GETBRANCHINFO::NONE) iferr_ignore("GetBranchInfo"); for (BranchInfo& info : infos) { if (info.name != "UVSettingsBranch"_s) continue; GeListNode* viewSettings = info.head->GetFirst(); while (viewSettings) { GeData value; viewSettings->GetParameter(CreateDescID(UV_SETTINGS_FILTER_SHOW_GRID), value, DESCFLAGS_GET::NONE); Bool valueBool = value.GetBool(); value = {!valueBool}; viewSettings->SetParameter(CreateDescID(UV_SETTINGS_FILTER_SHOW_GRID), value, DESCFLAGS_SET::NONE); viewSettings = viewSettings->GetNext(); } } EventAdd(); return true; } Python script code snippet (special thanks to @m_adam on this one): import c4d def main() -> None: sh = doc.FindSceneHook(1053309) for branch in sh.GetBranchInfo(): if branch["name"] != "UVSettingsBranch": continue viewSettings = branch["head"].GetFirst() while viewSettings: viewSettings[c4d.UV_SETTINGS_FILTER_SHOW_GRID] = not viewSettings[c4d.UV_SETTINGS_FILTER_SHOW_GRID] viewSettings = viewSettings.GetNext() c4d.EventAdd() if __name__ == '__main__': main()
  • ZBrush 2024 FileExecute Issue

    ZBrush SDK windows 2024 c++
    3
    0 Votes
    3 Posts
    2k Views
    B
    Thank you for the detailed reply, @i_mazlov! I was unable to determine the root cause of the issue, however I was able to work around it by preloading direct dependencies with a FileExecute call and using the SearchPath Windows function to locate the runtime dependency DLLs and pass the absolute paths to LoadLibrary (I'm aware this is not recommended, but should be fine for this internally used plugin). I couldn't find anything in the newly added Maxon options that appeared to affect the loading behavior. My only remaining theory is that the new licensing process is indirectly triggering this behavior: LoadLibrary function returns STATUS_DLL_NOT_FOUND error on impersonate thread in Windows https://learn.microsoft.com/en-us/troubleshoot/windows-client/setup-upgrade-and-drivers/loadlibrary-function-returns-status-dll-not-found-error-impersonate-thread Thanks again! Nick
  • GraphModelInterface.GetNode not worked

    Cinema 4D SDK python windows 2025
    3
    0 Votes
    3 Posts
    683 Views
    DunhouD
    Hi @m_adam , Thanks for your reply, it worked well. But I still have some doubts about the usage of Node Path, such as how to calculate the port through NodePath instead of concatenating strings (which can easily lead to misoperation). There are few examples and answers for NodePath on the forum, and it seems that people rarely use it. Do you have any insights on this, or in other words, what usage scenarios do you think NodePath is more suitable for. Cheers~ DunHou
  • 0 Votes
    2 Posts
    678 Views
    i_mazlovI
    Hi @heytraile, Please have a look at our Support Procedures: How to Ask Question section. Usually it is very helpful if one provides a relevant code snippet, demonstrating the issue. This helps to reduce the room for any misinterpretations of your original question. Regarding the issue you're describing, the "null" being logged to the console is often a symptom of an implementation issue within a function that overrides the base implementation. Specifically, if a function (such as Message() or RestoreLayout()) is expected to return a particular type (e.g., bool), but instead returns None or a mismatched type, it can result in unexpected behavior and the "null" log. Cheers, Ilia
  • 0 Votes
    2 Posts
    540 Views
    ferdinandF
    Hello @T1001, Thank your for reaching out to us and for splitting your questions into multiple topics. But let's put this topic on hold here until we have asked your other question (which I set as the main question). Cheers, Ferdinand
  • 0 Votes
    7 Posts
    2k Views
    ferdinandF
    Hey @T1001, find below an example for a user area which draws multiple semi-transparent bitmaps on top of each other. I will likely add this code example to the SDK at some point, as this is a common task. Please provide feedback when you think things are missing. The example focuses on the core problem of having an 'alien' app which places bitmap data in memory and the Image API wrapping around that data with transparencies. What I have not yet done here, is that I avoided all copying altogether and directly operate on the raw memory managed and owned by the alien app. Which is possible to do in the public API I think, but one would have to write a component for ImageBaseInterface so that one can modify the Get/SetPixelHandler's. This would have been out of scope for this example. But I have it on my backlog, as I can see how this would be a desirable feature for render engine vendors and similar plugin types. Cheers, Ferdinand Result image_layers.mp4 Code /* Demonstrates how to implement a dialog that wraps around image buffers provided by an alien application. The buffers are of type RGBA-32 but could also follow any other pixel format the Image API supports. The buffers are drawn with their transparencies on top of each other in a user area and automatically scaled to the size of the user area. The example contains the following sections: * namespace alien: Contains code that generates image data in memory, mocking an alien app. * namespace cinema: Contains the code to wrap that alien image data in Cinema 4D with: * class ImageLayersArea: The UI element which wraps the buffers concretely. * class ImageLayersAreaDialog: The dialog implementation which uses an #ImageLayersArea. * class ImageLayersAreaCommand: The command which wraps the dialog as a menu/palette item which can be invoked by the user to open the dialog. */ // std is here only used to simulate an alien app, do not use std in your regular Cinema projects. #include <vector> #include <algorithm> #include "c4d_basedocument.h" #include "c4d_colors.h" #include "c4d_commandplugin.h" #include "c4d_general.h" #include "c4d_gui.h" #include "maxon/gfx_image.h" #include "maxon/gfx_image_colorprofile.h" #include "maxon/gfx_image_colorspaces.h" #include "maxon/gfx_image_pixelformats.h" #include "maxon/gfx_image_storage.h" #include "maxon/mediasession_image_export.h" // Must be included before the specialization. #include "maxon/mediasession_image_export_psd.h" #include "maxon/lib_math.h" // The plugin ID of the #ImageLayersAreaCommand. You must register unique plugin IDs for your plugins // via https://developers.maxon.net/. static const cinema::Int32 g_image_layers_command = 1064549; // The definition of bitmap buffers generated by the alien application. static const cinema::Int32 g_buffer_height = 2048; static const cinema::Int32 g_buffer_width = 2048; static const cinema::Int32 g_buffer_channel_count = 4; static const cinema::Int32 g_buffer_line_size = g_buffer_width * g_buffer_channel_count; static const cinema::Int32 g_buffer_size = g_buffer_line_size * g_buffer_height; // Title used by the dialog and command, and the IDs of the dialog gadgets. #define CMD_TITLE "C++ SDK: Image Layers Area"_s #define ID_GRP_BUTTONS 1000 #define ID_UA_IMAGE_LAYERS 2000 #define ID_BTN_ADD 2001 #define ID_BTN_REMOVE 2002 #define ID_BTN_SAVE 2003 /// We need this because IMAGEINTERPOLATIONMODE is currently not exposed in the public API, I /// will fix this in a future release. namespace maxon { enum class IMAGEINTERPOLATIONMODE { NEARESTNEIGHBOR, ///< worst quality, no interpolation at all LINEAR, ///< linear interpolation between pixels BICUBIC, ///< best quality using bicubic interpolation } MAXON_ENUM_LIST(IMAGEINTERPOLATIONMODE); } // end of namespace maxon // --- Alien Application Code /// This namespace is meant to simulate some alien application which places bitmap data in memory /// which we are going to wrap with the Image API. The example is using here the std library for /// the sole purpose of simulating an alien application. PLEASE NOTE THAT THE USE OF STD IN CINEMA /// PROJECTS IS STRONGLY DISCOURAGED. namespace alien { // A list of alien bitmap buffers held and managed by an alien application. Each float* in the // vector is a head to a buffer as defined by the g_buffer_ constants. std::vector<float*> g_alien_buffers; // A random number generator used by the alien application. maxon::LinearCongruentialRandom<maxon::Float32> g_random; /// Places an RGBA buffer in memory which holds a horizontal gradient in an otherwise largely /// transparent bitmap. This could for example be a buffer of an render engine which stores /// its data as #float. /// /// We are going to draw something like this, a horizontal gradient that only covers a portion /// of the height of the bitmap buffer, leaving the rest of the buffer entirely transparent. /// /// ---------------------------------------- /// /// /// @@@@@%%%%#####****+++++====-----::::.... /// /// /// ---------------------------------------- float* AddAlienBuffer() { iferr_scope; // The starting position and height of the horizontal gradient bar. const int32_t gradientHeight = std::max( 3, int32_t (g_random.Get01() * float(g_buffer_height) * 0.333)); const int32_t gradientStart = std::min( g_buffer_height - gradientHeight, int32_t (g_random.Get01() * float(g_buffer_height))); const int32_t gradientEnd = gradientStart + gradientHeight - 1; // The knots of the gradient. We either interpolate from opaque to transparent or vice versa. We // dip here our toes a bit into the Maxon API with ColorA32 so that we later use its color // interpolation function. const float alphaA = g_random.Get01() > .5 ? 0.0 : 1.0; const float alphaB = alphaA > 0.5 ? 0.0 : 1.0; const maxon::ColorA32 gradientColorA( g_random.Get01(), g_random.Get01(), g_random.Get01(), alphaA); const maxon::ColorA32 gradientColorB( g_random.Get01(), g_random.Get01(), g_random.Get01(), alphaB); // Allocate a new buffer. float* buffer = new float[g_buffer_size]; // Now we write a gradient into an otherwise fully transparent image. We write RGBA float data // in an at this point undefined color profile. We will then on the Cinema 4D side interpret // this data as PixelFormats::RGBA::F32() with an sRGB-2.1 profile. for (int32_t y = 0; y < g_buffer_height; ++y) { // We are outside of the gradient strip, we fill the line with fully transparent pure black. if (MAXON_LIKELY((y < gradientStart) || (y > gradientEnd))) { for (int32_t x = 0; x < g_buffer_width; x++) { const int32_t i = (y * g_buffer_line_size) + (x * g_buffer_channel_count); buffer[i + 0] = 0.0; // R buffer[i + 1] = 0.0; // G buffer[i + 2] = 0.0; // B buffer[i + 3] = 0.0; // A } } // We are inside the gradient strip, we write a gradient into the buffer. else { for (int32_t x = 0; x < g_buffer_width; x++) { const int32_t i = (y * g_buffer_line_size) + (x * g_buffer_channel_count); const maxon::ColorA32 color = maxon::BlendColor( gradientColorA, gradientColorB, float(x) / float(g_buffer_width)); buffer[i + 0] = color.r; buffer[i + 1] = color.g; buffer[i + 2] = color.b; buffer[i + 3] = color.a; } } } // Append the buffer to the global buffers and return the buffer. g_alien_buffers.push_back(buffer); return buffer; } /// Removes the last element from the alien buffer list and frees its memory. void PopAlienBuffer() { if (g_alien_buffers.empty()) return; float* last = g_alien_buffers.back(); delete[] last; g_alien_buffers.pop_back(); } } // end of alien // --- Cinema 4D Application Code namespace cinema { using namespace maxon; // --- UserArea Implementation -------------------------------------------------------------------- /// Implements a custom GUI that wraps around the bitmap buffers of an alien application. class ImageLayersArea : public GeUserArea { private: // Holds the images wrapping the alien buffers. Since we later want save out these buffers as // multi layer PSD files, we right away pick ImageLayerRef and not ImageRef as our image class. BaseArray<ImageLayerRef> _layers; public: // --- Custom Methods // Adds an image layer to the user area from the given #buffer. We rely here on the globally defined // buffer metadata, otherwise we would have to describe the layout of #buffer with more arguments. Result<void> AddLayer(float* buffer) { iferr_scope; // Allocate a new maxon Image API (layer) bitmap that uses the default non-linear RGB profile // (sRGB 2.1) and an RGBA four bytes per channel float pixel format, i.e., 16 bytes per pixel. // We could also import here an ICC profile from disk to match an exotic color profile used // by alien app. But when that is the case, we would have to convert colors here to sRGB 2.1 as // shown in the example_color_management examples. const ImageLayerRef layer = ImageLayerClasses::RASTER().Create() iferr_return; const maxon::ColorProfile profile = ColorSpaces::RGBspace().GetDefaultNonlinearColorProfile(); const PixelFormat format = PixelFormats::RGBA::F32(); const Int32 channelCount = format.GetChannelCount(); // Init the bitmap with its size, storage convention, and pixel format. Whe choose here the // "normal", i.e., consecutive layout. An alternative could be a planar layout. layer.Init(g_buffer_width, g_buffer_height, ImagePixelStorageClasses::Normal(), format) iferr_return; // Set some metadata on the image layer. Setting a name is irrelevant, the rest is not. layer.Set(IMAGEPROPERTIES::NAME, FormatString("layer_@", _layers.GetCount() + 1)) iferr_return; layer.Set(IMAGEPROPERTIES::IMAGE::COLORPROFILE, profile) iferr_return; layer.Set(IMAGEPROPERTIES::TYPE, IMAGEPROPERTIES::ITYPE::LAYER) iferr_return; // Get the pixel handler to write data into the image. const maxon::SetPixelHandlerStruct handler = layer.SetPixelHandler( format, format.GetChannelOffsets(), profile, maxon::SETPIXELHANDLERFLAGS::NONE) iferr_return; // Copy the data line by line. for (Int32 y = 0; y < g_buffer_height; ++y) { // Construct a Pix, i.e., UChar , line buffer pointer for our current buffer pointer. The // Image API handles all data at its lowest level as UChar. E.g., a line with 10 RGBA::F32 // pixels, i.e, 10 * 4, elements will be actually stored as, 10 * 4 * 4 elements, as an F32 // is decomposed into four UChar bytes. Pix* head = reinterpret_cast<Pix*>(buffer); // Write the line buffer into the image. Despite its name, an ImagePos can express up to // a line in an image. handler.SetPixel( ImagePos(0, y, g_buffer_width), PixelMutableBuffer(head, format.GetBitsPerPixel()), SETPIXELFLAGS::NONE) iferr_return; // Advance the buffer to the next line. buffer += g_buffer_line_size; } // Append our layer to list of layers. _layers.Append(layer) iferr_return; return OK; } /// Pops the last layer from the list of layers. Bool PopImage() { return _layers.Pop(); } /// Saves the whole layer list as a PSD to disk. Result<void> Save() { iferr_scope; // Check that we are on the main thread and then ask the user for a save location. if (!GeIsMainThreadAndNoDrawThread()) return IllegalStateError( MAXON_SOURCE_LOCATION, "This function cannot be run off main-thread"_s); Filename file; if (!file.FileSelect( FILESELECTTYPE::IMAGES, FILESELECT::SAVE, "Select file path:"_s, "psd"_s)) return OK; // Insatiate an ImageTextureRef, the root level bitmap type required to save images. // A texture can only hold entries of type IMAGEHIERARCHY::IMAGE, adding multiple images // to a ImageTextureRef will not result in layers, but all data being put into the "background" // layer of the image. const ImageTextureRef texture = ImageTextureClasses::TEXTURE().Create() iferr_return; const ImageRef base = ImageClasses::IMAGE().Create() iferr_return; base.Init(g_buffer_width, g_buffer_height, ImagePixelStorageClasses::Normal(), PixelFormats::RGBA::F32()) iferr_return; base.Set(IMAGEPROPERTIES::IMAGE::COLORPROFILE, maxon::ColorProfile()) iferr_return; texture.AddChildren(IMAGEHIERARCHY::IMAGE, base, ImageBaseRef()) iferr_return; // Now iterate over our layers with transparencies and add them one by one to the image. for (const ImageLayerRef layer : _layers) { // It is really importan that #layer has IMAGEPROPERTIES::TYPE set to LAYER. base.AddChildren(IMAGEHIERARCHY::LAYER, layer, ImageBaseRef()) iferr_return; } // Save #texture as a PSD. const MediaOutputUrlRef psd = ImageSaverClasses::Psd().Create() iferr_return; texture.Save(MaxonConvert( file, MAXONCONVERTMODE::NONE), psd, MEDIASESSIONFLAGS::NONE) iferr_return; return OK; } // --- GeUserArea Methods /// Called by Cinema 4D to request the minium #width and #height for the area. Bool GetMinSize(Int32& w, Int32& h) { w = h = 256; return true; } /// Called by Cinema 4D to let the area draw its content. void DrawMsg(Int32 x1, Int32 y1, Int32 x2, Int32 y2, const BaseContainer& msg) { // Enable some drawing optimizations. OffScreenOn(); SetClippingRegion(x1, y1, x2, y2); // Draw the background with the default background color. DrawSetPen(COLOR_BG); DrawRectangle(x1, y1, x2, y2); Float32 wx = Float32(x1); Float32 wy = Float32(y1); Float32 ww = Float32(x2 - x1); Float32 wh = Float32(y2 - y1); // Draw our layers on top of each other with bicubic interpolation. for (ImageBaseRef image : _layers) DrawImageRef(image, wx, wy, ww, wh, 1.0, IMAGEINTERPOLATIONMODE::BICUBIC); } }; // --- Dialog Implementation ---------------------------------------------------------------------- /// Realizes a dialog that displays a bitmap with multiple layers with transparencies. class ImageLayersAreaDialog : public GeDialog { private: // The custom user area and the default number of layers which are being added. ImageLayersArea _layers; const Int32 _defaultLayerCount = 3; public: // --- Custom Methods /// Adds an alien buffer and wraps it with an image in the #ImageLayersArea in tandem. Result<void> AddBuffer() { iferr_scope; float* buffer = alien::AddAlienBuffer(); _layers.AddLayer(buffer) iferr_return; return OK; } /// Pops an alien buffer and its wrapping image in the #ImageLayersArea in tandem. Result<void> PopBuffer() { _layers.PopImage(); alien::PopAlienBuffer(); return OK; } // --- GeDialog Methods /// Called by Cinema 4D to let the dialog populate itself with gadgets. Bool CreateLayout() { Bool result = true; static const Int32 margin = 5; // Set the title and the margin between the group and the border and between items in the // group. We are defining here the implicitly existing outmost group. SetTitle(CMD_TITLE); result &= GroupBorderSpace(margin, margin, margin, margin); result &= GroupSpace(margin, margin); // Add the image layers user area. result &= AddUserArea(ID_UA_IMAGE_LAYERS, BFH_SCALEFIT | BFV_SCALEFIT, 0, 200) != nullptr; result &= AttachUserArea(_layers, ID_UA_IMAGE_LAYERS); // Add a group with holds three elements pre row (opposed to the one element by row of the // default group and add the three buttons. result &= GroupBegin(ID_GRP_BUTTONS, BFH_SCALEFIT, 3, 0, ""_s, 0); { result &= GroupSpace(margin, margin); result &= AddButton(ID_BTN_ADD, BFH_SCALE, 0, 0, "Add"_s) != nullptr; result &= AddButton(ID_BTN_REMOVE, BFH_SCALE, 0, 0, "Remove"_s) != nullptr; result &= AddButton(ID_BTN_SAVE, BFH_SCALE, 0, 0, "Save"_s) != nullptr; } result &= GroupEnd(); return result; } /// Called by Cinema 4D to let the dialog init its values once its UI has been built. Bool InitValues() { // We implemented many methods here as Result<T>, the error system of the Maxon API. This method // is not of type Result<T> and we must therefore terimate the error handling in this function. // When an error is bubbling up through the 'iferr_return' of the #AddBuffer call, the function // will exit through this scope handler with #err being the error which is raised. iferr_scope_handler { // This is for demo purposes only, please avoid console spam in production code, use loggers // like DiagnosticsOutput instead. ApplicationOutput("@ failed with error: @", MAXON_FUNCTIONNAME, err); return false; }; // Add a few layers by default. for (Int32 i = 0; i < _defaultLayerCount; i++) { AddBuffer() iferr_return; } return true; } /// Called by Cinema 4D when the user clicks elements in the UI. /// /// Propagated are here only true click events, to realize things like scrubbing, mouse-over, /// etc, one has to implement GeDialog::Message (where one can also handle clicks). Bool Command(Int32 cid, const BaseContainer& msg) { iferr_scope_handler { // This is for demo purposes only, please avoid console spam in production code, use loggers // like DiagnosticsOutput instead. #err is the error exposed to the scope handler. ApplicationOutput("@ failed with error: @", MAXON_FUNCTIONNAME, err); return false; }; // We handle the buttons, when we add or pop layers, we force the user area to redraw. if (cid == ID_BTN_ADD) { AddBuffer() iferr_return; _layers.Redraw(); } else if (cid == ID_BTN_REMOVE) { PopBuffer() iferr_return; _layers.Redraw(); } else if (cid == ID_BTN_SAVE) { _layers.Save() iferr_return; } return true; } }; // --- Command Implementation --------------------------------------------------------------------- /// Realizes the command with which the user can open and close an the #ImageLayersAreaDialog. class ImageLayersAreaCommand : public CommandData { private: // The dialog of the command, we keep using the same instance over the life time of Cinema 4D. ImageLayersAreaDialog _dialog; public: /// Returns an instance of the #ImageLayersCommand. static ImageLayersAreaCommand* Alloc() { return NewObjClear(ImageLayersAreaCommand); } /// Called by Cinema 4D when the user invokes the command. Bool Execute(BaseDocument* doc, GeDialog* parentManager) { // Fold the dialog when open and unfolded, otherwise unfold it (Open both opens a never opened // dialog and unfolds a folded dialog). if (_dialog.IsOpen() && !_dialog.GetFolding()) _dialog.SetFolding(true); else _dialog.Open(DLG_TYPE::ASYNC, g_image_layers_command); return true; } /// Called by Cinema 4D to restore the UI associated with a command on layout switches. Bool RestoreLayout(void* secret) { return _dialog.RestoreLayout(g_image_layers_command, 0, secret); } }; } // namespace cinema /// Registers the #ImageLayersAreaCommand as a CommandData plugin. /// /// Must be called in the main.cpp of this module when #PluginStart() is emitted. cinema::Bool RegisterImageLayersAreaExample() { return cinema::RegisterCommandPlugin( g_image_layers_command, CMD_TITLE, 0, nullptr, "Opens a dialog holding a custom UI element that stacks multiple bitmaps with transparencies."_s, cinema::ImageLayersAreaCommand::Alloc()); }