Getting Started: Foundations

Overview

  • The Source Processor is a tool that automatically analyses the plugin source code and creates utility code.
  • Debug messages can be written with ApplicationOutput() or DiagnosticOutput().
  • The plugin code must handle error objects that are returned by sub-functions.
  • It is also possible to return error object to report errors.
  • Plugins using the "classic" API must implement PluginStart(), PluginMessage() and PluginEnd().
  • The "classic" API allows to create new objects and tools by implementing plugin hooks like ObjectData etc.
  • Most elements of the "classic" API are based on C4DAtom, GeListNode and BaseList2D.
  • Parameters of Cinema 4D elements are accessed with C4DAtom::GetParameter() and C4DAtom::SetParameter(). The parameter IDs are represented with DescID objects.
  • The MAXON API allows to create new components by implementing interfaces. These implementation are shared with registries or as published object.

Source Processor

The Source Processor is a Python script that is executed every time a MAXON API framework or plugin is build. The script checks the code for certain programming errors and creates additional code.

If a project file is created with the Project Tool, the script is automatically added to the build process. It is not needed to run it manually. The output console of the IDE will display which file or files are parsed and what errors the script has detected.

Printing Messages

To debug a program and to follow the program flow it is useful to print a statement to the IDE's console window. This can easily be done with DiagnosticOutput().

For more debug functions see Debug and Output Functions and Debugging.

// This example simply prints "Hello World!" to the debug console.
DiagnosticOutput("Hello World!");
// This example defines a function that prints "Hello World!" to the debug console.
// MAXON API includes
// include header files from core.framwork
#include "maxon/apibase.h"
#include "maxon/string.h"
// ------------------------------------------------------------------------
// Prints "Hello World!" to the console.
// ------------------------------------------------------------------------
static void PrintHelloWorld()
{
const maxon::String world { "World!" };
DiagnosticOutput("Hello @", world);
}

Error System

The MAXON API includes a sophisticated error system. This system allows functions not only to return arbitrary values but also dedicated error objects. Such an error object contains information on what exactly went wrong within the function. For an overview see Error Handling.

This simple code snippet accesses the name of the host machine:

// get machine data
const maxon::DataDictionary data = maxon::Application::GetMachineInfo();
// get machine name
const maxon::Result<maxon::String> result = data.Get(maxon::MACHINEINFO::COMPUTERNAME);

maxon::Application::GetMachineInfo() returns a DataDictionary that includes various system data. To access the data one can use maxon::DataDictionaryInterface::Get(). This function does not return a primitive value but a maxon::Result object. Such a maxon::Result object contains either the return value or an error object.

It is possible to manually handle the maxon::Result object. See also Error Result.

// get machine data
const maxon::DataDictionary data = maxon::Application::GetMachineInfo();
// get machine name
const maxon::Result<maxon::String> result = data.Get(maxon::MACHINEINFO::COMPUTERNAME);
// check if the Result object contains an error or not
if (result == maxon::OK)
{
// get the value stored in the Result object
const maxon::String name = result.GetValue();
DiagnosticOutput("Machine Name: @", name);
}
else
{
// get the error stored in the Result object
const maxon::Error& err = result.GetError();
DiagnosticOutput("Error: @", err);
}

The preferred way of handling errors is to use various attributes. These attributes can be used to handle the program flow in the case of an error. E.g. ifnoerr() executes the given code only if no error occurred.

// get machine data
maxon::DataDictionary data = maxon::Application::GetMachineInfo();
// get the machine name
ifnoerr (const maxon::String name = data.Get(maxon::MACHINEINFO::COMPUTERNAME))
{
// print the machine name
DiagnosticOutput("Machine Name: @", name);
}

Within a function that returns a maxon::Result one can return either a valid return value or an error object. Such an error object can be one of many build-in error types. See Error Types.

Within such a function one can also return the error returned by a sub-function. This is automatically done by using the attribute iferr_return. To use this macro one must prepare the scope using iferr_scope.

The attribute iferr_scope_handler allows to define some code that is invoked when an error is detected with iferr_return.

// This example defines a function that uses iferr_scope_handler to handle any internal error.
// ------------------------------------------------------------------------
// Prints the machine name to the console.
// If an error occurs, the error is printed instead.
// ------------------------------------------------------------------------
static void PrintMachineName()
{
// handle errors
{
// print error
// "err" is defined within iferr_scope_handler
DiagnosticOutput("Error: @", err);
};
// get application data
const maxon::DataDictionary data = maxon::Application::GetMachineInfo();
// get machine name
const maxon::String machineName = data.Get(maxon::MACHINEINFO::COMPUTERNAME, "Unknown"_s) iferr_return;
// print machine name
DiagnosticOutput("Machine Name: @", machineName);
}
// This example defines a function that uses iferr_scope in order to handle errors using iferr_return.
// core.framework
// ------------------------------------------------------------------------
// Compares the machine name with the given name.
// @param[in] name A maxon::String to be compared with the machine name.
// @return True if the name is equal to the machine name; false if the name is not equal to the machine name.
// ------------------------------------------------------------------------
static maxon::Result<maxon::Bool> CompareMachineName(const maxon::String& name)
{
// needed for "iferr_return"
// check if the given string argument is set
// return an error if the string is empty
if (name.IsEmpty())
return maxon::IllegalArgumentError(MAXON_SOURCE_LOCATION, "\"name\" argument is empty."_s);
// get the machine name
// return an error if the machine name could not be obtained
const maxon::DataDictionary data = maxon::Application::GetMachineInfo();
const maxon::String machineName = data.Get(maxon::MACHINEINFO::COMPUTERNAME) iferr_return;
// return true if the strings are the same
if (name.Compare(machineName) == maxon::COMPARERESULT::EQUAL)
return true;
return false;
}

Classic API Foundations

Basic Plugin Functions

Every plugin that uses the classic API (linking to cinema.framework) must implement the global functions PluginStart(), PluginMessage() and PluginEnd(). This is typically done in a main.cpp file.

PluginStart() is used to register classic API plugins. PluginMessage() can receive various global messages. PluginEnd() is used to free data before the plugin in unloaded when Cinema 4D ends. See Plugin Functions Manual.

Classic plugins are created by implementing a plugin class e.g. ObjectData or TagData. Such an implementation must be registered in PluginStart() using a specific function like RegisterObjectPlugin(). See General Plugin Information Manual.

C4DAtom

C4DAtom is the base class for many elements of the classic API. It gives access to the element's type, its parameter Description, parameter values and dirty state. The class is also used to copy the element and send messages to it. See C4DAtom Manual.

// "cubeObject" is a BaseObject which is based on C4DAtom.
// This example checks the object's type, reads a parameter
// and copies the object.
// get type
const Int32 type = cubeObject->GetType();
DiagnosticOutput("Type: @", type);
// get a parameter value
GeData data;
cubeObject->GetParameter(PRIM_CUBE_LEN, data, DESCFLAGS_GET::NONE);
const ::Vector size = data.GetVector();
DiagnosticOutput("Size: @", size);
// create copy
C4DAtom* const clone = cubeObject->GetClone(COPYFLAGS::NONE, nullptr);
if (clone == nullptr)
return maxon::OutOfMemoryError(MAXON_SOURCE_LOCATION);
// insert object into the BaseDocument
BaseObject* const objectClone = static_cast<BaseObject*>(clone);
doc->InsertObject(objectClone, nullptr, nullptr);

GeListNode

GeListNode is another frequently used base class. It allows to organize elements in lists and trees. It also gives access to the element's document and registration information. See GeListNode Manual.

// "sceneObject" is a BaseObject which is based on GeListNode.
// This example accesses the child and next object, the
// BaseDocument and registration information on the object.
// get child object
BaseObject* const childObject = sceneObject->GetDown();
// get next object
BaseObject* const nextObject = sceneObject->GetNext();
// get the document
BaseDocument* const document = sceneObject->GetDocument();
// get info
const Int32 info = sceneObject->GetInfo();
{
DiagnosticOutput("Object is hidden in the Plugins menu");
}

BaseList2D

The base class BaseList2D provides functions to handle the element's name, animation tracks, internal data, bits, shaders, layers and IDs. See BaseList2D Manual.

// This example creates a new Material.
// The "Material class is based on "BaseList2D",
// so it is possible to insert shaders and assign layers.
// create a new material
Material* const newMaterial = Material::Alloc();
if (newMaterial == nullptr)
return maxon::OutOfMemoryError(MAXON_SOURCE_LOCATION);
// insert material
doc->InsertMaterial(newMaterial);
// set name
newMaterial->SetName("The new Material"_s);
// make a new shader
BaseShader* const noiseShader = BaseShader::Alloc(Xnoise);
if (noiseShader == nullptr)
return maxon::OutOfMemoryError(MAXON_SOURCE_LOCATION);
// insert shader
newMaterial->InsertShader(noiseShader);
// make layer
GeListHead* const root = doc->GetLayerObjectRoot();
if (root == nullptr)
return maxon::UnexpectedError(MAXON_SOURCE_LOCATION);
LayerObject* const newLayer = LayerObject::Alloc();
if (newLayer == nullptr)
return maxon::OutOfMemoryError(MAXON_SOURCE_LOCATION);
newLayer->SetName("New Layer"_s);
// insert layer
root->InsertLast(newLayer);
// set layer
newMaterial->SetLayerObject(newLayer);

Parameter IDs

The functions C4DAtom::GetParameter() and C4DAtom::SetParameter() allow to access the parameter values of objects, materials, tags etc. The ID of such a parameter is defined using a DescID object. Such a DescID object is composed of multiple DescLevel elements. The complete DescID is also needed when handling an animation track of a given parameter. See DescID Manual.

// This example reads a parameter of the given cube object
// and creates an animation track for that parameter.
// construct DescID
DescID parameterID;
parameterID.PushId(DescLevel(VECTOR_X, DTYPE_REAL, 0));
// get value
GeData data;
cubeObject->GetParameter(parameterID, data, DESCFLAGS_GET::NONE);
const Float value = data.GetFloat();
DiagnosticOutput("Value: @", value);
// make animation track
// search for the track
CTrack* track = cubeObject->FindCTrack(parameterID);
if (track == nullptr)
{
// track not found, create new track
track = CTrack::Alloc(cubeObject, parameterID);
if (track == nullptr)
return maxon::OutOfMemoryError(MAXON_SOURCE_LOCATION);
// add track to stage object
cubeObject->InsertTrackSorted(track);
}

MAXON API Foundations

Interfaces

An interface is a public abstract declaration of a class. Such an interface can be implemented one or multiple times; such implementations are separated from the public interface declaration. This interface system is the base for most MAXON API classes. See MAXON API Interfaces.

// This example shows the declaration of a simple interface.
// ---------------------------------------------------------------------
// Simple class that stores a maxon::Int number.
// ---------------------------------------------------------------------
class SimpleClassInterface : MAXON_INTERFACE_BASES(maxon::Object)
{
MAXON_INTERFACE(SimpleClassInterface, MAXON_REFERENCE_NORMAL, "net.maxonexample.interfaces.simpleclass");
public:
// ---------------------------------------------------------------------
// Sets the number to store.
// ---------------------------------------------------------------------
MAXON_METHOD void SetNumber(maxon::Int number);
// ---------------------------------------------------------------------
// Returns the stored number.
// ---------------------------------------------------------------------
MAXON_METHOD maxon::Int GetNumber() const;
};
// This interface is declared in a file named "simpleclass.h". The automatically
// generated files are therefore named "simpleclass1.hxx" and "simpleclass2.hxx"
// The .hxx header files define the reference class "SimpleClassRef".
#include "simpleclass1.hxx"
// declare the published objects "SomeSimpleClass" and "OtherSimpleClass"
// that give access to implementations of SimpleClassInterface
// the declaration must be placed between the two hxx files since "SimpleClassRef" is defined in the first hxx file
MAXON_DECLARATION(maxon::Class<SimpleClassRef>, SomeSimpleClass, "net.maxonexample.somesimpleclass");
MAXON_DECLARATION(SimpleClassRef, OtherSimpleClass, "net.maxonexample.othersimpleclass");
#include "simpleclass2.hxx"
// This example shows the implementation of a simple interface.
// This class implements SimpleClassInterface.
class SimpleClassImplementation : public maxon::Component<SimpleClassImplementation, SimpleClassInterface>
{
public:
// implementation of interface methods
MAXON_METHOD void SetNumber(maxon::Int number)
{
_value = number;
}
MAXON_METHOD maxon::Int GetNumber() const
{
return _value;
}
// private data only available inside the implementation
private:
maxon::Int _value = 1;
};
// This example creates an instance of an interface
// and uses the created instance.
// define the ID of the component to use
const maxon::Id id { "net.maxonexample.class.somesimpleclass" };
// get component class of the given ID from the global maxon::Classes registry
const maxon::Class<SimpleClassRef>& componentClass = maxon::Classes::Get<SimpleClassRef>(id);
// create reference
const SimpleClassRef simpleClass = componentClass.Create() iferr_return;
// use reference
simpleClass.SetNumber(123);
const maxon::Int number = simpleClass.GetNumber();
DiagnosticOutput("Number: @", number);

References

The MAXON API makes heavy use of reference counting. New instances of a given class are typically handled as reference counted objects. The instance will be freed when all references using that object are deleted. See References.

// This example allocates a new Vector and handles the ownership using a StrongRef.
// When the StrongRef object is deleted; the Vector instance gets freed.
{
// allocate an object
// typically one should not allocate objects this way; this is only an example
// strong reference takes ownership
const maxon::StrongRef<maxon::Vector> vectorRef { vec };
// edit referenced object
vectorRef->x = 100.0f;
vectorRef->y = 200.0f;
vectorRef->z = 300.0f;
DiagnosticOutput("Vector: @", vectorRef);
// when the scope is left, the referenced object is deleted
}

Registries

A registry is used to store implementations of a given interface. Using such a registry it is possible to access all implementations of that interface. See Registries.

// This example prints the IDs of all stream conversion implementations.
// These implementations are registered at maxon::StreamConversions.
// check all stream conversions
for (const auto& it : maxon::StreamConversions::GetEntriesWithId())
{
// get ID
const maxon::Id& eid = it.GetKey();
DiagnosticOutput("Stream Conversion: @", eid);
}

Published Objects

A published object gives access to a certain object. This can be any kind of object in memory; typically it is a class representing an implementation of an interface. See Published Objects.

// This example uses a specific stream conversion implementation.
// An instance of that implementation is created by obtaining
// the reference class stored at the published object "HexEncoder".
// get encoder factory from published object StreamConversions::HexEncoder
// and use it to create a new instance
const maxon::StreamConversionRef hexEncoder = maxon::StreamConversions::HexEncoder().Create() iferr_return;
// the original number
const maxon::Int number = 123;
source.Append((maxon::Char)number) iferr_return;
// convert to HEX
hexEncoder.ConvertAll(source, destination) iferr_return;
// print result
const maxon::String hexString { destination };
DiagnosticOutput("Hex: @", hexString);