Extending the Command Line with Python
-
Hi All!
I'm trying to run a python script before running a command line render. For example, change the subdivisions of a scene before you render the project.
The problem is the order of operations. The example below doesn't work.
The custom script is called -customsubs and works with the C4DPL_COMMANDLINEARGS function.
"C:\Program Files\MAXON\Cinema 4D R21\Commandline.exe" -render%userprofile%\Desktop\test.c4d -frame 0 0 -oimage %userprofile%\Desktop\test -omultipass %userprofile%\Desktop\test_mp -oformat TIFF -customsubs
I found a user with a similar problem but he is using C++ and this method of priorities is not working with python as far as I know.
Link to user with same problemSo the questions is: How can I run a custom python script before the -render command?
Thanks you!:)
-
Hi,
I think you cannot invoke a Python script with the Cinema 4D CL app. But you can run a Python script with c4dpy from the command line. To run your script manager scripts with c4dpy you would have either to make sure that they do not rely on that special script manager environment (not a very elegant solution) or alternatively emulate that environment. Since you want the changes of the script to be persistent, you would have also save the document after that. I had a go at that fun problem and came up with this tiny wrapper that does its best to emulate the script manager environment [1]. The usage in your scenario would be then:
c4dpy script_manager_environment.py -script my_script.py -in my_doc.c4d -out my_doc_scripted.c4d CommandLine -my_doc_scripted.c4d ...
Where
script_manager_environment.py
is a file with the code from [1]. I did some simple tests and it seemed to work in these tests.Cheers,
zipit[1] Note that it is very unlikely that the environment that the wrapper does create is identical to the one Cinema 4D does create for its script manager (see the module docstring for details on what the wrapper does). A safer but lengthier version of such wrapper would use
importlib
instead ofrunpy
. Due to that, your computer might explode and other terrible things could happen. You have been warned The code forscript_manager_environment.py
can be found here."""A command line wrapper for Cinema 4D Script Manager Python scripts. The wrapper makes scripts that have been written for the Cinema4D script manager, i.e. rely on the presence of predefined attributes like ``doc`` or ``op``, executeable in a ``c4dpy`` environment. The wrapper takes a script path and a Cinema 4D document path as its CL arguments, then executes the script on the document and finally saves the modified document. The argument syntax of the script is: ``[-h] [-out OUT] -in IN -script SCRIPT`` Where ``SCRIPT`` is the path to the script to run and ``IN`` is the document to run the script on and ``OUT`` is an optional output path. If ``OUT`` is not present, the modified document will be written to ``IN``. The wrapper will set the active document of the running ``c4dpy`` instance to an instance of the document specified by ``IN`` and also reveal the following objects in the dictionary of the executing module: doc (``c4d.documents.BaseDocument``): The document specified by ``IN``. op (``c4d.BaseObject`` or ``None``): The active object in the document. tp (``c4d.modules.thinkingparticles.TP_MasterSystem``): The particle master system of the document. The script does accept both absolute and relative paths for its path inputs. For an overview of the command line arguments of the script, run the script with the ``-h`` flag. """ import argparse import c4d import os import runpy def get_paths(): """Parses the command line arguments of the script and ensures that the file paths do resolve and converts them to absolute paths. Returns: ``tuple[str, str, str]``: The absolute script path, document path and the document output path. Prints: IOError: If the argument values for the ``script`` or ``in`` path is not an existing file path. """ parser = argparse.ArgumentParser( description=("Wraps a Python script that has been written for the " "Cinema 4D main app, so that it can be executed on a " "document from the c4dpy command line.")) arguments = { "-script": { "help": "The path to the python script to execute.", "required": True, "type": str }, "-in": { "help": "The path to the document to execute the script on.", "required": True, "type": str }, "-out": { "default": None, "help": ("An optional path to write the state of the " "document to, after the scripted has been executed " ". If not provided, the '-in' path will be used " "instead."), "required": False, "type": str }, } for name, kwargs in arguments.items(): parser.add_argument(name, **kwargs) paths = parser.parse_args().__dict__ # Validate the paths for name, path in paths.items(): path = os.path.abspath(path) if path is not None else None paths[name] = path if name not in ("script", "in"): continue elif not os.path.exists(path) or not os.path.isfile(path): msg = "IOError: Non-existing or non-file path received: {}" print msg.format(path) return None if paths["out"] is None: paths["out"] = paths["in"] return paths["script"], paths["in"], paths["out"], def run_script(script_path, in_path, out_path): """Runs a Python script on a Cinema document and saves the modified document. Args: script_path (``str``): The absolute file path of the script. in_path (``str``): The absolute file path of the input document. out_path (``str``): The absolute file path of the output document. Prints: IOError: When Cinema cannot load the file at ``in_path``. """ # Load the document and build the globals. doc = c4d.documents.LoadDocument( name=in_path, loadflags=c4d.SCENEFILTER_OBJECTS | c4d.SCENEFILTER_MATERIALS, thread=None) if doc is None: msg = "IOError: Could not load Cinema 4D document at: {}." print msg.format(in_path) return c4d.documents.SetActiveDocument(doc) op = doc.GetActiveObject() tp = doc.GetParticleSystem() # Execute the script data = runpy.run_path( script_path, init_globals={"doc": doc, "op": op, "tp": tp}, run_name="__main__") # Save the modified state. result = c4d.documents.SaveDocument(doc=doc, name=out_path, saveflags=c4d.SAVEDOCUMENTFLAGS_NONE, format=c4d.FORMAT_C4DEXPORT) if not result: msg = "IOError: Could not write to file path: {}." print msg.format(file_path) return msg = "Executed '{}' on '{}' and saved the result to '{}'." print msg.format(os.path.split(script_path)[1], os.path.split(in_path)[1], out_path) def main(): """Entry point. """ paths = get_paths() if paths: run_script(*paths) if __name__ == "__main__": main()
-
Do you want to execute a Python Script or just some Python code?
You can write a Python plugin, that reacts to command line arguments (and thus executes your code). This is done by implementing
PluginMessage()
. But I don't know if that allows executing code before the command line renderer is executed. -
@PluginStudent said in Extending the Command Line with Python:
Do you want to execute a Python Script or just some Python code?
Python code is always a Python script But given the fact that he said:
For example, change the subdivisions of a scene before you render the project.
It seems very likely that he wants to execute a Python script that was intended for Cinema's Script Manager environment.
You can write a Python plugin, that reacts to command line arguments (and thus executes your code). This is done by implementing
PluginMessage()
. But I don't know if that allows executing code before the command line renderer is executed.Hm, I might be wrong about this, but I would have assumed that the command line app does not pipe through its arguments to the plugin messages, since it is a different app. It would also solve not much IMHO, as you would have in a plugin the same problem of faithfully recreating the Script Manager environment.
Cheers,
zipit -
Thanks Zipit and PluginStudent,
@PluginStudent I already use the Pluginmessage function, see example below.
def PluginMessage(id, data): if id == c4d.C4DPL_COMMANDLINEARGS: argstring = ' '.join(sys.argv) if "- customsubs" in argstring: CommandRenderChangesomething(sys.argv) return True
But than you have the problem that is not running before the -render command just like you said. It all about the order of operations I think. Cinema needs to wait until the script is done before starting the render.
@zipit Very interesting and something to look at. I already have another workaround. I can set a RenderDocument function in the script. Meaning I will do everything from the script. Making the adjustments and also the rendering. Skipping the whole -render.
But I like to do it with the method above for many reason to long to explain:D Something like this must be possible right? Or maybe not
-
https://developers.maxon.net/forum/topic/9303/12381_command-line-args-before-render-solved/3
This is exactly what I also need, only just for Python.
-
@jwzegelaar said in Extending the Command Line with Python:
But I like to do it with the method above for many reasons to long to explain:D Something like this must be possible right? Or maybe not
No this is not possible, you will have to use workarounds (various exists, the one from @zipit seems for us totally valid).
But we need to know your requirements to suggest you the best one because we can't find a good reason why the method described by @zipit is not correct for you.
If its too confidential feel free to contact us on
[email protected]
Cheers,
Maxime. -
Hi Maxime,
Thank you for the response, we are trying out a few others methods now. There is not much confidential, it just comes down to our current queue manager software and workflow methods here, and with that this option from Zipit is not ideal.
Thank you very much for all the support guys, I will update this post when we know more.
Stay safe!
-
We found a good workaround! We now made a custom commandline plugin where we can add our own arguments. And it works great. If anybody in the future has this issue you can always contact us and we will help.
Thanks again everybody for all the help!
-
@jwzegelaar said in Extending the Command Line with Python:
We found a good workaround! We now made a custom commandline plugin where we can add our own arguments. And it works great. If anybody in the future has this issue you can always contact us and we will help.
Thanks again everybody for all the help!
Hi jwzegelaar,
I believe that I share the same needs as you, where I also need to pass in external parameters via a .py file to make modifications to the file before rendering. Unfortunately, I am encountering the same issue as you.
Although this post has been quite some time, I am still hopeful for your assistance. Your help would be greatly appreciated and it is highly important to me. -