.. _dpac-plenary-cambridge-2025: .. role:: grey .. role:: red .. role:: green .. role:: yellow .. role:: orange .. role:: blue .. Scripting workshop (DPAC 2025) ============================== .. hint:: This tutorial is designed to be followed with Gaia Sky 3.6.6+! This page contains the notes of the Gaia Sky scripting workshop in the DPAC consortium meeting 2025 in Cambridge, UK (April 2025). The main aim of this tutorial is to provide a general understanding of the scripting system in Gaia Sky, and to train the participants in the creation of scripts with the main aim to render videos. **Presentation (slides):** Find the accompanying presentation for this tutorial `here <https://gaia.ari.uni-heidelberg.de/gaiasky/presentation/202504/>`__. The topics covered in this tutorial are the following: - Gaia Sky introduction (see `presentation <https://gaia.ari.uni-heidelberg.de/gaiasky/presentation/202504/>`__). - Dataset manager. - Controls, movement, selection. - User interface. - Video tools (frame output, camera paths, keyframes). - Scripting workshop: - :ref:`1. Installing the environment <2025-env>`. - :ref:`2. The full API <2025-api>`. - :ref:`3. Go to object calls <2025-goto>`. - :ref:`4. Orbit around focus <2025-orbit>`. - :ref:`5. Saving/restoring settings <2025-backup-restore>`. - :ref:`6. Start/stop time, time warp factor <2025-time>`. - :ref:`7. Time and camera transitions <2025-transitions>`. - :ref:`8. Component type visibility <2025-component-types>`. - :ref:`9. Record/play camera path files <2025-camera-paths>`. - :ref:`10. Use frame output system <2025-frame-output>`. - :ref:`11. Advanced topics: camera and scene parking runnables <2025-sync>`. *Estimated duration:* 2.5 hours Before starting... ------------------ Have a look at our :ref:`quick start guide <quick-start-guide>` to learn the basic usage of Gaia Sky. This guide covers the first few slides of the presentation and more. - :ref:`Quick start guide <quick-start-guide>`. Scripting workshop ------------------ This is the start of the hands-on session. Gaia Sky exposes an API that is accessible through Python (via Py4j) or through HTTP over a network (using the :ref:`REST API HTTP server <rest-server>`). .. _2025-env: 1. Installing the environment ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ In this tutorial, we focus on the writing of Python scripts that tap into the Gaia Sky API. You will need: - `Python 3 <https://python.org>`__ to run the scripts. - `Git <https://git-scm.com>`__ to get the code. - On Linux, Git is probably already installed. - On macOS, use ``brew install git``. - On Windows, use `Git for windows <https://git-scm.com/downloads/win>`__. - `Pipenv <https://pipenv.pypa.io>`__ to manage the environment. You can install ``pipenv``` with ``pip``, which already comes with Python: .. code:: bash pip install --user pipenv Now, get the `workshop project <https://codeberg.org/gaiasky/gaiasky-workshop2025>`__ from git: .. code:: bash git clone https://codeberg.org/gaiasky/gaiasky-workshop2025.git cd gaiasky-workshop2025 Now, you can enter the virtual environment and install the dependencies. .. code:: bash pipenv shell pipenv install This should install ``numpy`` and ``py4j``. If you don't have ``git`` installed, you can just download the ``gaiasky-script.py`` file and install the dependencies. .. code:: bash # Either one or the other pipenv install py4j numpy pipenv install -r requirements.txt Once this is ready, you can run a script with the Python 3 interpreter in your virtual environment. .. hint:: Before running a script, make sure that an instance of Gaia Sky is running in the same computer. Otherwise, the script will fail. In order to run the script ``gaiasky-script.py``, you would run this in a terminal: .. code:: bash python3 gaiasky-script.py .. _2025-api: 2. API ^^^^^^ `Here <https://gaia.ari.uni-heidelberg.de/gaiasky/docs/javadoc/latest/gaiasky/script/IScriptingInterface.html>`__ is the full documentation of the API. .. _2025-goto: 3. Our first script ^^^^^^^^^^^^^^^^^^^ You can either start with ``gaiasky-script-commented.py`` and uncomment the required lines, or you can start with an empty file and copy-paste the code. We'll assume you start with an empty file. We'll start simple. Copy paste the following code in a file named ``my-script.py`` within your ``workshop`` directory. .. code-block:: python :linenos: from py4j.clientserver import ClientServer, JavaParameters # Create the gateway to connect to the Gaia Sky instance. gateway = ClientServer(java_parameters=JavaParameters(auto_convert=True)) # gs is our entry point to call API methods. gs = gateway.entry_point # We just go to Mars. gs.goToObject("Mars") # Terminate the connection and exit. gateway.shutdown() This is a very simple script that moves the camera to Mars and stops. You can test it by running ``python my-script.py``. Remember that you need to open Gaia Sky first! .. _2025-orbit: 4. Orbiting around ^^^^^^^^^^^^^^^^^^ Now we want our camera to go to Mars, and to orbit around for a while. To achieve this, we can edit our ``my-script.py`` so that it looks like this: .. code-block:: python :linenos: :emphasize-lines: 11-21 from py4j.clientserver import ClientServer, JavaParameters # Create the gateway to connect to the Gaia Sky instance. gateway = ClientServer(java_parameters=JavaParameters(auto_convert=True)) # gs is our entry point to call API methods. gs = gateway.entry_point # We just go to Mars. gs.goToObject("Mars") # Make sure the camera is in cinematic mode so that it keeps its momentum. gs.setCinematicCamera(True) # Set the orbiting speed of the camera in focus mode (in [0,100]). gs.setCameraRotationSpeed(40) # Add a gentle push in X. First parameter is deltaX, second is deltaY. gs.cameraRotate(-0.4, 0.0) # Now we wait for 5 seconds, and stop the camera. gs.sleep(5) gs.cameraStop() # Terminate the connection and exit. gateway.shutdown() Run again and see the result. .. _2025-backup-restore: 5. Backing up and restoring settings ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ You may have noted that after the previous script finishes, the camera is left in cinematic mode, even if the script started with the camera in non-cinematic mode. This is because settings modified in scripts are not rolled back when the scripts end. To solve this, you can backup and restore the settings at the beginning and end of your scripts: - ``backupSettings()`` -- backs up the current settings in the settings stack [`link <https://gaia.ari.uni-heidelberg.de/gaiasky/docs/javadoc/latest/gaiasky/script/IScriptingInterface.html#backupSettings()>`__]. - ``restoreSettings()`` -- restores the most recent backup from the top of the settings stack [`link <https://gaia.ari.uni-heidelberg.de/gaiasky/docs/javadoc/latest/gaiasky/script/IScriptingInterface.html#restoreSettings()>`__]. To try it in our script, edit it like this: .. code-block:: python :linenos: :emphasize-lines: 8,9,26,27 from py4j.clientserver import ClientServer, JavaParameters # Create the gateway to connect to the Gaia Sky instance. gateway = ClientServer(java_parameters=JavaParameters(auto_convert=True)) # gs is our entry point to call API methods. gs = gateway.entry_point # Back up the current settings. gs.backupSettings() # We just go to Mars. gs.goToObject("Mars") # Make sure the camera is in cinematic mode so that it keeps its momentum. gs.setCinematicCamera(True) # Set the orbiting speed of the camera in focus mode (in [0,100]). gs.setCameraRotationSpeed(40) # Add a gentle push in X. First parameter is deltaX, second is deltaY. gs.cameraRotate(-0.4, 0.0) # Now we wait for 5 seconds, and stop the camera. gs.sleep(5) gs.cameraStop() # Restore settings before exit. gs.restoreSettings() # Terminate the connection and exit. gateway.shutdown() Run it, and you should see the camera back in non-cinematic mode at the end (if this is how it started!). An unfortunate side effect of ``restoreSettings()`` is that the camera is put in free mode after the call. .. _2025-time: 6. Playing with time ^^^^^^^^^^^^^^^^^^^^ In Gaia Sky, you can also start and stop time so that satellites, planets, and stars move. The most important API calls that manipulate time are: - ``startSimulationTime()`` -- [`link <https://gaia.ari.uni-heidelberg.de/gaiasky/docs/javadoc/latest/gaiasky/script/IScriptingInterface.html#startSimulationTime()>`__]. - ``stopSimulationTime()`` -- [`link <https://gaia.ari.uni-heidelberg.de/gaiasky/docs/javadoc/latest/gaiasky/script/IScriptingInterface.html#stopSimulationTime()>`__]. - ``setTimeWarp(double factor)`` -- [`link <https://gaia.ari.uni-heidelberg.de/gaiasky/docs/javadoc/latest/gaiasky/script/IScriptingInterface.html#setTimeWarp(double)>`__]. - **Set the current time** (`here <https://gaia.ari.uni-heidelberg.de/gaiasky/docs/javadoc/latest/gaiasky/script/IScriptingInterface.html#setSimulationTime(int,int,int,int,int,int,int)>`__ and `here <https://gaia.ari.uni-heidelberg.de/gaiasky/docs/javadoc/latest/gaiasky/script/IScriptingInterface.html#setSimulationTime(long)>`__), and **get the current time** (`here <https://gaia.ari.uni-heidelberg.de/gaiasky/docs/javadoc/latest/gaiasky/script/IScriptingInterface.html#getSimulationTimeArr()>`__ and `here <https://gaia.ari.uni-heidelberg.de/gaiasky/docs/javadoc/latest/gaiasky/script/IScriptingInterface.html#getSimulationTime()>`__). ``setTimeWarp(double)`` sets the pace at which time passes. Set it to 1 to use real world time speed. Increase it to make time run faster. ``start-`` and ``stopSimulationTime()`` start and stop the time respectively. Let's edit our script to have a look at the rotation of Mars. .. admonition:: Docs See the :ref:`time pane section <time-pane>` in the user manual. .. code-block:: python :linenos: :emphasize-lines: 26-32 from py4j.clientserver import ClientServer, JavaParameters # Create the gateway to connect to the Gaia Sky instance. gateway = ClientServer(java_parameters=JavaParameters(auto_convert=True)) # gs is our entry point to call API methods. gs = gateway.entry_point # Back up the current settings. gs.backupSettings() # We just go to Mars. gs.goToObject("Mars") # Make sure the camera is in cinematic mode so that it keeps its momentum. gs.setCinematicCamera(True) # Set the orbiting speed of the camera in focus mode (in [0,100]). gs.setCameraRotationSpeed(40) # Add a gentle push in X. First parameter is deltaX, second is deltaY. gs.cameraRotate(-0.4, 0.0) # Now we wait for 5 seconds, and stop the camera. gs.sleep(5) gs.cameraStop() # Time pace is 800x faster than normally. gs.setTimeWarp(800.0) # Start time. gs.startSimulationTime() gs.sleep(8) # Stop time. gs.stopSimulationTime() # Restore settings before exit. gs.restoreSettings() # Terminate the connection and exit. gateway.shutdown() .. _2025-different: Why always different? ^^^^^^^^^^^^^^^^^^^^^ You may have noted that the outcome is different every time you run the script. This is because we are not taking into account the initial state of Gaia Sky. In this case, the most important factors that play a role in how the script develops as it runs are: - Starting position and orientation of our camera. - Starting time. As we'll see later, those are not the only factors. But they are indeed the most important. Let's control them by setting an initial camera location and initial time. To achieve this, first we need to set a starting position (and orientation!) for our camera. The camera orientation is defined by two vectors, the **direction** vector (where the camera looks), and the **up** vector. To capture the position and orientation of the camera at any time, we can use these calls: - ``getCameraPosition(string units)`` -- returns the camera position vector in the given units [`here <https://gaia.ari.uni-heidelberg.de/gaiasky/docs/javadoc/latest/gaiasky/script/IScriptingInterface.html#getCameraPosition(java.lang.String)>`__]. If no parameters are passed, the position is given in Km. There is a test script `here <https://codeberg.org/gaiasky/gaiasky/src/branch/master/assets/scripts/tests/get-cam-state.py>`__ that prints out the camera position in various units and orientation. This script is reproduced below. You can just open a python REPL shell and paste the contents in. .. code-block:: python :linenos: # Test script. Tests getCameraPosition(units) with different units, # getCameraDirection() and getCameraUp(). # Created by Toni Sagrista import math from py4j.clientserver import ClientServer, JavaParameters gateway = ClientServer(java_parameters=JavaParameters(auto_convert=True)) gs = gateway.entry_point print("Camera position:") pos = gs.getCameraPosition("internal") print( " - Internal: [%.10f, %.10f, %.10f]" %(pos[0], pos[1], pos[2])) pos = gs.getCameraPosition("km") print( " - Km: [%.10f, %.10f, %.10f]" %(pos[0], pos[1], pos[2])) pos = gs.getCameraPosition("au") print( " - AU: [%.10f, %.10f, %.10f]" %(pos[0], pos[1], pos[2])) pos = gs.getCameraPosition("ly") print( " - Light years: [%.10f, %.10f, %.10f]" %(pos[0], pos[1], pos[2])) pos = gs.getCameraPosition("pc") print( " - Parsecs: [%.10f, %.10f, %.10f]" %(pos[0], pos[1], pos[2])) print() print("Camera orientation:") dir = gs.getCameraDirection() print( " - Direction: [%.10f, %.10f, %.10f]" %(dir[0], dir[1], dir[2])) up = gs.getCameraUp() print( " - Up: [%.10f, %.10f, %.10f]" %(up[0], up[1], up[2])) gateway.shutdown() - ``getCameraDirection()`` -- returns the direction unit vector of our current camera [`here <https://gaia.ari.uni-heidelberg.de/gaiasky/docs/javadoc/latest/gaiasky/script/IScriptingInterface.html#getCameraDirection()>`__]. - ``getCameraUp()`` -- returns the up vector of our current camera [`here <https://gaia.ari.uni-heidelberg.de/gaiasky/docs/javadoc/latest/gaiasky/script/IScriptingInterface.html#getCameraUp()>`__]. We can set the state with the analogous ``setCamera[Position|Direction|Up]()`` methods. For the initial time, we can just set it like this: - ``setSimulationTime(int year, int month, int day, int hour, int min, int sec, int millisec)`` -- [`here <https://gaia.ari.uni-heidelberg.de/gaiasky/docs/javadoc/latest/gaiasky/script/IScriptingInterface.html#setSimulationTime(int,int,int,int,int,int,int)>`__]. We use these methods to capture the following initial state: - Cam pos (km): [7765514.7261459418, 3367392.3812921559, -148604366.1028463840] - Cam direction: [0.7297110211, 0.3130747529, -0.6078700723] - Cam up: [-0.2821898299, 0.9476653108, 0.1493296977] - Time: 17 Mar 2025, 10:39:13 UTC Now, we can initialize the state of our script: .. code-block:: python :linenos: :emphasize-lines: 11-17 from py4j.clientserver import ClientServer, JavaParameters # Create the gateway to connect to the Gaia Sky instance. gateway = ClientServer(java_parameters=JavaParameters(auto_convert=True)) # gs is our entry point to call API methods. gs = gateway.entry_point # Back up the current settings. gs.backupSettings() # Initialize state with position and time. gs.setCameraFree() gs.setSimulationTime(2025, 3, 17, 10, 39, 13, 0) gs.setCameraPosition([7765514.7261459418, 3367392.3812921559, -148604366.1028463840]) gs.setCameraDirection([0.7297110211, 0.3130747529, -0.6078700723]) gs.setCameraUp([-0.2821898299, 0.9476653108, 0.1493296977]) gs.sleep(3) # We just go to Mars. gs.goToObject("Mars") # Make sure the camera is in cinematic mode so that it keeps its momentum. gs.setCinematicCamera(True) # Set the orbiting speed of the camera in focus mode (in [0,100]). gs.setCameraRotationSpeed(40) # Add a gentle push in X. First parameter is deltaX, second is deltaY. gs.cameraRotate(-0.4, 0.0) # Now we wait for 5 seconds, and stop the camera. gs.sleep(5) gs.cameraStop() # Time pace is 800x faster than normally. gs.setTimeWarp(800.0) # Start time. gs.startSimulationTime() gs.sleep(8) # Stop time. gs.stopSimulationTime() # Restore settings before exit. gs.restoreSettings() # Terminate the connection and exit. gateway.shutdown() Run the script multiple times, and it should now always start from the same position, orientation and time. There are other things that play a role in how a script runs. Some of the calls we are using (like ``goToObject()``) implement a lot of functionality in the background for us, and they use the current camera speed settings to move the camera around. It is possible to fully control the camera state frame-by-frame, but this requires some more advanced techniques that we'll see later on. However, we offer some **middle-ground** which enables setting-up smooth camera (position, orientation) and time transitions. .. _2025-transitions: 7. Creating transitions ^^^^^^^^^^^^^^^^^^^^^^^ The family of API calls that starts with ``cameraTransition(pos, dir, up, seconds)`` (`here <https://gaia.ari.uni-heidelberg.de/gaiasky/docs/javadoc/latest/gaiasky/script/IScriptingInterface.html#cameraTransition(double%5B%5D,double%5B%5D,double%5B%5D,double)>`__) enables the definition of smooth transitions that use different mapping functions (logistic sigmoid, logit) from the current state to a user-defined final camera position, camera orientation, or time. The full documentation for these transition methods is :ref:`here <transitions>`. If, after the end of the current script, we want to return to the Earth and do it with more granular control, we can use transitions. .. code-block:: python :linenos: :emphasize-lines: 42-55 from py4j.clientserver import ClientServer, JavaParameters # Create the gateway to connect to the Gaia Sky instance. gateway = ClientServer(java_parameters=JavaParameters(auto_convert=True)) # gs is our entry point to call API methods. gs = gateway.entry_point # Back up the current settings. gs.backupSettings() # Initialize state with position and time. gs.setCameraFree() gs.setSimulationTime(2025, 3, 17, 10, 39, 13, 0) gs.setCameraPosition([7765514.7261459418, 3367392.3812921559, -148604366.1028463840]) gs.setCameraDirection([0.7297110211, 0.3130747529, -0.6078700723]) gs.setCameraUp([-0.2821898299, 0.9476653108, 0.1493296977]) gs.sleep(3) # We just go to Mars. gs.goToObject("Mars") # Make sure the camera is in cinematic mode so that it keeps its momentum. gs.setCinematicCamera(True) # Set the orbiting speed of the camera in focus mode (in [0,100]). gs.setCameraRotationSpeed(40) # Add a gentle push in X. First parameter is deltaX, second is deltaY. gs.cameraRotate(-0.4, 0.0) # Now we wait for 5 seconds, and stop the camera. gs.sleep(5) gs.cameraStop() # Time pace is 800x faster than normally. gs.setTimeWarp(800.0) # Start time. gs.startSimulationTime() gs.sleep(8) # Stop time. gs.stopSimulationTime() # Back to Earth. We first need to set the camera free, # as we are using a transition. gs.setCameraFree() gs.cameraTransition([7.6099302829, 3.3000312627, -148.6345636443], # pos "internal", # units [-0.8321802084, -0.2822031396, 0.4765726385], # dir [-0.3583614028, 0.9301845136, -0.0749524287], # up 8.0, # duration (pos) "logisticsigmoid", # mapping function (pos) 30.0, # smoothing factor (pos) 4.0, # duration (orient) "logisticsigmoid", # mapping function (orient) 12.0, # smoothing factor (orient) True) # sync # Restore settings before exit. gs.restoreSettings() # Terminate the connection and exit. gateway.shutdown() Doing camera (and time!) motions like this is a bit more involved, but it typically results in much better looking scripts due to the smooth movement produced by the mapping functions. Keep in mind that you need to capture the final camera state with `get-cam-state.py <https://codeberg.org/gaiasky/gaiasky/src/branch/master/assets/scripts/tests/get-cam-state.py>`__. .. admonition:: Docs See the :ref:`transitions section <transitions>` in the user manual. .. _2025-component-types: 8. Component visibility ^^^^^^^^^^^^^^^^^^^^^^^ Objects in Gaia Sky are organized into types, which we call **component types**. These component types can be hidden and shown all at once. Examples of component types are **planets**, **moons**, **stars**, **grids**, or **labels** (see full list :ref:`here <type-visibility>`). While scripting, we can toggle types on and off with: - ``setComponentTypeVisibility(String key, boolean visible)`` -- enables or disables (``True`` or ``False``) the component type identified with the given ``key``. The key is in the form ``"element.planets"``, ``"element.stars"``, etc. See call `here <https://gaia.ari.uni-heidelberg.de/gaiasky/docs/javadoc/latest/gaiasky/script/IScriptingInterface.html#setComponentTypeVisibility(java.lang.String,boolean)>`__. Let's modify our script so that we mute the labels half-way through. .. code-block:: python :linenos: :emphasize-lines: 30,31 from py4j.clientserver import ClientServer, JavaParameters # Create the gateway to connect to the Gaia Sky instance. gateway = ClientServer(java_parameters=JavaParameters(auto_convert=True)) # gs is our entry point to call API methods. gs = gateway.entry_point # Back up the current settings. gs.backupSettings() # Initialize state with position and time. gs.setCameraFree() gs.setSimulationTime(2025, 3, 17, 10, 39, 13, 0) gs.setCameraPosition([7765514.7261459418, 3367392.3812921559, -148604366.1028463840]) gs.setCameraDirection([0.7297110211, 0.3130747529, -0.6078700723]) gs.setCameraUp([-0.2821898299, 0.9476653108, 0.1493296977]) gs.sleep(3) # We just go to Mars. gs.goToObject("Mars") # Make sure the camera is in cinematic mode so that it keeps its momentum. gs.setCinematicCamera(True) # Set the orbiting speed of the camera in focus mode (in [0,100]). gs.setCameraRotationSpeed(40) # Add a gentle push in X. First parameter is deltaX, second is deltaY. gs.cameraRotate(-0.4, 0.0) # Disable labels. gs.setComponentTypeVisibility("element.labels", False) # Now we wait for 5 seconds, and stop the camera. gs.sleep(5) gs.cameraStop() # Time pace is 800x faster than normally. gs.setTimeWarp(800.0) # Start time. gs.startSimulationTime() gs.sleep(8) # Stop time. gs.stopSimulationTime() # Back to Earth. We first need to set the camera free, # as we are using a transition. gs.setCameraFree() gs.cameraTransition([7.6099302829, 3.3000312627, -148.6345636443], # pos "internal", # units [-0.8321802084, -0.2822031396, 0.4765726385], # dir [-0.3583614028, 0.9301845136, -0.0749524287], # up 8.0, # duration (pos) "logisticsigmoid", # mapping function (pos) 30.0, # smoothing factor (pos) 4.0, # duration (orient) "logisticsigmoid", # mapping function (orient) 12.0, # smoothing factor (orient) True) # sync # Restore settings before exit. gs.restoreSettings() # Terminate the connection and exit. gateway.shutdown() As you see, now labels disappear when the camera starts to orbit Mars. Note that after the script ends, labels are re-enabled due to the call to ``gs.restoreSettings()``! .. hint:: You can enable a representation of star velocities as 3D vectors with ``"element.velocityvectors"``! The length, number, and color map of those vectors can be tuned with the family of calls ``setProperMotions[...]()`` (see `here <https://gaia.ari.uni-heidelberg.de/gaiasky/docs/javadoc/latest/search.html?q=setProperMotions>`_). .. admonition:: Docs See the :ref:`component types section <type-visibility>` in the user manual. .. _2025-camera-paths: 9. Camera paths ^^^^^^^^^^^^^^^ Gaia Sky includes a feature to record and play back camera paths. This comes in handy if we want to showcase a certain itinerary through a dataset, for example. .. _qs-record-playback: **Recording a camera path** -- The system will capture the camera state at every frame and save it into a ``.gsc`` (for Gaia Sky camera) file. We can start a recording by clicking on the |rec| icon in the camera pane of the control panel. Once the recording mode is active, the icon will turn red |recred|. Click on it again in order to stop recording and save the camera file to disk with an auto-generated file name (default location is ``$GS_DATA/camera`` (see the :ref:`folders <folders>` section in the Gaia Sky documentation). **Playing a camera path** -- In order to playback a previously recorded ``.gsc`` camera file, click on the |play| icon and select the desired camera path. The recording will start immediately. .. tip:: **Mind the FPS!** The camera recording system stores the position of the camera for every frame! It is important that recording and playback are done with the same (stable) frame rate. To set the target recording frame rate, edit the "Target FPS" field in the camcorder settings of the preferences window. That will make sure the camera path is using the right frame rate. In order to play back the camera file at the right frame rate, we can edit the "Maximum frame rate" input in the graphics settings of the preferences window. .. figure:: ../img/quickstart/202402_camera-paths.jpg :alt: Camrecorder :align: center :target: _images/202402_camera-paths.jpg Location of the controls of the camcorder in Gaia Sky UI. .. |rec| image:: ../img/ui/rec-icon-gray.png :class: filter-invert .. |recred| image:: ../img/ui/rec-icon-red.png .. |play| image:: ../img/ui/play-icon.png :class: filter-invert Of course, you can also instruct your scripts to start and stop recording a camera path. These are the relevant methods: - ``startRecordingCameraPath(String fileName)`` -- starts recording a camera path file that will be saved with the given name [`here <https://gaia.ari.uni-heidelberg.de/gaiasky/docs/javadoc/latest/gaiasky/script/IScriptingInterface.html#startRecordingCameraPath(java.lang.String)>`__]. - ``stopRecordingCameraPath()`` -- stops the current recording (if any) and saves it as a file [`here <https://gaia.ari.uni-heidelberg.de/gaiasky/docs/javadoc/latest/gaiasky/script/IScriptingInterface.html#stopRecordingCameraPath()>`__]. - ``playCameraPath(String fileName)`` -- plays the given camera path file (absolute path) [`here <https://gaia.ari.uni-heidelberg.de/gaiasky/docs/javadoc/latest/gaiasky/script/IScriptingInterface.html#runCameraPath(java.lang.String)>`__]. Camera paths are totally deterministic, as they save the full state (camera position and direction, time) at every frame. They have a specific frame rate. .. admonition:: Docs See the :ref:`camera paths section <camera-paths>` in the user manual. .. _2025-frame-output: 10. Frame output system ^^^^^^^^^^^^^^^^^^^^^^^ Here we learn about the frame output system to produce high-quality videos. In order to create high-quality videos, Gaia Sky offers the possibility to export every single still frame to an image file using the :ref:`frame output subsystem <frame-output>`. The resolution of these still frames can be set independently of the current screen resolution. We can start the frame output system by pressing :guilabel:`F6`. Once active, the system starts saving each still frame to disk (frame rate goes down, most probably). The save location of the still frame images is, by default, ``$GS_DATA/frames/[prefix]_[num].jpg``, where ``[prefix]`` is an arbitrary string that can be defined in the preferences. The save location, mode (simple or advanced), and the resolution can also be defined in the preferences. .. figure:: ../img/quickstart/2023_frame-output-prefs.jpg :alt: Frame output :align: center :target: _images/2023_frame-output-prefs.jpg The configuration screen for the frame output system Gaia Sky offers some calls to start, stop, and configure the frame output system: - ``setFrameOutput(boolean active)`` -- start and stop the frame output system [`here <https://gaia.ari.uni-heidelberg.de/gaiasky/docs/javadoc/latest/gaiasky/script/IScriptingInterface.html#setFrameOutput(boolean)>`__]. - ``configureFrameOutput(int width, int height, double fps, String directory, String namePrefix)`` -- configure the frame output system with the resolution of the frames, the frame rate, the output directory, and the prefix to use for the image files [`here <https://gaia.ari.uni-heidelberg.de/gaiasky/docs/javadoc/latest/gaiasky/script/IScriptingInterface.html#configureFrameOutput(int,int,double,java.lang.String,java.lang.String)>`__]. Once we have the still frame images, we can convert them to a video using ``ffmpeg`` or any other encoding software. You can convert the frames into a video with: .. code:: bash ffmpeg -framerate 30 -start_number [start_img_num] -i [prefix]%05d.jpg -vframes [num_images] -s 1280x720 -c:v libx264 -pix_fmt yuv420p -crf 23 -r 30 [out_video_filename].mp4 - The input and output framerate (``-framerate``, ``-r``) must match that defined in Gaia Sky. - The resolution (``-s``) must be the resolution of the still frames. - Use ``-crf`` to change the quality, in [0-51]. Lower numbers lead to better quality videos, with 0 being lossless. 23 is a good default. Additional information on how to convert the still frames to a video can be found in the :ref:`capturing videos section <capture-videos>` of the Gaia Sky user manual. Alternatively, we can use `OBS <https://obsproject.com>`__ to record the script directly. In this case, since we are capturing the window of our OS, we are limited by the resolution of our display. .. admonition:: Docs See the :ref:`frame output section <frame-output>` in the user manual. .. _2025-sync: 11. Advanced: syncing with main thread ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ If there's still time, here we learn how to park runnables that run in sync with the main update-render cycle in Gaia Sky. Let's suppose we want to count the number of frames during the execution of our script, and print the number at the end. To that effect, we need a piece of code that runs after every frame to increment a variable. We can achieve this by parking a runnable in the main thread. - ``parkRunnable(String name, Runnable object)`` -- parks a runnable whose ``run()`` method will be executed after every frame, until it is unparked [`here <https://gaia.ari.uni-heidelberg.de/gaiasky/docs/javadoc/latest/gaiasky/script/IScriptingInterface.html#parkRunnable(java.lang.String,java.lang.Runnable)>`__]. - ``removeRunnable(String name)`` -- unparks the runnable identified by the given name so that it does not run anymore [`here <https://gaia.ari.uni-heidelberg.de/gaiasky/docs/javadoc/latest/gaiasky/script/IScriptingInterface.html#removeRunnable(java.lang.String)>`__]. With these two methods, we just need to create the runnable object with the code we require. Let's edit our script with these lines: .. code-block:: python :linenos: :emphasize-lines: 11-23,74-76 from py4j.clientserver import ClientServer, JavaParameters # Create the gateway to connect to the Gaia Sky instance. gateway = ClientServer(java_parameters=JavaParameters(auto_convert=True)) # gs is our entry point to call API methods. gs = gateway.entry_point # Back up the current settings. gs.backupSettings() class FrameCounterRunnable(object): def __init__(self): self.nframes = 0 def run(self): self.nframes = self.nframes + 1 class Java: implements = ["java.lang.Runnable"] # Create runnable object and submit it. my_runnable = FrameCounterRunnable() gs.parkRunnable("framecounter", my_runnable) # Initialize state with position and time. gs.setCameraFree() gs.setSimulationTime(2025, 3, 17, 10, 39, 13, 0) gs.setCameraPosition([7765514.7261459418, 3367392.3812921559, -148604366.1028463840]) gs.setCameraDirection([0.7297110211, 0.3130747529, -0.6078700723]) gs.setCameraUp([-0.2821898299, 0.9476653108, 0.1493296977]) gs.sleep(3) # We just go to Mars. gs.goToObject("Mars") # Make sure the camera is in cinematic mode so that it keeps its momentum. gs.setCinematicCamera(True) # Set the orbiting speed of the camera in focus mode (in [0,100]). gs.setCameraRotationSpeed(40) # Add a gentle push in X. First parameter is deltaX, second is deltaY. gs.cameraRotate(-0.4, 0.0) # Disable labels. gs.setComponentTypeVisibility("element.labels", False) # Now we wait for 5 seconds, and stop the camera. gs.sleep(5) gs.cameraStop() # Time pace is 800x faster than normally. gs.setTimeWarp(800.0) # Start time. gs.startSimulationTime() gs.sleep(8) # Stop time. gs.stopSimulationTime() # Back to Earth. We first need to set the camera free, # as we are using a transition. gs.setCameraFree() gs.cameraTransition([7.6099302829, 3.3000312627, -148.6345636443], # pos "internal", # units [-0.8321802084, -0.2822031396, 0.4765726385], # dir [-0.3583614028, 0.9301845136, -0.0749524287], # up 8.0, # duration (pos) "logisticsigmoid", # mapping function (pos) 30.0, # smoothing factor (pos) 4.0, # duration (orient) "logisticsigmoid", # mapping function (orient) 12.0, # smoothing factor (orient) True) # sync # Remove runnable object and print frames. gs.removeRunnable("framecounter") print(f"Number of frames: {my_runnable.nframes}") # Restore settings before exit. gs.restoreSettings() # Terminate the connection and exit. gateway.shutdown() Of course, this is a very simple example only. Much more complex operations can be performed with this technique, like adding or updating objects, manipulating the camera, etc. There are more examples in the `showcase scripts directory <https://codeberg.org/gaiasky/gaiasky/src/branch/master/assets/scripts/showcases>`__ of our repository. For instance: - ``camera-constant-turn.py`` -- creates a constant camera turn using a parked runnable. - ``earth-nea-s.py`` -- demonstrates the motions of NEAs relative to the Earth-Moon system. - ``earth-venus-dance.py`` -- creates and updates lines between the Earth and Venus at fixed time intervals. - ``epycicles.py`` -- demonstrates the apparent retrograde motion of Marse from the geocentric reference system. - ``lunar-libration.py`` -- demonstrates the libration of the Moon, viewed from the Earth's surface. There are many more examples in the sowcases directory. .. admonition:: Docs See the :ref:`synchronizing with the main loop section <sync-runnables>` in the user manual.