Group Details Private

administrators

  • RE: Bake animation in the background

    Hi @brian-michael,

    I've forked your posting in a dedicated thread. For your following postings please stick to our guidelines, which you can find in the Support Procedures, namely:

    Singular Subject: From all this follows that a topic must have a singular and sparse subject tied to a specific problem

    especially when it comes to N-years-old threads 🙂

    Regarding your question, please share more context on what specifically you're trying to do, because depending on that you can end up in a completely different ways of approaching your goal.

    For example, if you'd like to bake animation in a "read-only manner" (just take the object transformations and store/send them somewhere), then the suggested approach would be to clone document and process it in a separate C4DThread. You can check Ferdinand's code example on the exact same topic in application to rendering document: RenderDocument/PythonCallBack : how to display progress prints during the render. However, with such approach you're limited to not being able to modify original document (because you'd use the cloned document instead of the active one).

    Cheers,
    Ilia

    posted in Cinema 4D SDK
  • RE: Monitoring object parameter changes

    Hello @Márton,

    Thank you for reaching out to us. No, I would say we are fine here in this thread since the subject is literally 'Monitoring object parameter changes'.

    The primary issue is that you are using the wrong method. You should be using BaseObject::GetUniqueIP but there is also a gotcha as it is not as unique as one might think it is. Because it is only unique within a cache. And since caches can contain caches, this can then lead to ambiguity.

    I would also question a bit as how sensible it is that you poke around that deeply in caches. Even for render engines which must do scene complex synchronization, I find this a bit questionable. I understand the desire for doing this for performance reasons. But caches are not meant to be updated/scanned partially, when an object is dirty and rebuilt its cache, you should sync the full cache. I would always try to stay on the GeMarker/MAXON_CREATOR_ID level when possible on pull over whole new caches when a generator went dirty. Partially updating caches, i.e., you try to figure out what in a cache changed and only update that, strikes me as very error prone as this relies on the assumption that the plugin/object implementing that cache is doing nothing hacky itself for performance reasons (and there is no such gurantee). When carefully designed, one can problably write something that chery picks only the 'relevant' stuff, but I would consider this a workaround or hack.

    I would also recommend having a look at How to detect a new light and pram change? for a practical example of how to sync a scene graph (and I deliberately did not go into the cache level there). The example is in Python but translates relatively directly to C++. As metioned in my code example below, in C++ one would usually use BaseList2D::GetGeMarker instead of MAXON_REATOR_ID as the former is the data the latter is based on (but markers are inaccessible in Python). Finally, here is some older thread about the subject of IDs.

    Find below a brief example in Python. Again, I am aware that you are on C++. Please come back when you need help with translating any example code to C++.

    Cheers,
    Ferdinand

    Result

    Output in Cinema 4D for a Figure object, running the script a second time after changing the Segments paramater.
    d8aaba2e-65f6-46e1-bafe-0132a9bc7b34-image.png

    The diff. The GUID and marker ID obviously changed. But in the case of the Figure object it seems also to be the case that its hierarchy is not stable, something in the "Right Joint" hierarchy jumped. This also highlights why I would avoid tracking caches when I can, as they are meant to be dynamic and each object implementation can pull off countless hacky things with its cache. I would strongly recommend tracking the dirtiness of an object and operate on the generator level as shown in How to detect a new light and pram change?.

    6858d95d-e22f-44b5-989b-021e98c659e6-image.png

    Code

    """Stores the different identifiers of the objects in the cache of the selected object and compares 
    them to a previous state saved to disk.
    
    To run this script, first save it to a file, e.g., `inspect_cache.py`, as it infers other file paths
    from its own location. Then select an object in the Object Manager and execute the script. The script
    will print the current cache of the selected object to the console and save it to disk. Now invoke
    a cache rebuild on the object by for example changing a parameter. Run the script again and it will
    print the new cache and compare it to the previous one. If the caches differ, a HTML diff will be
    created and opened in the default web browser.
    """
    
    import os
    import difflib
    import webbrowser
    
    import c4d
    import mxutils
    
    doc: c4d.documents.BaseDocument  # The currently active document.
    op: c4d.BaseObject | None  # The primary selected object in `doc`. Can be `None`.
    
    DIFF_CHANGES: bool = True  # If `True`, the script will open an HTML dif for the changes.
    PATH_PREV: str = os.path.join(os.path.dirname(__file__), "id_prev.txt") # Path to the previous cache.
    PATH_DIFF: str = os.path.join(os.path.dirname(__file__), "id_diff.html") # Path to the diff file.
    
    def main() -> None:
        """Called by Cinema 4D when the script is being executed.
        """
        if op is None:
            raise ValueError("No object selected.")
        
        # Iterate over the cache of the selected object #op.
        result: str = ""
        for node, _, depth, *_ in mxutils.RecurseGraph(op, yieldCaches=True, yieldHierarchy=True, 
                                                       complexYield=True):
            if not isinstance(node, c4d.BaseObject):
                continue # Should not happen since we do not step into branches.
            
            # The padded name of the object, e.g., "    + Cube            ".
            head: str = f"{' ' * depth} + {node.GetName()}".ljust(40)
    
            # This identifies an object uniquely in a cache and is persistent over cache rebuilding.
            # A cache hierarchy [a, b, c] will always return the same unique IPs [s, t, u], even when
            # the user changed a parameter resulting in the same hierarchy (but possibly otherwise 
            # different data). But there is a gotcha: These IDs are only unique within the same cache. 
            # And since caches can contain caches, you can end up with this:
            #
            #        generator
            #          a - First cache element of #generator, UIP: 1
            #            b - First cache element of #a, UIP: 1
            #            c - Second cache element of #a, UIP: 2
            #          b - Second cache element of #generator, UIP: 2
            #            a - First cache element of #b, UIP: 1
            #            c - Second cache element of #b, UIP: 2
            #          c - Third cache element of #generator, UIP: 3
            #
            # There is no builtin way to safely decompose caches in that detail safely, you must write
            # something yourself (you could for example hash everything up to the generator and call
            # that the unique ID of the object).
            #
            uip: str = str(node.GetUniqueIP()).ljust(30)
    
            # I have quite frankly no idea for what this ID is good for, I never used it. The docs
            # also strike me as not quite correct, since they claim that this falls back to #uip, but in
            # some cases, e.g., the Figure object, this does not seem to hold true, as this ID changes
            # over cache rebuilds. This is also not persistent over Cinema 4D sessions (load/save).
            guid: str = str(node.GetGUID()).ljust(30)
    
            # This is the ID assigned to an object when it is being created. It wil change for cache
            # rebuilds but is persistent over different Cinema 4D sessions (load/save). Since caches
            # are being rebuilt (the whole point of generators) it is useless for identifying objects
            # in a cache but for everything else it is the ID of choice. In C++ you can use GeMarker
            # and BaseList2D::GetMarker() to directly access the data the MAXON_CREATOR_ID is based on.
            uid: str = str(bytes(node.FindUniqueID(c4d.MAXON_CREATOR_ID)))
            result += f"{head} uip = {uip} guid = {guid} uid = {uid}\n"
    
        # Print our little tree to the console and check for changes.
        print(f"Current hierarchy of {op}:")
        print(result)
    
        if os.path.exists(PATH_PREV):
            cache: str = ""
            with open(PATH_PREV, "r") as f:
                cache: str = f.read()
    
            print("-" * 80)
            print("Previous hierarchy:")
            print(cache)
            print("-" * 80)
            print("Cache is matching current hierarchy:", cache == result)
    
            # Build the HTML diff when asked for and the cache has changed. Open it in the browser.
            if cache != result and DIFF_CHANGES:
                diff: str = difflib.HtmlDiff().make_file(
                    cache.splitlines(), result.splitlines(), 
                    fromdesc="Previous cache", todesc="Current hierarchy")
                
                with open(PATH_DIFF, "w") as f:
                    f.write(diff)
                webbrowser.open(PATH_DIFF)
    
        # Write the current hierarchy as the new previous state to disk.
        with open(PATH_PREV, "w") as f:
            f.write(result)
        print("-" * 80)
        print(f"Cache written to {PATH_PREV}.")
    
    
    if __name__ == '__main__':
        main()
    
    posted in Cinema 4D SDK
  • RE: 2025.0.0 SDK Release

    Hey @mikeudin,

    we are aware and working on it. Meanwhile this applies:

    Getting Started with the Cinema 4D C++ SDK on macOS: Prerequisites:

    Xcode 13 is incompatible with more recent versions of macOS such as Ventura and Sonoma. But you must still use Xcode 13 to build the more recent Cinema 4D C++ SDKs for macOS, as Xcode 13 is the last release that supports the legacy build system. We strongly recommend using macOS Monterey to run Xcode 13 as the circulating hack of reaching into the Xcode 13 package to directly invoke the Xcode app is more and more error prone the further you update macOS. Attempting to build the Cinema 4D SDK with Xcode 14 or later will result in build errors. We are working on supporting the standard build system of Xcode.

    See also the forum thread Development requirements for C4D 2025.

    Cheers,
    Ferdinand

    posted in News & Information
  • RE: Global static classes?

    As I said, you will run into access violations when you instantiate your fields on the class instead of the Init function. And as I also said, in your concrete case it would probably even be fine if you would directly do it in the class scope. But I would advise against breaking this rule. There is nothing to optimize here, either return a new instance when this is only called once in a blue moon and you do not want to bother with initializing a field. Or define a field and initialize it up in NodeData::Init. And no, scene elements (NodeData) should not share data on their class. We have types in the Maxon API to deal with this when you really want some heavy data structure which shall be initialized exactly once, but that is total overkill in this case.

    Cheers,
    Ferdinand

    posted in Cinema 4D SDK
  • RE: Global static classes?

    Hey @ECHekman,

    Thank you for reaching out to us. You should avoid such things as sPluginNodeInfo (which I assume is meant to return an InfoStruct and not a PluginNodeInfoStruct and to be the focal point of your question). The correct way to do this in the context of NodeData plugin hooks, would be to place an uninitialized field on your node and then initialize it in NodeData::Init (and possibly implement NodeData::CopyTo, ::Read, and ::Write to also support copy and IO events). When you want a const value, implement a function which returns a const value each time when called, possibly pulling up an internal value (a 'getter' property) or simply returning a new value each time.

    You will land in a world of hurt sooner or later when you ignore this in our API (as in access violations, I just had a dev internally who tried to pull up point data in this manner and then ran into said access violations). Your case is probably fine, but you should not do this. I personally would also avoid marking functions as virtual which are not. SomeClass::PluginNodeInfo is likely meant to be a concrete implementation and therefore should not be virtual.

    The same thing applies to global instances (which is probably what was meant in docs primarily).

    // Not a good idea ...
    static InfoStruct g_info;
    
    int main() {
        g_info.Foo();
        return 0;
    }
    

    Cheers,
    Ferdinand

    posted in Cinema 4D SDK
  • RE: Detect error-causing nodes in XPresso

    Hi @kng_ito ,

    Please excuse the delayed answer.

    According to our codebase the Execute() function will return GV_CALC_ERR_CALCULATION_FAILED for the calculation errors happening on master or global scope, namely when the GvNodeMaster was not able to calculate successfully (i.e. GvCalcState higher than GV_CALC_STATE_LOCAL_FAILURE). Issues like zero division only cause local failure, hence are not returned as error in Execute() function. Unfortunately, there's not much you can effectively do here.

    In our SDK support meeting Ferdinand suggested an approach of adding intermediate xpresso python node, where you can detect and handle issues yourself. However, this highly depends on your use-case scenario, because if you for example need to batch-process xpresso graphs, this won't be any practical.

    Cheers,
    Ilia

    posted in Cinema 4D SDK
  • RE: Vertex Map Tag not in sync error

    Hi Daniel,

    Please excuse the delayed answer.

    You're not supposed to create tags via constructor. It's expected to be created using MakeVariableTag() function instead. Please have a look at Ferdinand's example here: Negative Vertex Map values

    Cheers,
    Ilia

    posted in Cinema 4D SDK
  • RE: Correct OCIO Color Value Conversion via Python?

    Hey @blkmsk,

    Thank you for reaching out to us. To make it short, this is currently not yet possible with the python SDK as OCIO has not been wrapped for it. But we are working on it as we are speaking, and OCIO will hopefully be added with an upcoming minor release of Cinema 4D (although 2025.1 will not yet contain it for sure).

    In the meantime, all we can offers is you using our C++ API, for details please see the C++ SDK: OpenColorIO Manual. In Python, with a third party OCIO library and parsing our OCIO config file, and the directly accessible document parameters, especially the new 2025.0.0 ones:

    DOCUMENT_COLOR_MANAGEMENT : Expresses the color management mode a document is in, with the values:
    DOCUMENT_COLOR_MANAGEMENT_BASIC : The document is legacy color management mode.
    DOCUMENT_COLOR_MANAGEMENT_OCIO : The document is OCIO color management mode.
    DOCUMENT_OCIO_CONFIG : Stores the path to the currently used OCIO configuration file of a document.
    DOCUMENT_OCIO_RENDER_COLORSPACE : Stores the currently selected render space integer symbol from the loaded OCIO configuration.
    DOCUMENT_OCIO_DISPLAY_COLORSPACE : Stores the currently selected display space integer symbol from the loaded OCIO configuration.
    DOCUMENT_OCIO_VIEW_TRANSFORM : Stores the currently selected view transform integer symbol from the loaded OCIO configuration.
    DOCUMENT_OCIO_VIEW_TRANSFORM_THUMBNAILS : Stores the currently selected view transform integer symbol for thumbnails from the loaded OCIO configuration.
    DOCUMENT_OCIO_RENDER_COLORSPACE_NAME : [New in 2025.0.0] Directly stores the string for the currently selected render space.
    DOCUMENT_OCIO_DISPLAY_COLORSPACE_NAME : [New in 2025.0.0] Directly stores the string for the currently selected display space.
    DOCUMENT_OCIO_VIEW_TRANSFORM_NAME : [New in 2025.0.0] Directly stores the string for the currently selected view transform.
    DOCUMENT_OCIO_VIEW_TRANSFORM_THUMBNAILS_NAME : [New in 2025.0.0] Directly stores the string for the currently selected view transform for thumbnails.
    

    You might be able to cook up something, but that would be out of scope of support. When you want to do something with OCIO in Python, you must wait for now.

    Cheers,
    Ferdinand

    posted in Cinema 4D SDK
  • RE: Programing a Tabulated BRDF Node / Shader - possible ?

    Hey,

    Out of curiosity, I did some poking in Redshift, and what I forgot, is that you are in principle unable to escape the current shading context of a sample in Redshift. I.e., you cannot just say, "screw the current UV coordinate, please sample my texture 'there'" due to the closure thing which Redshift has going.

    With the Standard Renderer, you can do the following, which is pretty close. I compute the v (i.e., view angle coordinate) as described above. Standard also does not give you directly light ray angles, but you can get the light contribution where I use the diffuse component of the light(s) as a faksimile: High diffuse = high angle of incident/light contribution.

    82ee51e1-15a9-487b-8cb0-91eec633352e-image.png
    Fig. I: A crude iridescence setup in Standard. test.png is your texture from above, I forgot to take the inverse of values as 1 - x.

    view_angle.zip

    In Redshift you cannot do the same light contribution hack, and I currently do not see how you could set the sampling coordinate of the texture in the first place. Maybe the Redshift pro's know more.

    Cheers,
    Ferdinand

    posted in Cinema 4D SDK
  • RE: Programing a Tabulated BRDF Node / Shader - possible ?

    Hey @mogh,

    Hey, it could very well be that Redshifts Closure paradigm at least complicates things with OSL. But for what I did there, a simple view angle dependent shader, I would say all the data is accessible here.

    When I look more closely at your texture, it seems to model true Iridescence, as it maps out both the viewing and illumination angle. Which you cannot do with Redshift shaders atm as there is no such input as a 'light direction', but the Redshift team can tell you probably more. My example only models a simple view angle dependent shader. You would read such texture via the dot product I would say. The angle of incident is the absolute of the dot product of the surface normal and the inverse ray direction (inverse because the ray 'comes in' while the normal 'goes away'). The dot product expresses the angle between two vectors in the interval [0°, 180°] as [-1, 1]. So, when you take the absolute of that, you get [0°, 90°] as [0, 1] which you then can directly use as an UV coordinate. In your example you seem to have to take the inverse as they flip things according to your image (i.e., v: float = 1.0 - abs(-ray * normal)). For the light ray it would be the same, it would just replace the ray in the former example, and then makeup your u/x axis.

    This might be why Adrian said that this is not possible. It is better to ask there the Redshift team, as my Redshift knowledge is somewhat limited. For the Standard Renderer, you would have access to all that data, but that would result in a shader you can only use in Classic materials of the Standard Renderer. But you would have to use the C++ API, as you would have to start digging in the VolumeData to get access to the RayLight data.

    Maybe you could fake the light angle in some manner to get a semi-correct result?


    As a warning though, what Adrian wrote there about the C++ SDK strikes me as wrong. There is a semi-public Redshift C++ SDK, but I doubt that it will allow you to write shaders for Redshift, due to how the Nodes API is setup. You can implement shaders for your own render engine, but neither Standard nor Redshift render shaders are really implementable for third parties right now.

    Cheers,
    Ferdinand

    posted in Cinema 4D SDK