Skip to content

edgency/c4dtools

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

98 Commits
 
 
 
 
 
 
 
 

Repository files navigation

c4dtools - Py4D Programming Utilities

c4dtools is a lightweight library providing convenient classes and functions that play nice with the Cinema 4D Python API. The most important tools are the automated resource parser and self-contained library importer.

The c4dtools library is throughly documented and fullfills most of the rules defined in PEP8. The library is fast as well. The resource-symbol loading will be cached by default (resulting in a file called c4d_symbols.cache, encoded in JSON format) to speed-up loading of the symbols. If the original c4d_symbols.h file is changed, the cache is rebuilt.

For Cinema 4D User

The c4dtools is not a plugin. It is "add on" code that can be used by devlopers to make their life easier. A plugin that relies on this library requires you to install c4dtools before it can be used (the plugin will not even register to Cinema4D).

Installation

The c4dtools library is casual Python package. For the Python interpreter embedded into Cinema 4D to find it, you need to install it into the preferences folder of your Cinema 4D installation.

  1. Download and unpack the c4dtools library from github.
  2. Goto the Cinema 4D preferences and press the "Open Preferences Folder..." button in the bottom left.
  3. From the folder that has opened, navigat to library > python > packages. The next folder you select depends on your OS.
  4. Copy the c4dtools, the one containing all the *.py files, directly into the folder specific to your OS.

To install a new version of the library, simply delete the old c4dtools folder and paste the new one.

Alternative Installation

Add a directory to the PYTHONPATH environment variable where you put in the c4dtools library folder. Again, the library folder is only the one containing all the *.py files.

Example Code

import c4d
import c4dtools
import c4dtools.resource.menuparser as menuparser

res, importer = c4dtools.prepare()

# Import libraries from the `lib` folder relative to the plugins
# directory, 100% self-contained and independent from `sys.path`.
with importer.protected():
   import mylib

# Ensure that `mylibb` is not in the cached modules. Just for
# demonstrational purpose, does not need to be done in a plugin.
assert 'mylib' not in sys.modules

# An arbitrary function from the imported module.
mylib.do_stuff()

class MyDialog(c4d.gui.GeDialog):

    def CreateLayout(self):
        # Access symbols from the `res/c4d_symbols.h` file via
        # the global `res` variable returned by `c4dtools.prepare()`.
        success = self.LoadDialogResource(res.DLG_MYDIALOG)

        # Parse and evaluate MENU files since version 1.2.0. A MENU file might
        # look like this:
        #
        # MENU MENU_FILE {
        #     MENU_FILE_ITEM1;
        #     MENU_FILE_ITEM2;
        #     ---------------;
        #     COMMAND 5159; # Cube object command
        #     COMMAND SOME_OTHER_RESOURCE_SYMBOL;
        #     ---------------;
        #     MENU MENU_FILE_SUB {
        #         # ...
        #     }
        # }
        #
        # IDs and strings are taken from c4d_symbols.h/c4d_strings.str respectively.
        if success:
           menuparser.parse_and_prepare(res.file('menus', 'main.mnu'), self, res)

        return success

    def InitValues(self):
        # Load strings from the `res/strings_xx/c4d_strings.str` file
        # via `res.string`.
        string = res.string.EDT_STRING1("Peter")
        self.SetString(res.EDT_STRING1, string)

        # New in version 1.1.0, but does not allow string-argument substitution.
        # (Replacing the `#` which is default for Cinema resource files, see
        # `c4d.plugins.GeLoadString()`)
        self.SetString(*res.string.EDT_STRING2.both)

        # New shortcut for acessing resource strings in 1.3.0:
        arg1 = 'Argument 1 Text'
        arg2 = 'and Argument 2 Text'
        c4d.gui.MessageDialog(res['IDS_MESSAGE_INITIALIZED', arg1, arg2])

        return True

class MyCommand(c4dtools.plugins.Command):

    PLUGIN_ID = 100008 # !! Must be obtained from the plugincafe !!
    PLUGIN_NAME = res.string.IDC_MYCOMMAND()
    PLUGIN_HELP = res.string.IDC_MYCOMMAND_HELP()
    PLUGIN_ICON = res.file('icons', 'command.png')

    def Execute(self, doc):
        dlg = MyDialog()
        return dlg.Open(c4d.DLG_TYPE_MODAL)

# Invoke the registration of `MyCommand` via `c4dtools.plugins.main()`
# on the main-run of the python-plugin.
if __name__ == '__main__':
    c4dtools.plugins.main()

    # Alternative:
    MyCommand().register()

Important notice for developers

When you as a developer are relying on the fact that the user has installed the c4dtools library on his local machine (as described above), you're fine since he is responsible for updating the library. You should however give the user a notice that your plugin relies on a certain version of the c4dtools library and that the user must update his local copy if his version is older than the version your plugin requires to run.

It's a bit more tricky to deliver the library with a plugin. You as a developer have to ensure that the library imported from your plugin's local folder does not reside in sys.modules !!

Why?

The version you are delivering with your plugin might be older than the version another plugin requires. If your plugin is loaded before the other, it is importing a version of the library it can not run with.

How to fix?

It's just a six-liner actually, the middle-part is just for demonstrational purpose.

# Store the old module configuration.
old_modules = sys.modules.copy()

# Import your stuff, for example:
lib_path = os.path.join(os.path.dirname(__file__), 'lib')
sys.path.insert(0, lib_path)
try:
    import c4dtools
finally:
    sys.path.pop(0)

# Restore the previous module configuration making sure to not
# remove modules not loaded from the local libraries folder.
for k, v in sys.modules.items():
    if k not in old_modules and hasattr(v, '__file__') or not v:
        if not v or v.__file__.startswith(lib_path):
            sys.modules.pop(k)
    else:
        sys.modules[k] = v

res, _ = c4dtools.prepare(__file__, __res__)

Note: We're omitting the second value returned by c4dtools.prepare() since we do not need an Importer (we're doing it manually in the snippet above).

License

The c4dtools library is licensed under the Simplified BSD License since version 1.1.0. It was licensed under the GNU General Public License before.

About

Utilities for the Cinema 4D Python API

Resources

Stars

Watchers

Forks

Packages