1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425
|
\chapter{Scripting}
\label{ch:scripting}
\sectionauthor*{A. Wolf, G. Zotti, with additions by Wolfgang Laun}
\section{Introduction}
\label{sec:scripting:introduction}
The scripting facility is Stellarium's version of a
\emph{Presentation}, a feature that may be used to run an astronomical
or other show for instruction or entertainment from within the
Stellarium program. The use of scripts was recognized as a perfect way
of arranging a presentation of a sequence of astronomical events
from the earliest versions of Stellarium.
The original \emph{Stratoscript} was quite limited in what it could do,
and so a new Stellarium Scripting System has been developed.
Since version 0.10.1, Stellarium has included a scripting feature based on
the Qt5 Scripting
Engine\footnote{\url{https://doc.qt.io/qt-5/qtscript-index.html}}. This
made it possible to write small programs within Stellarium to produce
automatic presentations, set up custom configurations, and to automate
repetitive tasks.
By version 0.14.0 the new scripting engine had reached a level where
it had all required features for usage, and support of scripts for
the old \emph{Stratoscript} engine has been discontinued.
The programming language
\indexterm{ECMAScript}\footnote{\url{https://en.wikipedia.org/wiki/ECMAScript}}
(also known as \indexterm{JavaScript}) gives users access to all basic ECMAScript
language features such as flow control, variables, string manipulation
and so on. Its integration with Qt's QtScript module and the way Stellarium's main
components (``StelModule''s) have been designed to work means that all module
functions which are labeled as ``slots'' can be called from JavaScript.
Interaction with Stellarium-specific features is done via a collection
of objects which represent components of Stellarium itself. The
various modules of Stellarium, and also activated plugins, can be
called in scripts to calculate, move the scene, switch on and off
display of objects, etc. You can write text output into text files
with the \command{output()} command. You can call all public slots
which are documented in the scripting API documentation\footnote{
\url{https://www.stellarium.org/doc/\StelSeries/scripting.html}}.
With the adaptation to Qt6 in versions 1.0 and later we had to switch to
yet another scripting engine in 2022. Qt6 comes with
QJSEngine\footnote{\url{https://doc.qt.io/qt-6/qtjavascript.html}}, another
JavaScript engine which behaves mostly similar to the older QtScript.
However, small differences exist, so that care must be taken if scripts
should be developed for both series of Stellarium. Some older scripts
will not work on versions 1.0 and later or at least require some adaptations
described in section~\ref{sec:scripting:differences}.
% Section 2020-03-26 by Wolfgang Laun
\section{The Script Console}
\label{sec:scripting:console}
It is possible to load, edit, run and save scripts using the script
console window. To toggle the script console, press \key{F12}.
\subsection{The Tabs in the Console}
The three tabs ``Script'', ``Log'' and ``Output'' each provide a text
field. In the ``Script'' text field you can edit a script,
which can be loaded from and saved to a file, and executed. The ``Log''
text field receives all errors and other messages resulting from the
script's execution; the ``Output'' text field will show the results of
\texttt{core.output()} function calls.
The fourth tab, ``Settings'', provides some configuration options.
\subsection{The Menu Bar}
The menu bar contains buttons for loading and saving a script. A file
dialog is shown for selecting the file. The ``clear'' button lets you
clear the contents of the text field in the currently selected tab.
The drop-down menu ``Execute:'' offers a selection of menu entries, mainly
intended to let you restore the sky to a clean state. The entry
``selected text as script'' executes selected text in the ``Script'' text
field, allowing to test scripts during development.
Entries ``remove screen text'', ``remove screen images'' and
``remove screen markers'' remove labels, images and markers, respectively,
that may have been left over on the screen from previous script actions.
Each of the ``clear map'' entries
results in a call to \texttt{core.clear()}, using one of the arguments
``natural'', ``starchart'', ``deepspace'', ``galactic'' or ``supergalactic''.
These calls reset Stellarium's state, resulting in a basic state
of the display and providing a clean starting point for another run of
your script.
The button SSC runs the Stellarium Script Preprocessor with the script in the text
field as input and produces an output by recursively inserting all
included scripts into the text.
\emph{This output replaces the contents of the Script text field. Store the original script if you need it!}
\subsection{German Keyboards}
There is a glitch in the keyboard module of the underlying system that
is responsible for keyboard handling. It affects users of Windows and
Linux systems with a German, and probably also other international
keyboards: The key combinations \key{AltGr+8} and \key{AltGr+9} cannot
be used for typing \verb+[+ and \verb+]+. You need to work around by
copy-pasting these two characters. You can also try to use a command
line argument like \texttt{-platform windows:altgr}.
\section{Includes}
\label{sec:scripting:includes}
Stellarium provides a mechanism for splitting scripts into different
files. Typical functions or lists of variables or celestial objects can be stored in
separate \file{.inc} files and used within other scripts through the
\textbf{include()} command:
\begin{script}
include("common_objects.inc");
\end{script}
\section{Minimal Scripts}
\label{sec:scripting:MinimalScript}
This script prints ``Hello Universe'' in the Script Console log window and into \file{log.txt}:
\begin{script}
core.debug("Hello Universe");
\end{script}
\noindent This script prints ``Hello Universe'' in the Script Console output window and into the file \file{output.txt}
which you will also find in the user data directory. The absolute path to the file will also be given in \file{log.txt}.
\begin{script}
core.output("Hello Universe");
\end{script}
The file \file{output.txt} will be rewritten on each run of Stellarium. In case you need to save a copy of the current output file to another file, call
\begin{script}
core.saveOutputAs("myImportantData.txt");
core.resetOutput();
\end{script}
\noindent This script uses the LabelMgr module to display ``Hello Universe'' in red, fontsize 20, on the screen for 3 seconds.
\begin{script}
var label=LabelMgr.labelScreen("Hello Universe", 200, 200,
true, 20, "#ff0000");
core.wait(3);
LabelMgr.deleteLabel(label);
\end{script}
\section{Critical Scripting Differences introduced with version 1.0}
\label{sec:scripting:differences}
Qt5's QtScript module which has been used in versions 0.10 to 0.22 was more flexible and easier to fine-tune than Qt6's QJSEngine,
which is used in the Qt6-based 1.* series of the program, i.e., versions 1.0 (22.3) and later.
Still, most scripts that have been developed for Stellarium versions 0.10 to 0.22
still run without or with just minor modifications as described in this section.
\subsection{Pause/Resume}
\label{sec:scripting:differences:pause}
The Qt6-based builds (version 1.0 and later) do not offer a way to pause and resume a script as was available in the Qt5-based builds (0.10 to 0.22).
\subsection{The Vec3f problem}
\label{sec:scripting:differences:Vec3f}
One of the most important little helper classes in the main program are three-dimensional vectors.
\texttt{Vec3f} describe those with single-precision floating point data,
and \texttt{Vec3d} are those with double-precision floating point data.
\begin{sidewaystable}
{\ttfamily\scriptsize
\begin{tabular}{rlll}
&Input & Result with Qt5 & Result with Qt6 \\\hline
x&\textbf{var f1=new V3d(.1,.2,.3);} & & \\
&core.output(f1); & [0.1, 0.2, 0.3] & V3d(0x249a1cf37c0) [some address] \\
&core.output(+f1.toString()); & [0.1, 0.2, 0.3] & V3d(0x249a1cf37c0) [some address] \\
x&\textbf{core.output(f1.toHex());} & \#19334c & \#19334c \\
x&\textbf{core.output(f1.toVec3d());} & [0.1, 0.2, 0.3] & [0.1, 0.2, 0.3] \\\hline
x&\textbf{var f2=new V3d(f1.x(), 2*f1.y(), 3*f1.z());} & & \\
&core.output(f2); & [0.1, 0.4, 0.9] & V3d(0x249a1cf3a60) [some address] \\
x&\textbf{core.output(f2.toVec3d());} & [0.1, 0.4, 0.8999999999999999] & [0.1, 0.4, 0.9] \\\hline
x&\textbf{var crimson=new Color("Crimson");} & & \\
x&\textbf{core.output(crimson.toHex());} & \#dc143c & \#dc143c \\
&core.output(crimson.toVec3f()); & [r:0.8627451062202454, g:0.0784313753247261, b:0.23529411852359772] & [0.862745, 0.0784314, 0.235294] \\
&core.output(crimson.r); & 0.8627451062202454 & error \\
&core.output(crimson.r()); & error & 0.8627451062202454 \\
x&\textbf{core.output(crimson.getR());} & 0.8627451062202454 & 0.8627451062202454 \\
x&\textbf{core.output(crimson.getG());} & 0.0784313753247261 & 0.0784313753247261 \\
x&\textbf{core.output(crimson.getB());} & 0.23529411852359772 & 0.23529411852359772 \\
&crimson.g=0.444; & (works) & (error) \\
&crimson.setG(0.444); & (error) & (works) \\
x&\textbf{crimson=new Color(crimson.getR(), 0.444, crimson.getB());} & (reassign crimson; recommended for Qt5 and Qt6) & \\
x&\textbf{core.output(crimson.toRGBString());} & [r:0.862745, g:0.444, b:0.235294] & [r:0.862745, g:0.444, b:0.235294] \\\hline
x&\multicolumn{2}{l}{\textbf{var eq=new Color(GridLinesMgr.getColorEquatorJ2000Grid());}} & \\
x&\textbf{core.output(eq.toHex());} & \#00aaff & \#00aaff \\
&core.output(eq.toVec3f()); & [r:0, g:0.6666669845581055, b:1] & [0, 0.666667, 1] \\
&core.output(eq.toVec3d()); & (n.a.) & [0, 0.666667, 1] \\
&core.output(eq.toString()); & [0, 0.666667, 1] & Color(0x24a11712910) [some address] \\
x&\textbf{core.output(eq.toRGBString());} & [r:0, g:0.666667, b:1] & [r:0, g:0.666667, b:1] \\\hline
x&\textbf{var c=new Color(core.vec3f(.3, .4, .5));} & & \\
&core.output(c.toString()); & [0.3, 0.4, 0.5] & Color(0x249a1e078b0) [some address] \\
&core.output(c.toVec3f()); & [r:0.30000001192092896, g:0.4000000059604645, b:0.5] & [0.3, 0.4, 0.5] \\
x&\textbf{var d=c;} // assign to other & & \\
&core.output(d.toVec3f()); & [r:0.30000001192092896, g:0.4000000059604645, b:0.5] & [0.3, 0.4, 0.5] \\\hline
x&\textbf{var c2=new Color(.3, .4, .5);} & & \\
&core.output(c2.toString()); & [0.3, 0.4, 0.5] & Color(0x24a11712a60) [some address] \\
&core.output(c2.toVec3f()); & [r:0.30000001192092896, g:0.4000000059604645, b:0.5] & [0.3, 0.4, 0.5] \\
x&\textbf{var d2=c2;} // assign to other & & \\
&core.output(d2.toVec3f()); & [r:0.30000001192092896, g:0.4000000059604645, b:0.5] & [0.3, 0.4, 0.5]
\end{tabular}}
\caption{Use of V3d, V3f and Color wrapper classes. Only use the calls marked with \texttt{x} in scripts targeted at all versions of Stellarium.}
\label{tab:scripting:Vec3f}
\end{sidewaystable}
As late as in version 0.19 we added \texttt{Vec3f} and later \texttt{Vec3d} data types to the Javascript environment.
These allowed addressing variables of C++ classes \texttt{Vec3f} or \texttt{Vec3d} with named sub-fields \texttt{r}, \texttt{g}, \texttt{b}
(if used to describe colors), or \texttt{x}, \texttt{y}, \texttt{z} (same data but contextually understood as 3D data).
These functions may have pleased the JavaScript connoisseur when running scripts with Qt5-based builds of
Stellarium (series 0.*, and now usually found on 32-bit and older systems), but we recommend to abstain from their use in new scripts.
Qt6's scripting module cannot be extended that easily. The \texttt{Vec3d} and \texttt{Vec3f} data types are not directly scriptable,
but we can deal with method arguments of these types when they are mentioned in the API documentation (see \ref{sec:scripting:introduction}).
To access internals of these classes, we must use wrapper classes which bear different names:
\begin{description}
\item[\texttt{V3d}] can be used where \texttt{Vec3d} must be accessed.
\item[\texttt{V3f}] can be used where \texttt{Vec3f} must be accessed.
\item[\texttt{Color}] can be used where a \texttt{Vec3f} must be accessed which represents a color value.
\end{description}
They behave slightly different when run using Qt5 or Qt6, as shown in Table~\ref{tab:scripting:Vec3f}. If you aim for maximum compatibility between versions,
only use the calls which provide equal results in both series. This current state is far from optimal or even pretty, but a pragmatic solution that works.
We invite advanced developers to find a better solution that works with both Qt5 and Qt6.
\section{Example: Retrograde motion of Mars}
\label{sec:scripting:RetrogradeMotionOfMars}
A good way begin writing of scripts: set yourself a specific
goal and try to achieve it with the help of few simple steps. Any
complex script can be split into simple parts or tasks, which may solve any
newbie problems in scripting.
Let me explain it with examples.
Imagine that you have set a goal to make a demonstration of a very
beautiful, but longish phenomenon --- the retrograde motion of the
planet Mars (Fig.~\ref{fig:Mars2005}).
\begin{figure}[tb]
\centering\includegraphics[width=0.8\linewidth]{Mars2005_tezel.jpg}
\caption{Retrograde motion of Mars in 2005. {\small(Credit \& Copyright: Tunc Tezel --- APOD: 2006 April 22 -- Z is for Mars.)}}
\label{fig:Mars2005}
\end{figure}
\subsection{Script header}
Any ``complex'' script should contain a few lines in the first part of
the file, which contains important data for humans --- the name of the
script and its description --- and some rules for Stellarium. You can
even assign default shortcuts in the script header, but make sure you
assign a key not used elsewhere! The shortcuts are read during startup
from all scripts in the \file{scripts} sub-directories of both program
and user data directories (see section
\ref{sec:FilesAndDirectories:DirectoryStructure}). The description
may cover several lines (until the end of the commented header) and
should therefore be the last entry of the header.
\begin{script}
//
// Name: Retrograde motion of Mars
// Author: John Doe
// License: Public Domain
// Version: 1.0
// Shortcut: Ctrl+M
// Description: A demo of retrograde motion of Mars.
//
\end{script}
\subsection{A body of script}
At the first stage of writing of the script for a demo of
retrograde motion of Mars we should set some limits for
our demo. For example we want to see motion of Mars every
day during 250 days since October $1^{st}$, 2009.
Choosing a value of field of view and of the coordinates
of the center of the screen should be done at the this
stage also.
Let's add few lines of code into the script after the header
and run it:
\begin{script}
core.setDate("2009-10-01T10:00:00");
core.moveToRaDec("08h44m41s", "+18d09m13s",1);
StelMovementMgr.zoomTo(40, 1);
for (i=0; i<250; i++)
{
core.setDate("+ 1 days");
core.wait(0.2);
}
\end{script}
\noindent\textbf{Note:}
The \texttt{wait(delay\_seconds)} instruction inside the loop is important:
when you are just setting the time in rapid succession, there is no guarantee
that the view is updated, or a textual result has been updated when writing ephemerides,
before executing the next time update!
The \texttt{wait()} ensures the program can update the main simulation loop and
put the planets where they should be at that set time.
The minimum delay required may depend on your computer's speed.
OK, Stellarium is doing something, but what exactly is it
doing? The ground and atmosphere is enabled and any
motion of Mars is invisible. Let's add an another few
lines into the script (hiding the landscape and atmosphere)
after setting date and time:
\begin{script}
LandscapeMgr.setFlagLandscape(false);
LandscapeMgr.setFlagAtmosphere(false);
\end{script}
The whole sky is moving now --- let's lock it! Add this line
after previous lines:
\begin{script}
StelMovementMgr.setFlagLockEquPos(true);
\end{script}
It looks better now, but what about cardinal points,
elements of GUI and some ``glitch of movement''?
Let's change the script:
\begin{script}
core.setDate("2009-10-01T10:00:00");
LandscapeMgr.setFlagCardinalsPoints(false);
LandscapeMgr.setFlagLandscape(false);
LandscapeMgr.setFlagAtmosphere(false);
core.setGuiVisible(false);
core.moveToRaDec("08h44m41s", "+18d09m13s",1);
StelMovementMgr.setFlagLockEquPos(true);
StelMovementMgr.zoomTo(40, 1);
core.wait(2);
for (i=0; i<250; i++)
{
core.setDate("+ 1 days");
core.wait(0.2);
}
core.setGuiVisible(true);
\end{script}
It's better, but let's draw the ``path'' of Mars! Add
those line before the loop:
\begin{script}
core.selectObjectByName("Mars", false);
SolarSystem.setFlagIsolatedTrails(true);
SolarSystem.setFlagTrails(true);
\end{script}
Hmm\ldots let's add a few strings with info for users (insert
those lines after the header):
\begin{script}
var color = "#ff9900";
var info = LabelMgr.labelScreen("A motion of Mars", 20, 20,
false, 24, color);
var apx = LabelMgr.labelScreen("Setup best viewing angle, FOV
and date/time.", 20, 50, false, 18, color);
LabelMgr.setLabelShow(info, true);
LabelMgr.setLabelShow(apx, true);
core.wait(2);
LabelMgr.setLabelShow(apx, false);
\end{script}
Let's add some improvements to display info for users ---
change in the loop:
\begin{script}
var label = LabelMgr.labelObject(" Normal motion, West to
East", "Mars", true, 16, color, "SE");
for (i=0; i<250; i++)
{
core.setDate("+ 1 days");
if ((i % 10) == 0)
{
var strDate = "Day " + i;
LabelMgr.setLabelShow(apx, false);
var apx = LabelMgr.labelScreen(strDate, 20,
50, false, 16, color);
LabelMgr.setLabelShow(apx, true);
}
if (i == 75)
{
LabelMgr.deleteLabel(label);
label = LabelMgr.labelObject(" Retrograde or
opposite motion begins", "Mars",
true, 16, color, "SE");
core.wait(2);
LabelMgr.deleteLabel(label);
label = LabelMgr.labelObject(" Retrograde
motion", "Mars", true, 16, color,
"SE");
}
if (i == 160)
{
LabelMgr.deleteLabel(label);
label = LabelMgr.labelObject(" Normal motion
returns", "Mars", true, 16, color,
"SE");
core.wait(2);
LabelMgr.deleteLabel(label);
label = LabelMgr.labelObject(" Normal motion",
"Mars", true, 16, color, "SE");
}
core.wait(0.2);
}
\end{script}
\section{More Examples}
\label{sec:scripting:examples}
The best source of examples is the \file{scripts} sub-directory of the
main Stellarium source tree. This directory contains a sub-directory
called \file{tests} which are not installed with Stellarium, but are
nonetheless useful sources of example code for various scripting
features.
% TODO: More examples?
%%% Local Variables:
%%% mode: latex
%%% TeX-master: "guide"
%%% End:
|