Plugins are stored in the plugins folder within the user folder. Each plugin should have a suffix called .pyp or .pypv (for encrypted files). When Cinema 4D starts, it finds all files in this folder that end with .pyp or .pypv and executes the plugin. A simple plugin possible thus looks like this:
def main(): print "Hello World!" main()
Such a plugin is not very interesting, as it is only run when the program starts. Therefore we have the ability to register plugin hooks in various parts of the program.
All plugin hooks are built upon data classes derived from
BaseData
. These data classes contain a set of methods that are called by Cinema 4D. An example from
MessageData
:
class SampleData(plugins.MessageData): def CoreMessage(self, id, bc): pass
To register the derived class with Cinema 4D there is a specific Register*() function for each data class. Some of them take a new object of your data class or just the class so Cinema 4D can create instances on its own:
plugins.RegisterCommandPlugin(id=PLUGIN_ID, str="TestBase-Plugin", info=0, dat=SampleData())
The registration functions for
NodeData
instead want a class name like this:
class SampleData(plugins.ObjectData): def GetVirtualObjects(self, op, hierarchyhelp): pass plugins.RegisterObjectPlugin(id=PLUGIN_ID, str="TestNode-Plugin", g=SampleData, description="", icon=None, info=c4d.OBJECT_GENERATOR)
There are a few things to say about the lifetime of the data class instances, especially with convern to member variables. In those cases where a new object is passed to the registration function, as shown in the first example above, then this instance is kept for the whole Cinema 4D session. Since it is constructor and destructor are called as usual, no special concern is necessary. The data classes where the name needs to be passed to the registration function have a
1:1
correspondence with a node in Cinema 4D. Thus they are allocated and deleted by Cinema 4D along with the node. When this happens the constructor and the destructor are called as usual. However, Cinema 4D additionally calls the
NodeData.Init()
methof after the constructor and
NodeData.Free()
before the destructor.
While .pyp or .pypv can be placed directly in the plugin directory, it is often better to group them into hierarchies. The standard layout for a plugin folder is like this:
myPlugin/ myPlugin.pyp ... res/ c4d_symbols.h description/ myDescription.h myDescription.res ... dialogs/ myDialog.res ... strings_us/ c4d_strings.str description/ myDescription.str ... dialogs/ myDialog.str ... strings_de/ strings_jp/ ... myIcon.tif myWhatever.any ...
The main file is myPlugin.pyp , which registers the hooks. The res directory contains plugin resources, which currently means dialogs, descriptions and strings.
For each description there is a res with the description and a .h file with enums for the constants used in the description. See Descriptions in Cinema 4D . Each dialog is contained in its own .res file. The c4d_symbols.h file should contain enums for the constants used in the .res files.
Then there should be a directory named strings_xx for each language that the plugin supports, where xx is a two-letter language or country code according to the ISO 639 standard or the ISO 3361-1 standard. Currently there are Cinema 4D versions available for the following codes:
us - American English de - German fr - French it - Italian jp - Japanese
Each of the language directories should contain a .str file for every dialog and a c4d_strings.str for other resource strings. It is recommended that you first develop the plugin in one language, and then just copy the strings directory before translating. Finally you can of course have any other files you like in your folder, for example plugin icons or logos. These can be conveniently accessed using __file__ :
dir, file = os.path.split(__file__)
PluginMessage
(
id
,
data
)
¶
Defining this function allows you to receive plugin messages. These can either be from Cinema 4D or from other plugins via
GePluginMessage()
.
Parameters: |
|
||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Return type: |
bool |
||||||||||||||
Returns: |
True if you consumed the message, otherwise False . |
To retrieve Cinema 4D command line arguments at any time, implement
PluginMessage()
and filter the
C4DPL_COMMANDLINEARGS
message:
import c4d import sys def PluginMessage(id, data): if id==c4d.C4DPL_COMMANDLINEARGS: print sys.argv #print arguments return True return False
Warning
Arguments which are handled by Cinema 4D modules are removed from the list. For example, try starting Cinema 4D with “Cinema 4D.exe –hello -parallel”: open the console, parallel is not in the printed arguments list.
The Python in Cinema 4D function reloads and recompiles the source of a .pyp file. Python modules which are imported by a .pyp file will not be reloaded again. Python first checks if the module is already imported, if yes this is skipped and just the reference is set.
You can use
reload()
to force the reload of a Python module when
C4DPL_RELOADPYTHONPLUGINS
message is received in
PluginMessage()
. This is also the place where you can close system resources (e.g. sockets, files) you opened before.
To add your own menus, intercept
C4DPL_BUILDMENU
in
PluginMessage()
and call
GetMenuResource()
to retrieve the main menu resource container. Here is a complete example:
def EnhanceMainMenu(): mainMenu = gui.GetMenuResource("M_EDITOR") # Get main menu resource pluginsMenu = gui.SearchPluginMenuResource() # Get 'Plugins' main menu resource menu = c4d.BaseContainer() # Create a container to hold a new menu information menu.InsData(c4d.MENURESOURCE_SUBTITLE, "Py-Test") # Set the name of the menu menu.InsData(c4d.MENURESOURCE_COMMAND, "IDM_NEU") # Add registered default command 'New Scene' to the menu menu.InsData(c4d.MENURESOURCE_SEPERATOR, True); # Add a separator menu.InsData(c4d.MENURESOURCE_COMMAND, "PLUGIN_CMD_5159") # Add command 'Cube' with ID 5159 to the menu submenu = c4d.BaseContainer() # Create a new submenu container submenu.InsData(c4d.MENURESOURCE_SUBTITLE, "Submenu") # This is a submenu submenu.InsData(c4d.MENURESOURCE_COMMAND, "IDM_SPEICHERN") # Add registered default command 'Save' to the menu menu.InsData(c4d.MENURESOURCE_SUBMENU, submenu) # Add the submenu if pluginsMenu: # Insert menu after 'Plugins' menu mainMenu.InsDataAfter(c4d.MENURESOURCE_STRING, menu, pluginsMenu) else: # Insert menu after the last existing menu ('Plugins' menu was not found) mainMenu.InsData(c4d.MENURESOURCE_STRING, menu) def PluginMessage(id, data): if id==c4d.C4DPL_BUILDMENU: EnhanceMainMenu()
Note
If menus are modified from outside
C4DPL_BUILDMENU
message,
gui.UpdateMenus()
needs to be called.