Introduction
Maxon has released Release 20 of Cinema 4D and it comes with a bunch of new features (amongst them plugin breaking API changes, which is a bit of a bummer, but it needed to be done…). One of the lesser talked about features is the ability to set up multiple Plugin Search Paths. Previously you had you AppData plugin path and the system-wide plugin path in the Cinema 4D Rxx/plugins folder. The latter does not exist any more by default. Instead you are told to use your AppData plugin folder instead. This works fine if you are the only person working on your Workstation. It’s a pain if you are not.

Luckily you can still create your own Cinema 4D R20/plugins if you are so inclined. However, the big thing here (at least for me) is: the Search Paths also support UNC paths! Which means you can set up a shared network folder and have all your Cinema 4D R20 installations point to this folder and magically manage all your plugins through a single folder.
Licensing plugins
Using a shared folder works great for plugins that do not require a license to begin with or those that use the licensing system provided by Cinema itself (via its serial number). These licenses are stored in your workstation’s registry and are thus independent from the plugin folder location.
A problem occurs when you are dealing with plugins that require license files. Unfortunately those you will still have to install in your local plugin folder (e.g. the AppData one). But since you can combine multiple plugin folder paths, this is not a big issue.
Multi-User Environments
Cinema store the plugin paths in a file called plugins.json, located in the AppData folders. This means it stores the setting per-User. Which also means it’s tricky to centrally manage the setting for all users in a domain. You can of course pre-create a plugins.json file and just copy it over the existing (or non-existing-yet) one per user-login. But this will remove any custom setting that user may have made. Instead I decided to write a small Python script, since this is plain JSON and easy to parse. If you’re interested, I’ve put the script on Github.
import json import codecs import os _DEBUG = False # _DEBUG = True # C4D can use UNC paths, but the next time they are changed within the app, they are stored as path + authoritypath # instead, so for each UNC path there are two possible ways they may be stored as _PLUGINPATHUNC = "//SERVER/FOLDER/R20/plugins" _PLUGINPATH = "FOLDER/R20/plugins" _PLUGINAUTHORITYPATH = "SERVER" # A JSON block that contains a path definition _NEWPATH = { "_0": { "referenceIndex": 0, "referenceDataType": "net.maxon.interface.url-C", "_scheme": "file", "_path": _PLUGINPATH, "_authority": { "referenceDataType": "net.maxon.interface.url-C", "_scheme": "authority", "_path": _PLUGINAUTHORITYPATH, "_authority": {}, "_data": {} }, "_data": {} }, "_1": True } # The contents of a brand new definition file with out path already in it (by default the plugins.json file does # not exist until a path is manually added in the application) _NEWFILE = { "identification": "plugins", "content": { "referenceDataType": "net.maxon.interface.datadictionary-C", "_impl": { "_mode": 2, "_data": [ { "dataType": "net.maxon.datatype.id", "content": "searchPaths" }, { "dataType": "(net.maxon.interface.url-C,bool)", "isArray": True, "content": [ { "_0": { "referenceIndex": 0, "referenceDataType": "net.maxon.interface.url-C", "_scheme": "file", "_path": _PLUGINPATH, "_authority": { "referenceDataType": "net.maxon.interface.url-C", "_scheme": "authority", "_path": _PLUGINAUTHORITYPATH, "_authority": {}, "_data": {} }, "_data": {} }, "_1": True } ] } ] } } } def debug(msg): if _DEBUG: print(msg) def hasPluginPath(content, pathToCheckFor): if "_path" in content: if content["_path"] == pathToCheckFor: return True return False userAppData = os.getenv('APPDATA') debug(userAppData) maxon = os.path.join(userAppData, "MAXON") debug(maxon) if (os.path.exists(maxon)): # only look into R20 user folders userDirsR20 = [x for x in os.listdir(maxon) if str(x).lower().startswith("cinema 4d r20")] for userDir in userDirsR20: pluginsJsonPath = os.path.join(maxon, userDir, "plugins.json") debug(pluginsJsonPath) if os.path.exists(pluginsJsonPath): # the plugins.json file is stored as "UTF-8 with BOM", so regular open() will not open it correctly and json.load will fail with codecs.open(pluginsJsonPath, "r+", "utf-8-sig") as f: j = json.load(f) found_PLUGINPATH = False for data in j['content']['_impl']['_data']: if data['dataType'] == "(net.maxon.interface.url-C,bool)": for idx, content in enumerate(data['content']): debug(content) if hasPluginPath(content["_0"], _PLUGINPATH) or hasPluginPath(content["_0"], _PLUGINPATHUNC): found_PLUGINPATH = True data['content'][idx] = _NEWPATH break if not found_PLUGINPATH: found_PLUGINPATH = True data['content'].append(_NEWPATH) # write the changes back into the file if found_PLUGINPATH: f.seek(0) if _DEBUG: print(json.dumps(j, indent=4, sort_keys=False)) else: f.writelines(json.dumps(j, indent=4, sort_keys=False)) f.truncate() else: with codecs.open(pluginsJsonPath, "w+", "utf-8-sig") as f: f.writelines(json.dumps(_NEWFILE, indent=4, sort_keys=False)) else: exit