Setting bitmap in Redshift dome light
-
I'm trying to create a Redshift dome light and add a bitmap to the 'Texture' link field. It all seems to work except the bitmap is not added. This is what I have so far:
Bool MyDialog::CreateRSDomeLight(BaseDocument* doc, BaseObject* parent, BaseObject* prev, Int32 domeType, Filename bitmapToSet) { Bool success = false; GeData param; BaseObject* domeLight = nullptr; BaseContainer *data= nullptr, *shddata = nullptr; BaseShader* shd = nullptr; domeLight = BaseObject::Alloc(RSLight); if (domeLight != nullptr) { param.SetInt32(REDSHIFT_LIGHT_TYPE_DOME); domeLight->SetParameter(DescID(REDSHIFT_LIGHT_TYPE), param, DESCFLAGS_SET::NONE); doc->InsertObject(domeLight, parent, prev, false); doc->AddUndo(UNDOTYPE::NEWOBJ, domeLight); success = true; data = domeLight->GetDataInstance(); shd = BaseShader::Alloc(Xbitmap); if (shd != nullptr) { shddata = shd->GetDataInstance(); if (GeFExist(bitmapToSet), false)) { shddata->SetFilename(BITMAPSHADER_FILENAME, bitmapToSet)); shd->Message(MSG_UPDATE); data->SetLink(REDSHIFT_LIGHT_DOME_TEX0, shd); domeLight->Message(MSG_UPDATE); } } } return success; }
This all works fine - the light is created, it's added to the scene, and is set as a dome light - but the texture link remains empty. This technique has worked for C4D materials and other objects but the Redshift link field doesn't seem to like it. I can see that in the description the field is actually an 'RSFILE' rather than a shader link - is that the problem? I've tried just adding a filename to the ljnk field but that doesn't work either.
Anyway, if I'm missing something obvious and there's a way to do this, I'd be grateful for any help.
Steve
-
Hello @spedler,
Thank you for reaching out to us. The problem you are facing is caused by multiple Redshift data types not being exposed in the public API. But these non-public types are then usually composed out of public types. So, while you cannot read or write the type as whole, you can do so for its levels (called sub-channels in the GUI). The problem has been discussed before on the forum here and here. I go there over the technical background in Python (I am sure you are aware of) and means how to discover the sub-channel IDs interactively in Cinema 4D with the "show sub-channels" option.
In your concrete case the correct
DescLevel
tuple to write to Texture.Path would beREDSHIFT_LIGHT_DOME_TEX0, REDSHIFT_FILE_PATH
.Cheers,
Ferdinand -
Hi Ferdinand,
That's great, many thanks. I had a feeling it was a data type not (yet?) exposed in the API, but I'll read through those other posts and see if I can get it to work.
Thanks again,
Steve
-
Hey Steve,
I am sorry, I could have been a bit more verbose here. We are currently under release preparations, so time is a rare commodity. I assumed you would know what I was hinting at when mentioning multiple levels of a
DescID
. Find a small pseudo code example at the end of my posting which should get you going.While writing the code example, I also saw that you are trying to write a shader link (i.e., treat the parameter like a
BaseLink
with theTexBoxGui
), but that parameter subchannel is for a file path, i.e., effectively a string. Or am I overlooking something here and one can also somehow place a shader in that RS DomeLight parameter? I am not the biggest RS expert ...Cheers,
FerdinandThe code:
iferr_scope; ... BaseObject* rsDomeLight; // A RS dome light instance pointer you somehow got hold of. const maxon::String somefilePath; // A file path somehow provided by your code. ... // We construct a two level DescID to write only the Path component of the Texture parameter, ... const DescId rsDomeLightTexturePathId = DescId(DescLevel(REDSHIFT_LIGHT_DOME_TEX0), DescLevel(REDSHIFT_FILE_PATH)); // and then write to that parameter component. Note that you cannot use BaseContainer access when // writing sub-channel data, but must use C4DAtom::Get/SetParameter. if (!rsDomeLight->SetParameter(rsDomeLightTexturePathId, GeData(somefilePath), DESCFLAGS_SET::NONE)) return maxon::UnexpectedError( MAXON_SOURCE_LOCATION, "Could not write Texture.Path component in RS DomeLight."_s);
-
Hi Ferdinand,
Thank you very much for taking the time to post the pseudocode. I know you must be very busy right now!
Unfortunately I've tried this method numerous times and I just can't get it to work. Here's the code:
Bool MyDialog::CreateRSDomeLight(BaseDocument* doc, BaseObject* parent, BaseObject* prev, Filename fullPath) { Bool success = false; BaseObject* domeLight = nullptr; domeLight = BaseObject::Alloc(RSLight); if (domeLight != nullptr) { param.SetInt32(REDSHIFT_LIGHT_TYPE_DOME); domeLight->SetParameter(DescID(REDSHIFT_LIGHT_TYPE), param, DESCFLAGS_SET::NONE); doc->InsertObject(domeLight, parent, prev, false); doc->AddUndo(UNDOTYPE::NEWOBJ, domeLight); success = true; // all working fine until now String pathToFile = fullPath.GetString(); // path is verified as correct param.SetString(pathToFile); const DescID rsDomeLightTexturePathId = DescID(DescLevel(REDSHIFT_LIGHT_DOME_TEX0), DescLevel(REDSHIFT_FILE_PATH)); if (!domeLight->SetParameter(rsDomeLightTexturePathId, param, DESCFLAGS_SET::NONE)) // <- Fails here { ApplicationOutput("Failed to write Texture.Path component in RS DomeLight."); success = false; } } return success; }
It allworks fine up to the point when SetParameter() is called using the two-level DescID. It doesn't crash, just fails to set the bitmap into the link field. I must be doing something wrong but I can't see anything obvious. What am I missing?
Cheers,
Steve
-
I'm sorry to keep harping on about this but there's something odd here. In case the 'RSFILE' custom GUI was causing problems I tried to set sub-channels in the dome light colour. If you look at this code, having created a Redshift dome light I set the object's base color (in the Basic tab) using a two-level DescID method to set a sub-channel. This works correctly. Then I tried the same thing to set the dome light colour - but it doesn't work. Using a single-level DescID does work, however, to set the light intensity:
// two-level DescID works on BaseObject parameter const DescID domeLightBaseColorId = DescID(DescLevel(ID_BASEOBJECT_COLOR), DescLevel(VECTOR_X)); param.SetFloat(0.25); domeLight->SetParameter(domeLightBaseColorId, param, DESCFLAGS_SET::NONE); // single-level DescID works correctly on Redshift dome light parameter const DescID domeLightMultiplerId = DescID(DescLevel(REDSHIFT_LIGHT_DOME_MULTIPLIER)); param.SetFloat(5.0); domeLight->SetParameter(domeLightMultiplerId, param, DESCFLAGS_SET::NONE); // two level DescID does not work on Redshift dome light parameter const DescID domeLightColorId = DescID(DescLevel(REDSHIFT_LIGHT_DOME_COLOR), DescLevel(VECTOR_Y)); param.SetFloat(0.5); domeLight->SetParameter(domeLightColorId, param, DESCFLAGS_SET::NONE);
I also tried setting the dome light colour using a single-level DescID and this works without problems, but I couldn't set the individual sub-channels using the above method.
Either I'm being really dumb or there's something not working with this method when accessing a Redshift object.
-
Hello @spedler,
Please excuse the slight delay. Yes,
DescID
's can be nasty little fellas from time to time. But it is possible to write thisTexture.Path
sub-channel; find an example at the end of my posting. For me this successfully creates an RS dome light and sets the texture thingy. The underlying gotcha is here - which I did not respect in my hand-wavey 'pseudo-code' - that you must be quite precise from time to time when defining complexDescID
instances for them to work in all places.The example goes over some technical background and hints at a workflow (Python) for how to gather the here required magic numbers on your own.
Cheers,
FerdinandThe result:
The code:#include "maxon/assets.h" #include "c4d_general.h" #include "lib_description.h" #include "orslight.h" #include "drsfile.h" maxon::Result<void> CreateRSDomeLightWithTexture(BaseDocument* doc); maxon::Result<void> CreateRSDomeLightWithTexture(BaseDocument* doc) { iferr_scope; // This is just some boiler plate code to get a texture from the Asset API/Browser, you will // need an internet connection for this to run. // Get the Asset API user preferences repository to get a texture asset from it. maxon::AssetRepositoryRef repository = maxon::AssetInterface::GetUserPrefsRepository(); if (!repository) return maxon::UnexpectedError(MAXON_SOURCE_LOCATION, "Could not retrieve user repository."_s); // Get the "bg-final.jpg" texture asset in Textures/Surfaces/Backgrounds, retrieve its URL, ... const maxon::Id assetId("file_ef50ab8cc676f624"); const maxon::AssetDescription asset = repository.FindLatestAsset( maxon::Id(), assetId, maxon::Id(), maxon::ASSET_FIND_MODE::LATEST) iferr_return; // Get the texture URL string. const maxon::Url assetUrl = maxon::AssetInterface::GetAssetUrl(asset, true) iferr_return; const maxon::String filePath = assetUrl.GetUrl(); // Allocate the Redshift light object, in S26 we still must use a hardcoded ID, with the next // SDK this will change, there we could use the symbol Orslight. BaseObject* rsLight = BaseObject::Alloc(1036751); if (!rsLight) return maxon::OutOfMemoryError(MAXON_SOURCE_LOCATION, "Could not allocate light object."_s); // The meat of the example, DescIDs. They can be tricky little fellas, as they can be defined // in different levels of verbosity. // The most simple form of defining a DescID, we simply wrap an integer, in this case for // the light type integer of an RS light. This form can only be used to define single // level desc ids. const DescID shortDescID = DescID(REDSHIFT_LIGHT_TYPE); // This is a slightly more verbose variant where we manually define the first DescLevel. const DescID mediumDescID = DescID(DescLevel(REDSHIFT_LIGHT_TYPE)); // The most verbose variant of defining a DescID with one DescLevel. const DescID verboseDescID = DescID( DescLevel(REDSHIFT_LIGHT_TYPE, // The ID of the thing which is being addressed. DTYPE_LONG, // The data type ID of what is being addressed, long/int here. 0 // The creator ID of the parameter. This sort of expresses // which entity created the parameter, often enough you can // set this to 0 as I do here, and everything will still work // fine. For native elements this is often MAXON_CREATOR_ID. )); // All three these IDs are equivalent in this case, in the case of single level DescID you can // often get away with being less verbose. // We can use all three DescID definitions to write the light type (Dome). We write the type // in reverse DescID complexity order, so that it is actually least complex type which 'counts'. for (const DescID& did : { verboseDescID , mediumDescID, shortDescID }) { if (!rsLight->SetParameter(did, REDSHIFT_LIGHT_TYPE_DOME, DESCFLAGS_SET::NONE)) return maxon::UnexpectedError(MAXON_SOURCE_LOCATION, "Could not set parameter."_s); } // For the composed multi-level parameter, we must make use of such very precise DescID definition, // as Cinema 4D otherwise refuses to write this parameter (SetParamter fails). const DescID texturePathId = DescID( // 1st DescLevel for the outer unexposed datatype of "Texture". DescLevel( REDSHIFT_LIGHT_DOME_TEX0, // The parameter ID (12000) for "Texture" 1036765, // The datatype ID for for "Texture", this type is not exposed, // we must use the raw ID. 1036751 // The creator ID, Orslight in this case. ), // 2nd DescLevel for the inner exposed datatype of "Texture.Path". DescLevel( REDSHIFT_FILE_PATH, // The sub-channel parameter ID (1000) for "Texture.Path" DTYPE_STRING, // The data type ID of this component, string in this case. 0 // The creator ID, I just went with undefined here. )); // You might now ask how I knew all these magic numbers? The answer is I used this little Python // script to inspect the description of an Orslight instance. /* import c4d import typing op: typing.Optional[c4d.BaseObject] # The active object, None if unselected def main() -> None: """Will print out the description of op, which can be handy from time to time. """ if op is None: return for data, descId, _ in op.GetDescription(0): print (f"{descId = }") for cid, value in data: print(f"\t{cid = }, {value = }") if __name__ == '__main__': main() */ // When we know the name of the parameter, or better is first DescLevel, it is easy to find this // line in the print out which contains all the relevant information. //(12000, 1036765, 1036751) // 1 Texture // 2 Texture // 3 3 // 10 1 // 21 1036766 // 1920165492 0 // 25 0 // 999 REDSHIFT_LIGHT_DOME_TEX0 if (!rsLight->SetParameter(texturePathId, filePath, DESCFLAGS_SET::NONE)) return maxon::UnexpectedError(MAXON_SOURCE_LOCATION, "Could not set parameter."_s); doc->InsertObject(rsLight, nullptr, nullptr); EventAdd(); return maxon::OK; } // I am not sure how familiar you are with the maxon API, but here is an // example for how you could call this function from the classic API, i.e., // something that is not a maxon::Result. Bool DoStuff(BaseDocument* doc) { iferr_scope_handler{ ApplicationOutput("Error: @", err); return false; }; CreateRSDomeLightWithTexture(doc) iferr_return; // ... return true; };
-
Hi Ferdinand,
Thank you very much for this very long and helpful post. It's definitely going above and beyond the usual support. I think TBH it ought to become part of the DescID manual in the SDK docs, it's so useful.
I'll need to peruse it at length to be sure I understand how it all works, but with this I'm sure I can get it working. I'll mark this thread as solved when I do.
Thanks again,
Steve
-
Just tried the code and got it working now, no problems. Many thanks for taking the time to sort this out for me, very much appreciated!
Steve
-
Hey @spedler,
I am glad that this solved your problem. Regarding putting this in the docs or SDK: I have created a task for doing this, but there is other stuff in front of the queue, so it might take some time before I find the time to do so.
Cheers,
Ferdinand -
-