Further Reading
In the last article I showed how the Cinema 4D Team Render Flask server works and how we could add our own routes. Instead of editing the open source server directly one can also write plugins that will add functionality to the server. Plugins can also react to various messages sent by the server.
Simple Example
To add a route to the server we can simply create an ordinary Python plugin. In this plugin’s PluginMessage() function we wait for the C4DPL_WEBSERVER_START message. When we catch this message we can register our new routes:
import c4d MY_ROUTE = '/pluginHelloWorld' def SimpleFunction(): return "Hello World Plugin!" def Register(app, route, function, **options): app.add_url_rule(route, options.pop('endpoint', None), function, **options) def PluginMessage(id, data): if id == c4d.C4DPL_WEBSERVER_START: # getting the webserver object app = c4d.modules.net.webserver.app # getting the "register" function register = c4d.modules.net.webserver.main["register"] Register(app, register(MY_ROUTE), SimpleFunction) return True
The route’s address is defined in a global constant. This constant is used in the utility function “Register” that allows us to easily add a new route. To access the original “register” function and other components of the webserver we can access the c4d.modules.net.webserver object. This object allows us to get useful functions and objects. The functionality is implemented in the “SimpleFunction” which now just returns a string.
Resources
For meaningful functions we have to access different parts of the Team Render system. The global NetRenderService can easily be obtained with:
service = c4d.modules.net.GetGlobalNetRenderService()
To handle the actual requests to the server we have to use the flask module. This can be accessed via:
flask = c4d.modules.net.webserver.flask print (flask.session)
The current jobs of the render server are obtained with:
webjobs = c4d.modules.net.webserver.jobs
If we want to get the server itself this can be done with:
server = c4d.modules.net.webserver.app
Using Decorators
In the last article we saw how decorators can be used to add standard functionality to our route function. For example we can check if the user calling this route is logged in or if this user has admin privileges.
The decorator function calls are not available on start up. But we can use a little trick to use them. Inside our function we can define another sub-function. This sub-function will only be defined and called when the host function is called. At this time, the decorators are available.
This simple example returns a JSON object with all jobs of the calling user. This is only available for logged in users.
MY_JOBS_ROUTE = '/getMyJobs' def GetMyJobs(): # get the "RequireLogin" function RequireLogin = c4d.modules.net.webserver.main["RequireLogin"] @RequireLogin def GetMyJobsSub(): jsonify = c4d.modules.net.webserver.main["jsonify"] flask = c4d.modules.net.webserver.flask jobs = c4d.modules.net.webserver.jobs #get the jobs of the current user foundjobs = jobs.GetJobsOrJob(flask.session[u'useruuid'],0,0,0,0) # prepare result result = {} result['jobs'] = foundjobs # add userid for convenience result['useruuid'] = flask.session[u'useruuid'] return jsonify(result) return GetMyJobsSub()
This example shows how to check if the given user-id corresponds to a user account with admin privileges. This route is only accessible for admins.
ADMIN_ROUTE = '/checkAdmin/<userid>' def CheckAdmin(userid): RequireLogin = c4d.modules.net.webserver.main["RequireLogin"] RequiredAdmin = c4d.modules.net.webserver.main["RequiredAdmin"] @RequireLogin @RequiredAdmin def CheckAdminSub(userid): userUuid = uuid.UUID(userid) service = c4d.modules.net.GetGlobalNetRenderService() userPool = service.GetUserPool() #get users users = userPool.GetUsers() # check if given user is admin for user in users: if user['uuid'] == userUuid: if user['isadmin'] == True: return "true" return "false" return CheckAdminSub(userid)
We must pay attention to the fact that the user-id is stored as an object of the type UUID.
Messages
The Team Render server will send different messages that we can catch in our PluginMessage() function. Messages related to render jobs will contain the job uuid in the message data. The IDs are defined in the lib_net.h header file.
- C4DPL_JOBCREATED_PRE: message send directly before job is added
- C4DPL_JOBCREATED_POST: message send after a job was added
- C4DPL_JOBSTARTED_PRE: message send before rendering starts
- C4DPL_JOBSTARTED_POST: message send after rendering started
- C4DPL_JOBFINISHED_PRE: message send directly before job is finished
- C4DPL_JOBFINISHED_POST: message send after job is finished
Some messages are related to the state of the webserver. In this case the message data is None.
- C4DPL_WEBSERVER_START: webserver started
- C4DPL_WEBSERVER_STOP: webserver stopped
This final example shows how to react to such a message. Simply cast the data into an UUID object and use that ID to get more information on the specific render job:
if id == c4d.C4DPL_JOBFINISHED_POST: jobUuid = c4d.Cast(c4d.modules.uuid.UUID, data) service = c4d.modules.net.GetGlobalNetRenderService() jobs = service.GetJobsList(triggerWatchDog=False, rdata=True, assets=False, results=True, log=True, selectedJob=jobUuid, selectedJobOnly=True) if jobs: job = jobs[0] print ("end of job '%s'" % job[c4d.JOB_NAME])