How can I use a render token to indicate the render type?
-
Hi there,
I've been having trouble with c4d's render tokens. I'm trying to make a token that will change based on the render type (multipass or regular), but the tokens always evaluate to a regular image render, even when I'm not rendering a regular image. See my code here for a further breakdown:"""Multipass token problem. Goal is to have a render token that can dynamically change based on the current render type (multipass or regular image). What's confusing is that tokens are evaluated about 3 times when the project is added to the render queue, then an additional 11-14 times during the actual render. The token used for multipass renders is the second token that is evaluated during the actual render, which occurs before multipass is active. Around the 6th or 7th evaluation, multipass becomes active (and this evaluates correctly), but the correct multipass token is never used in the file name. """ import c4d from datetime import datetime # A hypothetical dict with info specific to the render type. dynamic_token_dict = {"multipass": "multi", "regular": "reg"} def get_render_type(data) -> str: """Supposed to return the render type. Multipass or Regular image. Returns: The render type + the time the token was made. """ print("Getting token...") print(data[7]) # Pass id for multipass renders or NOTOK if not multipass dynamic_token = dynamic_token_dict[ "regular" if data[7] == c4d.NOTOK else "multipass" ] now = str(datetime.now()) print(dynamic_token + now) return dynamic_token + now if __name__ == "__main__": token_set = set( token_dict["_token"] for token_dict in c4d.modules.tokensystem.GetAllTokenEntries() ) if "r_type" not in token_set: c4d.plugins.RegisterToken( "r_type", "[Render] Type", "reg/multi", get_render_type )
Is this possible? Also, why do tokens evaluate so many times? Thanks in advance.
-G -
Hello @GordOrb,
Welcome to the Plugin Café forum and the Cinema 4D development 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
Well, the docs tell you that
data[7]
will not be populated all the time. And yes, plugin functions get often called more often than it might look like from the outside world.data[7] (int) The pass ID used for rendering or NOTOK if multipass is not active or not yet recognized.
You could try to use the fields
data[5]
ordata[6]
(the pass names/labels), but that is just some other info which has to be populated by something and might not be there all the time. When possible, I would always go over the document which is being passed, because there you can be sure that data is set. E.g., something like this:import c4d import mxutils def eval_token_is_multipass(data: dict[str, any]) -> str: """Evaluates an %is_multipass token. """ doc: c4d.documents.BaseDocument = mxutils.CheckType(data[0], c4d.documents.BaseDocument) return "multipass" if doc.GetActiveRenderData()[c4d.RDATA_MULTIPASS_ENABLE] else "regular"
Cheers,
Ferdinand -
Hi @ferdinand,
Thank you for the introduction and for welcoming me to this forum. Glad to be part of the community.
In my testing, I found that data[5], data[6], and data[7] were all set at the same time (around the 6th or 7th evaluation), so I don't think I can use that to determine the type of render. This makes me wonder, how is an api user supposed to use those data values in any meaningful way when the render token evaluated before the data is populated?
I appreciate the code you provided, but I don't think that solves my question either. Please correct me if I'm wrong, but my understanding is that you can kick off a render job that outputs BOTH a regular image and multi-pass images in the same job. The example you gave indicates if multipass is enabled in the render settings, but this will also be true during the job's regular image render (so the regular image filename will incorrectly indicate a multipass render). Please see the attached image for how I expect this to be used.
Thanks again,
-G -
Hey @GordOrb,
This is not possible. You are not really meant to distinguish the file names you are writing for. I also do not understand the purpose of doing what you want to do. You can already name the beauty file and the multipass file differently in the UI. And when you write out each pass on its own, Cinema 4D will already name them differently for you.
There will be singular token call for all pass layers, and before that a call for the RGB/beauty layer. None of the calls for naming the file contains pass information. The inner logic of it seems to be that you first get the call for evaluating the token for a filename and after that the actual passes.
The purpose of some fields in the passed
data
is probably more of internal than public nature, since you cannot really do much with that info due to how the token calls are coming in. In some contexts these fields might be useful when you use the Token System (C++ Manual) more manually. But at least for the passes case, this is not really true either, because when you start to render manually, you will have to fill in the pass data on your own when constructing aRenderPathData
.So, long story short: this is not really possible, at least in the intended way. Upon having a closer look at the time stamps and my file output, I realized that you could try to rely on the rule 'the call after the call for the RGB pass', because that is at least how it works in my example.
isMultipass: '1', pname: ' ', ptype: ' ', pid: ' -1', t: 9366346949848 // First call, irrelevant isMultipass: '1', pname: ' ', ptype: ' ', pid: ' -1', t: 9367113676312 // Time stamp of saved RGB/beauty. isMultipass: '1', pname: ' RGB', ptype: ' RGB', pid: ' -1', t: 9367113987757 // RGB/beauty pass isMultipass: '1', pname: ' ', ptype: ' ', pid: ' -1', t: 9367114252401 // Time stamp of all passes. ...
If this works, this would be hack, and it would be then up to you establish if that holds true or not. To establish "what is the last call", you could have some global list into which you push things. Make sure not to store any Cinema 4D rendering data in that global list (
BaseDocument
,BaseContainer
,RenderData
) or you might end up in world of hurt.Cheers,
FerdinandWritten Files:
My console dump, here I search for the time stamp9367114252401
of my files, it is the fourth call, long before we get the calls for the passes. The only thing that comes before is a call for the RGB, i. e., the beauty pass.
Code:""" """ import c4d import mxutils import time def get_render_type(data: c4d.BaseContainer) -> str: """Dumps information about #data into the token and the console. """ # Get the render document #rdoc, and build a string for the UUID of rdoc as well as its memory # location and #data. Unsurprisingly, the mem locs are meaningless here in Python as everything # must be wrapped by Python objects in each call. In C++ you might be able to establish "sameness" # between calls by the location of #data (the rendering document which is used is always the # same as shown by its UUID). But I doubt that you will have luck with #data in C++, because it # is likely constructed for each call there too (and not reused). rdoc: c4d.documents.BaseDocument = mxutils.CheckType(data[0], c4d.documents.BaseDocument) uuid: bytes = bytes(rdoc.FindUniqueID(c4d.MAXON_CREATOR_ID)) ident: str = f"doc-uuid: {uuid}, doc-mem: {id(rdoc)}, bc-mem: {id(data)}" # Build the token. token: str = (f"isMultipass: '{rdoc.GetActiveRenderData()[c4d.RDATA_MULTIPASS_ENABLE]}', " f"pname: '{data[5]:>20}', ptype: '{data[6]:>10}', pid: '{data[7]:>3}', t: {time.perf_counter_ns()}") print (ident, token) return token if __name__ == "__main__": if "r_type" not in set(item["_token"] for item in c4d.modules.tokensystem.GetAllTokenEntries()): c4d.plugins.RegisterToken("r_type", "[Render] Type", "reg/multi", get_render_type)