Maxon Developers Maxon Developers
    • Documentation
      • Cinema 4D Python API
      • Cinema 4D C++ API
      • Cineware API
      • ZBrush GoZ API
      • Code Examples on Github
    • Forum
    • Downloads
    • Support
      • Support Procedures
      • Registered Developer Program
      • Plugin IDs
      • Contact Us
    • Unread
    • Recent
    • Tags
    • Users
    • Login

    Extending the Command Line with Python

    Cinema 4D SDK
    python r21
    5
    10
    2.2k
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • J
      jwzegelaar
      last edited by Manuel

      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 problem

      So the questions is: How can I run a custom python script before the -render command?

      Thanks you!:)

      1 Reply Last reply Reply Quote 0
      • ferdinandF
        ferdinand
        last edited by ferdinand

        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 of runpy. 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()
        

        MAXON SDK Specialist
        developers.maxon.net

        1 Reply Last reply Reply Quote 2
        • P
          PluginStudent
          last edited by

          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.

          See Command Line Arguments.

          ferdinandF 1 Reply Last reply Reply Quote 1
          • ferdinandF
            ferdinand @PluginStudent
            last edited by

            @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

            MAXON SDK Specialist
            developers.maxon.net

            1 Reply Last reply Reply Quote 1
            • J
              jwzegelaar
              last edited by

              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😬

              M 1 Reply Last reply Reply Quote 0
              • J
                jwzegelaar
                last edited by

                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. 😇

                1 Reply Last reply Reply Quote 0
                • M
                  m_adam @jwzegelaar
                  last edited by m_adam

                  @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.

                  MAXON SDK Specialist

                  Development Blog, MAXON Registered Developer

                  1 Reply Last reply Reply Quote 1
                  • J
                    jwzegelaar
                    last edited by

                    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!

                    1 Reply Last reply Reply Quote 0
                    • J
                      jwzegelaar
                      last edited by

                      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!

                      moghurtM 1 Reply Last reply Reply Quote 1
                      • moghurtM
                        moghurt @jwzegelaar
                        last edited by

                        @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.

                        1 Reply Last reply Reply Quote 0
                        • ferdinandF ferdinand referenced this topic on
                        • First post
                          Last post