Modifying Cinema 4D Preferences from External Python Script?
-
I'm curious whether it would be possible to create a system whereby an external python script could modify the preferences of a running instance of Cinema 4D.
My first thought was to generate some code in the external script and pass it to Cinema 4D for execution, but that seems (A) potentially unsafe and (B) more complex than it needs to be.
My second thought was to set a system environment variable from the external script and read it from Cinema 4D. In my case it could be something simple like setting EXTERNAL_STATE = 1 under certain circumstances. This would require Cinema 4D to watch for changes to the environment variable and I'm not sure that's possible.
The third approach might be for the external script to create a temporary file to represent one state and then remove it in another state. Alternatively it could write data into that file for Cinema 4D to read. This would still require some way for Cinema 4D to watch for changes.
I suppose I'm curious if there are any known approaches to this sort of thing or which of my 3 potential approaches might be most reasonable. In the 2nd and 3rd cases I'm also curious whether anyone knows of an elegant/efficient way to watch for a change to an environment variable or file.
Thinking on it a bit more I suppose the most elegant solution would be to send messages directly to Cinema 4D that it could catch and handle in a plug-in. Is that possible?
Thanks!
-
There are many solutions, with drawbacks each. The simplest method would be to create a plugin that polls for change information... create a dialog with a timer, or (not really recommended) a parallel thread with a busy loop that Sleep()s between polls. On timer/poll time, that dialog/thread will read info from the file system about changes, e.g. a file where you list all changes in a format that your external script and your internal dialog agree upon.
C4D will not automagically "know" what has changed so you will need to define your own file format to exchange that information. The dialog will perform the changes internally. Note that some things, like the font, cannot be changed without a restart.
This is more or less what you're probably thinking about in your third solution.Your method 1 doesn't work on its own because C4D does not simply execute a script from the outside. You would still need an internal running process that accepts that script and knows when to execute it (you can execute text as a script in Python). Which essentially makes it identical with option 3 but unsafe.
Your method 2 is the same as method 3 - watch for changes from the inside - except that you do not have any information to pass; you only look at the environment variable and know "there's a change" but you don't know what is changed, or supposed to be changed.
Ultimately setting up a watch process in C4D is the way to go, and have your own format to exchange data between an external something and the internal script is always better than passing active code (especially if you are working on an environment with external access of any kind).
-- If you'd be working on C++, you could open your own invisible window in the background where you take control of the message loop, and send messages through the OS without the detour of the file system. Then this window would produce internal C4D messages to evaluate. This would allow you to directly pass information from program to program. Python does not allow you to do that as the C4D API does not have methods to take control of windows on this deep level.
-
Hello @wuzelwazel,
Thank you for reaching out to us and please excuse the short delay.
The simplest approach to do what you want to do would be to simply place a machine-readable settings file, e.g., a JSON or XML file somewhere, and the write a plugin that reads in that file on the startup of Cinema 4D and applies its values to the preferences of Cinema 4D or whatever else you want to change.
You could also make this a bit more dynamic by using instead a file-system monitor, e.g., watchdog to continuously watch the state of a file to react to updates to it (or write your own if you do not like 3rd party libs). On a file update you would then carry out what solution one did carry on plugin start.
A third option would be to use the message symbol
MSG_BODYPAINTEXCHANGE
. It can be used in conjunction with a shell/brach script which uses the following syntax."Cinema 4D.exe" PLUGIN_MESSAGE_{PLUGIN_ID}:{MESSAGE}
e.g. for the code example shown below:
"Cinema 4D.exe" PLUGIN_MESSAGE_1058759:HELLO_WORLD
When you run this shell/batch script the first time it will launch Cinema 4D and send the message to the plugin. For all subsequent invocations, the message will be sent to the already opened Cinema 4D instance. The caveat here is that in Python you can receive the message but not the message data. So, when you run the shell/batch script, your plugin will receive the message, but the message data will be
None
. But that should be enough to force the plugin to read in a JSON file.Find below a simple implementation with a
CommandData
plugin.Cheers,
FerdinandThe result:
The code:
import c4d class SomeCommandData(c4d.plugins.CommandData): """Listens for the command line message. """ ID_PLUGIN = 1058759 def Execute(self, doc): """Does not do anything. """ raise NotImplementedError("I do not do anything.") def Message(self, cid, data): """Listens for the MSG_BODYPAINTEXCHANGE message. """ if cid == c4d.MSG_BODYPAINTEXCHANGE: print ("MSG_BODYPAINTEXCHANGE: Do stuff.") return True return False if __name__ == '__main__': myPlugin = SomeCommandData() c4d.plugins.RegisterCommandPlugin( id=SomeCommandData.ID_PLUGIN, str="PC13727 (External Commands)", info=c4d.PLUGINFLAG_SMALLNODE, icon=None, help="PC13727", dat=myPlugin)
-
Hello @wuzelwazel,
without any further questions or postings, we will consider this thread as solved by Friday the 4th, February 2022.
Thank you for your understanding,
Ferdinand