XML3D Cinema4D exporter plug-in

In addition to our blender exporter, we now offer an XML3D exporter plug-in for Maxon’s Cinema4D R12. As usual it is available on github under the following URL: https://github.com/xml3d/Cinema4D-Exporter.

Installation:

Copy the xml3dExporter and xml3dMouseEventTag directories to your <Cinema4D_Install>/plugins directory:

Restart Cinema4D. You will find it in the Python menu under Plugins->XML3DExporter

Exporting to XML3D


Usage:

  • Create your scene.
  • To add some XML3D mouse events to a object, right-click on the object and select Python Tags -> XML3DMouseEventTag
    Adding XML3D mouse event tag
  • You can then fill in the events in the attribute window
    Editing XML3D mouse event tag
  • When you are ready to export, go to Python->Plugins->XML3DExporter
  • Pick the size of the display window the XML3D element should have
  • Tick the Embed into XHTML if you want to directly display in XML3D-enabled browser
  • Hit Export and pick the filename to store your scene and hit Save.
  • While exporting, a progress bar at the bottom of Cinema4D shows you the progress of the operation.
    Select export options
  • Successful export is announced on the status bar with message Exporting completed: 3.5 seconds.
  • If you do not see the message, something went haywire. In this case, go to Python->Console to activate the debug console. Check the messages there. To submit a bug, send a snapshot of the console to the mailing list.

Working with instances

If a small set of rules is kept, the plugin can correctly export instances from Cinema4D. We highly recommend using this feature as much as possible, as it can save a lot of disk space and loading time.

There are several ways, how you can create instances in Cinema4D:

  • using the Duplicate Tool
  • using MoGraph’s Cloner Object
  • probably some other, we are not aware of.

The important thing is that instances are correctly exported if and only if they appear as Cinema4D instance objects. The instance object has the green “shadow cube” icon and looks like this:

Such an object is a link to some other object as can be seen under the Object tab in Attributes, when the instance is selected:

In this case we can see that instance Cube.0 links to the object Cube. When you are using the Duplicate Tool, everything works out of the box. The Duplicate Tool creates exactly such instances and no additional care is needed.

With the Cloner Object more care is needed, as without special preparations, it will not create any exportable instances. To force it do so, you have to:

  • Tell it to create so-called Render Instances

  • Turn the Cloner Object to an editable using the Make Editable Object button.

Before these two steps your Cloner Object in Objects List looked like this:

and after it should look like this:

Notice that now Cinema4D instances show up again and will thus get correctly exported. Cloner Objects can be generally nested, so you need to repeat these steps on every level of the Cloner Object, where instantiation is desired. This allows you to have fine control over the level of instantiation. For example consider the following scene:

Single seat is the core geometry and four levels of instancing are available:

  • Two single seats form a double seat
  • Two double seats form a quad seat
  • Two quad seats form a row
  • Number of rows form the whole cabin

Now, if we tick Render Instances on all levels as suggested above, will will really get full instantiation, e.g. the only actual geometry will be a single seat:

This is optimal as far as file size goes, but it will create deep transformation hierarchies, which may be not so good for the ray tracer. An alternative is for example to tick the Render Instance only on the top two levels and on the bottom ones leave it unchecked. After Make Editable on ALL levels, we will get a structure like this:

You can see that now four seats are formed by actual geometry and only this quadruple is instanced to a row and rows instanced to a cabin. This increased the amount of real geometry by a factor of four but decreased the nesting level by a factor of two.


Developer Information:

This chapter contains information about the programming internals of the plugins and is intended for future developers and extenders of the plugins.

Cinema4D Plugin Architecture

Cinema4D provides several ways for writing plugins. The most comprehensive one is the C++SDK, which is directly shipped with all Cinema4D versions. The most easiest and nearly as comprehensive as the C++SDK one is Py4D. Plugins can be written in Python programming language. Py4D is a community project and can be installed as plugin (v0.9.99883) for Cinema4D R11.5. The installation is not necessary for versions >= R12, because these versions are directly shipped with Py4D (>= v0.9.99883). Melange and C.O.F.F.E.E. are two more approaches to write plugins.
In the following I will focus on Py4D.

Plugin types

The Py4D API provides several static methods to register the individual plugins. For more details about the parameter, please have a look at the API documentation (see section: Helpful Links).

  • ToolData: RegisterToolPlugin()
  • CommandData: RegisterCommandPlugin()
  • MessageData: RegisterMessagePlugin()
  • BitmapSaverData: RegisterBitmapSaverPlugin()
  • BitmapLoaderData: RegisterBitmapLoaderPlugin()
  • TagData: RegisterTagPlugin()
  • ObjectData: RegisterObjectPlugin()
  • SceneLoaderData: RegisterSceneLoaderPlugin()
  • SceneSaverData: RegisterSceneSaverPlugin()

Basic steps for creating a plugin (e.g. CommandData plugin)

  • Create new file: TestPlugin.pyp
  • Insert definition of extended CommandData class:
import c4d
class XML3DCommandData(c4d.plugins.CommandData):
    def Execute(self, doc):
        return True
  • Define unique plugin id:
PLUGIN_ID_EXPORTER = 1193733
  • Define entry point and register plugin:
if __name__ == "__main__":
    c4d.plugins.RegisterCommandPlugin(id=PLUGIN_ID_EXPORTER, str="XML3DExporter",help="XML3D Exporter",info=0, dat=XML3DCommandData())
  • Create a new folder TestPlugin/ in $(C4D_INSTALL_DIR)/plugins/
  • Put TestPlugin.pyp into $(C4D_INSTALL_DIR)/plugins/TestPlugin/
  • Launch Cinema4D. It scans the plugins/ folder for *.pyp files and executes the code in the main part, which we used to call the appropriate function to register our plugin.

General Exporters Architecture

The exporter plugins are implemented as CommandData plugins. The advantage over implementing the exporters as SceneSaverData is that we can build our own gui. In the following I will explain the core modules of the exporters. I will therefore sometimes refer to the XML3DExporter as example.

Module: XML3DExporterGUI

There are two possibilities to build a gui. Either we define the whole gui as parts in a bunch of resource files or we build it programmatically in our application.
As in every high level programming language or library we can programmatically build the gui by plugging objects into containers and calling the appropiate methods. I refer at this point to the API (see Helpful Links). Just one information: We have to derive our own gui from c4d.gui.GeDialog

class XML3DExporterGUI(c4d.gui.GeDialog):

After the user filled in all necessary field and clicked the ‘Submit’-button, the system calls the method Command(). The system passes an id to the method to indicate the type of the fired user event. The following code fragment shows this process:

def Command(self, id, msg):
    if id == 10321:
        self.Close()
    elif id == 10311:
        self.export()
    return True

Module: XML3DCommandData

The plugin registration is done in the same way as described above. We pass an object of type XML3DCommandData to the method RegisterCommandPlugin. The method XML3DCommandData::Execute() will be
automatically called as soon as the user activates the plugin. The method then fires up the gui to capture the export properties.

class XML3DCommandData(c4d.plugins.CommandData):
    def Execute(self, doc):
        dialog = XML3DExporterGUI()
        return self.dialog.Open(False, pluginid=PLUGIN_ID_EXPORTER, defaulth=200, defaultw=340)

Storing the export properties

The input fields of the export dialog are always filled with the last entered data. This avoids breaking the work flow. Cinema4D therefore provides the static method SetWorldPluginData(). We can store any
property, together with its index in the container, in this container. Cinema4D automatically writes the content of this container to a predefined path when shutting down.

def storeSettings(self):
    if self.settings is None:
        self.settings = c4d.BaseContainer()
    self.settings.SetString(0, self.targetPath)
    self.settings.SetString(1, self.GetString(self.panelWidth))
    self.settings.SetString(2, self.GetString(self.panelHeight))
    self.settings.SetBool(3, self.GetBool(self.embedIntoXHTML))
    self.settings.SetLong(4, self.GetLong(self.exportStrategy))
    result = c4d.plugins.SetWorldPluginData(PLUGIN_ID_EXPORTER, self.settings, False)
    return result

Reading the export properties

The method GetWorldPluginData() composes the counterpart to SetWorldPluginData(). During launch, Cinema4D reads all previously stored container.

def readSettings(self):
    self.settings = c4d.plugins.GetWorldPluginData(PLUGIN_ID_EXPORTER)
        if self.settings is None:
            return False
        else:
            self.targetPath = self.settings.GetString(0)
            self.SetString(self.panelWidth, self.settings.GetString(1))
            self.SetString(self.panelHeight, self.settings.GetString(2))
            self.SetBool(self.embedIntoXHTML, self.settings.GetBool(3))
            self.SetLong(self.exportStrategy, self.settings.GetLong(4))
            return True

Invoking the exporter

The exporter expects an object of type BaseDocument. A BaseDocument contains all information about the scene. The active document (there can be many scenes opened at the same time) can be retrieved with the following line of code:

scene = c4d.documents.GetActiveDocument()

The most important methods of BaseDocument are:

  • c4d.documents.BaseDocument.GetFirstObject(): This method returns an object of type BaseObject, in fact the first one in the scene graph. The following code fragment shows how to recursively traverse the scene graph to find an object of type Oenvironment:
def findWorldAmbient(self, obj):
    while obj != None:
        if obj.GetType() == c4d.Oenvironment:
            objContainer = obj.GetDataInstance()
            ambColor = obj[c4d.ENVIRONMENT_AMBIENT]
            ambStrength = obj[c4d.ENVIRONMENT_AMBIENTSTRENGTH]
            self.ambientWorld = ambColor * ambStrength
            break
        else:
            self.findWorldAmbient(obj.GetDown())
            obj = obj.GetNext()
  • c4d.documents.BaseDocument.GetFirstMaterial(): Alongside the scene graph, Cinema4D also manages a material graph, which can be traversed in the same way as the scen graph.
  • c4d.documents.BaseDocument.Polygonize(): Polygonizing a scene means making all objects editable. Warning: Some objects will be removed from the scene graph.

Best Practices

  • Measuring code execution time:
start = c4d.GeGetMilliSeconds()
...
elapsed = c4d.GeGetMilliSeconds() - start
  • Visualize overall progress:
c4d.StatusSetText("Exporting scene graph...")
c4d.StatusSetBar(0)
  • Print type of BaseObject:
def getTypeAsString(self, obj):
    if obj.GetType() == c4d.Opolygon:
        return "Polygon"
    elif obj.GetType() == c4d.Olight:
        return "Light"
    elif obj.GetType() == c4d.Ocamera:
        return "Camera"
    elif obj.GetType() == c4d.Oinstance:
        return "Instance"
    elif obj.GetType() == c4d.Oenvironment:
        return "Environment"
    elif obj.GetType() == c4d.Onull:
        return "Null"
    else:
        return "Unknown"
  • Materials do not have an ambient term (for several reasons). It is possible to insert a global ambient term by adding an object of type Environment to the scene graph (see section Invoking the Exporter).
  • Mesh data is stored in multi-index format. A face contains three indexes to the vertices of that face and three (probably) different indexes to the normals of that face. If the exported format expects single-index meshes, one have transform the multi-index meshes to single-index meshes by hand. Cinema4D do not provide a mechanism for this.

This mechanism is implemented in XML3DExporter::writeDataObject().

  • Creating smooth vertex normals: Cinema4D provides an easy way to get smooth vertex normals:
normals = obj.CreatePhongNormals()

This method only returns a valid normal array if the object has an attached phong tag.

  • Unique object/material names: Cinema4D allows for non-unique object/material names. A possible solution to this might be renaming all objects/materials such that their names are unique.

Source Code Documentation

The code of the exporters is documented using the Python docstrings with the epytext markup. This markup allows for generating HTML cross-linked output similar to JavaDoc or Doxygen. You can of course always view the documentation in your Python console using the in-built help() functionality.

Latest HTML documentation can be found here:

To generate and view the HTML documentation yourself from the code, follow the HOWTO-DOC.txt file, or:

  1. Install Epydoc (http://epydoc.sourceforge.net/)
  2. Change directory to Cinema4D/R11.5/docs or Cinema4D/R12/docs
  3. python "PYTHON_INSTALL_FOLDER/Scripts/epydoc.py" --config epydoc.cfg
  4. Open html/index.html

Troubleshooting the documentation generation

  • If the epydoc installation cannot find your python installation, make sure you have matching binary version. Epydoc requires 32bit version of Python. On a 64bit system, you can have side-by-side installation of 64bit and 32bit Python.
  • If you have multiple python installations, make sure to call the correct “python”, e.g. the one where epydoc is installed.

 

If you have any further questions, please refer to our mailing list.