at the end of this week, I will leave Maxon for a new direction in my life.
In the last five years, I tried to give you the best answers to your questions and to provide you with documentation and examples that would make your daily life easier.
During my time here, I met remarkable developers and learned about their projects. I learned a lot by working with you here on the forum, via mail or in person. Especially, I want to thank Riccardo, Maxime and Manuel for being great colleagues and an amazing team. Be assured that this team will continue to work with great commitment to make sure you get the support you need.
I wish you all the best.
the "Help" does not use a GeUserArea.
The "Help" is displayed using the HtmlViewerCustomGui GUI element. This GUI element encapsulates the local browser to display a HTML document.
If you want to create the same user experience, you can simply use that GUI element to load a custom HTML document that presents your information.
As always you can use the Q&A Functionality to mark a thread as a question.
Cinema 40 R20 introduces a new, elaborate error handling system. A function can return now a dedicated result object that stores additional information if the operation was successful or not.
NewMem does return such a result value. You find information on how to handle errors in the documentation: Error Handling. See also Error System in the API Transition.
That being said, in most cases there should be no reason to allocate memory with NewMem. It is recommended to use arrays like BaseArray instead. Such an array provides save access to and management of the allocated data.
An object like BaseObject can store data. This data is typically stored in the object's BaseContainer. But there are cases where the data is not stored in the BaseContainer so in general it is saver to use GetParameter().
A BaseContainer can store any kind of data. This includes other BaseContainers. The BaseContainer of the object stores a sub-BaseContainer that stores all the values of the user data. The ID of the User-Data-BaseContainer is c4d.ID_USERDATA. Using this ID you can obtain the BaseContainer:
bc = op.GetData()
userDataValues = bc[c4d.ID_USERDATA]
userDataValues = op.GetParameter(c4d.ID_USERDATA, c4d.DESCFLAGS_GET_0)
This BaseContainer stores the parameter values of user data parameters.
A parameter is identified using a DescID. This DescID has nothing to do with UUID.
A DescID is composed of one or several DescLevel components. In most cases the DescID has only one DescLevel. The DescLevel defines these properties:
A DescID can have multiple DescLevels since a parameter may be stored inside another parameter. As described above, user data values are stored in a BaseContainer that is stored in the default BaseContainer. The full DescID of a user-data parameter has to reflect that. The DescID of a user-data parameter must first identify the user-data container and then the actual parameter in that container. This is done using two DescLevels:
id = c4d.DescID(c4d.DescLevel(c4d.ID_USERDATA, c4d.DTYPE_SUBCONTAINER, 0), c4d.DescLevel(1))
value = op.GetParameter(id, c4d.DESCFLAGS_GET_0)
But the Python API also allows you to do the same with less code:
value = op[c4d.ID_USERDATA, 1]
Or you access the user-data BaseContainer:
bc = op.GetData()
userDataValues = bc[c4d.ID_USERDATA]
value = userData
The DescIDs are not necessarily stored in a file. The number of parameters of an object may change depending on how the object is used. The list of all displayed parameters is stored in the parameter Description. This Description contains the IDs as well as information on how to display the parameters in the Attribute Manager. The information how the parameter should be displayed is also stored as a BaseContainer (but this is NOT the object's BaseContainer).
The parameter description of user data parameters is stored in the object. You can access this data with GetUserDataContainer():
for id, descriptionBC in op.GetUserDataContainer():
print("User Data ID: " + str(id))
print("User Data Value: " + str(op[id]))
print("User Data Name: " + descriptionBC[c4d.DESC_NAME])
To make that clear: the VALUES of user data parameters are stored in the object's BaseContainer. The DESCRIPTION of the user data parameters is stored in the BaseContainers accessed with GetUserDataContainer().
You find an overview over the most fundamental concepts and classes currently in the C++ documentation under Foundations, especially Classic API Base Classes.
You can also find further information about these classes:
you can dynamically change the icon of ObjectData and TagData based plugins by implementing NodeData.Message() and reacting to the message MSG_GETCUSTOMICON. There you can define the BaseBitmap used as the icon. Something like this:
if type == c4d.MSG_GETCUSTOMICON:
# load a standard Cinema icon
icon = c4d.bitmaps.InitResourceBitmap(c4d.RESOURCEIMAGE_OK)
data["bmp"] = icon
data["w"] = icon.GetBw()
data["h"] = icon.GetBh()
# set as filled
data["filled"] = True
You find related information in the C++ documentation (NodeData::Message() Manual).
as Cairyn pointed out, the undo system does not support nesting.
So calling CallCommand() multiple times is the same as pressing multiple buttons in the GUI - it will create multiple undo steps. The only alternative is to not use CallCommand() but to do the specific operations (using the API) yourself.
you might have to add the pyp file path to the system path using:
you can disable style checks in the projectdefinition.txt file of your project. See Stylecheck.
Also just a reminder that only Visual Studio 2015 is officially supported to develop Cinema 4D R20 plugins.
the best solution is probably to create the BaseArray in the caller and hand over a reference to that BaseArray. Your function can then safely fill the BaseArray:
maxon::Result<void> GetVertexNormals(PolygonObject * const pObj, maxon::BaseArray<Vector>& normals)
If you encounter unexpected break points, please open a new thread to discuss this issue.
as @eZioPan has shown you can use the C4DNoise class to sample a noise. Additionally, you find further noises in the c4d.utils.noise namespace.
A noise function typically just returns a scalar value. So if you want to create a color you have to combine different noises (with different seeds) or you sample the same noise with different offsets.
Please also make sure to mark your post as a question using our Q&A system.
And if you are only interested in BaseObjects, there is also GetObjectName().
See BaseObject Manual.
a FILENAME parameter has no options to set a suffix.
If you want to only select files of a specific type you could implement SetDParameter() to check if the given Filename is acceptable.
Hello and welcome to the PluginCafe,
Please post your questions regarding the Cinema 4D API in the "Cinema 4D Development" forum.
Also, please use tags and the Q&A system to mark questions. Please read "Read Before Posting".
To handle links when coping objects one can use a AliasTrans object. This object allows to fix links after objects are copied.
trans = c4d.AliasTrans()
if trans is None or trans.Init(doc) is False:
objects = doc.GetActiveObjects(c4d.GETACTIVEOBJECTFLAGS_0)
for obj in objects:
clone = obj.GetClone(c4d.COPYFLAGS_0, trans)
if clone is not None:
clone.SetName(obj.GetName() + " clone")
doc.InsertObject(clone, None, None)
You find more information in the C4DAtom Manual or the AliasTrans Python documentation.
ColorDialog is just the Python version of C++'s GeChooseColor() function.
It uses the flags DR_COLORFIELD_ICC_BASEDOC and DR_COLORFIELD_ICC_BPTEX which just define the proper color profile. Typically is is enough to just set 0.
there should be header files for all shaders.
The header file of Xgradient is xslagradient.h.
The header file of Xvertexmap is xslavertexmap.h.
actually, three-component vectors (DTYPE_VECTOR) and four-component vectors (DTYPE_VECTOR4D) behave differently. DTYPE_VECTOR shows <<Multiple>> component-based while DTYPE_VECTOR4D shows <<Multiple>> for all components if only one component is different.
DTYPE_VECTOR and CUSTOMGUI_VECTOR are very old parts of Cinema 4D, so they are not implemented as iCustomGui plugins. DTYPE_VECTOR4D displays <<Multiple>> for all components if one component is different. So I'm afraid there isn't any source code within Cinema that does what you want to do.
The typical procedure is to obtain the '"tristate" using
_isTristate = tristate.GetTri();
and then using this value with the GeDialog functions. E.g. the last argument of GeDialog::SetFloat() is a "tristate" argument.
If your custom data type should behave like DTYPE_VECTOR4D, then it must display <<Multiple>> for all components if only one component is different.
a volume loader is just an ordinary generator. This means the stuff it creates is in its cache. You can access the volumes stored in its cache as usual:
if (baseObject->IsInstanceOf(Ovolumeloader) == false)
BaseObject* const cache = baseObject->GetCache();
if (cache == nullptr)
if (cache->IsInstanceOf(Ovolume) == false)
const VolumeObject* const volumeObject = static_cast<VolumeObject*>(cache);
ApplicationOutput("Volume Object: " + volumeObject->GetName());
See also VolumeObject Manual.
please post your questions regarding the Cinema 4D APIs in the "Cinema 4D Development" category. Also, please use tags and the Q&A system. You find all that information in "Read Before Posting".
Is your question how to get the currently selected item of the combo box?
You can implement the "Command" function of your dialog to be informed when the selected value changed. This way you could rename the object when that happens.
Alternatively, you can access the selected element using a custom member function of the dialog. You can use the returned value to do what you want in the caller code.
Something like this:
self.AddComboBox(5000, c4d.BFH_LEFT, 100, 10, False)
self.AddChild(5000, 0, "Child 0")
self.AddChild(5000, 1, "Child 1")
self.AddChild(5000, 2, "Child 2")
def Command(self, id, msg):
# print currently selected "child""
if id == 5000:
value = self.GetInt32(5000)
print("got value: " + str(value))
return c4d.gui.GeDialog.Command(self, id, msg)
# custom function
dialog = TestDialog()
dialog.Open(c4d.DLG_TYPE_MODAL_RESIZEABLE, 132456, -1 ,-1 ,400 ,400)
value = dialog.GetSelectedChild()
using CallButton() is the same as pressing the button in the GUI. So the CallButton() function does not return anything.
The "Add Goal" functionality creates a new Null object. This null object is inserted after the host object of the tag. Sou you could simply get the "next" object to get the goal object.
tag = doc.GetActiveTag()
if not tag:
# should check for type of course
# assume that the newly created null object is now the next object after the host object
hostObject = tag.GetObject()
nextObject = hostObject.GetNext()
Alternatively, you could simply read the now defined BaseLink to the goal object:
goalObject = tag[c4d.ID_CA_IK_TAG_TARGET]
using CallCommand() is the same as pressing the button in the GUI. This means that if you use CallCommand(), the complete command is executed - including any undo-creation within that command.
You find an overview over the undo system in the C++ documentation: Undo System Manual.