ExchangeMapper Material Import Manual

Introduction

To import a custom Material definition (read ExchangeMapper Material Definition Mapper Manual), into your own custom Cinema 4D material, the next interfaces should be implemented according to the specific needs:

One material definition can have multiple maxon::material::MaterialImportInterface or maxon::nodes::NodeMaterialImportInterface implementations describing how the import process takes place.

It is up to the requirements to create the material definition and import the data to choose which MaterialImportRef/MaterialNodeImportRef will perform the material creation.
It is also possible to create multiple material types by using multiples MaterialImportRef/MaterialNodeImportRef.

The import process does not provide an automatic baking functionality if a given material description is not handled by the maxon::material::MaterialImportInterface or maxon::nodes::NodeMaterialImportInterface implementations.

This ensures that, the 3-rd party developer should carefully considers what an unknown material description could mean for his material type since he should know and, probably, generate the default values.

However, one may be interested to explicitly convert the given material description to another one that he already knows. For more information read ExchangeMapper Material Definition Mapper Manual.

MaterialImportInterface

maxon::material::MaterialImportInterface can be called at any time: from a SceneLoaderData but also from a regular CommandData or anything else.

maxon::material::MaterialImportInterface provides a CreateMaterial() methods that need to be implemented.

It's important to registers the maxon::Class object representing the current maxon::material::MaterialImportInterface implementation into the maxon::material::MaterialImporters registry. Because of this, a SceneLoaderData can expose to the user all possible import solutions available. The following code is an implementation example:

static maxon::GenericData g_materialImportRegistration;
maxon::Result<void> SimpleMaterialImport::Register()
{
importDescription._name = maxon::LoadResourceString(maxonexample::GLOBALSTRINGS::SIMPLEMATERIALIMPORT);
importDescription._class = SimpleMaterialImport::GetClass();
g_materialImportRegistration = maxon::material::MaterialImporters::Register(SimpleMaterialImport::GetDescriptor().GetId(), std::move(importDescription)) iferr_return;
return maxon::OK;
}
void SimpleMaterialImport::Free()
{
g_materialImportRegistration = maxon::GenericData();
}
MAXON_COMPONENT_OBJECT_REGISTER(SimpleMaterialImport, "net.maxonsdk.doc_cpp.class.simplematerialimport");
return OK
Definition: apibase.h:2771
String LoadResourceString(const STRID &identifier, const ARGUMENTS &... args)
Loads the string 'identifier' from the resource and format with the passed parameters.
Definition: string.h:2305
#define MAXON_COMPONENT_OBJECT_REGISTER(C,...)
Definition: objectbase.h:2490
GenericDataTemplate< false > GenericData
Definition: genericdata.h:223
#define iferr_scope
Definition: resultbase.h:1396
#define iferr_return
Definition: resultbase.h:1531
Definition: materialimport.h:105
String _name
Definition: materialimport.h:106
Class< MaterialImportRef > _class
Definition: materialimport.h:107

The maxon::material::MaterialImportInterface::CreateMaterial() returns a pointer to a BaseMaterial.
The ownership of this BaseMaterial should be properly managed: whilst it's up to the maxon::material::MaterialImportInterface to allocate the BaseMaterial, the importer is responsible to insert it into the document (and transfer the ownership to it) or either to free the allocated BaseMaterial.

This method is called during the import process with the material definition (maxon::material::MaterialExchangeData) and only for context purpose, the BaseDocument the BaseMaterial should be created into and a maxon::DataDictionary containing the import configuration.

The following code is an implementation using the SimpleMaterial from Cinema 4D SDK as the target of the import process.

maxon::Result<BaseMaterial*> SimpleMaterialImport::CreateMaterial(const maxon::material::MaterialExchangeData& materialData, BaseDocument& baseDocument, const maxon::DataDictionary& config)
{
// We extend the capabilities of SimpleMaterial in the example.main.
const Int32 ID_SIMPLEMAT = 1001164; // The PLUGIN_ID of the MaterialData we want to create
const Int32 SIMPLEMATERIAL_COLOR = 1000; // The ID of the Color parameter
// Allocate a default BaseMaterial of our SimpleMaterial
BasePlugin* materialPlugin = FindPlugin(ID_SIMPLEMAT, PLUGINTYPE::MATERIAL);
CheckState(materialPlugin != nullptr);
BaseMaterial* simpleMaterial = (BaseMaterial*)(materialPlugin->Alloc(ID_SIMPLEMAT));
CheckState(simpleMaterial != nullptr);
// Simple material, only expose information about color, so we retrieve the default value
Vector simpleMaterialColor = simpleMaterial->GetDataInstanceRef().GetVector(SIMPLEMATERIAL_COLOR);
// Loads all the defaults values for the give material definition type (keep in mind a MaterialExchangeData may only reflect a part of the whole material definition)
const maxon::DataDictionary defaultParameters = maxon::material::ParameterStorageInterface::LoadDefaults(materialData._materialTypeId) iferr_return;
// Then for each specific material definition we describe how to load the color.
// If the input material definition is a STANDARDSURFACE
if (materialData._materialTypeId == maxon::MATERIAL::PORTBUNDLE::STANDARDSURFACE::GetId())
{
const maxon::material::TypedConstantParameter<maxon::Color> baseColor = maxon::material::ParameterStorageInterface::GetOrDefault<maxon::Color>(materialData._parameters, defaultParameters, maxon::MATERIAL::PORTBUNDLE::STANDARDSURFACE::BASE_COLOR) iferr_return;
simpleMaterialColor = baseColor._value.GetVector();
}
// If the input material definition is a FBXSURFACELAMBERT
else if (materialData._materialTypeId == maxon::MATERIAL::PORTBUNDLE::FBXSURFACELAMBERT::GetId() || materialData._materialTypeId == maxon::MATERIAL::PORTBUNDLE::FBXSURFACEPHONG::GetId())
{
const maxon::material::TypedConstantParameter<maxon::Color> diffuse = maxon::material::ParameterStorageInterface::GetOrDefault<maxon::Color>(materialData._parameters, defaultParameters, maxon::MATERIAL::PORTBUNDLE::FBXSURFACELAMBERT::DIFFUSE) iferr_return;
simpleMaterialColor = diffuse._value.GetVector();
}
// In a case the material definition is unknown, we check if it's possible to map this unknown material definition to those we support (like STANDARDSURFACE)
else
{
// Search if its possible to have a Standard Surface
maxon::Id srcMaterialDefinition = materialData._materialTypeId;
maxon::Id targetMaterialDefinition = maxon::MATERIAL::PORTBUNDLE::STANDARDSURFACE::GetId();
maxon::material::MaterialMappingDescription unknowToStandardMappingDescription;
for (const maxon::material::MaterialMappingDescription& mapping : maxon::material::MaterialMappers::GetEntries())
{
if (mapping._source != srcMaterialDefinition)
continue;
if (mapping._target != targetMaterialDefinition)
continue;
unknowToStandardMappingDescription = mapping;
break;
}
// If we found a mapper from the unknown material definition to a standard Surface, we perform the mapping
if (unknowToStandardMappingDescription._source == srcMaterialDefinition && unknowToStandardMappingDescription._target == targetMaterialDefinition)
{
maxon::material::MaterialMappingRef unknowToStandardMapping = unknowToStandardMappingDescription._class.Create() iferr_return;
unknowToStandardMapping.PassOne(materialData._parameters) iferr_return;
maxon::material::MaterialExchangeData convertedExchangeData = unknowToStandardMapping.PassTwo(materialData._textures) iferr_return;
// Finally recursively call the Import process but this time with the converted Standard surface material definition, that we already support
return CreateMaterial(convertedExchangeData, baseDocument, config);
}
}
// Set the data read from a material definition to the material
simpleMaterial->GetDataInstanceRef().SetVector(SIMPLEMATERIAL_COLOR, simpleMaterialColor);
return simpleMaterial;
}
MATERIAL
Definition: asset_browser.h:5
Definition: apibaseid.h:243
static MAXON_METHOD Result< DataDictionary > LoadDefaults(const Id &materialType)
Py_ssize_t PyObject * mapping
Definition: unicodeobject.h:919
#define CheckState(condition,...)
Definition: errorbase.h:510
BaseMaterial * CreateMaterial(BaseList2D *const asset, Int32 graphIndex, SUBSTANCE_MATERIAL_MODE mode)
BasePlugin * FindPlugin(Int32 id, PLUGINTYPE type)
maxon::Int32 Int32
Definition: ge_sys_math.h:51
maxon::Vec3< maxon::Float64, 1 > Vector
Definition: ge_math.h:140
The maxon namespace contains all declarations of the Maxon API.
Definition: autoweight.h:21
Definition: materialparameter.h:191
DataDictionary _parameters
Definition: materialparameter.h:210
Id _materialTypeId
Definition: materialparameter.h:203
HashMap< Id, Data > _textures
Definition: materialparameter.h:219
Definition: materialmapping.h:119
Id _source
Definition: materialmapping.h:120
Id _target
Definition: materialmapping.h:121
Class< MaterialMappingRef > _class
Definition: materialmapping.h:122
Definition: materialparameter.h:271
TYPE _value
Definition: materialparameter.h:286

NodeMaterialImportInterface

maxon::nodes::NodeMaterialImportInterface can be called at any time, e.g. from a SceneLoaderData but also from a regular CommandData or anything else.

maxon::nodes::NodeMaterialImportInterface provides an Import method that needs to be implemented.

This method is called during the import process with a node graph to be filled (NodesGraphModelRef) with the material definition (maxon::material::MaterialExchangeData) and only for context purpose, the document. It's up to the importer to decide what to do:

  • it can read attribute values directly from the material description and fed the data directly within the end node of the graph or
  • it can create an "import" node that represents the material description, then link the value from this "import" node to the end node of the graph to have the raw data exposed to the user.

To help in the process of the "import" node the maxon::nodes::NodeMaterialImportHelperInterface can be used to creates attributes that will be exposed as a node in the node graph.

maxon::Result<void> ExampleNodeMaterialImport::Import(maxon::nodes::NodesGraphModelRef& graph, const maxon::material::MaterialExchangeData& materialData, BaseDocument& baseDocument)
{
MAXON_SCOPE // We print some information on texture references and substances to the console.
{
const maxon::HashMap<maxon::Id, maxon::Data> & textureReferences = materialData._textures;
for (const auto& textureEntry : textureReferences)
{
const maxon::Id& textureId = textureEntry.GetKey();
const maxon::Data& textureData = textureEntry.GetValue();
const maxon::DataType textureType = textureData.GetType();
if (textureType == maxon::GetDataType<maxon::material::ImageReference>())
{
DiagnosticOutput("Parameter '@' has image reference to file '@'.", textureId, imageReference._absolutePath);
}
else if (textureType == maxon::GetDataType<maxon::material::SubstanceReference>())
{
DiagnosticOutput("Parameter '@' has substance reference named '@'->'@' to file '@'.", textureId, substanceReference._assetName, substanceReference._outputChannelName, substanceReference._absolutePath);
// The information stored in substanceReference should suffice to reference a specific instance of an asset instance through the API
// defined in 'lib_substance.h'.
}
else
{
DiagnosticOutput("Parameter '@' has texture reference of unknown type '@'.", textureId, textureType);
}
}
}
// In reality, we would want to create appropriate nodes or put the values directly on our end node inputs.
// For example, MATERIAL::PORTBUNDLE::STANDARDSURFACE::EMISSION_COLOR -> maxonexample::NODE::ENDNODE::EMISSIONCOLOR
MAXON_SCOPE // We implement the default import that would be available if we did register this class in our node space.
{
maxon::nodes::NodeMaterialImportHelperRef helper = maxon::nodes::NodeMaterialImportHelperInterface::CreateAndInitialize(graph, materialData._materialTypeId) iferr_return;
helper.AddParameters(materialData._parameters) iferr_return;
const maxon::nodes::NodeMaterialImportHelperInterface::GroupNodeData groupData = helper.Finalize() iferr_return;
// This example space does not support image node, otherwise we could use this helper.
// Note this will add into the node graph the Bitmap node that is defined for the drag and drop of a picture.
// maxon::nodes::NodeMaterialImportHelperInterface::AddConnectedTextureNodes(groupData, materialData) iferr_return;
}
return maxon::OK;
}
BaseList2D* ExampleNodeMaterialImport::FindSubstanceAsset(BaseDocument& baseDocument, const String& assetName)
{
AutoAlloc<AtomArray> substances;
if (substances == nullptr)
return nullptr;
const Bool onlySelected = false; // Get all Substances in the document
GetSubstances(&baseDocument, substances, onlySelected);
for (Int32 index = 0; index < substances->GetCount(); ++index)
{
BaseList2D* asset = static_cast<BaseList2D*>(substances->GetIndex(index));
if (asset == nullptr)
continue; // This should not happen, but let's be safe.
if (asset->GetName() == assetName)
return asset;
}
return nullptr;
}
#define Import(a0, a1, a2, a3, a4, a5)
Definition: Python-ast.h:536
Definition: datatypebase.h:1235
Result< typename std::conditional< GetCollectionKind< T >::value==COLLECTION_KIND::ARRAY||std::is_void_v< T >, T, std::add_lvalue_reference_t< const T > >::type > Get() const
Definition: datatypebase.h:1395
const DataType & GetType() const
Definition: datatypebase.h:1288
Definition: datatypebase.h:770
static MAXON_METHOD Result< NodeMaterialImportHelperRef > CreateAndInitialize(NodesGraphModelRef &graph, const Id &materialType)
Py_ssize_t * index
Definition: abstract.h:374
OK
User has selected a font.
Definition: customgui_fontchooser.h:0
bool Bool
boolean type, possible values are only false/true, 8 bit
Definition: apibase.h:180
#define MAXON_SCOPE
Definition: apibase.h:2922
int32_t Int32
32 bit signed integer datatype.
Definition: apibase.h:175
#define DiagnosticOutput(formatString,...)
Definition: debugdiagnostics.h:170
void GetSubstances(BaseDocument *const doc, AtomArray *arr, Bool onlySelected)
Defines a reference to an image file to represent a material parameter.
Definition: materialparameter.h:49
Url _absolutePath
Definition: materialparameter.h:56
Definition: materialparameter.h:75
String _assetName
Definition: materialparameter.h:90
Url _absolutePath
Definition: materialparameter.h:82
String _outputChannelName
The name of the substance output according to the GetSubstanceOutput query of the C4D Substance API.
Definition: materialparameter.h:95

Further Reading