Global Moderators

Forum wide moderators

Private

Posts

  • RE: V-Ray Materials Not Detected by Project Asset Inspector

    Please also share which version of Cinema 4D you are using, I'll try to have a look this or next week.

    edit: I briefly checked with scripts/05_modules/node/create_redshift_nodematerial_2024.py and there, with Redshift, it works and the assets show up in the inspector right away. Which hints at this being an exlusive VRay issue which only can be solved by Chaos.

    c613fbf4-4621-46d8-af96-1253e7df7c6a-image.png

  • RE: Shader bitmap rendering with MemoryFileStruct

    Hey @WickedP,

    Thank you for reaching out to us. This is rather hard to answer as is. It is not that your question would be inadequate or the code example unclear, but this is also a subject I would have to try out myself to know for sure. The problem with pseudo code is that it is not executable, when you want a definitive answer, I must ask you to provide compileable code which demonstrates the issue.

    About your Issue

    Since you say that sending such image file to the Picture Viewer works, we can rule out that you made any mistakes in setting up the Filename in memory mode or properly encoding your image data.

    What is noteworthy is that Filename is a Cinema API type from the 'old' days which is today just a hollowed out shell that wraps UrlInterface. When using the memory feature of Filename you are actually using maxon::URLSCHEME_MEMORYFILESTRUCT (not to be confused with URLSCHEME_MEMORY). Internally we tend to use yet another file virtualization scheme implemented for URLs when embedding virtual files in scenes or renderings, maxon::URLSCHEME_RAMDISK.

    There could be a threading issue with your Filename (check if the issue also happens when you pass RENDERFLAGS::NODOCUMENTCLONE to your RenderDocument call).

    The most forward fix would be however to use a ram disk, as that is what is proven to work with render engines. The problem is that there are currently no public examples for creating URLSCHEME_RAMDISK urls in the public SDK (as it is sort of a semi-official thing). The only place where I used it was in the Asset API docs, but only to ensure ram disk assets are being cached, not to create new ones. But ram disk scheme urls have a convenience interface, RamDiskInterface which makes it quite straight forward to use. Find below some mock code for your use case (did not compile it due to the lack of a project for this topic).

    Cheers,
    Ferdinand

    using namespace maxon;
    
    Result<void> DoRamDiskStuff()
    {
      iferr_scope; // Error handler for this scope because we are using the Maxon API. When you want to merge
                   // this with Cinema API code, i.e., use it in a function that is not of type Result<T>,
                   // you would have to use here iferr_scope_handler to terminate errors into the return
                   // type of your function.
    
      // Create a new mounting point for ram disk files. Use a unique ID in reversed domain name notation 
      // to avoid conflicts with other mounting points or use the other constructor to create an automatic 
      // hashed mounting point (which will not be persistent over reruns).
      RamDiskRef mountingPoint = RamDiskInterface::Create("net.mycompany.ramdisk.foo"_s) iferr_return;
      Url root = mountingPoint.GetRoot() iferr_return;
    
      // There are now basically two ways to create ram disk files: 
      //   (1) direct creation where we effectively treat the Url just like a file.
      //   (2) lazy creation where we provide a callback that will be called when the file is first accessed.
    
      // Our payload.
      const Char payload[] = "Hello World!";
    
      // Direct creation:
    
      // We create a file under our ram disk mounting point, it would have an URL such as 
      // "ramdisk://.../file.txt". You can directly use this URL in all parameters and file 
      // operations. We can also turn an Url back into a Filename using cinema::MaxonConvert.
      Url directFile = (root + "direct_file.txt"_s) iferr_return;
    
      // Now we write to the file, this is just standard Maxon API file I/O.
      InOutputStreamRef inout = directFile.OpenInOutputStream() iferr_return;
      inout.Write(payload) iferr_return;
    
      // And this would be the way to read back.
      Char buffer[SIZEOF(payload)];
      inout.Seek(0) iferr_return;
      inout.Read(buffer) iferr_return;
    
      // Deferred/Lazy creation:
    
      // This way is in a certain sense a bit easier (as stream handlers can be a bit scary when new
      // to the Maxon API). The advantage is of course that we only pay the costs when something is
      // actually needed. The downside is that is this can happen at an inconvenient time. Which is
      // also why I showed in the Asset API examples for how to build caches in advance. But in your 
      // case where you create the URLs yourself, this makes no sense.
    
      // This is just this one call which we invoke on our ram disk mounting point. We provide a callback 
      // that will be called when the file is first accessed, and in this callback we create an IoMemoryRef 
      // with our payload and return it.
      Url lazyFile = mountingPoint.CreateLazyFile(ToSingletonBlock("lazy_file.txt"_s),
        [payload] () -> Result<IoMemoryRef>
        {
          iferr_scope;
          DiagnosticOutput("Lazy content creation triggered.");
          IoMemoryRef mem = IoMemoryRef::Create() iferr_return;
          mem.WriteBytes(0, payload) iferr_return;
          return mem;
        }) iferr_return;
    
      // Reading back would work the same here.
    
      return OK;
    }
    

    In your specific case, I do not know if BaseBitmap::Save will work directly on a Filename that wraps a ram disk url (via Filename file = cinema::MaxonConvert(myRamDiskUrl, MAXONCONVERTMODE::WRITE);), but you can try and it should work. When push comes to shove you can always write data directly as you showed yourself.

  • RE: V-Ray Materials Not Detected by Project Asset Inspector

    Hey @Ross,

    Thank you for reaching out to us. Assets in the sense of the Asset Inspector are realized via MSG_GETALLASSETS in general. For material nodes you have to set IMAGENODEASSETID and IMAGENODEPORTS on the node space, or directly implement MATERIALMESSAGEHANDLERFUNC (all attributes are in the same namespace). Under the hood, all these Nodes API systems pipe back into MSG_GETALLASSETS and it is the implementer who must realize them, i.e., Chaos in this case. You as a user cannot do anything about, the asset data is either there or not.

    I am currently on my Mac where I do not have VRay installed. The main question would be: Does VRay show its assets when you wire up the same materials manually? I think it does, right? You can try sending a MSG_UPDATE to the material holding the graph(s) but that should not be necessary by default(myMaterial.Message(c4d.MSG_UPDATE)). It could be that VRay is doing some black magic with its assets and MATERIALMESSAGEHANDLERFUNC which is not correctly setup when you programmatically create graphs. But that (a) strikes me as a bit unlikely and (b) only Chaos can help you with that.

    VRay is as a third party plugin generally out of scope of support, so their support is your best shot. But when you share your code and a scene, I can have an informal look as what is going wrong there (but it might take a week or so before I get to it).

    Cheers,
    Ferdinand

  • RE: How to start a new line in plugin help with localization?

    It depends a bit on what you are doing. When you look at these screenshot I already posted above


    then you can see, Cinema 4D is already doing this automatically. The tooltip for a command¹ is always split into a name, description and shortcut. As a plugin author, you define the name and description and Cinema 4D then composes them for the tooltip. The shortcut will only be shown when this command has a shortcut set, either because your user set it up, or because you programmatically set it in some sort of 'installer' routine for your plugin. That the Py - Resource Gui example does not have a shortcut shown, is simply due to the fact that I did not define one.

    But your question implies a different type of 'shortcut', namely modifier keys. I.e., you do not want to display a key combination with which something can be activated (commands already fully handle this for you), but rather keys that change the behaviour of something while it is running. The canonical place to display such information would be the status bar. The Move tool shows for example this in the status bar once it has been activated (and a few other criteria are met):

    91530485-629f-4d43-8376-70ce83a1fb3a-image.png

    Other than the command tooltips, this is not automated. And each plugin has to set this on its own via c4d.gui.StatusSetText. The problem is a bit that these kind of status messages make mostly sense for tools, i.e., things where you have clearly defined states of a thing being active or not, and when the user does send mouse- or keyboard interactions. These states help managing the status bar because you not only have to set these messages, you also have to clear the status bar once you are done.

    So, it depends on what kind of plugin you implement how to do this concretely. When you implement just a command with a dialog, there is no predefined way how to do this, as dialogs are not meant to have such modifier keys. You could maybe show or hide such message based on if your dialog got or lost focus.

    Cheers,
    Ferdinand

    ¹ - Everything in this context is a command, even non CommandData plugins such as objects or tools, because Cinema 4D will always wrap them with a CommandData plugin for you, so that they can show up in palettes and menus. So, in menus and palettes are only commands, even when you did not implement one yourself.

  • RE: How to start a new line in plugin help with localization?

    Hey @Dunhou,

    our c4d_string.str files use standard ASCII escaping (of unicode). There is the relatively new py-cmd_gui_resources_2024 example which documents this (of particular interest would be unicode_encode_strings.py for you). But when I wrote unicode_encode_strings.py, I think it was a deliberate decision of mine to exclude the so called control characters in ASCII/Unicode (i.e., \x00 to \x1F as Python Unicode escape sequences). Which includes the whitespace characters like \t, \n, \r, etc. So, the script will not encode them for you.

    If you take the code example from above and wanted to add a line break in its help string, you cannot use \n in the string, because it would be escaped as \\n in the output, which is not what you want. Instead, you would have to use \x0A (i.e., the value 10 which is the code point for a line break in ASCII). But since this is not Python, but our C++ code which follows the Unicode standard to the letter, we do not use the \x prefix but \u and also pad it to 4 digits, so it would actually be \u000A for a line break.

    STRINGTABLE
    {
    	IDS_PLUGIN_NAME "Py - Resource Gui"; 
    	IDS_PLUGIN_HELP "Opens a dialog window that is populated\u000a using resource files.";
    }
    

    d48918e5-eca2-4b9d-90f5-36101de3f0bc-image.png

    If you wanted a more generic solution than writing such strings by hand, you would have to update EscapeUnicode in unicode_encode_strings.py to something like this:

    def EscapeUnicode(item: str) -> str:
        """Escapes both non-ASCII and control characters in a string to their unicode code point representation,
           e.g., \u000A for a line break.
        """
        return re.sub(r"([^\x00-\x7f]|\x00-\x1F)",
                        lambda m: "\\u{:04x}".format(ord(m.group(1))), item
        )
    

    You could also just escape everything, but that would make the strings less readable in the resource files. I am personally also not sure it is a good idea to allow users putting control characters into strings, primarily due to whitespace. I would for example say that it is not wanted that strings in general and tooltips in particular contain line breaks, and that it is up to Cinema 4D to handle wrapping strings. When I look at our Move tool, I see no line breaks in the help string? The help string is just 'Move Tool', the other things (the name and the shortcut) are added by Cinema 4D dynamically (and therefore put onto a new line).

    2d99ee72-e853-415c-a4a6-448796c526b7-image.png

    Cheers,
    Ferdinand

  • RE: How do I retrieve a command call from it's index number ?

    The second ID is the sub ID. You can have a command (FOO = 100) which supports the sub-commands (BAR=1, and BAZ=2). So, you can then call CallCommand(100, 1) or CallCommand(100, 2) to invoke the two different things FOO can do. But as you said yourself, this is a rather unusual thing, and it is even more unusual that I manged to randomly click five things in a row that all have sub IDs 😄

    What should also be said is that my little "extrude" log there does not really reproduce the modelling operations I carried out. It just runs the tools in the order they are invoked. And because when you run this right after your created the log, the extrude tool will for example still have set the same extrude depth from the last operation. But when you manually extrude something, and then run the script again, it will use these new current extrude tool settings, i.e., and not the ones from when your 'recorded' the log.

    The script log can be useful, but it is not the auto-script-recording feature users often whish it was.

  • RE: How do I retrieve a command call from it's index number ?

    Hey @Dimitris_Derm. ,

    I still do not 100% follow what you are saying and asking, which likely means that you or I are misunderstanding something.

    What are Commands?

    Cinema 4D is built itself on the same plugin architecture offered to third parties. There are many plugin types in Cinema 4D, but a central one is CommandData. A command is an operation that can be invoked without any arguments except the plugin ID of the command to invoke (and optionally a sub ID). CallComand is the function to call commands by their command/plugin ID. I.e., when you implement a command plugin as a user, everyone can call it via its command ID. Commands in this sense are not to be confused with modelling or uv commands, which are a different thing.

    Almost all buttons and menu entries in Cinema 4D are commands. So, when you for example implement an ObjectData 'Plane' plugin and register it, Cinema 4D will create a hidden CommandData plugin for you, so that this object can be created from a menu. Here shown with the builtin 'Plane' object:

    40b30de8-d931-4b34-9fda-d8f9b2cd0a51-image.png

    What are Symbols?

    The raw integer values for plugin IDs are usually (but not always) aliased with a 'symbol'. Symbol is just programmer slang for an alphanumeric label under which some data is accessible. E.g., foo, bar, and baz below are symbols:

    foo: int = 123456
    bar: dict[str, float] = {"Hello": 1.0, "World": 2.0}
    baz: callable = lambda x: x * 2
    

    In C(++), the langauge Cinema 4D is written in, the word symbol is very common. In the Cinema API it always refers to some alpha-numeric label for some integer value. There is code which is shipped to customers (the public APIs) and there is code that is internal. In order not to have users use raw integer values, we have to define such symbol, i.e., alias in the public API. This does not always happen.

    What is the Script Log doing?

    The script log is just a series of the the commands being called. There are no classes, functions, or methods. In some cases the script log creates more complex code (for tools), but the script log is never a place to learn the API, as it only operates via command IDs. And the script log uses the raw integer values because for once there is not always a symbol, and it would also not be trivial to access the symbol for each command from the C++ code that generates the log.

    3a8deeef-11fb-44c5-8f55-954e4e691b4d-image.png

    Cheers,
    Ferdinand

    PS:

    By "method" I meant the programmatic term which some times coincides with the term "function".

    I understood that, but just as a clarification, method and function are not synonymous. A method is a function that is associated with an object (i.e., it is a member of a class). A function is a more general term that can refer to any callable piece of code, including methods, but also standalone functions that are not associated with any object.

  • RE: Executing a Redshift texture bake from Python

    Hello @mplec1,

    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

    The bake tag and by extension cinema::BakeTexture (and its Python equivalent c4d.utils.BakeTexture) unfortunately do not support Redshift, even for the older Xpresso based materials.

    Redshift uses the tools found in Redshift/Tools/Texture Baking for baking. And while you can programmatically create a bake set and then programmatically click the 'Bake' button in it (which both would also work in a headless version of Cinema 4D, such as c4dpy), the following dialog which opens to set baking details and actually start the rendering is sealed, i.e., you cannot interact with it from the public API. And opening such dialog would also fail in a headless Cinema 4D instance.

    So, I am afraid there is currently no solution for your problem. You can technically export the whole scene to a format such FBX or USD, and use the builtin baking output (which would also work in a headless environment, as long as you do NOT pass SAVEDOCUMENTFLAGS_DIALOGSALLOWED to the save/export operation). But the output of that automated baking is often of poor quality compared to manually baking object(s) via bake sets.

    Cheers,
    Ferdinand

  • RE: How do I retrieve a command call from it's index number ?

    Hey @Dimitris_Derm. ,

    what do you mean by 'method'? CallCommand just invokes the CommandData plugin which has been registered under the plugin ID you passed to the call.

    What you can do, is resolve the numeric plugin ID integer value to a symbol. But unlike for message IDs, the docs currently do not resolve symbols to their integer values. You can do two things:

    1. Just search in the main symbol definition file with a text editor, i.e., {c4d}/resource/modules/python/libs/python311/c4d/__init__.py
    2. Or use mxutils.SymbolTranslationCache which exists for the very purpose of resolving such integer values to symbols.

    But in your case, 14046 , you will draw a blank in both cases, which simply means developers never defined a public symbol for that command.
    1b4e6537-4fad-48a1-b4ec-c6edd4103083-image.png

    Cheers,
    Ferdinand

  • RE: Using the Bridge Tool

    Hello @Dimitris_Derm.

    No, with islands I do not mean different objects. With islands are polygon (selection) islands meant. And that is just the term that is used for these things. The "Plane" object below has two polygon islands, the left and right rectangle shown in the viewport, each composed out of 4 * 5 polygons. They are islands because they are topological disjunct from each other - you cannot 'get' from one island to another without jumping over a gap.

    a88ac530-c105-430c-bd11-f922f5688881-image.png

    The same can be applied to selections, as shown below. Now the left polygon island in the mesh has two polygonal selection islands. You cannot get from one selection island to another without jumping over a gap (they are topologically disjunct).

    bf41be6a-3993-4eb1-b603-438df9aa1bb3-image.png

    This is a requirement because as my code example demonstrates, when bridging in island mode, you just specify a polygon and a point (and set the flags), and the tool then 'grows out' the to be bridged patch from the given polygon, based on the active polygon selection. And this growing will stop at topological boundaries. So, when you would bridge from the selection in the lower left corner of the left rectangle (to some unspecified target in another mesh), it would only grow into these four polygons. The selection in the top right corner would never be part of the operation.

    Cheers,
    Ferdinand