py_msc_preference

Demonstrates how to implement a preference category in the Cinema 4D preference dialog, exposing parameters as global preferences.

DETAILS

The preference plugin exposes values globally through the world container so other parts of Cinema 4D can access them. Filename parameters are intentionally omitted because they are unsupported.

To run this example, place the file in the Cinema 4D plugins folder and restart Cinema 4D.

Topics:

  • Registering a custom preference category
  • Sharing preference values through the world container
  • Handling unsupported filename preference parameters

Files

py_msc_preference.pyp

"""Demonstrates how to implement a preference category in the Cinema 4D preference dialog, exposing 
parameters as global preferences.

The preference plugin exposes values globally through the world container so other parts of Cinema 
4D can access them. Filename parameters are intentionally omitted because they are unsupported.

To run this example, place the file in the Cinema 4D plugins folder and restart Cinema 4D.

Topics:
    - Registering a custom preference category
    - Sharing preference values through the world container
    - Handling unsupported filename preference parameters
"""
__copyright__ = "Copyright 2026, MAXON Computer"
__author__ = "Maxime Adam"
__date__ = "25/08/2026"
__license__ = "Apache-2.0 license"
__version__ = "19.0.0"

import c4d

# Unique plugin ID obtained from developers.maxon.net
PLUGIN_ID = 1039699

# Unique plugin ID for world preference container obtained from developers.maxon.net
WPREF_PYPREFERENCE = 1039700

# ID for the World Preference Container parameter

WPREF_PYPREFERENCE_CHECK = 1000
WPREF_PYPREFERENCE_NUMBER = 1001


# The first time Cinema 4D will compile this script into Python Bytecode, symbols will not be yet parsed.
# This will cause PYPREFERENCE_CHECK and PYPREFERENCE_NUMBER not defined in the c4d module.
# Thats why we manually do it (numbers are defined in res/description/pypreference.h)
if not hasattr(c4d, "PYPREFERENCE_CHECK"):
    c4d.PYPREFERENCE_CHECK = 1000

if not hasattr(c4d, "PYPREFERENCE_NUMBER"):
    c4d.PYPREFERENCE_NUMBER = 1001


class PreferenceHelper(object):

    @staticmethod
    def GetPreferenceContainer():
        """Helper method to retrieve or create the WPREF_PYPREFERENCE container instance stored in the world container.

        Returns:
            c4d.BaseContainer: The container instance stored in the world container.

        Raises:
            RuntimeError: The BaseContainer can't be retrieved.
            MemoryError: The BaseContainer can't be created.
        """
        # Retrieves the world container instance
        world = c4d.GetWorldContainerInstance()
        if world is None:
            raise RuntimeError("Failed to retrieve the world container instance.")

        # Retrieves the container of our plugin, stored in the world container instance
        # Parameter values will be stored in this container.
        bc = world.GetContainerInstance(WPREF_PYPREFERENCE)

        # If there is no container, creates one
        if bc is None:
            # Defines an empty container
            world.SetContainer(WPREF_PYPREFERENCE, c4d.BaseContainer())

            # Retrieves this empty container instance
            bc = world.GetContainerInstance(WPREF_PYPREFERENCE)
            if bc is None:
                raise MemoryError("Failed to create a BaseContainer.")

        return bc

    def InitValues(self, descId, description=None):
        """Helper method to define type and default value of parameter

        Args:
            descId (c4d.DescID): The parameter ID describing the type and the ID of the parameter you want to initialize.
            description (c4d.Description, optional): The description of the PreferenceData. Defaults to None.

        Returns:
            True if success otherwise False.
        """
        # Retrieves the world BaseContainer of this preference, where values have to be defined
        bc = self.GetPreferenceContainer()

        # Defines default values
        paramId = descId[0].id
        if paramId == c4d.PYPREFERENCE_CHECK:
            self.InitPreferenceValue(WPREF_PYPREFERENCE_CHECK, True, description, descId, bc)
        elif paramId == c4d.PYPREFERENCE_NUMBER:
            self.InitPreferenceValue(WPREF_PYPREFERENCE_NUMBER, 10, description, descId, bc)

        return True


class Preference(c4d.plugins.PreferenceData, PreferenceHelper):

    def Init(self, node, isCloneInit=False):
        """Called by Cinema 4D on the initialization of the PreferenceData, the place to define the type of object.

        Args:
            node (c4d.GeListNode): The instance of the PreferenceData.
            isCloneInit (bool): True if the preference data is a copy of another one.

        Returns:
            True if the initialization success, otherwise False will not create the object.
        """

        # Init default values
        self.InitValues(c4d.DescID(c4d.DescLevel(c4d.PYPREFERENCE_CHECK, c4d.DTYPE_BOOL, 0)))
        self.InitValues(c4d.DescID(c4d.DescLevel(c4d.PYPREFERENCE_NUMBER, c4d.DTYPE_LONG, 0)))

        return True

    def GetDDescription(self, node, description, flags):
        """Called by Cinema 4D when the description (UI) is queried.

        Args:
            node (c4d.GeListNode): The instance of the PreferenceData.
            description (c4d.Description): The description to modify.
            flags (int): The flags for the description operation.

        Returns:
            Union[Bool, tuple(bool, Any, DESCFLAGS_DESC)]: The success status or the data to be returned.
        """
        # Loads description for this objects
        if not description.LoadDescription("pypreference"):
            return False

        # If default values are asked (right click on the description), defines the default values
        if flags & c4d.DESCFLAGS_DESC_NEEDDEFAULTVALUE:
            self.InitValues(c4d.DescID(c4d.DescLevel(
                c4d.PYPREFERENCE_CHECK, c4d.DTYPE_BOOL, 0)), description)
            self.InitValues(c4d.DescID(c4d.DescLevel(
                c4d.PYPREFERENCE_NUMBER, c4d.DTYPE_LONG, 0)), description)

        return True, flags | c4d.DESCFLAGS_DESC_LOADED

    def SetDParameter(self, node, id, data, flags):
        """Called by Cinema 4D, when SetParameter is call from the node.

        The main purpose is to store the data in the world container.

        Args:
            node (c4d.GeListNode): The instance of the PreferenceData.
            id (c4d.DescID): The parameter Id.
            data (Any): the data, the user defines and we have to store.
            flags (DESCFLAGS_SET): The input flags passed to define the operation.

        Returns:
            Union[Bool, tuple(bool, Any, DESCFLAGS_SET)]: The success status or the data to be returned.
        """
        # Retrieves the world BaseContainer of this preference, where values have to be defined
        bc = self.GetPreferenceContainer()

        # Retrieves the parameter ID changed
        paramID = id[0].id

        # Store the values in the World Container
        if paramID == c4d.PYPREFERENCE_CHECK:
            bc.SetBool(WPREF_PYPREFERENCE_CHECK, data)
            return True, flags | c4d.DESCFLAGS_SET_PARAM_SET
        elif paramID == c4d.PYPREFERENCE_NUMBER:
            bc.SetInt32(WPREF_PYPREFERENCE_NUMBER, data)
            return True, flags | c4d.DESCFLAGS_SET_PARAM_SET

        return False

    def GetDParameter(self, node, id, flags):
        """Called by Cinema 4D, when GetParameter is call from the node.

        The main purpose is to return the data from the world container.

        Args:
            node (c4d.GeListNode): The instance of the PreferenceData.
            id (c4d.DescID): The parameter Id.
            flags (DESCFLAGS_GET): The input flags passed to define the operation.

        Returns:
            Union[Bool, tuple(bool, Any, DESCFLAGS_GET)]: The success status or the data to be returned.
        """
        # Retrieves the world BaseContainer of this preference, where values have to be retrieved
        bc = self.GetPreferenceContainer()

        # Retrieves the parameter ID asked
        paramID = id[0].id

        # Returns the values from the World Container
        if paramID == c4d.PYPREFERENCE_CHECK:
            return True, bc.GetBool(WPREF_PYPREFERENCE_CHECK), flags | c4d.DESCFLAGS_GET_PARAM_GET
        elif paramID == c4d.PYPREFERENCE_NUMBER:
            return True, bc.GetInt32(WPREF_PYPREFERENCE_NUMBER), flags | c4d.DESCFLAGS_GET_PARAM_GET

        return False

    def GetDEnabling(self, node, id, t_data, flags, itemdesc):
        """Called  by Cinema 4D to decide which parameters should be enabled or disabled (ghosted).

        Args:
            node (c4d.GeListNode): The instance of the PreferenceData.
            id (c4d.DescID): The Description ID of the parameter.
            t_data (Any.): The current data for the parameter.
            flags: Not used
            itemdesc (c4d.BaseContainer): The description, encoded to a container.

        Returns:
            True if the parameter should be enabled, otherwise False.
        """
        # Retrieves the parameter ID asked
        paramID = id[0].id

        # Check box is always enable
        if paramID == c4d.PYPREFERENCE_CHECK:
            return True

        # Number is enable only if the checkbox is enable
        elif paramID == c4d.PYPREFERENCE_NUMBER:
            # Retrieves the world BaseContainer of this preference, where values have
            # to be retrieved
            bc = self.GetPreferenceContainer()

            # Returns the state value of the checkbox
            return bc.GetBool(WPREF_PYPREFERENCE_CHECK)

        return False


if __name__ == '__main__':
    c4d.plugins.RegisterPreferencePlugin(id=PLUGIN_ID,
                                         g=Preference,
                                         name="Py SDK - Preference",
                                         description="pypreference",
                                         parentid=0,
                                         sortid=0)

res/c4d_symbols.h

enum
{
	// End of symbol definition
	_DUMMY_ELEMENT_
};

res/description/pypreference.h

#ifndef PYPREFERENCE_H__
#define PYPREFERENCE_H__

enum
{
	PYPREFERENCE_MAIN_GROUP					= 999,
	PYPREFERENCE_CHECK							= 1000,
	PYPREFERENCE_NUMBER							= 1001,

	PYPREFERENCE_DUMMY
};

#endif // PYPREFERENCE_H__

res/description/pypreference.res

CONTAINER pypreference
{
	NAME pypreference;

	GROUP PYPREFERENCE_MAIN_GROUP
	{
		DEFAULT 1;
		COLUMNS 1;

		BOOL PYPREFERENCE_CHECK			{ ANIM OFF; }
		LONG PYPREFERENCE_NUMBER		{ MIN 1; MAX 1000; }
	}
}

res/strings_us/description/pypreference.str

STRINGTABLE pypreference
{
	pypreference "Py-Preference";

	PYPREFERENCE_CHECK					"Check";
	PYPREFERENCE_NUMBER					"Number";
}