Implementing Plugins¶
Explains the basic concepts of implementing plugins.
A Python plugin is a .pyp module that Cinema 4D loads at startup. It usually registers a hook: a class derived
from a c4d.plugins data class that Cinema 4D calls to implement a command, object, tag, tool, or file format.
This guide covers registration, scene-node hooks, plugin files and resources, and application lifecycle messages.
Unlike a Script Manager script, a plugin is loaded once at startup, can keep state for the whole session, and can bring its own interface and localization resources.
Hooks¶
The list below shows every Python plugin hook. Click a hook to reveal its registration function and the SDK examples
that demonstrate it. The lists are generated from the .pyp files of the SDK, so one example can show up for multiple
hooks, and a hook without an example is still fully documented in the c4d.plugins API.
Hook
Description
Commands and Messages
CommandData
Runs on demand from menus, palettes, and shortcuts.
Example |
Description |
|---|---|
Demonstrates a dialog with dynamic content, where the content changes based on user input. |
|
Demonstrates the most basic command/dialog plugin possible. |
|
Demonstrates how to implement a dialog that hosts an HTML viewer and can send and receive data to/from the displayed web page. |
|
Demonstrates how to store dialog layout data and values persistently over dialog open/close and Cinema 4D restart boundaries. |
|
Demonstrates a simple way to store dialog layout data and values persistently over dialog open/close boundaries (but not over Cinema 4D restart boundaries). |
|
Demonstrates a plugin that uses resources to define its dialog layout and string translations. |
|
Demonstrates a simple dialog with two number input fields, a combo box and a button to carry out numeric operations on the two numbers. |
|
Demonstrates implementing a simple legacy memory viewer dialog. |
|
Demonstrates the legacy Sculpting API; it is highly specialized and not that useful outside of sculpting. |
|
Demonstrates implementing a simple legacy texture baking dialog. |
|
Demonstrates a tool finalization workflow at the example of editing SDS weights. |
|
Demonstrates how to implement licensing checks in plugins. |
|
Demonstrates simple licensing workflow for Python plugins in Cinema 4D. |
MessageData
Listens to application-wide core messages.
No SDK examples are available for this hook yet.
Scene Elements
ObjectData
Implements objects, generators, and deformers.
Example |
Description |
|---|---|
Demonstrates how to implement licensing checks in plugins. |
|
Demonstrates NodeData serialization and custom branching. |
|
Demonstrates how to implement a scene element that dynamically updates its icon. |
|
Demonstrates how to implement a spline object generator with handles to drive parameters. |
|
Demonstrates how to implement a node (at the example of an object) that dynamically updates its parameters. |
|
Demonstrates how to implement a classic particle modifier object that applies a simple gravitation effect for each particle. |
|
Demonstrates how to implement a scene element respecting OpenColorIO color management. |
|
Demonstrates a spline offset generator (very questionable design, should not be followed). |
|
Demonstrates how to implement a polygonal object generator with handles to drive parameters. |
|
Demonstrates how to implement a modifier object that modifies a point object using the pull sculpting brush tool. |
|
Demonstrates how to implement a deformer object that pushes the points of a point object to a spherical shape, driven by radius and strength parameters. |
TagData
Implements tags attached to objects.
Example |
Description |
|---|---|
Demonstrates how to implement a tag that forces the host object to look at the camera (like the Look At Camera Tag). |
ShaderData
Implements classic material shaders.
Example |
Description |
|---|---|
Demonstrates how to implement a shader that computes a fresnel effect. |
NodeData
Generic base for custom node elements.
No SDK examples are available for this hook yet.
Interactive Tools
ToolData
Handles interactive viewport input.
Example |
Description |
|---|---|
Demonstrates how to implement a simple tool. |
|
Demonstrates how to implement a ToolData with a linkBox where it’s possible to drag and drop an object, which is then cloned and added to the document in a random position. |
SculptBrushToolData
Implements sculpting brushes.
Example |
Description |
|---|---|
Demonstrates how to implement a brush tool that modifies a BaseObject by grabbing all points under the brush. |
|
Demonstrates how to implement a brush tool that rasterizes the stencil onto the polygons touched by the brush, accessing the stencil and BodyPaint layer. |
|
Demonstrates how to implement a brush tool that modifies a BaseObject by pulling all points under the brush. |
|
Demonstrates how to implement a brush tool that modifies a BaseObject by twisting all points under the brush. |
File Formats
SceneLoaderData
Imports a scene file format.
Example |
Description |
|---|---|
Demonstrates a scene loader plugin for IES Meta data. |
SceneSaverData
Exports a scene file format.
Example |
Description |
|---|---|
Demonstrates how to implement a scene saver plugin that exports all IES Meta information from the current document to a txt file, iterating through all objects in the scene. |
BitmapLoaderData
Reads an image file format.
Example |
Description |
|---|---|
Demonstrates how to implement a custom bitmap loader to import a custom picture format into Cinema 4D. |
BitmapSaverData
Writes an image file format.
Example |
Description |
|---|---|
Demonstrates how to implement a custom bitmap saver to export a custom picture format into Cinema 4D. |
Preferences
PreferenceData
Adds a page to the Preferences.
Example |
Description |
|---|---|
Demonstrates how to implement a preference category in the Cinema 4D preference dialog, exposing parameters as global preferences. |
Module-Level Hooks
Render tokens
Replaces tokens in render paths.
Example |
Description |
|---|---|
Demonstrates how to register two Tokens, one visible in the render settings and the other not. |
PluginMessage()
Receives startup, menu, and shutdown messages.
Example |
Description |
|---|---|
Demonstrates how to implement licensing checks in plugins. |
|
Demonstrates how to build custom menus for your plugins. |
Writing and Registering Hooks¶
Hook classes derive from BaseData and override the callbacks they need. For example, a
CommandData runs when the user invokes its command:
import c4d
class HelloCommand(c4d.plugins.CommandData):
"""Prints a message when the command is executed."""
def Execute(self, doc: c4d.documents.BaseDocument) -> bool:
print("Hello from a command!")
return True
Registration¶
Every hook is made known to Cinema 4D with a dedicated Register*() function in c4d.plugins. Which argument
that function expects depends on the hook:
Hooks that exist once per session, such as
CommandData,MessageData, orToolData, take an instance of your class via thedatargument.Hooks that exist once per scene element, such as
ObjectData,TagData, orShaderData, take the class itself via thegargument, so that Cinema 4D can create one instance for each node. These hooks also take the name of the description resource that defines their parameters.
PLUGIN_ID: int = ... # Replace with your unique ID from developers.maxon.net.
c4d.plugins.RegisterCommandPlugin(
id=PLUGIN_ID, str="Hello Command", info=0, icon=None, help="Says hello.", dat=HelloCommand())
For an object plugin, pass g=MyObjectData (the class, not an instance) to
RegisterObjectPlugin() and provide its description resource name and object flags. See
py_obj_double_circle for a complete example.
Registration functions return True on success. Do this work inside an if __name__ == "__main__": block, as
the SDK examples do, so that importing the module from elsewhere does not register the plugin twice.
Warning
The ID passed to a registration function must be globally unique. If two plugins claim the same ID, only one of them is loaded at startup. Always obtain IDs from the plugin ID generator on developers.maxon.net to avoid collisions.
The info argument takes PLUGINFLAG_* flags. Use PLUGINFLAG_HIDEPLUGINMENU to place a command in a custom
menu without duplicating its default entry (Enhancing the Main Menu). For localized plugin names and help text,
load str and help from string resources (Resource Files).
Scene Nodes and Their Hooks¶
Hooks for scene elements, such as ObjectData or TagData, split a plugin into two layers.
The scene layer consists of the nodes in the document, e.g.,
BaseObjectandBaseTag, which share the base classBaseList2D. Cinema 4D creates, stores, and manages these nodes. You only work with them through the node API.The implementation layer consists of the instances of your hook class. It is yours: you decide what a node generates, draws, or does, and which data it keeps.
You register a hook class only once. Cinema 4D then creates one instance of it for each node of that type, and links the node and its instance with each other. Built-in nodes, such as the Cube in the diagram, have no Python implementation.
Fig. 1: The two layers of node plugins. The scene tree is driven by Cinema 4D, the instances of the registered hook classes are provided by the plugin.
The two sides see each other in different ways:
A node is mostly opaque about its implementation. It only hands it out when explicitly asked, with
BaseList2D.GetNodeData(). This works for Python-defined nodes only.An implementation is always given the node it is working for, as an argument of its callbacks, e.g.,
nodeinNodeData.InitoropinObjectData.GetVirtualObjects. Outside of callbacks,NodeData.Get()returns the node of an instance.
This also defines the lifetime of an instance:
- When you pass an instance to the registration function, that instance is kept for the whole Cinema 4D session.
This applies to command and message hooks, which have no scene node.
- When you pass a class to the registration function, there is a 1:1 relationship between an instance and a node
in the scene. Cinema 4D creates and frees the implementation with the node, calling
NodeData.Initafter construction andNodeData.Freebefore destruction. The node link is not available in the constructor; initialize node parameters inInit().
Threading and Startup¶
Many hook methods, such as GetVirtualObjects or the Execute method of a tag, are called from a threading
context, where you must not modify the document or touch the GUI. The module level code of a plugin runs in the
restricted boot phase of Cinema 4D. Avoid GUI operations, long running file operations, or web requests
there. See Scene Execution for the execution model and Licensing for the consequences of
the boot phase.
Plugin Files¶
Cinema 4D searches the plugins folder in the user preferences folder at startup, and executes every file with the
suffix .pyp, or .pypv for encrypted plugin files. A .pyp file can be placed directly into that folder, but it
is usually better to group a plugin in a subfolder.
A plugin with resources typically has this layout:
myPlugin/
├── myPlugin.pyp # The plugin module, registers the hooks.
├── myIcon.tif # Any other files the plugin needs, e.g., icons.
└── res/ # The plugin resources.
├── c4d_symbols.h # Global symbols of the plugin, e.g., string and dialog IDs.
├── description/ # Description resources for objects, tags, shaders, etc.
│ ├── omyobject.h # The parameter symbols of the description.
│ └── omyobject.res # The parameter layout of the description.
├── dialogs/ # Dialog resources for GeDialog.
│ └── idd_main.res
├── strings_en-US/ # The English strings, required.
│ ├── c4d_strings.str # Generic strings, e.g., the plugin name and help text.
│ ├── description/
│ │ └── omyobject.str # The strings of the description.
│ └── dialogs/
│ └── idd_main.str # The strings of the dialog.
└── strings_de-DE/ # Any other language, same structure as en-US.
└── ...
The .pyp file registers hooks; res holds descriptions, dialogs, and translations. Locate assets relative to
__file__ rather than the process working directory:
import os
pluginFolder: str = os.path.dirname(__file__)
iconPath: str = os.path.join(pluginFolder, "myIcon.tif")
Warning
If the console reports Could not find required ‘__res__’. at startup, check the plugin’s res directory and
its required symbol and string files.
Resource Files¶
Resources separate the definition of an interface and its text from the code that drives it. A plugin that implements an object, tag, or shader needs a description resource to expose its parameters. A plugin with a dialog can define it in a dialog resource instead of building it in code. Any plugin should put user-facing strings into string tables, so that it can be translated.
This section covers the files and their folders. Dialog and Description Resources explains how to write dialog and description resources.
File |
Purpose |
|---|---|
|
Declares the global symbols of the plugin as an |
|
The parameter layout of an object, tag, shader, or other scene element, and the symbols for its parameters.
The file name must match the |
|
The markup of a dialog, loaded with |
|
The strings for a language. Each |
Using Symbols and Strings¶
The symbols of description headers are available as attributes of c4d, e.g., c4d.PYCIRCLEOBJECT_RAD. The
symbols in c4d_symbols.h are not. Import them into the module namespace with mxutils.ImportSymbols(), see
Symbol Parsing, and load a localized string with c4d.plugins.GeLoadString():
import os
import typing
import c4d
import mxutils
# Makes ID_PLUGIN, IDS_PLUGIN_NAME, etc. available as module attributes.
mxutils.ImportSymbols(os.path.join(os.path.dirname(__file__), "res"))
if typing.TYPE_CHECKING: # Silences linters, which cannot see resource symbols.
ID_PLUGIN: int
IDS_PLUGIN_NAME: int
IDS_PLUGIN_HELP: int
name: str = c4d.plugins.GeLoadString(IDS_PLUGIN_NAME)
help: str = c4d.plugins.GeLoadString(IDS_PLUGIN_HELP)
A string table in strings_en-US/c4d_strings.str looks like this:
STRINGTABLE
{
IDS_PLUGIN_NAME "Py SDK - Resource Gui";
IDS_PLUGIN_HELP "Opens a dialog window that is populated using resource files.";
}
GeLoadString() returns the string in the current language of Cinema 4D, and falls back to en-US
when the plugin has no translation for it.
Functions such as GeLoadString and LoadDialogResource use the GeResource stored in the
plugin module’s global __res__. Cinema 4D normally creates it for .pyp files. When loading resources from
another module or location, initialize a resource once and share it among the modules that need it:
if __name__ == "__main__":
res: c4d.plugins.GeResource | None = globals().get("__res__", None)
if not isinstance(res, c4d.plugins.GeResource):
res = c4d.plugins.GeResource()
if not res.Init(os.path.join(os.path.dirname(__file__), "res")):
raise RuntimeError("Failed to initialize the plugin resources.")
__res__ = res
MyCommand.Register()
Note
py_cmd_gui_resources builds a dialog from
two resources, loads translated strings, and shows manual __res__ initialization. For a node description and
its description registration argument, see
py_obj_double_circle.
Localization¶
For each language a plugin supports, there must be a strings_<language> folder named after the language code. The
languages that ship with Cinema 4D, as defined in its resource/language folder, are:
Folder |
Language |
|---|---|
|
English (required, fallback language) |
|
German |
|
Spanish |
|
French |
|
Italian |
|
Japanese |
|
Korean |
|
Brazilian Portuguese |
|
Simplified Chinese |
A plugin is not required to support all of them. Develop it in en-US first, then copy the strings folder for each
translation. Other languages can be introduced by adding a folder named after the scheme above to the resource/language
folder of Cinema 4D and placing a language.txt file in it which contains the display name of the language.
Note
Before R20, languages were named by a short code, e.g., strings_us for English or strings_jp for Japanese.
strings_us is still picked up for compatibility, but new plugins should use strings_en-US.
Warning
Non-ASCII characters in .str files, e.g., in Chinese, Japanese, French, or German strings, must be written as
ASCII escape sequences. Cinema 4D has no built-in tool for this conversion; the example script
unicode_encode_strings.py is a starting point for a conversion pipeline.
Plugin Messages¶
Besides hooks, a plugin module can define the function PluginMessage. Cinema 4D calls it for lifecycle events, such
as startup, menu building, and shutdown. Other plugins can send messages to it via c4d.GePluginMessage(). The
function is called for every plugin module that defines it. This is distinct from the MessageData hook, which
receives core messages of the application.
-
PluginMessage(id, data)¶ Called to receive plugin messages. These can either be from Cinema 4D or from other plugins via
GePluginMessage().- Parameters
id (int) –
The message ID. Built-in ones are:
C4DPL_INIT_SYS
Initialize system.
C4DPL_INIT
Initialize the plugin, calling PluginStart.
C4DPL_END
End the plugin, calling PluginEnd.
C4DPL_MESSAGE
Receive plugin message, calling PluginMessage.
C4DPL_BUILDMENU
Called when the menus are built during startup. Use
GetMenuResource()etc. to add own menus here if necessary. See the example.C4DPL_STARTACTIVITY
Sent to all plugins after all PluginStart have been called.
C4DPL_ENDACTIVITY
Sent to all plugins before any PluginEnd has been called. This allows to close dialogs, end threads, delete temporary/undo buffers etc.
C4DPL_CHANGEDSECURITYTOKEN
Sent to all plugins if the Security Token of Team Render was changed.
C4DPL_SHUTDOWNTHREADS
Sent to all plugins before the threading system of Cinema 4D is shutdown. After that no threads should be started.
C4DPL_LAYOUTCHANGED
Sent to all plugins after the layout has been switched.
C4DPL_RELOADPYTHONPLUGINS
Sent when Python plugins are to be reloaded. See Plugin Messages.
C4DPL_COMMANDLINEARGS
Sent to let plugins parse the command line arguments used when starting Cinema 4D. Retrieve the arguments by calling sys.argv. See Command Line Arguments.
C4DPL_EDITIMAGE
Sent to allow plugins to start any other actions when Cinema 4D tries to show an image (e.g. in the shader popup). data is dict{‘imagefn’: str, filename of the image that triggered the message}
C4DPL_ENDPROGRAM
Sent when Cinema 4D is about to close. data is dict{‘cancel’: bool, set to True to cancel close of Cinema 4D}
C4DPL_DEVICECHANGE
Sent when a device has changed. data is dict{‘name’: str, name of the device, ‘eject’: bool Eject, state of the device}
C4DPL_NETWORK_CHANGE
Sent to all plugins if the network configuration on the system changed.
C4DPL_SYSTEM_SLEEP
Sent to all plugins if the system goes to sleep.
C4DPL_SYSTEM_WAKE
Sent to all plugins if the system wakes up.
C4DPL_PROGRAM_STARTED
Sent when the application has been started.
data (any) – The message data.
- Return type
bool
- Returns
True if the message was consumed, otherwise False.
Command Line Arguments¶
To read the command line arguments of Cinema 4D, filter the C4DPL_COMMANDLINEARGS message in
PluginMessage():
import c4d
import sys
def PluginMessage(id: int, data: object) -> bool:
if id == c4d.C4DPL_COMMANDLINEARGS:
print(sys.argv)
return True
return False
Save this code as a .pyp file in the plugins folder, start Cinema 4D with arguments such as Cinema 4D.exe --hello,
and open the console to see them.
Warning
Arguments which are handled by Cinema 4D modules are removed from the list. When you start Cinema 4D with --hello -parallel, -parallel will not show up in the printed arguments.
Note
Reload Python Plugins recompiles and executes .pyp files, but does not reload modules they imported. Handle
C4DPL_RELOADPYTHONPLUGINS in PluginMessage() to release open resources such as sockets or files, and use
importlib.reload() if an imported module must also be reloaded.