Py4D Imports: How-To

When using and distributing third-party modules in a Cinema 4D Python plugin, many problems can arise when used incorrectly. Some users have hundreds of Cinema 4D plugins, and many of them use third-party modules. Some plugins will stop working when another plugin delivers the same third-party module in a different version or even one with the same name but completely different functionality!

When you import modules from your Python Plugin’s directory, you should never do it the naive way, unless you EXPLICITLY WANT these third-party modules to be accessible from the outside (eg. when exposing a Python API for your plugin).

import os, sys
sys.path.append(os.path.join(os.path.dirname(__file__), 'lib'))
import some_module  # The module will live on in sys.modules after the plugin finished loading

Warning

Using the naive approach shown above is dangerous and can lead to incompatibilties between plugins.

Using the Node.Py Runtime

It is generally recommended to make use of the Node.Py runtime. This can be achieved by generating a stand-alone version using the c4ddev build-loader command.

$ c4ddev build-loader -cmo loader.pyp -e myentrypoint

The generated loader.pyp contains the Node.Py runtime and its dependencies and will load myentrypoint.py when the plugin is loaded. This file will then have full access to the Node.Py runtime and can make use of the nodepy-pm package manager.

$ nodepy-pm install py/numpy
$ tree
| loader.pyp
| myentrypoint.py
| utils.py
| nodepy_modules/
\-| .pip/
  \-| Lib/
    \-| site-packages/
      \-| numpy/ ...
        | numpy-1.9.2.dist-info/ ...
$ cat myentrypoint
# myentrypoint.py
import numpy  # loaded from nodepy_modules/.pip/Lib/site-packages
utils = require('./utils')

# Register your C4D Python plugins ...

The Node.Py runtime will manage the full isolation of the module environment (using localimport) and imported Python modules will not be visible to other plugins.

Note

C4DDev currently provides no mechanism to automatically compile Python sources and package them as it does with the c4ddev pypkg command. There is an outstanding task to bring this feature to C4DDev: NiklasRosenstein/c4ddev#8

Using localimport

Using the localimport module allows you to import Python modules in an isolated environment and will ensure that other plugins will not see the modules that another plugin has imported.

with localimport(['lib']) as _importer:
  # This line would not be necessary if everyone would use localimport,
  # but since we can not garuantee that...
  _importer.disable(['some_module'])
  import some_module
  assert 'some_module' in sys.modules
assert 'some_module' not in sys.modules

But how do you use localimport when it is a module, too?

The answer is a minified version that you can copy&paste directly into your plugins source code. Below is a minified version of localimport-v1.5. Other (and eventually newer) versions are available here.

# localimport-v1.7.3-blob-mcw99
import base64 as b, types as t, zlib as z; m=t.ModuleType('localimport');
m.__file__ = __file__; blob=b'\
eJydWUuP20YSvutXEMiBpIfmeOLDAkJo7GaRAMEGORiLPUQrEBTVkumhSKK75Uhj5L+nHv2iSNpyfBiTXY+uqq76qpoqy+qsP\
/SyLIv4t+a5rVT0vleiU1o0XfSDdM8dEf95PFVNm9f96V28KstPQqqm71D4Kf9H/jZeNaehlzqq++Fqn49tv7PPvbJPw/PxrJ\
vWvqqro2hZ1WJX1c924aUZDk0rVs0B2XK7adMd+s2bbVF8v15Fe3GIGi1OKrmk8BpJoc+yiy45L6aOQy5xScspWiWWNbaN0ol\
Te4de0klMqmz7umoTdKarTiIbKv0B9aGMXSx6leN6Xu0U/u+4YatDLyNcK/E9gvOxCnBPR5hocBRQETVkiDrvRsozz4O6rAP/\
lWexsi8/VxAY64lVgH9AWIqOvNDyyv63SHCWmPcR9yoSl1oMOvpf1Z7FT1L2MggdbRa5va1C1Fif5b6REcSi67Wl5EpXUqs/G\
tiFdkUejrv4VLXlEDqr4FiAnO2F0sVvfScyzjRFL+gHRAmJ4GmES2gYMWP+4XbEgdtbDxuF2v1heVdWERoV9YPovAWxjFMotc\
OAfHisTbcXl6xtOjpX0Z1PQlYaFA58ILAdEkM3YzY6ZgY6WPYitBr+iYuo0f+Syd4I2vPhiXZNidekPqljXXk1gOH7ZEGKxLw\
U0Qoy9ADPSfxdnDrjkPbuzRqpxLJZ09KWGNwqeCibIXFi4yBDSie0sbGSxCz5Y990iX2B80Vz/YkEbo6kul6eKDk93QQ7qro9\
P6ARcCyYAmZjfMybTgkI6Bur2iQr0jjzliKP/F2fWU/Invj/XfwqYcrrp/RhHAxTWKgxAfQdMNmQI/MphbQ49XX1Y6XET/QIa\
InCDljzQTadLoHPQJO4aDjkkmsUStSmMNIAfUuT3S+OEOFDLtm8+JFO2XhvseklxyeCS6AOI2Sik3pFOtTQNjqJc7L8hbhAH3\
NMGZqu0eVwLeKypMcyfgCdYL4Sw0M8XGPHUi/y1J6pX2TqgenUc0gKcgLiEkAwemjBYM2watoUZGlpHgnvOFXN+cEJHo+F5fy\
9GX62bAQJxFHt97RrEkQepDIKzkP8aC3Owd0UzPk6W30nXx9zQQMuhehNZ2GgG/682FZCXhtrqVZIzBaLjZ4pGPtqAYV4GT4o\
RxMblB+r/e/8mNmlXyt5FCZYpvKHSqloFWDPksXOWLDV4wigAx8Omr1stTuKG5if7mMSKsVA38tcfxN3n6azQf+GmJuQc6FuJ\
gB4STG7L6Gi7apuMdI0uBgU63cfRU3dHqx6+1zMzGTvirdARXTojqW+DkIVCbxlKdhOQnRuyQ4QipkyM0jZZEyUaA9ZMC6UcG\
Lcqvd9CemrCpxN8AXq0j3DLNvvsUu0gtZSU5oYHq+HonOQCDVoe3kUmt6SpzQ/lDiuwvBhUgbwAY8F8AHDQmw2AZ1Zty1nMsG\
h1MZr2tJBoofEV2y2di6DhqKrrjaIQByjKKY+1Td8PNH8UGhnhmn3vBn0FqIDaF41MID52SyJYdKqdPNJcMbtzhoEAzmDXtMx\
1GSy5QtGzdUsv8vHMaOLV5jNZVjeJjPYAc/OzS3Bc83xz7TESm6gr3IQj1N/Oiehq9IfEa/1+3ML+fz5T7ticpD/s4tNV9Z9p\
2Hvgudmzxwm6fjVZYUbGZRLjmCrNYdDdIUSmielSRI49zkaSD90SLgnDLAHhMEOggcjiTuu0ammw1tBZIzIAYySQ5eaYdMN25\
0/aB60nUlu2r511oEApIqQBgVSHl24ffrLYymF6s+yFlSpHSB6rQu8duZ7IQZ8SEZcOVkCBVkLONL6uToKRTbvBUCcFJ5cjOU\
mdMraL7OwZ+WcqBnOfiFH3K3HOoAIN2+UoZBiAAktis8xC8Vr/j+LJ1LxerKUgRQegorXn//MYnyM13aS2ay3WeyyntfdKxFN\
plppvsTnwfwYr2cWMyoWv4nPBbMeblKMa+9hRF9F0Yz+Ing2kPgsrhnUKiYuX8LD6vUzmY/nxvu23YD0lpqDEciHfkhgMRhYo\
v+IK58fziJUkp6fFcDLytaenfmVPmlfoD7316u5q9pILA2C+FCEllPgt4uee7vcZZIYwmviIMWhuRQgnEsAa93grYHGbujntl\
N8qFSltQw15tA9ExZOM+hxVPSlvZRCIreTuPCdMVAHxKlo6J9NWXMwVOZU4iCZW0FGoHClmEmVkUjGL1gcLH+L3fwBJMTfAK7\
Xri0Fi0lwFUKag7SLn2tewWbBZHKzKX+Aofb7/gxoe7IN2NBJhhBS7Knp0nBGHpl2sXRJwQ3DcXGaQhz6QOHN6DhWPeoxN7oD\
HXcpxQq39rpqd9lKROWiRYMvLc544vFr60acCe94i9t+bw3EBTTQNv0w7yn/0tmaM98CRzUHXNh5+sHNA/6TH5RQWAdmTMzoY\
1QwyFl+8h52dA6BVbtz00JjLnlPhvtwUOXCdnfp7Cksa2Yxcz+abIIyZyBVMQtsZ40NPyJ5p00h0TRhFyNI6pFP0y+kQdKkIS\
6MYHYBp8Pl87DHr2nzaP/FQ1wQcQ3EDLYUJoyx/1yxef39NmgXv+DHLtswvIzt+O4YSheO8N1WRng+5mRDeA1EtiZafHJMyG4\
tfNqix2EAbHHPR8ABcdBBb9A9QF/uxkv9cjIP3Daz+cFgWuULM8FI58ygsr1jrrxrzrPZMZm+tlMVM1NoXreikjzHf515JpPN\
GEh5PDNe2nAvXEuoQzttpl1NfLEXcrLC3x+/4n8yEmAgvclXT9+uvrV732hHy6FE6/6TkP7qYHqxVYZ5bVDSpLbpQkaaejg5y\
0xhow4u6ExcvKJveFww6sYfVkCOEsP+PBCp86404xeTH6A4g65DV81lgJqZ7oCxMLoilgt/OPD7GUi9xTHYnm+FN3CxBrwwGH\
8XpkWn6TT8t5DuLqjz31gpqb8Me/a6yn78C3ib3Vn7n6F4Uyqc+/r70qD7pQsGRQTzLpwfXeLivm1f7YXM+IcXBTnsBhiX6Kk\
fQ60Krofvon9LAfvuo901Gq6npmsOjZBR8kHrQa0fH4+QDOcd/pj7CNO47g+HR8+WrlZ/AaI7XVw='
exec(z.decompress(b.b64decode(blob)), vars(m)); _localimport=m;localimport=getattr(m,"localimport")
del blob, b, t, z, m;

Using localimport + pypkg

If you’re using c4ddev pypkg to package additional Python modules, it is common to have a devel/ directory with all the dependencies. In development mode, you can make localimport load the dependencies from that directory, and otherwise load it from the Python Egg generated with c4ddev pypkg.

if _debug:
  _importer = localimport(['devel'])
else:
  _importer = localimport('res/lib-{0}.egg'.format(sys.version[:3].replace('.', '-')))

with _importer:
    import res
    import requests
    import nr.concurrency
    from nr.c4d.utils import load_bitmap

Note that in most cases, the devel/ directory will not contain just the source code of the modules that you want to use, but the complete repository (eg. when you’re using Git submodules, which I highly recommend).

devel/
  requests/
    requests/
    README.md
    setup.py
    ...

In order to have the correct sys.path setup when loading modules from the devel/ directory, you can place a .pth file into that directory.

devel/
  devel.pth       <<
  requests/
    requests/
    README.md
    setup.py
  ...

This file can list an additional include directory per line. So for the above example, in order to be able to import the requests module in development mode, we simply add the following line to the devel.pth file.

requests/requests

Note

You don’t need this file in release mode when your third-party modules are packaged with c4ddev pypkg.