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 this Texture.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 complex DescID
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,
Ferdinand
The 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;
};