<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://wiki.flightgear.org/w/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=TheEagle</id>
	<title>FlightGear wiki - User contributions [en]</title>
	<link rel="self" type="application/atom+xml" href="https://wiki.flightgear.org/w/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=TheEagle"/>
	<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/Special:Contributions/TheEagle"/>
	<updated>2026-05-30T05:35:10Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.39.6</generator>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=PUI&amp;diff=139997</id>
		<title>PUI</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=PUI&amp;diff=139997"/>
		<updated>2024-06-25T08:02:34Z</updated>

		<summary type="html">&lt;p&gt;TheEagle: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Feature to be removed|version=2023.3|reason=[[Plib]]/PUI is to be removed and replaced by a [[Canvas widget matrix|Canvas/Nasal based solution]] to help improve compatibility with [[OSG]] 3.6+ and [[OpenGL]] 4.x ([[Core Profile support|OpenGL Core Profile]], see [[2022.X Release plan]] ) and improve performance by [[Unifying the 2D rendering backend via canvas|phasing out legacy OpenGL code]]. Therefore, people should be careful when extending the '''PUI''' based legacy GUI engine&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/34921495/&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;{{cite web&lt;br /&gt;
  |url    =  https://sourceforge.net/p/flightgear/mailman/message/35157325/ &lt;br /&gt;
  |title  =  &amp;lt;nowiki&amp;gt; Re: [Flightgear-devel] GUI options (Was: Aircraft center) &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |author =  &amp;lt;nowiki&amp;gt; Torsten Dreyer &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |date   =  Jun 14th, 2016 &lt;br /&gt;
  |added  =  Jun 14th, 2016 &lt;br /&gt;
  |script_version = 0.40 &lt;br /&gt;
  }}&amp;lt;/ref&amp;gt; and coordinate any related work with the devel mailing list first-this applies particularly to adding any [[Template:PUI_widget|additional hard-coded PUI widgets]]. }}&lt;br /&gt;
[[File:Exit.png|thumb|FlightGear GUI dialog (exit.xml)stored in $FG_ROOT/gui/dialogs]]&lt;br /&gt;
{{GUI Navbar}}&lt;br /&gt;
&lt;br /&gt;
[[File:Custom-autopilot-dialog.png|right|200px]]&lt;br /&gt;
[[PUI]] is the standard GUI engine used in FlightGear, it is part of [[PLIB]] and is using raw, fixed-pipeline, [[OpenGL]] code internally (no [[OpenSceneGraph]]). As of mid 2021, FlightGear is in the process of working towards adopting the OpenGL Core Profile, this means that [[PUI]] is causing trouble and needs to be replaced/disabled or ported.&lt;br /&gt;
&lt;br /&gt;
PUI provides a fairly basic, but robust, set of widgets. PUI is also used for rendering the [[Menubar]].&lt;br /&gt;
&lt;br /&gt;
FlightGear uses a GUI widget set that is implemented on top of raw (legacy) [[OpenGL]]. This has many advantages from a portability standpoint and from the standpoint of integrating with window systems.  [[PUI]] doesn't have every feature under the sun, but it was never meant to.  It's relatively small, lean, mean, and written on top of [[OpenGL]] which makes life *much* easier for us.&lt;br /&gt;
&lt;br /&gt;
[[PUI]] already is a separate distinct library within Plib.  It depends on some central utility stuff, but that's about it.  So it is pretty stripped down and separate already.&amp;lt;ref&amp;gt;https://www.mail-archive.com/flightgear-devel@lists.sourceforge.net/msg26011.html&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We are to some extent hamstrung by the rather old GUI toolkit we use. However, replacing that is going to be non-trivial, and it would affect not just the core GUI but also all the dialog boxes that have been set up for particular aircraft.&amp;lt;ref&amp;gt;https://www.mail-archive.com/flightgear-devel@lists.sourceforge.net/msg25985.html&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Flightgear can look amazing when you use the HDR pipeline. We absolutely need this to stay relevant. The problem is that HDR is not compatible with OpenGL1 PUI. Work on getting rid of PUI has been going on for over a decade (longer than it took to implement it).&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37727346/&amp;lt;/ref&amp;gt; &lt;br /&gt;
&lt;br /&gt;
[[File:menubar2.jpg|517px]]&lt;br /&gt;
&lt;br /&gt;
== Replacement status ==&lt;br /&gt;
{{See also|FlightGear_and_OpenGL_ES#Example:_disabling_PUI}}&lt;br /&gt;
&lt;br /&gt;
{{Note| Update 09/2022: The PUI replacement [[QtQuick_use_in_FlightGear#Background|was going to be Qt]] but it started to get very complicated with changes in Qt 5.15 + Qt 6, so James is going with a more light-weight Canvas based approach now.&lt;br /&gt;
&lt;br /&gt;
Qt added support for Vulkan / Metal / D3D starting in 5.15, but FlightGear / OpenSceneGraph can’t support those, so integrating the two renderers went from being ‘complicated but ok’  to ‘very very complicated’. &lt;br /&gt;
&lt;br /&gt;
So now James is going with something much more lightweight using some C++ compatibility code, some Nasal for styling and the existing [[Canvas widgets|Canvas widget rendering]] from Thomas Geymayer (TheTom) with [[Canvas Widget Matrix|some extensions and additions]], based on the [[Howto:Processing_legacy_PUI_dialogs_using_Canvas#Original_Discussion|plans originally discussed when the Canvas GUI system was added to FlightGear]]: some pieces are in FlightGear &amp;amp; FGData already. &lt;br /&gt;
&lt;br /&gt;
James has basic dialogs working okay but not the more complex ones and everything looks kind of ugly, he needs to improve the visual look before he shares screenshots to avoid everyone freaking out :) The disadvantage of this approach is James is far from expert at creating visual appearances this way, so it’s kind on unrewarding and slow for him. If someone likes messing with CSS-type styling, border-images and hover-states, ping him since we could probably move things also faster &amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37701750/&amp;lt;/ref&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Motivation ===&lt;br /&gt;
{{Main article|Core Profile support}}&lt;br /&gt;
&lt;br /&gt;
The bigger issue here is we need to ditch [[PUI]] (which is in progress) and some OpenGL 1.0 code (HUD, 2D panels especially - can be #ifdef for now) so we can enable Core profile on Mac - since Mac 4.x support (we only hit about 4.3 alas, but with some extensions to get in sight of 4.5) is Core profile only, no Compatibility mode.&lt;br /&gt;
&lt;br /&gt;
Improving the frame-rate and modernised 3D rendering, can’t be worked on until the PUI code, 2D panels and Shiva are removed, but doing so is a frustrating slow path&amp;lt;ref&amp;gt;{{cite web&lt;br /&gt;
  |url    =  https://sourceforge.net/p/flightgear/mailman/message/35623408/ &lt;br /&gt;
  |title  =  &amp;lt;nowiki&amp;gt; Re: [Flightgear-devel] canvas non svg-elements broken &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |author =  &amp;lt;nowiki&amp;gt; James Turner &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |date   =  Jan 24th, 2017 &lt;br /&gt;
  |added  =  Jan 24th, 2017 &lt;br /&gt;
  |script_version = 0.40 &lt;br /&gt;
  }}&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Note|There is now an alternative to ShivaVG in SimGear - ShaderVG, which is working pretty much as good as ShivaVG. It can be enabled by passing -DENABLE_SHADERVG to cmake when building SImGear from source.}}&lt;br /&gt;
&lt;br /&gt;
Given that with many graphics drivers PUI doesn't render correctly when higher shader quality is on, many people are convinced PUI needs to be replaced.&amp;lt;ref&amp;gt;{{cite web&lt;br /&gt;
  |url    =  https://sourceforge.net/p/flightgear/mailman/message/35624918/ &lt;br /&gt;
  |title  =  &amp;lt;nowiki&amp;gt; Re: [Flightgear-devel] canvas non svg-elements broken &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |author =  &amp;lt;nowiki&amp;gt; Thorsten Renk &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |date   =  Jan 25th, 2017 &lt;br /&gt;
  |added  =  Jan 25th, 2017 &lt;br /&gt;
  |script_version = 0.40 &lt;br /&gt;
  }}&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Canvas Emulation ==&lt;br /&gt;
{{See also|Canvas widget matrix}}&lt;br /&gt;
'''Last updated: 04/2023 &amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37805054/&amp;lt;/ref&amp;gt;&lt;br /&gt;
'''&lt;br /&gt;
=== Gallery ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery&amp;gt;&lt;br /&gt;
About-dialog-xmldialog-04-2023.png|about.xml PUI/XML dialog parsed/rendered via the [[Canvas GUI]] and the underlying XMLDialog.nas parser/translator (04-2023)&lt;br /&gt;
Logging-dialog-via-xmldialog-04-2023.png|logging.xml PUI/XML dialog parsed/rendered via the [[Canvas GUI]] and the underlying XMLDialog.nas parser/translator (04-2023)&lt;br /&gt;
Failures-dialog-via-xmldialog-04-2023.png|failures dialog-via-xmldialog-04-2023.png{{!}}thumb{{!}}logging.xml PUI/XML dialog parsed/rendered via the [[Canvas GUI]] and the underlying XMLDialog.nas parser/translator (04-2023)&lt;br /&gt;
Environment-dlg-via-canvas-bridge-04-2023.png|Screen shot showing the XMLDialog.nas bridge in use to parse/render the environment.xml PUI/XML dialog (unmodified)&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Status ===&lt;br /&gt;
As of 01/2023, the replacement for PUI is making good progress&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37765706/&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
In February 2022, James reported having something in progress locally, and that should even be something we can try in March/April 2022. Hopefully that’s quick enough, knowing it has been a long time coming.&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37606439/&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
There's now an XML to Nasal bridge, to keep PUI dialogs working (Disabled by a CMake option). This is implemented on top of the [[Canvas]] system.&lt;br /&gt;
&lt;br /&gt;
This builds equivalent C++ objects to what the [[PUI]] dialogs build, with properties exposed to Nasal. Peer objects are created by Nasal [[callbacks]], which can implement the various dialog functions needed to keep compatibility, especially the [[#Background|‘update’ and ‘apply’ hooks]].&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/flightgear/ci/cbd5ef9e7b8f433dbc1ba97bae7d7cfc894e7cef/&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For remaining work to be done, please refer to [https://sourceforge.net/p/flightgear/mailman/message/37881226/]&lt;br /&gt;
&lt;br /&gt;
{{Note|The PUI Compatible Canvas GUI is a compile time option: Use the following [[Building using CMake|cmake option]] to enable this code: {{CMake Build Option|option=ENABLE_PUICOMPAT|default=ON|description=This requires latest SG/FG AND FGDATA}}. But this is all highly fluid and most of it really developer only. At one point they will become default or be accessible in the launcher.&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37834004/&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you compiled with PUICompatibility enabled, but still don't see any dialogs - only the menubar, you might need to enable the GUI module in defaults.xml:{{PropArg}}&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37833149/&amp;lt;/ref&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
The new Compatibility files can now be found in $FG_SRC/GUI:&lt;br /&gt;
&lt;br /&gt;
* {{flightgear file|src/GUI/FGPUICompatDialog.cxx }}&lt;br /&gt;
* {{flightgear file|src/GUI/FGPUICompatDialog.hxx }}&lt;br /&gt;
* {{flightgear file|src/GUI/PUICompatObject.cxx  }}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;s&amp;gt;01/2023: James is slowly working on creating the pop-up menu and combo-box widgets [...]&amp;lt;/s&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;s&amp;gt;One of the major things slowing down James' work on the popup-menu and combo box is time to restart the simulator for each change, he is looking for some solution to re-load the widget Nasal code independently. &amp;lt;ref&amp;gt;&amp;lt;https://sourceforge.net/p/flightgear/mailman/message/37756183/&amp;lt;/ref&amp;gt;&amp;lt;/s&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;s&amp;gt;James has done about 75% (hah!) of the C++ work to enable live reloading of modules this way, but unfortunately there are some code paths that would become crashy if you use the feature for the Canvas: because it would reference the ‘old’ (pre-reload) Nasal code, and not the new one, and therefore you’d get a weird mix of old and new, and then just crash the sim. To fix it properly I need to track down those places that store a reference to Canvas and give them a re-init method, so we don’t keep the stale references.&amp;lt;/s&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;s&amp;gt;The general non-fun-ness of that kind of debugging is James has been making such slow progress on my PUI replacement widgets recently &amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37756263/&amp;lt;/ref&amp;gt;&amp;lt;/s&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Popup menus and combo box widgets are working now - also, the simulator doesn't have to be restarted anymore - any Nasal module (including the canvas module can now be reloaded live:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;fgcommand(&amp;quot;nasal-reload&amp;quot;, {&amp;quot;module&amp;quot;: &amp;quot;canvas&amp;quot;})&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Todo ===&lt;br /&gt;
&amp;lt;s&amp;gt;Nothing in particular: help debugging the grid layout (which is in simgear) would be good. For now, you could create a grid layout manually using some Nasal.&amp;lt;/s&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The grid layout is now mostly working (from my experience) but it behaves weirdly when there isn't enough place to accommodate all widgets.&lt;br /&gt;
&lt;br /&gt;
Global menu keybindings won’t worry but don’t touch that, I have a separate plan for it.&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37805736/&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As of 04/2023, remaining items on the todo list are: &amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37805060/&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Grid layout&lt;br /&gt;
** GridLayout needs debugged: After this, the basic UI is usable, would probably make it a switchable option on ‘next’ (I can already use the simulator with the new UI today, but all the sizes are off in the grid layouts which we use everywhere)&lt;br /&gt;
&lt;br /&gt;
*  list {{Done}}&lt;br /&gt;
*  table (and tree ?) models corresponding views&lt;br /&gt;
* waypoint list&lt;br /&gt;
* Re-write the waypoint list and airport dialog to work again (since these use custom widgets):&lt;br /&gt;
** log list &lt;br /&gt;
** airport-list equivalents&lt;br /&gt;
*  Nasal access to the menu i18n functions&lt;br /&gt;
*  selection support for single- and multi-line text (which is being worked on -&amp;gt; CanvasPangoText){{Note|For the LineEdit widget, a hack has been implemented to provide more-or-less working selection highlighting while the PangoText element isn't done.}}&lt;br /&gt;
* Keyboard shortcut handling, although this is not really a PUI vs replacement issue, to me we should handle UI shortcuts in the same was normal keyboard.xml shortcuts&lt;br /&gt;
** this previously worked because PUI got ‘first chance’ at handling key input. But we always had duplication between keyboard.xml and other places, so I would rather standardise on keyboard.xml, but allow a UI element to set a scope for shortcuts. (So that Copy/paste shortcuts etc can work in a text input, at least … but probably some other standard ones) &amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37805188/&amp;lt;/ref&amp;gt;&lt;br /&gt;
* Ensure all the places which use the PUI combo-box and list-widget work : there’s a few of these.&lt;br /&gt;
&lt;br /&gt;
The CanvasPangoText stuff is needed but we can perfectly well run the UI on next without it.&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37805176/&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Parser ===&lt;br /&gt;
{{Main article|pui2canvas}}&lt;br /&gt;
The corresponding Nasal/Canvas module to dynamically &amp;quot;translate&amp;quot; legacy PUI/XML dialogs into Canvas dialogs (at runtime), is to be found in {{fgdata file|Nasal/gui/XMLDialog.nas}} &amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/fgdata/ci/fe7c87b21a69f88ddb87d89453c48d12b69660e2/&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Widgets ===&lt;br /&gt;
{{Main article|Howto:Creating a Canvas GUI Widget}}&lt;br /&gt;
&lt;br /&gt;
It’s split between Simgear (see classes with widget / layout in the name) and in FGData. (Eg widgets/Button.nas). To be able to use the existing dialog XML files un-modified (which is a design goal), James is extending the widget types with many additional ones (eg PUI has slider, dial, combo-box, checkbox, all of which need to be created, see [[Canvas widget matrix]].&lt;br /&gt;
&lt;br /&gt;
Thomas’s canvas widgets have a very good separation of API + state from appearance, so all styling is in its own file, and James is being very strict about maintaining this separation, so we also retain the re-styling feature of the PUI UI, which many people also rely on. This does make the process of adding new widgets more complex, however.&lt;br /&gt;
&lt;br /&gt;
{{See also|Canvas widgets}}&lt;br /&gt;
&lt;br /&gt;
=== Layouting ===&lt;br /&gt;
The other thing is to preserve all the layouting: James has added a grid layout to Simgear, since that is supported by the existing PUI code (even though the layouts are not actually part of PUI itself). The problem is getting the sizing / hinting of all the widgets to match the PUI values, so that dialogs look approximately the same under the new UI as they did with PUI; again this a design goal so that all existing dialogs in aircraft and add-ons, which we can’t update, continue to work and be usable. Debugging that is also proving quite tricky, since there’s all kinds of hard-coded assumptions built into PUI widgets about pixels, font-sizes etc which are not true in the new system.&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37701792/&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Background ==&lt;br /&gt;
In FlightGear, PUI dialogs are standard [[PropertyList XML File]]s that are stored in $FG_ROOT/gui/dialogs, they can contain the widgets mentioned in {{readme file|gui}}, using a simple layout engine discussed in {{readme file|layout}}, and bindings using a combination of so called fgcommands operating on properties (see {{readme file|commands}}) and custom [[Nasal]] (FlightGear scripting) code. &lt;br /&gt;
&lt;br /&gt;
In addition, each PUI/XML dialog may contain Nasal script sections that are executed when opening/closing the dialog, a feature which is commonly used for procedurally creating/updating widgets using the [[Nasal_library#cmdarg.28.29|cmdarg() API]], which allows the dialog tree to be traversed and manipulated prior to the dialog being rendered.&lt;br /&gt;
Widgets can be conditionally hidden/shown using a wrapper for [[Conditions|SGCondition]] in [[Nasal_library/props|props.nas]] &lt;br /&gt;
The &amp;lt;code&amp;gt;canvas&amp;lt;/code&amp;gt; widget also supports its own embedded Nasal code section to execute arbitrary widget specific Nasal code upon opening/closing the dialog/widget.&lt;br /&gt;
&lt;br /&gt;
PUI/XML dialogs can be loaded, dynamically created, updated and closed using a handful of fgcommands:&lt;br /&gt;
* {{fgcommand|dialog-new}}&lt;br /&gt;
* {{fgcommand|dialog-show}}&lt;br /&gt;
* {{fgcommand|dialog-update}}&lt;br /&gt;
* {{fgcommand|dialog-apply}}&lt;br /&gt;
* {{fgcommand|dialog-close}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
PUI related OpenGL code is particularly infamous for causing rendering artifacts for people on AMD/ATI and Intel hardware (especially in combination with certain fonts/styles and [[Effects|effects]]/[[Shaders|shaders]]), see {{Issue|2213}}.&lt;br /&gt;
&lt;br /&gt;
PUI is also known to affect rendering performance quite significantly (see {{search |keywords=anthrax+gui}}), while also preventing FlightGear from using a more recent version of OpenGL&amp;lt;ref&amp;gt;http://sourceforge.net/p/flightgear/mailman/message/34532040/&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;http://forum.flightgear.org/viewtopic.php?f=71&amp;amp;t=24046&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
However, improving the frame-rate and modernised 3D rendering,  can’t be worked on until the PUI code, 2D panels and Shiva are removed, but doing so is a frustrating slow path&amp;lt;ref&amp;gt;{{cite web&lt;br /&gt;
  |url    =  https://sourceforge.net/p/flightgear/mailman/message/35623408/ &lt;br /&gt;
  |title  =  &amp;lt;nowiki&amp;gt; Re: [Flightgear-devel] canvas non svg-elements broken &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |author =  &amp;lt;nowiki&amp;gt; James Turner &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |date   =  Jan 24th, 2017 &lt;br /&gt;
  |added  =  Jan 24th, 2017 &lt;br /&gt;
  |script_version = 0.40 &lt;br /&gt;
  }}&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Besides, while most people seem to agree PUI needs to be replaced, it sounds as if the fallout from doing so would be more painful (cumulatively) than the pain its existence causes.&amp;lt;ref&amp;gt;{{cite web&lt;br /&gt;
  |url    =  https://sourceforge.net/p/flightgear/mailman/message/35623408/ &lt;br /&gt;
  |title  =  &amp;lt;nowiki&amp;gt; Re: [Flightgear-devel] canvas non svg-elements broken &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |author =  &amp;lt;nowiki&amp;gt; James Turner &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |date   =  Jan 24th, 2017 &lt;br /&gt;
  |added  =  Jan 24th, 2017 &lt;br /&gt;
  |script_version = 0.40 &lt;br /&gt;
  }}&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
But given that with many graphics drivers PUI doesn't render correctly when higher shader quality is on, graphics folks are also convinced it needs to be replaced.&amp;lt;ref&amp;gt;{{cite web&lt;br /&gt;
  |url    =  https://sourceforge.net/p/flightgear/mailman/message/35624918/ &lt;br /&gt;
  |title  =  &amp;lt;nowiki&amp;gt; Re: [Flightgear-devel] canvas non svg-elements broken &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |author =  &amp;lt;nowiki&amp;gt; Thorsten Renk &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |date   =  Jan 25th, 2017 &lt;br /&gt;
  |added  =  Jan 25th, 2017 &lt;br /&gt;
  |script_version = 0.40 &lt;br /&gt;
  }}&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
In addition, [[OpenSceneGraph]] (OSG) can obviously not help optimize any PUI related GL code and PUI widgets are generally considered to be pretty archaic and not easy to extend&amp;lt;ref&amp;gt;http://sourceforge.net/p/flightgear/mailman/message/26832164/&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;http://sourceforge.net/p/flightgear/mailman/message/10587120/&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;http://sourceforge.net/p/flightgear/mailman/message/10587272/&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
{{Note|We use the GUI code from PLIB, which doesn't know anything about OSG. See the SGPuDrawable class in $FG_SRC/Viewer/renderer.cxx for the implementation. The one catch is that OSG has a strong notion of separation between the update of a &amp;quot;scene&amp;quot; and its rendering, and that might not play well with arbitrary existing OpenGL code. }}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
As of late 2015, there is heavy activity towards providing alternatives to a PUI-based UI:&lt;br /&gt;
* [[Integrated Web GUI]] (external, browser-based - fully asynchronous)&lt;br /&gt;
* [[Howto:Processing legacy PUI dialogs using Canvas]] (internal, using the [[Canvas]] system and a simple [[Nasal]] parser to deal with existing PUI/XML dialogs)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery mode=&amp;quot;packed&amp;quot;&amp;gt;&lt;br /&gt;
Early-Phi-screen shot.PNG|Screenshot showing [[Phi]]&lt;br /&gt;
About-dialog-rendered-by-canvas.png|Screenshot showing a dialog rendered by the [[Howto:Processing legacy PUI dialogs using Canvas|pui2canvas parser]]&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== History ==&lt;br /&gt;
{{See also|QtQuick use in FlightGear}}&lt;br /&gt;
{{Caution|This is merely kept for future reference, many of the ideas discussed below have become obsolete as of 09/2022, if in doubt, please get in touch via devel-list.}}&amp;lt;small&amp;gt;&lt;br /&gt;
=== 2021 ===&lt;br /&gt;
In early 2021, James did a brief evaluation of [https://github.com/ocornut/imgui ImGUI] as a possibility - he's also evaluating it in some projects at his day job. &lt;br /&gt;
&lt;br /&gt;
The API is interesting if you’re starting from scratch and only exclusively in C++, but it’s not a great fit for how we define GUIs in FG (via [[XML]]/[[Nasal]]): there is no back door that he could find, to access the persistent state of the GUI, or build it up in a data-driven way. So making a mapping to keep our existing GUI XML working (and updating correctly) becomes a bit of a chore.&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37295586/&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pretty much all of the FlightGear GUI is defined in XML space, except for a few oddments. Let’s say 95% at this point.&lt;br /&gt;
&lt;br /&gt;
Additionally, even if we were to change that for the core sim (and James' current plan is to keep the XML syntax and just extend it), the installed base of '''aircraft''' ship their own XML dialogs (eg., replacing the autopilot dialog with a custom one, or just adding their own aircraft-specific helper controls for doors / lights / cargo / whatever). So even if we found the most sublimely beautiful, intuitive, compact GUI description language in the world, we have to keep the XML syntax working as-is, for the foreseeable future.&lt;br /&gt;
&lt;br /&gt;
Given this, James' intention is to replace the output side [[PUI]] but keep the front-end side (XML / properties / bindings) unchanged, and therefore backwards compatible, and simply add some new types / options / widget types going forward. This will mean we still have to deal with the slightly cumbersome XML+Nasal syntax, but, well, it’s not *that* cumbersome. &amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37296280/&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In mid 2021, James reported that he is working on some ‘PUICompat’ classes which hold/own/represent the GUI state in C++, but which can be used with [[Nasal/CppBind|Nasal CppBind]]. And of course we could indeed expose those later on. &amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37297727/&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In July 2021, James suggested not to worry about the existing GUI code, it’s going in the bin.&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37325905/&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
For the time being, the in-sim GUi can’t do Unicode yet, but James is working on it, hopefully available soon.&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37328200/&amp;lt;/ref&amp;gt;&lt;br /&gt;
Also, the in-sim menubar will change with the new GUI, *and* we use a native menubar on macOS. Until James replaces the PUI fonts we can’t use Unicode symbols. So, will have to come back to it once the new GUI is merged.&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37327237/&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 2020 ===&lt;br /&gt;
As of 03/2020, the PLIB replacement was reported to be in the backlog &amp;lt;ref&amp;gt;{{cite web&lt;br /&gt;
  |url    =  https://sourceforge.net/p/flightgear/mailman/message/36951255/ &lt;br /&gt;
  |title  =  &amp;lt;nowiki&amp;gt;Re: [Flightgear-devel] C++ and/or OSG question &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |author =  &amp;lt;nowiki&amp;gt; Scott Giese &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |date   =  Mar 18th, 2020 &lt;br /&gt;
  |added  =  Mar 18th, 2020 &lt;br /&gt;
  |script_version = 0.50 &lt;br /&gt;
  }}&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
With PLIB replacement work likely being re-scheduled for [[Post FlightGear 2020.2 LTS changes]] &amp;lt;ref&amp;gt;{{cite web&lt;br /&gt;
  |url    =  https://sourceforge.net/p/flightgear/mailman/message/36985375/ &lt;br /&gt;
  |title  =  &amp;lt;nowiki&amp;gt;Re: [Flightgear-devel] Working Group Proposal - Boost and PLIB replacement - Delegation and Teamwork &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |author =  &amp;lt;nowiki&amp;gt; James Turner &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |date   =  April 18th, 2020 &lt;br /&gt;
  |added  =  April 18th, 2020 &lt;br /&gt;
  |script_version = 1.10 &lt;br /&gt;
  }}&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
In 08/2020, James stated that he was still working (exceptionally slowly!) on replacing PUI (as part of removing legacy OpenGL code (PUI, HUD, 2d panels, porting Canvas away form ShivaVG), so we can switch to Vulkan/VSG at some point (ideally at some point before Apple turn off OpenGL support…)), and that he doesn't think /any/ of us *want* to be working on these features particularly, but projects like these need to be done before anyone can have fun with VSG or Vulkan (personally James would be very enthusiastic about working on VSG support (being on macOS, I also have the most to lose / gain from it)). Sometimes that’s just how it goes. Equally they will all benefit the project ultimately, but the payoff both for the individual and the project is very drawn out. &amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37077158/&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In 10/2020, the estimation is that PUI should be going away really ‘soon’, the replacement code is already in next / 2020.2. (There’s no plan to actually /use/ the new UI code in 2020.2, but if the bugs in it prove simple, we could actually turn it for specific dialog: it seems very unlikely this makes sense for a stable release however)&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37123818/&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The fixed-function removal, is actively in hand (Gaetan is working on the 2D panel part, and James is going to focus on PUI in the rest of 2020). James hopes that means next year he can focus on Vulkan, or at least, using OSG as if /were/ VSG, so the migration in a year or two when VSG is stable, is not so painful.&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37129098/&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The PUI UI and replacement can co-exist in the same build (they already do, effectively.&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37131297/&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In 11/2020, James suggested not to worry about details such as PUI styling/theming etc: we’re not making such changes for the LTS, and PUI should be gone before the next release.&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37142225/&amp;lt;/ref&amp;gt; Also, he recommended (to everyone) to stop touching GUI stuff for a few weeks, as he's about to turn it all on its head.&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37142356/&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 2019 ===&lt;br /&gt;
The new GUI has been incubating for a few years, but there's apparently been a lot of progress as of 10/2019.&lt;br /&gt;
&lt;br /&gt;
It is likely the new GUI will be a user opt-in feature - at least initially. The sunset of the PLIB library _could_ happen relatively soon, but &lt;br /&gt;
people will need to see the new GUI and react to it before we could commit to PLIB removal &amp;lt;ref&amp;gt;{{cite web&lt;br /&gt;
  |url    =  https://sourceforge.net/p/flightgear/mailman/message/36784489/ &lt;br /&gt;
  |title  =  &amp;lt;nowiki&amp;gt;Re: [Flightgear-devel] in Flightgear/Simgear C++ Code: What needs Doing? What are YOU Doing? What would you do if you had the time? &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |author =  &amp;lt;nowiki&amp;gt; Scott Giese &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |date   =  Oct 14th, 2019&lt;br /&gt;
  |added  =  Oct 14th, 2019 &lt;br /&gt;
  |script_version = 0.50 &lt;br /&gt;
  }}&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
In 02/2019, James stated that the new UI is coming quite soon. &amp;lt;ref&amp;gt;{{cite web&lt;br /&gt;
  |url    =  https://sourceforge.net/p/flightgear/mailman/message/36589420/&lt;br /&gt;
  |title  =  &amp;lt;nowiki&amp;gt; Re: [Flightgear-devel] Lagging &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |author =  &amp;lt;nowiki&amp;gt; James Turner &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |date   =  Feb 17th, 2019 &lt;br /&gt;
  |added  =  Feb 17th, 2019&lt;br /&gt;
  |script_version = 0.40 &lt;br /&gt;
  }}&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 2018 ===&lt;br /&gt;
{{See also|QtQuick use in FlightGear}}&lt;br /&gt;
&lt;br /&gt;
According to James, we have to accept that changes like testing or making [[PUI]] a modular thing or moving the JS code tend to have an immediate pain for some people (because strange stuff gets broken, and takes some time to get fixed), whereas the payoff in terms of improved joystick handling or replacing the UI or testing of all subsystems will take some months or years to be felt.&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/36323240/&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
James said he was in the middle of porting the launcher’s final tab (location) to the new UI scheme, once that’s done he will start sketching out the ‘in sim’ settings UI using the same pieces. He’ll send a request for contributions around about that in the next three-four weeks, once he has enough templates &amp;amp; examples that there is a clear pattern to follow.&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/flightgear-devel/thread/2d10f16c-ddc8-07f3-07e7-eca2a0a0d55f%40zaretto.com/#msg36293586&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
James announced that the launcher is now feature complete with the QtQuick UI, he’ll keep making bug-fixes of course as people report them, but my todo list for the core features is now done&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/36355833/&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== 2017 ===&lt;br /&gt;
&lt;br /&gt;
In 10/2017, James said he was getting really close to having the PUI replacement UI suitable for beta-testing.&amp;lt;ref&amp;gt;{{cite web&lt;br /&gt;
  |url    =  https://sourceforge.net/p/flightgear/mailman/message/36067646/ &lt;br /&gt;
  |title  =  &amp;lt;nowiki&amp;gt; Re: [Flightgear-devel] PLIB features in plib svn but not in any&lt;br /&gt;
 release &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |author =  &amp;lt;nowiki&amp;gt; James Turner &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |date   =  Oct 7th, 2017 &lt;br /&gt;
  |added  =  Oct 7th, 2017 &lt;br /&gt;
  |script_version = 0.40 &lt;br /&gt;
  }}&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Originally, James was hoping to land the PUI replacement GUI in the last dev cycle in 2017 (at least as a proof-of-concept, probably not as the default UI), so wouldn’t expend lots of effort on things like collapsible sections which might be a lot of work with the current PUI/Canvas approaches, but are trivial with the new [[QtQuick use in FlightGear|QtQuick based UI scheme]].&amp;lt;ref&amp;gt;{{cite web&lt;br /&gt;
  |url    =  https://sourceforge.net/p/flightgear/mailman/message/36059822/ &lt;br /&gt;
  |title  =  &amp;lt;nowiki&amp;gt; Re: [Flightgear-devel] Canvas MapLayer for OpenStreetMap, OpenAIP,&lt;br /&gt;
 VFR Sectionals &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |author =  &amp;lt;nowiki&amp;gt; James Turner &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |date   =  Oct 1st, 2017 &lt;br /&gt;
  |added  =  Oct 1st, 2017 &lt;br /&gt;
  |script_version = 0.40 &lt;br /&gt;
  }}&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Once the basic new UI is in place we can experiment with different re-arrangements easily, without being limited by PUI. (James expects PUI to live on as the default / alternate UI while this happens)&amp;lt;ref&amp;gt;{{cite web&lt;br /&gt;
  |url    =  https://sourceforge.net/p/flightgear/mailman/message/36061229/ &lt;br /&gt;
  |title  =  &amp;lt;nowiki&amp;gt; Re: [Flightgear-devel] FGFS macOS Menubar &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |author =  &amp;lt;nowiki&amp;gt; James Turner &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |date   =  Oct 2nd, 2017 &lt;br /&gt;
  |added  =  Oct 2nd, 2017 &lt;br /&gt;
  |script_version = 0.40 &lt;br /&gt;
  }}&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Technically, this entails replacing dialog.cxx with some code which builds up some special ‘PUI-Emulation’ QtQuick controls, so that existing dialogs (especially from Aircraft) continue to work. This mode will look somewhat ugly but hopefully no more ugly than PUI! And it helps that the set of widgets we have in PUI is limited, and the ‘tricky’ widgets (map, scrolling list, etc) are in the dialogs will will replace with new ones first.&amp;lt;ref&amp;gt;{{cite web&lt;br /&gt;
  |url    =  https://sourceforge.net/p/flightgear/mailman/message/36068767/ &lt;br /&gt;
  |title  =  &amp;lt;nowiki&amp;gt; Re: [Flightgear-devel] PLIB features in plib svn but not in any&lt;br /&gt;
 release &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |author =  &amp;lt;nowiki&amp;gt; James Turner &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |date   =  Oct 8th, 2017 &lt;br /&gt;
  |added  =  Oct 8th, 2017 &lt;br /&gt;
  |script_version = 0.40 &lt;br /&gt;
  }}&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This PUI emulation isn't going to use any code from PLIB&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/36985303/&amp;lt;/ref&amp;gt;, so what will remain from it in FG afterwards will be the FNT and JS components (reworked).&amp;lt;ref&amp;gt;{{cite web&lt;br /&gt;
  |url    =  https://sourceforge.net/p/flightgear/mailman/message/36068817/ &lt;br /&gt;
  |title  =  &amp;lt;nowiki&amp;gt; Re: [Flightgear-devel] PLIB features in plib svn but not in any&lt;br /&gt;
 release &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |author =  &amp;lt;nowiki&amp;gt; Florent Rougon &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |date   =  Oct 8th, 2017 &lt;br /&gt;
  |added  =  Oct 8th, 2017 &lt;br /&gt;
  |script_version = 0.40 &lt;br /&gt;
  }}&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 2016 ===&lt;br /&gt;
We need to explore if Qt Quick or widgets is the better way to go, with a preference towards Qt Quick, since it will allow things closer to current PUI (semi-transparent widgets on top of the 3D content). It still allows creating separate windows too (Erik’s use case), but means we get consistent, custom theming on all platforms, since standard desktop look is not so nice for a flight-sim.&lt;br /&gt;
&lt;br /&gt;
James intention is to make a QML-hosting OSG-drawable, and add this as a concept in Qt-enabled builds, then port some PUI dialogs to it, and see how easy / painful the process is. Hell create some QObjects which expose commands and the property tree and Nasal.&lt;br /&gt;
&lt;br /&gt;
This has the advantage of not touching the OSG window at all, QtQuick simply provides some additional OpenGL rendering on top, which we pass events to - pretty much exactly the same as what we already do for PUI, and hopefully can co-exist with it. James got this working in a local OSG tree in a standalone demo, but that’s based on osgQt - he wants to see if he can make it work on a normal, non-Qt OSG window inside FlightGear.&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/35156051/&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The idea is to host an XML dialog (Currently done with PUI) from Qt also. The question is if that can be done keeping ‘source compatibility’ exactly with the current UI XML and Nasal interface. It probably can be, but many of the PUI widgets have really ugly APIs, especially for combo-boxes, drop-down menus and scrolling lists, where it makes sense to map a Nasal class to a Qt ItemModel, which would be more elegant and easy to work with in both Nasal and integrate on the Qt side. But obviously would mean changing the dialog sources.&lt;br /&gt;
&lt;br /&gt;
Of course, aircraft dialogs tend to be simpler, and not use the more complex PUI widgets, precisely for reasons like this, but that’s a limitation it would be best to remove.&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/35155721/&amp;lt;/ref&amp;gt;&lt;br /&gt;
&amp;lt;/small&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== References ==&lt;br /&gt;
{{Appendix}}&lt;br /&gt;
&lt;br /&gt;
[[Category:PUI| ]]&lt;br /&gt;
[[Category:Rendering roadblocks]]&lt;/div&gt;</summary>
		<author><name>TheEagle</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=PUI&amp;diff=139996</id>
		<title>PUI</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=PUI&amp;diff=139996"/>
		<updated>2024-06-25T08:00:36Z</updated>

		<summary type="html">&lt;p&gt;TheEagle: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Feature to be removed|version=2023.3|reason=[[Plib]]/PUI is to be removed and replaced by a [[Canvas widget matrix|Canvas/Nasal based solution]] to help improve compatibility with [[OSG]] 3.6+ and [[OpenGL]] 4.x ([[Core Profile support|OpenGL Core Profile]], see [[2022.X Release plan]] ) and improve performance by [[Unifying the 2D rendering backend via canvas|phasing out legacy OpenGL code]]. Therefore, people should be careful when extending the '''PUI''' based legacy GUI engine&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/34921495/&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;{{cite web&lt;br /&gt;
  |url    =  https://sourceforge.net/p/flightgear/mailman/message/35157325/ &lt;br /&gt;
  |title  =  &amp;lt;nowiki&amp;gt; Re: [Flightgear-devel] GUI options (Was: Aircraft center) &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |author =  &amp;lt;nowiki&amp;gt; Torsten Dreyer &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |date   =  Jun 14th, 2016 &lt;br /&gt;
  |added  =  Jun 14th, 2016 &lt;br /&gt;
  |script_version = 0.40 &lt;br /&gt;
  }}&amp;lt;/ref&amp;gt; and coordinate any related work with the devel mailing list first-this applies particularly to adding any [[Template:PUI_widget|additional hard-coded PUI widgets]]. }}&lt;br /&gt;
[[File:Exit.png|thumb|FlightGear GUI dialog (exit.xml)stored in $FG_ROOT/gui/dialogs]]&lt;br /&gt;
{{GUI Navbar}}&lt;br /&gt;
&lt;br /&gt;
[[File:Custom-autopilot-dialog.png|right|200px]]&lt;br /&gt;
[[PUI]] is the standard GUI engine used in FlightGear, it is part of [[PLIB]] and is using raw, fixed-pipeline, [[OpenGL]] code internally (no [[OpenSceneGraph]]). As of mid 2021, FlightGear is in the process of working towards adopting the OpenGL Core Profile, this means that [[PUI]] is causing trouble and needs to be replaced/disabled or ported.&lt;br /&gt;
&lt;br /&gt;
PUI provides a fairly basic, but robust, set of widgets. PUI is also used for rendering the [[Menubar]].&lt;br /&gt;
&lt;br /&gt;
FlightGear uses a GUI widget set that is implemented on top of raw (legacy) [[OpenGL]]. This has many advantages from a portability standpoint and from the standpoint of integrating with window systems.  [[PUI]] doesn't have every feature under the sun, but it was never meant to.  It's relatively small, lean, mean, and written on top of [[OpenGL]] which makes life *much* easier for us.&lt;br /&gt;
&lt;br /&gt;
[[PUI]] already is a separate distinct library within Plib.  It depends on some central utility stuff, but that's about it.  So it is pretty stripped down and separate already.&amp;lt;ref&amp;gt;https://www.mail-archive.com/flightgear-devel@lists.sourceforge.net/msg26011.html&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We are to some extent hamstrung by the rather old GUI toolkit we use. However, replacing that is going to be non-trivial, and it would affect not just the core GUI but also all the dialog boxes that have been set up for particular aircraft.&amp;lt;ref&amp;gt;https://www.mail-archive.com/flightgear-devel@lists.sourceforge.net/msg25985.html&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Flightgear can look amazing when you use the HDR pipeline. We absolutely need this to stay relevant. The problem is that HDR is not compatible with OpenGL1 PUI. Work on getting rid of PUI has been going on for over a decade (longer than it took to implement it).&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37727346/&amp;lt;/ref&amp;gt; &lt;br /&gt;
&lt;br /&gt;
[[File:menubar2.jpg|517px]]&lt;br /&gt;
&lt;br /&gt;
== Replacement status ==&lt;br /&gt;
{{See also|FlightGear_and_OpenGL_ES#Example:_disabling_PUI}}&lt;br /&gt;
&lt;br /&gt;
{{Note| Update 09/2022: The PUI replacement [[QtQuick_use_in_FlightGear#Background|was going to be Qt]] but it started to get very complicated with changes in Qt 5.15 + Qt 6, so James is going with a more light-weight Canvas based approach now.&lt;br /&gt;
&lt;br /&gt;
Qt added support for Vulkan / Metal / D3D starting in 5.15, but FlightGear / OpenSceneGraph can’t support those, so integrating the two renderers went from being ‘complicated but ok’  to ‘very very complicated’. &lt;br /&gt;
&lt;br /&gt;
So now James is going with something much more lightweight using some C++ compatibility code, some Nasal for styling and the existing [[Canvas widgets|Canvas widget rendering]] from Thomas Geymayer (TheTom) with [[Canvas Widget Matrix|some extensions and additions]], based on the [[Howto:Processing_legacy_PUI_dialogs_using_Canvas#Original_Discussion|plans originally discussed when the Canvas GUI system was added to FlightGear]]: some pieces are in FlightGear &amp;amp; FGData already. &lt;br /&gt;
&lt;br /&gt;
James has basic dialogs working okay but not the more complex ones and everything looks kind of ugly, he needs to improve the visual look before he shares screenshots to avoid everyone freaking out :) The disadvantage of this approach is James is far from expert at creating visual appearances this way, so it’s kind on unrewarding and slow for him. If someone likes messing with CSS-type styling, border-images and hover-states, ping him since we could probably move things also faster &amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37701750/&amp;lt;/ref&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Motivation ===&lt;br /&gt;
{{Main article|Core Profile support}}&lt;br /&gt;
&lt;br /&gt;
The bigger issue here is we need to ditch [[PUI]] (which is in progress) and some OpenGL 1.0 code (HUD, 2D panels especially - can be #ifdef for now) so we can enable Core profile on Mac - since Mac 4.x support (we only hit about 4.3 alas, but with some extensions to get in sight of 4.5) is Core profile only, no Compatibility mode.&lt;br /&gt;
&lt;br /&gt;
Improving the frame-rate and modernised 3D rendering, can’t be worked on until the PUI code, 2D panels and Shiva are removed, but doing so is a frustrating slow path&amp;lt;ref&amp;gt;{{cite web&lt;br /&gt;
  |url    =  https://sourceforge.net/p/flightgear/mailman/message/35623408/ &lt;br /&gt;
  |title  =  &amp;lt;nowiki&amp;gt; Re: [Flightgear-devel] canvas non svg-elements broken &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |author =  &amp;lt;nowiki&amp;gt; James Turner &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |date   =  Jan 24th, 2017 &lt;br /&gt;
  |added  =  Jan 24th, 2017 &lt;br /&gt;
  |script_version = 0.40 &lt;br /&gt;
  }}&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Given that with many graphics drivers PUI doesn't render correctly when higher shader quality is on, many people are convinced PUI needs to be replaced.&amp;lt;ref&amp;gt;{{cite web&lt;br /&gt;
  |url    =  https://sourceforge.net/p/flightgear/mailman/message/35624918/ &lt;br /&gt;
  |title  =  &amp;lt;nowiki&amp;gt; Re: [Flightgear-devel] canvas non svg-elements broken &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |author =  &amp;lt;nowiki&amp;gt; Thorsten Renk &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |date   =  Jan 25th, 2017 &lt;br /&gt;
  |added  =  Jan 25th, 2017 &lt;br /&gt;
  |script_version = 0.40 &lt;br /&gt;
  }}&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Canvas Emulation ==&lt;br /&gt;
{{See also|Canvas widget matrix}}&lt;br /&gt;
'''Last updated: 04/2023 &amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37805054/&amp;lt;/ref&amp;gt;&lt;br /&gt;
'''&lt;br /&gt;
=== Gallery ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery&amp;gt;&lt;br /&gt;
About-dialog-xmldialog-04-2023.png|about.xml PUI/XML dialog parsed/rendered via the [[Canvas GUI]] and the underlying XMLDialog.nas parser/translator (04-2023)&lt;br /&gt;
Logging-dialog-via-xmldialog-04-2023.png|logging.xml PUI/XML dialog parsed/rendered via the [[Canvas GUI]] and the underlying XMLDialog.nas parser/translator (04-2023)&lt;br /&gt;
Failures-dialog-via-xmldialog-04-2023.png|failures dialog-via-xmldialog-04-2023.png{{!}}thumb{{!}}logging.xml PUI/XML dialog parsed/rendered via the [[Canvas GUI]] and the underlying XMLDialog.nas parser/translator (04-2023)&lt;br /&gt;
Environment-dlg-via-canvas-bridge-04-2023.png|Screen shot showing the XMLDialog.nas bridge in use to parse/render the environment.xml PUI/XML dialog (unmodified)&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Status ===&lt;br /&gt;
As of 01/2023, the replacement for PUI is making good progress&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37765706/&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
In February 2022, James reported having something in progress locally, and that should even be something we can try in March/April 2022. Hopefully that’s quick enough, knowing it has been a long time coming.&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37606439/&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
There's now an XML to Nasal bridge, to keep PUI dialogs working (Disabled by a CMake option). This is implemented on top of the [[Canvas]] system.&lt;br /&gt;
&lt;br /&gt;
This builds equivalent C++ objects to what the [[PUI]] dialogs build, with properties exposed to Nasal. Peer objects are created by Nasal [[callbacks]], which can implement the various dialog functions needed to keep compatibility, especially the [[#Background|‘update’ and ‘apply’ hooks]].&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/flightgear/ci/cbd5ef9e7b8f433dbc1ba97bae7d7cfc894e7cef/&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For remaining work to be done, please refer to [https://sourceforge.net/p/flightgear/mailman/message/37881226/]&lt;br /&gt;
&lt;br /&gt;
{{Note|The PUI Compatible Canvas GUI is a compile time option: Use the following [[Building using CMake|cmake option]] to enable this code: {{CMake Build Option|option=ENABLE_PUICOMPAT|default=ON|description=This requires latest SG/FG AND FGDATA}}. But this is all highly fluid and most of it really developer only. At one point they will become default or be accessible in the launcher.&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37834004/&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you compiled with PUICompatibility enabled, but still don't see any dialogs - only the menubar, you might need to enable the GUI module in defaults.xml:{{PropArg}}&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37833149/&amp;lt;/ref&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
The new Compatibility files can now be found in $FG_SRC/GUI:&lt;br /&gt;
&lt;br /&gt;
* {{flightgear file|src/GUI/FGPUICompatDialog.cxx }}&lt;br /&gt;
* {{flightgear file|src/GUI/FGPUICompatDialog.hxx }}&lt;br /&gt;
* {{flightgear file|src/GUI/PUICompatObject.cxx  }}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;s&amp;gt;01/2023: James is slowly working on creating the pop-up menu and combo-box widgets [...]&amp;lt;/s&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&amp;lt;s&amp;gt;One of the major things slowing down James' work on the popup-menu and combo box is time to restart the simulator for each change, he is looking for some solution to re-load the widget Nasal code independently. &amp;lt;ref&amp;gt;&amp;lt;https://sourceforge.net/p/flightgear/mailman/message/37756183/&amp;lt;/ref&amp;gt;&amp;lt;/s&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;s&amp;gt;James has done about 75% (hah!) of the C++ work to enable live reloading of modules this way, but unfortunately there are some code paths that would become crashy if you use the feature for the Canvas: because it would reference the ‘old’ (pre-reload) Nasal code, and not the new one, and therefore you’d get a weird mix of old and new, and then just crash the sim. To fix it properly I need to track down those places that store a reference to Canvas and give them a re-init method, so we don’t keep the stale references.&amp;lt;/s&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;s&amp;gt;The general non-fun-ness of that kind of debugging is James has been making such slow progress on my PUI replacement widgets recently &amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37756263/&amp;lt;/ref&amp;gt;&amp;lt;/s&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Popup menus and combo box widgets are working now - also, the simulator doesn't have to be restarted anymore - any Nasal module (including the canvas module can now be reloaded live:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;fgcommand(&amp;quot;nasal-reload&amp;quot;, {&amp;quot;module&amp;quot;: &amp;quot;canvas&amp;quot;})&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Todo ===&lt;br /&gt;
&amp;lt;s&amp;gt;Nothing in particular: help debugging the grid layout (which is in simgear) would be good. For now, you could create a grid layout manually using some Nasal.&amp;lt;/s&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The grid layout is now mostly working (from my experience) but it behaves weirdly when there isn't enough place to accommodate all widgets.&lt;br /&gt;
&lt;br /&gt;
Global menu keybindings won’t worry but don’t touch that, I have a separate plan for it.&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37805736/&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As of 04/2023, remaining items on the todo list are: &amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37805060/&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Grid layout&lt;br /&gt;
** GridLayout needs debugged: After this, the basic UI is usable, would probably make it a switchable option on ‘next’ (I can already use the simulator with the new UI today, but all the sizes are off in the grid layouts which we use everywhere)&lt;br /&gt;
&lt;br /&gt;
*  list {{Done}}&lt;br /&gt;
*  table (and tree ?) models corresponding views&lt;br /&gt;
* waypoint list&lt;br /&gt;
* Re-write the waypoint list and airport dialog to work again (since these use custom widgets):&lt;br /&gt;
** log list &lt;br /&gt;
** airport-list equivalents&lt;br /&gt;
*  Nasal access to the menu i18n functions&lt;br /&gt;
*  selection support for single- and multi-line text (which is being worked on -&amp;gt; CanvasPangoText){{Note|For the LineEdit widget, a hack has been implemented to provide more-or-less working selection highlighting while the PangoText element isn't done.}}&lt;br /&gt;
* Keyboard shortcut handling, although this is not really a PUI vs replacement issue, to me we should handle UI shortcuts in the same was normal keyboard.xml shortcuts&lt;br /&gt;
** this previously worked because PUI got ‘first chance’ at handling key input. But we always had duplication between keyboard.xml and other places, so I would rather standardise on keyboard.xml, but allow a UI element to set a scope for shortcuts. (So that Copy/paste shortcuts etc can work in a text input, at least … but probably some other standard ones) &amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37805188/&amp;lt;/ref&amp;gt;&lt;br /&gt;
* Ensure all the places which use the PUI combo-box and list-widget work : there’s a few of these.&lt;br /&gt;
&lt;br /&gt;
The CanvasPangoText stuff is needed but we can perfectly well run the UI on next without it.&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37805176/&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Parser ===&lt;br /&gt;
{{Main article|pui2canvas}}&lt;br /&gt;
The corresponding Nasal/Canvas module to dynamically &amp;quot;translate&amp;quot; legacy PUI/XML dialogs into Canvas dialogs (at runtime), is to be found in {{fgdata file|Nasal/gui/XMLDialog.nas}} &amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/fgdata/ci/fe7c87b21a69f88ddb87d89453c48d12b69660e2/&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Widgets ===&lt;br /&gt;
{{Main article|Howto:Creating a Canvas GUI Widget}}&lt;br /&gt;
&lt;br /&gt;
It’s split between Simgear (see classes with widget / layout in the name) and in FGData. (Eg widgets/Button.nas). To be able to use the existing dialog XML files un-modified (which is a design goal), James is extending the widget types with many additional ones (eg PUI has slider, dial, combo-box, checkbox, all of which need to be created, see [[Canvas widget matrix]].&lt;br /&gt;
&lt;br /&gt;
Thomas’s canvas widgets have a very good separation of API + state from appearance, so all styling is in its own file, and James is being very strict about maintaining this separation, so we also retain the re-styling feature of the PUI UI, which many people also rely on. This does make the process of adding new widgets more complex, however.&lt;br /&gt;
&lt;br /&gt;
{{See also|Canvas widgets}}&lt;br /&gt;
&lt;br /&gt;
=== Layouting ===&lt;br /&gt;
The other thing is to preserve all the layouting: James has added a grid layout to Simgear, since that is supported by the existing PUI code (even though the layouts are not actually part of PUI itself). The problem is getting the sizing / hinting of all the widgets to match the PUI values, so that dialogs look approximately the same under the new UI as they did with PUI; again this a design goal so that all existing dialogs in aircraft and add-ons, which we can’t update, continue to work and be usable. Debugging that is also proving quite tricky, since there’s all kinds of hard-coded assumptions built into PUI widgets about pixels, font-sizes etc which are not true in the new system.&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37701792/&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Background ==&lt;br /&gt;
In FlightGear, PUI dialogs are standard [[PropertyList XML File]]s that are stored in $FG_ROOT/gui/dialogs, they can contain the widgets mentioned in {{readme file|gui}}, using a simple layout engine discussed in {{readme file|layout}}, and bindings using a combination of so called fgcommands operating on properties (see {{readme file|commands}}) and custom [[Nasal]] (FlightGear scripting) code. &lt;br /&gt;
&lt;br /&gt;
In addition, each PUI/XML dialog may contain Nasal script sections that are executed when opening/closing the dialog, a feature which is commonly used for procedurally creating/updating widgets using the [[Nasal_library#cmdarg.28.29|cmdarg() API]], which allows the dialog tree to be traversed and manipulated prior to the dialog being rendered.&lt;br /&gt;
Widgets can be conditionally hidden/shown using a wrapper for [[Conditions|SGCondition]] in [[Nasal_library/props|props.nas]] &lt;br /&gt;
The &amp;lt;code&amp;gt;canvas&amp;lt;/code&amp;gt; widget also supports its own embedded Nasal code section to execute arbitrary widget specific Nasal code upon opening/closing the dialog/widget.&lt;br /&gt;
&lt;br /&gt;
PUI/XML dialogs can be loaded, dynamically created, updated and closed using a handful of fgcommands:&lt;br /&gt;
* {{fgcommand|dialog-new}}&lt;br /&gt;
* {{fgcommand|dialog-show}}&lt;br /&gt;
* {{fgcommand|dialog-update}}&lt;br /&gt;
* {{fgcommand|dialog-apply}}&lt;br /&gt;
* {{fgcommand|dialog-close}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
PUI related OpenGL code is particularly infamous for causing rendering artifacts for people on AMD/ATI and Intel hardware (especially in combination with certain fonts/styles and [[Effects|effects]]/[[Shaders|shaders]]), see {{Issue|2213}}.&lt;br /&gt;
&lt;br /&gt;
PUI is also known to affect rendering performance quite significantly (see {{search |keywords=anthrax+gui}}), while also preventing FlightGear from using a more recent version of OpenGL&amp;lt;ref&amp;gt;http://sourceforge.net/p/flightgear/mailman/message/34532040/&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;http://forum.flightgear.org/viewtopic.php?f=71&amp;amp;t=24046&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
However, improving the frame-rate and modernised 3D rendering,  can’t be worked on until the PUI code, 2D panels and Shiva are removed, but doing so is a frustrating slow path&amp;lt;ref&amp;gt;{{cite web&lt;br /&gt;
  |url    =  https://sourceforge.net/p/flightgear/mailman/message/35623408/ &lt;br /&gt;
  |title  =  &amp;lt;nowiki&amp;gt; Re: [Flightgear-devel] canvas non svg-elements broken &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |author =  &amp;lt;nowiki&amp;gt; James Turner &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |date   =  Jan 24th, 2017 &lt;br /&gt;
  |added  =  Jan 24th, 2017 &lt;br /&gt;
  |script_version = 0.40 &lt;br /&gt;
  }}&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Besides, while most people seem to agree PUI needs to be replaced, it sounds as if the fallout from doing so would be more painful (cumulatively) than the pain its existence causes.&amp;lt;ref&amp;gt;{{cite web&lt;br /&gt;
  |url    =  https://sourceforge.net/p/flightgear/mailman/message/35623408/ &lt;br /&gt;
  |title  =  &amp;lt;nowiki&amp;gt; Re: [Flightgear-devel] canvas non svg-elements broken &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |author =  &amp;lt;nowiki&amp;gt; James Turner &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |date   =  Jan 24th, 2017 &lt;br /&gt;
  |added  =  Jan 24th, 2017 &lt;br /&gt;
  |script_version = 0.40 &lt;br /&gt;
  }}&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
But given that with many graphics drivers PUI doesn't render correctly when higher shader quality is on, graphics folks are also convinced it needs to be replaced.&amp;lt;ref&amp;gt;{{cite web&lt;br /&gt;
  |url    =  https://sourceforge.net/p/flightgear/mailman/message/35624918/ &lt;br /&gt;
  |title  =  &amp;lt;nowiki&amp;gt; Re: [Flightgear-devel] canvas non svg-elements broken &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |author =  &amp;lt;nowiki&amp;gt; Thorsten Renk &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |date   =  Jan 25th, 2017 &lt;br /&gt;
  |added  =  Jan 25th, 2017 &lt;br /&gt;
  |script_version = 0.40 &lt;br /&gt;
  }}&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
In addition, [[OpenSceneGraph]] (OSG) can obviously not help optimize any PUI related GL code and PUI widgets are generally considered to be pretty archaic and not easy to extend&amp;lt;ref&amp;gt;http://sourceforge.net/p/flightgear/mailman/message/26832164/&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;http://sourceforge.net/p/flightgear/mailman/message/10587120/&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;http://sourceforge.net/p/flightgear/mailman/message/10587272/&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
{{Note|We use the GUI code from PLIB, which doesn't know anything about OSG. See the SGPuDrawable class in $FG_SRC/Viewer/renderer.cxx for the implementation. The one catch is that OSG has a strong notion of separation between the update of a &amp;quot;scene&amp;quot; and its rendering, and that might not play well with arbitrary existing OpenGL code. }}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
As of late 2015, there is heavy activity towards providing alternatives to a PUI-based UI:&lt;br /&gt;
* [[Integrated Web GUI]] (external, browser-based - fully asynchronous)&lt;br /&gt;
* [[Howto:Processing legacy PUI dialogs using Canvas]] (internal, using the [[Canvas]] system and a simple [[Nasal]] parser to deal with existing PUI/XML dialogs)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;gallery mode=&amp;quot;packed&amp;quot;&amp;gt;&lt;br /&gt;
Early-Phi-screen shot.PNG|Screenshot showing [[Phi]]&lt;br /&gt;
About-dialog-rendered-by-canvas.png|Screenshot showing a dialog rendered by the [[Howto:Processing legacy PUI dialogs using Canvas|pui2canvas parser]]&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== History ==&lt;br /&gt;
{{See also|QtQuick use in FlightGear}}&lt;br /&gt;
{{Caution|This is merely kept for future reference, many of the ideas discussed below have become obsolete as of 09/2022, if in doubt, please get in touch via devel-list.}}&amp;lt;small&amp;gt;&lt;br /&gt;
=== 2021 ===&lt;br /&gt;
In early 2021, James did a brief evaluation of [https://github.com/ocornut/imgui ImGUI] as a possibility - he's also evaluating it in some projects at his day job. &lt;br /&gt;
&lt;br /&gt;
The API is interesting if you’re starting from scratch and only exclusively in C++, but it’s not a great fit for how we define GUIs in FG (via [[XML]]/[[Nasal]]): there is no back door that he could find, to access the persistent state of the GUI, or build it up in a data-driven way. So making a mapping to keep our existing GUI XML working (and updating correctly) becomes a bit of a chore.&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37295586/&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pretty much all of the FlightGear GUI is defined in XML space, except for a few oddments. Let’s say 95% at this point.&lt;br /&gt;
&lt;br /&gt;
Additionally, even if we were to change that for the core sim (and James' current plan is to keep the XML syntax and just extend it), the installed base of '''aircraft''' ship their own XML dialogs (eg., replacing the autopilot dialog with a custom one, or just adding their own aircraft-specific helper controls for doors / lights / cargo / whatever). So even if we found the most sublimely beautiful, intuitive, compact GUI description language in the world, we have to keep the XML syntax working as-is, for the foreseeable future.&lt;br /&gt;
&lt;br /&gt;
Given this, James' intention is to replace the output side [[PUI]] but keep the front-end side (XML / properties / bindings) unchanged, and therefore backwards compatible, and simply add some new types / options / widget types going forward. This will mean we still have to deal with the slightly cumbersome XML+Nasal syntax, but, well, it’s not *that* cumbersome. &amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37296280/&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In mid 2021, James reported that he is working on some ‘PUICompat’ classes which hold/own/represent the GUI state in C++, but which can be used with [[Nasal/CppBind|Nasal CppBind]]. And of course we could indeed expose those later on. &amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37297727/&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In July 2021, James suggested not to worry about the existing GUI code, it’s going in the bin.&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37325905/&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
For the time being, the in-sim GUi can’t do Unicode yet, but James is working on it, hopefully available soon.&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37328200/&amp;lt;/ref&amp;gt;&lt;br /&gt;
Also, the in-sim menubar will change with the new GUI, *and* we use a native menubar on macOS. Until James replaces the PUI fonts we can’t use Unicode symbols. So, will have to come back to it once the new GUI is merged.&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37327237/&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 2020 ===&lt;br /&gt;
As of 03/2020, the PLIB replacement was reported to be in the backlog &amp;lt;ref&amp;gt;{{cite web&lt;br /&gt;
  |url    =  https://sourceforge.net/p/flightgear/mailman/message/36951255/ &lt;br /&gt;
  |title  =  &amp;lt;nowiki&amp;gt;Re: [Flightgear-devel] C++ and/or OSG question &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |author =  &amp;lt;nowiki&amp;gt; Scott Giese &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |date   =  Mar 18th, 2020 &lt;br /&gt;
  |added  =  Mar 18th, 2020 &lt;br /&gt;
  |script_version = 0.50 &lt;br /&gt;
  }}&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
With PLIB replacement work likely being re-scheduled for [[Post FlightGear 2020.2 LTS changes]] &amp;lt;ref&amp;gt;{{cite web&lt;br /&gt;
  |url    =  https://sourceforge.net/p/flightgear/mailman/message/36985375/ &lt;br /&gt;
  |title  =  &amp;lt;nowiki&amp;gt;Re: [Flightgear-devel] Working Group Proposal - Boost and PLIB replacement - Delegation and Teamwork &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |author =  &amp;lt;nowiki&amp;gt; James Turner &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |date   =  April 18th, 2020 &lt;br /&gt;
  |added  =  April 18th, 2020 &lt;br /&gt;
  |script_version = 1.10 &lt;br /&gt;
  }}&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
In 08/2020, James stated that he was still working (exceptionally slowly!) on replacing PUI (as part of removing legacy OpenGL code (PUI, HUD, 2d panels, porting Canvas away form ShivaVG), so we can switch to Vulkan/VSG at some point (ideally at some point before Apple turn off OpenGL support…)), and that he doesn't think /any/ of us *want* to be working on these features particularly, but projects like these need to be done before anyone can have fun with VSG or Vulkan (personally James would be very enthusiastic about working on VSG support (being on macOS, I also have the most to lose / gain from it)). Sometimes that’s just how it goes. Equally they will all benefit the project ultimately, but the payoff both for the individual and the project is very drawn out. &amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37077158/&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In 10/2020, the estimation is that PUI should be going away really ‘soon’, the replacement code is already in next / 2020.2. (There’s no plan to actually /use/ the new UI code in 2020.2, but if the bugs in it prove simple, we could actually turn it for specific dialog: it seems very unlikely this makes sense for a stable release however)&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37123818/&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The fixed-function removal, is actively in hand (Gaetan is working on the 2D panel part, and James is going to focus on PUI in the rest of 2020). James hopes that means next year he can focus on Vulkan, or at least, using OSG as if /were/ VSG, so the migration in a year or two when VSG is stable, is not so painful.&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37129098/&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The PUI UI and replacement can co-exist in the same build (they already do, effectively.&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37131297/&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In 11/2020, James suggested not to worry about details such as PUI styling/theming etc: we’re not making such changes for the LTS, and PUI should be gone before the next release.&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37142225/&amp;lt;/ref&amp;gt; Also, he recommended (to everyone) to stop touching GUI stuff for a few weeks, as he's about to turn it all on its head.&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37142356/&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 2019 ===&lt;br /&gt;
The new GUI has been incubating for a few years, but there's apparently been a lot of progress as of 10/2019.&lt;br /&gt;
&lt;br /&gt;
It is likely the new GUI will be a user opt-in feature - at least initially. The sunset of the PLIB library _could_ happen relatively soon, but &lt;br /&gt;
people will need to see the new GUI and react to it before we could commit to PLIB removal &amp;lt;ref&amp;gt;{{cite web&lt;br /&gt;
  |url    =  https://sourceforge.net/p/flightgear/mailman/message/36784489/ &lt;br /&gt;
  |title  =  &amp;lt;nowiki&amp;gt;Re: [Flightgear-devel] in Flightgear/Simgear C++ Code: What needs Doing? What are YOU Doing? What would you do if you had the time? &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |author =  &amp;lt;nowiki&amp;gt; Scott Giese &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |date   =  Oct 14th, 2019&lt;br /&gt;
  |added  =  Oct 14th, 2019 &lt;br /&gt;
  |script_version = 0.50 &lt;br /&gt;
  }}&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
In 02/2019, James stated that the new UI is coming quite soon. &amp;lt;ref&amp;gt;{{cite web&lt;br /&gt;
  |url    =  https://sourceforge.net/p/flightgear/mailman/message/36589420/&lt;br /&gt;
  |title  =  &amp;lt;nowiki&amp;gt; Re: [Flightgear-devel] Lagging &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |author =  &amp;lt;nowiki&amp;gt; James Turner &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |date   =  Feb 17th, 2019 &lt;br /&gt;
  |added  =  Feb 17th, 2019&lt;br /&gt;
  |script_version = 0.40 &lt;br /&gt;
  }}&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 2018 ===&lt;br /&gt;
{{See also|QtQuick use in FlightGear}}&lt;br /&gt;
&lt;br /&gt;
According to James, we have to accept that changes like testing or making [[PUI]] a modular thing or moving the JS code tend to have an immediate pain for some people (because strange stuff gets broken, and takes some time to get fixed), whereas the payoff in terms of improved joystick handling or replacing the UI or testing of all subsystems will take some months or years to be felt.&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/36323240/&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
James said he was in the middle of porting the launcher’s final tab (location) to the new UI scheme, once that’s done he will start sketching out the ‘in sim’ settings UI using the same pieces. He’ll send a request for contributions around about that in the next three-four weeks, once he has enough templates &amp;amp; examples that there is a clear pattern to follow.&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/flightgear-devel/thread/2d10f16c-ddc8-07f3-07e7-eca2a0a0d55f%40zaretto.com/#msg36293586&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
James announced that the launcher is now feature complete with the QtQuick UI, he’ll keep making bug-fixes of course as people report them, but my todo list for the core features is now done&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/36355833/&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== 2017 ===&lt;br /&gt;
&lt;br /&gt;
In 10/2017, James said he was getting really close to having the PUI replacement UI suitable for beta-testing.&amp;lt;ref&amp;gt;{{cite web&lt;br /&gt;
  |url    =  https://sourceforge.net/p/flightgear/mailman/message/36067646/ &lt;br /&gt;
  |title  =  &amp;lt;nowiki&amp;gt; Re: [Flightgear-devel] PLIB features in plib svn but not in any&lt;br /&gt;
 release &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |author =  &amp;lt;nowiki&amp;gt; James Turner &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |date   =  Oct 7th, 2017 &lt;br /&gt;
  |added  =  Oct 7th, 2017 &lt;br /&gt;
  |script_version = 0.40 &lt;br /&gt;
  }}&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Originally, James was hoping to land the PUI replacement GUI in the last dev cycle in 2017 (at least as a proof-of-concept, probably not as the default UI), so wouldn’t expend lots of effort on things like collapsible sections which might be a lot of work with the current PUI/Canvas approaches, but are trivial with the new [[QtQuick use in FlightGear|QtQuick based UI scheme]].&amp;lt;ref&amp;gt;{{cite web&lt;br /&gt;
  |url    =  https://sourceforge.net/p/flightgear/mailman/message/36059822/ &lt;br /&gt;
  |title  =  &amp;lt;nowiki&amp;gt; Re: [Flightgear-devel] Canvas MapLayer for OpenStreetMap, OpenAIP,&lt;br /&gt;
 VFR Sectionals &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |author =  &amp;lt;nowiki&amp;gt; James Turner &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |date   =  Oct 1st, 2017 &lt;br /&gt;
  |added  =  Oct 1st, 2017 &lt;br /&gt;
  |script_version = 0.40 &lt;br /&gt;
  }}&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Once the basic new UI is in place we can experiment with different re-arrangements easily, without being limited by PUI. (James expects PUI to live on as the default / alternate UI while this happens)&amp;lt;ref&amp;gt;{{cite web&lt;br /&gt;
  |url    =  https://sourceforge.net/p/flightgear/mailman/message/36061229/ &lt;br /&gt;
  |title  =  &amp;lt;nowiki&amp;gt; Re: [Flightgear-devel] FGFS macOS Menubar &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |author =  &amp;lt;nowiki&amp;gt; James Turner &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |date   =  Oct 2nd, 2017 &lt;br /&gt;
  |added  =  Oct 2nd, 2017 &lt;br /&gt;
  |script_version = 0.40 &lt;br /&gt;
  }}&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Technically, this entails replacing dialog.cxx with some code which builds up some special ‘PUI-Emulation’ QtQuick controls, so that existing dialogs (especially from Aircraft) continue to work. This mode will look somewhat ugly but hopefully no more ugly than PUI! And it helps that the set of widgets we have in PUI is limited, and the ‘tricky’ widgets (map, scrolling list, etc) are in the dialogs will will replace with new ones first.&amp;lt;ref&amp;gt;{{cite web&lt;br /&gt;
  |url    =  https://sourceforge.net/p/flightgear/mailman/message/36068767/ &lt;br /&gt;
  |title  =  &amp;lt;nowiki&amp;gt; Re: [Flightgear-devel] PLIB features in plib svn but not in any&lt;br /&gt;
 release &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |author =  &amp;lt;nowiki&amp;gt; James Turner &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |date   =  Oct 8th, 2017 &lt;br /&gt;
  |added  =  Oct 8th, 2017 &lt;br /&gt;
  |script_version = 0.40 &lt;br /&gt;
  }}&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This PUI emulation isn't going to use any code from PLIB&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/36985303/&amp;lt;/ref&amp;gt;, so what will remain from it in FG afterwards will be the FNT and JS components (reworked).&amp;lt;ref&amp;gt;{{cite web&lt;br /&gt;
  |url    =  https://sourceforge.net/p/flightgear/mailman/message/36068817/ &lt;br /&gt;
  |title  =  &amp;lt;nowiki&amp;gt; Re: [Flightgear-devel] PLIB features in plib svn but not in any&lt;br /&gt;
 release &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |author =  &amp;lt;nowiki&amp;gt; Florent Rougon &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |date   =  Oct 8th, 2017 &lt;br /&gt;
  |added  =  Oct 8th, 2017 &lt;br /&gt;
  |script_version = 0.40 &lt;br /&gt;
  }}&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 2016 ===&lt;br /&gt;
We need to explore if Qt Quick or widgets is the better way to go, with a preference towards Qt Quick, since it will allow things closer to current PUI (semi-transparent widgets on top of the 3D content). It still allows creating separate windows too (Erik’s use case), but means we get consistent, custom theming on all platforms, since standard desktop look is not so nice for a flight-sim.&lt;br /&gt;
&lt;br /&gt;
James intention is to make a QML-hosting OSG-drawable, and add this as a concept in Qt-enabled builds, then port some PUI dialogs to it, and see how easy / painful the process is. Hell create some QObjects which expose commands and the property tree and Nasal.&lt;br /&gt;
&lt;br /&gt;
This has the advantage of not touching the OSG window at all, QtQuick simply provides some additional OpenGL rendering on top, which we pass events to - pretty much exactly the same as what we already do for PUI, and hopefully can co-exist with it. James got this working in a local OSG tree in a standalone demo, but that’s based on osgQt - he wants to see if he can make it work on a normal, non-Qt OSG window inside FlightGear.&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/35156051/&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The idea is to host an XML dialog (Currently done with PUI) from Qt also. The question is if that can be done keeping ‘source compatibility’ exactly with the current UI XML and Nasal interface. It probably can be, but many of the PUI widgets have really ugly APIs, especially for combo-boxes, drop-down menus and scrolling lists, where it makes sense to map a Nasal class to a Qt ItemModel, which would be more elegant and easy to work with in both Nasal and integrate on the Qt side. But obviously would mean changing the dialog sources.&lt;br /&gt;
&lt;br /&gt;
Of course, aircraft dialogs tend to be simpler, and not use the more complex PUI widgets, precisely for reasons like this, but that’s a limitation it would be best to remove.&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/35155721/&amp;lt;/ref&amp;gt;&lt;br /&gt;
&amp;lt;/small&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== References ==&lt;br /&gt;
{{Appendix}}&lt;br /&gt;
&lt;br /&gt;
[[Category:PUI| ]]&lt;br /&gt;
[[Category:Rendering roadblocks]]&lt;/div&gt;</summary>
		<author><name>TheEagle</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Nasal_library&amp;diff=139441</id>
		<title>Nasal library</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Nasal_library&amp;diff=139441"/>
		<updated>2024-03-17T08:11:23Z</updated>

		<summary type="html">&lt;p&gt;TheEagle: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Nasal Navigation|nocat=1}}&lt;br /&gt;
This page documents the global '''library functions and variables''' of FlightGear's built-in scripting language, [[Nasal]]. This includes ''[[#Core library functions|core library functions]]'', which were included in Nasal before its integration into FlightGear, the ''[[#Extension functions|extension functions]]'', which have been subsequently added, and are specifically designed for FlightGear, and the ''[[#variables|global variables]]'', which are conversion variables, added with extension functions, for converting between units. The relevant folders in [[Git]] are:&lt;br /&gt;
* {{flightgear file|src/Scripting}}&lt;br /&gt;
* {{simgear file|simgear/nasal}}&lt;br /&gt;
&lt;br /&gt;
All these functions and variables are in the global namespace, that is, they are directly accessible (e.g., one can call &amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; inline&amp;gt;magvar()&amp;lt;/syntaxhighlight&amp;gt; instead of &amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; inline&amp;gt;namespace.magvar()&amp;lt;/syntaxhighlight&amp;gt;). However, if a namespace must be used, &amp;lt;code&amp;gt;globals&amp;lt;/code&amp;gt; is the correct namespace, but using it is not recommended. For a more complete explanation, see [[Nasal Namespaces in-depth]].&lt;br /&gt;
&lt;br /&gt;
{{tip|Copy &amp;amp; paste the examples into your [[Nasal Console]] and execute them to see what they do.|width=70%}}&lt;br /&gt;
&lt;br /&gt;
== Core library functions ==&lt;br /&gt;
This is the list of the basic '''core library functions.''' Most of these functions were part of the original Nasal library (before its integration in to FlightGear), while some have been added or changed over time.  See also:&lt;br /&gt;
* http://plausible.org/nasal/lib.html ([http://web.archive.org/web/20101010094553/http://plausible.org/nasal/lib.html archive])&lt;br /&gt;
* {{simgear file|simgear/nasal/lib.c}} ([http://sourceforge.net/p/flightgear/simgear/ci/next/log/?path=/simgear/nasal/lib.c history])&lt;br /&gt;
&lt;br /&gt;
=== append() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = append(vector, element[, element[, ...]]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=42|t=Source}}&lt;br /&gt;
|text = This function appends, or adds, the given element(s) to the end of the vector given in the first argument.  Returns the vector operated on.&lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector to which the arguments will be appended.&lt;br /&gt;
|param2 = element&lt;br /&gt;
|param2text = An element to be added to the vector.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize the vector&lt;br /&gt;
append(vector, 4); # Append the number 4 to the end of the vector&lt;br /&gt;
debug.dump(vector); # Print the contents of the vector&lt;br /&gt;
|example2 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize the vector&lt;br /&gt;
append(vector, 4, 5, 6); # Append the numbers 4, 5, and 6 to the end of the vector&lt;br /&gt;
debug.dump(vector); # Print the contents of the vector&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== bind() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = bind(function, locals[, outer_scope]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=502|t=Source}}&lt;br /&gt;
|text = This creates a new function object. A function in Nasal is three things: the actual code, a hash/namespace of local variables available to the function namespace, and the closure object of that namespace. These correspond to the three arguments respectively.&lt;br /&gt;
|param1 = function&lt;br /&gt;
|param1text = Function to evaluate.&lt;br /&gt;
|param2 = locals&lt;br /&gt;
|param2text = Hash containing values that will become the namespace (first closure) for the function.&lt;br /&gt;
|param3 = outer_scope&lt;br /&gt;
|param3text = Optional function which is bound to the next closure. This can be bound to yet another, making a linked list.&lt;br /&gt;
|example1 = # This is a namespace/hash with a single member, named &amp;quot;key,&amp;quot; which is initialized to 12 &lt;br /&gt;
var Namespace = {&lt;br /&gt;
    key: 12&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
# This is different namespace/hash containing a function&lt;br /&gt;
# dividing a variable &amp;quot;key&amp;quot; (which is unavailable/nil in this namespace) by 2&lt;br /&gt;
var AnotherNamespace = {&lt;br /&gt;
    ret: func {&lt;br /&gt;
        key /= 2;&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
# To see that key is not available, try to call AnotherNamespace.ret() first&lt;br /&gt;
call(AnotherNamespace.ret, [], nil, nil, var errors = []);&lt;br /&gt;
if(size(errors)){&lt;br /&gt;
    print(&amp;quot;Key could not be divided/resolved!&amp;quot;);&lt;br /&gt;
    debug.printerror(errors);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
# Associate the AnotherNamespace.ret() function with the first namespace&lt;br /&gt;
# so that &amp;quot;key&amp;quot; is now available&lt;br /&gt;
var function = bind(AnotherNamespace.ret, Namespace);&lt;br /&gt;
&lt;br /&gt;
# Invoke the new function&lt;br /&gt;
function();&lt;br /&gt;
&lt;br /&gt;
# Print out the value of Namespace.key&lt;br /&gt;
# It was changed to 12 from 6 by AnotherNamespace.ret()&lt;br /&gt;
print(Namespace.key);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== call() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = call(func[, args[, me[, locals[, error]]]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=247|t=Source}}&lt;br /&gt;
|text = Calls the given function with the given arguments and returns the result.  This function is very useful as it allows much more control over function calls and catches any errors or {{func link|die()}} calls that would normally trigger run-time errors cancelling execution of the script otherwise. &lt;br /&gt;
|param1 = func&lt;br /&gt;
|param1text = Function to execute.&lt;br /&gt;
|param2 = args&lt;br /&gt;
|param2text = Vector containing arguments to give to the called function.&lt;br /&gt;
|param3 = me&lt;br /&gt;
|param3text = &amp;lt;code&amp;gt;'''me'''&amp;lt;/code&amp;gt; reference for the function call (i.e., for method calls). If given, this will override any &amp;lt;code&amp;gt;'''me'''&amp;lt;/code&amp;gt; value existing in the namespace (locals argument).&lt;br /&gt;
|param4 = locals&lt;br /&gt;
|param4text = A hash with key/value pairs that will be available to the called function, typically used as the namespace for the function to be called.&lt;br /&gt;
|param5 = error&lt;br /&gt;
|param5text = A vector to append errors to.  If the called function generates an error, the error, place, and line will be written to this.  These errors can be printed using {{func link|printerror()|debug}}.&lt;br /&gt;
|example1 =&lt;br /&gt;
# prints &amp;quot;Called from call()&amp;quot;&lt;br /&gt;
call(func {&lt;br /&gt;
    print(&amp;quot;Called from call()&amp;quot;);&lt;br /&gt;
});&lt;br /&gt;
|example2 =&lt;br /&gt;
# prints &amp;quot;a = 1 : b = 2&lt;br /&gt;
call(func(a, b){&lt;br /&gt;
        print(&amp;quot;a = &amp;quot;, a, &amp;quot; : b = &amp;quot;, b);&lt;br /&gt;
    },&lt;br /&gt;
    [1, 2]&lt;br /&gt;
);&lt;br /&gt;
|example3 =&lt;br /&gt;
var Hash = {&lt;br /&gt;
    new: func {&lt;br /&gt;
        var m = { parents: [Hash] };&lt;br /&gt;
&lt;br /&gt;
        m.el1 = &amp;quot;string1&amp;quot;;&lt;br /&gt;
        m.el2 = &amp;quot;string2&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
        return m;&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
# prints &amp;quot;me.el1 = string1&amp;quot;, then &amp;quot;me.el2 = string2&amp;quot; on the next line&lt;br /&gt;
call(func(a, b){        &lt;br /&gt;
        print(&amp;quot;me.el&amp;quot;, a, &amp;quot; = &amp;quot;, me[&amp;quot;el&amp;quot; ~ a]);      &lt;br /&gt;
        print(&amp;quot;me.el&amp;quot;, b, &amp;quot; = &amp;quot;, me[&amp;quot;el&amp;quot; ~ b]);&lt;br /&gt;
    },&lt;br /&gt;
    [1, 2],&lt;br /&gt;
    Hash.new()&lt;br /&gt;
);&lt;br /&gt;
|example4 =&lt;br /&gt;
# prints the value of math.pi&lt;br /&gt;
call(func {&lt;br /&gt;
        print(pi);&lt;br /&gt;
    }, nil, nil, &lt;br /&gt;
    math&lt;br /&gt;
);&lt;br /&gt;
|example5 =&lt;br /&gt;
call(func {&lt;br /&gt;
        print(math.ip); # math.ip doesn't exist&lt;br /&gt;
    }, nil, nil, nil,&lt;br /&gt;
    var errs = []&lt;br /&gt;
);&lt;br /&gt;
debug.printerror(errs); # The error is caught and printed using debug.printerror()&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== caller() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = caller([level]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=404|t=Source}}&lt;br /&gt;
|text = Returns a vector containing a record from the current call stack.  The level numbering starts from the currently executing function (level 0).  Level 1 (the default) is the caller of the current function, and so on.&lt;br /&gt;
&lt;br /&gt;
The result is a four-element vector containing '''[0]''' a hash of local variables, '''[1]''' the function object, '''[2]''' the full source file name (incl. path) and '''[3]''' the line number. &lt;br /&gt;
|param1 = level&lt;br /&gt;
|param1text = Optional integer specifying the stack level to return a result from.  Defaults to 1 (i.e. the caller of the currently executing function).&lt;br /&gt;
|example1 =&lt;br /&gt;
var myFunction = func(a, b){&lt;br /&gt;
    debug.dump(caller(0)[0]); # prints a hash of local variables, including arguments a and b&lt;br /&gt;
    return 2 * 2;&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;2 x 2 = &amp;quot;, myFunction(2, 2));&lt;br /&gt;
|example2 =&lt;br /&gt;
var get_arg_value = func(){&lt;br /&gt;
    print(&amp;quot;Argument to myFunc = &amp;quot;, caller(1)[0]['a']); # print the value of myFunc's single argument, using caller()&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
var myFunc = func(a){&lt;br /&gt;
    get_arg_value();&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
myFunc(3);&lt;br /&gt;
|example3text = This is a real example taken from {{fgdata file|Nasal/canvas/MapStructure.nas}}.  Function &amp;lt;code&amp;gt;r()&amp;lt;/code&amp;gt; (above the TODOs) returns a hash with the key/value pairs as per its arguments. For example, something like this is returned: &amp;lt;code&amp;gt;{ name: &amp;quot;&amp;lt;name&amp;gt;&amp;quot;, vis: 1, zindex: nil }&amp;lt;/code&amp;gt;.&lt;br /&gt;
|example3 =&lt;br /&gt;
var MapStructure_selfTest = func() {&lt;br /&gt;
	var temp = {};&lt;br /&gt;
	temp.dlg = canvas.Window.new([600,400],&amp;quot;dialog&amp;quot;);&lt;br /&gt;
	temp.canvas = temp.dlg.createCanvas().setColorBackground(1,1,1,0.5);&lt;br /&gt;
	temp.root = temp.canvas.createGroup();&lt;br /&gt;
	var TestMap = temp.root.createChild(&amp;quot;map&amp;quot;);&lt;br /&gt;
	TestMap.setController(&amp;quot;Aircraft position&amp;quot;);&lt;br /&gt;
	TestMap.setRange(25); # TODO: implement zooming/panning via mouse/wheel here, for lack of buttons :-/&lt;br /&gt;
	TestMap.setTranslation(&lt;br /&gt;
		temp.canvas.get(&amp;quot;view[0]&amp;quot;)/2,&lt;br /&gt;
		temp.canvas.get(&amp;quot;view[1]&amp;quot;)/2&lt;br /&gt;
	);&lt;br /&gt;
	var r = func(name,vis=1,zindex=nil) return caller(0)[0];&lt;br /&gt;
	# TODO: we'll need some z-indexing here, right now it's just random&lt;br /&gt;
	# TODO: use foreach/keys to show all layers in this case by traversing SymbolLayer.registry direclty ?&lt;br /&gt;
	# maybe encode implicit z-indexing for each lcontroller ctor call ? - i.e. preferred above/below order ?&lt;br /&gt;
	foreach(var type; [r('TFC',0),r('APT'),r('DME'),r('VOR'),r('NDB'),r('FIX',0),r('RTE'),r('WPT'),r('FLT'),r('WXR'),r('APS'), ] ) &lt;br /&gt;
		TestMap.addLayer(factory: canvas.SymbolLayer, type_arg: type.name,&lt;br /&gt;
					visible: type.vis, priority: type.zindex,&lt;br /&gt;
		);&lt;br /&gt;
}; # MapStructure_selfTest&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== chr() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = chr(code);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=175|t=Source}}&lt;br /&gt;
|text = Returns a character as per the single argument. Extended ASCII is supported (see http://www.asciitable.com/ for a list of supported characters), although this may vary between different systems.  For a list of the most commonly used characters, see the {{wikipedia|ASCII#ASCII printable code chart|ASCII printable code chart}} ('''Dec''' column). The following table lists supported control characters, along with their equivalent control characters in Nasal strings.  {{Note|In Nasal, only strings enclosed with double-quotes (&amp;lt;code&amp;gt;&amp;quot;string&amp;quot;&amp;lt;/code&amp;gt;) supports control chracters.  Strings in single quotes (&amp;lt;code&amp;gt;'string'&amp;lt;/code&amp;gt;) do not.}}&lt;br /&gt;
{{{!}} class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Code !! Name !! Equivalent to&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 10 {{!!}} {{Wikipedia|Newline}} {{!!}} &amp;lt;code&amp;gt;\n&amp;lt;/code&amp;gt;&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 9 {{!!}} {{Wikipedia|Tab key#Tab characters|Horizontal tab}} {{!!}} &amp;lt;code&amp;gt;\t&amp;lt;/code&amp;gt;&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 13 {{!!}} {{Wikipedia|Carriage return}} {{!!}} &amp;lt;code&amp;gt;\r&amp;lt;/code&amp;gt;&lt;br /&gt;
{{!}}}&lt;br /&gt;
|param1 = code&lt;br /&gt;
|param1text = Integer character code for the desired glyph.&lt;br /&gt;
|example1 = print(&amp;quot;Code 65 = &amp;quot;, chr(65)); # prints &amp;quot;Code 65 = A&amp;quot;&lt;br /&gt;
|example2text = This example displays all of the characters in a list, in the format &amp;lt;code&amp;gt;Code '''n''' = &amp;gt;'''char'''&amp;lt;&amp;lt;/code&amp;gt;, '''n''' being the index, and '''char''' being the character.&lt;br /&gt;
|example2 =&lt;br /&gt;
for(var i = 0; i &amp;lt;= 255; i += 1){&lt;br /&gt;
    print(&amp;quot;Code &amp;quot;, i, &amp;quot; = &amp;gt;&amp;quot;, chr(i), &amp;quot;&amp;lt;&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== closure() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = closure(func[, level]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=421|t=Source}}&lt;br /&gt;
|text = Returns the hash table containing the lexical namespace of the given function. The level numbering start with level 0 being the namespace of '''func'''. &lt;br /&gt;
|param1 = func&lt;br /&gt;
|param1text = Function to evaluate.&lt;br /&gt;
|param2 = level&lt;br /&gt;
|param2text = Optional integer specifying the scope level.  Defaults to 0 (the namespace of '''func''').&lt;br /&gt;
|example1 =&lt;br /&gt;
var get_math_e = func {&lt;br /&gt;
    return e; # return the value of math.e&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var myFunction = bind(get_math_e, math); # bind get_math_e to the math namespace, so that math.e is immediately available to get_math_e&lt;br /&gt;
debug.dump(closure(myFunction)); # print the namespace of get_math_e&lt;br /&gt;
&lt;br /&gt;
print(myFunction());&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== cmp() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = cmp(a, b);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=112|t=Source}}&lt;br /&gt;
|text = Compares two strings, returning -1 if '''a''' is less than '''b''', 0 if they are identical and 1 if '''a''' is greater than '''b'''. &lt;br /&gt;
|param1 = a&lt;br /&gt;
|param1text = First string argument for comparison.&lt;br /&gt;
|param2 = b&lt;br /&gt;
|param2text = Second string argument for comparison.&lt;br /&gt;
|example1 = print(cmp(&amp;quot;1&amp;quot;, &amp;quot;two&amp;quot;)); # prints -1&lt;br /&gt;
|example2 = print(cmp(&amp;quot;string&amp;quot;, &amp;quot;string&amp;quot;)); # prints 0&lt;br /&gt;
|example3 = print(cmp(&amp;quot;one&amp;quot;, &amp;quot;2&amp;quot;)); # prints 1&lt;br /&gt;
|example4 = print(cmp(&amp;quot;string1&amp;quot;, &amp;quot;string2&amp;quot;)); # prints -1&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== compile() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = compile(code[, filename]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=220|t=Source}}&lt;br /&gt;
|text = Compiles the specified code string and returns a function object bound to the current lexical context.  If there is an error, the function dies, with the argument to {{func link|die()}} being '''filename'''.&lt;br /&gt;
|param1 = code&lt;br /&gt;
|param1text = String containing Nasal code to be compiled.&lt;br /&gt;
|param2 = filename&lt;br /&gt;
|param2text = Optional string used for error messages/logging. Defaults to &amp;lt;code&amp;gt;&amp;lt;compile&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
|example1 = &lt;br /&gt;
var myCode = 'print(&amp;quot;hello&amp;quot;);';&lt;br /&gt;
var helloFunc = compile(myCode, &amp;quot;myCode&amp;quot;);&lt;br /&gt;
helloFunc();&lt;br /&gt;
|example2text = &amp;lt;code&amp;gt;compile&amp;lt;/code&amp;gt; is very convenient to support Nasal loaded from other files.  For instance, [[PropertyList XML files]] (such as GUI dialogs) may contain embedded Nasal sections that need to be parsed, processed and compiled.  For an example of how to do this, save the below XML code as &amp;lt;tt&amp;gt;''[[$FG_ROOT]]/gui/dialogs/test.xml''&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PropertyList&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nasal&amp;gt;&amp;lt;![CDATA[&lt;br /&gt;
print(&amp;quot;You have FlightGear v&amp;quot;, getprop(&amp;quot;/sim/version/flightgear&amp;quot;));&lt;br /&gt;
]]&amp;gt;&amp;lt;/nasal&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/PropertyList&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Now, start FlightGear and execute this code in the [[Nasal Console]].&lt;br /&gt;
|example2 =&lt;br /&gt;
# Build the path&lt;br /&gt;
var FGRoot = getprop(&amp;quot;/sim/fg-root&amp;quot;);&lt;br /&gt;
var filename = &amp;quot;/gui/dialogs/test.xml&amp;quot;;&lt;br /&gt;
var path = FGRoot ~ filename;&lt;br /&gt;
&lt;br /&gt;
var blob = io.read_properties(path);&lt;br /&gt;
var script = blob.getValues().nasal; # Get the nasal string&lt;br /&gt;
&lt;br /&gt;
# Compile the script.  We're passing the filename here for better runtime diagnostics &lt;br /&gt;
var code = call(func {&lt;br /&gt;
    compile(script, filename);&lt;br /&gt;
}, nil, nil, var compilation_errors = []);&lt;br /&gt;
&lt;br /&gt;
if(size(compilation_errors)){&lt;br /&gt;
    die(&amp;quot;Error compiling code in: &amp;quot; ~ filename);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
# Invoke the compiled script, equivalent to code(); &lt;br /&gt;
# We're using call() here to detect errors:&lt;br /&gt;
call(code, [], nil, nil, var runtime_errors = []);&lt;br /&gt;
&lt;br /&gt;
if(size(runtime_errors)){&lt;br /&gt;
    die(&amp;quot;Error calling code compiled loaded from: &amp;quot; ~ filename);&lt;br /&gt;
}&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== contains() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = contains(hash, key);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=184|t=Source}}&lt;br /&gt;
|text = Returns 1 (True) if the hash contains the specified key, or 0 (False) if not.&lt;br /&gt;
|param1 = hash&lt;br /&gt;
|param1text = The hash to search in.&lt;br /&gt;
|param2 = key&lt;br /&gt;
|param2text = The scalar to be searched for, contained as a key in the hash.&lt;br /&gt;
|example1 =&lt;br /&gt;
# Initialize a hash&lt;br /&gt;
var hash = {&lt;br /&gt;
    element: &amp;quot;value&amp;quot;&lt;br /&gt;
};&lt;br /&gt;
print(contains(hash, &amp;quot;element&amp;quot;) ? &amp;quot;Yes&amp;quot; : &amp;quot;No&amp;quot;); # This will print &amp;quot;Yes&amp;quot;&lt;br /&gt;
|example2 =&lt;br /&gt;
# Initialize a hash&lt;br /&gt;
var hash = {&lt;br /&gt;
    element: &amp;quot;value&amp;quot;&lt;br /&gt;
};&lt;br /&gt;
print(contains(hash, &amp;quot;element2&amp;quot;) ? &amp;quot;Yes&amp;quot; : &amp;quot;No&amp;quot;); # This will print &amp;quot;No&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===contains()===&lt;br /&gt;
{{Requires commit|desc=Vector support|details=see {{Merge-request|project=fgdata|id=305}}|commit=ee39abbd3b70c9b6d5e3a1c4ccedddaac1a92b11|repo=Simgear}}&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = contains(vector, item);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=184|t=Source}}&lt;br /&gt;
|text = Returns 1 (True) if the vector contains the specified item, or 0 (False) if not.&lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector to search in.&lt;br /&gt;
|param2 = item&lt;br /&gt;
|param2text = The object to be searched for in the vector.&lt;br /&gt;
|example1 =&lt;br /&gt;
# Initialize a hash&lt;br /&gt;
var vec = [&amp;quot;element&amp;quot;, &amp;quot;foo&amp;quot;];&lt;br /&gt;
print(contains(vec, &amp;quot;element&amp;quot;) ? &amp;quot;Yes&amp;quot; : &amp;quot;No&amp;quot;); # This will print &amp;quot;Yes&amp;quot;&lt;br /&gt;
|example2 =&lt;br /&gt;
# Initialize a hash&lt;br /&gt;
var vec = [&amp;quot;element&amp;quot;, &amp;quot;foo&amp;quot;];&lt;br /&gt;
print(contains(vec, &amp;quot;element2&amp;quot;) ? &amp;quot;Yes&amp;quot; : &amp;quot;No&amp;quot;); # This will print &amp;quot;No&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===delete() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = delete(hash, key);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=83|t=Source}}&lt;br /&gt;
|text = Deletes the key from the hash if it exists. Operationally, this is NOT identical to setting the hash value specified by the key to &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; as the key will stay in the hash (at least for a while). This variant potentially frees storage by deleting the reference to the key and by shrinking the hash.  Returns the hash that has been operated on.&lt;br /&gt;
|param1 = hash&lt;br /&gt;
|param1text = The hash from which to delete the key.&lt;br /&gt;
|param2 = key&lt;br /&gt;
|param2text = The scalar to be deleted, contained as a key in the hash.&lt;br /&gt;
|example1 =&lt;br /&gt;
# Initialize the hash&lt;br /&gt;
var hash = {&lt;br /&gt;
    element1: &amp;quot;value1&amp;quot;,&lt;br /&gt;
    element2: &amp;quot;value2&amp;quot;&lt;br /&gt;
};&lt;br /&gt;
delete(hash, &amp;quot;element1&amp;quot;); # Delete element1&lt;br /&gt;
debug.dump(hash); # prints the hash, which is now minus element1&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===die()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = die(error);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=288|t=Source}}&lt;br /&gt;
|text = Terminates execution and unwinds the stack.  The place and the line will be added to the '''error'''.  This invokes the same internal exception handler used for internal runtime errors. Use this to signal fatal errors, or to implement exception handling. The error thrown (including internal runtime errors) can be caught with {{func link|call()}}.&lt;br /&gt;
|param1 = error&lt;br /&gt;
|param1text = String describing the error.&lt;br /&gt;
:{{inote|This parameter is technically optional, but it is highly recommended to use it.}}&lt;br /&gt;
|example1 = &lt;br /&gt;
print(&amp;quot;Will print&amp;quot;);&lt;br /&gt;
die(&amp;quot;Don't go any further!&amp;quot;); &lt;br /&gt;
print(&amp;quot;Won't print&amp;quot;); # Will not be printed because die() stops the process&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== find()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = find(needle, haystack);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=450|t=Source}}&lt;br /&gt;
|text = Finds and returns the index of the first occurrence of the string '''needle''' in the string '''haystack''', or -1 if no such occurrence was found.&lt;br /&gt;
|param1 = needle&lt;br /&gt;
|param1text = String to search for.&lt;br /&gt;
|param2 = haystack&lt;br /&gt;
|param2text = String to search in.&lt;br /&gt;
|example1 = print(find(&amp;quot;c&amp;quot;, &amp;quot;abcdef&amp;quot;)); # prints 2&lt;br /&gt;
|example2 = print(find(&amp;quot;x&amp;quot;, &amp;quot;abcdef&amp;quot;)); # prints -1&lt;br /&gt;
|example3 = print(find(&amp;quot;cd&amp;quot;, &amp;quot;abcdef&amp;quot;)); # prints 2&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===ghosttype()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = ghosttype(ghost);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=207|t=Source}}&lt;br /&gt;
|text = Returns a string containing either a descriptive name of a ghost (a raw C/C++ object), or a unique id (the pointer to the C/C++ &amp;lt;code&amp;gt;naGhostType&amp;lt;/code&amp;gt; instance) if no name has been set.  Ghost is an acronym that stands for '''G'''arbage-collected '''H'''andle to '''O'''ut'''S'''ide '''T'''hingy.&lt;br /&gt;
|param1 = ghost&lt;br /&gt;
|param1text = Ghost to return a description for.&lt;br /&gt;
|example1 = print(ghosttype(airportinfo())); # prints &amp;quot;airport&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===id()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = id(object);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=570|t=Source}}&lt;br /&gt;
|text = Returns a string containing information on the type and ID of the object provided in the single argument.  The information is returned in the form of &amp;lt;code&amp;gt;'''&amp;lt;type&amp;gt;''':'''&amp;lt;id&amp;gt;'''&amp;lt;/code&amp;gt;, where '''&amp;lt;type&amp;gt;''' is the type of object, and '''&amp;lt;id&amp;gt;''' is the ID.&lt;br /&gt;
|param1 = object&lt;br /&gt;
|param1text = Can be either of a string, a vector, a hash, a code, a function, or a ghost.&lt;br /&gt;
|example1 = print(id(&amp;quot;A&amp;quot;)); # prints &amp;quot;str:000000001624A590&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===int() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = int(number);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=90|t=Source}}&lt;br /&gt;
|text = Returns the integer part of the numeric value of the single argument, or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if none exists.&lt;br /&gt;
|param1 = number&lt;br /&gt;
|param1text = Number or string with just a number in it to return an integer from.&lt;br /&gt;
|example1 = print(int(23)); # prints &amp;quot;23&amp;quot;&lt;br /&gt;
|example2 = print(int(23.123)); # prints &amp;quot;23&amp;quot;&lt;br /&gt;
|example3 = debug.dump(int(&amp;quot;string&amp;quot;)); # prints &amp;quot;nil&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===keys()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = keys(hash);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=33|t=Source}}&lt;br /&gt;
|text = Returns a vector containing the list of keys found in the single hash argument. &lt;br /&gt;
|param1 = hash&lt;br /&gt;
|param1text = The hash to return the keys from.&lt;br /&gt;
|example1 = &lt;br /&gt;
# Initialize a hash&lt;br /&gt;
var hash = {&lt;br /&gt;
    element1: &amp;quot;value&amp;quot;,&lt;br /&gt;
    element2: &amp;quot;value&amp;quot;&lt;br /&gt;
};&lt;br /&gt;
debug.dump(keys(hash)); # print the vector&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===left()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = left(string, length);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=149|t=Source}}&lt;br /&gt;
|version = 2.12&lt;br /&gt;
|commit = {{simgear commit|bd7163|t=commit}}&lt;br /&gt;
|text = Returns a substring of '''string''', starting from the left.&lt;br /&gt;
|param1 = string&lt;br /&gt;
|param1text = String to return part of.&lt;br /&gt;
|param2 = length&lt;br /&gt;
|param2text = Integer specifying the length of the substring to return.&lt;br /&gt;
|example1 = print(left(&amp;quot;string&amp;quot;, 2)); # prints &amp;quot;st&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== num()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = num(number);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=102|t=Source}}&lt;br /&gt;
|text = Returns the numerical value of the single string argument, or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if none exists. &lt;br /&gt;
|param1 = number&lt;br /&gt;
|param1text = String with just a number in it to return a number from.&lt;br /&gt;
|example1 = print(num(&amp;quot;23&amp;quot;)); # prints &amp;quot;23&amp;quot;&lt;br /&gt;
|example2 = print(num(&amp;quot;23.123&amp;quot;)); # prints &amp;quot;23.123&amp;quot;&lt;br /&gt;
|example3 = debug.dump(num(&amp;quot;string&amp;quot;)); # prints &amp;quot;nil&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===pop()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = pop(vector);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=50|t=Source}}&lt;br /&gt;
|text = Removes and returns the last element of the single vector argument, or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if the vector is empty. &lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = Vector to remove an element from.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
pop(vector);&lt;br /&gt;
debug.dump(vector); # prints &amp;quot;[1, 2]&amp;quot;&lt;br /&gt;
|example2 = &lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
debug.dump(pop(vector)); # prints &amp;quot;3&amp;quot;&lt;br /&gt;
|example3 = &lt;br /&gt;
var vector = [];&lt;br /&gt;
debug.dump(pop(vector)); # prints &amp;quot;nil&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===right()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = right(string, length);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=161|t=Source}}&lt;br /&gt;
|version = 2.12&lt;br /&gt;
|commit = {{simgear commit|bd7163|t=commit}}&lt;br /&gt;
|text = Returns a substring of '''string''', starting from the right.&lt;br /&gt;
|param1 = string&lt;br /&gt;
|param1text = String to return part of.&lt;br /&gt;
|param2 = length&lt;br /&gt;
|param2text = Integer specifying the length of the substring to return.&lt;br /&gt;
|example1 = print(right(&amp;quot;string&amp;quot;, 2)); # prints &amp;quot;ng&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== setsize()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = setsize(vector, size);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=56|t=Source}}&lt;br /&gt;
|text = Sets the size of a vector. The first argument specifies a vector, the second a number representing the desired size of that vector. If the vector is currently larger than the specified size, it is truncated. If it is smaller, it is padded with &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; entries. Returns the vector operated upon. &lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector to be operated on.&lt;br /&gt;
|param2 = size&lt;br /&gt;
|param2text = The desired size of the vector in number of entries.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize a vector&lt;br /&gt;
setsize(vector, 4);&lt;br /&gt;
debug.dump(vector); # print the vector&lt;br /&gt;
|example2 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize a vector&lt;br /&gt;
setsize(vector, 2);&lt;br /&gt;
debug.dump(vector); # print the vector&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===size()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = size(object);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=23|t=Source}}&lt;br /&gt;
|text = Returns the size of the single argument. For strings, this is the length in bytes. For vectors, this is the number of elements. For hashes, it is the number of key/value pairs. If the argument is &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; or a number, this error will be thrown: &amp;lt;code&amp;gt;object has no size()&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param1 = object&lt;br /&gt;
|param1text = Object to find the size of.  Must be a string, a vector or a hash.&lt;br /&gt;
|example1 = &lt;br /&gt;
var string = &amp;quot;string&amp;quot;;&lt;br /&gt;
print(size(string)); # prints &amp;quot;6&amp;quot;&lt;br /&gt;
|example2 =&lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
print(size(vector)); # prints &amp;quot;3&amp;quot;&lt;br /&gt;
|example3 =&lt;br /&gt;
var hash = {&lt;br /&gt;
    element1: &amp;quot;value1&amp;quot;,&lt;br /&gt;
    element2: &amp;quot;value2&amp;quot;,&lt;br /&gt;
    element3: &amp;quot;value3&amp;quot;&lt;br /&gt;
};&lt;br /&gt;
print(size(hash)); # prints &amp;quot;3&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== sort()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = sort(vector, function);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=542|t=Source}}&lt;br /&gt;
|text = Returns a vector containing the elements in the input '''vector''' sorted in according to the rule given by '''function'''. Implemented with the ANSI C {{func link|qsort()|link=http://www.cplusplus.com/reference/cstdlib/qsort/}}, &amp;lt;code&amp;gt;sort()&amp;lt;/code&amp;gt; is stable.  This means that if the rules in the first example are used, equal elements in the output vector will appear in the same relative order as they do in the input.  It is run in a loop, so '''function''' is run several times.&lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = Input vector to sort.&lt;br /&gt;
|param2 = function&lt;br /&gt;
|param2text = Function according to which the elements will be sorted by.  It should take two arguments and should return one of 1, 0, or -1.&lt;br /&gt;
{{{!}} class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Return value !! Meaning&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} less than 0 {{!!}} first argument should go before second argument&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 0 {{!!}} first argument equals second argument&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} greater than 0 {{!!}} first argument should go after second argument&lt;br /&gt;
{{!}}}&lt;br /&gt;
&lt;br /&gt;
|example1text = This example sorts elements from smallest to greatest.&lt;br /&gt;
|example1 = &lt;br /&gt;
var sort_rules = func(a, b){&lt;br /&gt;
    if(a &amp;lt; b){&lt;br /&gt;
        return -1; # A should before b in the returned vector&lt;br /&gt;
    }elsif(a == b){&lt;br /&gt;
        return 0; # A is equivalent to b &lt;br /&gt;
    }else{&lt;br /&gt;
        return 1; # A should after b in the returned vector&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
debug.dump(sort([3, 2, 5, 6, 4, 1], sort_rules)); # prints &amp;quot;[1, 2, 3, 4, 5, 6]&amp;quot;&lt;br /&gt;
|example2text = This example sorts elements from greatest to smallest.&lt;br /&gt;
|example2 = &lt;br /&gt;
# Outputs the elements in reverse order (greatest to smallest)&lt;br /&gt;
var sort_rules = func(a, b){&lt;br /&gt;
    if(a &amp;lt; b){&lt;br /&gt;
        return 1; # -1 in the above example&lt;br /&gt;
    }elsif(a == b){&lt;br /&gt;
        return 0;&lt;br /&gt;
    }else{&lt;br /&gt;
        return -1; # 1 in the above example&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
debug.dump(sort([3, 2, 5, 6, 4, 1], sort_rules)); # prints &amp;quot;[6, 5, 4, 3, 2, 1]&amp;quot;&lt;br /&gt;
|example3text = This example sorts a vector of strings (runways for example) from smallest to greatest.&lt;br /&gt;
|example3 = &lt;br /&gt;
var runways = [&amp;quot;09R&amp;quot;,&amp;quot;27R&amp;quot;,&amp;quot;26L&amp;quot;,&amp;quot;09L&amp;quot;,&amp;quot;15&amp;quot;];&lt;br /&gt;
var rwy = sort(runways,func(a,b) cmp(a,b));&lt;br /&gt;
debug.dump(rwy); # prints ['09L','09R','15','26L','27R']&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== split()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = split(delimiter, string);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=460|t=Source}}&lt;br /&gt;
|text = Splits the input string into a vector of substrings bounded by occurrences of the delimiter substring.&lt;br /&gt;
|param1 = delimiter&lt;br /&gt;
|param1text = String that will split the substrings in the returned vector.&lt;br /&gt;
|param2 = string&lt;br /&gt;
|param2text = String to split up.&lt;br /&gt;
|example1 = debug.dump(split(&amp;quot;cd&amp;quot;, &amp;quot;abcdef&amp;quot;)); # prints &amp;quot;['ab', 'ef']&amp;quot;&lt;br /&gt;
|example2 = debug.dump(split(&amp;quot;.&amp;quot;, &amp;quot;3.2.0&amp;quot;)); # prints &amp;quot;[3, 2, 0]&amp;quot;&lt;br /&gt;
|example3 = debug.dump(split(&amp;quot;/&amp;quot;, &amp;quot;path/to/file&amp;quot;)); # prints &amp;quot;['path', 'to', 'file']&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===sprintf()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;sprintf(format[, arg[, arg, [...]]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=355|t=Source}}&lt;br /&gt;
|text = Creates and returns a string formatted using ANSI C {{func link|vsnprintf()|link=http://en.cppreference.com/w/c/io/vfprintf}} &amp;lt;ref&amp;gt;&lt;br /&gt;
{{Cite web&lt;br /&gt;
|url = http://sourceforge.net/p/flightgear/simgear/ci/next/tree/simgear/nasal/lib.c#l308&lt;br /&gt;
|title = fgdata/simgear/simgear/nasal/lib.c, line 308&lt;br /&gt;
|accessdate = October 2015&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;/ref&amp;gt;.  Below is a table of supported format specifiers.&lt;br /&gt;
{{{!}} class=&amp;quot;wikitable&amp;quot; width=&amp;quot;75%&amp;quot;&lt;br /&gt;
{{!}}+ %[flags][width][.precision]specifier&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} Flags&lt;br /&gt;
{{!-}}&lt;br /&gt;
! Flag !! Output&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;+&amp;lt;/code&amp;gt; {{!!}} Forces to precede the result with a plus or minus sign ('''+''' or '''-''') even for positive numbers. By default, only negative numbers are preceded with a '''-''' sign.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} ''space'' {{!!}} Prefixes non-signed numbers with a space.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;-&amp;lt;/code&amp;gt; {{!!}} Left-align the output of this placeholder (the default is to right-align the output) when the width option is specified.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; {{!!}} Use 0 instead of spaces to pad a field when the width option is specified.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;#&amp;lt;/code&amp;gt; {{!!}} Used with &amp;lt;code&amp;gt;o&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;x&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;X&amp;lt;/code&amp;gt; specifiers the value is preceded with &amp;lt;tt&amp;gt;0&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;0x&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;0X&amp;lt;/tt&amp;gt; respectively for values different than zero. Used with &amp;lt;code&amp;gt;e&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;E&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;f&amp;lt;/code&amp;gt;, it forces the written output to contain a decimal point even if no digits would follow. By default, if no digits follow, no decimal point is written. Used with &amp;lt;code&amp;gt;g&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;G&amp;lt;/code&amp;gt; the result is the same as with &amp;lt;code&amp;gt;e&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;E&amp;lt;/code&amp;gt; but trailing zeros are not removed.&lt;br /&gt;
{{!-}}&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} Width&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} colspan=&amp;quot;2&amp;quot; {{!}} Integer specifying the minimum number of characters to be returned. This includes the decimal point and decimal fraction as well as + or - signs.&lt;br /&gt;
{{!-}}&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} Precision&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} colspan=&amp;quot;2&amp;quot; {{!}} Integer preceded by a dot specifying the number of decimal places to be written.&lt;br /&gt;
{{!-}}&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} Specifiers&lt;br /&gt;
{{!-}}&lt;br /&gt;
! Specifier !! Output&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;d&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;i&amp;lt;/code&amp;gt; {{!!}} Signed decimal number.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;s&amp;lt;/code&amp;gt; {{!!}} A string&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;%&amp;lt;/code&amp;gt; {{!!}} Percent (%) character.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;c&amp;lt;/code&amp;gt; {{!!}} A single character assigned to a character code, the code given in an integer argument.  See http://www.asciitable.com/ for a list of supported characters and their codes.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;o&amp;lt;/code&amp;gt; {{!!}} Unsigned integer as an octal number.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;u&amp;lt;/code&amp;gt; {{!!}} Unsigned decimal integer.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;x&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;X&amp;lt;/code&amp;gt; {{!!}} Unsigned integer as a hexadecimal number.  If &amp;lt;code&amp;gt;x&amp;lt;/code&amp;gt; is used, any letters in the number are lowercase, while &amp;lt;code&amp;gt;X&amp;lt;/code&amp;gt; gives uppercase.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;e&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;E&amp;lt;/code&amp;gt; {{!!}} Double value in scientific notation (i.e., ''[-]ddd.ddd'''e'''[+/-]ddd''), with an exponent being denoted by &amp;lt;tt&amp;gt;e&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;E&amp;lt;/tt&amp;gt; depending on whether an upper or lowercase is used respectively.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;f&amp;lt;/code&amp;gt; {{!!}} Floating-point number, in fixed decimal notation, by default with 6 decimal places.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;F&amp;lt;/code&amp;gt; {{!!}} Appears to be available&amp;lt;ref&amp;gt;&lt;br /&gt;
{{Cite web&lt;br /&gt;
|url = http://sourceforge.net/p/flightgear/simgear/ci/next/tree/simgear/nasal/lib.c#l389&lt;br /&gt;
|title = fgdata/simgear/simgear/nasal/lib.c, line 389&lt;br /&gt;
|accessdate = October 2015&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;/ref&amp;gt;, but doesn't work.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;g&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;G&amp;lt;/code&amp;gt; {{!!}} Double in either normal or exponential notation, whichever is more appropriate for its magnitude. &amp;lt;code&amp;gt;g&amp;lt;/code&amp;gt; uses lower-case letters, &amp;lt;code&amp;gt;G&amp;lt;/code&amp;gt; uses upper-case letters. This type differs slightly from fixed-point notation in that insignificant zeroes to the right of the decimal point are not included.  Also, the decimal point is not included on whole numbers.&lt;br /&gt;
{{!}}}&lt;br /&gt;
&lt;br /&gt;
|param1 = format&lt;br /&gt;
|param1text = String specifying the format.  Can be used with or without a format specifiers.  See below for examples.&lt;br /&gt;
|param2 = arg&lt;br /&gt;
|param2text = Argument specifying a value to replace a format placeholder (such as &amp;lt;code&amp;gt;%d&amp;lt;/code&amp;gt;) in the format string.  Not required if there are no format specifiers.&lt;br /&gt;
&lt;br /&gt;
|example1 = print(sprintf(&amp;quot;%i&amp;quot;, 54)); # prints &amp;quot;54&amp;quot;&lt;br /&gt;
|example2 = print(sprintf(&amp;quot;Pi = %+.10f&amp;quot;, math.pi)); # prints &amp;quot;Pi = +3.1415926536&amp;quot;&lt;br /&gt;
|example3 = &lt;br /&gt;
print(sprintf(&amp;quot;%6d&amp;quot;, 23)); # prints &amp;quot;    23&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;%06d&amp;quot;, 23)); # prints &amp;quot;000023&amp;quot;&lt;br /&gt;
|example4 =&lt;br /&gt;
var FGVer = getprop(&amp;quot;/sim/version/flightgear&amp;quot;);&lt;br /&gt;
print(sprintf(&amp;quot;You have FlightGear v%s&amp;quot;, FGVer)); # prints &amp;quot;You have FlightGear v&amp;lt;your version&amp;gt;&amp;quot;&lt;br /&gt;
|example5 = &lt;br /&gt;
print(sprintf(&amp;quot;Hexadecimal 100000 = %X&amp;quot;, 100000)); # prints &amp;quot;Hexadecimal 100000 = 186A0&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;Hexadecimal 100000 = %x&amp;quot;, 100000)); # prints &amp;quot;Hexadecimal 100000 = 186a0&amp;quot;&lt;br /&gt;
|example6 = print(sprintf(&amp;quot;Code 65 is %c&amp;quot;, 65)); # prints &amp;quot;Code 65 is A&amp;quot;&lt;br /&gt;
|example7 = &lt;br /&gt;
print(sprintf(&amp;quot;%e&amp;quot;, 54)); # prints &amp;quot;5.400000e+001&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;%E&amp;quot;, 54)); # prints &amp;quot;5.400000E+001&amp;quot;&lt;br /&gt;
|example8 = print(sprintf(&amp;quot;%o&amp;quot;, 54)); # prints &amp;quot;66&amp;quot;&lt;br /&gt;
|example9 = print(sprintf(&amp;quot;50%% of 100 is %i&amp;quot;, 100 / 2)); # prints &amp;quot;50% of 100 is 50&amp;quot;&lt;br /&gt;
|example10 =&lt;br /&gt;
print(sprintf(&amp;quot;%.2f&amp;quot;, 1.4));   #prints &amp;quot;1.40&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;%.1f&amp;quot;, 1.4));   #prints &amp;quot;1.4&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;% 4.1f&amp;quot;, 1.4)); #prints &amp;quot; 1.4&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;%04.1f&amp;quot;, 1.4)); #prints &amp;quot;01.4&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;% 6.1f&amp;quot;, 1.4)); #prints &amp;quot;   1.4&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;%06.1f&amp;quot;, 1.4)); #prints &amp;quot;0001.4&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===streq()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = streq(a, b);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=129|t=Source}}&lt;br /&gt;
|text = Tests the string values of the two arguments for equality. This function is needed because the &amp;lt;code&amp;gt;'''=='''&amp;lt;/code&amp;gt; operator (see [[Nasal_Operators#Equality|Nasal Operators]]) tests for numeric equality first.  If either or both the arguments are not strings, 0 (False) will be returned.  Returns either 0 (False) or 1 (True).  {{Note|This function is rarely required in typical code.}}&lt;br /&gt;
|param1 = a&lt;br /&gt;
|param1text = First argument for testing equality.&lt;br /&gt;
|param2 = b&lt;br /&gt;
|param2text = Second argument for testing equality.&lt;br /&gt;
|example1 = print(streq(&amp;quot;0&amp;quot;, &amp;quot;0&amp;quot;)); # prints &amp;quot;1&amp;quot; (True)&lt;br /&gt;
|example2 = &lt;br /&gt;
print(0 == 0.0); # prints &amp;quot;1&amp;quot; (True)&lt;br /&gt;
print(streq(&amp;quot;0&amp;quot;, &amp;quot;0.0&amp;quot;)); # prints &amp;quot;0&amp;quot; (False)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===substr()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = substr(string, start [, length]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=129|t=Source}}&lt;br /&gt;
|text = Similar the {{func link|subvec()}}, but operates on strings. Computes and returns a substring. The first argument specifies a string, the second is the index of the start of a substring, the optional third argument specifies a length (the default is to return the rest of the string from the start).&lt;br /&gt;
|param1 = string&lt;br /&gt;
|param1text = String to return a substring from.&lt;br /&gt;
|param2 = start&lt;br /&gt;
|param2text = Integer specifying the start of a substring. Negative values specify a position from the end of the string.&lt;br /&gt;
|param3 = length&lt;br /&gt;
|param3text = Optional argument specifying the length of the substring. Defaults to the end of the string.&lt;br /&gt;
|example1 = print(substr(&amp;quot;abcde&amp;quot;, 1, 3)); # prints &amp;quot;bcd&amp;quot;&lt;br /&gt;
|example2 = print(substr(&amp;quot;abcde&amp;quot;, 1)); # prints &amp;quot;bcde&amp;quot;&lt;br /&gt;
|example3 = print(substr(&amp;quot;abcde&amp;quot;, 2, 1)); # prints &amp;quot;c&amp;quot;&lt;br /&gt;
|example4 = print(substr(&amp;quot;abcde&amp;quot;, -2)); # prints &amp;quot;de&amp;quot;&lt;br /&gt;
|example5 = print(substr(&amp;quot;abcde&amp;quot;, -3, 2)); # prints &amp;quot;cd&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===subvec()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = subvec(vector, start[, length]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=63|t=Source}}&lt;br /&gt;
|text = Returns a sub-range of a vector. The first argument specifies a vector, the second a starting index, and the optional third argument indicates a length (the default is to the end of the vector). &lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector to take the sub-vector from.&lt;br /&gt;
|param2 = start&lt;br /&gt;
|param2text = The starting point of the sub-vector within the given vector.&lt;br /&gt;
|param3 = length&lt;br /&gt;
|param3text = Optional argument specifying the length of the sub-vector, from the starting point.&lt;br /&gt;
'''Notes:'''&lt;br /&gt;
* Omitting the ''vector'' and ''start'' arguments is not an error (possibly it should be) but the return value is ''nil''.&lt;br /&gt;
* A negative ''start'' argument ''is'' an error. This seems wrong. Perhaps the language designer could comment.&lt;br /&gt;
* A value of ''start'' greater than ''size(vector)'' causes an error. A value equal to ''size(vector)'' returns an empty vector.&lt;br /&gt;
* If the value of ''length'' is greater than ''size(vector) - start'' then it is ignored. That is, all elements from ''start'' to the end of ''vector'' are returned. If ''length'' is zero then an empty vector is returned. A negative value of ''length'' causes an error.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
debug.dump(subvec(vector, 0)); # prints &amp;quot;[1, 2, 3]&amp;quot;&lt;br /&gt;
|example2 = &lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
debug.dump(subvec(vector, 1)); # prints &amp;quot;[2, 3]&amp;quot;&lt;br /&gt;
|example3 = &lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
debug.dump(subvec(vector, 1, 1)); # prints &amp;quot;[2]&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== typeof()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = typeof(object);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=193|t=Source}}&lt;br /&gt;
|text = Returns a string indicating the whether the object is &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt;, a scalar (number or string), a vector, a hash, a function, or a ghost.&lt;br /&gt;
|param1 = object&lt;br /&gt;
|param1text = Object to return the type of.&lt;br /&gt;
|example1 = &lt;br /&gt;
var object = nil;&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;nil&amp;quot;&lt;br /&gt;
|example2 = &lt;br /&gt;
var object = &amp;quot;Hello world!&amp;quot;;&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;scalar&amp;quot;&lt;br /&gt;
|example3 = &lt;br /&gt;
var object = math.pi;&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;scalar&amp;quot;&lt;br /&gt;
|example4 = &lt;br /&gt;
var object = [1, 2, 3];&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;vector&amp;quot;&lt;br /&gt;
|example5 = &lt;br /&gt;
var object = {};&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;hash&amp;quot;&lt;br /&gt;
|example6 = &lt;br /&gt;
var object = func {};&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;func&amp;quot;&lt;br /&gt;
|example7 =&lt;br /&gt;
var object = airportinfo();&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;ghost&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == Extension modules ==&lt;br /&gt;
=== thread ===&lt;br /&gt;
{{WIP}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.newthread(func);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = start a new worker thread&lt;br /&gt;
|example1 = thread.newthread( func() {} );&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.newlock();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = create a new lock&lt;br /&gt;
|example1 = var lock = thread.newlock()&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.lock();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = lock a lock&lt;br /&gt;
|example1 = var lock = thread.newlock()&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.unlock();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = unlock a lock&lt;br /&gt;
|example1 = var lock = thread.unlock()&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.newsem();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = create a new {{Wikipedia|semaphore}}&lt;br /&gt;
|example1 = var semaphore = thread.newsem()&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.semdown();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = semaphore down&lt;br /&gt;
|example1 = thread.semdown(semaphore)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.semup();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = semaphore up&lt;br /&gt;
|example1 = thread.semup(semaphore)&lt;br /&gt;
}} --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Extension functions==&lt;br /&gt;
The '''extension functions''' are global functions that have been added to Nasal since its integration into FlightGear. Unlike the core library functions, they are generally specifically designed to interact directly with FlightGear. Extension functions come from three source files:&lt;br /&gt;
*{{flightgear file|src/Scripting/NasalPositioned.cxx}}&lt;br /&gt;
*{{flightgear file|src/Scripting/NasalSys.cxx}}&lt;br /&gt;
*{{fgdata file|Nasal/globals.nas}}&lt;br /&gt;
&lt;br /&gt;
===abort()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = abort();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=565|t=Source}}&lt;br /&gt;
|text = This function is a wrapper for the C++ {{func link|abort()|link=http://www.cplusplus.com/reference/cstdlib/abort/}} function. It simply aborts FlightGear with an error, which varies depending on the operating system. This function should not really be used; instead, please use the &amp;quot;exit&amp;quot; [[Fgcommands|fgcommand]], which will exit FlightGear more gracefully (see example below).&lt;br /&gt;
|example1text = This example will immediately stop FlightGear with an error, such as &amp;quot;FlightGear has stopped working.&amp;quot;&lt;br /&gt;
|example1 = abort();&lt;br /&gt;
|example2text = For exiting FlightGear in a better way, please use the following code:&lt;br /&gt;
|example2 = fgcommand(&amp;quot;exit&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== abs() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = abs(number);&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = This simple function returns the {{wikipedia|absolute value|noicon=1}} of the provided number.&lt;br /&gt;
|param1 = number&lt;br /&gt;
|param1text = This argument is required and should be a number.&lt;br /&gt;
|example1 = print(abs(1)); # prints &amp;quot;1&amp;quot;&lt;br /&gt;
|example2 = print(abs(-1)); # prints &amp;quot;1&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===aircraftToCart() ===&lt;br /&gt;
This new function in FG 2017.2.1 takes coordinates in aircraft structural coordinate system, and translate them into geocentric coordinates.&lt;br /&gt;
Example for (5,6,7):&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
var pos = aircraftToCart({x: -5, y: 6, z: -7});&lt;br /&gt;
var coord = geo.Coord.new();&lt;br /&gt;
coord.set_xyz(pos.x, pos.y, pos.z);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Notice: x and z is inverted sign on purpose.&lt;br /&gt;
if you want lat. lon, alt from that, just call: (degrees and meters)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
coord.lat()&lt;br /&gt;
coord.lon()&lt;br /&gt;
coord.alt()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===addcommand() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = addcommand(name, code);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=659|t=Source}}&lt;br /&gt;
|version = 2.12&lt;br /&gt;
|commit = {{flightgear commit|7b663c|t=commit}}&lt;br /&gt;
|text = {{see also|Howto:Add new fgcommands to FlightGear}}&lt;br /&gt;
&lt;br /&gt;
This function enables the addition of a new custom [[fgcommands|fgcommand]] to FlightGear from within Nasal. An fgcommand created using this method can be used in exactly the same way as the built-in fgcommands. Also, an fgcommand created via this method will always return True or 1, like all other fgcommands.&lt;br /&gt;
|param1 = name&lt;br /&gt;
|param1text = This will become the name of the new fgcommand. Must be a string.&lt;br /&gt;
|param2 = code&lt;br /&gt;
|param2text = The code that will be executed when the fgcommand is run. Must be a function.&lt;br /&gt;
|example1text = This example adds a new fgcommand and then runs it. Although it executes a simple {{func link|print()}} statement, any valid Nasal code can be used.&lt;br /&gt;
|example1 = addcommand(&amp;quot;myFGCmd&amp;quot;, func(node) {&lt;br /&gt;
    print(&amp;quot;fgcommand 'myFGCmd' has been run.&amp;quot;);&lt;br /&gt;
    props.dump( node );&lt;br /&gt;
});&lt;br /&gt;
fgcommand(&amp;quot;myFGCmd&amp;quot;, props.Node.new({foo:1, bar:2}) );&lt;br /&gt;
|example2text = This example demonstrates how parameters are defined in a new fgcommand.&lt;br /&gt;
|example2 = addcommand(&amp;quot;myFGCmd&amp;quot;, func(node){&lt;br /&gt;
    print(node.getNode(&amp;quot;number&amp;quot;).getValue()); # prints the value of &amp;quot;number,&amp;quot; which is 12&lt;br /&gt;
});&lt;br /&gt;
fgcommand(&amp;quot;myFGCmd&amp;quot;, props.Node.new({&amp;quot;number&amp;quot;: 12}));&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===airportinfo()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = airportinfo();&lt;br /&gt;
airportinfo(type);&lt;br /&gt;
airportinfo(id);&lt;br /&gt;
airportinfo(lat, lon[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1024|t=Source}}&lt;br /&gt;
|text = Function for retrieval of airport, heliport, or seaplane base information. It returns a Nasal ghost; however, its structure is like that of a Nasal hash. The following information is returned:&lt;br /&gt;
* '''parents''': A vector containing a hash of various functions to access information about the runway. See {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2659}} for full list.&lt;br /&gt;
* '''lon''': Longitude of the location.&lt;br /&gt;
* '''lat''': Latitude of the location.&lt;br /&gt;
* '''has_metar''': True or false depending whether the airport has a [[METAR]] code defined for it.&lt;br /&gt;
* '''elevation''': Elevation of the location in metres.&lt;br /&gt;
* '''id''': ICAO code of the airport (or ID of the seaplane base/heliport).&lt;br /&gt;
* '''name''': Name of the airport/heliport/seaplane base.&lt;br /&gt;
* '''runways'''&lt;br /&gt;
** '''&amp;lt;runway name&amp;gt;'''&lt;br /&gt;
*** '''id''': Name of runway.&lt;br /&gt;
*** '''lat''': Latitude of the runway.&lt;br /&gt;
*** '''lon''': Longitude of the runway.&lt;br /&gt;
*** '''heading''': Heading of the runway.&lt;br /&gt;
*** '''length''': Length of the runway in metres.&lt;br /&gt;
*** '''width''': Width of the runway in metres.&lt;br /&gt;
*** '''surface''': Runway surface type.&lt;br /&gt;
*** '''threshold''': Length of the runway's {{wikipedia|displaced threshold}} in metres. Will return 0 if there is none.&lt;br /&gt;
*** '''stopway''': Length of the runway's stopway (the area before the threshold) in metres. Will return 0 if there is none.&lt;br /&gt;
*** '''reciprocal''': &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt; ghost of the reciprocal runway.&lt;br /&gt;
*** '''ils_frequency_mhz''': ILS frequency in megahertz.&lt;br /&gt;
*** '''ils''': &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost of the ILS transmitter.&lt;br /&gt;
* '''helipads'''&lt;br /&gt;
** '''&amp;lt;helipad name&amp;gt;'''&lt;br /&gt;
*** '''id''': Name of helipad.&lt;br /&gt;
*** '''lat''': Latitude of the helipad.&lt;br /&gt;
*** '''lon''': Longitude of the helipad.&lt;br /&gt;
*** '''heading''': Heading of the helipad.&lt;br /&gt;
*** '''length''': Length of the helipad in metres.&lt;br /&gt;
*** '''width''': Width of the helipad in metres.&lt;br /&gt;
*** '''surface''': Helipad surface type.&lt;br /&gt;
* '''taxiways'''&lt;br /&gt;
** '''&amp;lt;taxiway name&amp;gt;'''&lt;br /&gt;
*** '''id''': Name of taxiway.&lt;br /&gt;
*** '''lat''': Latitude of the taxiway.&lt;br /&gt;
*** '''lon''': Longitude of the taxiway.&lt;br /&gt;
*** '''heading''': Heading of the taxiway.&lt;br /&gt;
*** '''length''': Length of the taxiway in metres.&lt;br /&gt;
*** '''width''': Width of the taxiway in metres.&lt;br /&gt;
*** '''surface''': Taxiway surface type.&lt;br /&gt;
&lt;br /&gt;
Information is extracted in the same way as accessing members of a Nasal hash. For example:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
# prints to lengths of the runways of the nearest airport in feet and metres&lt;br /&gt;
var info = airportinfo();&lt;br /&gt;
print(&amp;quot;-- Lengths of the runways at &amp;quot;, info.name, &amp;quot; (&amp;quot;, info.id, &amp;quot;) --&amp;quot;);&lt;br /&gt;
foreach(var rwy; keys(info.runways)){&lt;br /&gt;
    print(rwy, &amp;quot;: &amp;quot;, math.round(info.runways[rwy].length * M2FT), &amp;quot; ft (&amp;quot;, info.runways[rwy].length, &amp;quot; m)&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that searches for locations that are a long way away (e.g., the nearest seaplane base to the middle of the Sahara) may cause FlightGear to pause for an amount of time.&lt;br /&gt;
|param1 = id&lt;br /&gt;
|param1text = The {{wikipedia|International Civil Aviation Organization airport code|ICAO code|noicon=1}} of an airport to retrieve information about.&lt;br /&gt;
|param2 = type&lt;br /&gt;
|param2text = When this argument is used, the function will return the closest airport of a certain type. Can be one of &amp;quot;heliport,&amp;quot; &amp;quot;seaport,&amp;quot; or &amp;quot;airport&amp;quot; (default).&lt;br /&gt;
: {{inote|Running this function without any parameters is equivalent to this:&lt;br /&gt;
: &amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
airportinfo(&amp;quot;airport&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
|param3 = lat ''and'' lon&lt;br /&gt;
|param3text = When these parameters are used, the function will return information on the nearest airport, heliport or seaplane base (depending on the '''type''' parameter) to those coordinates.&lt;br /&gt;
|example1 = var info = airportinfo();&lt;br /&gt;
print(&amp;quot;Nearest airport: &amp;quot;, info.name, &amp;quot; (&amp;quot;, info.id, &amp;quot;)&amp;quot;); # prints the name and ICAO code of the nearest airport&lt;br /&gt;
|example2 = var info = airportinfo(&amp;quot;heliport&amp;quot;);&lt;br /&gt;
print(&amp;quot;Elevation of the nearest heliport: &amp;quot;, math.round(info.elevation * M2FT), &amp;quot; ft&amp;quot;); # prints the elevation and name of the nearest heliport&lt;br /&gt;
|example3 = var info = airportinfo(&amp;quot;KSQL&amp;quot;);&lt;br /&gt;
print(&amp;quot;-- Runways of &amp;quot;, info.name, &amp;quot; (&amp;quot;, info.id, &amp;quot;): --&amp;quot;);&lt;br /&gt;
foreach(var rwy; keys(info.runways)) {&lt;br /&gt;
    print(rwy); # prints the runways of KSQL&lt;br /&gt;
}&lt;br /&gt;
|example4 = var info = airportinfo(37.81909385, -122.4722484);&lt;br /&gt;
print(&amp;quot;Coordinates of the nearest airport: &amp;quot;, info.lat, &amp;quot;, &amp;quot;, info.lon); # print the name and ICAO of the nearest airport to the Golden Gate Bridge&lt;br /&gt;
|example5 = var info = airportinfo(37.81909385, -122.4722484, &amp;quot;seaport&amp;quot;);&lt;br /&gt;
print(&amp;quot;Nearest seaplane base: &amp;quot;, info.name, &amp;quot; (&amp;quot;, info.id, &amp;quot;)&amp;quot;); # print the name and ID of the nearest seaplane base to the Golden Gate Bridge&lt;br /&gt;
|example6text = This example prints the all information from an &amp;lt;code&amp;gt;airportinfo()&amp;lt;/code&amp;gt; call.&lt;br /&gt;
|example6 = var info = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
print(info.name);&lt;br /&gt;
print(info.id);&lt;br /&gt;
print(info.lat);&lt;br /&gt;
print(info.lon);&lt;br /&gt;
print(info.has_metar);&lt;br /&gt;
print(info.elevation);&lt;br /&gt;
foreach(var rwy; keys(info.runways)){&lt;br /&gt;
    print(&amp;quot;-- &amp;quot;, rwy, &amp;quot; --&amp;quot;);&lt;br /&gt;
    print(info.runways[rwy].lat);&lt;br /&gt;
    print(info.runways[rwy].lon);&lt;br /&gt;
    print(info.runways[rwy].length);&lt;br /&gt;
    print(info.runways[rwy].width);&lt;br /&gt;
    print(info.runways[rwy].heading);&lt;br /&gt;
    print(info.runways[rwy].stopway);&lt;br /&gt;
    print(info.runways[rwy].threshold);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===airwaysRoute() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = airwaysRoute(start, end[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1933|t=Source}}&lt;br /&gt;
|text = {{see also|Nasal Flightplan}}&lt;br /&gt;
This function returns a vector containing waypoints between two given waypoints. The returned waypoints are ghosts, but can be accessed in the same way as a Nasal hash. See [[Nasal Flightplan]] for more information.&lt;br /&gt;
|param1 = start&lt;br /&gt;
|param1text = Start waypoint, in the form of a waypoint ghost, such as that provided by {{func link|flightplan()}}.&lt;br /&gt;
|param2 = end&lt;br /&gt;
|param2text = Same as above.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = Instructs the function to compute a high level route (when set to &amp;quot;highlevel&amp;quot;), or a low level route (when set to &amp;quot;lowlevel&amp;quot;). Defaults to &amp;quot;highlevel.&amp;quot;&lt;br /&gt;
|example1text = In the [[route manager]] dialog, add two waypoints to the flightplan, ideally ones that are far apart (tip: use the [[Map]] for this). Then run this code in your Nasal Console.&lt;br /&gt;
|example1 = var fp = flightplan();&lt;br /&gt;
var start = fp.getWP(0);&lt;br /&gt;
var end = fp.getWP(fp.getPlanSize() - 1);&lt;br /&gt;
var rt = airwaysRoute(start, end);&lt;br /&gt;
foreach(var wp; rt){&lt;br /&gt;
    print(wp.wp_name); # print the waypoints in the computed route&lt;br /&gt;
}&lt;br /&gt;
|example2text = Exactly the same as above, but computes a low level path.&lt;br /&gt;
|example2 = var fp = flightplan();&lt;br /&gt;
var start = fp.getWP(0);&lt;br /&gt;
var end = fp.getWP(fp.getPlanSize() - 1);&lt;br /&gt;
var rt = airwaysRoute(start, end, &amp;quot;lowlevel&amp;quot;);&lt;br /&gt;
foreach(var wp; rt){&lt;br /&gt;
    print(wp.wp_name); # print the waypoints in the computed route&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===airway()===&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = airway(ident [, pos]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2644|t=Source}}&lt;br /&gt;
|text = {{see also|Nasal Flightplan}}&lt;br /&gt;
This function returns a ghost containing an airway of a specified id.&lt;br /&gt;
|param1 = ident&lt;br /&gt;
|param1text = a Positioned ghost (leg, navaid, airport) and so on, passed to the search function.&lt;br /&gt;
|param2 = pos&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===assert()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = assert(condition[, message]);&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|version = 3.2&lt;br /&gt;
|commit = {{fgdata commit|8b16a7|t=commit}}&lt;br /&gt;
|text = Returns either true if the condition evaluates as true, or aborts with a {{func link|die()}} call, which can be customised.&lt;br /&gt;
|param1 = condition&lt;br /&gt;
|param1text = Condition to evaluate.&lt;br /&gt;
|param2 = message&lt;br /&gt;
|param2text = Optional message that will be used in any {{func link|die()}} call. Defaults to &amp;quot;assertion failed!&amp;quot;&lt;br /&gt;
|example1 = var a = 1;&lt;br /&gt;
var b = 2;&lt;br /&gt;
print(assert(a &amp;lt; b)); # prints &amp;quot;1&amp;quot; (true)&lt;br /&gt;
|example2 = var a = 1;&lt;br /&gt;
var b = 2;&lt;br /&gt;
assert(a &amp;gt; b, 'a is not greater than b'); # aborts with a custom error message&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===carttogeod()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = carttogeod(x, y, z);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=945|t=Source}}&lt;br /&gt;
|text = Converts {{wikipedia|ECEF|Earth-centered, Earth-fixed}} coordinates (x, y and z) to {{wikipedia|geodetic coordinates}} (latitude, longitude, and altitude). A vector is returned containing latitude and longitude, both in degrees, and altitude, which is returned in metres above the equatorial radius of Earth as defined by the {{wikipedia|WGS 84}} (6,378,137 metres).&amp;lt;ref&amp;gt;{{simgear file|simgear/math/sg_geodesy.hxx|l=43}}&amp;lt;/ref&amp;gt;&lt;br /&gt;
|param1 = x&lt;br /&gt;
|param1text = Mandatory x-axis value, in metres.&lt;br /&gt;
|param2 = y&lt;br /&gt;
|param2text = Mandatory y-axis value, in metres.&lt;br /&gt;
|param3 = z&lt;br /&gt;
|param3text = Mandatory z-axis value, in metres.&lt;br /&gt;
|example1 = var (lat, lon, alt) = carttogeod(6378137, 0, 0); # point is the intersection of the prime meridian and equator.&lt;br /&gt;
print(&amp;quot;Latitude: &amp;quot;, lat); # prints lat, lon and alt, which are all zero, see above&lt;br /&gt;
print(&amp;quot;Longitude: &amp;quot;, lon);&lt;br /&gt;
print(&amp;quot;Altitude: &amp;quot;, alt);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===cmdarg()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|private = _cmdarg()&lt;br /&gt;
|syntax = cmdarg();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=513|t=Part 1}} {{!}} {{fgdata file|Nasal/globals.nas|t=Part 2}}&lt;br /&gt;
|text = &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; returns the property root of certain types of XML files. These could be nodes in the [[Property Tree]], or temporary and/or non-public nodes outside the Property tree. &lt;br /&gt;
It is used by Nasal scripts embedded in XML files. It returns a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object (see {{fgdata file|Nasal/props.nas}}), and you can use all of its methods on the returned value. &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; should only be used in four types/places of XML files:&lt;br /&gt;
* Bindings: This is needed so that the value of a joystick's axis can be accessed internally.&lt;br /&gt;
* Dialogs: This will return the root of the dialog in the Property Tree. This is useful for dialogs that are created/modified procedurally (e.g. for populating/changing widgets while loading the dialog). &lt;br /&gt;
* Embedded Canvases: The Nasal code behind [[Canvas]] windows [[Howto:Adding a canvas to a GUI dialog|embedded in PUI dialogs]] can use it to accessing the root directory of their Canvas.&lt;br /&gt;
* Animation XML files: If the animation XML file is used in an AI/MP model, &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; will return the root of the AI model in the &amp;lt;code&amp;gt;/ai/models/&amp;lt;/code&amp;gt; directory. Examples: &amp;lt;code&amp;gt;/ai/models/aircraft[3]/&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;/ai/models/multiplayer[1]/&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You should not use &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; in places other than those stated above. Although it won't cause an error, it will return the value of the last legitimate &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; call. &lt;br /&gt;
&lt;br /&gt;
Also, you should not delay &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; using {{func link|maketimer()}}, {{func link|settimer()}} or {{func link|setlistener()}}, because it will return an unrelated property.&lt;br /&gt;
|example1 = fgcommand(&amp;quot;dialog-show&amp;quot;, {&amp;quot;dialog-name&amp;quot;: &amp;quot;cmdarg-demo&amp;quot;});&lt;br /&gt;
|example1text = &amp;lt;br&amp;gt;This example demonstrates the usage of &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; in a binding.  Save the below XML snippet as &amp;lt;tt&amp;gt;[[$FG_ROOT]]/gui/dialogs/cmdarg-demo.xml&amp;lt;/tt&amp;gt;. Then run the Nasal snippet below in your [[Nasal Console]]. Upon clicking {{button|Close}}, a message will be printed sowing the root of the binding in the Property Tree.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PropertyList&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;name&amp;gt;cmdarg-demo&amp;lt;/name&amp;gt;&lt;br /&gt;
&amp;lt;layout&amp;gt;vbox&amp;lt;/layout&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;text&amp;gt;&lt;br /&gt;
  &amp;lt;label&amp;gt;Click &amp;quot;Close&amp;quot; to activate the demonstration (a message in the console).&amp;lt;/label&amp;gt;&lt;br /&gt;
&amp;lt;/text&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;button&amp;gt;&lt;br /&gt;
  &amp;lt;legend&amp;gt;Close&amp;lt;/legend&amp;gt;&lt;br /&gt;
  &amp;lt;binding&amp;gt;&lt;br /&gt;
    &amp;lt;command&amp;gt;nasal&amp;lt;/command&amp;gt;&lt;br /&gt;
    &amp;lt;script&amp;gt;print(&amp;quot;Button binding root: '&amp;quot; ~ cmdarg().getPath() ~ &amp;quot;'&amp;quot;);&amp;lt;/script&amp;gt;&lt;br /&gt;
  &amp;lt;/binding&amp;gt;&lt;br /&gt;
  &amp;lt;binding&amp;gt;&lt;br /&gt;
    &amp;lt;command&amp;gt;dialog-close&amp;lt;/command&amp;gt;&lt;br /&gt;
  &amp;lt;/binding&amp;gt;&lt;br /&gt;
&amp;lt;/button&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/PropertyList&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|example2text = This example demonstrates the usage of &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; in Nasal code within dialogs.  Open &amp;lt;tt&amp;gt;[[$FG_ROOT]]/gui/dialogs/cmdarg-demo.xml&amp;lt;/tt&amp;gt; from the previous example, copy &amp;amp; paste the code below, and save it. Then run the same Nasal snippet as the previous example in your Nasal Console. If you click {{button|Click me!}}, the button's label will change to &amp;quot;I've been changed!&amp;quot;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PropertyList&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;name&amp;gt;cmdarg-demo&amp;lt;/name&amp;gt;&lt;br /&gt;
&amp;lt;layout&amp;gt;vbox&amp;lt;/layout&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;text&amp;gt;&lt;br /&gt;
  &amp;lt;label&amp;gt;Click &amp;quot;Click me!&amp;quot; to activate the demonstration (the button's label will change).&amp;lt;/label&amp;gt;&lt;br /&gt;
&amp;lt;/text&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;button&amp;gt;&lt;br /&gt;
  &amp;lt;legend&amp;gt;Click me!&amp;lt;/legend&amp;gt;&lt;br /&gt;
  &amp;lt;binding&amp;gt;&lt;br /&gt;
    &amp;lt;command&amp;gt;nasal&amp;lt;/command&amp;gt;&lt;br /&gt;
    &amp;lt;script&amp;gt;change_label();&amp;lt;/script&amp;gt;&lt;br /&gt;
  &amp;lt;/binding&amp;gt;&lt;br /&gt;
&amp;lt;/button&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;button&amp;gt;&lt;br /&gt;
  &amp;lt;legend&amp;gt;Close&amp;lt;/legend&amp;gt;&lt;br /&gt;
  &amp;lt;binding&amp;gt;&lt;br /&gt;
    &amp;lt;command&amp;gt;dialog-close&amp;lt;/command&amp;gt;&lt;br /&gt;
  &amp;lt;/binding&amp;gt;&lt;br /&gt;
&amp;lt;/button&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nasal&amp;gt;&lt;br /&gt;
  &amp;lt;open&amp;gt;&amp;lt;![CDATA[&lt;br /&gt;
    var dlg_root = cmdarg();&lt;br /&gt;
    var dlg_name = {&amp;quot;dialog-name&amp;quot;: &amp;quot;cmdarg-demo&amp;quot;};&lt;br /&gt;
    var change_label = func {&lt;br /&gt;
        dlg_root.getNode(&amp;quot;button[0]/legend&amp;quot;).setValue(&amp;quot;I've been changed!&amp;quot;);&lt;br /&gt;
        fgcommand(&amp;quot;dialog-close&amp;quot;, dlg_name);&lt;br /&gt;
        fgcommand(&amp;quot;dialog-show&amp;quot;, dlg_name);&lt;br /&gt;
    }&lt;br /&gt;
  ]]&amp;gt;&amp;lt;/open&amp;gt;&lt;br /&gt;
&amp;lt;/nasal&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/PropertyList&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|example3text = For an example of &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; used with Canvas, please see [[Howto:Adding a canvas to a GUI dialog#FGPlot|Howto:Adding a canvas to a GUI dialog]].&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===courseAndDistance()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = courseAndDistance(to);&lt;br /&gt;
courseAndDistance(from, to);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1668|t=Source}}&lt;br /&gt;
|text = Returns a vector containing the course from one point to another and the distance between them in nautical miles. The course is the initial bearing (see [http://www.movable-type.co.uk/scripts/latlong.html#bearing here]), and is in the range 0–360. Both arguments can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object with geodetic coordinates (cartesian coordinates will not be accepted)&lt;br /&gt;
|param1 = from&lt;br /&gt;
|param1text = Optional parameter defining the from where the function should calculate its results. If the function is given one argument ('''to'''), the aircraft's current position will be used. As well as the argument types as defined above, this argument can be two numbers separated with a comma, as if the function is taking three arguments. See example 5 below.&lt;br /&gt;
|param2 = to&lt;br /&gt;
|param2text = Like the first parameter, but defines the second point.&lt;br /&gt;
|example1text = This example demonstrates the usage of the function with the &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt; ghost type.&lt;br /&gt;
|example1 = var from = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var to = airportinfo(&amp;quot;KSQL&amp;quot;);&lt;br /&gt;
var (course, dist) = courseAndDistance(from, to);&lt;br /&gt;
print(course); # prints course from KSFO to KSQL&lt;br /&gt;
print(dist); # prints distance in nm from KSFO to KSQL&lt;br /&gt;
|example2text = This example demonstrates the usage of the function with hashes containing ''lat'' and ''lon''.&lt;br /&gt;
|example2 = var from = {lat: 0, lon: 0};&lt;br /&gt;
var to = {lat: 1, lon: 1};&lt;br /&gt;
var (course, dist) = courseAndDistance(from, to);&lt;br /&gt;
print(course);&lt;br /&gt;
print(dist);&lt;br /&gt;
|example3text = This example demonstrates usage of a geo.Coord object.&lt;br /&gt;
|example3 = var from = geo.Coord.new().set_latlon(0, 0);&lt;br /&gt;
var to = geo.Coord.new().set_latlon(1, 1);&lt;br /&gt;
var (course, dist) = courseAndDistance(from, to);&lt;br /&gt;
print(course);&lt;br /&gt;
print(dist);&lt;br /&gt;
|example4text = This example demonstrates usage of differing parameter types.&lt;br /&gt;
|example4 = var from = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var to = geo.Coord.new().set_latlon(0, 0);&lt;br /&gt;
var (course, dist) = courseAndDistance(from, to);&lt;br /&gt;
print(course);&lt;br /&gt;
print(dist);&lt;br /&gt;
|example5text = The same as above, but the other way round.&lt;br /&gt;
|example5 = var to = {lat: 1, lon: 1};&lt;br /&gt;
var (course, dist) = courseAndDistance(0, 0, to);&lt;br /&gt;
print(course);&lt;br /&gt;
print(dist);&lt;br /&gt;
|example6text = Usage of just one parameter.&lt;br /&gt;
|example6 = var dest = airportinfo(&amp;quot;KSQL&amp;quot;);&lt;br /&gt;
var (course, dist) = courseAndDistance(dest);&lt;br /&gt;
print(&amp;quot;Turn to heading &amp;quot;, math.round(course), &amp;quot;. You have &amp;quot;, sprintf(&amp;quot;%.2f&amp;quot;, dist), &amp;quot; nm to go&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===createFlightplan()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = createFlightplan(path);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2331|t=Source}}&lt;br /&gt;
|text = Creates an empty flightplan object. It accepts one argument, ''path'' passed an absolute path to a .fgfp / .gpx file, it will populate the flightplan with waypoints from the file.&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Optional parameter defining the file from which a flightplan will be populated.&lt;br /&gt;
|example1 = &lt;br /&gt;
var path = getprop(&amp;quot;/sim/fg-home&amp;quot;) ~ &amp;quot;/Export/test.fgfp&amp;quot;;&lt;br /&gt;
var flightplan = createFlightplan(path);&lt;br /&gt;
debug.dump(flightplan);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===createDiscontinuity()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = createDiscontinuity();&lt;br /&gt;
|text = Returns a &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost object. A route discontinuity is inserted by an {{abbr|FMS|Flight Management System}} when it is unsure how to connect two waypoints.&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2045|t=Source}}&lt;br /&gt;
|version = 2016.1&lt;br /&gt;
|commit = {{flightgear commit|caead6|t=commit}}&lt;br /&gt;
}}&lt;br /&gt;
===createViaTo()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = createViaTo(airway, waypoint);&lt;br /&gt;
|text = Returns a &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost object. It represents a route &amp;quot;via '''airway''' to '''waypoint'''&amp;quot;.&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2009|t=Source}}&lt;br /&gt;
|version = 2016.1&lt;br /&gt;
|commit = {{flightgear commit|caead6|t=commit}}&lt;br /&gt;
|param1 = airway&lt;br /&gt;
|param1text = The name of an airway.&lt;br /&gt;
|param2 = waypoint&lt;br /&gt;
|param2text = Must be in the airway and one of:&lt;br /&gt;
* The name of a waypoint.&lt;br /&gt;
* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt; ghost object.&lt;br /&gt;
}}&lt;br /&gt;
===createWP()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = createWP(pos, name[, flag]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1964|t=Source}}&lt;br /&gt;
|text = Creates a new waypoint ghost object.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Dictates the position of the new waypoint. It can be one of the following:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. See example 4 below.&lt;br /&gt;
|param2 = name&lt;br /&gt;
|param2text = String that will become the name of the new waypoint.&lt;br /&gt;
|param3 = flag&lt;br /&gt;
|param3text = Optional string that will tell FlightGear what type of waypoint it is. Must be one of &amp;quot;sid,&amp;quot; &amp;quot;star,&amp;quot; &amp;quot;approach,&amp;quot; &amp;quot;missed,&amp;quot; or &amp;quot;pseudo.&amp;quot;&lt;br /&gt;
|example1text = Creates a waypoint directly in front and 1 km away and appends it to the flight plan.&lt;br /&gt;
|example1 = var pos = geo.aircraft_position().apply_course_distance(getprop(&amp;quot;/orientation/heading-deg&amp;quot;), 1000);&lt;br /&gt;
var wp = createWP(pos, &amp;quot;NEWWP&amp;quot;);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
|example2 = var pos = geo.aircraft_position().apply_course_distance(getprop(&amp;quot;/orientation/heading-deg&amp;quot;), 1000);&lt;br /&gt;
var wp = createWP({lat: pos.lat(), lon: pos.lon()}, &amp;quot;NEWWP&amp;quot;);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
|example3 = var apt = airportinfo();&lt;br /&gt;
var wp = createWP(apt, &amp;quot;NEWWP&amp;quot;);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
|example4 = var pos = geo.aircraft_position().apply_course_distance(getprop(&amp;quot;/orientation/heading-deg&amp;quot;), 1000);&lt;br /&gt;
var wp = createWP(pos.lat(), pos.lon(), &amp;quot;NEWWP&amp;quot;);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
|example5text = Creates a new waypoint and adds it to the flight plan. Waypoints of the type &amp;quot;pseudo&amp;quot; are then removed from the flight plan, including the new waypoint. The {{func link|print()}} statements show this.&lt;br /&gt;
|example5 = var pos = geo.aircraft_position().apply_course_distance(getprop(&amp;quot;/orientation/heading-deg&amp;quot;), 1000);&lt;br /&gt;
var wp = createWP(pos, &amp;quot;NEWWP&amp;quot;, &amp;quot;pseudo&amp;quot;);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
print(fp.getPlanSize());&lt;br /&gt;
fp.clearWPType(&amp;quot;pseudo&amp;quot;);&lt;br /&gt;
print(fp.getPlanSize());&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===createWPFrom()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = createWPFrom(object[, flag]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1989|t=Source}}&lt;br /&gt;
|text = Creates a new waypoint object from another object.&lt;br /&gt;
|param1 = object&lt;br /&gt;
|param1text = A ghost object. Must be a ghost type that is one of &amp;quot;airport,&amp;quot; &amp;quot;navaid,&amp;quot; &amp;quot;runway,&amp;quot; or &amp;quot;fix.&amp;quot;&lt;br /&gt;
|param2 = flag&lt;br /&gt;
|param2text = Optional string that will tell FlightGear what type of waypoint it is. Must be one of &amp;quot;sid,&amp;quot; &amp;quot;star,&amp;quot; &amp;quot;approach,&amp;quot; &amp;quot;missed,&amp;quot; or &amp;quot;pseudo.&amp;quot;&lt;br /&gt;
|example1text = Creates a new waypoint and appends it to the flight plan.&lt;br /&gt;
|example1 = var apt = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var wp = createWPFrom(apt);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
|example2text = Creates a new waypoint and appends it to the flight plan. This way point is then removed; the {{func link|print()}} statements prove this.&lt;br /&gt;
|example2 = var apt = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var wp = createWPFrom(apt, &amp;quot;pseudo&amp;quot;);&lt;br /&gt;
print(wp.wp_name);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
print(fp.getPlanSize());&lt;br /&gt;
fp.clearWPType(&amp;quot;pseudo&amp;quot;);&lt;br /&gt;
print(fp.getPlanSize());&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===defined()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = defined(symbol);&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Returns 1 (true) or 0 (false) depending on whether a variable exists.&lt;br /&gt;
|param1 = symbol&lt;br /&gt;
|param1text = A string that will be what the function searches for.&lt;br /&gt;
|example1 = var number = 12;&lt;br /&gt;
var check_exist = func {&lt;br /&gt;
    print(&amp;quot;Variable 'number' &amp;quot;, defined(&amp;quot;number&amp;quot;) == 1 ? &amp;quot;exists&amp;quot; : &amp;quot;does not exist&amp;quot;); # 'number' does exist&lt;br /&gt;
    print(&amp;quot;Variable 'number2' &amp;quot;, defined(&amp;quot;number2&amp;quot;) == 1 ? &amp;quot;exists&amp;quot; : &amp;quot;does not exist&amp;quot;); # 'number2' does not exist&lt;br /&gt;
}&lt;br /&gt;
check_exist();&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===directory()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = directory(path);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=572|t=Source}}&lt;br /&gt;
|text = Returns a vector containing a list of the folders and files in a given file path or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if the path doesn't exist. Hidden folders and files are not added to the vector.&lt;br /&gt;
{{inote|The first two elements of the vector will be &amp;lt;code&amp;gt;'.'&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;'..'&amp;lt;/code&amp;gt;. These are for navigating back up the file tree, but have no use in Nasal. They can be safely removed from the vector.}}&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Absolute file path.&lt;br /&gt;
|example1text = Gets the folders and files in [[$FG_ROOT]], and then removes the extra first two elements (see note above). &lt;br /&gt;
|example1 = var dir = directory(getprop(&amp;quot;/sim/fg-root&amp;quot;)); # get directory&lt;br /&gt;
dir = subvec(dir, 2); # strips off the first two elements&lt;br /&gt;
debug.dump(dir); # dump the vector&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===fgcommand()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = fgcommand(cmd[, args]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=456|t=Part 1}} {{!}} {{fgdata file|Nasal/globals.nas|t=Part 2}}&lt;br /&gt;
|text = Runs an fgcommand. See also {{readme file|commands}} and [[Bindings]] for more information. See {{flightgear file|src/Main/fg_commands.cxx|l=1425}} for the full list of fgcommands. Note that fgcommands generated by {{func link|addcommand()}} can also be run using this function. Also, the full list of fgcommands depends on the version of FlightGear you have. Returns 1 (true) if the fgcommand succeeded or 0 (false) if it failed.&lt;br /&gt;
|param1 = cmd&lt;br /&gt;
|param1text = String that is the name of the command that is to be run.&lt;br /&gt;
|param2 = args&lt;br /&gt;
|param2text = If the fgcommand takes arguments, they are inputted using this argument. Can either be a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object, or a hash (see examples below).&lt;br /&gt;
|example1 = fgcommand(&amp;quot;null&amp;quot;); # does nothing&lt;br /&gt;
|example2 = var args = props.Node.new({'script': 'print(&amp;quot;Running fgcommand&amp;quot;);'});&lt;br /&gt;
if (fgcommand(&amp;quot;nasal&amp;quot;, args)) { # prints &amp;quot;Running fgcommand&amp;quot; and then one of these print statements&lt;br /&gt;
    print(&amp;quot;Fgcommand succeeded&amp;quot;);&lt;br /&gt;
} else {&lt;br /&gt;
    print(&amp;quot;Fgcommand encountered a problem&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
|example3 = var args = { 'dialog-name': 'about' };&lt;br /&gt;
fgcommand(&amp;quot;dialog-show&amp;quot;, args); # shows the 'about' dialog&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findAirportsByICAO() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findAirportsByICAO(search[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1096|t=Source}}&lt;br /&gt;
|text = Returns a vector containing &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt; ghost objects which are (by default) airports whose ICAO code matches the search string. The results are sorted by range from closest to furthest.&lt;br /&gt;
|param1 = search&lt;br /&gt;
|param1text = Search string for the function. Can either be a partial or a full ICAO code.&lt;br /&gt;
:{{icaution|The more matches there are for the given code, the longer the function will take. Passing just one character (e.g., &amp;quot;K&amp;quot;), might make FlightGear hang for a certain amount of time.}}&lt;br /&gt;
|param2 = type&lt;br /&gt;
|param2text = This will narrow the search to airports of a certain type. By default, only airports are searched for. May be one of &amp;quot;airport,&amp;quot; &amp;quot;heliport,&amp;quot; or &amp;quot;seaport.&amp;quot;&lt;br /&gt;
|example1 = var apts = findAirportsByICAO(&amp;quot;KSF&amp;quot;); # finds all airports matching &amp;quot;KSF&amp;quot;&lt;br /&gt;
foreach(var apt; apts){&lt;br /&gt;
    print(apt.name, &amp;quot; (&amp;quot;, apt.id, &amp;quot;)&amp;quot;); # prints them&lt;br /&gt;
}&lt;br /&gt;
|example2 = var apts = findAirportsByICAO(&amp;quot;SP0&amp;quot;, &amp;quot;seaport&amp;quot;); # finds all seaplane bases matching &amp;quot;SP0&amp;quot;&lt;br /&gt;
foreach(var apt; apts){&lt;br /&gt;
    print(apt.name, &amp;quot; (&amp;quot;, apt.id, &amp;quot;)&amp;quot;); # prints them&lt;br /&gt;
}&lt;br /&gt;
|example3 = var apt = findAirportsByICAO(&amp;quot;XBET&amp;quot;); # one way to check if an airport does exist&amp;quot;&lt;br /&gt;
if (size(apt) == 0) {&lt;br /&gt;
    print(&amp;quot;Airport does not exist&amp;quot;); # this one will be printed&lt;br /&gt;
} else {&lt;br /&gt;
    print(&amp;quot;Airport does exist&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findAirportsWithinRange()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findAirportsWithinRange([pos, ]range[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1066|t=Source}}&lt;br /&gt;
|text = Returns a vector of &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt; ghost object which are (by default) airports that are within a given range of a given position, or the aircraft's current position. The results are sorted by range from closest to furthest.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findAirportsWithinRange(lat, lon, range, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = range&lt;br /&gt;
|param2text = Mandatory number giving the range in nautical miles within which to search for airports/heliports/seaplane bases.only airports are searched for.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = This will narrow the search to airports of a certain type. By default, only airports are searched for. May be one of &amp;quot;airport,&amp;quot; &amp;quot;heliport,&amp;quot; or &amp;quot;seaport.&amp;quot;&lt;br /&gt;
|example1text = Searches for airports within 10 nm of [[KSFO]].&lt;br /&gt;
|example1 = var pos = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var apts = findAirportsWithinRange(pos, 10);&lt;br /&gt;
foreach(var apt; apts){&lt;br /&gt;
    print(apt.name, &amp;quot; (&amp;quot;, apt.id, &amp;quot;)&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
|example2text = Searches for seaplane bases within 15 nm of [[KSFO]].&lt;br /&gt;
|example2 = var pos = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var apts = findAirportsWithinRange(pos, 15, &amp;quot;seaport&amp;quot;);&lt;br /&gt;
foreach(var apt; apts){&lt;br /&gt;
    print(apt.name, &amp;quot; (&amp;quot;, apt.id, &amp;quot;)&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
|example3text = Searches for airports within 10 nm of your current position.&lt;br /&gt;
|example3 = var apts = findAirportsWithinRange(10);&lt;br /&gt;
foreach(var apt; apts){&lt;br /&gt;
    print(apt.name, &amp;quot; (&amp;quot;, apt.id, &amp;quot;)&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findCommByFrequencyMHz()===&lt;br /&gt;
 findCommByFrequencyMHz([pos, ]freq[, type]);&lt;br /&gt;
[[sourceforge:p/flightgear/flightgear/ci/next/tree/src/Scripting/NasalPositioned.cxx#l1547|source]]&lt;br /&gt;
&lt;br /&gt;
Returns a &amp;lt;code&amp;gt;comm&amp;lt;/code&amp;gt; ghost object for a station matching a given frequency. If there is more than one station with that frequency, the nearest station is returned.&lt;br /&gt;
&lt;br /&gt;
'''pos'''&lt;br /&gt;
&lt;br /&gt;
: Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findCommByFrequencyMHz(lat, lon, freq, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
; freq&lt;br /&gt;
: Frequency, in megahertz, of the station to search for.&lt;br /&gt;
; type&lt;br /&gt;
: This will narrow the search to station of a certain type. Defaults to &amp;quot;all.&amp;quot; For the full list of accepted type arguments, see flightgear/src/Navaids/positioned.cxx (line 160)&lt;br /&gt;
'''Example'''&lt;br /&gt;
 var com = findCommByFrequencyMHz(123.6);&lt;br /&gt;
 print(&amp;quot;ID: &amp;quot;, com.id); # prints info about the comm station&lt;br /&gt;
 print(&amp;quot;Name: &amp;quot;, com.name);&lt;br /&gt;
 print(&amp;quot;Latitude: &amp;quot;, com.lat);&lt;br /&gt;
 print(&amp;quot;Longitude: &amp;quot;, com.lon);&lt;br /&gt;
 print(&amp;quot;Type: &amp;quot;, com.type);&lt;br /&gt;
 print(&amp;quot;Frequency: &amp;quot;, sprintf(&amp;quot;%.3f&amp;quot;, com.frequency), &amp;quot; Mhz&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
=== findFixesByID() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findFixesByID([pos, ]id);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1627|t=Source}}&lt;br /&gt;
|text = Returns a vector containing &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt; ghost objects matching a given ID, sorted by range from a certain position.&lt;br /&gt;
{{inote|Fixes are (usually) also known as waypoints.}}&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findFixesByID(lat, lon, id);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = id&lt;br /&gt;
|param2text = Full or partial ID of the fix to search for.&lt;br /&gt;
:{{inote|1=Inputting a partial ID does not work correctly (see [http://forum.flightgear.org/viewtopic.php?f=30&amp;amp;t=28129 here]). It is best to just input a full ID.}}&lt;br /&gt;
|example1 = var fixes = findFixesByID(&amp;quot;POGIC&amp;quot;);&lt;br /&gt;
foreach(var fix; fixes){&lt;br /&gt;
    print(fix.id, &amp;quot; - lat: &amp;quot;, fix.lat, &amp;quot; {{!}} lon: &amp;quot;, fix.lon); # prints information about POGIC&lt;br /&gt;
}&lt;br /&gt;
|example2 = var fix = findFixesByID(&amp;quot;ZUNAP&amp;quot;);&lt;br /&gt;
fix = fix[0];&lt;br /&gt;
var (course, dist) = courseAndDistance(fix);&lt;br /&gt;
print(&amp;quot;Turn to heading &amp;quot;, math.round(course), &amp;quot;. You have &amp;quot;, sprintf(&amp;quot;%.2f&amp;quot;, dist), &amp;quot; nm to go to reach &amp;quot;, fixes[0].id);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findNavaidByFrequencyMHz()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findNavaidByFrequencyMHz([pos, ]freq[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1547|t=Source}}&lt;br /&gt;
|text = Returns a &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost object for a navaid matching a given frequency. If there is more than one navaid with that frequency, the nearest station is returned.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findNavaidByFrequencyMHz(lat, lon, freq, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = freq&lt;br /&gt;
|param2text = Frequency, in megahertz, of the navaid to search for.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = This will narrow the search to navaids of a certain type. Defaults to &amp;quot;all.&amp;quot; For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.&lt;br /&gt;
|example1 = var navaid = findNavaidByFrequencyMHz(109.55);&lt;br /&gt;
print(&amp;quot;ID: &amp;quot;, navaid.id); # prints info about the navaid&lt;br /&gt;
print(&amp;quot;Name: &amp;quot;, navaid.name);&lt;br /&gt;
print(&amp;quot;Latitude: &amp;quot;, navaid.lat);&lt;br /&gt;
print(&amp;quot;Longitude: &amp;quot;, navaid.lon);&lt;br /&gt;
print(&amp;quot;Elevation (AMSL): &amp;quot;, navaid.elevation, &amp;quot; m&amp;quot;);&lt;br /&gt;
print(&amp;quot;Type: &amp;quot;, navaid.type);&lt;br /&gt;
print(&amp;quot;Frequency: &amp;quot;, sprintf(&amp;quot;%.3f&amp;quot;, navaid.frequency / 100), &amp;quot; Mhz&amp;quot;);&lt;br /&gt;
print(&amp;quot;Range: &amp;quot;, navaid.range_nm, &amp;quot; nm&amp;quot;);&lt;br /&gt;
if(navaid.course) print(&amp;quot;Course: &amp;quot;, navaid.course);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== findNavaidsByFrequencyMHz()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findNavaidsByFrequencyMHz([pos, ]freq[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1572|t=Source}}&lt;br /&gt;
|text = Returns a vector conatining &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost objects for navaids that match a given frequency, sorted from nearest to furthest.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findNavaidsByFrequencyMHz(lat, lon, freq, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = freq&lt;br /&gt;
|param2text = Frequency, in megahertz, of the navaid to search for.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = This will narrow the search to navaids of a certain type. Defaults to &amp;quot;all.&amp;quot; For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.&lt;br /&gt;
|example1 = var navaids = findNavaidsByFrequencyMHz(109.55);&lt;br /&gt;
foreach(var navaid; navaids){&lt;br /&gt;
    print(&amp;quot;--&amp;quot;);&lt;br /&gt;
    print(&amp;quot;ID: &amp;quot;, navaid.id); # prints info about the navaid&lt;br /&gt;
    print(&amp;quot;Name: &amp;quot;, navaid.name);&lt;br /&gt;
    print(&amp;quot;Latitude: &amp;quot;, navaid.lat);&lt;br /&gt;
    print(&amp;quot;Longitude: &amp;quot;, navaid.lon);&lt;br /&gt;
    print(&amp;quot;Elevation (AMSL): &amp;quot;, navaid.elevation, &amp;quot; m&amp;quot;);&lt;br /&gt;
    print(&amp;quot;Type: &amp;quot;, navaid.type);&lt;br /&gt;
    print(&amp;quot;Frequency: &amp;quot;, sprintf(&amp;quot;%.3f&amp;quot;, navaid.frequency / 100), &amp;quot; Mhz&amp;quot;);&lt;br /&gt;
    print(&amp;quot;Range: &amp;quot;, navaid.range_nm, &amp;quot; nm&amp;quot;);&lt;br /&gt;
    if(navaid.course) print(&amp;quot;Course: &amp;quot;, navaid.course);&lt;br /&gt;
    print(&amp;quot;--&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findNavaidsByID()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findNavaidsByID([pos, ]id[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1600|t=Source}}&lt;br /&gt;
|text = Returns a vector containing &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost objects matching a given ID, sorted by range from a certain position.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findNavaidsByID(lat, lon, id, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = id&lt;br /&gt;
|param2text = Full or partial ID of the fix to search for.&lt;br /&gt;
:{{inote|1=Inputting a partial ID does not work correctly (see [http://forum.flightgear.org/viewtopic.php?f=30&amp;amp;t=28129 here]). It is best to just input a full ID.}}&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = This will narrow the search to navaids of a certain type. Defaults to &amp;quot;all.&amp;quot; For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.&lt;br /&gt;
|example1 = var navaid = findNavaidsByID(&amp;quot;MXW&amp;quot;);&lt;br /&gt;
navaid = navaid[0];&lt;br /&gt;
print(&amp;quot;ID: &amp;quot;, navaid.id); # prints info about 'MXW' (a VOR station)&lt;br /&gt;
print(&amp;quot;Name: &amp;quot;, navaid.name);&lt;br /&gt;
print(&amp;quot;Latitude: &amp;quot;, navaid.lat);&lt;br /&gt;
print(&amp;quot;Longitude: &amp;quot;, navaid.lon);&lt;br /&gt;
print(&amp;quot;Elevation (AMSL): &amp;quot;, navaid.elevation, &amp;quot; m&amp;quot;);&lt;br /&gt;
print(&amp;quot;Type: &amp;quot;, navaid.type);&lt;br /&gt;
print(&amp;quot;Frequency: &amp;quot;, sprintf(&amp;quot;%.3f&amp;quot;, navaid.frequency / 1000), &amp;quot; Mhz&amp;quot;);&lt;br /&gt;
print(&amp;quot;Range: &amp;quot;, navaid.range_nm, &amp;quot; nm&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findNavaidsWithinRange()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findNavaidsWithinRange([pos, ]range[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1518|t=Source}}&lt;br /&gt;
|text = Returns a vector of &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost objects which are within a given range of a given position (by default the aircraft's current position). The results are sorted from closest to furthest.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findNavaidsWithinRange(lat, lon, range, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = range&lt;br /&gt;
|param2text = Mandatory number giving the range in nautical miles within which to search for navaids.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = This will narrow the search to navaids of a certain type. Defaults to &amp;quot;all.&amp;quot; For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.&lt;br /&gt;
|example1text = Searches for navaids within 10 nm of [[KSFO]].&lt;br /&gt;
|example1 = var pos = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var navs = findNavaidsWithinRange(pos, 10);&lt;br /&gt;
foreach(var nav; navs){&lt;br /&gt;
    print(nav.name, &amp;quot; (ID: &amp;quot;, nav.id, &amp;quot;)&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
|example2text = Searches for navaids within 10 nm of your current position.&lt;br /&gt;
|example2 = var navs = findNavaidsWithinRange(10);&lt;br /&gt;
foreach(var nav; navs){&lt;br /&gt;
    print(nav.name, &amp;quot; (ID: &amp;quot;, nav.id, &amp;quot;)&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===finddata()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = finddata(path);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=603|t=Source}}&lt;br /&gt;
|text = Takes a relative path and tries to return an absolute one. It works by appending the relative path to some paths and testing to see if they exist. As of FlightGear v3.7, these paths are the TerraSync directory (tested first) and [[$FG_ROOT]]. &lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = A relative path as a string.&lt;br /&gt;
|example1 = var path = finddata(&amp;quot;Aircraft/Generic&amp;quot;);&lt;br /&gt;
print(path); # prints the absolute path to $FG_ROOT/Aircraft/Generic&lt;br /&gt;
|example2 = var path = finddata(&amp;quot;Airports&amp;quot;);&lt;br /&gt;
print(path); # prints the absolute path to &amp;lt;TerraSync dir&amp;gt;/Airports&lt;br /&gt;
|example3 = var path = finddata(&amp;quot;preferences.xml&amp;quot;);&lt;br /&gt;
print(path); # prints the absolute path to $FG_ROOT/preferences.xml&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===flightplan()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = flightplan([path]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1738|t=Source}}&lt;br /&gt;
|text = {{see also|Nasal Flightplan}}&lt;br /&gt;
Returns a flight plan object, either one for the current flight plan, or one loaded from a given path.&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Optional path to flight plan XML file.&lt;br /&gt;
|example1text = Gets the active flight plan and gets the ID of the current waypoint. Note that this example requires a flight plan to be set in the [[Route Manager]] first.&lt;br /&gt;
|example1 = var fp = flightplan();&lt;br /&gt;
print(fp.getWP(fp.current).id);&lt;br /&gt;
|example2text = Creates a new flight plan from an XML file and prints the IDs of the waypoints. Note that this example requires a flight plan to have been created and saved as &amp;lt;tt&amp;gt;''[[$FG_HOME]]/fp-demo.xml''&amp;lt;/tt&amp;gt;.&lt;br /&gt;
|example2 = var path = getprop('/sim/fg-home') ~ '/fp-demo.xml';&lt;br /&gt;
var fp = flightplan(path);&lt;br /&gt;
for(var i = 0; i &amp;lt; fp.getPlanSize(); i += 1){&lt;br /&gt;
    print(fp.getWP(i).id);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===geodinfo()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = geodinfo(lat, lon[, max_alt]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=981|t=Source}}&lt;br /&gt;
|text = Returns a vector containing two entries or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if no information could be obtained because the terrain tile wasn't loaded. The first entry in the vector is the elevation (in meters) for the given point, and the second is a hash with information about the assigned material (as defined in &amp;lt;tt&amp;gt;''[[$FG_ROOT]]/Materials''&amp;lt;/tt&amp;gt;), or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if there was no material information available (for example, because there is an untextured building at that location). The structure of the hash is as follows (see also {{readme file|materials}}):&lt;br /&gt;
* '''light_coverage:''' The coverage of a single point of light in m&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;.&lt;br /&gt;
* '''bumpiness:''' Normalized bumpiness factor for the material.&lt;br /&gt;
* '''load_resistance:''' The amount of pressure in N/m&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt; the material can withstand without deformation.&lt;br /&gt;
* '''solid:''' 1 (true) or false (0) depending on whether the material is solid or not.&lt;br /&gt;
* '''names:''' Vector of scenery types (usually generated by [[TerraGear]]) that will use this material. &lt;br /&gt;
* '''friction_factor:''' Normalized friction factor of the material.&lt;br /&gt;
* '''rolling_friction:''' The rolling friction coefficient of the material.&lt;br /&gt;
&lt;br /&gt;
Note that this function is a ''very'' CPU-intensive operation, particularly in FlightGear v2.4 and earlier. It is advised to use this function as little as possible.&lt;br /&gt;
|param1 = lat&lt;br /&gt;
|param1text = Latitude, inputted as a number.&lt;br /&gt;
|param2 = lon&lt;br /&gt;
|param2text = Longitude, inputted as a number.&lt;br /&gt;
|param3 = max_alt&lt;br /&gt;
|param3text = The altitude, in metres, from which the function will begin searching for the height of the terrain. Defaults to 10,000 metres. If the terrain is higher than this argument specifies, &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; will be returned.&lt;br /&gt;
|example1text = Dumps information about ground underneath the aircraft.&lt;br /&gt;
|example1 = var pos = geo.aircraft_position();&lt;br /&gt;
var info = geodinfo(pos.lat(), pos.lon());&lt;br /&gt;
debug.dump(info);&lt;br /&gt;
|example2text = Prints whether the ground underneath the aircraft is solid or is water.&lt;br /&gt;
|example2 = var pos = geo.aircraft_position();&lt;br /&gt;
var info = geodinfo(pos.lat(), pos.lon());&lt;br /&gt;
if (info != nil and info[1] != nil) {&lt;br /&gt;
    print(&amp;quot;The ground underneath the aircraft is &amp;quot;, info[1].solid == 1 ? &amp;quot;solid.&amp;quot; : &amp;quot;water.&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== geodtocart() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = geodtocart(lat, lon, alt);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=962|t=Source}}&lt;br /&gt;
|text = Converts {{wikipedia|geodetic coordinates}} (latitude, longitude, and altitude) to {{wikipedia|ECEF|Earth-centered, Earth-fixed}} coordinates (x, y and z). A vector is returned containing x, y, and z in metres. The equatorial radius of earth used is that defined by the {{wikipedia|WGS 84}} (6,378,137 metres). All argument are mandatory.&lt;br /&gt;
|param1 = lat&lt;br /&gt;
|param1text = Latitude, in degrees.&lt;br /&gt;
|param2 = lon&lt;br /&gt;
|param2text = Longitude, in degrees.&lt;br /&gt;
|param3 = alt&lt;br /&gt;
|param3text = Altitude, in metres.&lt;br /&gt;
|example1 = var (x, y, z) = geodtocart(0, 0, 0); # point is the intersection of the prime meridian and equator.&lt;br /&gt;
print(&amp;quot;x: &amp;quot;, x); # prints &amp;quot;x: 6378137&amp;quot;&lt;br /&gt;
print(&amp;quot;y: &amp;quot;, y); # prints &amp;quot;y: 0&amp;quot;&lt;br /&gt;
print(&amp;quot;z: &amp;quot;, z); # prints &amp;quot;y: 0&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== get_cart_ground_intersection()===&lt;br /&gt;
Introduced in 2017.2.1, see [[Terrain Detection]].&lt;br /&gt;
&lt;br /&gt;
===getprop()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;getprop(path[, path[, ...]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=345|t=Source}}&lt;br /&gt;
|text = Returns the value of a node in the [[Property Tree]] or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if the node does not exist or the value is not a number (NaN).&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = There needs to be at least one argument, but there is no limit to the maximum amount of arguments that can be given. The arguments will be concatenated together to form a property tree path. The arguments must be strings, but in FlightGear v3.2 onwards, there is also support (added by {{flightgear commit|34ed79}}) for numeric arguments as indices. See example 2 below.&lt;br /&gt;
|example1 = print(&amp;quot;You have FlightGear v&amp;quot;, getprop(&amp;quot;/sim/version/flightgear&amp;quot;)); # prints FlightGear version&lt;br /&gt;
|example2text = Note that the example below will only work in FlightGear 3.2 and above.&lt;br /&gt;
|example2 = for(var i = 0; i &amp;lt; 8; i += 1){&lt;br /&gt;
    print(&amp;quot;View #&amp;quot;, i + 1, &amp;quot; is named &amp;quot;, getprop(&amp;quot;/sim/view&amp;quot;, i, &amp;quot;name&amp;quot;));&lt;br /&gt;
}&lt;br /&gt;
|example3text = Same as above, but is supported by all versions of FlightGear.&lt;br /&gt;
|example3 = for(var i = 0; i &amp;lt; 8; i += 1){&lt;br /&gt;
    print(&amp;quot;View #&amp;quot;, i + 1, &amp;quot; is named &amp;quot;, getprop(&amp;quot;/sim/view[&amp;quot; ~ i ~ &amp;quot;]/name&amp;quot;));&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
==== See also====&lt;br /&gt;
{{note| If you have to read/write the same property multiple times (e.g. in an update loop), it is more efficient to use a node object: &lt;br /&gt;
To get a Node rather than its value, use &amp;lt;code&amp;gt;props.globals.getNode()&amp;lt;/code&amp;gt; - see [[Nasal_library/props]]. }}&lt;br /&gt;
&lt;br /&gt;
===greatCircleMove()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = greatCircleMove([pos, ]course, dist);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1681|t=Source}}&lt;br /&gt;
|text = Calculates a new set of geodetic coordinates using inputs of course and distance, either from the aircraft's current position (by default) or from another set of coordinates. Returns a hash containing two members, ''lat'' and ''lon'' (latitude and longitude respectively).&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to calculate from. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost object.&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A &amp;lt;code&amp;gt;geo.Coord&amp;lt;/code&amp;gt; object&lt;br /&gt;
:* A lat/lon pair, that is, a pair of numbers (latitude followed by longitude) separated by a comma: &amp;lt;code&amp;gt;greatCircleMove(lat,lon, ...)&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = course&lt;br /&gt;
|param2text = Course to new set of coordinates, in degrees (in the range 0–360).&lt;br /&gt;
|param3 = dist&lt;br /&gt;
|param3text = Distance in nautical miles to the new set of coordinates.&lt;br /&gt;
|example1 = var pos = greatCircleMove(0,0, 0, 1);&lt;br /&gt;
debug.dump(pos); # print hash with coordinates&lt;br /&gt;
|example2 = var fix = findFixesByID(&amp;quot;POGIC&amp;quot;);&lt;br /&gt;
fix = fix[0];&lt;br /&gt;
var pos = greatCircleMove(fix, 45, 10);&lt;br /&gt;
debug.dump(pos); # print hash with coordinates&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===interpolate()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|private = _interpolate()&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;interpolate(prop, value1, time1[, value2, time2[, ...]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=522|t=Part 1}} {{!}} {{fgdata file|Nasal/globals.nas|t=Part 2}}&lt;br /&gt;
|text = Linearly interpolates a node in the property tree to a given value in a specified time. The value/time pairs will be run one after the other in the order that they are passed to the function. Note that the interpolation will continue even when the simulation is paused.&lt;br /&gt;
|param1 = prop&lt;br /&gt;
|param1text = String or &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object that indicates a node in the property tree to be used.&lt;br /&gt;
|param2 = value''n''&lt;br /&gt;
|param2text = Target value to change the property to in the set amount of time. This should be a number.&lt;br /&gt;
|param3 = time''n''&lt;br /&gt;
|param3text = Time in seconds, that will be taken for the interpolation.&lt;br /&gt;
|example1text = Paste the code below into the Nasal Console and execute. Then, open the Property Browser and look for the property. Finally, run the code again, and watch the value of the property change.&lt;br /&gt;
|example1 = setprop(&amp;quot;/test&amp;quot;, 0); # (re-)set property&lt;br /&gt;
interpolate(&amp;quot;/test&amp;quot;,&lt;br /&gt;
    50, 5, # interpolate to 50 in 5 seconds&lt;br /&gt;
    10, 2, # interpolate to 10 in 2 seconds&lt;br /&gt;
    0, 5); # interpolate to 0 in 5 seconds&lt;br /&gt;
|example2 = # Apply the left brake at 20% per second&lt;br /&gt;
var prop = &amp;quot;controls/gear/brake-left&amp;quot;;&lt;br /&gt;
var dist = 1 - getprop(prop);&lt;br /&gt;
if (dist == 1) {&lt;br /&gt;
    interpolate(prop, 1, dist / 0.2);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===isa()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = isa(object, class);&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Checks if an object is an instance of, or inherits from, a second object (or class), returning 1 (true) if it is and 0 (false) if otherwise.&lt;br /&gt;
|param1 = object&lt;br /&gt;
|param1text = Object to check.&lt;br /&gt;
|param2 = class&lt;br /&gt;
|param2text = Class/object to check that '''object''' inherits from or is an instance of.&lt;br /&gt;
|example1 = var coord = geo.Coord.new();&lt;br /&gt;
if(isa(coord, geo.Coord)){&lt;br /&gt;
    print(&amp;quot;Variable 'coord' is an instance of class 'geo.Coord'&amp;quot;); # this one will be printed&lt;br /&gt;
} else {&lt;br /&gt;
    print(&amp;quot;Variable 'coord' is not an instance of class 'geo.Coord'&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
|example2 = var coord = geo.Coord.new();&lt;br /&gt;
if(isa(coord, props.Node)){&lt;br /&gt;
    print(&amp;quot;Variable 'coord' is an instance of class 'props.Node'&amp;quot;);&lt;br /&gt;
} else {&lt;br /&gt;
    print(&amp;quot;Variable 'coord' is not an instance of class 'props.Node'&amp;quot;); # this one will be printed&lt;br /&gt;
}&lt;br /&gt;
|example3text = The example below demonstrates checking of inheritance.&lt;br /&gt;
|example3 = var Const = {&lt;br /&gt;
    constant: 2,&lt;br /&gt;
    getConst: func {&lt;br /&gt;
        return me.constant;&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
var Add = {&lt;br /&gt;
    new: func {&lt;br /&gt;
        return { parents: [Add, Const] };&lt;br /&gt;
    },&lt;br /&gt;
&lt;br /&gt;
    addToConst: func(a){&lt;br /&gt;
        return a * me.getConst();&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
var m = Add.new();&lt;br /&gt;
print(m.addToConst(4));&lt;br /&gt;
&lt;br /&gt;
if(isa(m, Add)) print(&amp;quot;Variable 'm' is an instance of class 'Add'&amp;quot;); # will be printed&lt;br /&gt;
if(isa(m, Const)) print(&amp;quot;Variable 'm' is an instance of class 'Const'&amp;quot;); # will also be printed&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===logprint()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;logprint(priority[, msg[, msg[, ...]]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=431|t=Source}}&lt;br /&gt;
|text = Concatenates a message and logs it with a given priority level. Unlike {{func link|print()}} and {{func link|printlog()}}, message outputted by this function will be logged in your &amp;lt;code&amp;gt;[[Commonly used debugging tools#fgfs.log|fgfs.log]]&amp;lt;/code&amp;gt; file as coming from the Nasal file itself rather than from {{flightgear file|src/Scripting/NasalSys.cxx}}.&lt;br /&gt;
|param1 = priority&lt;br /&gt;
|param1text = Number specifying the priority level of the outputted message:&lt;br /&gt;
:{{{!}} class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Number !! Debug type&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 1 {{!!}} Bulk&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 2 {{!!}} Debug&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 3 {{!!}} Info&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 4 {{!!}} Warn&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 5 {{!!}} Alert&lt;br /&gt;
{{!}}}&lt;br /&gt;
|param2 = msg&lt;br /&gt;
|param2text = The message. There is no limit to the arguments you give give. They will be concatenated together before logging.&lt;br /&gt;
|example1 = # logs the value of pi to three decimal places with log level 3&lt;br /&gt;
logprint(3, &amp;quot;pi = &amp;quot;, sprintf(&amp;quot;%.3f&amp;quot;, math.pi));&lt;br /&gt;
|example2 = logprint(5, &amp;quot;Alert! This is an important message!&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
{{note| &lt;br /&gt;
The following constants have been added to the development branch of FlightGear (&amp;quot;next&amp;quot;) and will be releases with FG 2020.1 so you won't have to remember the numbers anymore:&lt;br /&gt;
&lt;br /&gt;
LOG_BULK, LOG_WARN, LOG_DEBUG, LOG_INFO, LOG_ALERT, DEV_WARN, DEV_ALERT }}&lt;br /&gt;
&lt;br /&gt;
===magvar() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = magvar([pos]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1642|t=Source}}&lt;br /&gt;
|text = Returns the {{wikipedia|magnetic variation}} at a given set of coordinates. The table below gives the magnetic model used depending on the version of FlightGear.&lt;br /&gt;
{{{!}} class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! FlightGear versions !! Model !! Reference date&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 3.6 and above {{!!}} {{wikipedia|World Magnetic Model}} (WMM) 2015 {{!!}} 1 January 2015&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 0.9.11-pre1 to 3.4 {{!!}} WMM 2005 {{!!}} 1 January 2005&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 0.7.3 to 0.9.10 {{!!}} WMM 2000 {{!!}} 1 January 2000&lt;br /&gt;
{{!}}}&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to calculate from. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost object.&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A &amp;lt;code&amp;gt;geo.Coord&amp;lt;/code&amp;gt; object&lt;br /&gt;
:* A lat/lon pair, that is, a pair of numbers (latitude followed by longitude) separated by a comma: &amp;lt;code&amp;gt;magvar(lat,lon)&amp;lt;/code&amp;gt;.&lt;br /&gt;
|example1 = print(magvar(0, 0)); # prints the magnetic variation at 0, 0&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===maketimer() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = maketimer(interval[, self], function);&lt;br /&gt;
|source = ''Implemented using the {{API Link|flightgear|class|TimerObj}} class.''&amp;lt;br&amp;gt;{{flightgear file|src/Scripting/NasalSys.cxx|l=90|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=533|t=Part 2}}&lt;br /&gt;
|version = 2.12&lt;br /&gt;
|commit = {{flightgear commit|ab939f|t=commit}}&lt;br /&gt;
|text = Returns a timer object containing the following methods and members:&lt;br /&gt;
* '''start()''': Starts the timer.&lt;br /&gt;
* '''stop()''': Stops the timer.&lt;br /&gt;
* '''restart(interval)''': Restarts the timer with the given interval.&lt;br /&gt;
* '''singleShot''': Bool showing whether the timer is only to be run once, or continuously until told to stop. Can be both set and read from (see examples).&lt;br /&gt;
* '''isRunning''': Read-only bool telling whether the timer is currently running.&lt;br /&gt;
* '''simulatedTime''': (FG 2017.1+; {{flightgear commit|0af316|t=commit}}) Bool telling whether the timer is using simulated time (which accounts for pause, etc.). Defaults to false (use real time). Can be both read and set. This cannot be changed while the timer is running.&lt;br /&gt;
Unlike {{func link|settimer()}}, which it replaces, &amp;lt;code&amp;gt;maketimer()&amp;lt;/code&amp;gt; provides more control over the timer. In addition, it can help reduce memory usage.&lt;br /&gt;
|param1 = interval&lt;br /&gt;
|param1text = Interval in seconds for the timer.&lt;br /&gt;
|param2 = self&lt;br /&gt;
|param2text = Optional parameter specifying what any &amp;lt;code&amp;gt;'''me'''&amp;lt;/code&amp;gt; references in the function being called will refer to.&lt;br /&gt;
|param3 = function&lt;br /&gt;
|param3text = Function to be called at the given interval.&lt;br /&gt;
|example1 = var timer = maketimer(1, func(){&lt;br /&gt;
    print(&amp;quot;Hello, World!&amp;quot;); # print &amp;quot;Hello, World!&amp;quot; once every second (call timer.stop() to stop it)&lt;br /&gt;
});&lt;br /&gt;
timer.start();&lt;br /&gt;
|example2 = var timer = maketimer(1, math, func(){&lt;br /&gt;
    print(me.math); # 'me' reference is the 'math' namespace&lt;br /&gt;
});&lt;br /&gt;
timer.singleShot = 1; # timer will only be run once&lt;br /&gt;
timer.start();&lt;br /&gt;
|example3 = var timer = maketimer(1, func(){&lt;br /&gt;
    print(&amp;quot;Hello, World!&amp;quot;); # print &amp;quot;Hello, World!&amp;quot; once every second (call timer.stop() to stop it)&lt;br /&gt;
});&lt;br /&gt;
timer.start();&lt;br /&gt;
print(timer.isRunning); # prints 1&lt;br /&gt;
|example4text = In the example below, &amp;quot;Hello, World!&amp;quot; will be printed after one second the first time, and after two seconds thereafter.&lt;br /&gt;
|example4 = var update = func(){&lt;br /&gt;
    print(&amp;quot;Hello, World!&amp;quot;);&lt;br /&gt;
    timer.restart(2); # restarts the timer with a two second interval&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var timer = maketimer(1, update);&lt;br /&gt;
timer.singleShot = 1;&lt;br /&gt;
timer.start();&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===maketimestamp()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = maketimestamp()&lt;br /&gt;
|source = ''Implemented using the {{API Link|flightgear|class|TimeStampObj}} class.''&amp;lt;br&amp;gt;{{flightgear file|src/Scripting/NasalSys.cxx|l=214|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=589|t=Part 2}}&lt;br /&gt;
|version = 2019.2&lt;br /&gt;
|commit = {{flightgear commit|7db06300|t=commit}}&lt;br /&gt;
|text = Returns a time stamp object to allow high resolution timing of Nasal operations. When created the timer will automatically be stamped. The object has the following methods:&lt;br /&gt;
* '''stamp()''': Resets the timing operation. Call this first.&lt;br /&gt;
* '''elapsedMSec()''': returns number of milliseconds elapsed since stamp() called. Resolution may vary depending on platform but is usually at least millisecond accuracy.&lt;br /&gt;
* '''elapsedUSec()''': returns number of microseconds elapsed since stamp() called. Resolution may vary depending on platform but is usually at least millisecond accuracy.&lt;br /&gt;
|&lt;br /&gt;
|example1text = In the example below the number of milliseconds elapsed will be printed.&lt;br /&gt;
|example1 = var timestamp = maketimestamp();&lt;br /&gt;
timestamp.stamp();&lt;br /&gt;
print(timestamp.elapsedMSec(), &amp;quot;ms elapsed&amp;quot;);&lt;br /&gt;
print(timestamp.elapsedMSec(), &amp;quot;ms elapsed&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===md5()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = md5(string);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=758|t=Source}}&lt;br /&gt;
|version = 3.2&lt;br /&gt;
|commit = {{flightgear commit|cfbf9e|t=commit}}&lt;br /&gt;
|text = Returns a the {{wikipedia|MD5}} hash (as a string) of the inputted string.&lt;br /&gt;
|param1 = string&lt;br /&gt;
|param1text = String the generate the hash of. Mandatory.&lt;br /&gt;
|example1text = The below code should output &amp;lt;code&amp;gt;65a8e27d8879283831b664bd8b7f0ad4&amp;lt;/code&amp;gt;.&lt;br /&gt;
|example1 = print(md5(&amp;quot;Hello, World!&amp;quot;));&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===navinfo()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = navinfo(lat, lon, type, id);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1453|t=Source}}&lt;br /&gt;
|text = Returns vector &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost objects matching the given '''type''' and '''id''' or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; on error.&lt;br /&gt;
|param1 = lat ''and'' lon&lt;br /&gt;
|param1text = If given, the returned navaids will be put into order of ascending distance from the location.&lt;br /&gt;
|param2 = type&lt;br /&gt;
|param2text = Narrows the search to the given type. Must be one of &amp;quot;any,&amp;quot; &amp;quot;fix,&amp;quot; &amp;quot;vor,&amp;quot; &amp;quot;ndb,&amp;quot; &amp;quot;ils,&amp;quot; &amp;quot;dme,&amp;quot; or &amp;quot;tacan.&amp;quot; Defaults to the equivalent of &amp;quot;any.&amp;quot;&lt;br /&gt;
|param3 = id&lt;br /&gt;
|param3text = ID to search for. Note that, although all the parameters are technically optional, this parameter must be given, otherwise an empty vector will be returned.&lt;br /&gt;
|example1 = navinfo(&amp;quot;vor&amp;quot;); # returns all VORs&lt;br /&gt;
|example2 = navinfo(&amp;quot;HAM&amp;quot;); # return all navaids whose names start with &amp;quot;HAM&amp;quot;&lt;br /&gt;
|example3 = navinfo(&amp;quot;vor&amp;quot;, &amp;quot;HAM&amp;quot;); # return all VORs whose names start with &amp;quot;HAM&amp;quot;&lt;br /&gt;
|example4 = navinfo(34,48,&amp;quot;vor&amp;quot;,&amp;quot;HAM&amp;quot;); # return all VORs whose names start with &amp;quot;HAM&amp;quot; and sorted by distance relative to 34°, 48°&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===parse_markdown()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = parse_markdown(markdown);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=745|t=Part 1}} {{!}} {{simgear file|simgear/misc/SimpleMarkdown.cxx|t=Part 2}} &lt;br /&gt;
|version = 3.2&lt;br /&gt;
|text = Parses a string containing {{wikipedia|Markdown}} and returns the result as a string. As of FlightGear 2016.1, it is just a simple parser, and does the following:&lt;br /&gt;
* It strips whitespace from the beginning of the string.&lt;br /&gt;
* It supports [http://daringfireball.net/projects/markdown/syntax#p paragraphs and line breaks].&lt;br /&gt;
* It collapses whitespace.&lt;br /&gt;
* It converts unordered [http://daringfireball.net/projects/markdown/syntax#list lists] to use a bullet character (&amp;amp;bull;). Note that the bullet character is implemented in hexadecimal UTF-8 character bytes (&amp;lt;code&amp;gt;E2 80 A2&amp;lt;/code&amp;gt;), as so may not work properly when the being displayed in an encoding other than UTF-8.&lt;br /&gt;
|param1 = markdown&lt;br /&gt;
|param1text = String containing Markdown to be parsed.&lt;br /&gt;
|example1text = &lt;br /&gt;
Save the below code as &amp;lt;tt&amp;gt;''[[$FG_ROOT]]/gui/dialogs/test.xml''&amp;lt;/tt&amp;gt;, then run the Nasal code below it to open the dialog. To change the markdown to be parsed, simply change the code in the highlighted section, save it, and reload the GUI (&amp;lt;tt&amp;gt;Debug &amp;gt; Reload GUI&amp;lt;/tt&amp;gt;).&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot; highlight=&amp;quot;41-50&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PropertyList&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;name&amp;gt;test&amp;lt;/name&amp;gt;&lt;br /&gt;
&amp;lt;layout&amp;gt;vbox&amp;lt;/layout&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;group&amp;gt;&lt;br /&gt;
    &amp;lt;layout&amp;gt;hbox&amp;lt;/layout&amp;gt;&lt;br /&gt;
    &lt;br /&gt;
    &amp;lt;empty&amp;gt;&lt;br /&gt;
        &amp;lt;stretch&amp;gt;true&amp;lt;/stretch&amp;gt;&lt;br /&gt;
    &amp;lt;/empty&amp;gt;&lt;br /&gt;
    &lt;br /&gt;
    &amp;lt;text&amp;gt;&lt;br /&gt;
        &amp;lt;label&amp;gt;parse_markdown() test dialog&amp;lt;/label&amp;gt;&lt;br /&gt;
    &amp;lt;/text&amp;gt;&lt;br /&gt;
    &lt;br /&gt;
    &amp;lt;empty&amp;gt;&lt;br /&gt;
        &amp;lt;stretch&amp;gt;true&amp;lt;/stretch&amp;gt;&lt;br /&gt;
    &amp;lt;/empty&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;button&amp;gt;&lt;br /&gt;
        &amp;lt;legend&amp;gt;&amp;lt;/legend&amp;gt;&lt;br /&gt;
        &amp;lt;pref-width&amp;gt;16&amp;lt;/pref-width&amp;gt;&lt;br /&gt;
        &amp;lt;pref-height&amp;gt;16&amp;lt;/pref-height&amp;gt;&lt;br /&gt;
        &amp;lt;binding&amp;gt;&lt;br /&gt;
            &amp;lt;command&amp;gt;dialog-close&amp;lt;/command&amp;gt;&lt;br /&gt;
        &amp;lt;/binding&amp;gt;&lt;br /&gt;
    &amp;lt;/button&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/group&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;canvas&amp;gt;&lt;br /&gt;
    &amp;lt;name&amp;gt;Canvas plot&amp;lt;/name&amp;gt;&lt;br /&gt;
    &amp;lt;stretch&amp;gt;true&amp;lt;/stretch&amp;gt;&lt;br /&gt;
    &amp;lt;pref-width&amp;gt;400&amp;lt;/pref-width&amp;gt;&lt;br /&gt;
    &amp;lt;pref-height&amp;gt;300&amp;lt;/pref-height&amp;gt;&lt;br /&gt;
    &amp;lt;nasal&amp;gt;&lt;br /&gt;
        &amp;lt;load&amp;gt;&amp;lt;![CDATA[&lt;br /&gt;
var text = 'Items:&lt;br /&gt;
* apples&lt;br /&gt;
* oranges&lt;br /&gt;
* pears&lt;br /&gt;
&lt;br /&gt;
Some text.&lt;br /&gt;
Some more items:&lt;br /&gt;
* apples&lt;br /&gt;
* oranges&lt;br /&gt;
* pears';&lt;br /&gt;
&lt;br /&gt;
var parsed = parse_markdown(text);&lt;br /&gt;
&lt;br /&gt;
var root_canvas = canvas.get(cmdarg());&lt;br /&gt;
root_canvas.setColorBackground(255, 255, 255);&lt;br /&gt;
var root = root_canvas.createGroup();&lt;br /&gt;
&lt;br /&gt;
var text_dis = root.createChild(&amp;quot;text&amp;quot;)&lt;br /&gt;
    .setText(parsed)&lt;br /&gt;
    .setTranslation(5, 5)&lt;br /&gt;
    .setFont(&amp;quot;LiberationFonts\LiberationSans-Regular.ttf&amp;quot;)&lt;br /&gt;
    .setFontSize(15)&lt;br /&gt;
    .setColor(0, 0, 0)&lt;br /&gt;
    .setDrawMode(canvas.Text.TEXT)&lt;br /&gt;
    .setAlignment(&amp;quot;left-top&amp;quot;);&lt;br /&gt;
        ]]&amp;gt;&amp;lt;/load&amp;gt;&lt;br /&gt;
    &amp;lt;/nasal&amp;gt;&lt;br /&gt;
&amp;lt;/canvas&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/PropertyList&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|example1 = fgcommand(&amp;quot;dialog-show&amp;quot;, {&amp;quot;dialog-name&amp;quot;: &amp;quot;test&amp;quot;});&lt;br /&gt;
|example2text = The example below parses Markdown and outputs it in a HTML document. The parsed text is placed in &amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot; inline&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/syntaxhighlight&amp;gt; tags. To change the Markdown to be parsed, simply edit the variable &amp;lt;tt&amp;gt;markdown&amp;lt;/tt&amp;gt; at the top of the code.&lt;br /&gt;
|example2 = &amp;lt;nowiki&amp;gt;var markdown = 'Items:&lt;br /&gt;
* apples&lt;br /&gt;
* oranges&lt;br /&gt;
* pears&lt;br /&gt;
&lt;br /&gt;
Some text.&lt;br /&gt;
Some more items:&lt;br /&gt;
* apples&lt;br /&gt;
* oranges&lt;br /&gt;
* pears';&lt;br /&gt;
&lt;br /&gt;
var parsed = parse_markdown(markdown);&lt;br /&gt;
&lt;br /&gt;
debug.dump(parsed);&lt;br /&gt;
&lt;br /&gt;
var path = string.normpath(getprop(&amp;quot;/sim/fg-home&amp;quot;) ~ '/Export/parse_markdown()-test.html');&lt;br /&gt;
&lt;br /&gt;
var file = io.open(path, &amp;quot;w&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
var html = &amp;quot;&amp;lt;!DOCTYPE html&amp;gt;\n\n&amp;lt;html&amp;gt;\n\n&amp;lt;head&amp;gt;\n\t&amp;lt;meta charset=\&amp;quot;UTF-8\&amp;quot;&amp;gt;\n\t&amp;lt;title&amp;gt;parse_markdown() test generated by Nasal&amp;lt;/title&amp;gt;\n&amp;lt;/head&amp;gt;\n\n&amp;lt;body&amp;gt;\n\t&amp;lt;pre&amp;gt;&amp;quot; ~ parsed ~ &amp;quot;&amp;lt;/pre&amp;gt;\n&amp;lt;/body&amp;gt;\n\n&amp;lt;/html&amp;gt;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
io.write(file, html);&lt;br /&gt;
io.close(file);&lt;br /&gt;
print(&amp;quot;Done, file ready for viewing (&amp;quot; ~ path ~ &amp;quot;)&amp;quot;);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===parsexml()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;parsexml(path[, start[, end[, data[, pro_ins]]]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=710|t=Source}}&lt;br /&gt;
|text = This function is an interface into the built-in [http://expat.sourceforge.net/ Expat XML parser]. The absolute path to the file is returned as string, or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; is returned on error.&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Mandatory absolute path to the XML file to be parsed.&lt;br /&gt;
|param2 = start&lt;br /&gt;
|param2text = Optional callback function that will be called for every starting tag. The function should take two argument: the tag name and a hash containing attributes.&lt;br /&gt;
|param3 = end&lt;br /&gt;
|param3text = Optional callback function that will be called for every ending tag. The function should take one argument: the tag name.&lt;br /&gt;
|param4 = data&lt;br /&gt;
|param4text = Optional callback function that will be called for every piece of data within a set of tags. The function should take one argument: the data as a string.&lt;br /&gt;
|param5 = pro_ins&lt;br /&gt;
|param5text = Optional callback function that will be called for every {{wikipedia|Processing Instruction|processing instruction}}. The function should take two argument: the target and the data string.&lt;br /&gt;
|example1text = Save the below XML code in &amp;lt;tt&amp;gt;''[[$FG_HOME]]/Export/demo.xml''&amp;lt;/tt&amp;gt;. Then, execute the Nasal code below in the Nasal Console. The XML will be parsed and each bit of the code will be printed.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;?xml-stylesheet type=&amp;quot;text/xsl&amp;quot; href=&amp;quot;style.xsl&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;foo&amp;gt;&lt;br /&gt;
  &amp;lt;blah type=&amp;quot;string&amp;quot;&amp;gt;&amp;lt;![CDATA[ &amp;lt;sender&amp;gt;John Smith&amp;lt;/sender&amp;gt; ]]&amp;gt;&amp;lt;/blah&amp;gt;&lt;br /&gt;
  &amp;lt;blah2 type=&amp;quot;string&amp;quot;&amp;gt;Orange &amp;amp;amp; lemons&amp;lt;/blah2&amp;gt;&lt;br /&gt;
&amp;lt;/foo&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|example1 = var start = func(name, attr){&lt;br /&gt;
    print(&amp;quot;Starting tag: '&amp;quot;, name, &amp;quot;'&amp;quot;);&lt;br /&gt;
    foreach(var a; keys(attr)){&lt;br /&gt;
        print(&amp;quot;\twith attribute &amp;quot;, a, '=&amp;quot;', attr[a], '&amp;quot;');&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var end = func(name){&lt;br /&gt;
    print(&amp;quot;Ending tag: '&amp;quot;, name, &amp;quot;'&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var data = func(data){&lt;br /&gt;
    print(&amp;quot;Data = '&amp;quot;, data, &amp;quot;'&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var pro_instr = func(target, data){&lt;br /&gt;
    print(&amp;quot;Processing instruction: target = '&amp;quot;, target, &amp;quot;', data = '&amp;quot;, data, &amp;quot;'&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
parsexml(getprop(&amp;quot;/sim/fg-home&amp;quot;) ~ '/Export/demo.xml', start, end, data, pro_instr);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===print()===&lt;br /&gt;
{{Note|As of 07/2020, we are in the process of slowly deprecating and removing Nasal print() (in fgdata) where possible in favor of log [[#logprint()]] which takes a priority level.&lt;br /&gt;
&lt;br /&gt;
This is part of the goal that we achieve zero output at log-level at alert, and everything shown at ‘warn; is really a warning, not just information.&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37042224/&amp;lt;/ref&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;print(data[, data[, ...]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=415|t=Source}}&lt;br /&gt;
|text = Concatenates its arguments and then prints it to the terminal and the [[Commonly used debugging tools#fgfs.log|log]]. Note that a newline is automatically added.&lt;br /&gt;
|param1 = data&lt;br /&gt;
|param1text = Data to print. Only strings and numbers can be printed; other data types will not be. There many be any number of arguments; they will just be concatenated together.&lt;br /&gt;
|example1 = print(&amp;quot;Just&amp;quot;, &amp;quot; a &amp;quot;, &amp;quot;test&amp;quot;); # prints &amp;quot;Just a test&amp;quot;&lt;br /&gt;
|example2 = print(&amp;quot;pi = &amp;quot;, math.pi); # prints &amp;quot;pi = 3.141592...&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== printf() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;printf(format[, arg[, arg, [...]]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Creates and prints a formatted string. For a description of its arguments, see {{func link|sprintf()}} (it is, in fact, implemented using &amp;lt;code&amp;gt;sprintf()&amp;lt;/code&amp;gt;).&lt;br /&gt;
|example1 = printf(&amp;quot;In hexadecimal, 100000 = %X&amp;quot;, 186A0); # prints &amp;quot;In hexadecimal, 100000 = 186A0&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===printlog()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;printlog(level, data[, data[, ...]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Prints the given message with the given log level. If the log level is higher or equal to &amp;lt;code&amp;gt;/sim/logging/priority&amp;lt;/code&amp;gt;, it is printed.&lt;br /&gt;
|param1 = level&lt;br /&gt;
|param1text = Mandatory log level as a string. Must be one of &amp;quot;none,&amp;quot; &amp;quot;bulk,&amp;quot; &amp;quot;debug,&amp;quot; &amp;quot;info,&amp;quot; &amp;quot;warn,&amp;quot; or &amp;quot;alert.&amp;quot; Note that &amp;quot;none&amp;quot; will mean that the message will never be printed.&lt;br /&gt;
|param2 = data&lt;br /&gt;
|param2text = Data to be printed. Only strings and numbers will be printed; others will not be. There may be any number of arguments; they will just be concatenated together.&lt;br /&gt;
|example1 = printlog(&amp;quot;alert&amp;quot;, &amp;quot;This is an alert&amp;quot;); # message will be printed&lt;br /&gt;
|example2 = printlog(&amp;quot;info&amp;quot;, &amp;quot;Just informing you about something&amp;quot;); # message will be printed only if log level is set to &amp;quot;info&amp;quot; or less&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===rand()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = rand();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=554|t=Source}}&lt;br /&gt;
|text = Returns a random floating point number between 0 (inclusive) and 1 (exclusive). It takes no arguments.&lt;br /&gt;
|example1 = print(rand()); # prints random number&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===registerFlightPlanDelegate()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = registerFlightPlanDelegate(init_func);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1879|t=Source}}&lt;br /&gt;
|text = Registers a flight plan delegate. See &amp;lt;tt&amp;gt;''{{fgdata file|Nasal/route_manager.nas|t=$FG_ROOT/Nasal/route_manager.nas}}''&amp;lt;/tt&amp;gt; for examples.&lt;br /&gt;
|param1 = init_func&lt;br /&gt;
|param1text = Initialization function which will be called during FlightGear's startup.&lt;br /&gt;
}}&lt;br /&gt;
===removecommand()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = removecommand(cmd);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=668|t=Source}}&lt;br /&gt;
|text = Removes the given fgcommand. Returns &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt;.&lt;br /&gt;
{{caution|This will remove '''any''' [[fgcommands|fgcommand]], even those implemented in C++, so use with caution!}}&lt;br /&gt;
|param1 = cmd&lt;br /&gt;
|param1text = String specifying the name of the command to remove.&lt;br /&gt;
|example1 = addcommand(&amp;quot;hello&amp;quot;, func(){&lt;br /&gt;
    print(&amp;quot;Hello&amp;quot;);&lt;br /&gt;
});&lt;br /&gt;
fgcommand(&amp;quot;hello&amp;quot;); # &amp;quot;Hello&amp;quot; will be printed&lt;br /&gt;
removecommand(&amp;quot;hello&amp;quot;); # removes it&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===removelistener()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = removelistener(id);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=1384|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=506|t=Part 2}}&lt;br /&gt;
|text = Removes and deactivates the given listener and returns the number of listeners left or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; on error.&lt;br /&gt;
{{note|It is good practice to remove listeners when they are not required anymore. This prevents the listeners reducing FlightGear's run performance.}}&lt;br /&gt;
|param1 = id&lt;br /&gt;
|param1text = ID of listener as returned by {{func link|setlistener()}}.&lt;br /&gt;
|example1 = var ls = setlistener(&amp;quot;/sim/test&amp;quot;, func(){&lt;br /&gt;
    print(&amp;quot;Property '/sim/test' has been changed&amp;quot;);&lt;br /&gt;
});&lt;br /&gt;
setprop(&amp;quot;/sim/test&amp;quot;, &amp;quot;blah&amp;quot;); # trigger listener&lt;br /&gt;
var rem = removelistener(ls); # remove listener&lt;br /&gt;
print(&amp;quot;There are &amp;quot;, rem, &amp;quot; listeners remaining&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===resolvepath()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = resolvepath(path);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=604|t=Source}}&lt;br /&gt;
|text = Takes a relative path as a string and uses [[SimGear]]'s path-resolving framework to return an absolute path as a string. If the path could not be resolved, an empty string is returned. See [[Resolving Paths]] for a detailed description of the algorithm. This function can also be used to check if a file exists.&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Relative path to be completed.&lt;br /&gt;
|example1 = print(resolvepath(&amp;quot;Nasal/globals.nas&amp;quot;)); # prints the equivalent of $FG_ROOT/Nasal/globals.nas&lt;br /&gt;
|example2 = print(resolvepath(&amp;quot;blah&amp;quot;)); # prints nothing; could not be resolved&lt;br /&gt;
|example3 = var file_path = resolvepath(&amp;quot;Aircraft/SenecaII/some-file&amp;quot;);&lt;br /&gt;
if (file_path != &amp;quot;&amp;quot;){&lt;br /&gt;
    gui.popupTip(&amp;quot;some-file found&amp;quot;, 2);&lt;br /&gt;
} else {&lt;br /&gt;
    gui.popupTip(&amp;quot;some-file not found&amp;quot;, 2);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===setlistener()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;setlistener(node, code[, init[, type]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|private = _setlistener()&lt;br /&gt;
|source = {{flightgear file|src/Scripting/Nasal|l=499|t=Part 1}} {{!}} {{flightgear file|src/Scripting/Nasal|l=1350|t=Part 2}}&amp;lt;br&amp;gt;''Listener implemented using the {{API Link|flightgear|class|FGNasalListener}} class.''&lt;br /&gt;
|text = Creates a listener which will be triggered when the given property is changed (depending on the '''type'''). A unique integer ID is returned; this can later be used as the argument to {{func link|removelistener()}}.&lt;br /&gt;
{{note|Listeners are known to be a source of resource leaks. To avoid this, please take measures such as:&lt;br /&gt;
* Using {{func link|removelistener()}} when they are not needed any more.&lt;br /&gt;
* Using a single initialization listener.&lt;br /&gt;
* Avoiding tying listeners to properties that are rapidly updated (e.g., many times per frame).&lt;br /&gt;
}}&lt;br /&gt;
|param1 = node&lt;br /&gt;
|param1text = Mandatory string or &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object pointing to a property in the [[Property Tree]].&lt;br /&gt;
|param2 = code&lt;br /&gt;
|param2text = Mandatory callback function to execute when the listener is triggered. The function can take up to four arguments in the following order:&lt;br /&gt;
* '''changed''': a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object pointing to the changed node.&lt;br /&gt;
* '''listen''': a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object pointing to the listened-to node. Note that this argument maybe different depending on the '''type'''.&lt;br /&gt;
* '''mode''': an integer telling how the listener was triggered. 0 means that the value was changed. 1 means that a child property was added. -1 means that a child property was removed.&lt;br /&gt;
* '''is_child''': boolean telling whether '''changed''' is a child property of the listened-to '''node''' or not. 1 (true) if it is, 0 (false) otherwise.&lt;br /&gt;
|param3 = init&lt;br /&gt;
|param3text = If set to 1 (true), the listener will additionally be triggered when it is created. This argument is optional and defaults to 0 (false).&lt;br /&gt;
|param4 = type&lt;br /&gt;
|param4text = Integer specifying the listener's behavior. 0 means that the listener will only trigger when the property is changed. 1 means that the trigger will always be triggered when the property is written to. 2 will mean that the listener will be triggered even if child properties are modified. This argument is optional and defaults to 1.&lt;br /&gt;
|example1 = var prop = props.globals.initNode(&amp;quot;/sim/test&amp;quot;, &amp;quot;&amp;quot;, &amp;quot;STRING&amp;quot;); # create property&lt;br /&gt;
var id = setlistener(&amp;quot;/sim/test&amp;quot;, func(n){ # create listener&lt;br /&gt;
    print(&amp;quot;Value: &amp;quot;, n.getValue());&lt;br /&gt;
});&lt;br /&gt;
setprop(&amp;quot;/sim/test&amp;quot;, &amp;quot;blah&amp;quot;); # trigger listener&lt;br /&gt;
removelistener(id); # remove listener&lt;br /&gt;
prop.remove(); # remove property&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===setprop()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;setprop(path[, path[, ...]], value);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=385|t=Source}}&lt;br /&gt;
|text = Sets the value of a property in the [[Property Tree]]. If the property does not exist, it will be created. Returns 1 (true) on success or 0 (false) if there was an error.&lt;br /&gt;
{{note|If you want to remove a property, you will have to use one of the &amp;lt;code&amp;gt;props&amp;lt;/code&amp;gt; helpers:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
props.globals.getNode(&amp;quot;foo/bar&amp;quot;).remove(); # take out the complete node&lt;br /&gt;
props.globals.getNode(&amp;quot;foo&amp;quot;).removeChild(&amp;quot;bar&amp;quot;); # take out a certain child node&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = There needs to be at least one '''path''' argument, but there is no limit to the maximum amount that can be given. They will be concatenated together to form a property tree path. The arguments must be strings, but in FlightGear v3.2 onwards, there also is support (added by {{flightgear commit|34ed79}}) for numeric arguments as indices. See example 2 below.&lt;br /&gt;
|param2 = value&lt;br /&gt;
|param2text = Value to write to the given property. Must be either a string or a number.&lt;br /&gt;
|example1 = setprop(&amp;quot;/sim/demo&amp;quot;, &amp;quot;This is a demo&amp;quot;);&lt;br /&gt;
|example2text = Note that the example below will only work in FlightGear 3.2 and above.&lt;br /&gt;
|example2 = for(var i = 0; i &amp;lt; 3; i += 1){&lt;br /&gt;
    setprop(&amp;quot;/sim/demo&amp;quot;, i, &amp;quot;Demo #&amp;quot; ~ i));&lt;br /&gt;
}&lt;br /&gt;
|example3text = Same as above, but is supported by all versions of FlightGear.&lt;br /&gt;
|example3 = for(var i = 0; i &amp;lt; 3; i += 1){&lt;br /&gt;
    setprop(&amp;quot;/sim/demo[&amp;quot; ~ i ~ &amp;quot;]&amp;quot;, &amp;quot;Demo #&amp;quot; ~ i));&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
====See also====&lt;br /&gt;
{{note| If you have to read/write the same property multiple times (e.g. in an update loop), it is more efficient to use a node object: &lt;br /&gt;
To get a Node rather than its value, use &amp;lt;code&amp;gt;props.globals.getNode()&amp;lt;/code&amp;gt; - see [[Nasal_library/props]]. }}&lt;br /&gt;
&lt;br /&gt;
===settimer() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = settimer(function, delta[, realtime]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=499|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=1286|t=Part 2}}&lt;br /&gt;
|text = Runs the given function a specified amount of seconds after the current time. Returns &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt;.&lt;br /&gt;
{{note|Improper use of &amp;lt;code&amp;gt;settimer()&amp;lt;/code&amp;gt; may cause resource leaks. It is also highly recommended that the newer {{func link|maketimer()}} should be used instead of this function.}} &lt;br /&gt;
|param1 = function&lt;br /&gt;
|param1text = Mandatory function that will be called. It may be necessary to enclose code in an anonymous function (see example).&lt;br /&gt;
|param2 = delta&lt;br /&gt;
|param2text = Mandatory amount of time in seconds after which the function will be called.&lt;br /&gt;
|param3 = realtime&lt;br /&gt;
|param3text = If 1 (true), &amp;quot;real time&amp;quot; will be used instead of &amp;quot;simulation time.&amp;quot; Defaults to 0 (false). Note that if &amp;quot;simulation time&amp;quot; is used, the timer will not run while FlightGear is paused.&lt;br /&gt;
|example1 = var myFunc = func(){&lt;br /&gt;
    print(&amp;quot;Hello&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
settimer(myFunc, 2); # runs myFunc after 2 seconds&lt;br /&gt;
|example2 = var sqr = func(a){&lt;br /&gt;
    return a * a;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
settimer(func(){&lt;br /&gt;
    print(sqr(2)); # will print 4 after 2 seconds&lt;br /&gt;
}, 2);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===srand() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = srand();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=559|t=Source}}&lt;br /&gt;
|text = Makes the pseudorandom number generator (see {{func link|rand()}}) generate a new {{wikipedia|random seed|noicon=1}} based on time. Returns 0.&lt;br /&gt;
}}&lt;br /&gt;
===systime()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = systime();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=770|t=Source}}&lt;br /&gt;
|text = Returns the {{wikipedia|Unix time}} (seconds since since 00:00:00 UTC, 1/1/1970) as a floating point number with high resolution. This function is useful for benchmarking purposes (see example 2).&lt;br /&gt;
{{note|1=High resolution timers under Windows can produce inaccurate or fixed sub-millisecond results.&amp;lt;ref&amp;gt;{{cite web|url=http://forum.flightgear.org/viewtopic.php?f=30&amp;amp;t=29259|title=Nasal: systime() ??!?|author=Necolatis|date=Apr 2nd, 2016}}&amp;lt;/ref&amp;gt; This is due to the underlying {{func link|GetSystemTimeAsFileTime()|link=https://msdn.microsoft.com/en-us/library/windows/desktop/ms724397(v=vs.85).aspx}} API call, which depends on hardware availability of suitable high resolution timers. See also [https://msdn.microsoft.com/en-us/library/windows/desktop/dn553408(v=vs.85).aspx Acquiring high-resolution time stamps]}}&lt;br /&gt;
|example1 = print(&amp;quot;Unix time: &amp;quot;, systime()); # prints Unix time&lt;br /&gt;
|example2 = var myFunc = func(){&lt;br /&gt;
    for(var i = 0; i &amp;lt;= 10; i += 1){&lt;br /&gt;
        print(&amp;quot;Interation #&amp;quot;, i);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
var t = systime(); # record time&lt;br /&gt;
myFunc(); # run function&lt;br /&gt;
var t2 = systime(); # record new time&lt;br /&gt;
print(&amp;quot;myFunc() took &amp;quot;, t2 - t, &amp;quot; seconds&amp;quot;); # print result&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===thisfunc()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thisfunc();&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Returns the function from which this function is called. This allows a function to reliably and safely call itself from within a closure.&lt;br /&gt;
|example1 = var stringify_vec = func(input){&lt;br /&gt;
    if (typeof(input) == &amp;quot;scalar&amp;quot;){&lt;br /&gt;
        return sprintf(&amp;quot;%s&amp;quot;, input);&lt;br /&gt;
    } elsif (typeof(input) == &amp;quot;vector&amp;quot;) {&lt;br /&gt;
        if (size(input) == 0) return &amp;quot;[]&amp;quot;;&lt;br /&gt;
        var this = thisfunc();&lt;br /&gt;
        var buffer = &amp;quot;[&amp;quot;;&lt;br /&gt;
        for(var i = 0; i &amp;lt; size(input); i += 1){&lt;br /&gt;
            buffer ~= this(input[i]);&lt;br /&gt;
            if (i == size(input) - 1) {&lt;br /&gt;
                buffer ~= &amp;quot;]&amp;quot;;&lt;br /&gt;
            } else {&lt;br /&gt;
                buffer ~= &amp;quot;, &amp;quot;;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        return buffer;&lt;br /&gt;
    } else {&lt;br /&gt;
        die(&amp;quot;stringify_vec(): Error! Invalid input. Must be a vector or scalar&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var test_vec = [&amp;quot;a&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c&amp;quot;, 1, 2, 3];&lt;br /&gt;
debug.dump(stringify_vec(test_vec)); # prints &amp;quot;[a, b, c, 1, 2, 3]&amp;quot;&lt;br /&gt;
test_vec = [];&lt;br /&gt;
debug.dump(stringify_vec(test_vec)); # prints &amp;quot;[]&amp;quot;&lt;br /&gt;
test_vec = {};&lt;br /&gt;
debug.dump(stringify_vec(test_vec)); # will throw an error&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===tileIndex() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = tileIndex();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1720|t=Source}}&lt;br /&gt;
|text = Returns the index of the tile at the aircraft's current position as a string. This corresponds to the name of the STG file of the tile. For example, at [[KSFO]], this would be &amp;lt;code&amp;gt;942050&amp;lt;/code&amp;gt;, corresponding to &amp;lt;tt&amp;gt;''[[$FG_SCENERY]]/Terrain/w130n30/w123n3/942050.stg''&amp;lt;/tt&amp;gt;.&lt;br /&gt;
|example1 = print(tileIndex()); # print index&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== tilePath()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = tilePath();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1712|t=Source}}&lt;br /&gt;
|text = Returns the base path of the tile at the aircraft's current position as a string. For example, at KSFO, this would be &amp;lt;code&amp;gt;w130n30/w123n3&amp;lt;/code&amp;gt;, corresponding to &amp;lt;tt&amp;gt;''[[$FG_SCENERY]]/Terrain/w130n30/w123n3''&amp;lt;/tt&amp;gt;.&lt;br /&gt;
|example1 = print(tilePath()); # print path&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== values()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = values(hash);&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Returns a vector containing the values of the given hash.&lt;br /&gt;
|param1 = hash&lt;br /&gt;
|param1text = Mandatory hash to get the values of.&lt;br /&gt;
|example1 = var hash = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 1,&lt;br /&gt;
    &amp;quot;b&amp;quot;: 2,&lt;br /&gt;
    &amp;quot;c&amp;quot;: 3&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
foreach(var val; values(hash)){&lt;br /&gt;
    print(val);&lt;br /&gt;
}&lt;br /&gt;
|example2text = The below example does exactly the same thing as the above example, but does not use &amp;lt;code&amp;gt;values()&amp;lt;/code&amp;gt;:&lt;br /&gt;
|example2 = var hash = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 1,&lt;br /&gt;
    &amp;quot;b&amp;quot;: 2,&lt;br /&gt;
    &amp;quot;c&amp;quot;: 3&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
foreach(var key; keys(hash)){&lt;br /&gt;
    print(hash[key]);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==Extension functions new in FG 2020.1 ==&lt;br /&gt;
The following functions have been added to the nasal core library and will be released with FlightGear version 2020.1. &lt;br /&gt;
Before the release they are available in the development branch &amp;quot;next&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
===isfunc()===&lt;br /&gt;
Returns 1 if type or argument is a function, otherwise 0.&lt;br /&gt;
&lt;br /&gt;
===isghost()===&lt;br /&gt;
Returns 1 if type or argument is a ghost, otherwise 0.&lt;br /&gt;
&lt;br /&gt;
=== ishash()===&lt;br /&gt;
Returns 1 if type or argument is a hash, otherwise 0.&lt;br /&gt;
&lt;br /&gt;
===isint()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = isint(x);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}&lt;br /&gt;
|text = Returns 1 if argument has a numeric value and x == floor(x), e.g. integer.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===isnum()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = isnum(x);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}&lt;br /&gt;
|text = Returns 1 if typeof(x) is &amp;quot;scalar&amp;quot; and x has a numeric value otherwise 0. &lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===isscalar()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = isscalar(x);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}&lt;br /&gt;
|text = Returns 1 if type or argument is a scalar (string or numeric), otherwise (vector, hash, func, ...) it returns 0. This is useful to check if a variable can be converted to string e.g. when useing the string concat operator &amp;quot;~&amp;quot;. &lt;br /&gt;
|example1 = var a = &amp;quot;foo&amp;quot;; &lt;br /&gt;
var b=42;&lt;br /&gt;
if (isscalar(a) and isscalar(b)) print(a~b);&lt;br /&gt;
if (isstr(a)) print(&amp;quot;a is a string&amp;quot;);&lt;br /&gt;
if (isint(b)) print(&amp;quot;b is an integer&amp;quot;);&lt;br /&gt;
# if (isscalar(a))... is equivalent to if (typeof(a) == &amp;quot;scalar&amp;quot;)...&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===isstr()===&lt;br /&gt;
Returns 1 if type or argument is a string, otherwise 0. &lt;br /&gt;
&lt;br /&gt;
===isvec()===&lt;br /&gt;
Returns 1 if type or argument is a vector, otherwise 0.&lt;br /&gt;
&lt;br /&gt;
===vecindex()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = vecindex(vector, value);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}&lt;br /&gt;
|text = Returns the index of value or nil, if value is not found in vector.&lt;br /&gt;
|example1=&lt;br /&gt;
var myvector = [&amp;quot;apple&amp;quot;, &amp;quot;bananna&amp;quot;, &amp;quot;coconut&amp;quot;];&lt;br /&gt;
# to check if a vector contains a certain value compare vecindex to nil&lt;br /&gt;
if (vecindex(myvector, &amp;quot;apple&amp;quot;) != nil) {&lt;br /&gt;
    print(&amp;quot;found apple&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
# WARNING: this will not work as desired because index is 0&lt;br /&gt;
if (vecindex(myvector, &amp;quot;apple&amp;quot;)) {&lt;br /&gt;
    print(&amp;quot;found apple&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==Variables==&lt;br /&gt;
Various global constants (technically variables) are provided for use in converting between different units. They are all found in {{fgdata file|Nasal/globals.nas|text=$FG_ROOT/Nasal/globals.nas}}.&lt;br /&gt;
&lt;br /&gt;
===D2R===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var radians = degrees * D2R;&lt;br /&gt;
|text = Converts an angle from degrees to radians when multiplied by the angle in degrees. Equal to &amp;lt;code&amp;gt;π / 180&amp;lt;/code&amp;gt;.&lt;br /&gt;
}}&lt;br /&gt;
===FPS2KT===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var knots = feet_per_second * FPS2KT;&lt;br /&gt;
|text = Converts a velocity from feet per second to knots when multiplied by the velocity in feet per second. Approximately equal to 0.5925.&lt;br /&gt;
}}&lt;br /&gt;
===FT2M===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var metres = feet * FT2M;&lt;br /&gt;
|text = Converts a length from feet to metres when multiplied by the length in feet. Equal to 0.3048.&lt;br /&gt;
}}&lt;br /&gt;
===GAL2L ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var litres = gallons * GAL2L;&lt;br /&gt;
|text = Converts a volume from US liquid gallons to litres when multiplied by the volume in gallons. Approximately equal to 3.7854.&lt;br /&gt;
}}&lt;br /&gt;
===IN2M===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var metres = inches * IN2M;&lt;br /&gt;
|text = Converts a length from inches to metres when multiplied by the length in inches. Equal to 0.0254.&lt;br /&gt;
}}&lt;br /&gt;
===KG2LB===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var pounds = kilograms * KG2LB;&lt;br /&gt;
|text = Converts a mass from kilograms to pounds when multiplied by the mass in kilograms. Approximately equal to 2.2046.&lt;br /&gt;
}}&lt;br /&gt;
===KT2FPS===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var feet_per_second = knots * KT2FPS;&lt;br /&gt;
|text = Converts a velocity from knots to feet per second when multiplied by the velocity in knots. Approximately equal to 1.6878.&lt;br /&gt;
}}&lt;br /&gt;
===KT2MPS===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var metres_per_second = knots * KT2MPS;&lt;br /&gt;
|text = Converts a velocity from knots to metres per second when multiplied by the velocity in knots. Approximately equal to 0.5144.&lt;br /&gt;
}}&lt;br /&gt;
===L2GAL===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var gallons = litres * L2GAL;&lt;br /&gt;
|text = Converts a volume from litres to US liquid gallons when multiplied by the volume in litres. Approximately equal to 0.2642.&lt;br /&gt;
}}&lt;br /&gt;
===LB2KG===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var kilograms = pounds * LB2KG;&lt;br /&gt;
|text = Converts a mass from pounds to kilograms when multiplied by the mass in pounds. Approximately equal to 0.4536.&lt;br /&gt;
}}&lt;br /&gt;
===M2FT ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var feet = metres * M2FT;&lt;br /&gt;
|text = Converts a length from metres to feet when multiplied by the length in metres. Approximately equal to 3.2808.&lt;br /&gt;
}}&lt;br /&gt;
===M2IN===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var inches = metres * M2IN;&lt;br /&gt;
|text = Converts a length from metres to inches when multiplied by the length in metres. Approximately equal to 39.3701.&lt;br /&gt;
}}&lt;br /&gt;
===M2NM ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var nautical_miles = metres * M2NM;&lt;br /&gt;
|text = Converts a length from metres to nautical miles when multiplied by the length in metres. Approximately equal to 0.00054.&lt;br /&gt;
}}&lt;br /&gt;
===MPS2KT ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var knots = metres_per_second * MPS2KT;&lt;br /&gt;
|text = Converts a velocity from metres per second to knots when multiplied by the velocity in metres per second. Approximately equal to 1.9438.&lt;br /&gt;
}}&lt;br /&gt;
===NM2M ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var metres = nautical_miles * NM2M;&lt;br /&gt;
|text = Converts a length from nautical miles to metres when multiplied by the length in nautical miles. Equal to 1,852.&lt;br /&gt;
}}&lt;br /&gt;
===R2D===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var degrees = radians * R2D;&lt;br /&gt;
|text = Converts an angle from radians to degrees when multiplied by the angle in radians. Equal to &amp;lt;code&amp;gt;180 / π&amp;lt;/code&amp;gt;.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Appendix}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Nasal namespaces}}&lt;/div&gt;</summary>
		<author><name>TheEagle</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Template:Cessna&amp;diff=139116</id>
		<title>Template:Cessna</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Template:Cessna&amp;diff=139116"/>
		<updated>2024-02-13T17:39:42Z</updated>

		<summary type="html">&lt;p&gt;TheEagle: Added new Cessna 310 family&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Navbox&lt;br /&gt;
| name      = Cessna&lt;br /&gt;
| title     = {{LangSwitch&lt;br /&gt;
                | ar = [[:Category:Cessna|Cessna]] طائرات&lt;br /&gt;
                | ca = Aeronaus [[:Category:Cessna|Cessna]]&lt;br /&gt;
                | de = [[:Category:Cessna|Cessna]] Flugzeuge&lt;br /&gt;
                | en = [[:Category:Cessna|Cessna]] aircraft&lt;br /&gt;
                | es = Aeronaves [[:Category:Cessna|Cessna]] &lt;br /&gt;
                | fr = Aéronefs [[:Category:Cessna|Cessna]]&lt;br /&gt;
                | it = Aeromobili [[:Category:Cessna|Cessna]]&lt;br /&gt;
                | ru = Летательные аппараты [[:Category:Cessna|Cessna]]&lt;br /&gt;
                | zh = [[:Category:Cessna|赛斯纳]]飞机 &lt;br /&gt;
              }}&lt;br /&gt;
| listclass = hlist&lt;br /&gt;
| group1 = {{LangSwitch&lt;br /&gt;
             | ca = Aeronaus civils&lt;br /&gt;
             | de = Zivile Flugzeuge&lt;br /&gt;
             | en = Civilian aircraft&lt;br /&gt;
             | es = Aeronaves Civiles&lt;br /&gt;
             | fr = Aéronefs civil&lt;br /&gt;
             | it = Aeromobili civili&lt;br /&gt;
             | ru = Гражданские летательные аппараты&lt;br /&gt;
             | zh = 民用飞机&lt;br /&gt;
           }}&lt;br /&gt;
| list1  =&lt;br /&gt;
* [[Cessna 140|140]]&lt;br /&gt;
* [[Cessna 150|150L]]&lt;br /&gt;
* {{LangSwitch&lt;br /&gt;
    | ca = [[:ca:Cessna 172P|172P]]&lt;br /&gt;
    | de = [[:de:Cessna 172P|172P]]&lt;br /&gt;
    | en = [[Cessna 172P|172P]]&lt;br /&gt;
    | es = [[:es:Cessna 172P|172P]]&lt;br /&gt;
    | fr = [[:fr:Cessna 172P|172P]]&lt;br /&gt;
    | it = [[:it:Cessna 172P|172P]]&lt;br /&gt;
    | nl = [[:nl:Cessna 172P|172P]]&lt;br /&gt;
    | ru = [[:ru:Cessna 172P|172P]]&lt;br /&gt;
    | zh = [[:zh:Cessna 172P|172P]]&lt;br /&gt;
  }}&lt;br /&gt;
* [[Cessna 172R Skyhawk|172R]]&lt;br /&gt;
* {{LangSwitch&lt;br /&gt;
    | de = [[:de:Cessna 182S|182S]]&lt;br /&gt;
    | en = [[Cessna 182S|182S]]&lt;br /&gt;
  }}&lt;br /&gt;
* {{LangSwitch&lt;br /&gt;
    | de = [[:de:Cessna 182T|182T]]&lt;br /&gt;
    | en = [[Cessna 182T|182T]]&lt;br /&gt;
  }}&lt;br /&gt;
* [[Cessna C310|310B]]&lt;br /&gt;
* [[Cessna 310 family|310]]&lt;br /&gt;
* {{LangSwitch&lt;br /&gt;
    | ca = [[:ca:Cessna 337G Skymaster|337G Skymaster]]&lt;br /&gt;
    | de = [[:de:Cessna 337G Skymaster|337G Skymaster]]&lt;br /&gt;
    | en = [[Cessna 337G Skymaster|337G Skymaster]]&lt;br /&gt;
    | es = [[:es:Cessna 337G Skymaster|337G Skymaster]]&lt;br /&gt;
    | fr = [[:fr:Cessna 337G Skymaster|337G Skymaster]]&lt;br /&gt;
    | it = [[:it:Cessna 337G Skymaster|337G Skymaster]]&lt;br /&gt;
  }}&lt;br /&gt;
* [[Cessna 550 Citation II|550 Citation II]]&lt;br /&gt;
* [[Cessna Citation Bravo|Citation Bravo]]&lt;br /&gt;
* {{LangSwitch&lt;br /&gt;
    | ca = [[:ca:Cessna Citation X|Citation X]]&lt;br /&gt;
    | de = [[:de:Cessna Citation X|Citation X]]&lt;br /&gt;
    | en = [[Cessna Citation X|Citation X]]&lt;br /&gt;
    | es = [[:es:Cessna Citation X|Citation X]]&lt;br /&gt;
    | fr = [[:fr:Cessna Citation X|Citation X]]&lt;br /&gt;
    | it = [[:it:Cessna Citation X|Citation X]]&lt;br /&gt;
  }}&lt;br /&gt;
&lt;br /&gt;
| group2 = {{LangSwitch&lt;br /&gt;
             | ca = Aeronaus militars&lt;br /&gt;
             | de = Militärische Flugzeuge&lt;br /&gt;
             | en = Military aircraft&lt;br /&gt;
             | es = Aeronaves Militar&lt;br /&gt;
             | fr = Aéronefs militaire&lt;br /&gt;
             | it = Aeromobili militari&lt;br /&gt;
             | ru = Военные летательные аппараты&lt;br /&gt;
             | zh = 军用飞机&lt;br /&gt;
           }}&lt;br /&gt;
| list2  =&lt;br /&gt;
* [[Cessna T-37|T-37]]&lt;br /&gt;
}}&amp;lt;includeonly&amp;gt;{{main other| [[Category:Cessna]] }}&amp;lt;/includeonly&amp;gt;&amp;lt;noinclude&amp;gt;&lt;br /&gt;
{{Informative template|1=&lt;br /&gt;
__NOTOC__&lt;br /&gt;
== Goal ==&lt;br /&gt;
This navigation template links together Cessna aircraft.&lt;br /&gt;
&lt;br /&gt;
Adding this template to a page in the article namespace will automatically add [[:Category:Cessna]] to that page.&lt;br /&gt;
&lt;br /&gt;
== Usage ==&lt;br /&gt;
Put this line before the categories and the language links at the bottom of the article.&lt;br /&gt;
&lt;br /&gt;
 {{obr}}'''Cessna'''{{cbr}}&lt;br /&gt;
&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
[[Category:Manufacturer navigation templates]]&lt;br /&gt;
&amp;lt;/noinclude&amp;gt;&lt;/div&gt;</summary>
		<author><name>TheEagle</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Cessna_310_family&amp;diff=138361</id>
		<title>Cessna 310 family</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Cessna_310_family&amp;diff=138361"/>
		<updated>2023-09-13T23:56:23Z</updated>

		<summary type="html">&lt;p&gt;TheEagle: Fix ratings&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Infobox Aircraft|name=Cessna 310|fgname1=c310p|forumtid=40501|license=GPLv2+|craft=aircraft|wikipedia=Cessna 310|devel-repo={{github source | user = TheFGFSEagle | repo = c310-family}}|ready=canvas|status-model=4|status-cockpit=3|status-systems=2|status-fdm=3|fgname=c310a|image=Cessna_310B,_static_runup.png|fdm=JSBSim|author3=chad3006|author2=Ysop|author1=TheEagle|devel-team=Cessna 310 development team|manufacturer=Cessna|propulsion=Twin-engine piston aircraft /Propeller aircraft/Tractor aircraft|config=Low wing aircraft/Tractor aircraft/Retractable gear aircraft/Tricycle landing gear aircraft|type=Civil utility aircraft/Military utility aircraft/Transport aircraft|alt=Cessna 310B, static runup|hangar=FGAddon|aircraft=c310-family}}&lt;br /&gt;
&lt;br /&gt;
The '''Cessna 310''' is an American four-to-six-seat, low-wing, twin-engine monoplane produced by Cessna between 1954 and 1980. It was the first twin-engine aircraft that Cessna put into production after World War II.&lt;br /&gt;
&lt;br /&gt;
== Status ==&lt;br /&gt;
This aircraft is under active development by The(FGFS)Eagle, ysop(flying) and chad3006.&lt;br /&gt;
&lt;br /&gt;
=== 3D model ===&lt;br /&gt;
Exterior and interior are modelled with a few details and fully animated, we also have most instruments (only navigational ones are missing). The map light is modelled and fully functional. No livery support yet.&lt;br /&gt;
&lt;br /&gt;
=== Systems ===&lt;br /&gt;
Custom vacuum, oil, fuel and procedural electrical system are implemented, along with flaps, gear and engine starting all depending on electrical current being available. Oil system and reservoir leaks are implemented, which can actually set the engine on fire !&lt;br /&gt;
&lt;br /&gt;
== Variants ==&lt;br /&gt;
At the moment, the 310A variant is being developed, and an FDM for the 310P is available (using the 310A 3D model and systems, however) - further variants are planned.&lt;br /&gt;
&lt;br /&gt;
== Description ==&lt;br /&gt;
&lt;br /&gt;
=== Doors ===&lt;br /&gt;
The cabin entry door can currently only be unlocked from the interior, by rotating the handle down. Then scroll it to open or close the door. Rotate the handle to the horizontal position to lock the door. &lt;br /&gt;
{{Note|If the handle is in the locked (horizontal) position, the door cannot be closed (if it is more than an inch open) or opened (if it is less than an inch open)|width=600px}}&lt;br /&gt;
The baggage door does not have its handle modelled yet - just scroll the door to open or close it.&lt;br /&gt;
&lt;br /&gt;
=== Windows ===&lt;br /&gt;
In the left front side window there is an openable hatch incorporated, that opens to the outside. To operate it, scroll the handle - it will first rotate to unlock the window then the window will open - scroll the handle into the other direction to reverse the process.&lt;br /&gt;
&lt;br /&gt;
=== Chocks ===&lt;br /&gt;
Interactive chocks are implemented - they are by default stored in the baggage compartment. To put them at the current position of the wheels (if the aircraft is rolling fast enough, it will just drive over them), open the baggage door then click them one by one. When the chocks are on the ground, and you should manage to drive over them and fly away - be warned, if you want to have them back you have to collect them where you left them ! Collecting the chocks is done by clicking them again, one by one - this however only works when the baggage door is open.&lt;br /&gt;
&lt;br /&gt;
=== Oil system ===&lt;br /&gt;
The custom oil system is very realistic (in my opinion), including oil consumption by the engine, increased engine friction when the oil level is too low and leakability of both reservoir and system - but there is a difference between the two leaks: If the reservoir has a leak, you won't see any effect of it until it the level is so low that the oil pump cannot suck from it anymore - only then the system will empty out as well, the oil pressure will drop, and the engine will not be lubricated anymore. If the system has a leak however, the effects can be seen immediately, as oil pressure will go down immediately. Both leak types can be triggered and repaired manually for both engines independently from the Failures dialog (&amp;lt;code&amp;gt;Menu -&amp;gt; Cessna 310 -&amp;gt; Failures&amp;lt;/code&amp;gt;). &lt;br /&gt;
&lt;br /&gt;
{{Warning|There  is no way to refill the oil reservoirs (yet) !}}&lt;br /&gt;
&lt;br /&gt;
=== Fuel system ===&lt;br /&gt;
On the 310A, there are two tip tanks (50 gallons each) and two optional auxiliary tanks, installed just outboard the engines in the wing. From the fuel selectors in the cockpit, each engine can be set to feed from:&lt;br /&gt;
&lt;br /&gt;
* no tank (cutoff)&lt;br /&gt;
* the main (tip) tank on the same wing as the engine&lt;br /&gt;
* the auxiliary tank on the same wing as the engine&lt;br /&gt;
* the main (tip) tank on the other wing (crossfeed)&lt;br /&gt;
&lt;br /&gt;
==== Main (tip) tanks ====&lt;br /&gt;
For the tip tanks, the fuel filler cap is modelled with all details - you have to first unlock it by loosening the two fasteners, then scroll the cap to gain access to the filler.&lt;br /&gt;
&lt;br /&gt;
==== Auxiliary tanks (optional) ====&lt;br /&gt;
They need to be enabled in the Equipment dialog (&amp;lt;code&amp;gt;Menu -&amp;gt; Cessna 310 -&amp;gt; Equipment&amp;lt;/code&amp;gt;), the fuel selector will be changed accordingly, and their fuel level can then be set from the &amp;lt;code&amp;gt;Fuel and payload&amp;lt;/code&amp;gt; dialog. Their fillers and filler caps are not yet modelled.&lt;br /&gt;
&lt;br /&gt;
=== Vacuum system ===&lt;br /&gt;
Each engine drives a vacuum pump, their suction is combined to drive the directional and horizon gyro. A suction gauge source selector is provided on the upper-right of the left switches panel, to enable the pilot to check the suction at four different places:&lt;br /&gt;
&lt;br /&gt;
* the left pump (&amp;lt;code&amp;gt;LEFT SOURCE&amp;lt;/code&amp;gt;)&lt;br /&gt;
* the right pump (&amp;lt;code&amp;gt;RIGHT SOURCE&amp;lt;/code&amp;gt;)&lt;br /&gt;
* the horizon gyro (&amp;lt;code&amp;gt;HOR GYRO&amp;lt;/code&amp;gt;)&lt;br /&gt;
* and the directional gyro (&amp;lt;code&amp;gt;DIR GYRO&amp;lt;/code&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
The suction at the selected source will be indicated on the suction gauge.&lt;br /&gt;
&lt;br /&gt;
=== Engines ===&lt;br /&gt;
The engine controls (throttle, prop pitch and mixture) are located on the top of the center pedestal. On the left and right sides of the pedestal are interconnected knobs to adjust the friction lock of the engine controls. &lt;br /&gt;
&lt;br /&gt;
On the front face of the pedestal are knobs to switch carb heat on and off. They have a locking mechanism to prevent them from creeping to a different position (just like the friction lock on the main engine controls) that can be actuated by scrolling while holding the Shift key. The carb heat is toggled by just clicking, without Shift.&lt;br /&gt;
&lt;br /&gt;
In the case of too low oil pressure, the engines will not be lubricated enough - friction will increase, the temperature will climb, eventually leading to an engine fire if you don't feather the prop and stop the engine.&lt;br /&gt;
&lt;br /&gt;
=== Flaps ===&lt;br /&gt;
The flaps on the 310A are a little special - instead of having preset positions you can select with a handle, you have an up / down switch you got to hold into the desired direction until the flaps are at the desired position (displayed on the flap position gauge). That switch is not yet modelled but its behaviour is reflected in the custom key bindings for the &amp;lt;code&amp;gt;[&amp;lt;/code&amp;gt;/&amp;lt;code&amp;gt;]&amp;lt;/code&amp;gt; keys - hold &amp;lt;code&amp;gt;[&amp;lt;/code&amp;gt; to retract the flaps, &amp;lt;code&amp;gt;]&amp;lt;/code&amp;gt; to extend, both at a rate of 5° per second.&lt;br /&gt;
&lt;br /&gt;
== Web links ==&lt;br /&gt;
Discord channel: https://discord.gg/8eFTtaBVAT then go to Cessna 310 #general.&lt;br /&gt;
&lt;br /&gt;
Matrix space: https://matrix.to/#/!fEOTalzMFXdlqnGjUM:matrix.org&lt;br /&gt;
&lt;br /&gt;
GitHub repo: https://github.com/TheFGFSEagle/c310-family&lt;br /&gt;
&lt;br /&gt;
Forum development topic: https://forum.flightgear.org/viewtopic.php?f=4&amp;amp;t=40501&lt;/div&gt;</summary>
		<author><name>TheEagle</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Cessna_310_family&amp;diff=138360</id>
		<title>Cessna 310 family</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Cessna_310_family&amp;diff=138360"/>
		<updated>2023-09-13T23:55:41Z</updated>

		<summary type="html">&lt;p&gt;TheEagle: Fix statuses&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Infobox Aircraft|name=Cessna 310|fgname1=c310p|forumtid=40501|license=GPLv2+|craft=aircraft|wikipedia=Cessna 310|devel-repo={{github source | user = TheFGFSEagle | repo = c310-family}}|ready=canvas|status-model=3|status-cockpit=3|status-systems=2|status-fdm=3|fgname=c310a|image=Cessna_310B,_static_runup.png|fdm=JSBSim|author3=chad3006|author2=Ysop|author1=TheEagle|devel-team=Cessna 310 development team|manufacturer=Cessna|propulsion=Twin-engine piston aircraft /Propeller aircraft/Tractor aircraft|config=Low wing aircraft/Tractor aircraft/Retractable gear aircraft/Tricycle landing gear aircraft|type=Civil utility aircraft/Military utility aircraft/Transport aircraft|alt=Cessna 310B, static runup|hangar=FGAddon|aircraft=c310-family}}&lt;br /&gt;
&lt;br /&gt;
The '''Cessna 310''' is an American four-to-six-seat, low-wing, twin-engine monoplane produced by Cessna between 1954 and 1980. It was the first twin-engine aircraft that Cessna put into production after World War II.&lt;br /&gt;
&lt;br /&gt;
== Status ==&lt;br /&gt;
This aircraft is under active development by The(FGFS)Eagle, ysop(flying) and chad3006.&lt;br /&gt;
&lt;br /&gt;
=== 3D model ===&lt;br /&gt;
Exterior and interior are modelled with a few details and fully animated, we also have most instruments (only navigational ones are missing). The map light is modelled and fully functional. No livery support yet.&lt;br /&gt;
&lt;br /&gt;
=== Systems ===&lt;br /&gt;
Custom vacuum, oil, fuel and procedural electrical system are implemented, along with flaps, gear and engine starting all depending on electrical current being available. Oil system and reservoir leaks are implemented, which can actually set the engine on fire !&lt;br /&gt;
&lt;br /&gt;
== Variants ==&lt;br /&gt;
At the moment, the 310A variant is being developed, and an FDM for the 310P is available (using the 310A 3D model and systems, however) - further variants are planned.&lt;br /&gt;
&lt;br /&gt;
== Description ==&lt;br /&gt;
&lt;br /&gt;
=== Doors ===&lt;br /&gt;
The cabin entry door can currently only be unlocked from the interior, by rotating the handle down. Then scroll it to open or close the door. Rotate the handle to the horizontal position to lock the door. &lt;br /&gt;
{{Note|If the handle is in the locked (horizontal) position, the door cannot be closed (if it is more than an inch open) or opened (if it is less than an inch open)|width=600px}}&lt;br /&gt;
The baggage door does not have its handle modelled yet - just scroll the door to open or close it.&lt;br /&gt;
&lt;br /&gt;
=== Windows ===&lt;br /&gt;
In the left front side window there is an openable hatch incorporated, that opens to the outside. To operate it, scroll the handle - it will first rotate to unlock the window then the window will open - scroll the handle into the other direction to reverse the process.&lt;br /&gt;
&lt;br /&gt;
=== Chocks ===&lt;br /&gt;
Interactive chocks are implemented - they are by default stored in the baggage compartment. To put them at the current position of the wheels (if the aircraft is rolling fast enough, it will just drive over them), open the baggage door then click them one by one. When the chocks are on the ground, and you should manage to drive over them and fly away - be warned, if you want to have them back you have to collect them where you left them ! Collecting the chocks is done by clicking them again, one by one - this however only works when the baggage door is open.&lt;br /&gt;
&lt;br /&gt;
=== Oil system ===&lt;br /&gt;
The custom oil system is very realistic (in my opinion), including oil consumption by the engine, increased engine friction when the oil level is too low and leakability of both reservoir and system - but there is a difference between the two leaks: If the reservoir has a leak, you won't see any effect of it until it the level is so low that the oil pump cannot suck from it anymore - only then the system will empty out as well, the oil pressure will drop, and the engine will not be lubricated anymore. If the system has a leak however, the effects can be seen immediately, as oil pressure will go down immediately. Both leak types can be triggered and repaired manually for both engines independently from the Failures dialog (&amp;lt;code&amp;gt;Menu -&amp;gt; Cessna 310 -&amp;gt; Failures&amp;lt;/code&amp;gt;). &lt;br /&gt;
&lt;br /&gt;
{{Warning|There  is no way to refill the oil reservoirs (yet) !}}&lt;br /&gt;
&lt;br /&gt;
=== Fuel system ===&lt;br /&gt;
On the 310A, there are two tip tanks (50 gallons each) and two optional auxiliary tanks, installed just outboard the engines in the wing. From the fuel selectors in the cockpit, each engine can be set to feed from:&lt;br /&gt;
&lt;br /&gt;
* no tank (cutoff)&lt;br /&gt;
* the main (tip) tank on the same wing as the engine&lt;br /&gt;
* the auxiliary tank on the same wing as the engine&lt;br /&gt;
* the main (tip) tank on the other wing (crossfeed)&lt;br /&gt;
&lt;br /&gt;
==== Main (tip) tanks ====&lt;br /&gt;
For the tip tanks, the fuel filler cap is modelled with all details - you have to first unlock it by loosening the two fasteners, then scroll the cap to gain access to the filler.&lt;br /&gt;
&lt;br /&gt;
==== Auxiliary tanks (optional) ====&lt;br /&gt;
They need to be enabled in the Equipment dialog (&amp;lt;code&amp;gt;Menu -&amp;gt; Cessna 310 -&amp;gt; Equipment&amp;lt;/code&amp;gt;), the fuel selector will be changed accordingly, and their fuel level can then be set from the &amp;lt;code&amp;gt;Fuel and payload&amp;lt;/code&amp;gt; dialog. Their fillers and filler caps are not yet modelled.&lt;br /&gt;
&lt;br /&gt;
=== Vacuum system ===&lt;br /&gt;
Each engine drives a vacuum pump, their suction is combined to drive the directional and horizon gyro. A suction gauge source selector is provided on the upper-right of the left switches panel, to enable the pilot to check the suction at four different places:&lt;br /&gt;
&lt;br /&gt;
* the left pump (&amp;lt;code&amp;gt;LEFT SOURCE&amp;lt;/code&amp;gt;)&lt;br /&gt;
* the right pump (&amp;lt;code&amp;gt;RIGHT SOURCE&amp;lt;/code&amp;gt;)&lt;br /&gt;
* the horizon gyro (&amp;lt;code&amp;gt;HOR GYRO&amp;lt;/code&amp;gt;)&lt;br /&gt;
* and the directional gyro (&amp;lt;code&amp;gt;DIR GYRO&amp;lt;/code&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
The suction at the selected source will be indicated on the suction gauge.&lt;br /&gt;
&lt;br /&gt;
=== Engines ===&lt;br /&gt;
The engine controls (throttle, prop pitch and mixture) are located on the top of the center pedestal. On the left and right sides of the pedestal are interconnected knobs to adjust the friction lock of the engine controls. &lt;br /&gt;
&lt;br /&gt;
On the front face of the pedestal are knobs to switch carb heat on and off. They have a locking mechanism to prevent them from creeping to a different position (just like the friction lock on the main engine controls) that can be actuated by scrolling while holding the Shift key. The carb heat is toggled by just clicking, without Shift.&lt;br /&gt;
&lt;br /&gt;
In the case of too low oil pressure, the engines will not be lubricated enough - friction will increase, the temperature will climb, eventually leading to an engine fire if you don't feather the prop and stop the engine.&lt;br /&gt;
&lt;br /&gt;
=== Flaps ===&lt;br /&gt;
The flaps on the 310A are a little special - instead of having preset positions you can select with a handle, you have an up / down switch you got to hold into the desired direction until the flaps are at the desired position (displayed on the flap position gauge). That switch is not yet modelled but its behaviour is reflected in the custom key bindings for the &amp;lt;code&amp;gt;[&amp;lt;/code&amp;gt;/&amp;lt;code&amp;gt;]&amp;lt;/code&amp;gt; keys - hold &amp;lt;code&amp;gt;[&amp;lt;/code&amp;gt; to retract the flaps, &amp;lt;code&amp;gt;]&amp;lt;/code&amp;gt; to extend, both at a rate of 5° per second.&lt;br /&gt;
&lt;br /&gt;
== Web links ==&lt;br /&gt;
Discord channel: https://discord.gg/8eFTtaBVAT then go to Cessna 310 #general.&lt;br /&gt;
&lt;br /&gt;
Matrix space: https://matrix.to/#/!fEOTalzMFXdlqnGjUM:matrix.org&lt;br /&gt;
&lt;br /&gt;
GitHub repo: https://github.com/TheFGFSEagle/c310-family&lt;br /&gt;
&lt;br /&gt;
Forum development topic: https://forum.flightgear.org/viewtopic.php?f=4&amp;amp;t=40501&lt;/div&gt;</summary>
		<author><name>TheEagle</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Cessna_310_family&amp;diff=138359</id>
		<title>Cessna 310 family</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Cessna_310_family&amp;diff=138359"/>
		<updated>2023-09-13T23:51:32Z</updated>

		<summary type="html">&lt;p&gt;TheEagle: Added infobox&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Infobox Aircraft|name=Cessna 310|fgname1=c310p|forumtid=40501|license=GPLv2+|craft=aircraft|wikipedia=Cessna 310|devel-repo={{github source | user = TheFGFSEagle | repo = c310-family}}|ready=canvas|status-model=3|status-cockpit=2|status-systems=3|status-fdm=1|fgname=c310a|image=Cessna_310B,_static_runup.png|fdm=JSBSim|author3=chad3006|author2=Ysop|author1=TheEagle|devel-team=Cessna 310 development team|manufacturer=Cessna|propulsion=Twin-engine piston aircraft /Propeller aircraft/Tractor aircraft|config=Low wing aircraft/Tractor aircraft/Retractable gear aircraft/Tricycle landing gear aircraft|type=Civil utility aircraft/Military utility aircraft/Transport aircraft|alt=Cessna 310B, static runup|hangar=FGAddon|aircraft=c310-family}}&lt;br /&gt;
&lt;br /&gt;
The '''Cessna 310''' is an American four-to-six-seat, low-wing, twin-engine monoplane produced by Cessna between 1954 and 1980. It was the first twin-engine aircraft that Cessna put into production after World War II.&lt;br /&gt;
&lt;br /&gt;
== Status ==&lt;br /&gt;
This aircraft is under active development by The(FGFS)Eagle, ysop(flying) and chad3006.&lt;br /&gt;
&lt;br /&gt;
=== 3D model ===&lt;br /&gt;
Exterior and interior are modelled with a few details and fully animated, we also have most instruments (only navigational ones are missing). The map light is modelled and fully functional. No livery support yet.&lt;br /&gt;
&lt;br /&gt;
=== Systems ===&lt;br /&gt;
Custom vacuum, oil, fuel and procedural electrical system are implemented, along with flaps, gear and engine starting all depending on electrical current being available. Oil system and reservoir leaks are implemented, which can actually set the engine on fire !&lt;br /&gt;
&lt;br /&gt;
== Variants ==&lt;br /&gt;
At the moment, the 310A variant is being developed, and an FDM for the 310P is available (using the 310A 3D model and systems, however) - further variants are planned.&lt;br /&gt;
&lt;br /&gt;
== Description ==&lt;br /&gt;
&lt;br /&gt;
=== Doors ===&lt;br /&gt;
The cabin entry door can currently only be unlocked from the interior, by rotating the handle down. Then scroll it to open or close the door. Rotate the handle to the horizontal position to lock the door. &lt;br /&gt;
{{Note|If the handle is in the locked (horizontal) position, the door cannot be closed (if it is more than an inch open) or opened (if it is less than an inch open)|width=600px}}&lt;br /&gt;
The baggage door does not have its handle modelled yet - just scroll the door to open or close it.&lt;br /&gt;
&lt;br /&gt;
=== Windows ===&lt;br /&gt;
In the left front side window there is an openable hatch incorporated, that opens to the outside. To operate it, scroll the handle - it will first rotate to unlock the window then the window will open - scroll the handle into the other direction to reverse the process.&lt;br /&gt;
&lt;br /&gt;
=== Chocks ===&lt;br /&gt;
Interactive chocks are implemented - they are by default stored in the baggage compartment. To put them at the current position of the wheels (if the aircraft is rolling fast enough, it will just drive over them), open the baggage door then click them one by one. When the chocks are on the ground, and you should manage to drive over them and fly away - be warned, if you want to have them back you have to collect them where you left them ! Collecting the chocks is done by clicking them again, one by one - this however only works when the baggage door is open.&lt;br /&gt;
&lt;br /&gt;
=== Oil system ===&lt;br /&gt;
The custom oil system is very realistic (in my opinion), including oil consumption by the engine, increased engine friction when the oil level is too low and leakability of both reservoir and system - but there is a difference between the two leaks: If the reservoir has a leak, you won't see any effect of it until it the level is so low that the oil pump cannot suck from it anymore - only then the system will empty out as well, the oil pressure will drop, and the engine will not be lubricated anymore. If the system has a leak however, the effects can be seen immediately, as oil pressure will go down immediately. Both leak types can be triggered and repaired manually for both engines independently from the Failures dialog (&amp;lt;code&amp;gt;Menu -&amp;gt; Cessna 310 -&amp;gt; Failures&amp;lt;/code&amp;gt;). &lt;br /&gt;
&lt;br /&gt;
{{Warning|There  is no way to refill the oil reservoirs (yet) !}}&lt;br /&gt;
&lt;br /&gt;
=== Fuel system ===&lt;br /&gt;
On the 310A, there are two tip tanks (50 gallons each) and two optional auxiliary tanks, installed just outboard the engines in the wing. From the fuel selectors in the cockpit, each engine can be set to feed from:&lt;br /&gt;
&lt;br /&gt;
* no tank (cutoff)&lt;br /&gt;
* the main (tip) tank on the same wing as the engine&lt;br /&gt;
* the auxiliary tank on the same wing as the engine&lt;br /&gt;
* the main (tip) tank on the other wing (crossfeed)&lt;br /&gt;
&lt;br /&gt;
==== Main (tip) tanks ====&lt;br /&gt;
For the tip tanks, the fuel filler cap is modelled with all details - you have to first unlock it by loosening the two fasteners, then scroll the cap to gain access to the filler.&lt;br /&gt;
&lt;br /&gt;
==== Auxiliary tanks (optional) ====&lt;br /&gt;
They need to be enabled in the Equipment dialog (&amp;lt;code&amp;gt;Menu -&amp;gt; Cessna 310 -&amp;gt; Equipment&amp;lt;/code&amp;gt;), the fuel selector will be changed accordingly, and their fuel level can then be set from the &amp;lt;code&amp;gt;Fuel and payload&amp;lt;/code&amp;gt; dialog. Their fillers and filler caps are not yet modelled.&lt;br /&gt;
&lt;br /&gt;
=== Vacuum system ===&lt;br /&gt;
Each engine drives a vacuum pump, their suction is combined to drive the directional and horizon gyro. A suction gauge source selector is provided on the upper-right of the left switches panel, to enable the pilot to check the suction at four different places:&lt;br /&gt;
&lt;br /&gt;
* the left pump (&amp;lt;code&amp;gt;LEFT SOURCE&amp;lt;/code&amp;gt;)&lt;br /&gt;
* the right pump (&amp;lt;code&amp;gt;RIGHT SOURCE&amp;lt;/code&amp;gt;)&lt;br /&gt;
* the horizon gyro (&amp;lt;code&amp;gt;HOR GYRO&amp;lt;/code&amp;gt;)&lt;br /&gt;
* and the directional gyro (&amp;lt;code&amp;gt;DIR GYRO&amp;lt;/code&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
The suction at the selected source will be indicated on the suction gauge.&lt;br /&gt;
&lt;br /&gt;
=== Engines ===&lt;br /&gt;
The engine controls (throttle, prop pitch and mixture) are located on the top of the center pedestal. On the left and right sides of the pedestal are interconnected knobs to adjust the friction lock of the engine controls. &lt;br /&gt;
&lt;br /&gt;
On the front face of the pedestal are knobs to switch carb heat on and off. They have a locking mechanism to prevent them from creeping to a different position (just like the friction lock on the main engine controls) that can be actuated by scrolling while holding the Shift key. The carb heat is toggled by just clicking, without Shift.&lt;br /&gt;
&lt;br /&gt;
In the case of too low oil pressure, the engines will not be lubricated enough - friction will increase, the temperature will climb, eventually leading to an engine fire if you don't feather the prop and stop the engine.&lt;br /&gt;
&lt;br /&gt;
=== Flaps ===&lt;br /&gt;
The flaps on the 310A are a little special - instead of having preset positions you can select with a handle, you have an up / down switch you got to hold into the desired direction until the flaps are at the desired position (displayed on the flap position gauge). That switch is not yet modelled but its behaviour is reflected in the custom key bindings for the &amp;lt;code&amp;gt;[&amp;lt;/code&amp;gt;/&amp;lt;code&amp;gt;]&amp;lt;/code&amp;gt; keys - hold &amp;lt;code&amp;gt;[&amp;lt;/code&amp;gt; to retract the flaps, &amp;lt;code&amp;gt;]&amp;lt;/code&amp;gt; to extend, both at a rate of 5° per second.&lt;br /&gt;
&lt;br /&gt;
== Web links ==&lt;br /&gt;
Discord channel: https://discord.gg/8eFTtaBVAT then go to Cessna 310 #general.&lt;br /&gt;
&lt;br /&gt;
Matrix space: https://matrix.to/#/!fEOTalzMFXdlqnGjUM:matrix.org&lt;br /&gt;
&lt;br /&gt;
GitHub repo: https://github.com/TheFGFSEagle/c310-family&lt;br /&gt;
&lt;br /&gt;
Forum development topic: https://forum.flightgear.org/viewtopic.php?f=4&amp;amp;t=40501&lt;/div&gt;</summary>
		<author><name>TheEagle</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=File:Cessna_310B,_static_runup.png&amp;diff=138358</id>
		<title>File:Cessna 310B, static runup.png</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=File:Cessna_310B,_static_runup.png&amp;diff=138358"/>
		<updated>2023-09-13T22:48:34Z</updated>

		<summary type="html">&lt;p&gt;TheEagle: Uploaded own work with UploadWizard&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=={{int:filedesc}}==&lt;br /&gt;
{{Information&lt;br /&gt;
|description={{en|1=Cessna 310B doing a static runup with.}}&lt;br /&gt;
|date=2023-06-01&lt;br /&gt;
|source={{own}}&lt;br /&gt;
|author=[[User:TheEagle|TheEagle]]&lt;br /&gt;
|permission=&lt;br /&gt;
|other versions=&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=={{int:license-header}}==&lt;br /&gt;
{{self|cc-zero}}&lt;br /&gt;
&lt;br /&gt;
[[Category:Cessna 310 screenshots]]&lt;/div&gt;</summary>
		<author><name>TheEagle</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Category:Cessna_310_screenshots&amp;diff=138357</id>
		<title>Category:Cessna 310 screenshots</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Category:Cessna_310_screenshots&amp;diff=138357"/>
		<updated>2023-09-13T22:46:43Z</updated>

		<summary type="html">&lt;p&gt;TheEagle: Added category&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Screenshots of the Cessna 310&lt;/div&gt;</summary>
		<author><name>TheEagle</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Cessna_310_family&amp;diff=138356</id>
		<title>Cessna 310 family</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Cessna_310_family&amp;diff=138356"/>
		<updated>2023-09-13T22:22:49Z</updated>

		<summary type="html">&lt;p&gt;TheEagle: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The '''Cessna 310''' is an American four-to-six-seat, low-wing, twin-engine monoplane produced by Cessna between 1954 and 1980. It was the first twin-engine aircraft that Cessna put into production after World War II.&lt;br /&gt;
&lt;br /&gt;
== Status ==&lt;br /&gt;
This aircraft is under active development by The(FGFS)Eagle, ysop(flying) and chad3006.&lt;br /&gt;
&lt;br /&gt;
=== 3D model ===&lt;br /&gt;
Exterior and interior are modelled with a few details and fully animated, we also have most instruments (only navigational ones are missing). The map light is modelled and fully functional. No livery support yet.&lt;br /&gt;
&lt;br /&gt;
=== Systems ===&lt;br /&gt;
Custom vacuum, oil, fuel and procedural electrical system are implemented, along with flaps, gear and engine starting all depending on electrical current being available. Oil system and reservoir leaks are implemented, which can actually set the engine on fire !&lt;br /&gt;
&lt;br /&gt;
== Variants ==&lt;br /&gt;
At the moment, the 310A variant is being developed, and an FDM for the 310P is available (using the 310A 3D model and systems, however) - further variants are planned.&lt;br /&gt;
&lt;br /&gt;
== Description ==&lt;br /&gt;
&lt;br /&gt;
=== Doors ===&lt;br /&gt;
The cabin entry door can currently only be unlocked from the interior, by rotating the handle down. Then scroll it to open or close the door. Rotate the handle to the horizontal position to lock the door. &lt;br /&gt;
{{Note|If the handle is in the locked (horizontal) position, the door cannot be closed (if it is more than an inch open) or opened (if it is less than an inch open)}}&lt;br /&gt;
The baggage door does not have its handle modelled yet - just scroll the door to open or close it.&lt;br /&gt;
&lt;br /&gt;
=== Windows ===&lt;br /&gt;
In the left front side window there is an openable hatch incorporated, that opens to the outside. To operate it, scroll the handle - it will first rotate to unlock the window then the window will open - scroll the handle into the other direction to reverse the process.&lt;br /&gt;
&lt;br /&gt;
=== Chocks ===&lt;br /&gt;
Interactive chocks are implemented - they are by default stored in the baggage compartment. To put them at the current position of the wheels (if the aircraft is rolling fast enough, it will just drive over them), open the baggage door then click them one by one. When the chocks are on the ground, and you should manage to drive over them and fly away - be warned, if you want to have them back you have to collect them where you left them ! Collecting the chocks is done by clicking them again, one by one - this however only works when the baggage door is open.&lt;br /&gt;
&lt;br /&gt;
=== Oil system ===&lt;br /&gt;
The custom oil system is very realistic (in my opinion), including oil consumption by the engine, increased engine friction when the oil level is too low and leakability of both reservoir and system - but there is a difference between the two leaks: If the reservoir has a leak, you won't see any effect of it until it the level is so low that the oil pump cannot suck from it anymore - only then the system will empty out as well, the oil pressure will drop, and the engine will not be lubricated anymore. If the system has a leak however, the effects can be seen immediately, as oil pressure will go down immediately. Both leak types can be triggered and repaired manually for both engines independently from the Failures dialog (&amp;lt;code&amp;gt;Menu -&amp;gt; Cessna 310 -&amp;gt; Failures&amp;lt;/code&amp;gt;). &lt;br /&gt;
&lt;br /&gt;
{{Warning|There  is no way to refill the oil reservoirs (yet) !}}&lt;br /&gt;
&lt;br /&gt;
=== Fuel system ===&lt;br /&gt;
On the 310A, there are two tip tanks (50 gallons each) and two optional auxiliary tanks, installed just outboard the engines in the wing. From the fuel selectors in the cockpit, each engine can be set to feed from:&lt;br /&gt;
&lt;br /&gt;
* no tank (cutoff)&lt;br /&gt;
* the main (tip) tank on the same wing as the engine&lt;br /&gt;
* the auxiliary tank on the same wing as the engine&lt;br /&gt;
* the main (tip) tank on the other wing (crossfeed)&lt;br /&gt;
&lt;br /&gt;
==== Main (tip) tanks ====&lt;br /&gt;
For the tip tanks, the fuel filler cap is modelled with all details - you have to first unlock it by loosening the two fasteners, then scroll the cap to gain access to the filler.&lt;br /&gt;
&lt;br /&gt;
==== Auxiliary tanks (optional) ====&lt;br /&gt;
They need to be enabled in the Equipment dialog (&amp;lt;code&amp;gt;Menu -&amp;gt; Cessna 310 -&amp;gt; Equipment&amp;lt;/code&amp;gt;), the fuel selector will be changed accordingly, and their fuel level can then be set from the &amp;lt;code&amp;gt;Fuel and payload&amp;lt;/code&amp;gt; dialog. Their fillers and filler caps are not yet modelled.&lt;br /&gt;
&lt;br /&gt;
=== Vacuum system ===&lt;br /&gt;
Each engine drives a vacuum pump, their suction is combined to drive the directional and horizon gyro. A suction gauge source selector is provided on the upper-right of the left switches panel, to enable the pilot to check the suction at four different places:&lt;br /&gt;
&lt;br /&gt;
* the left pump (&amp;lt;code&amp;gt;LEFT SOURCE&amp;lt;/code&amp;gt;)&lt;br /&gt;
* the right pump (&amp;lt;code&amp;gt;RIGHT SOURCE&amp;lt;/code&amp;gt;)&lt;br /&gt;
* the horizon gyro (&amp;lt;code&amp;gt;HOR GYRO&amp;lt;/code&amp;gt;)&lt;br /&gt;
* and the directional gyro (&amp;lt;code&amp;gt;DIR GYRO&amp;lt;/code&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
The suction at the selected source will be indicated on the suction gauge.&lt;br /&gt;
&lt;br /&gt;
=== Engines ===&lt;br /&gt;
The engine controls (throttle, prop pitch and mixture) are located on the top of the center pedestal. On the left and right sides of the pedestal are interconnected knobs to adjust the friction lock of the engine controls. &lt;br /&gt;
&lt;br /&gt;
On the front face of the pedestal are knobs to switch carb heat on and off. They have a locking mechanism to prevent them from creeping to a different position (just like the friction lock on the main engine controls) that can be actuated by scrolling while holding the Shift key. The carb heat is toggled by just clicking, without Shift.&lt;br /&gt;
&lt;br /&gt;
In the case of too low oil pressure, the engines will not be lubricated enough - friction will increase, the temperature will climb, eventually leading to an engine fire if you don't feather the prop and stop the engine.&lt;br /&gt;
&lt;br /&gt;
=== Flaps ===&lt;br /&gt;
The flaps on the 310A are a little special - instead of having preset positions you can select with a handle, you have an up / down switch you got to hold into the desired direction until the flaps are at the desired position (displayed on the flap position gauge). That switch is not yet modelled but its behaviour is reflected in the custom key bindings for the &amp;lt;code&amp;gt;[&amp;lt;/code&amp;gt;/&amp;lt;code&amp;gt;]&amp;lt;/code&amp;gt; keys - hold &amp;lt;code&amp;gt;[&amp;lt;/code&amp;gt; to retract the flaps, &amp;lt;code&amp;gt;]&amp;lt;/code&amp;gt; to extend, both at a rate of 5° per second.&lt;br /&gt;
&lt;br /&gt;
== Web links ==&lt;br /&gt;
Discord channel: https://discord.gg/8eFTtaBVAT then go to Cessna 310 #general.&lt;br /&gt;
&lt;br /&gt;
Matrix space: https://matrix.to/#/!fEOTalzMFXdlqnGjUM:matrix.org&lt;br /&gt;
&lt;br /&gt;
GitHub repo: https://github.com/TheFGFSEagle/c310-family&lt;br /&gt;
&lt;br /&gt;
Forum development topic: https://forum.flightgear.org/viewtopic.php?f=4&amp;amp;t=40501&lt;/div&gt;</summary>
		<author><name>TheEagle</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Keyboard_shortcuts&amp;diff=138355</id>
		<title>Keyboard shortcuts</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Keyboard_shortcuts&amp;diff=138355"/>
		<updated>2023-09-13T18:57:51Z</updated>

		<summary type="html">&lt;p&gt;TheEagle: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Cheat sheet for using the most common '''keyboard shortcuts'''.&lt;br /&gt;
&lt;br /&gt;
{{note|As keyboard bindings can differ from aircraft to aircraft, some of these shortcuts may be different than showed below}}&lt;br /&gt;
{{tip|If you so wish you can also [[Howto:Reassign keyboard bindings|customize keyboard bindings]]}}&lt;br /&gt;
&lt;br /&gt;
== Keyboard bindings ==&lt;br /&gt;
{| class=&amp;quot;vatop&amp;quot;&lt;br /&gt;
|width=&amp;quot;500&amp;quot; |&lt;br /&gt;
=== General ===&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Key combination&lt;br /&gt;
! Action&lt;br /&gt;
|-&lt;br /&gt;
| {{key press|/}}&lt;br /&gt;
| Show [[property browser]]&lt;br /&gt;
|-&lt;br /&gt;
| {{key press|Ctrl|/}}&lt;br /&gt;
| Show Canvas property browser&lt;br /&gt;
|-&lt;br /&gt;
| {{key press|?}}&lt;br /&gt;
| Show current aircraft specific help&lt;br /&gt;
|-&lt;br /&gt;
| {{key press|:}}&lt;br /&gt;
| vi-like [[multikey commands]]&lt;br /&gt;
|-&lt;br /&gt;
| {{key press|,}}&lt;br /&gt;
| Apply left brakes&lt;br /&gt;
|-&lt;br /&gt;
| {{key press|.}}&lt;br /&gt;
| Apply right brakes&lt;br /&gt;
|-&lt;br /&gt;
| {{key press|Shift|M}}&lt;br /&gt;
| Show map&lt;br /&gt;
|-&lt;br /&gt;
| {{key press|Ctrl|C}}&lt;br /&gt;
| Toggle [[Howto: Make a clickable panel|clickable panel hotspots]]&lt;br /&gt;
|-&lt;br /&gt;
| {{key press|Esc}}&lt;br /&gt;
| Quit FlightGear&lt;br /&gt;
|-&lt;br /&gt;
| {{key press|H}}&lt;br /&gt;
| Cycle through [[HUD]] styles&lt;br /&gt;
|-&lt;br /&gt;
| {{key press|Shift|H}}&lt;br /&gt;
| Cycle through [[HUD]] brightnesses&lt;br /&gt;
|-&lt;br /&gt;
| {{key press|F3}}&lt;br /&gt;
| Print screen&lt;br /&gt;
|-&lt;br /&gt;
| {{key press|F10}}&lt;br /&gt;
| Toggle [[menu]]bar visibility&lt;br /&gt;
|-&lt;br /&gt;
| {{key press|F11}}&lt;br /&gt;
| Open [[autopilot]] dialog&lt;br /&gt;
|-&lt;br /&gt;
| {{key press|F12}}&lt;br /&gt;
| Open radio settings dialog&lt;br /&gt;
|-&lt;br /&gt;
| {{key press|P}}&lt;br /&gt;
| Pause/continue sim&lt;br /&gt;
|-&lt;br /&gt;
| {{key press|Shift|P}}&lt;br /&gt;
| Toggle [[2D panel]]&lt;br /&gt;
|-&lt;br /&gt;
| {{key press|Shift|Esc}}&lt;br /&gt;
| Reset FlightGear&lt;br /&gt;
|-&lt;br /&gt;
| {{key press|Shift|F1}}&lt;br /&gt;
| Load flight&lt;br /&gt;
|-&lt;br /&gt;
| {{key press|Shift|F2}}&lt;br /&gt;
| Save flight&lt;br /&gt;
|-&lt;br /&gt;
| {{key press|Shift|F5}}&lt;br /&gt;
| Scroll 2D panel down&lt;br /&gt;
|-&lt;br /&gt;
| {{key press|Shift|F6}}&lt;br /&gt;
| Scroll 2D panel up&lt;br /&gt;
|-&lt;br /&gt;
| {{key press|Shift|F7}}&lt;br /&gt;
| Scroll 2D panel left&lt;br /&gt;
|-&lt;br /&gt;
| {{key press|Shift|F8}}&lt;br /&gt;
| Scroll 2D panel right&lt;br /&gt;
|-&lt;br /&gt;
| {{key press|Shift|F10}}&lt;br /&gt;
| Toggle fullscreen&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== View ===&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Key combination&lt;br /&gt;
! Action&lt;br /&gt;
|-&lt;br /&gt;
| {{key press|Ctrl|X}}&lt;br /&gt;
| Reset zoom to default&lt;br /&gt;
|-&lt;br /&gt;
| {{key press|V}}&lt;br /&gt;
| Next view&lt;br /&gt;
|-&lt;br /&gt;
| {{key press|Shift|V}}&lt;br /&gt;
| Previous view&lt;br /&gt;
|-&lt;br /&gt;
| {{key press|X}}&lt;br /&gt;
| Zoom in&lt;br /&gt;
|-&lt;br /&gt;
| {{key press|Shift|X}}&lt;br /&gt;
| Zoom out&lt;br /&gt;
|-&lt;br /&gt;
| {{key press|Z}}&lt;br /&gt;
| Increase visibility&lt;br /&gt;
|-&lt;br /&gt;
| {{key press|Shift|Z}}&lt;br /&gt;
| Decrease visibility&lt;br /&gt;
|-&lt;br /&gt;
| {{key press|Ctrl|R}}&lt;br /&gt;
| Instant replay&lt;br /&gt;
|}&lt;br /&gt;
|width=&amp;quot;500&amp;quot;|&lt;br /&gt;
=== Flying ===&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Key combination&lt;br /&gt;
! Action&lt;br /&gt;
|-&lt;br /&gt;
| {{key press|&amp;amp;#125;}} {{key press|&amp;amp;#125;}} {{key press|&amp;amp;#125;}} {{key press|s}}&lt;br /&gt;
| Start a propeller engine (&amp;quot;&amp;amp;#125;&amp;quot; x3 to set magneto, then hold &amp;quot;s&amp;quot; to start)&lt;br /&gt;
|-&lt;br /&gt;
| {{key press|/}}&lt;br /&gt;
| Show [[property browser]]&lt;br /&gt;
|-&lt;br /&gt;
| {{key press|B}}&lt;br /&gt;
| Apply all brakes&lt;br /&gt;
|-&lt;br /&gt;
| {{key press|Shift|B}}&lt;br /&gt;
| Toggle parking brake(s)&lt;br /&gt;
|-&lt;br /&gt;
| {{key press|G}}&lt;br /&gt;
| Gear up&lt;br /&gt;
|-&lt;br /&gt;
| {{key press|Shift|G}}&lt;br /&gt;
| Gear down&lt;br /&gt;
|-&lt;br /&gt;
| {{key press|J}}&lt;br /&gt;
| Spoilers/air brake down&lt;br /&gt;
|-&lt;br /&gt;
| {{key press|K}}&lt;br /&gt;
| Spoilers/air brake up&lt;br /&gt;
|-&lt;br /&gt;
| {{key press|[}}&lt;br /&gt;
| Flaps up&lt;br /&gt;
|-&lt;br /&gt;
| {{key press|]}}&lt;br /&gt;
| Flaps down&lt;br /&gt;
|-&lt;br /&gt;
| {{key press|Alt|2}}&lt;br /&gt;
| Toggle Auto-coordination&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Autopilot ===&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Key combination&lt;br /&gt;
! Action&lt;br /&gt;
|-&lt;br /&gt;
| {{key press|Ctrl|A}}&lt;br /&gt;
| Altitude lock&lt;br /&gt;
|-&lt;br /&gt;
| {{key press|Ctrl|H}}&lt;br /&gt;
| Heading lock&lt;br /&gt;
|-&lt;br /&gt;
| {{key press|Ctrl|G}}&lt;br /&gt;
| Glideslope lock&lt;br /&gt;
|-&lt;br /&gt;
| {{key press|Ctrl|N}}&lt;br /&gt;
| NAV1 lock&lt;br /&gt;
|-&lt;br /&gt;
| {{key press|Ctrl|P}}&lt;br /&gt;
| Pitch hold&lt;br /&gt;
|-&lt;br /&gt;
| {{key press|Ctrl|S}}&lt;br /&gt;
| Autothrottle&lt;br /&gt;
|-&lt;br /&gt;
| {{key press|Ctrl|T}}&lt;br /&gt;
| Terrain lock&lt;br /&gt;
|-&lt;br /&gt;
| {{key press|Ctrl|W}}&lt;br /&gt;
| Wing level&lt;br /&gt;
|-&lt;br /&gt;
| {{key press|F6}}&lt;br /&gt;
| Heading lock&lt;br /&gt;
|}&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
* [[Bindings]]&lt;br /&gt;
* [[Howto:Reassign keyboard bindings]]&lt;br /&gt;
&lt;br /&gt;
[[Category:FlightGear keyboard]]&lt;/div&gt;</summary>
		<author><name>TheEagle</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Cessna_310_family&amp;diff=138346</id>
		<title>Cessna 310 family</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Cessna_310_family&amp;diff=138346"/>
		<updated>2023-09-07T21:44:16Z</updated>

		<summary type="html">&lt;p&gt;TheEagle: Major update part 3&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The '''Cessna 310''' is an American four-to-six-seat, low-wing, twin-engine monoplane produced by Cessna between 1954 and 1980. It was the first twin-engine aircraft that Cessna put into production after World War II.&lt;br /&gt;
&lt;br /&gt;
== Status ==&lt;br /&gt;
This aircraft is under active development by The(FGFS)Eagle, ysop(flying) and chad3006.&lt;br /&gt;
&lt;br /&gt;
=== 3D model ===&lt;br /&gt;
Exterior and interior are modelled with a few details and fully animated, we also have most instruments (only navigational ones are missing). The map light is modelled and fully functional. No livery support yet.&lt;br /&gt;
&lt;br /&gt;
=== Systems ===&lt;br /&gt;
Custom vacuum, oil, fuel and procedural electrical system are implemented, along with flaps, gear and engine starting all depending on electrical current being available. Oil system and reservoir leaks are implemented, which can actually set the engine on fire !&lt;br /&gt;
&lt;br /&gt;
== Variants ==&lt;br /&gt;
At the moment, the 310A variant is being developed, and an FDM for the 310P is available (using the 310A 3D model and systems, however) - further variants are planned.&lt;br /&gt;
&lt;br /&gt;
== Description ==&lt;br /&gt;
&lt;br /&gt;
=== Doors ===&lt;br /&gt;
The cabin entry door can currently only be unlocked from the interior, by rotating the handle down. Then scroll it to open or close the door. Rotate the handle to the horizontal position to lock the door. &lt;br /&gt;
{{Note|If the handle is in the locked (horizontal) position, the door cannot be closed (if it is more than an inch open) or opened (if it is less than an inch open)}}&lt;br /&gt;
The baggage door does not have its handle modelled yet - just scroll the door to open or close it.&lt;br /&gt;
&lt;br /&gt;
=== Windows ===&lt;br /&gt;
In the left front side window there is an openable hatch incorporated, that opens to the outside. To operate it, scroll the handle - it will first rotate to unlock the window then the window will open - scroll the handle into the other direction to reverse the process.&lt;br /&gt;
&lt;br /&gt;
=== Chocks ===&lt;br /&gt;
Interactive chocks are implemented - they are by default stored in the baggage compartment. To put them at the current position of the wheels (if the aircraft is rolling fast enough, it will just drive over them), open the baggage door then click them one by one. When the chocks are on the ground, and you should manage to drive over them and fly away - be warned, if you want to have them back you have to collect them where you left them ! Collecting the chocks is done by clicking them again, one by one - this however only works when the baggage door is open.&lt;br /&gt;
&lt;br /&gt;
=== Oil system ===&lt;br /&gt;
The custom oil system is very realistic (in my opinion), including oil consumption by the engine, increased engine friction when the oil level is too low and leakability of both reservoir and system - but there is a difference between the two leaks: If the reservoir has a leak, you won't see any effect of it until it the level is so low that the oil pump cannot suck from it anymore - only then the system will empty out as well, the oil pressure will drop, and the engine will not be lubricated anymore. If the system has a leak however, the effects can be seen immediately, as oil pressure will go down immediately. Both leak types can be triggered and repaired manually for both engines independently from the Failures dialog (&amp;lt;code&amp;gt;Menu -&amp;gt; Cessna 310 -&amp;gt; Failures&amp;lt;/code&amp;gt;). &lt;br /&gt;
&lt;br /&gt;
{{Warning|There  is no way to refill the oil reservoirs (yet) !}}&lt;br /&gt;
&lt;br /&gt;
=== Fuel system ===&lt;br /&gt;
On the 310A, there are two tip tanks (50 gallons each) and two optional auxiliary tanks, installed just outboard the engines in the wing. From the fuel selectors in the cockpit, each engine can be set to feed from:&lt;br /&gt;
&lt;br /&gt;
* no tank (cutoff)&lt;br /&gt;
* the main (tip) tank on the same wing as the engine&lt;br /&gt;
* the auxiliary tank on the same wing as the engine&lt;br /&gt;
* the main (tip) tank on the other wing (crossfeed)&lt;br /&gt;
&lt;br /&gt;
==== Main (tip) tanks ====&lt;br /&gt;
For the tip tanks, the fuel filler cap is modelled with all details - you have to first unlock it by loosening the two fasteners, then scroll the cap to gain access to the filler.&lt;br /&gt;
&lt;br /&gt;
==== Auxiliary tanks (optional) ====&lt;br /&gt;
They need to be enabled in the Equipment dialog (&amp;lt;code&amp;gt;Menu -&amp;gt; Cessna 310 -&amp;gt; Equipment&amp;lt;/code&amp;gt;), the fuel selector will be changed accordingly, and their fuel level can then be set from the &amp;lt;code&amp;gt;Fuel and payload&amp;lt;/code&amp;gt; dialog. Their fillers and filler caps are not yet modelled.&lt;br /&gt;
&lt;br /&gt;
=== Vacuum system ===&lt;br /&gt;
Each engine drives a vacuum pump, their suction is combined to drive the directional and horizon gyro. A suction gauge source selector is provided on the upper-right of the left switches panel, to enable the pilot to check the suction at four different places:&lt;br /&gt;
&lt;br /&gt;
* the left pump (&amp;lt;code&amp;gt;LEFT SOURCE&amp;lt;/code&amp;gt;)&lt;br /&gt;
* the right pump (&amp;lt;code&amp;gt;RIGHT SOURCE&amp;lt;/code&amp;gt;)&lt;br /&gt;
* the horizon gyro (&amp;lt;code&amp;gt;HOR GYRO&amp;lt;/code&amp;gt;)&lt;br /&gt;
* and the directional gyro (&amp;lt;code&amp;gt;DIR GYRO&amp;lt;/code&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
The suction at the selected source will be indicated on the suction gauge.&lt;br /&gt;
&lt;br /&gt;
=== Engines ===&lt;br /&gt;
The engine controls (throttle, prop pitch and mixture) are located on the top of the center pedestal. On the left and right sides of the pedestal are interconnected knobs to adjust the friction lock of the engine controls. &lt;br /&gt;
&lt;br /&gt;
On the front face of the pedestal are knobs to switch carb heat on and off. They have a locking mechanism to prevent them from creeping to a different position (just like the friction lock on the main engine controls) that can be actuated by scrolling while holding the Shift key. The carb heat is toggled by just clicking, without Shift.&lt;br /&gt;
&lt;br /&gt;
In the case of too low oil pressure, the engines will not be lubricated enough - friction will increase, the temperature will climb, eventually leading to an engine fire if you don't feather the prop and stop the engine.&lt;br /&gt;
&lt;br /&gt;
=== Flaps ===&lt;br /&gt;
The flaps on the 310A are a little special - instead of having preset positions you can select with a handle, you have an up / down switch you got to hold into the desired direction until the flaps are at the desired position (displayed on the flap position gauge). That switch is not yet modelled but its behaviour is reflected in the custom key bindings for the &amp;lt;code&amp;gt;[&amp;lt;/code&amp;gt;/&amp;lt;code&amp;gt;]&amp;lt;/code&amp;gt; keys - hold &amp;lt;code&amp;gt;[&amp;lt;/code&amp;gt; to retract the flaps, &amp;lt;code&amp;gt;]&amp;lt;/code&amp;gt; to extend.&lt;br /&gt;
&lt;br /&gt;
== Web links ==&lt;br /&gt;
Discord channel: https://discord.gg/8eFTtaBVAT then go to Cessna 310 #general.&lt;br /&gt;
&lt;br /&gt;
Matrix space: https://matrix.to/#/!fEOTalzMFXdlqnGjUM:matrix.org&lt;br /&gt;
&lt;br /&gt;
GitHub repo: https://github.com/TheFGFSEagle/c310-family&lt;br /&gt;
&lt;br /&gt;
Forum development topic: https://forum.flightgear.org/viewtopic.php?f=4&amp;amp;t=40501&lt;/div&gt;</summary>
		<author><name>TheEagle</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Cessna_310_family&amp;diff=138345</id>
		<title>Cessna 310 family</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Cessna_310_family&amp;diff=138345"/>
		<updated>2023-09-07T19:45:44Z</updated>

		<summary type="html">&lt;p&gt;TheEagle: Major update part 2&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The '''Cessna 310''' is an American four-to-six-seat, low-wing, twin-engine monoplane produced by Cessna between 1954 and 1980. It was the first twin-engine aircraft that Cessna put into production after World War II.&lt;br /&gt;
&lt;br /&gt;
== Status ==&lt;br /&gt;
This aircraft is under active development by The(FGFS)Eagle, ysop(flying) and chad3006.&lt;br /&gt;
&lt;br /&gt;
=== 3D model ===&lt;br /&gt;
Exterior and interior are modelled with a few details and fully animated, we also have most instruments (only navigational ones are missing). The map light is modelled and fully functional. No livery support yet.&lt;br /&gt;
&lt;br /&gt;
=== Systems ===&lt;br /&gt;
Custom vacuum, oil, fuel and procedural electrical system are implemented, along with flaps, gear and engine starting all depending on electrical current being available. Oil system and reservoir leaks are implemented, which can actually set the engine on fire !&lt;br /&gt;
&lt;br /&gt;
== Variants ==&lt;br /&gt;
At the moment, the 310A variant is being developed, and an FDM for the 310P is available (using the 310A 3D model and systems, however) - further variants are planned.&lt;br /&gt;
&lt;br /&gt;
== Description ==&lt;br /&gt;
&lt;br /&gt;
=== Doors ===&lt;br /&gt;
The cabin entry door can currently only be unlocked from the interior, by rotating the handle down. Then scroll it to open or close the door. Rotate the handle to the horizontal position to lock the door. &lt;br /&gt;
{{Note|If the handle is in the locked (horizontal) position, the door cannot be closed (if it is more than an inch open) or opened (if it is less than an inch open)}}&lt;br /&gt;
The baggage door does not have its handle modelled yet - just scroll the door to open or close it.&lt;br /&gt;
&lt;br /&gt;
=== Windows ===&lt;br /&gt;
In the left front side window there is an openable hatch incorporated, that opens to the outside. To operate it, scroll the handle - it will first rotate to unlock the window then the window will open - scroll the handle into the other direction to reverse the process.&lt;br /&gt;
&lt;br /&gt;
=== Chocks ===&lt;br /&gt;
Interactive chocks are implemented - they are by default stored in the baggage compartment. To put them at the current position of the wheels (if the aircraft is rolling fast enough, it will just drive over them), open the baggage door then click them one by one. When the chocks are on the ground, and you should manage to drive over them and fly away - be warned, if you want to have them back you have to collect them where you left them ! Collecting the chocks is done by clicking them again, one by one - this however only works when the baggage door is open.&lt;br /&gt;
&lt;br /&gt;
=== Oil system ===&lt;br /&gt;
The custom oil system is very realistic (in my opinion), including oil consumption by the engine, increased engine friction when the oil level is too low and leakability of both reservoir and system - but there is a difference between the two leaks: If the reservoir has a leak, you won't see any effect of it until it the level is so low that the oil pump cannot suck from it anymore - only then the system will empty out as well, the oil pressure will drop, and the engine will not be lubricated anymore. If the system has a leak however, the effects can be seen immediately, as oil pressure will go down immediately. Both leak types can be triggered and repaired manually for both engines independently from the Failures dialog (&amp;lt;code&amp;gt;Menu -&amp;gt; Cessna 310 -&amp;gt; Failures&amp;lt;/code&amp;gt;). &lt;br /&gt;
&lt;br /&gt;
{{Warning|There  is no way to refill the oil reservoirs (yet) !}}&lt;br /&gt;
&lt;br /&gt;
=== Fuel system ===&lt;br /&gt;
On the 310A, there are two tip tanks (50 gallons each) and two optional auxiliary tanks, installed just outboard the engines in the wing. From the fuel selectors in the cockpit, each engine can be set to feed from:&lt;br /&gt;
&lt;br /&gt;
* no tank (cutoff)&lt;br /&gt;
* the main (tip) tank on the same wing as the engine&lt;br /&gt;
* the auxiliary tank on the same wing as the engine&lt;br /&gt;
* the main (tip) tank on the other wing (crossfeed)&lt;br /&gt;
&lt;br /&gt;
==== Main (tip) tanks ====&lt;br /&gt;
For the tip tanks, the fuel filler cap is modelled with all details - you have to first unlock it by loosening the two fasteners, then scroll the cap to gain access to the filler.&lt;br /&gt;
&lt;br /&gt;
==== Auxiliary tanks (optional) ====&lt;br /&gt;
They need to be enabled in the Equipment dialog (&amp;lt;code&amp;gt;Menu -&amp;gt; Cessna 310 -&amp;gt; Equipment&amp;lt;/code&amp;gt;), the fuel selector will be changed accordingly, and their fuel level can then be set from the &amp;lt;code&amp;gt;Fuel and payload&amp;lt;/code&amp;gt; dialog. Their fillers and filler caps are not yet modelled.&lt;br /&gt;
&lt;br /&gt;
=== Vacuum system ===&lt;br /&gt;
Each engine drives a vacuum pump, their suction is combined to drive the directional and horizon gyro. A suction gauge source selector is provided on the upper-right of the left switches panel, to enable the pilot to check the suction at four different places:&lt;br /&gt;
&lt;br /&gt;
* the left pump (&amp;lt;code&amp;gt;LEFT SOURCE&amp;lt;/code&amp;gt;)&lt;br /&gt;
* the right pump (&amp;lt;code&amp;gt;RIGHT SOURCE&amp;lt;/code&amp;gt;)&lt;br /&gt;
* the horizon gyro (&amp;lt;code&amp;gt;HOR GYRO&amp;lt;/code&amp;gt;)&lt;br /&gt;
* and the directional gyro (&amp;lt;code&amp;gt;DIR GYRO&amp;lt;/code&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
The suction at the selected source will be indicated on the suction gauge.&lt;br /&gt;
&lt;br /&gt;
== Web links ==&lt;br /&gt;
Discord channel: https://discord.gg/8eFTtaBVAT then go to Cessna 310 #general.&lt;br /&gt;
&lt;br /&gt;
Matrix space: https://matrix.to/#/!fEOTalzMFXdlqnGjUM:matrix.org&lt;br /&gt;
&lt;br /&gt;
GitHub repo: https://github.com/TheFGFSEagle/c310-family&lt;br /&gt;
&lt;br /&gt;
Forum development topic: https://forum.flightgear.org/viewtopic.php?f=4&amp;amp;t=40501&lt;/div&gt;</summary>
		<author><name>TheEagle</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Cessna_310_family&amp;diff=138344</id>
		<title>Cessna 310 family</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Cessna_310_family&amp;diff=138344"/>
		<updated>2023-09-07T19:36:18Z</updated>

		<summary type="html">&lt;p&gt;TheEagle: Major update part 1&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The '''Cessna 310''' is an American four-to-six-seat, low-wing, twin-engine monoplane produced by Cessna between 1954 and 1980. It was the first twin-engine aircraft that Cessna put into production after World War II.&lt;br /&gt;
&lt;br /&gt;
== Status ==&lt;br /&gt;
This aircraft is under active development by The(FGFS)Eagle, ysop(flying) and chad3006.&lt;br /&gt;
&lt;br /&gt;
=== 3D model ===&lt;br /&gt;
Exterior and interior are modelled with a few details and fully animated, we also have most instruments (only navigational ones are missing). The map light is modelled and fully functional. No livery support yet.&lt;br /&gt;
&lt;br /&gt;
=== Systems ===&lt;br /&gt;
Custom vacuum, oil, fuel and procedural electrical system are implemented, along with flaps, gear and engine starting all depending on electrical current being available. Oil system and reservoir leaks are implemented, which can actually set the engine on fire !&lt;br /&gt;
&lt;br /&gt;
== Variants ==&lt;br /&gt;
At the moment, the 310A variant is being developed, and an FDM for the 310P is available (using the 310A 3D model and systems, however) - further variants are planned.&lt;br /&gt;
&lt;br /&gt;
== Description ==&lt;br /&gt;
&lt;br /&gt;
=== Doors ===&lt;br /&gt;
The cabin entry door can currently only be unlocked from the interior, by rotating the handle down. Then scroll it to open or close the door. Rotate the handle to the horizontal position to lock the door. &lt;br /&gt;
{{Note|If the handle is in the locked (horizontal) position, the door cannot be closed (if it is more than an inch open) or opened (if it is less than an inch open)}}&lt;br /&gt;
The baggage door does not have its handle modelled yet - just scroll the door to open or close it.&lt;br /&gt;
&lt;br /&gt;
=== Windows ===&lt;br /&gt;
In the left front side window there is an openable hatch incorporated, that opens to the outside. To operate it, scroll the handle - it will first rotate to unlock the window then the window will open - scroll the handle into the other direction to reverse the process.&lt;br /&gt;
&lt;br /&gt;
=== Chocks ===&lt;br /&gt;
Interactive chocks are implemented - they are by default stored in the baggage compartment. To put them at the current position of the wheels (if the aircraft is rolling fast enough, it will just drive over them), open the baggage door then click them one by one. When the chocks are on the ground, and you should manage to drive over them and fly away - be warned, if you want to have them back you have to collect them where you left them ! Collecting the chocks is done by clicking them again, one by one - this however only works when the baggage door is open.&lt;br /&gt;
&lt;br /&gt;
=== Oil system ===&lt;br /&gt;
The custom oil system is very realistic (in my opinion), including oil consumption by the engine, increased engine friction when the oil level is too low and leakability of both reservoir and system - but there is a difference between the two leaks: If the reservoir has a leak, you won't see any effect of it until it the level is so low that the oil pump cannot suck from it anymore - only then the system will empty out as well, the oil pressure will drop, and the engine will not be lubricated anymore. If the system has a leak however, the effects can be seen immediately, as oil pressure will go down immediately. Both leak types can be triggered and repaired manually for both engines independently from the Failures dialog (&amp;lt;code&amp;gt;Menu -&amp;gt; Cessna 310 -&amp;gt; Failures&amp;lt;/code&amp;gt;). &lt;br /&gt;
&lt;br /&gt;
There is no way to refill the oil reservoirs (yet) !&lt;br /&gt;
&lt;br /&gt;
=== Fuel system ===&lt;br /&gt;
On the 310A, there are two tip tanks (50 gallons each) and two optional auxiliary tanks, installed just outboard the engines in the wing. From the fuel selectors in the cockpit, each engine can be set to feed from:&lt;br /&gt;
&lt;br /&gt;
* no tank (cutoff)&lt;br /&gt;
* the main (tip) tank on the same wing as the engine&lt;br /&gt;
* the auxiliary tank on the same wing as the engine&lt;br /&gt;
* the main (tip) tank on the other wing (crossfeed)&lt;br /&gt;
&lt;br /&gt;
==== Main (tip) tanks ====&lt;br /&gt;
For the tip tanks, the fuel filler cap is modelled with all details - you have to first unlock it by loosening the two fasteners, then scroll the cap to gain access to the filler.&lt;br /&gt;
&lt;br /&gt;
==== Auxiliary tanks (optional) ====&lt;br /&gt;
They need to be enabled in the Equipment dialog (&amp;lt;code&amp;gt;Menu -&amp;gt; Cessna 310 -&amp;gt; Equipment&amp;lt;/code&amp;gt;), the fuel selector will be changed accordingly, and their fuel level can then be set from the &amp;lt;code&amp;gt;Fuel and payload&amp;lt;/code&amp;gt; dialog. Their fillers and filler caps are not yet modelled.&lt;br /&gt;
&lt;br /&gt;
== Web links ==&lt;br /&gt;
Discord channel: https://discord.gg/8eFTtaBVAT then go to Cessna 310 #general.&lt;br /&gt;
&lt;br /&gt;
Matrix space: https://matrix.to/#/!fEOTalzMFXdlqnGjUM:matrix.org&lt;br /&gt;
&lt;br /&gt;
GitHub repo: https://github.com/TheFGFSEagle/c310-family&lt;br /&gt;
&lt;br /&gt;
Forum development topic: https://forum.flightgear.org/viewtopic.php?f=4&amp;amp;t=40501&lt;/div&gt;</summary>
		<author><name>TheEagle</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Using_JavaProp_to_generate_JSBSim_propeller_coefficients&amp;diff=138166</id>
		<title>Using JavaProp to generate JSBSim propeller coefficients</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Using_JavaProp_to_generate_JSBSim_propeller_coefficients&amp;diff=138166"/>
		<updated>2023-08-14T22:31:15Z</updated>

		<summary type="html">&lt;p&gt;TheEagle: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
This is a tutorial on how to generate thrust and power coefficients for a JSBSim propeller using JavaProp. JavaProp can be obtained from Martin Hepperle's website - follow the instructions at https://www.mh-aerotools.de/airfoils/javaprop.htm -&amp;gt; Step by Step Instructions to install a local copy of JavaProp.&lt;br /&gt;
&lt;br /&gt;
As example &amp;quot;subject&amp;quot; for this tutorial I will use the propeller of the Daher TBM 900.&lt;br /&gt;
&lt;br /&gt;
== Step 1 - Gathering data ==&lt;br /&gt;
Find the diameter, spinner diameter, number of blades and the minimum / maximum / feather / reverse pitch settings, governor minimum and maximum RPM for the propeller to be modelled. Barely available, but knowing the real airfoil is of course really really good !&lt;br /&gt;
&lt;br /&gt;
Most of this data is usually available in the POH of the aircraft - but the TCDS (Type Certificate Data Sheet) of the EASA / FAA  has proven to be an important data source, too.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Here's the data for the propeller of the TBM 900:&lt;br /&gt;
&lt;br /&gt;
* Model (name): HC-E5N-3C&lt;br /&gt;
* Number of blades: 5&lt;br /&gt;
* Diameter: 91 in (2.311 m) (I usually pick the larger diameter of the minimum and maximum diameters supplied in the POH)&lt;br /&gt;
* Spinner diameter: can be measured on / estimated from the three-view drawing of the aircraft - not the diameter at the spinner root but the one at the blade station is to be used ! 0.45 m in this case.&lt;br /&gt;
* Pitch settings '''''at the 30 in station''''':&lt;br /&gt;
** Reverse pitch stop: -9°&lt;br /&gt;
** Low pitch stop: 19.5°&lt;br /&gt;
** High pitch stop: often is not supplied in the POH if the propeller is feathering - it may be supplied in the TCDS of the propeller, but in this case no pitch settings are specified in the TCDS at all - so I took a guess at 30°.&lt;br /&gt;
** Feather pitch stop: 85°&lt;br /&gt;
* Minimum prop governor speed: usually in the POH -&amp;gt; General -&amp;gt; Descriptive data -&amp;gt; Propeller, else in the TCDS or as last resort the lower end of the green range on the propeller tachometer. None of those was available for the TBM 900 though … so I'm guessing at 1500.&lt;br /&gt;
* Maximum prop governor speed: 2000 rpm&lt;br /&gt;
&lt;br /&gt;
Additionally, an airspeed to design the propeller for has to be chosen - should be a low cruise speed. I picked 170 kts &lt;br /&gt;
&lt;br /&gt;
== Step 2 - Starting JavaProp and initial setup: ==&lt;br /&gt;
Now start JavaProp - on Linux systems open a terminal window (&amp;lt;code&amp;gt;Ctrl&amp;lt;/code&amp;gt;+&amp;lt;code&amp;gt;Alt&amp;lt;/code&amp;gt;+&amp;lt;code&amp;gt;T&amp;lt;/code&amp;gt;), &amp;lt;code&amp;gt;cd&amp;lt;/code&amp;gt;into the directory containing &amp;lt;code&amp;gt;JavaProp.sh&amp;lt;/code&amp;gt; and execute&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;~/MH-Aerotools/JavaProp$ ./JavaProp.sh&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
It will (hopefully) show a splash screen for a few moments, then you are shown the main window, with the Design tab open. Enter all your data there (name, number of blades, airspeed&lt;br /&gt;
[[Category:JSBSim]]&lt;br /&gt;
{{DEFAULTSORT:Using JavaProp to generate JSBSim propeller coefficients}}&lt;/div&gt;</summary>
		<author><name>TheEagle</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Using_JavaProp_to_generate_JSBSim_propeller_coefficients&amp;diff=138144</id>
		<title>Using JavaProp to generate JSBSim propeller coefficients</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Using_JavaProp_to_generate_JSBSim_propeller_coefficients&amp;diff=138144"/>
		<updated>2023-08-13T01:33:33Z</updated>

		<summary type="html">&lt;p&gt;TheEagle: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
This is a tutorial on how to generate thrust and power coefficients for a JSBSim propeller using JavaProp. JavaProp can be obtained from Martin Hepperle's website - follow the instructions at https://www.mh-aerotools.de/airfoils/javaprop.htm -&amp;gt; Step by Step Instructions to install a local copy of JavaProp.&lt;br /&gt;
&lt;br /&gt;
As example &amp;quot;subject&amp;quot; for this tutorial I will use the propeller of the Daher TBM 900.&lt;br /&gt;
&lt;br /&gt;
== Step 1 - Gathering data ==&lt;br /&gt;
Find the diameter, spinner diameter, number of blades and the minimum / maximum / feather / reverse pitch settings, governor minimum and maximum RPM for the propeller to be modelled. Barely available, but knowing the real airfoil is of course really really good !&lt;br /&gt;
&lt;br /&gt;
Most of this data is usually available in the POH of the aircraft - but the TCDS (Type Certificate Data Sheet) of the EASA / FAA  has proven to be an important data source, too.&lt;br /&gt;
&lt;br /&gt;
== Step 2 - Starting JavaProp and initial setup ==&lt;br /&gt;
Now start JavaProp - on Linux systems open a terminal window (&amp;lt;code&amp;gt;Ctrl&amp;lt;/code&amp;gt;+&amp;lt;code&amp;gt;Alt&amp;lt;/code&amp;gt;+&amp;lt;code&amp;gt;T&amp;lt;/code&amp;gt;)&lt;br /&gt;
[[Category:JSBSim]]&lt;br /&gt;
{{DEFAULTSORT:Using JavaProp to generate JSBSim propeller coefficients}}&lt;/div&gt;</summary>
		<author><name>TheEagle</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Using_JavaProp_to_generate_JSBSim_propeller_coefficients&amp;diff=137864</id>
		<title>Using JavaProp to generate JSBSim propeller coefficients</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Using_JavaProp_to_generate_JSBSim_propeller_coefficients&amp;diff=137864"/>
		<updated>2023-07-03T16:54:20Z</updated>

		<summary type="html">&lt;p&gt;TheEagle: Created page&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This is a tutorial on how to generate thrust and power coefficients for a JSBSim propeller using JavaProp.&lt;br /&gt;
[[Category:JSBSim]]&lt;br /&gt;
{{DEFAULTSORT:Using JavaProp to generate JSBSim propeller coefficients}}&lt;/div&gt;</summary>
		<author><name>TheEagle</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Pax_transport_addon&amp;diff=137681</id>
		<title>Pax transport addon</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Pax_transport_addon&amp;diff=137681"/>
		<updated>2023-05-12T17:36:05Z</updated>

		<summary type="html">&lt;p&gt;TheEagle: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Infobox addon|name=Pax transport|started=May 2023|contributors=TheFGFSEagle|status=Alpha|desc=This add-on allows you to transport cargo and persons between places}}&lt;br /&gt;
&lt;br /&gt;
This add-on allows you to transport cargo and persons between places.&lt;/div&gt;</summary>
		<author><name>TheEagle</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Template:Infobox_addon&amp;diff=137680</id>
		<title>Template:Infobox addon</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Template:Infobox_addon&amp;diff=137680"/>
		<updated>2023-05-12T17:34:13Z</updated>

		<summary type="html">&lt;p&gt;TheEagle: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{infobox subsystem&lt;br /&gt;
| name        = {{{name|noname}}}&lt;br /&gt;
| started     = {{{started|started-in}}}&lt;br /&gt;
| description = {{{desc|description}}}&lt;br /&gt;
| developers  = {{{contributors|none}}}&lt;br /&gt;
| status      = {{{status|unknown}}}&lt;br /&gt;
| coderepo = {{{url|none}}}&lt;br /&gt;
}}&amp;lt;includeonly&amp;gt;{{main other|[[Category:FlightGear addons]]}}{{howto other|[[Category:FlightGear addons]]}}&amp;lt;/includeonly&amp;gt;&amp;lt;noinclude&amp;gt;&lt;br /&gt;
{{-}}&lt;br /&gt;
{{Informative template|1=&lt;br /&gt;
__NOTOC__&lt;br /&gt;
== Goal ==&lt;br /&gt;
&amp;lt;!-- Short description of the purpose of the template, any automatic categorization and if there is things to consider when using or maintaining the template --&amp;gt;&lt;br /&gt;
This template can be placed on articles about FlightGear [[addon]]s.&lt;br /&gt;
&lt;br /&gt;
Adding this template to a page will automatically add [[:Category:FlightGear addons]] to that page.&lt;br /&gt;
&lt;br /&gt;
== Usage ==&lt;br /&gt;
 {{obr}}'''infobox addon'''&lt;br /&gt;
 {{!}} ''name''         = &lt;br /&gt;
 {{!}} ''started''      = &lt;br /&gt;
 {{!}} ''desc''         = &lt;br /&gt;
 {{!}} ''contributors'' = &lt;br /&gt;
 {{!}} ''status''       = &lt;br /&gt;
 {{!}} &amp;quot;coderepo&amp;quot;   =&lt;br /&gt;
 {{cbr}}&lt;br /&gt;
All parameters are optional.&lt;br /&gt;
&lt;br /&gt;
; name:         The name of the addon.  Defaults to '''noname'''.&lt;br /&gt;
; started:      When development started.  Defaults to '''started-in'''.&lt;br /&gt;
; description:  A short description of the addon.  Defaults to '''description'''.&lt;br /&gt;
; status:       The status of the addon.  Defaults to '''unknown'''.&lt;br /&gt;
; developers:   The developers of the addon.  Defaults to '''none'''.&lt;br /&gt;
; coderepo:   URL of a Git repo containing the code for the addon&lt;br /&gt;
&lt;br /&gt;
== Examples ==&lt;br /&gt;
 {{obr}}infobox addon{{cbr}}&lt;br /&gt;
{{infobox addon}}&lt;br /&gt;
{{-}}&lt;br /&gt;
 {{obr}}infobox addon&lt;br /&gt;
 {{!}} name         = MoonWalker&lt;br /&gt;
 {{!}} started      = August 2005&lt;br /&gt;
 {{!}} desc         = Modifies the walker to simulate moon gravity.&lt;br /&gt;
 {{!}} contributors = &lt;br /&gt;
 * Joe Jumper&lt;br /&gt;
 * Jittery Jim&lt;br /&gt;
 {{!}} status       = Concept&lt;br /&gt;
 {{!}} coderepo = https://github.com/JoeJumper/MoonWalker&lt;br /&gt;
 {{cbr}}&lt;br /&gt;
{{infobox addon&lt;br /&gt;
| name         = MoonWalker&lt;br /&gt;
| started      = August 2005&lt;br /&gt;
| desc         = Modifies the walker to simulate moon gravity.&lt;br /&gt;
| contributors = &lt;br /&gt;
* Joe Jumper&lt;br /&gt;
* Jittery Jim&lt;br /&gt;
| status       = Concept&lt;br /&gt;
| coderepo = https://github.com/JoeJumper/MoonWalker&lt;br /&gt;
}}&lt;br /&gt;
{{-}}&lt;br /&gt;
}}&lt;br /&gt;
[[Category:Infobox templates]]&lt;br /&gt;
&amp;lt;/noinclude&amp;gt;&lt;/div&gt;</summary>
		<author><name>TheEagle</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Template:Infobox_addon&amp;diff=137679</id>
		<title>Template:Infobox addon</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Template:Infobox_addon&amp;diff=137679"/>
		<updated>2023-05-12T17:30:52Z</updated>

		<summary type="html">&lt;p&gt;TheEagle: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{infobox subsystem&lt;br /&gt;
| name        = {{{name|noname}}}&lt;br /&gt;
| started     = {{{started|started-in}}}&lt;br /&gt;
| description = {{{desc|description}}}&lt;br /&gt;
| developers  = {{{contributors|none}}}&lt;br /&gt;
| status      = {{{status|unknown}}}&lt;br /&gt;
| coderepo = {{{url|none}}}&lt;br /&gt;
}}&amp;lt;includeonly&amp;gt;{{main other|[[Category:FlightGear addons]]}}{{howto other|[[Category:FlightGear addons]]}}&amp;lt;/includeonly&amp;gt;&amp;lt;noinclude&amp;gt;&lt;br /&gt;
{{-}}&lt;br /&gt;
{{Informative template|1=&lt;br /&gt;
__NOTOC__&lt;br /&gt;
== Goal ==&lt;br /&gt;
&amp;lt;!-- Short description of the purpose of the template, any automatic categorization and if there is things to consider when using or maintaining the template --&amp;gt;&lt;br /&gt;
This template can be placed on articles about FlightGear [[addon]]s.&lt;br /&gt;
&lt;br /&gt;
Adding this template to a page will automatically add [[:Category:FlightGear addons]] to that page.&lt;br /&gt;
&lt;br /&gt;
== Usage ==&lt;br /&gt;
 {{obr}}'''infobox addon'''&lt;br /&gt;
 {{!}} ''name''         = &lt;br /&gt;
 {{!}} ''started''      = &lt;br /&gt;
 {{!}} ''desc''         = &lt;br /&gt;
 {{!}} ''contributors'' = &lt;br /&gt;
 {{!}} ''status''       = &lt;br /&gt;
 {{!}} &amp;quot;coderepo&amp;quot;    =&lt;br /&gt;
 {{cbr}}&lt;br /&gt;
All parameters are optional.&lt;br /&gt;
&lt;br /&gt;
; name:         The name of the addon.  Defaults to '''noname'''.&lt;br /&gt;
; started:      When development started.  Defaults to '''started-in'''.&lt;br /&gt;
; description:  A short description of the addon.  Defaults to '''description'''.&lt;br /&gt;
; status:       The status of the addon.  Defaults to '''unknown'''.&lt;br /&gt;
; developers:   The developers of the addon.  Defaults to '''none'''.&lt;br /&gt;
; coderepo: URL of a Git repo containing the code for the addon&lt;br /&gt;
&lt;br /&gt;
== Examples ==&lt;br /&gt;
 {{obr}}infobox addon{{cbr}}&lt;br /&gt;
{{infobox addon}}&lt;br /&gt;
{{-}}&lt;br /&gt;
 {{obr}}infobox addon&lt;br /&gt;
 {{!}} name         = MoonWalker&lt;br /&gt;
 {{!}} started      = August 2005&lt;br /&gt;
 {{!}} desc         = Modifies the walker to simulate moon gravity.&lt;br /&gt;
 {{!}} contributors = &lt;br /&gt;
 * Joe Jumper&lt;br /&gt;
 * Jittery Jim&lt;br /&gt;
 {{!}} status       = Concept&lt;br /&gt;
 {{!}} coderepo = https://github.com/JoeJumper/MoonWalker&lt;br /&gt;
 {{cbr}}&lt;br /&gt;
{{infobox addon&lt;br /&gt;
| name         = MoonWalker&lt;br /&gt;
| started      = August 2005&lt;br /&gt;
| desc         = Modifies the walker to simulate moon gravity.&lt;br /&gt;
| contributors = &lt;br /&gt;
* Joe Jumper&lt;br /&gt;
* Jittery Jim&lt;br /&gt;
| status       = Concept&lt;br /&gt;
| coderepo = https://github.com/JoeJumper/MoonWalker&lt;br /&gt;
}}&lt;br /&gt;
{{-}}&lt;br /&gt;
}}&lt;br /&gt;
[[Category:Infobox templates]]&lt;br /&gt;
&amp;lt;/noinclude&amp;gt;&lt;/div&gt;</summary>
		<author><name>TheEagle</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Template:Infobox_addon&amp;diff=137678</id>
		<title>Template:Infobox addon</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Template:Infobox_addon&amp;diff=137678"/>
		<updated>2023-05-12T17:27:08Z</updated>

		<summary type="html">&lt;p&gt;TheEagle: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{infobox subsystem&lt;br /&gt;
| name        = {{{name|noname}}}&lt;br /&gt;
| started     = {{{started|started-in}}}&lt;br /&gt;
| description = {{{desc|description}}}&lt;br /&gt;
| developers  = {{{contributors|none}}}&lt;br /&gt;
| status      = {{{status|unknown}}}&lt;br /&gt;
| coderepo = {{{url|none}}}&lt;br /&gt;
}}&amp;lt;includeonly&amp;gt;{{main other|[[Category:FlightGear addons]]}}{{howto other|[[Category:FlightGear addons]]}}&amp;lt;/includeonly&amp;gt;&amp;lt;noinclude&amp;gt;&lt;br /&gt;
{{-}}&lt;br /&gt;
{{Informative template|1=&lt;br /&gt;
__NOTOC__&lt;br /&gt;
== Goal ==&lt;br /&gt;
&amp;lt;!-- Short description of the purpose of the template, any automatic categorization and if there is things to consider when using or maintaining the template --&amp;gt;&lt;br /&gt;
This template can be placed on articles about FlightGear [[addon]]s.&lt;br /&gt;
&lt;br /&gt;
Adding this template to a page will automatically add [[:Category:FlightGear addons]] to that page.&lt;br /&gt;
&lt;br /&gt;
== Usage ==&lt;br /&gt;
 {{obr}}'''infobox addon'''&lt;br /&gt;
 {{!}} ''name''         = &lt;br /&gt;
 {{!}} ''started''      = &lt;br /&gt;
 {{!}} ''desc''         = &lt;br /&gt;
 {{!}} ''contributors'' = &lt;br /&gt;
 {{!}} ''status''       = &lt;br /&gt;
 {{!}} &amp;quot;codrerepo&amp;quot;    =&lt;br /&gt;
 {{cbr}}&lt;br /&gt;
All parameters are optional.&lt;br /&gt;
&lt;br /&gt;
; name:         The name of the addon.  Defaults to '''noname'''.&lt;br /&gt;
; started:      When development started.  Defaults to '''started-in'''.&lt;br /&gt;
; description:  A short description of the addon.  Defaults to '''description'''.&lt;br /&gt;
; status:       The status of the addon.  Defaults to '''unknown'''.&lt;br /&gt;
; developers:   The developers of the addon.  Defaults to '''none'''.&lt;br /&gt;
; coderepo: URL of a Git repo containing the code for the addon&lt;br /&gt;
&lt;br /&gt;
== Examples ==&lt;br /&gt;
 {{obr}}infobox addon{{cbr}}&lt;br /&gt;
{{infobox addon}}&lt;br /&gt;
{{-}}&lt;br /&gt;
 {{obr}}infobox addon&lt;br /&gt;
 {{!}} name         = MoonWalker&lt;br /&gt;
 {{!}} started      = August 2005&lt;br /&gt;
 {{!}} desc         = Modifies the walker to simulate moon gravity.&lt;br /&gt;
 {{!}} contributors = &lt;br /&gt;
 * Joe Jumper&lt;br /&gt;
 * Jittery Jim&lt;br /&gt;
 {{!}} status       = Concept&lt;br /&gt;
 {{cbr}}&lt;br /&gt;
{{infobox addon&lt;br /&gt;
| name         = MoonWalker&lt;br /&gt;
| started      = August 2005&lt;br /&gt;
| desc         = Modifies the walker to simulate moon gravity.&lt;br /&gt;
| contributors = &lt;br /&gt;
* Joe Jumper&lt;br /&gt;
* Jittery Jim&lt;br /&gt;
| status       = Concept&lt;br /&gt;
| coderepo = https://github.com/JoeJumper/MoonWalker&lt;br /&gt;
}}&lt;br /&gt;
{{-}}&lt;br /&gt;
}}&lt;br /&gt;
[[Category:Infobox templates]]&lt;br /&gt;
&amp;lt;/noinclude&amp;gt;&lt;/div&gt;</summary>
		<author><name>TheEagle</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Pax_transport_addon&amp;diff=137677</id>
		<title>Pax transport addon</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Pax_transport_addon&amp;diff=137677"/>
		<updated>2023-05-12T17:24:05Z</updated>

		<summary type="html">&lt;p&gt;TheEagle: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Infobox addon|name=Pax transport|started=May 2023|desc=This add-on allows you to transport cargo and persons between places|contributors=TheFGFSEagle|status=Alpha}}&lt;br /&gt;
&lt;br /&gt;
This add-on allows you to transport cargo and persons between places.&lt;/div&gt;</summary>
		<author><name>TheEagle</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Pax_transport_addon&amp;diff=137676</id>
		<title>Pax transport addon</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Pax_transport_addon&amp;diff=137676"/>
		<updated>2023-05-12T17:23:42Z</updated>

		<summary type="html">&lt;p&gt;TheEagle: Created page&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This add-on allows you to transport cargo and persons between places.&lt;br /&gt;
&lt;br /&gt;
{{Infobox addon|name=Pax transport|started=May 2023|desc=This add-on allows you to transport cargo and persons between places|contributors=TheFGFSEagle|status=Alpha}}&lt;/div&gt;</summary>
		<author><name>TheEagle</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=User:TheEagle&amp;diff=137671</id>
		<title>User:TheEagle</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=User:TheEagle&amp;diff=137671"/>
		<updated>2023-05-12T03:17:15Z</updated>

		<summary type="html">&lt;p&gt;TheEagle: Added Pax transport addon&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;I like to fly in FlightGear, create and enhance scenery, aircraft and addons for FlightGear, write Python apps, hack the FG sources …&lt;br /&gt;
&lt;br /&gt;
=== My aircraft ===&lt;br /&gt;
Cessna 210 ([https://github.com/TheFGFSEagle/c210-family GitHub] | [[Cessna 210 Centurion family|Wiki]])&lt;br /&gt;
&lt;br /&gt;
Cessna 310 ([https://github.com/TheFGFSEagle/c310-family GitHub] | [[Cessna 310 family|Wiki]])&lt;br /&gt;
&lt;br /&gt;
Pilatus PC-6 ([https://github.com/TheFGFSEagle/pilatus-pc6-family GitHub] | Wiki)&lt;br /&gt;
&lt;br /&gt;
Cessna 208 ([https://github.com/TheFGFSEagle/c208-family GitHub] | Wiki)&lt;br /&gt;
&lt;br /&gt;
Mudry CAP10 ([https://github.com/TheFGFSEagle/mudry-cap10-family GitHub] | Wiki)&lt;br /&gt;
&lt;br /&gt;
=== My addons ===&lt;br /&gt;
Pax transport ([https://github.com/TheFGFSEagle/PaxTransport GitHub] | Wiki)&lt;/div&gt;</summary>
		<author><name>TheEagle</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Howto:Animate_models&amp;diff=137556</id>
		<title>Howto:Animate models</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Howto:Animate_models&amp;diff=137556"/>
		<updated>2023-04-14T00:59:50Z</updated>

		<summary type="html">&lt;p&gt;TheEagle: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The real world is full of motion. To simulate this in [[FlightGear]], '''models must be animated'''.&lt;br /&gt;
&lt;br /&gt;
FlightGear allows you to animate models in response to property changes: for example, the propellers can spin when the engine is on and the elevators can move up and down with your controller. There is no fixed limit on what parts can be animated: the only requirements are that the part is named in the 3D model file, and that there is a property in the main tree that you can use to get the positioning information. &lt;br /&gt;
&lt;br /&gt;
This document provides basic information for all kind of animations. When animating your model, it is very helpful to find an aircraft with parts similar to yours and use it as an example. Cut and paste the code into your wrapper file and then edit to suit.&lt;br /&gt;
&lt;br /&gt;
== Notes ==&lt;br /&gt;
=== File name of main model and animation XML file ===&lt;br /&gt;
{{main article|Aircraft-set.xml#Not used for loading multiplayer aircraft}}&lt;br /&gt;
The file name of the main model and animation XML file, or the .ac file if there is no XML file, (in essence the property &amp;lt;code&amp;gt;/sim/model/path&amp;lt;/code&amp;gt;) will be the name of the aircraft that is transmitted when using [[multiplayer]] and will also be used for loading multiplayer aircraft.&lt;br /&gt;
&lt;br /&gt;
There is also a mechanism to substitute a full aircraft model with a simpler AI aircraft model if one is available at the same file path (including for example &amp;lt;code&amp;gt;Models/Boeing-797-800.xml&amp;lt;/code&amp;gt;), but in &amp;lt;code&amp;gt;[[$FG_ROOT]]/'''AI'''/Aircraft/&amp;lt;/code&amp;gt; instead of &amp;lt;code&amp;gt;$FG_ROOT/Aircraft/&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== .ac files ===&lt;br /&gt;
{{Main article|AC files: Understanding and changing .ac code#Identifying an object}}&lt;br /&gt;
&lt;br /&gt;
When referring to an .ac file in your xml animation, it is important that the &amp;lt;code&amp;gt;&amp;lt;object-name&amp;gt;&amp;lt;/code&amp;gt; exactly matches the object named in the .ac file (this includes cases!). &lt;br /&gt;
&lt;br /&gt;
'''Note for SketchUp users:''' The spatial reference X/Y/Z used in animation to locate an object or a point are different from the ones in AC3D ie X values are the same in both but Y in animation must be matched to AC3D's -Z (Z value but opposite sign) and Z value in animation must be matched to AC3D's Y value. &lt;br /&gt;
&lt;br /&gt;
'''Note for SketchUp users:''' when exporting to AC3D in Sketchup, the .ac file will name the objects in your model to &amp;quot;blah&amp;quot; by default. You need to amend the relevant object names in your .ac file using text edit, so that the xml will work.&lt;br /&gt;
&lt;br /&gt;
=== Animation order ===&lt;br /&gt;
Animations are executed by FlightGear in the order that they are read in the model's .xml file. Therefore, it is very important to pay attention to the order, especially when multiple animations are applied to the same object(s). Wrong ordering of animations might cause [[Howto:Animate models#Timed|timed]] animations (used to create flashing lights) to not work. For further details see this [https://sourceforge.net/p/flightgear/mailman/message/37090714/ thread] on the development mailing list. Updated [https://scenery.flightgear.org/app.php?c=Models&amp;amp;a=browse&amp;amp;shared=18 shared Effects models] will be available very soon.&lt;br /&gt;
&lt;br /&gt;
Similar problems can be encountered with the dist-scale instead of the timed animation.&lt;br /&gt;
== Tags used in most animations ==&lt;br /&gt;
=== Name ===&lt;br /&gt;
With a name animation, you can group multiple objects. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;name&amp;gt;Collection1&amp;lt;/name&amp;gt;&lt;br /&gt;
  &amp;lt;object-name&amp;gt;Object1&amp;lt;/object-name&amp;gt;&lt;br /&gt;
  &amp;lt;object-name&amp;gt;Object2&amp;lt;/object-name&amp;gt;&lt;br /&gt;
  &amp;lt;object-name&amp;gt;Object3&amp;lt;/object-name&amp;gt;&lt;br /&gt;
 &amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The example above creates a &amp;quot;virtual object&amp;quot; with the name Collection1. In animation, we can animate this group of objects, by using:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
 &amp;lt;object-name&amp;gt;Collection1&amp;lt;/object-name&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Object-name ===&lt;br /&gt;
These names are set in the 3D model. Each single object has a unique name; for easy identification it is advised to use descriptive names (LeftElevator, Rudder etc.). Animations are only applied to those objects that are mentioned in an object-name line (one object per line!). Animations lacking those, will be applied to the entire model.&lt;br /&gt;
&lt;br /&gt;
=== Property ===&lt;br /&gt;
Each animation must be associated with exactly one property from the main FlightGear property tree (remember that the properties in the wrapper file are not part of the main tree), using &amp;lt;code&amp;gt;&amp;lt;property&amp;gt;&amp;lt;/code&amp;gt; to provide the property path:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;type&amp;gt;rotate&amp;lt;/type&amp;gt;&lt;br /&gt;
  &amp;lt;object-name&amp;gt;Rudder&amp;lt;/object-name&amp;gt;&lt;br /&gt;
  &amp;lt;property&amp;gt;controls/rudder&amp;lt;/property&amp;gt;&lt;br /&gt;
 &amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note the omission of the leading slash '/' when referring to the property. This assures that when the model is used for AI or multiplayer traffic the animations will follow that of the AI controller instead of that of the user.&lt;br /&gt;
&lt;br /&gt;
=== Axis ===&lt;br /&gt;
An axis part is required in every animation that involves a rotating or moving thing.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
  &amp;lt;axis&amp;gt;&lt;br /&gt;
   &amp;lt;x&amp;gt;0&amp;lt;/x&amp;gt;&lt;br /&gt;
   &amp;lt;y&amp;gt;1&amp;lt;/y&amp;gt;&lt;br /&gt;
   &amp;lt;z&amp;gt;0&amp;lt;/z&amp;gt;&lt;br /&gt;
  &amp;lt;/axis&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The axis are similar to the ones of the 3D model. There is a difference between rotation and translation:&lt;br /&gt;
* In rotation animations, the axis part defines around what axis the object rotates. Negative/positive values make the difference between counterclockwise and clockwise rotations.&lt;br /&gt;
* In translate animations, the part defines along what axis the object moves. If the x-axis is poiting backwards, an x-value of -1 will result in forward motion.&lt;br /&gt;
&lt;br /&gt;
You could also define two points, between which FlightGear will calculate the correct axis. This makes the use of a [[#Center|&amp;lt;nowiki&amp;gt;&amp;lt;center&amp;gt;&amp;lt;/nowiki&amp;gt;]] tag redundant! Such coordinates are extremely useful for animating control surfaces (rudder, elevators etc.).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
  &amp;lt;axis&amp;gt;   &lt;br /&gt;
   &amp;lt;x1-m&amp;gt; 4.9&amp;lt;/x1-m&amp;gt;&lt;br /&gt;
   &amp;lt;y1-m&amp;gt; 7.1&amp;lt;/y1-m&amp;gt;&lt;br /&gt;
   &amp;lt;z1-m&amp;gt;-1.0&amp;lt;/z1-m&amp;gt;&lt;br /&gt;
   &amp;lt;x2-m&amp;gt; 5.9&amp;lt;/x2-m&amp;gt;&lt;br /&gt;
   &amp;lt;y2-m&amp;gt;11.2&amp;lt;/y2-m&amp;gt;&lt;br /&gt;
   &amp;lt;z2-m&amp;gt;-0.5&amp;lt;/z2-m&amp;gt;&lt;br /&gt;
  &amp;lt;/axis&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Center ===&lt;br /&gt;
Various animations ([[#Rotate|rotate]], [[#Spin|spin]], [[#Scale|scale]]) move around a center point.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
  &amp;lt;center&amp;gt;&lt;br /&gt;
   &amp;lt;x-m&amp;gt;-1.50&amp;lt;/x-m&amp;gt;&lt;br /&gt;
   &amp;lt;y-m&amp;gt; 1   &amp;lt;/y-m&amp;gt;&lt;br /&gt;
   &amp;lt;z-m&amp;gt; 0.25&amp;lt;/z-m&amp;gt;&lt;br /&gt;
  &amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The axis are similar to the ones of the 3D model, so finding coordinates is easily done in 3D modeling software.&lt;br /&gt;
&lt;br /&gt;
=== Using a geometry object for axis and centre (2017.2) ===&lt;br /&gt;
&lt;br /&gt;
Added in V2017.2 is support to allow a geometry object (a line segment with two vertices) that is used to define both the centre and the axis for an animation. This will work with rotate, spin, translate and knob animations.&lt;br /&gt;
&lt;br /&gt;
{{Note|Since SimGear commit 6ca9141083e62be1161d0ca2d1e3b918494dd6ad on next, this feature can also be used for slider (usage equivalent to that for a translate animation) and for any of the &amp;lt;*-center&amp;gt; / &amp;lt;*-axis&amp;gt; tags of a locked-track animation.}}&lt;br /&gt;
&lt;br /&gt;
When used for translate animations, the axis line should be normalized (i.e. be 1 meter long) as its length acts as a factor for the translation distance.&lt;br /&gt;
&lt;br /&gt;
The XML syntax for this is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
&amp;lt;axis&amp;gt;&lt;br /&gt;
    &amp;lt;object-name&amp;gt;some-object&amp;lt;/object-name&amp;gt;&lt;br /&gt;
&amp;lt;/axis&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the &amp;lt;code&amp;gt;&amp;lt;axis&amp;gt;...&amp;lt;/axis&amp;gt;&amp;lt;/code&amp;gt; section is omitted entirely, &amp;lt;code&amp;gt;{object-name}-axis&amp;lt;/code&amp;gt; will be used by default, where &amp;lt;code&amp;gt;{object-name}&amp;lt;/code&amp;gt; is the name of the object being animated (if we are animating more than one object, the first object is used). In the earlier example this would be &amp;lt;code&amp;gt;Rudder-axis&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
In the '''.ac''' file, specify a SURF with type (bottom 4 bits, 0=polygon, 1=closedline, 2=line) set to 2, and two vertices that define the axis. For example:&lt;br /&gt;
&lt;br /&gt;
    OBJECT poly&lt;br /&gt;
    name &amp;quot;aileron.l-axis&amp;quot;&lt;br /&gt;
    numvert 2&lt;br /&gt;
    3.2077502191170844 0.18160835055097943 4.055616960642423&lt;br /&gt;
    2.6758650763079 0.28024033462188946 6.477876098622225&lt;br /&gt;
    numsurf 1&lt;br /&gt;
    SURF 0x12&lt;br /&gt;
    mat 0&lt;br /&gt;
    refs 2&lt;br /&gt;
    0 0 0&lt;br /&gt;
    1 0 0&lt;br /&gt;
    kids 0&lt;br /&gt;
&lt;br /&gt;
Once the object-name used for the axis has been processed the geometry object will be hidden. This also allows a visual check for any axis objects that are not yet assigned.&lt;br /&gt;
&lt;br /&gt;
It is possible to reuse the same object definition multiple times within a single XML file. &lt;br /&gt;
&lt;br /&gt;
[[File:Canopy-animation-axis-object.png|small|Illustration of where an axis object (2017.2) can be placed for a canopy]]&lt;br /&gt;
&lt;br /&gt;
[[File:Gauges-knobs-animation-axis-object.png|small|Illustration of where an axis object (2017.2) can be placed for cockpit elements]]&lt;br /&gt;
&lt;br /&gt;
== Additional tags that can be used in most animations ==&lt;br /&gt;
=== Conditions ===&lt;br /&gt;
Multiple animations can make use of a conditional. Check &amp;lt;tt&amp;gt;$FGDATA/Docs/README.conditions&amp;lt;/tt&amp;gt; for some more details.&lt;br /&gt;
&lt;br /&gt;
* '''equals:''' property value (or second property) is equal to value/(first)property.&lt;br /&gt;
* '''greater-than:''' property value (or second property) is larger than value/(first)property.&lt;br /&gt;
* '''greater-than-equals:''' property value (or second property) is greater than or equal to value/(first)property.&lt;br /&gt;
* '''less-than:''' property value (or second property) is smaller than value/(first)property.&lt;br /&gt;
* '''less-than-equals:''' property value (or second property) is smaller than or equal to value/(first)property.&lt;br /&gt;
&lt;br /&gt;
The example below is true when n1 has a value greater than 25.&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
  &amp;lt;condition&amp;gt;&lt;br /&gt;
   &amp;lt;greater-than&amp;gt;&lt;br /&gt;
    &amp;lt;property&amp;gt;engines/engine[1]/n1&amp;lt;/property&amp;gt;&lt;br /&gt;
    &amp;lt;value&amp;gt;25&amp;lt;/value&amp;gt;&lt;br /&gt;
   &amp;lt;/greater-than&amp;gt;&lt;br /&gt;
  &amp;lt;/condition&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then there are some special tags:&lt;br /&gt;
&lt;br /&gt;
* '''and:'''&lt;br /&gt;
* '''not:'''&lt;br /&gt;
* '''or:'''&lt;br /&gt;
&lt;br /&gt;
In the example below, the condition is true when either n1 is greater than 25% or equal to 0%.&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
  &amp;lt;condition&amp;gt;&lt;br /&gt;
   &amp;lt;or&amp;gt;&lt;br /&gt;
    &amp;lt;greater-than&amp;gt;&lt;br /&gt;
     &amp;lt;property&amp;gt;engines/engine[1]/n1&amp;lt;/property&amp;gt;&lt;br /&gt;
     &amp;lt;value&amp;gt;25&amp;lt;/value&amp;gt;&lt;br /&gt;
    &amp;lt;/greater-than&amp;gt;&lt;br /&gt;
    &amp;lt;equals&amp;gt;&lt;br /&gt;
     &amp;lt;property&amp;gt;engines/engine[1]/n1&amp;lt;/property&amp;gt;&lt;br /&gt;
     &amp;lt;value&amp;gt;0&amp;lt;/value&amp;gt;&lt;br /&gt;
    &amp;lt;/equals&amp;gt;&lt;br /&gt;
   &amp;lt;/or&amp;gt;&lt;br /&gt;
  &amp;lt;/condition&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
An example of implementation into an animation looks as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;object-name&amp;gt;Object&amp;lt;/object-name&amp;gt;&lt;br /&gt;
  &amp;lt;type&amp;gt;rotate&amp;lt;/type&amp;gt;&lt;br /&gt;
  &amp;lt;property&amp;gt;suface-positions/left-aileron-pos-norm&amp;lt;/property&amp;gt;&lt;br /&gt;
  &amp;lt;factor&amp;gt;25&amp;lt;/factor&amp;gt;&lt;br /&gt;
  &amp;lt;condition&amp;gt;&lt;br /&gt;
   &amp;lt;greater-than&amp;gt;&lt;br /&gt;
    &amp;lt;property&amp;gt;suface-positions/left-aileron-pos-norm&amp;lt;/property&amp;gt;&lt;br /&gt;
    &amp;lt;value&amp;gt;10&amp;lt;/value&amp;gt;&lt;br /&gt;
   &amp;lt;/greater-than&amp;gt;&lt;br /&gt;
  &amp;lt;/condition&amp;gt;&lt;br /&gt;
  &amp;lt;center&amp;gt;&lt;br /&gt;
   &amp;lt;x-m&amp;gt;-1.50&amp;lt;/x-m&amp;gt;&lt;br /&gt;
   &amp;lt;y-m&amp;gt; 1   &amp;lt;/y-m&amp;gt;&lt;br /&gt;
   &amp;lt;z-m&amp;gt; 0.25&amp;lt;/z-m&amp;gt;&lt;br /&gt;
  &amp;lt;/center&amp;gt;&lt;br /&gt;
  &amp;lt;axis&amp;gt;&lt;br /&gt;
   &amp;lt;x&amp;gt;0&amp;lt;/x&amp;gt;&lt;br /&gt;
   &amp;lt;y&amp;gt;1&amp;lt;/y&amp;gt;&lt;br /&gt;
   &amp;lt;z&amp;gt;0&amp;lt;/z&amp;gt;&lt;br /&gt;
  &amp;lt;/axis&amp;gt;&lt;br /&gt;
 &amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Interpolation ===&lt;br /&gt;
For non-fixed factors, an interpolation &amp;quot;table&amp;quot; can be created. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
  &amp;lt;interpolation&amp;gt;&lt;br /&gt;
   &amp;lt;entry&amp;gt;&lt;br /&gt;
    &amp;lt;ind&amp;gt; 0.0&amp;lt;/ind&amp;gt;&lt;br /&gt;
    &amp;lt;dep&amp;gt; 0.0&amp;lt;/dep&amp;gt;&lt;br /&gt;
   &amp;lt;/entry&amp;gt;&lt;br /&gt;
   &amp;lt;entry&amp;gt;&lt;br /&gt;
    &amp;lt;ind&amp;gt; 0.667&amp;lt;/ind&amp;gt;&lt;br /&gt;
    &amp;lt;dep&amp;gt; 0.0&amp;lt;/dep&amp;gt;&lt;br /&gt;
   &amp;lt;/entry&amp;gt;&lt;br /&gt;
   &amp;lt;entry&amp;gt;&lt;br /&gt;
    &amp;lt;ind&amp;gt; 1.0&amp;lt;/ind&amp;gt;&lt;br /&gt;
    &amp;lt;dep&amp;gt; 0.5&amp;lt;/dep&amp;gt;&lt;br /&gt;
   &amp;lt;/entry&amp;gt;&lt;br /&gt;
  &amp;lt;/interpolation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The lines above represent the following table:&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
!Input&lt;br /&gt;
!Output&lt;br /&gt;
|-&lt;br /&gt;
|0.0&lt;br /&gt;
|0.0&lt;br /&gt;
|-&lt;br /&gt;
|0.667&lt;br /&gt;
|0.0&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|0.5&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
You can add as many entries as you need. Interpolation tables are often used for gear animations (eg. to open doors during gear-movements and close them again once the gear is either retracted or fully extended).&lt;br /&gt;
&lt;br /&gt;
=== Expressions ===&lt;br /&gt;
For some animations it is possible to define complex animations by using [[Expressions|Expressions]]. This even allows to drive the animation from multiple properties without the need for additional Nasal scripts. Here is an example for a translate animation depending on two properties and the cosine function:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt;&lt;br /&gt;
     &amp;lt;type&amp;gt;translate&amp;lt;/type&amp;gt;&lt;br /&gt;
     &amp;lt;expression&amp;gt;&lt;br /&gt;
       &amp;lt;product&amp;gt;&lt;br /&gt;
         &amp;lt;property&amp;gt;/my/factor-property&amp;lt;/property&amp;gt;&lt;br /&gt;
         &amp;lt;cos&amp;gt;&lt;br /&gt;
           &amp;lt;deg2rad&amp;gt;&lt;br /&gt;
             &amp;lt;property&amp;gt;/my/angular-property&amp;lt;/property&amp;gt;&lt;br /&gt;
           &amp;lt;/deg2rad&amp;gt;&lt;br /&gt;
         &amp;lt;/cos&amp;gt;&lt;br /&gt;
       &amp;lt;/product&amp;gt;&lt;br /&gt;
     &amp;lt;/expression&amp;gt;&lt;br /&gt;
     [..]more elements[..]&lt;br /&gt;
 &amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Animations which can utilize [[Expressions|Expressions]] are: &lt;br /&gt;
* [[Howto:Animate_models#Translate|Translate]]&lt;br /&gt;
* [[Howto:Animate_models#Rotate|Rotate]]&lt;br /&gt;
* [[Howto:Animate_models#Scale|Scale]]&lt;br /&gt;
* [[Howto:Animate_models#Range|Range]]&lt;br /&gt;
* [[Howto:Animate_models#Blend|Blend]]&lt;br /&gt;
* [[Howto:Animate_models#Material animation|Material]]&lt;br /&gt;
&lt;br /&gt;
See more detailed info at [[Expressions|Expressions]]&lt;br /&gt;
&lt;br /&gt;
== Lights ==&lt;br /&gt;
As of January 2021 FlightGear supports multiple light sources just like Project Rembrandt has always done.&lt;br /&gt;
[[Compositor#Lights|Adding lights to a model]]&lt;br /&gt;
&lt;br /&gt;
== Object animations ==&lt;br /&gt;
=== Alpha-test ===&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;type&amp;gt;alpha-test&amp;lt;/type&amp;gt;&lt;br /&gt;
  &amp;lt;object-name&amp;gt;Object&amp;lt;/object-name&amp;gt;&lt;br /&gt;
  &amp;lt;alpha-factor&amp;gt;0.01&amp;lt;/alpha-factor&amp;gt;&lt;br /&gt;
 &amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
This &amp;quot;animation&amp;quot; is a way to set an alpha test on a model branch. The effect is to avoid depth buffer writing of pixel that are not seen because they are transparent. This is particulary useful when modeling a metallic structure or a tree with a billboard. The threshold of transparency is set with the &amp;lt;alpha-factor&amp;gt; element.  See also [[Pixel testing in effects]].&lt;br /&gt;
&lt;br /&gt;
=== Blend ===&lt;br /&gt;
Blends an object with the surrounding. Comparable to a translucency animation. A value of 0 corresponds to no transparency, i.e. and ordinary solid object, and a value of 1 makes the object fully transparent, i.e., not visible at all.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;type&amp;gt;blend&amp;lt;/type&amp;gt;&lt;br /&gt;
  &amp;lt;property&amp;gt;/velocities/airspeed-kt&amp;lt;/property&amp;gt;&lt;br /&gt;
  &amp;lt;factor&amp;gt;0.00025&amp;lt;/factor&amp;gt;&lt;br /&gt;
  &amp;lt;min&amp;gt;0.2&amp;lt;/min&amp;gt;&lt;br /&gt;
  &amp;lt;max&amp;gt;0.7&amp;lt;/max&amp;gt;&lt;br /&gt;
 &amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* '''property:'''&lt;br /&gt;
* '''factor:'''&lt;br /&gt;
* '''min:'''&lt;br /&gt;
* '''max:'''&lt;br /&gt;
* '''[[Howto:Animate_models#Expressions|expression]]:''' is optional. For more details see [[Expressions|Expressions]]&lt;br /&gt;
&lt;br /&gt;
Note that when using the Project Rembrandt renderer, all transparent and translucent objects must be registered to display properly.  [[Project_Rembrandt#Registering_all_translucent_surfaces|More information here.]]&lt;br /&gt;
&lt;br /&gt;
=== Billboard ===&lt;br /&gt;
This faces an object towards the viewer. Often used on 2D objects, like clouds, trees and lights.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;type&amp;gt;billboard&amp;lt;/type&amp;gt;&lt;br /&gt;
  &amp;lt;object-name&amp;gt;Object&amp;lt;/object-name&amp;gt;&lt;br /&gt;
  &amp;lt;spherical type=&amp;quot;bool&amp;quot;&amp;gt;true&amp;lt;/spherical&amp;gt;&lt;br /&gt;
 &amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* '''spherical:'''&lt;br /&gt;
&lt;br /&gt;
=== Dist-scale ===&lt;br /&gt;
Used to scale an object, based on the distance to the viewer. &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;&amp;lt;ind&amp;gt;&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;&amp;lt;dep&amp;gt;&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; (independent and dependent) are the distance in meters and the scale at that distance.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;type&amp;gt;dist-scale&amp;lt;/type&amp;gt;&lt;br /&gt;
  &amp;lt;object-name&amp;gt;Object&amp;lt;/object-name&amp;gt;&lt;br /&gt;
  &amp;lt;interpolation&amp;gt;&lt;br /&gt;
   &amp;lt;entry&amp;gt;&lt;br /&gt;
    &amp;lt;ind&amp;gt;0&amp;lt;/ind&amp;gt;&lt;br /&gt;
    &amp;lt;dep&amp;gt;1&amp;lt;/dep&amp;gt;&lt;br /&gt;
   &amp;lt;/entry&amp;gt;&lt;br /&gt;
   &amp;lt;entry&amp;gt;&lt;br /&gt;
    &amp;lt;ind&amp;gt;300&amp;lt;/ind&amp;gt;&lt;br /&gt;
    &amp;lt;dep&amp;gt;4&amp;lt;/dep&amp;gt;&lt;br /&gt;
   &amp;lt;/entry&amp;gt;&lt;br /&gt;
   &amp;lt;entry&amp;gt;&lt;br /&gt;
    &amp;lt;ind&amp;gt;1500&amp;lt;/ind&amp;gt;&lt;br /&gt;
    &amp;lt;dep&amp;gt;8&amp;lt;/dep&amp;gt;&lt;br /&gt;
   &amp;lt;/entry&amp;gt;&lt;br /&gt;
  &amp;lt;/interpolation&amp;gt;&lt;br /&gt;
 &amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can optionally add [[#Center|&amp;amp;lt;center&amp;amp;gt;]] coordinates, to scale the object around that point.&lt;br /&gt;
&lt;br /&gt;
=== Flash ===&lt;br /&gt;
&lt;br /&gt;
Used to scale an object based on the cosine of the angle between the axis provided in the animation and the view vector.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;type&amp;gt;flash&amp;lt;/type&amp;gt;&lt;br /&gt;
  &amp;lt;object-name&amp;gt;Object&amp;lt;/object-name&amp;gt;&lt;br /&gt;
  &amp;lt;offset&amp;gt;0.0&amp;lt;/offset&amp;gt;&lt;br /&gt;
  &amp;lt;factor&amp;gt;1.0&amp;lt;/factor&amp;gt;&lt;br /&gt;
  &amp;lt;power&amp;gt;2&amp;lt;/power&amp;gt;&lt;br /&gt;
  &amp;lt;two-sides type=&amp;quot;bool&amp;quot;&amp;gt;false&amp;lt;/two-sides&amp;gt;&lt;br /&gt;
  &amp;lt;min&amp;gt;0.0&amp;lt;/min&amp;gt;&lt;br /&gt;
  &amp;lt;max&amp;gt;1.0&amp;lt;/max&amp;gt;&lt;br /&gt;
  &amp;lt;center&amp;gt;&lt;br /&gt;
   &amp;lt;x-m&amp;gt;0.0&amp;lt;/x-m&amp;gt;&lt;br /&gt;
   &amp;lt;y-m&amp;gt;0.0&amp;lt;/y-m&amp;gt;&lt;br /&gt;
   &amp;lt;z-m&amp;gt;0.0&amp;lt;/z-m&amp;gt;&lt;br /&gt;
  &amp;lt;/center&amp;gt;&lt;br /&gt;
  &amp;lt;axis&amp;gt;&lt;br /&gt;
   &amp;lt;x&amp;gt;0.0&amp;lt;/x&amp;gt;&lt;br /&gt;
   &amp;lt;y&amp;gt;-1&amp;lt;/y&amp;gt;&lt;br /&gt;
   &amp;lt;z&amp;gt;0.1&amp;lt;/z&amp;gt;&lt;br /&gt;
  &amp;lt;/axis&amp;gt;&lt;br /&gt;
 &amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* '''offset:'''&lt;br /&gt;
* '''factor:'''&lt;br /&gt;
* '''power:'''&lt;br /&gt;
* '''two-sides:''' if false, nothing is drawn if the cosine is negative.&lt;br /&gt;
* '''min:'''&lt;br /&gt;
* '''max:'''&lt;br /&gt;
&lt;br /&gt;
scale = factor * pow( cosine, power ) + offset&lt;br /&gt;
&lt;br /&gt;
scale is then clamped between min and max.&lt;br /&gt;
&lt;br /&gt;
and this scale factor is applied to the object, from the center specified. It works best if scale is less than 1. Otherwise, there will be clipping issues.&lt;br /&gt;
&lt;br /&gt;
=== Noshadow ===&lt;br /&gt;
This animation is used to make sure an object will cast no shadow.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;type&amp;gt;noshadow&amp;lt;/type&amp;gt;&lt;br /&gt;
  &amp;lt;object-name&amp;gt;Object&amp;lt;/object-name&amp;gt;&lt;br /&gt;
 &amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Range ===&lt;br /&gt;
: ''See also [[Modeling - Getting Started#Level of Detail (LOD)]].''&lt;br /&gt;
&lt;br /&gt;
To prevent objects -like instruments- being drawn when the aircraft is actually too far away for them to be seen anyway, a range animation is used. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;type&amp;gt;range&amp;lt;/type&amp;gt;&lt;br /&gt;
  &amp;lt;min-m&amp;gt;0&amp;lt;/min-m&amp;gt;&lt;br /&gt;
  &amp;lt;max-m&amp;gt;30&amp;lt;/max-m&amp;gt;&lt;br /&gt;
 &amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* '''min-m:''' the shortest distance (in meters) from the object center at which it is visible.&lt;br /&gt;
* '''max-m:''' the largest distance (in meters) from the object center at which it is visible.&lt;br /&gt;
&lt;br /&gt;
You could also use the generic level of detail (LOD) properties, which can be set by the user through View &amp;gt; Adjust LOD rangers: &lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Property&lt;br /&gt;
! Description&lt;br /&gt;
! Default value&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;tt&amp;gt;/sim/rendering/static-lod/bare&amp;lt;/tt&amp;gt;&lt;br /&gt;
| only a rough exterior model&lt;br /&gt;
| 30,000 m&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;tt&amp;gt;/sim/rendering/static-lod/rough&amp;lt;/tt&amp;gt; &lt;br /&gt;
| most should be visible&lt;br /&gt;
| 9,000 m&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;tt&amp;gt;/sim/rendering/static-lod/detailed&amp;lt;/tt&amp;gt; &lt;br /&gt;
| all details should be visible&lt;br /&gt;
| 1,500 m&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The animation code will look like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;type&amp;gt;range&amp;lt;/type&amp;gt;&lt;br /&gt;
  &amp;lt;min-m&amp;gt;0&amp;lt;/min-m&amp;gt;&lt;br /&gt;
  &amp;lt;max-property&amp;gt;sim/rendering/static-lod/bare&amp;lt;/max-property&amp;gt;&lt;br /&gt;
 &amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can have both ranges (max and min) bound to a property, or just one of them.&lt;br /&gt;
* '''min-property:''' &lt;br /&gt;
* '''max-property:'''&lt;br /&gt;
* '''[[Howto:Animate_models#Expressions|expression]]:''' is optional. For more details see [[Expressions|Expressions]]&lt;br /&gt;
&lt;br /&gt;
=== Rotate ===&lt;br /&gt;
One of the most important and frequently used animations of all. It rotates an object to an absolute position in degrees, as provided by the property-value.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;object-name&amp;gt;Object&amp;lt;/object-name&amp;gt;&lt;br /&gt;
  &amp;lt;type&amp;gt;rotate&amp;lt;/type&amp;gt;&lt;br /&gt;
  &amp;lt;property&amp;gt;surface-positions/left-aileron-pos-norm&amp;lt;/property&amp;gt;&lt;br /&gt;
  &amp;lt;factor&amp;gt;25&amp;lt;/factor&amp;gt;&lt;br /&gt;
  &amp;lt;offset-deg&amp;gt;25&amp;lt;/offset-deg&amp;gt;&lt;br /&gt;
  &amp;lt;center&amp;gt;&lt;br /&gt;
   &amp;lt;x-m&amp;gt;-1.50&amp;lt;/x-m&amp;gt;&lt;br /&gt;
   &amp;lt;y-m&amp;gt; 1   &amp;lt;/y-m&amp;gt;&lt;br /&gt;
   &amp;lt;z-m&amp;gt; 0.25&amp;lt;/z-m&amp;gt;&lt;br /&gt;
  &amp;lt;/center&amp;gt;&lt;br /&gt;
  &amp;lt;axis&amp;gt;&lt;br /&gt;
   &amp;lt;x&amp;gt;0&amp;lt;/x&amp;gt;&lt;br /&gt;
   &amp;lt;y&amp;gt;1&amp;lt;/y&amp;gt;&lt;br /&gt;
   &amp;lt;z&amp;gt;0&amp;lt;/z&amp;gt;&lt;br /&gt;
  &amp;lt;/axis&amp;gt;&lt;br /&gt;
 &amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* '''factor:''' is optional.&lt;br /&gt;
* '''offset-deg:''' is optional. Offset in degrees.&lt;br /&gt;
* '''[[Howto:Animate_models#Expressions|expression]]:''' is optional. For more details see [[Expressions|Expressions]]&lt;br /&gt;
&lt;br /&gt;
=== Scale ===&lt;br /&gt;
A scale animation scales (resizes) an object. This can be either property-value dependant (first example) or a fixed scale (second example).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;type&amp;gt;scale&amp;lt;/type&amp;gt;&lt;br /&gt;
  &amp;lt;object-name&amp;gt;Object&amp;lt;/object-name&amp;gt;&lt;br /&gt;
  &amp;lt;property&amp;gt;sim/time/sun-angle-rad&amp;lt;/property&amp;gt;&lt;br /&gt;
  &amp;lt;x-min&amp;gt;1.0&amp;lt;/x-min&amp;gt;&lt;br /&gt;
  &amp;lt;y-min&amp;gt;1.0&amp;lt;/y-min&amp;gt;&lt;br /&gt;
  &amp;lt;z-min&amp;gt;1.0&amp;lt;/z-min&amp;gt;&lt;br /&gt;
  &amp;lt;x-factor&amp;gt;1.4&amp;lt;/x-factor&amp;gt;&lt;br /&gt;
  &amp;lt;y-factor&amp;gt;1.4&amp;lt;/y-factor&amp;gt;&lt;br /&gt;
  &amp;lt;z-factor&amp;gt;2.0&amp;lt;/z-factor&amp;gt;&lt;br /&gt;
 &amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* ?-min: the mimimum scale factor for each axis. If the property value would result in a smaller factor than this setting, the scale animation will hold.&lt;br /&gt;
* ?-factor: the scale factor for each axis (factor*property=scale factor).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;type&amp;gt;scale&amp;lt;/type&amp;gt;&lt;br /&gt;
  &amp;lt;x-offset&amp;gt;0.5&amp;lt;/x-offset&amp;gt;&lt;br /&gt;
  &amp;lt;y-offset&amp;gt;0.5&amp;lt;/y-offset&amp;gt;&lt;br /&gt;
  &amp;lt;z-offset&amp;gt;0.5&amp;lt;/z-offset&amp;gt;&lt;br /&gt;
 &amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* x.offset: the scale factor.&lt;br /&gt;
* Add [[#Center|&amp;amp;lt;center&amp;amp;gt;]] coordinates, to scale the object around that point.&lt;br /&gt;
* '''You can optionally use an [[Howto:Animate_models#Expressions|expression]] in the &amp;lt;factor&amp;gt; or &amp;lt;offset&amp;gt; inputs.''' For more details see [[Expressions|Expressions]]&lt;br /&gt;
&lt;br /&gt;
=== Select ===&lt;br /&gt;
This animation selects (or unselects) objects when certain conditions are true (or false). The example below shows the object when the n1 of engine[1] is higher than 25%.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;object-name&amp;gt;Object&amp;lt;/object-name&amp;gt;&lt;br /&gt;
  &amp;lt;type&amp;gt;select&amp;lt;/type&amp;gt;&lt;br /&gt;
  &amp;lt;condition&amp;gt;&lt;br /&gt;
   &amp;lt;greater-than&amp;gt;&lt;br /&gt;
    &amp;lt;property&amp;gt;engines/engine[0]/n1&amp;lt;/property&amp;gt;&lt;br /&gt;
    &amp;lt;value&amp;gt;25&amp;lt;/value&amp;gt;&lt;br /&gt;
   &amp;lt;/greater-than&amp;gt;&lt;br /&gt;
  &amp;lt;/condition&amp;gt;&lt;br /&gt;
 &amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Shader ===&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;type&amp;gt;shader&amp;lt;/type&amp;gt;&lt;br /&gt;
  &amp;lt;shader&amp;gt;chrome&amp;lt;/shader&amp;gt;&lt;br /&gt;
  &amp;lt;texture&amp;gt;chrome2.png&amp;lt;/texture&amp;gt;&lt;br /&gt;
  &amp;lt;object-name&amp;gt;Object&amp;lt;/object-name&amp;gt;&lt;br /&gt;
 &amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* '''shader:''' &lt;br /&gt;
* '''texture:''' path to the texture used by the shader.&lt;br /&gt;
&lt;br /&gt;
=== Spin ===&lt;br /&gt;
Very similar to [[#Rotate|rotate]], but the property provides a value in revolutions per minute (RPM) rather than an absolute position in degrees, and offset cannot be used.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;object-name&amp;gt;Object&amp;lt;/object-name&amp;gt;&lt;br /&gt;
  &amp;lt;type&amp;gt;spin&amp;lt;/type&amp;gt;&lt;br /&gt;
  &amp;lt;property&amp;gt;engines/engine[0]/n1&amp;lt;/property&amp;gt;&lt;br /&gt;
  &amp;lt;factor&amp;gt;25&amp;lt;/factor&amp;gt;&lt;br /&gt;
  &amp;lt;center&amp;gt;&lt;br /&gt;
   &amp;lt;x-m&amp;gt;-1.50&amp;lt;/x-m&amp;gt;&lt;br /&gt;
   &amp;lt;y-m&amp;gt; 1   &amp;lt;/y-m&amp;gt;&lt;br /&gt;
   &amp;lt;z-m&amp;gt; 0.25&amp;lt;/z-m&amp;gt;&lt;br /&gt;
  &amp;lt;/center&amp;gt;&lt;br /&gt;
  &amp;lt;axis&amp;gt;&lt;br /&gt;
   &amp;lt;x&amp;gt;0&amp;lt;/x&amp;gt;&lt;br /&gt;
   &amp;lt;y&amp;gt;1&amp;lt;/y&amp;gt;&lt;br /&gt;
   &amp;lt;z&amp;gt;0&amp;lt;/z&amp;gt;&lt;br /&gt;
  &amp;lt;/axis&amp;gt;&lt;br /&gt;
 &amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* '''factor:''' is optional.&lt;br /&gt;
&lt;br /&gt;
=== Timed ===&lt;br /&gt;
Swtiches between objects at specified intervals. This example switches between a lights-on model and a lights-off model. Lights on are shown 0.2 seconds, while lights off are displayed for 0.8 seconds.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;type&amp;gt;timed&amp;lt;/type&amp;gt;&lt;br /&gt;
  &amp;lt;object-name&amp;gt;BacklightOn&amp;lt;/object-name&amp;gt;&lt;br /&gt;
  &amp;lt;object-name&amp;gt;BacklightOff&amp;lt;/object-name&amp;gt;&lt;br /&gt;
  &amp;lt;use-personality type=&amp;quot;bool&amp;quot;&amp;gt;true&amp;lt;/use-personality&amp;gt;&lt;br /&gt;
  &amp;lt;branch-duration-sec&amp;gt;0.8&amp;lt;/branch-duration-sec&amp;gt;&lt;br /&gt;
  &amp;lt;branch-duration-sec&amp;gt;0.2&amp;lt;/branch-duration-sec&amp;gt;&lt;br /&gt;
 &amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Tracking ===&lt;br /&gt;
A ''locked-track animation'' can do exactly the same thing as the [https://docs.blender.org/manual/en/latest/animation/constraints/tracking/locked_track.html Locked Track constraint] available in Blender and can also be used to simulate simple inverse kinematic systems consisting of two bones connected with a revolute joint (aka hinge). For details see: [[Howto:Animate gear scissors using the tracking animation]]&lt;br /&gt;
&lt;br /&gt;
=== Translate ===&lt;br /&gt;
This animation moves an object along an axis (not to be confused with the &amp;lt;code&amp;gt;textranslate&amp;lt;/code&amp;gt; animation which only moves the texture on the UV map, not the geometry). The example below will move an object in the Y direction:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;0&amp;quot; cellspacing=&amp;quot;0&amp;quot; &lt;br /&gt;
!Property value&lt;br /&gt;
!Output&lt;br /&gt;
|-&lt;br /&gt;
| -1&lt;br /&gt;
| -2.5&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| 2.5&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 7.5&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;type&amp;gt;translate&amp;lt;/type&amp;gt;&lt;br /&gt;
  &amp;lt;object-name&amp;gt;Object&amp;lt;/object-name&amp;gt;&lt;br /&gt;
  &amp;lt;property&amp;gt;controls/seat/pilot/position-norm&amp;lt;/property&amp;gt;&lt;br /&gt;
  &amp;lt;factor&amp;gt;5&amp;lt;/factor&amp;gt;&lt;br /&gt;
  &amp;lt;offset-m&amp;gt;2.5&amp;lt;/offset-m&amp;gt;&lt;br /&gt;
  &amp;lt;axis&amp;gt;&lt;br /&gt;
   &amp;lt;x&amp;gt;0&amp;lt;/x&amp;gt;&lt;br /&gt;
   &amp;lt;y&amp;gt;1&amp;lt;/y&amp;gt;&lt;br /&gt;
   &amp;lt;z&amp;gt;0&amp;lt;/z&amp;gt;&lt;br /&gt;
  &amp;lt;/axis&amp;gt;&lt;br /&gt;
 &amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When using interpolation tables such as:&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
&amp;lt;interpolation&amp;gt;&lt;br /&gt;
	&amp;lt;entry&amp;gt;&amp;lt;ind&amp;gt;0.0&amp;lt;/ind&amp;gt;&amp;lt;dep&amp;gt; 0&amp;lt;/dep&amp;gt;&amp;lt;/entry&amp;gt;&lt;br /&gt;
	&amp;lt;entry&amp;gt;&amp;lt;ind&amp;gt;0.666&amp;lt;/ind&amp;gt;&amp;lt;dep&amp;gt;0.18&amp;lt;/dep&amp;gt;&amp;lt;/entry&amp;gt;&lt;br /&gt;
	&amp;lt;entry&amp;gt;&amp;lt;ind&amp;gt;1.0&amp;lt;/ind&amp;gt;&amp;lt;dep&amp;gt;0.27&amp;lt;/dep&amp;gt;&amp;lt;/entry&amp;gt;&lt;br /&gt;
&amp;lt;/interpolation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
The first figure (&amp;lt;code&amp;gt;&amp;lt;ind&amp;gt;&amp;lt;/code&amp;gt;) refers to the value of the property associated with the object and the second figure (&amp;lt;code&amp;gt;&amp;lt;dep&amp;gt;&amp;lt;/code&amp;gt;) refers to the amount that the object moves in metres. For example, in this case, the object moves 18cm when the value reads 0.66 and 27cm when the value is 1.&lt;br /&gt;
&lt;br /&gt;
'''IF used WTIHOUT property''' : The object is placed at some (Value of Offset) meters away from its original position, along the virtual axis formed by said original position and the point with coordinates x/y/z defined in the &amp;lt;axis&amp;gt; tag.&lt;br /&gt;
Mathematically, assuming the Object to translate is (in the model space) placed at point A (x1, y1, z1) and you want to relocate it to point B (x2, y2, z2) then x2,y2,z2 are the values in the &amp;lt;axis&amp;gt; tag of the Translate animation and &amp;lt;offset&amp;gt; can be computed as SQRT((x2-x1)^2+(y2-y1)^2+(z2-z1)^2)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* '''factor:''' is optional.&lt;br /&gt;
* '''offset-m:''' is optional. Offset in meters.&lt;br /&gt;
* '''[[Howto:Animate_models#Expressions|expression]]:''' is optional. For more details see [[Expressions|Expressions]]&lt;br /&gt;
&lt;br /&gt;
== Material animation ==&lt;br /&gt;
Animate the material properties of an object, such as it's texture, shininess or color.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt; &lt;br /&gt;
  &amp;lt;type&amp;gt;material&amp;lt;/type&amp;gt; &lt;br /&gt;
  &amp;lt;object-name&amp;gt;Object&amp;lt;/object-name&amp;gt;&lt;br /&gt;
  &amp;lt;property-base&amp;gt;sim/model/c172p/material&amp;lt;/property-base&amp;gt;&lt;br /&gt;
  ...&lt;br /&gt;
  lines as mentioned below&lt;br /&gt;
  ...&lt;br /&gt;
 &amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Optional:'''&lt;br /&gt;
* '''property-base:''' All other properties of this animation will be relative to this property node.&lt;br /&gt;
&lt;br /&gt;
'''Notes:'''&lt;br /&gt;
* Numbers are clamped to 0.0 - 1.0, except &amp;lt;code&amp;gt;shininess&amp;lt;/code&amp;gt;, which is clamped to 0 - 128.&lt;br /&gt;
* By appending &amp;lt;tt&amp;gt;-prop&amp;lt;/tt&amp;gt; each of the material properties can read its value from another property.&lt;br /&gt;
* '''New since SimGear commit &amp;lt;code&amp;gt;51149bf&amp;lt;/code&amp;gt;''': For each of the color components (&amp;lt;code&amp;gt;ambient&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;diffuse&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;emission&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;specular&amp;lt;/code&amp;gt;), the &amp;lt;code&amp;gt;red&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;green&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;blue&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;factor&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;offset&amp;lt;/code&amp;gt; tags can each contain an [[Expressions|expression]] instead of a value.&lt;br /&gt;
&lt;br /&gt;
=== Ambient ===&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
  &amp;lt;ambient&amp;gt;&lt;br /&gt;
   &amp;lt;red&amp;gt;1.0&amp;lt;/red&amp;gt;&lt;br /&gt;
   &amp;lt;green&amp;gt;0.2&amp;lt;/green&amp;gt;&lt;br /&gt;
   &amp;lt;blue&amp;gt;0.0&amp;lt;/blue&amp;gt;&lt;br /&gt;
  &amp;lt;/ambient&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Diffuse ===&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
  &amp;lt;diffuse&amp;gt;&lt;br /&gt;
   &amp;lt;red&amp;gt;1.0&amp;lt;/red&amp;gt;&lt;br /&gt;
   &amp;lt;green&amp;gt;0.2&amp;lt;/green&amp;gt;&lt;br /&gt;
   &amp;lt;blue&amp;gt;0.0&amp;lt;/blue&amp;gt;&lt;br /&gt;
  &amp;lt;/diffuse&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Emission ===&lt;br /&gt;
{{Main article|Howto: Illuminate faces}}&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
  &amp;lt;emission&amp;gt;&lt;br /&gt;
   &amp;lt;red&amp;gt;1.0&amp;lt;/red&amp;gt;&lt;br /&gt;
   &amp;lt;green&amp;gt;0.2&amp;lt;/green&amp;gt;&lt;br /&gt;
   &amp;lt;blue&amp;gt;0.0&amp;lt;/blue&amp;gt;&lt;br /&gt;
   &amp;lt;factor-prop&amp;gt;controls/lighting/panel-norm&amp;lt;/factor-prop&amp;gt;&lt;br /&gt;
  &amp;lt;/emission&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Emission colors are multiplied by the factor-prop value. 1 is maximum color intensity, while 0 is the minimum. Colors are calculated according to the [http://en.wikipedia.org/wiki/RGB_color_model RGB color model].&lt;br /&gt;
&lt;br /&gt;
=== Specular ===&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
  &amp;lt;specular&amp;gt;&lt;br /&gt;
   &amp;lt;red&amp;gt;1.0&amp;lt;/red&amp;gt;&lt;br /&gt;
   &amp;lt;green&amp;gt;0.2&amp;lt;/green&amp;gt;&lt;br /&gt;
   &amp;lt;blue&amp;gt;0.0&amp;lt;/blue&amp;gt;&lt;br /&gt;
  &amp;lt;/specular&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Transparency ===&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
  &amp;lt;transparency&amp;gt;&lt;br /&gt;
   &amp;lt;alpha-prop&amp;gt;rotors/tail/rpm&amp;lt;/alpha-prop&amp;gt;&lt;br /&gt;
   &amp;lt;factor&amp;gt;-0.0015&amp;lt;/factor&amp;gt;&lt;br /&gt;
   &amp;lt;offset&amp;gt;1&amp;lt;/offset&amp;gt;&lt;br /&gt;
  &amp;lt;/transparency&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Shininess ===&lt;br /&gt;
Shininess is clamped to 0-128.&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
  &amp;lt;shininess&amp;gt;105&amp;lt;/shininess&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Threshold ===&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
  &amp;lt;threshold&amp;gt;0.001&amp;lt;/threshold&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Texture ===&lt;br /&gt;
Used for the [[Livery over MP]] system.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
  &amp;lt;property-base&amp;gt;sim/model/livery&amp;lt;/property-base&amp;gt; &lt;br /&gt;
  &amp;lt;texture-prop&amp;gt;engine&amp;lt;/texture-prop&amp;gt; &lt;br /&gt;
  &amp;lt;texture&amp;gt;KLM.png&amp;lt;/texture&amp;gt; &lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Texture Animations ==&lt;br /&gt;
Applying different matrix transformations to the textures of an object.&lt;br /&gt;
&lt;br /&gt;
=== Textranslate ===&lt;br /&gt;
A very important animation for cockpits! This animation moves textures over a surface.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;type&amp;gt;textranslate&amp;lt;/type&amp;gt;&lt;br /&gt;
  &amp;lt;object-name&amp;gt;Object&amp;lt;/object-name&amp;gt;&lt;br /&gt;
  &amp;lt;property&amp;gt;autopilot/settings/target-speed-kt&amp;lt;/property&amp;gt;&lt;br /&gt;
  &amp;lt;bias&amp;gt;0.0001&amp;lt;/bias&amp;gt;&lt;br /&gt;
  &amp;lt;factor&amp;gt;0.001&amp;lt;/factor&amp;gt;&lt;br /&gt;
  &amp;lt;step&amp;gt;100&amp;lt;/step&amp;gt;&lt;br /&gt;
  &amp;lt;axis&amp;gt;&lt;br /&gt;
   &amp;lt;x&amp;gt;0&amp;lt;/x&amp;gt;&lt;br /&gt;
   &amp;lt;y&amp;gt;1&amp;lt;/y&amp;gt;&lt;br /&gt;
  &amp;lt;/axis&amp;gt;&lt;br /&gt;
 &amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* '''bias:''' Adds an offset to the property before factor/step. A small value is needed to compensate for [http://en.wikipedia.org/wiki/Floating_point#Accuracy_problems floating point accuracy].&lt;br /&gt;
* '''factor:''' property * factor * texture width/height = the amount of pixels that the texture should be translated. If your texture is 256 pixels, an textranslate of 0.1 will result in the texture moving with 26 pixels, into the direction specified by the axis settings.&lt;br /&gt;
* '''step:''' the step size at which the texture is translated. If this is set to 0.1, the texture will only be translated at 0.1, 0.2, 0.3 etc.&lt;br /&gt;
* '''axis:''' the direction in which the texture is translated. Y is up/down, while X is left/right.&lt;br /&gt;
&lt;br /&gt;
=== Texrotate ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
&amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;object-name&amp;gt;Object&amp;lt;/object-name&amp;gt;&lt;br /&gt;
  &amp;lt;type&amp;gt;texrotate&amp;lt;/type&amp;gt;&lt;br /&gt;
  &amp;lt;property&amp;gt;some/property/path&amp;lt;/property&amp;gt;&lt;br /&gt;
  &amp;lt;factor&amp;gt;25&amp;lt;/factor&amp;gt;&lt;br /&gt;
  &amp;lt;offset-deg&amp;gt;25&amp;lt;/offset-deg&amp;gt;&lt;br /&gt;
  &amp;lt;center&amp;gt;&lt;br /&gt;
    &amp;lt;x&amp;gt;0.5&amp;lt;/x&amp;gt;&lt;br /&gt;
    &amp;lt;y&amp;gt;0.5&amp;lt;/y&amp;gt;&lt;br /&gt;
    &amp;lt;z&amp;gt;0&amp;lt;/z&amp;gt;&lt;br /&gt;
  &amp;lt;/center&amp;gt;&lt;br /&gt;
  &amp;lt;axis&amp;gt;&lt;br /&gt;
    &amp;lt;x&amp;gt;0&amp;lt;/x&amp;gt;&lt;br /&gt;
    &amp;lt;y&amp;gt;0&amp;lt;/y&amp;gt;&lt;br /&gt;
    &amp;lt;z&amp;gt;1&amp;lt;/z&amp;gt;&lt;br /&gt;
  &amp;lt;/axis&amp;gt;&lt;br /&gt;
&amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Textrapezoid ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
&amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;type&amp;gt;textrapezoid&amp;lt;/type&amp;gt;&lt;br /&gt;
  &amp;lt;object-name&amp;gt;HUD.l.canvas&amp;lt;/object-name&amp;gt;&lt;br /&gt;
  &amp;lt;property&amp;gt;/hud/trapezoid-correction&amp;lt;/property&amp;gt;&lt;br /&gt;
  &amp;lt;side&amp;gt;bottom&amp;lt;/side&amp;gt;&lt;br /&gt;
&amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* '''side''': side of quad which should be scaled (''top'' (default)/''right''/''bottom''/''left'')&lt;br /&gt;
&lt;br /&gt;
=== Texmultiple ===&lt;br /&gt;
&lt;br /&gt;
Only one texture matrix can be applied to each object. With ''textmultiple'' multiple texture animations can be combined into a single matrix, applied to the specified object.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
&amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;type&amp;gt;texmultiple&amp;lt;/type&amp;gt;&lt;br /&gt;
  &amp;lt;object-name&amp;gt;HUD.l.canvas&amp;lt;/object-name&amp;gt;&lt;br /&gt;
  &amp;lt;transform&amp;gt;&lt;br /&gt;
    &amp;lt;subtype&amp;gt;textranslate&amp;lt;/subtype&amp;gt;&lt;br /&gt;
    &amp;lt;property&amp;gt;/hud/offset-x&amp;lt;/property&amp;gt;&lt;br /&gt;
    &amp;lt;axis&amp;gt;&lt;br /&gt;
     &amp;lt;x&amp;gt;1&amp;lt;/x&amp;gt;&lt;br /&gt;
     &amp;lt;y&amp;gt;0&amp;lt;/y&amp;gt;&lt;br /&gt;
     &amp;lt;z&amp;gt;0&amp;lt;/z&amp;gt;&lt;br /&gt;
   &amp;lt;/axis&amp;gt;&lt;br /&gt;
  &amp;lt;/transform&amp;gt;&lt;br /&gt;
  &amp;lt;transform&amp;gt;&lt;br /&gt;
    &amp;lt;subtype&amp;gt;textranslate&amp;lt;/subtype&amp;gt;&lt;br /&gt;
    &amp;lt;property&amp;gt;/hud/offset-y&amp;lt;/property&amp;gt;&lt;br /&gt;
    &amp;lt;axis&amp;gt;&lt;br /&gt;
     &amp;lt;x&amp;gt;0&amp;lt;/x&amp;gt;&lt;br /&gt;
     &amp;lt;y&amp;gt;1&amp;lt;/y&amp;gt;&lt;br /&gt;
     &amp;lt;z&amp;gt;0&amp;lt;/z&amp;gt;&lt;br /&gt;
   &amp;lt;/axis&amp;gt;&lt;br /&gt;
  &amp;lt;/transform&amp;gt;&lt;br /&gt;
  &amp;lt;transform&amp;gt;&lt;br /&gt;
    &amp;lt;subtype&amp;gt;textrapezoid&amp;lt;/subtype&amp;gt;&lt;br /&gt;
    &amp;lt;property&amp;gt;/hud/trapezoid-correction&amp;lt;/property&amp;gt;&lt;br /&gt;
  &amp;lt;/transform&amp;gt;&lt;br /&gt;
&amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Object interaction animations ==&lt;br /&gt;
=== Enable-hot ===&lt;br /&gt;
Scenery objects are automatically defined as solid by FlightGear, meaning that an aircraft can taxi on them and/or crash when touching. For certain objects (groundmarkings, beacon light-beams etc.) this might be an unwanted feature. The solidness can be disabled with the following animation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;object-name&amp;gt;Object&amp;lt;/object-name&amp;gt;&lt;br /&gt;
  &amp;lt;enable-hot type=&amp;quot;bool&amp;quot;&amp;gt;false&amp;lt;/enable-hot&amp;gt;&lt;br /&gt;
 &amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* '''enable-hot:''' can be either true or false. Remember that objects are automatically solid, so it should not be necessary to set this at all when wanting solidness.&lt;br /&gt;
&lt;br /&gt;
=== Interactions ===&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt; &lt;br /&gt;
  &amp;lt;type&amp;gt;interaction&amp;lt;/type&amp;gt; &lt;br /&gt;
  &amp;lt;object-name&amp;gt;Object&amp;lt;/object-name&amp;gt; &lt;br /&gt;
  &amp;lt;interaction-type&amp;gt;carrier-wire&amp;lt;/interaction-type&amp;gt; &lt;br /&gt;
 &amp;lt;/animation&amp;gt; &lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* '''interaction-type:''' can have the following values:&lt;br /&gt;
**'''carrier-catapult:'''&lt;br /&gt;
** '''carrier-wire:''' makes the object act as an arresting wire, as used on [[aircraft carrier]]s.&lt;br /&gt;
&lt;br /&gt;
== Direct manipulation animations ==&lt;br /&gt;
=== Knob / slider (v. 2.11-) ===&lt;br /&gt;
{{Main article|Knob / slider animation}}&lt;br /&gt;
&lt;br /&gt;
=== Pick ===&lt;br /&gt;
{{Main article|Howto: Make a clickable panel#Pick}}&lt;br /&gt;
&lt;br /&gt;
=== Touch ===&lt;br /&gt;
&lt;br /&gt;
The touch animation provides the normalized coordinates of a touch (or click) event on a 2d surface. The coordinates are passed in the argument and can be accessed using cmdarg() in Nasal.&lt;br /&gt;
&lt;br /&gt;
* Touch animation is designed to work with a quad that is being used as a Canvas placement (display).&lt;br /&gt;
* The touch animation must not be combined with a pick animation on the same object.&lt;br /&gt;
* More info here: [[Touch Animation]]&lt;br /&gt;
&lt;br /&gt;
==== touch example ====&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;animation&amp;gt;&lt;br /&gt;
        &amp;lt;type&amp;gt;touch&amp;lt;/type&amp;gt;&lt;br /&gt;
        &amp;lt;visible&amp;gt;true&amp;lt;/visible&amp;gt;&lt;br /&gt;
        &amp;lt;object-name&amp;gt;CanvasPlacement&amp;lt;/object-name&amp;gt;&lt;br /&gt;
        &amp;lt;action&amp;gt;&lt;br /&gt;
            &amp;lt;touch&amp;gt;0&amp;lt;/touch&amp;gt;&lt;br /&gt;
            &amp;lt;repeatable&amp;gt;false&amp;lt;/repeatable&amp;gt;&lt;br /&gt;
            &amp;lt;binding&amp;gt;&lt;br /&gt;
                &amp;lt;command&amp;gt;nasal&amp;lt;/command&amp;gt;&lt;br /&gt;
                &amp;lt;script&amp;gt;print(&amp;quot;touch input (&amp;quot;,cmdarg().getNode(&amp;quot;x&amp;quot;).getValue(),&amp;quot;,&amp;quot;,cmdarg().getNode(&amp;quot;y&amp;quot;).getValue())&amp;lt;/script&amp;gt;&lt;br /&gt;
            &amp;lt;/binding&amp;gt;&lt;br /&gt;
        &amp;lt;/action&amp;gt;&lt;br /&gt;
    &amp;lt;/animation&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Shadow Handling ==&lt;br /&gt;
There exist several possibilites for handling of shadows. &amp;lt;br /&amp;gt;&lt;br /&gt;
See '''[[ALS_technical_notes|ALS Technical Notes]]''' and more specific '''[[ALS_technical_notes#ALS_fuselage_shadow_effect|Fuselage Shadow Effect with ALS]]''' for a relatively simple shadow handling.&amp;lt;br /&amp;gt;&lt;br /&gt;
See '''[[Project Rembrandt]]''' which - amongst other functionality - implements a very realistic shadow mapping.&lt;br /&gt;
As of January 2021 Project Rembrandt was replaced by the '''[[Compositor]]''' renderer which combines both Project Rembrandt and ALS in a single rendering engine. This also means that the Fuselage Shadow Effect with ALS is now deprecated.&lt;br /&gt;
&lt;br /&gt;
== References ==&lt;br /&gt;
{{Appendix|all|&lt;br /&gt;
* {{cite web |url=http://www.opensubscriber.com/message/flightgear-devel@flightgear.org/958955.html |title=&amp;quot;material&amp;quot; animation (and the bo105 as an example) |first=Melchior |last=Franz |date=22 March 2005 |work=FlightGear-devel mailinglist }}&lt;br /&gt;
* {{cite web |url=http://www.mail-archive.com/flightgear-devel@lists.sourceforge.net/msg01546.html |title=flash animation |first=Frederic |last=Bouvier |date=22 Feb 2006 |work=FlightGear-devel mailinglist }}&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Related content ==&lt;br /&gt;
=== Wiki articles ===&lt;br /&gt;
* [[MP Fallback models]]&lt;br /&gt;
* [[Howto:Animate gear scissors]]&lt;br /&gt;
* [[Howto:Animate helicopters]]&lt;br /&gt;
* [[Howto:Creating 3D instruments]]&lt;br /&gt;
&lt;br /&gt;
=== Forum topics ===&lt;br /&gt;
* {{forum link|t=37353|title=3d models, how to produce them in an understandable way}} (April 2020-) - Touches on the subject of not using LOD range animations in scenery models.&lt;br /&gt;
* {{forum link|t=36545|title=speedo Drum settings}} (November 2019) - Animating a mechanical multi-digit drum counter&lt;br /&gt;
&lt;br /&gt;
[[Category:Aircraft enhancement|Animate models]]&lt;br /&gt;
[[Category:Howto|Animate models]]&lt;br /&gt;
[[Category:Modeling|Animate models]]&lt;br /&gt;
[[Category:Scenery enhancement|Animate models]]&lt;/div&gt;</summary>
		<author><name>TheEagle</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Howto:Animate_models&amp;diff=137555</id>
		<title>Howto:Animate models</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Howto:Animate_models&amp;diff=137555"/>
		<updated>2023-04-14T00:57:50Z</updated>

		<summary type="html">&lt;p&gt;TheEagle: Some small corrections, added documentation about commit 51149bf: Support expressions in material animations&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The real world is full of motion. To simulate this in [[FlightGear]], '''models must be animated'''.&lt;br /&gt;
&lt;br /&gt;
FlightGear allows you to animate models in response to property changes: for example, the propellers can spin when the engine is on and the elevators can move up and down with your controller. There is no fixed limit on what parts can be animated: the only requirements are that the part is named in the 3D model file, and that there is a property in the main tree that you can use to get the positioning information. &lt;br /&gt;
&lt;br /&gt;
This document provides basic information for all kind of animations. When animating your model, it is very helpful to find an aircraft with parts similar to yours and use it as an example. Cut and paste the code into your wrapper file and then edit to suit.&lt;br /&gt;
&lt;br /&gt;
== Notes ==&lt;br /&gt;
=== File name of main model and animation XML file ===&lt;br /&gt;
{{main article|Aircraft-set.xml#Not used for loading multiplayer aircraft}}&lt;br /&gt;
The file name of the main model and animation XML file, or the .ac file if there is no XML file, (in essence the property &amp;lt;code&amp;gt;/sim/model/path&amp;lt;/code&amp;gt;) will be the name of the aircraft that is transmitted when using [[multiplayer]] and will also be used for loading multiplayer aircraft.&lt;br /&gt;
&lt;br /&gt;
There is also a mechanism to substitute a full aircraft model with a simpler AI aircraft model if one is available at the same file path (including for example &amp;lt;code&amp;gt;Models/Boeing-797-800.xml&amp;lt;/code&amp;gt;), but in &amp;lt;code&amp;gt;[[$FG_ROOT]]/'''AI'''/Aircraft/&amp;lt;/code&amp;gt; instead of &amp;lt;code&amp;gt;$FG_ROOT/Aircraft/&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== .ac files ===&lt;br /&gt;
{{Main article|AC files: Understanding and changing .ac code#Identifying an object}}&lt;br /&gt;
&lt;br /&gt;
When referring to an .ac file in your xml animation, it is important that the &amp;lt;code&amp;gt;&amp;lt;object-name&amp;gt;&amp;lt;/code&amp;gt; exactly matches the object named in the .ac file (this includes cases!). &lt;br /&gt;
&lt;br /&gt;
'''Note for SketchUp users:''' The spatial reference X/Y/Z used in animation to locate an object or a point are different from the ones in AC3D ie X values are the same in both but Y in animation must be matched to AC3D's -Z (Z value but opposite sign) and Z value in animation must be matched to AC3D's Y value. &lt;br /&gt;
&lt;br /&gt;
'''Note for SketchUp users:''' when exporting to AC3D in Sketchup, the .ac file will name the objects in your model to &amp;quot;blah&amp;quot; by default. You need to amend the relevant object names in your .ac file using text edit, so that the xml will work.&lt;br /&gt;
&lt;br /&gt;
=== Animation order ===&lt;br /&gt;
Animations are executed by FlightGear in the order that they are read in the model's .xml file. Therefore, it is very important to pay attention to the order, especially when multiple animations are applied to the same object(s). Wrong ordering of animations might cause [[Howto:Animate models#Timed|timed]] animations (used to create flashing lights) to not work. For further details see this [https://sourceforge.net/p/flightgear/mailman/message/37090714/ thread] on the development mailing list. Updated [https://scenery.flightgear.org/app.php?c=Models&amp;amp;a=browse&amp;amp;shared=18 shared Effects models] will be available very soon.&lt;br /&gt;
&lt;br /&gt;
Similar problems can be encountered with the dist-scale instead of the timed animation.&lt;br /&gt;
== Tags used in most animations ==&lt;br /&gt;
=== Name ===&lt;br /&gt;
With a name animation, you can group multiple objects. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;name&amp;gt;Collection1&amp;lt;/name&amp;gt;&lt;br /&gt;
  &amp;lt;object-name&amp;gt;Object1&amp;lt;/object-name&amp;gt;&lt;br /&gt;
  &amp;lt;object-name&amp;gt;Object2&amp;lt;/object-name&amp;gt;&lt;br /&gt;
  &amp;lt;object-name&amp;gt;Object3&amp;lt;/object-name&amp;gt;&lt;br /&gt;
 &amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The example above creates a &amp;quot;virtual object&amp;quot; with the name Collection1. In animation, we can animate this group of objects, by using:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
 &amp;lt;object-name&amp;gt;Collection1&amp;lt;/object-name&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Object-name ===&lt;br /&gt;
These names are set in the 3D model. Each single object has a unique name; for easy identification it is advised to use descriptive names (LeftElevator, Rudder etc.). Animations are only applied to those objects that are mentioned in an object-name line (one object per line!). Animations lacking those, will be applied to the entire model.&lt;br /&gt;
&lt;br /&gt;
=== Property ===&lt;br /&gt;
Each animation must be associated with exactly one property from the main FlightGear property tree (remember that the properties in the wrapper file are not part of the main tree), using &amp;lt;code&amp;gt;&amp;lt;property&amp;gt;&amp;lt;/code&amp;gt; to provide the property path:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;type&amp;gt;rotate&amp;lt;/type&amp;gt;&lt;br /&gt;
  &amp;lt;object-name&amp;gt;Rudder&amp;lt;/object-name&amp;gt;&lt;br /&gt;
  &amp;lt;property&amp;gt;controls/rudder&amp;lt;/property&amp;gt;&lt;br /&gt;
 &amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note the omission of the leading slash '/' when referring to the property. This assures that when the model is used for AI or multiplayer traffic the animations will follow that of the AI controller instead of that of the user.&lt;br /&gt;
&lt;br /&gt;
=== Axis ===&lt;br /&gt;
An axis part is required in every animation that involves a rotating or moving thing.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
  &amp;lt;axis&amp;gt;&lt;br /&gt;
   &amp;lt;x&amp;gt;0&amp;lt;/x&amp;gt;&lt;br /&gt;
   &amp;lt;y&amp;gt;1&amp;lt;/y&amp;gt;&lt;br /&gt;
   &amp;lt;z&amp;gt;0&amp;lt;/z&amp;gt;&lt;br /&gt;
  &amp;lt;/axis&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The axis are similar to the ones of the 3D model. There is a difference between rotation and translation:&lt;br /&gt;
* In rotation animations, the axis part defines around what axis the object rotates. Negative/positive values make the difference between counterclockwise and clockwise rotations.&lt;br /&gt;
* In translate animations, the part defines along what axis the object moves. If the x-axis is poiting backwards, an x-value of -1 will result in forward motion.&lt;br /&gt;
&lt;br /&gt;
You could also define two points, between which FlightGear will calculate the correct axis. This makes the use of a [[#Center|&amp;lt;nowiki&amp;gt;&amp;lt;center&amp;gt;&amp;lt;/nowiki&amp;gt;]] tag redundant! Such coordinates are extremely useful for animating control surfaces (rudder, elevators etc.).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
  &amp;lt;axis&amp;gt;   &lt;br /&gt;
   &amp;lt;x1-m&amp;gt; 4.9&amp;lt;/x1-m&amp;gt;&lt;br /&gt;
   &amp;lt;y1-m&amp;gt; 7.1&amp;lt;/y1-m&amp;gt;&lt;br /&gt;
   &amp;lt;z1-m&amp;gt;-1.0&amp;lt;/z1-m&amp;gt;&lt;br /&gt;
   &amp;lt;x2-m&amp;gt; 5.9&amp;lt;/x2-m&amp;gt;&lt;br /&gt;
   &amp;lt;y2-m&amp;gt;11.2&amp;lt;/y2-m&amp;gt;&lt;br /&gt;
   &amp;lt;z2-m&amp;gt;-0.5&amp;lt;/z2-m&amp;gt;&lt;br /&gt;
  &amp;lt;/axis&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Center ===&lt;br /&gt;
Various animations ([[#Rotate|rotate]], [[#Spin|spin]], [[#Scale|scale]]) move around a center point.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
  &amp;lt;center&amp;gt;&lt;br /&gt;
   &amp;lt;x-m&amp;gt;-1.50&amp;lt;/x-m&amp;gt;&lt;br /&gt;
   &amp;lt;y-m&amp;gt; 1   &amp;lt;/y-m&amp;gt;&lt;br /&gt;
   &amp;lt;z-m&amp;gt; 0.25&amp;lt;/z-m&amp;gt;&lt;br /&gt;
  &amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The axis are similar to the ones of the 3D model, so finding coordinates is easily done in 3D modeling software.&lt;br /&gt;
&lt;br /&gt;
=== Using a geometry object for axis and centre (2017.2) ===&lt;br /&gt;
&lt;br /&gt;
Added in V2017.2 is support to allow a geometry object (a line segment with two vertices) that is used to define both the centre and the axis for an animation. This will work with rotate, spin, translate and knob animations.&lt;br /&gt;
&lt;br /&gt;
{{Note|Since SimGear commit 6ca9141083e62be1161d0ca2d1e3b918494dd6ad on next, this feature can also be used for slider (usage equivalent to that for a translate animation) and for any of the &amp;lt;*-center&amp;gt; / &amp;lt;*-axis&amp;gt; tags of a locked-track animation.}}&lt;br /&gt;
&lt;br /&gt;
When used for translate animations, the axis line should be normalized (i.e. be 1 meter long) as its length acts as a factor for the translation distance.&lt;br /&gt;
&lt;br /&gt;
The XML syntax for this is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
&amp;lt;axis&amp;gt;&lt;br /&gt;
    &amp;lt;object-name&amp;gt;some-object&amp;lt;/object-name&amp;gt;&lt;br /&gt;
&amp;lt;/axis&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the &amp;lt;code&amp;gt;&amp;lt;axis&amp;gt;...&amp;lt;/axis&amp;gt;&amp;lt;/code&amp;gt; section is omitted entirely, &amp;lt;code&amp;gt;{object-name}-axis&amp;lt;/code&amp;gt; will be used by default, where &amp;lt;code&amp;gt;{object-name}&amp;lt;/code&amp;gt; is the name of the object being animated (if we are animating more than one object, the first object is used). In the earlier example this would be &amp;lt;code&amp;gt;Rudder-axis&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
In the '''.ac''' file, specify a SURF with type (bottom 4 bits, 0=polygon, 1=closedline, 2=line) set to 2, and two vertices that define the axis. For example:&lt;br /&gt;
&lt;br /&gt;
    OBJECT poly&lt;br /&gt;
    name &amp;quot;aileron.l-axis&amp;quot;&lt;br /&gt;
    numvert 2&lt;br /&gt;
    3.2077502191170844 0.18160835055097943 4.055616960642423&lt;br /&gt;
    2.6758650763079 0.28024033462188946 6.477876098622225&lt;br /&gt;
    numsurf 1&lt;br /&gt;
    SURF 0x12&lt;br /&gt;
    mat 0&lt;br /&gt;
    refs 2&lt;br /&gt;
    0 0 0&lt;br /&gt;
    1 0 0&lt;br /&gt;
    kids 0&lt;br /&gt;
&lt;br /&gt;
Once the object-name used for the axis has been processed the geometry object will be hidden. This also allows a visual check for any axis objects that are not yet assigned.&lt;br /&gt;
&lt;br /&gt;
It is possible to reuse the same object definition multiple times within a single XML file. &lt;br /&gt;
&lt;br /&gt;
[[File:Canopy-animation-axis-object.png|small|Illustration of where an axis object (2017.2) can be placed for a canopy]]&lt;br /&gt;
&lt;br /&gt;
[[File:Gauges-knobs-animation-axis-object.png|small|Illustration of where an axis object (2017.2) can be placed for cockpit elements]]&lt;br /&gt;
&lt;br /&gt;
== Additional tags that can be used in most animations ==&lt;br /&gt;
=== Conditions ===&lt;br /&gt;
Multiple animations can make use of a conditional. Check &amp;lt;tt&amp;gt;$FGDATA/Docs/README.conditions&amp;lt;/tt&amp;gt; for some more details.&lt;br /&gt;
&lt;br /&gt;
* '''equals:''' property value (or second property) is equal to value/(first)property.&lt;br /&gt;
* '''greater-than:''' property value (or second property) is larger than value/(first)property.&lt;br /&gt;
* '''greater-than-equals:''' property value (or second property) is greater than or equal to value/(first)property.&lt;br /&gt;
* '''less-than:''' property value (or second property) is smaller than value/(first)property.&lt;br /&gt;
* '''less-than-equals:''' property value (or second property) is smaller than or equal to value/(first)property.&lt;br /&gt;
&lt;br /&gt;
The example below is true when n1 has a value greater than 25.&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
  &amp;lt;condition&amp;gt;&lt;br /&gt;
   &amp;lt;greater-than&amp;gt;&lt;br /&gt;
    &amp;lt;property&amp;gt;engines/engine[1]/n1&amp;lt;/property&amp;gt;&lt;br /&gt;
    &amp;lt;value&amp;gt;25&amp;lt;/value&amp;gt;&lt;br /&gt;
   &amp;lt;/greater-than&amp;gt;&lt;br /&gt;
  &amp;lt;/condition&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then there are some special tags:&lt;br /&gt;
&lt;br /&gt;
* '''and:'''&lt;br /&gt;
* '''not:'''&lt;br /&gt;
* '''or:'''&lt;br /&gt;
&lt;br /&gt;
In the example below, the condition is true when either n1 is greater than 25% or equal to 0%.&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
  &amp;lt;condition&amp;gt;&lt;br /&gt;
   &amp;lt;or&amp;gt;&lt;br /&gt;
    &amp;lt;greater-than&amp;gt;&lt;br /&gt;
     &amp;lt;property&amp;gt;engines/engine[1]/n1&amp;lt;/property&amp;gt;&lt;br /&gt;
     &amp;lt;value&amp;gt;25&amp;lt;/value&amp;gt;&lt;br /&gt;
    &amp;lt;/greater-than&amp;gt;&lt;br /&gt;
    &amp;lt;equals&amp;gt;&lt;br /&gt;
     &amp;lt;property&amp;gt;engines/engine[1]/n1&amp;lt;/property&amp;gt;&lt;br /&gt;
     &amp;lt;value&amp;gt;0&amp;lt;/value&amp;gt;&lt;br /&gt;
    &amp;lt;/equals&amp;gt;&lt;br /&gt;
   &amp;lt;/or&amp;gt;&lt;br /&gt;
  &amp;lt;/condition&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
An example of implementation into an animation looks as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;object-name&amp;gt;Object&amp;lt;/object-name&amp;gt;&lt;br /&gt;
  &amp;lt;type&amp;gt;rotate&amp;lt;/type&amp;gt;&lt;br /&gt;
  &amp;lt;property&amp;gt;suface-positions/left-aileron-pos-norm&amp;lt;/property&amp;gt;&lt;br /&gt;
  &amp;lt;factor&amp;gt;25&amp;lt;/factor&amp;gt;&lt;br /&gt;
  &amp;lt;condition&amp;gt;&lt;br /&gt;
   &amp;lt;greater-than&amp;gt;&lt;br /&gt;
    &amp;lt;property&amp;gt;suface-positions/left-aileron-pos-norm&amp;lt;/property&amp;gt;&lt;br /&gt;
    &amp;lt;value&amp;gt;10&amp;lt;/value&amp;gt;&lt;br /&gt;
   &amp;lt;/greater-than&amp;gt;&lt;br /&gt;
  &amp;lt;/condition&amp;gt;&lt;br /&gt;
  &amp;lt;center&amp;gt;&lt;br /&gt;
   &amp;lt;x-m&amp;gt;-1.50&amp;lt;/x-m&amp;gt;&lt;br /&gt;
   &amp;lt;y-m&amp;gt; 1   &amp;lt;/y-m&amp;gt;&lt;br /&gt;
   &amp;lt;z-m&amp;gt; 0.25&amp;lt;/z-m&amp;gt;&lt;br /&gt;
  &amp;lt;/center&amp;gt;&lt;br /&gt;
  &amp;lt;axis&amp;gt;&lt;br /&gt;
   &amp;lt;x&amp;gt;0&amp;lt;/x&amp;gt;&lt;br /&gt;
   &amp;lt;y&amp;gt;1&amp;lt;/y&amp;gt;&lt;br /&gt;
   &amp;lt;z&amp;gt;0&amp;lt;/z&amp;gt;&lt;br /&gt;
  &amp;lt;/axis&amp;gt;&lt;br /&gt;
 &amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Interpolation ===&lt;br /&gt;
For non-fixed factors, an interpolation &amp;quot;table&amp;quot; can be created. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
  &amp;lt;interpolation&amp;gt;&lt;br /&gt;
   &amp;lt;entry&amp;gt;&lt;br /&gt;
    &amp;lt;ind&amp;gt; 0.0&amp;lt;/ind&amp;gt;&lt;br /&gt;
    &amp;lt;dep&amp;gt; 0.0&amp;lt;/dep&amp;gt;&lt;br /&gt;
   &amp;lt;/entry&amp;gt;&lt;br /&gt;
   &amp;lt;entry&amp;gt;&lt;br /&gt;
    &amp;lt;ind&amp;gt; 0.667&amp;lt;/ind&amp;gt;&lt;br /&gt;
    &amp;lt;dep&amp;gt; 0.0&amp;lt;/dep&amp;gt;&lt;br /&gt;
   &amp;lt;/entry&amp;gt;&lt;br /&gt;
   &amp;lt;entry&amp;gt;&lt;br /&gt;
    &amp;lt;ind&amp;gt; 1.0&amp;lt;/ind&amp;gt;&lt;br /&gt;
    &amp;lt;dep&amp;gt; 0.5&amp;lt;/dep&amp;gt;&lt;br /&gt;
   &amp;lt;/entry&amp;gt;&lt;br /&gt;
  &amp;lt;/interpolation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The lines above represent the following table:&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
!Input&lt;br /&gt;
!Output&lt;br /&gt;
|-&lt;br /&gt;
|0.0&lt;br /&gt;
|0.0&lt;br /&gt;
|-&lt;br /&gt;
|0.667&lt;br /&gt;
|0.0&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|0.5&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
You can add as many entries as you need. Interpolation tables are often used for gear animations (eg. to open doors during gear-movements and close them again once the gear is either retracted or fully extended).&lt;br /&gt;
&lt;br /&gt;
=== Expressions ===&lt;br /&gt;
For some animations it is possible to define complex animations by using [[Expressions|Expressions]]. This even allows to drive the animation from multiple properties without the need for additional Nasal scripts. Here is an example for a translate animation depending on two properties and the cosine function:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt;&lt;br /&gt;
     &amp;lt;type&amp;gt;translate&amp;lt;/type&amp;gt;&lt;br /&gt;
     &amp;lt;expression&amp;gt;&lt;br /&gt;
       &amp;lt;product&amp;gt;&lt;br /&gt;
         &amp;lt;property&amp;gt;/my/factor-property&amp;lt;/property&amp;gt;&lt;br /&gt;
         &amp;lt;cos&amp;gt;&lt;br /&gt;
           &amp;lt;deg2rad&amp;gt;&lt;br /&gt;
             &amp;lt;property&amp;gt;/my/angular-property&amp;lt;/property&amp;gt;&lt;br /&gt;
           &amp;lt;/deg2rad&amp;gt;&lt;br /&gt;
         &amp;lt;/cos&amp;gt;&lt;br /&gt;
       &amp;lt;/product&amp;gt;&lt;br /&gt;
     &amp;lt;/expression&amp;gt;&lt;br /&gt;
     [..]more elements[..]&lt;br /&gt;
 &amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Animations which can utilize [[Expressions|Expressions]] are: &lt;br /&gt;
* [[Howto:Animate_models#Translate|Translate]]&lt;br /&gt;
* [[Howto:Animate_models#Rotate|Rotate]]&lt;br /&gt;
* [[Howto:Animate_models#Scale|Scale]]&lt;br /&gt;
* [[Howto:Animate_models#Range|Range]]&lt;br /&gt;
* [[Howto:Animate_models#Blend|Blend]]&lt;br /&gt;
&lt;br /&gt;
See more detailed info at [[Expressions|Expressions]]&lt;br /&gt;
&lt;br /&gt;
== Lights ==&lt;br /&gt;
As of January 2021 FlightGear supports multiple light sources just like Project Rembrandt has always done.&lt;br /&gt;
[[Compositor#Lights|Adding lights to a model]]&lt;br /&gt;
&lt;br /&gt;
== Object animations ==&lt;br /&gt;
=== Alpha-test ===&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;type&amp;gt;alpha-test&amp;lt;/type&amp;gt;&lt;br /&gt;
  &amp;lt;object-name&amp;gt;Object&amp;lt;/object-name&amp;gt;&lt;br /&gt;
  &amp;lt;alpha-factor&amp;gt;0.01&amp;lt;/alpha-factor&amp;gt;&lt;br /&gt;
 &amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
This &amp;quot;animation&amp;quot; is a way to set an alpha test on a model branch. The effect is to avoid depth buffer writing of pixel that are not seen because they are transparent. This is particulary useful when modeling a metallic structure or a tree with a billboard. The threshold of transparency is set with the &amp;lt;alpha-factor&amp;gt; element.  See also [[Pixel testing in effects]].&lt;br /&gt;
&lt;br /&gt;
=== Blend ===&lt;br /&gt;
Blends an object with the surrounding. Comparable to a translucency animation. A value of 0 corresponds to no transparency, i.e. and ordinary solid object, and a value of 1 makes the object fully transparent, i.e., not visible at all.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;type&amp;gt;blend&amp;lt;/type&amp;gt;&lt;br /&gt;
  &amp;lt;property&amp;gt;/velocities/airspeed-kt&amp;lt;/property&amp;gt;&lt;br /&gt;
  &amp;lt;factor&amp;gt;0.00025&amp;lt;/factor&amp;gt;&lt;br /&gt;
  &amp;lt;min&amp;gt;0.2&amp;lt;/min&amp;gt;&lt;br /&gt;
  &amp;lt;max&amp;gt;0.7&amp;lt;/max&amp;gt;&lt;br /&gt;
 &amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* '''property:'''&lt;br /&gt;
* '''factor:'''&lt;br /&gt;
* '''min:'''&lt;br /&gt;
* '''max:'''&lt;br /&gt;
* '''[[Howto:Animate_models#Expressions|expression]]:''' is optional. For more details see [[Expressions|Expressions]]&lt;br /&gt;
&lt;br /&gt;
Note that when using the Project Rembrandt renderer, all transparent and translucent objects must be registered to display properly.  [[Project_Rembrandt#Registering_all_translucent_surfaces|More information here.]]&lt;br /&gt;
&lt;br /&gt;
=== Billboard ===&lt;br /&gt;
This faces an object towards the viewer. Often used on 2D objects, like clouds, trees and lights.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;type&amp;gt;billboard&amp;lt;/type&amp;gt;&lt;br /&gt;
  &amp;lt;object-name&amp;gt;Object&amp;lt;/object-name&amp;gt;&lt;br /&gt;
  &amp;lt;spherical type=&amp;quot;bool&amp;quot;&amp;gt;true&amp;lt;/spherical&amp;gt;&lt;br /&gt;
 &amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* '''spherical:'''&lt;br /&gt;
&lt;br /&gt;
=== Dist-scale ===&lt;br /&gt;
Used to scale an object, based on the distance to the viewer. &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;&amp;lt;ind&amp;gt;&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;&amp;lt;dep&amp;gt;&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; (independent and dependent) are the distance in meters and the scale at that distance.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;type&amp;gt;dist-scale&amp;lt;/type&amp;gt;&lt;br /&gt;
  &amp;lt;object-name&amp;gt;Object&amp;lt;/object-name&amp;gt;&lt;br /&gt;
  &amp;lt;interpolation&amp;gt;&lt;br /&gt;
   &amp;lt;entry&amp;gt;&lt;br /&gt;
    &amp;lt;ind&amp;gt;0&amp;lt;/ind&amp;gt;&lt;br /&gt;
    &amp;lt;dep&amp;gt;1&amp;lt;/dep&amp;gt;&lt;br /&gt;
   &amp;lt;/entry&amp;gt;&lt;br /&gt;
   &amp;lt;entry&amp;gt;&lt;br /&gt;
    &amp;lt;ind&amp;gt;300&amp;lt;/ind&amp;gt;&lt;br /&gt;
    &amp;lt;dep&amp;gt;4&amp;lt;/dep&amp;gt;&lt;br /&gt;
   &amp;lt;/entry&amp;gt;&lt;br /&gt;
   &amp;lt;entry&amp;gt;&lt;br /&gt;
    &amp;lt;ind&amp;gt;1500&amp;lt;/ind&amp;gt;&lt;br /&gt;
    &amp;lt;dep&amp;gt;8&amp;lt;/dep&amp;gt;&lt;br /&gt;
   &amp;lt;/entry&amp;gt;&lt;br /&gt;
  &amp;lt;/interpolation&amp;gt;&lt;br /&gt;
 &amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can optionally add [[#Center|&amp;amp;lt;center&amp;amp;gt;]] coordinates, to scale the object around that point.&lt;br /&gt;
&lt;br /&gt;
=== Flash ===&lt;br /&gt;
&lt;br /&gt;
Used to scale an object based on the cosine of the angle between the axis provided in the animation and the view vector.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;type&amp;gt;flash&amp;lt;/type&amp;gt;&lt;br /&gt;
  &amp;lt;object-name&amp;gt;Object&amp;lt;/object-name&amp;gt;&lt;br /&gt;
  &amp;lt;offset&amp;gt;0.0&amp;lt;/offset&amp;gt;&lt;br /&gt;
  &amp;lt;factor&amp;gt;1.0&amp;lt;/factor&amp;gt;&lt;br /&gt;
  &amp;lt;power&amp;gt;2&amp;lt;/power&amp;gt;&lt;br /&gt;
  &amp;lt;two-sides type=&amp;quot;bool&amp;quot;&amp;gt;false&amp;lt;/two-sides&amp;gt;&lt;br /&gt;
  &amp;lt;min&amp;gt;0.0&amp;lt;/min&amp;gt;&lt;br /&gt;
  &amp;lt;max&amp;gt;1.0&amp;lt;/max&amp;gt;&lt;br /&gt;
  &amp;lt;center&amp;gt;&lt;br /&gt;
   &amp;lt;x-m&amp;gt;0.0&amp;lt;/x-m&amp;gt;&lt;br /&gt;
   &amp;lt;y-m&amp;gt;0.0&amp;lt;/y-m&amp;gt;&lt;br /&gt;
   &amp;lt;z-m&amp;gt;0.0&amp;lt;/z-m&amp;gt;&lt;br /&gt;
  &amp;lt;/center&amp;gt;&lt;br /&gt;
  &amp;lt;axis&amp;gt;&lt;br /&gt;
   &amp;lt;x&amp;gt;0.0&amp;lt;/x&amp;gt;&lt;br /&gt;
   &amp;lt;y&amp;gt;-1&amp;lt;/y&amp;gt;&lt;br /&gt;
   &amp;lt;z&amp;gt;0.1&amp;lt;/z&amp;gt;&lt;br /&gt;
  &amp;lt;/axis&amp;gt;&lt;br /&gt;
 &amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* '''offset:'''&lt;br /&gt;
* '''factor:'''&lt;br /&gt;
* '''power:'''&lt;br /&gt;
* '''two-sides:''' if false, nothing is drawn if the cosine is negative.&lt;br /&gt;
* '''min:'''&lt;br /&gt;
* '''max:'''&lt;br /&gt;
&lt;br /&gt;
scale = factor * pow( cosine, power ) + offset&lt;br /&gt;
&lt;br /&gt;
scale is then clamped between min and max.&lt;br /&gt;
&lt;br /&gt;
and this scale factor is applied to the object, from the center specified. It works best if scale is less than 1. Otherwise, there will be clipping issues.&lt;br /&gt;
&lt;br /&gt;
=== Noshadow ===&lt;br /&gt;
This animation is used to make sure an object will cast no shadow.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;type&amp;gt;noshadow&amp;lt;/type&amp;gt;&lt;br /&gt;
  &amp;lt;object-name&amp;gt;Object&amp;lt;/object-name&amp;gt;&lt;br /&gt;
 &amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Range ===&lt;br /&gt;
: ''See also [[Modeling - Getting Started#Level of Detail (LOD)]].''&lt;br /&gt;
&lt;br /&gt;
To prevent objects -like instruments- being drawn when the aircraft is actually too far away for them to be seen anyway, a range animation is used. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;type&amp;gt;range&amp;lt;/type&amp;gt;&lt;br /&gt;
  &amp;lt;min-m&amp;gt;0&amp;lt;/min-m&amp;gt;&lt;br /&gt;
  &amp;lt;max-m&amp;gt;30&amp;lt;/max-m&amp;gt;&lt;br /&gt;
 &amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* '''min-m:''' the shortest distance (in meters) from the object center at which it is visible.&lt;br /&gt;
* '''max-m:''' the largest distance (in meters) from the object center at which it is visible.&lt;br /&gt;
&lt;br /&gt;
You could also use the generic level of detail (LOD) properties, which can be set by the user through View &amp;gt; Adjust LOD rangers: &lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Property&lt;br /&gt;
! Description&lt;br /&gt;
! Default value&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;tt&amp;gt;/sim/rendering/static-lod/bare&amp;lt;/tt&amp;gt;&lt;br /&gt;
| only a rough exterior model&lt;br /&gt;
| 30,000 m&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;tt&amp;gt;/sim/rendering/static-lod/rough&amp;lt;/tt&amp;gt; &lt;br /&gt;
| most should be visible&lt;br /&gt;
| 9,000 m&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;tt&amp;gt;/sim/rendering/static-lod/detailed&amp;lt;/tt&amp;gt; &lt;br /&gt;
| all details should be visible&lt;br /&gt;
| 1,500 m&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The animation code will look like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;type&amp;gt;range&amp;lt;/type&amp;gt;&lt;br /&gt;
  &amp;lt;min-m&amp;gt;0&amp;lt;/min-m&amp;gt;&lt;br /&gt;
  &amp;lt;max-property&amp;gt;sim/rendering/static-lod/bare&amp;lt;/max-property&amp;gt;&lt;br /&gt;
 &amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can have both ranges (max and min) bound to a property, or just one of them.&lt;br /&gt;
* '''min-property:''' &lt;br /&gt;
* '''max-property:'''&lt;br /&gt;
* '''[[Howto:Animate_models#Expressions|expression]]:''' is optional. For more details see [[Expressions|Expressions]]&lt;br /&gt;
&lt;br /&gt;
=== Rotate ===&lt;br /&gt;
One of the most important and frequently used animations of all. It rotates an object to an absolute position in degrees, as provided by the property-value.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;object-name&amp;gt;Object&amp;lt;/object-name&amp;gt;&lt;br /&gt;
  &amp;lt;type&amp;gt;rotate&amp;lt;/type&amp;gt;&lt;br /&gt;
  &amp;lt;property&amp;gt;surface-positions/left-aileron-pos-norm&amp;lt;/property&amp;gt;&lt;br /&gt;
  &amp;lt;factor&amp;gt;25&amp;lt;/factor&amp;gt;&lt;br /&gt;
  &amp;lt;offset-deg&amp;gt;25&amp;lt;/offset-deg&amp;gt;&lt;br /&gt;
  &amp;lt;center&amp;gt;&lt;br /&gt;
   &amp;lt;x-m&amp;gt;-1.50&amp;lt;/x-m&amp;gt;&lt;br /&gt;
   &amp;lt;y-m&amp;gt; 1   &amp;lt;/y-m&amp;gt;&lt;br /&gt;
   &amp;lt;z-m&amp;gt; 0.25&amp;lt;/z-m&amp;gt;&lt;br /&gt;
  &amp;lt;/center&amp;gt;&lt;br /&gt;
  &amp;lt;axis&amp;gt;&lt;br /&gt;
   &amp;lt;x&amp;gt;0&amp;lt;/x&amp;gt;&lt;br /&gt;
   &amp;lt;y&amp;gt;1&amp;lt;/y&amp;gt;&lt;br /&gt;
   &amp;lt;z&amp;gt;0&amp;lt;/z&amp;gt;&lt;br /&gt;
  &amp;lt;/axis&amp;gt;&lt;br /&gt;
 &amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* '''factor:''' is optional.&lt;br /&gt;
* '''offset-deg:''' is optional. Offset in degrees.&lt;br /&gt;
* '''[[Howto:Animate_models#Expressions|expression]]:''' is optional. For more details see [[Expressions|Expressions]]&lt;br /&gt;
&lt;br /&gt;
=== Scale ===&lt;br /&gt;
A scale animation scales (resizes) an object. This can be either property-value dependant (first example) or a fixed scale (second example).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;type&amp;gt;scale&amp;lt;/type&amp;gt;&lt;br /&gt;
  &amp;lt;object-name&amp;gt;Object&amp;lt;/object-name&amp;gt;&lt;br /&gt;
  &amp;lt;property&amp;gt;sim/time/sun-angle-rad&amp;lt;/property&amp;gt;&lt;br /&gt;
  &amp;lt;x-min&amp;gt;1.0&amp;lt;/x-min&amp;gt;&lt;br /&gt;
  &amp;lt;y-min&amp;gt;1.0&amp;lt;/y-min&amp;gt;&lt;br /&gt;
  &amp;lt;z-min&amp;gt;1.0&amp;lt;/z-min&amp;gt;&lt;br /&gt;
  &amp;lt;x-factor&amp;gt;1.4&amp;lt;/x-factor&amp;gt;&lt;br /&gt;
  &amp;lt;y-factor&amp;gt;1.4&amp;lt;/y-factor&amp;gt;&lt;br /&gt;
  &amp;lt;z-factor&amp;gt;2.0&amp;lt;/z-factor&amp;gt;&lt;br /&gt;
 &amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* ?-min: the mimimum scale factor for each axis. If the property value would result in a smaller factor than this setting, the scale animation will hold.&lt;br /&gt;
* ?-factor: the scale factor for each axis (factor*property=scale factor).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;type&amp;gt;scale&amp;lt;/type&amp;gt;&lt;br /&gt;
  &amp;lt;x-offset&amp;gt;0.5&amp;lt;/x-offset&amp;gt;&lt;br /&gt;
  &amp;lt;y-offset&amp;gt;0.5&amp;lt;/y-offset&amp;gt;&lt;br /&gt;
  &amp;lt;z-offset&amp;gt;0.5&amp;lt;/z-offset&amp;gt;&lt;br /&gt;
 &amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* x.offset: the scale factor.&lt;br /&gt;
* Add [[#Center|&amp;amp;lt;center&amp;amp;gt;]] coordinates, to scale the object around that point.&lt;br /&gt;
* '''You can optionally use an [[Howto:Animate_models#Expressions|expression]] in the &amp;lt;factor&amp;gt; or &amp;lt;offset&amp;gt; inputs.''' For more details see [[Expressions|Expressions]]&lt;br /&gt;
&lt;br /&gt;
=== Select ===&lt;br /&gt;
This animation selects (or unselects) objects when certain conditions are true (or false). The example below shows the object when the n1 of engine[1] is higher than 25%.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;object-name&amp;gt;Object&amp;lt;/object-name&amp;gt;&lt;br /&gt;
  &amp;lt;type&amp;gt;select&amp;lt;/type&amp;gt;&lt;br /&gt;
  &amp;lt;condition&amp;gt;&lt;br /&gt;
   &amp;lt;greater-than&amp;gt;&lt;br /&gt;
    &amp;lt;property&amp;gt;engines/engine[0]/n1&amp;lt;/property&amp;gt;&lt;br /&gt;
    &amp;lt;value&amp;gt;25&amp;lt;/value&amp;gt;&lt;br /&gt;
   &amp;lt;/greater-than&amp;gt;&lt;br /&gt;
  &amp;lt;/condition&amp;gt;&lt;br /&gt;
 &amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Shader ===&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;type&amp;gt;shader&amp;lt;/type&amp;gt;&lt;br /&gt;
  &amp;lt;shader&amp;gt;chrome&amp;lt;/shader&amp;gt;&lt;br /&gt;
  &amp;lt;texture&amp;gt;chrome2.png&amp;lt;/texture&amp;gt;&lt;br /&gt;
  &amp;lt;object-name&amp;gt;Object&amp;lt;/object-name&amp;gt;&lt;br /&gt;
 &amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* '''shader:''' &lt;br /&gt;
* '''texture:''' path to the texture used by the shader.&lt;br /&gt;
&lt;br /&gt;
=== Spin ===&lt;br /&gt;
Very similar to [[#Rotate|rotate]], but the property provides a value in revolutions per minute (RPM) rather than an absolute position in degrees, and offset cannot be used.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;object-name&amp;gt;Object&amp;lt;/object-name&amp;gt;&lt;br /&gt;
  &amp;lt;type&amp;gt;spin&amp;lt;/type&amp;gt;&lt;br /&gt;
  &amp;lt;property&amp;gt;engines/engine[0]/n1&amp;lt;/property&amp;gt;&lt;br /&gt;
  &amp;lt;factor&amp;gt;25&amp;lt;/factor&amp;gt;&lt;br /&gt;
  &amp;lt;center&amp;gt;&lt;br /&gt;
   &amp;lt;x-m&amp;gt;-1.50&amp;lt;/x-m&amp;gt;&lt;br /&gt;
   &amp;lt;y-m&amp;gt; 1   &amp;lt;/y-m&amp;gt;&lt;br /&gt;
   &amp;lt;z-m&amp;gt; 0.25&amp;lt;/z-m&amp;gt;&lt;br /&gt;
  &amp;lt;/center&amp;gt;&lt;br /&gt;
  &amp;lt;axis&amp;gt;&lt;br /&gt;
   &amp;lt;x&amp;gt;0&amp;lt;/x&amp;gt;&lt;br /&gt;
   &amp;lt;y&amp;gt;1&amp;lt;/y&amp;gt;&lt;br /&gt;
   &amp;lt;z&amp;gt;0&amp;lt;/z&amp;gt;&lt;br /&gt;
  &amp;lt;/axis&amp;gt;&lt;br /&gt;
 &amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* '''factor:''' is optional.&lt;br /&gt;
&lt;br /&gt;
=== Timed ===&lt;br /&gt;
Swtiches between objects at specified intervals. This example switches between a lights-on model and a lights-off model. Lights on are shown 0.2 seconds, while lights off are displayed for 0.8 seconds.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;type&amp;gt;timed&amp;lt;/type&amp;gt;&lt;br /&gt;
  &amp;lt;object-name&amp;gt;BacklightOn&amp;lt;/object-name&amp;gt;&lt;br /&gt;
  &amp;lt;object-name&amp;gt;BacklightOff&amp;lt;/object-name&amp;gt;&lt;br /&gt;
  &amp;lt;use-personality type=&amp;quot;bool&amp;quot;&amp;gt;true&amp;lt;/use-personality&amp;gt;&lt;br /&gt;
  &amp;lt;branch-duration-sec&amp;gt;0.8&amp;lt;/branch-duration-sec&amp;gt;&lt;br /&gt;
  &amp;lt;branch-duration-sec&amp;gt;0.2&amp;lt;/branch-duration-sec&amp;gt;&lt;br /&gt;
 &amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Tracking ===&lt;br /&gt;
A ''locked-track animation'' can do exactly the same thing as the [https://docs.blender.org/manual/en/latest/animation/constraints/tracking/locked_track.html Locked Track constraint] available in Blender and can also be used to simulate simple inverse kinematic systems consisting of two bones connected with a revolute joint (aka hinge). For details see: [[Howto:Animate gear scissors using the tracking animation]]&lt;br /&gt;
&lt;br /&gt;
=== Translate ===&lt;br /&gt;
This animation moves an object along an axis (not to be confused with the &amp;lt;code&amp;gt;textranslate&amp;lt;/code&amp;gt; animation which only moves the texture on the UV map, not the geometry). The example below will move an object in the Y direction:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;0&amp;quot; cellspacing=&amp;quot;0&amp;quot; &lt;br /&gt;
!Property value&lt;br /&gt;
!Output&lt;br /&gt;
|-&lt;br /&gt;
| -1&lt;br /&gt;
| -2.5&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| 2.5&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 7.5&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;type&amp;gt;translate&amp;lt;/type&amp;gt;&lt;br /&gt;
  &amp;lt;object-name&amp;gt;Object&amp;lt;/object-name&amp;gt;&lt;br /&gt;
  &amp;lt;property&amp;gt;controls/seat/pilot/position-norm&amp;lt;/property&amp;gt;&lt;br /&gt;
  &amp;lt;factor&amp;gt;5&amp;lt;/factor&amp;gt;&lt;br /&gt;
  &amp;lt;offset-m&amp;gt;2.5&amp;lt;/offset-m&amp;gt;&lt;br /&gt;
  &amp;lt;axis&amp;gt;&lt;br /&gt;
   &amp;lt;x&amp;gt;0&amp;lt;/x&amp;gt;&lt;br /&gt;
   &amp;lt;y&amp;gt;1&amp;lt;/y&amp;gt;&lt;br /&gt;
   &amp;lt;z&amp;gt;0&amp;lt;/z&amp;gt;&lt;br /&gt;
  &amp;lt;/axis&amp;gt;&lt;br /&gt;
 &amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When using interpolation tables such as:&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
&amp;lt;interpolation&amp;gt;&lt;br /&gt;
	&amp;lt;entry&amp;gt;&amp;lt;ind&amp;gt;0.0&amp;lt;/ind&amp;gt;&amp;lt;dep&amp;gt; 0&amp;lt;/dep&amp;gt;&amp;lt;/entry&amp;gt;&lt;br /&gt;
	&amp;lt;entry&amp;gt;&amp;lt;ind&amp;gt;0.666&amp;lt;/ind&amp;gt;&amp;lt;dep&amp;gt;0.18&amp;lt;/dep&amp;gt;&amp;lt;/entry&amp;gt;&lt;br /&gt;
	&amp;lt;entry&amp;gt;&amp;lt;ind&amp;gt;1.0&amp;lt;/ind&amp;gt;&amp;lt;dep&amp;gt;0.27&amp;lt;/dep&amp;gt;&amp;lt;/entry&amp;gt;&lt;br /&gt;
&amp;lt;/interpolation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
The first figure (&amp;lt;code&amp;gt;&amp;lt;ind&amp;gt;&amp;lt;/code&amp;gt;) refers to the value of the property associated with the object and the second figure (&amp;lt;code&amp;gt;&amp;lt;dep&amp;gt;&amp;lt;/code&amp;gt;) refers to the amount that the object moves in metres. For example, in this case, the object moves 18cm when the value reads 0.66 and 27cm when the value is 1.&lt;br /&gt;
&lt;br /&gt;
'''IF used WTIHOUT property''' : The object is placed at some (Value of Offset) meters away from its original position, along the virtual axis formed by said original position and the point with coordinates x/y/z defined in the &amp;lt;axis&amp;gt; tag.&lt;br /&gt;
Mathematically, assuming the Object to translate is (in the model space) placed at point A (x1, y1, z1) and you want to relocate it to point B (x2, y2, z2) then x2,y2,z2 are the values in the &amp;lt;axis&amp;gt; tag of the Translate animation and &amp;lt;offset&amp;gt; can be computed as SQRT((x2-x1)^2+(y2-y1)^2+(z2-z1)^2)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* '''factor:''' is optional.&lt;br /&gt;
* '''offset-m:''' is optional. Offset in meters.&lt;br /&gt;
* '''[[Howto:Animate_models#Expressions|expression]]:''' is optional. For more details see [[Expressions|Expressions]]&lt;br /&gt;
&lt;br /&gt;
== Material animation ==&lt;br /&gt;
Animate the material properties of an object, such as it's texture, shininess or color.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt; &lt;br /&gt;
  &amp;lt;type&amp;gt;material&amp;lt;/type&amp;gt; &lt;br /&gt;
  &amp;lt;object-name&amp;gt;Object&amp;lt;/object-name&amp;gt;&lt;br /&gt;
  &amp;lt;property-base&amp;gt;sim/model/c172p/material&amp;lt;/property-base&amp;gt;&lt;br /&gt;
  ...&lt;br /&gt;
  lines as mentioned below&lt;br /&gt;
  ...&lt;br /&gt;
 &amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Optional:'''&lt;br /&gt;
* '''property-base:''' All other properties of this animation will be relative to this property node.&lt;br /&gt;
&lt;br /&gt;
'''Notes:'''&lt;br /&gt;
* Numbers are clamped to 0.0 - 1.0, except &amp;lt;code&amp;gt;shininess&amp;lt;/code&amp;gt;, which is clamped to 0 - 128.&lt;br /&gt;
* By appending &amp;lt;tt&amp;gt;-prop&amp;lt;/tt&amp;gt; each of the material properties can read its value from another property.&lt;br /&gt;
* '''New since SimGear commit &amp;lt;code&amp;gt;51149bf&amp;lt;/code&amp;gt;''': For each of the color components (&amp;lt;code&amp;gt;ambient&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;diffuse&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;emission&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;specular&amp;lt;/code&amp;gt;), the &amp;lt;code&amp;gt;red&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;green&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;blue&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;factor&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;offset&amp;lt;/code&amp;gt; tags can each contain an [[Expressions|expression]] instead of a value.&lt;br /&gt;
&lt;br /&gt;
=== Ambient ===&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
  &amp;lt;ambient&amp;gt;&lt;br /&gt;
   &amp;lt;red&amp;gt;1.0&amp;lt;/red&amp;gt;&lt;br /&gt;
   &amp;lt;green&amp;gt;0.2&amp;lt;/green&amp;gt;&lt;br /&gt;
   &amp;lt;blue&amp;gt;0.0&amp;lt;/blue&amp;gt;&lt;br /&gt;
  &amp;lt;/ambient&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Diffuse ===&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
  &amp;lt;diffuse&amp;gt;&lt;br /&gt;
   &amp;lt;red&amp;gt;1.0&amp;lt;/red&amp;gt;&lt;br /&gt;
   &amp;lt;green&amp;gt;0.2&amp;lt;/green&amp;gt;&lt;br /&gt;
   &amp;lt;blue&amp;gt;0.0&amp;lt;/blue&amp;gt;&lt;br /&gt;
  &amp;lt;/diffuse&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Emission ===&lt;br /&gt;
{{Main article|Howto: Illuminate faces}}&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
  &amp;lt;emission&amp;gt;&lt;br /&gt;
   &amp;lt;red&amp;gt;1.0&amp;lt;/red&amp;gt;&lt;br /&gt;
   &amp;lt;green&amp;gt;0.2&amp;lt;/green&amp;gt;&lt;br /&gt;
   &amp;lt;blue&amp;gt;0.0&amp;lt;/blue&amp;gt;&lt;br /&gt;
   &amp;lt;factor-prop&amp;gt;controls/lighting/panel-norm&amp;lt;/factor-prop&amp;gt;&lt;br /&gt;
  &amp;lt;/emission&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Emission colors are multiplied by the factor-prop value. 1 is maximum color intensity, while 0 is the minimum. Colors are calculated according to the [http://en.wikipedia.org/wiki/RGB_color_model RGB color model].&lt;br /&gt;
&lt;br /&gt;
=== Specular ===&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
  &amp;lt;specular&amp;gt;&lt;br /&gt;
   &amp;lt;red&amp;gt;1.0&amp;lt;/red&amp;gt;&lt;br /&gt;
   &amp;lt;green&amp;gt;0.2&amp;lt;/green&amp;gt;&lt;br /&gt;
   &amp;lt;blue&amp;gt;0.0&amp;lt;/blue&amp;gt;&lt;br /&gt;
  &amp;lt;/specular&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Transparency ===&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
  &amp;lt;transparency&amp;gt;&lt;br /&gt;
   &amp;lt;alpha-prop&amp;gt;rotors/tail/rpm&amp;lt;/alpha-prop&amp;gt;&lt;br /&gt;
   &amp;lt;factor&amp;gt;-0.0015&amp;lt;/factor&amp;gt;&lt;br /&gt;
   &amp;lt;offset&amp;gt;1&amp;lt;/offset&amp;gt;&lt;br /&gt;
  &amp;lt;/transparency&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Shininess ===&lt;br /&gt;
Shininess is clamped to 0-128.&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
  &amp;lt;shininess&amp;gt;105&amp;lt;/shininess&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Threshold ===&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
  &amp;lt;threshold&amp;gt;0.001&amp;lt;/threshold&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Texture ===&lt;br /&gt;
Used for the [[Livery over MP]] system.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
  &amp;lt;property-base&amp;gt;sim/model/livery&amp;lt;/property-base&amp;gt; &lt;br /&gt;
  &amp;lt;texture-prop&amp;gt;engine&amp;lt;/texture-prop&amp;gt; &lt;br /&gt;
  &amp;lt;texture&amp;gt;KLM.png&amp;lt;/texture&amp;gt; &lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Texture Animations ==&lt;br /&gt;
Applying different matrix transformations to the textures of an object.&lt;br /&gt;
&lt;br /&gt;
=== Textranslate ===&lt;br /&gt;
A very important animation for cockpits! This animation moves textures over a surface.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;type&amp;gt;textranslate&amp;lt;/type&amp;gt;&lt;br /&gt;
  &amp;lt;object-name&amp;gt;Object&amp;lt;/object-name&amp;gt;&lt;br /&gt;
  &amp;lt;property&amp;gt;autopilot/settings/target-speed-kt&amp;lt;/property&amp;gt;&lt;br /&gt;
  &amp;lt;bias&amp;gt;0.0001&amp;lt;/bias&amp;gt;&lt;br /&gt;
  &amp;lt;factor&amp;gt;0.001&amp;lt;/factor&amp;gt;&lt;br /&gt;
  &amp;lt;step&amp;gt;100&amp;lt;/step&amp;gt;&lt;br /&gt;
  &amp;lt;axis&amp;gt;&lt;br /&gt;
   &amp;lt;x&amp;gt;0&amp;lt;/x&amp;gt;&lt;br /&gt;
   &amp;lt;y&amp;gt;1&amp;lt;/y&amp;gt;&lt;br /&gt;
  &amp;lt;/axis&amp;gt;&lt;br /&gt;
 &amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* '''bias:''' Adds an offset to the property before factor/step. A small value is needed to compensate for [http://en.wikipedia.org/wiki/Floating_point#Accuracy_problems floating point accuracy].&lt;br /&gt;
* '''factor:''' property * factor * texture width/height = the amount of pixels that the texture should be translated. If your texture is 256 pixels, an textranslate of 0.1 will result in the texture moving with 26 pixels, into the direction specified by the axis settings.&lt;br /&gt;
* '''step:''' the step size at which the texture is translated. If this is set to 0.1, the texture will only be translated at 0.1, 0.2, 0.3 etc.&lt;br /&gt;
* '''axis:''' the direction in which the texture is translated. Y is up/down, while X is left/right.&lt;br /&gt;
&lt;br /&gt;
=== Texrotate ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
&amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;object-name&amp;gt;Object&amp;lt;/object-name&amp;gt;&lt;br /&gt;
  &amp;lt;type&amp;gt;texrotate&amp;lt;/type&amp;gt;&lt;br /&gt;
  &amp;lt;property&amp;gt;some/property/path&amp;lt;/property&amp;gt;&lt;br /&gt;
  &amp;lt;factor&amp;gt;25&amp;lt;/factor&amp;gt;&lt;br /&gt;
  &amp;lt;offset-deg&amp;gt;25&amp;lt;/offset-deg&amp;gt;&lt;br /&gt;
  &amp;lt;center&amp;gt;&lt;br /&gt;
    &amp;lt;x&amp;gt;0.5&amp;lt;/x&amp;gt;&lt;br /&gt;
    &amp;lt;y&amp;gt;0.5&amp;lt;/y&amp;gt;&lt;br /&gt;
    &amp;lt;z&amp;gt;0&amp;lt;/z&amp;gt;&lt;br /&gt;
  &amp;lt;/center&amp;gt;&lt;br /&gt;
  &amp;lt;axis&amp;gt;&lt;br /&gt;
    &amp;lt;x&amp;gt;0&amp;lt;/x&amp;gt;&lt;br /&gt;
    &amp;lt;y&amp;gt;0&amp;lt;/y&amp;gt;&lt;br /&gt;
    &amp;lt;z&amp;gt;1&amp;lt;/z&amp;gt;&lt;br /&gt;
  &amp;lt;/axis&amp;gt;&lt;br /&gt;
&amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Textrapezoid ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
&amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;type&amp;gt;textrapezoid&amp;lt;/type&amp;gt;&lt;br /&gt;
  &amp;lt;object-name&amp;gt;HUD.l.canvas&amp;lt;/object-name&amp;gt;&lt;br /&gt;
  &amp;lt;property&amp;gt;/hud/trapezoid-correction&amp;lt;/property&amp;gt;&lt;br /&gt;
  &amp;lt;side&amp;gt;bottom&amp;lt;/side&amp;gt;&lt;br /&gt;
&amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* '''side''': side of quad which should be scaled (''top'' (default)/''right''/''bottom''/''left'')&lt;br /&gt;
&lt;br /&gt;
=== Texmultiple ===&lt;br /&gt;
&lt;br /&gt;
Only one texture matrix can be applied to each object. With ''textmultiple'' multiple texture animations can be combined into a single matrix, applied to the specified object.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
&amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;type&amp;gt;texmultiple&amp;lt;/type&amp;gt;&lt;br /&gt;
  &amp;lt;object-name&amp;gt;HUD.l.canvas&amp;lt;/object-name&amp;gt;&lt;br /&gt;
  &amp;lt;transform&amp;gt;&lt;br /&gt;
    &amp;lt;subtype&amp;gt;textranslate&amp;lt;/subtype&amp;gt;&lt;br /&gt;
    &amp;lt;property&amp;gt;/hud/offset-x&amp;lt;/property&amp;gt;&lt;br /&gt;
    &amp;lt;axis&amp;gt;&lt;br /&gt;
     &amp;lt;x&amp;gt;1&amp;lt;/x&amp;gt;&lt;br /&gt;
     &amp;lt;y&amp;gt;0&amp;lt;/y&amp;gt;&lt;br /&gt;
     &amp;lt;z&amp;gt;0&amp;lt;/z&amp;gt;&lt;br /&gt;
   &amp;lt;/axis&amp;gt;&lt;br /&gt;
  &amp;lt;/transform&amp;gt;&lt;br /&gt;
  &amp;lt;transform&amp;gt;&lt;br /&gt;
    &amp;lt;subtype&amp;gt;textranslate&amp;lt;/subtype&amp;gt;&lt;br /&gt;
    &amp;lt;property&amp;gt;/hud/offset-y&amp;lt;/property&amp;gt;&lt;br /&gt;
    &amp;lt;axis&amp;gt;&lt;br /&gt;
     &amp;lt;x&amp;gt;0&amp;lt;/x&amp;gt;&lt;br /&gt;
     &amp;lt;y&amp;gt;1&amp;lt;/y&amp;gt;&lt;br /&gt;
     &amp;lt;z&amp;gt;0&amp;lt;/z&amp;gt;&lt;br /&gt;
   &amp;lt;/axis&amp;gt;&lt;br /&gt;
  &amp;lt;/transform&amp;gt;&lt;br /&gt;
  &amp;lt;transform&amp;gt;&lt;br /&gt;
    &amp;lt;subtype&amp;gt;textrapezoid&amp;lt;/subtype&amp;gt;&lt;br /&gt;
    &amp;lt;property&amp;gt;/hud/trapezoid-correction&amp;lt;/property&amp;gt;&lt;br /&gt;
  &amp;lt;/transform&amp;gt;&lt;br /&gt;
&amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Object interaction animations ==&lt;br /&gt;
=== Enable-hot ===&lt;br /&gt;
Scenery objects are automatically defined as solid by FlightGear, meaning that an aircraft can taxi on them and/or crash when touching. For certain objects (groundmarkings, beacon light-beams etc.) this might be an unwanted feature. The solidness can be disabled with the following animation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;object-name&amp;gt;Object&amp;lt;/object-name&amp;gt;&lt;br /&gt;
  &amp;lt;enable-hot type=&amp;quot;bool&amp;quot;&amp;gt;false&amp;lt;/enable-hot&amp;gt;&lt;br /&gt;
 &amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* '''enable-hot:''' can be either true or false. Remember that objects are automatically solid, so it should not be necessary to set this at all when wanting solidness.&lt;br /&gt;
&lt;br /&gt;
=== Interactions ===&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt; &lt;br /&gt;
  &amp;lt;type&amp;gt;interaction&amp;lt;/type&amp;gt; &lt;br /&gt;
  &amp;lt;object-name&amp;gt;Object&amp;lt;/object-name&amp;gt; &lt;br /&gt;
  &amp;lt;interaction-type&amp;gt;carrier-wire&amp;lt;/interaction-type&amp;gt; &lt;br /&gt;
 &amp;lt;/animation&amp;gt; &lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* '''interaction-type:''' can have the following values:&lt;br /&gt;
**'''carrier-catapult:'''&lt;br /&gt;
** '''carrier-wire:''' makes the object act as an arresting wire, as used on [[aircraft carrier]]s.&lt;br /&gt;
&lt;br /&gt;
== Direct manipulation animations ==&lt;br /&gt;
=== Knob / slider (v. 2.11-) ===&lt;br /&gt;
{{Main article|Knob / slider animation}}&lt;br /&gt;
&lt;br /&gt;
=== Pick ===&lt;br /&gt;
{{Main article|Howto: Make a clickable panel#Pick}}&lt;br /&gt;
&lt;br /&gt;
=== Touch ===&lt;br /&gt;
&lt;br /&gt;
The touch animation provides the normalized coordinates of a touch (or click) event on a 2d surface. The coordinates are passed in the argument and can be accessed using cmdarg() in Nasal.&lt;br /&gt;
&lt;br /&gt;
* Touch animation is designed to work with a quad that is being used as a Canvas placement (display).&lt;br /&gt;
* The touch animation must not be combined with a pick animation on the same object.&lt;br /&gt;
* More info here: [[Touch Animation]]&lt;br /&gt;
&lt;br /&gt;
==== touch example ====&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;animation&amp;gt;&lt;br /&gt;
        &amp;lt;type&amp;gt;touch&amp;lt;/type&amp;gt;&lt;br /&gt;
        &amp;lt;visible&amp;gt;true&amp;lt;/visible&amp;gt;&lt;br /&gt;
        &amp;lt;object-name&amp;gt;CanvasPlacement&amp;lt;/object-name&amp;gt;&lt;br /&gt;
        &amp;lt;action&amp;gt;&lt;br /&gt;
            &amp;lt;touch&amp;gt;0&amp;lt;/touch&amp;gt;&lt;br /&gt;
            &amp;lt;repeatable&amp;gt;false&amp;lt;/repeatable&amp;gt;&lt;br /&gt;
            &amp;lt;binding&amp;gt;&lt;br /&gt;
                &amp;lt;command&amp;gt;nasal&amp;lt;/command&amp;gt;&lt;br /&gt;
                &amp;lt;script&amp;gt;print(&amp;quot;touch input (&amp;quot;,cmdarg().getNode(&amp;quot;x&amp;quot;).getValue(),&amp;quot;,&amp;quot;,cmdarg().getNode(&amp;quot;y&amp;quot;).getValue())&amp;lt;/script&amp;gt;&lt;br /&gt;
            &amp;lt;/binding&amp;gt;&lt;br /&gt;
        &amp;lt;/action&amp;gt;&lt;br /&gt;
    &amp;lt;/animation&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Shadow Handling ==&lt;br /&gt;
There exist several possibilites for handling of shadows. &amp;lt;br /&amp;gt;&lt;br /&gt;
See '''[[ALS_technical_notes|ALS Technical Notes]]''' and more specific '''[[ALS_technical_notes#ALS_fuselage_shadow_effect|Fuselage Shadow Effect with ALS]]''' for a relatively simple shadow handling.&amp;lt;br /&amp;gt;&lt;br /&gt;
See '''[[Project Rembrandt]]''' which - amongst other functionality - implements a very realistic shadow mapping.&lt;br /&gt;
As of January 2021 Project Rembrandt was replaced by the '''[[Compositor]]''' renderer which combines both Project Rembrandt and ALS in a single rendering engine. This also means that the Fuselage Shadow Effect with ALS is now deprecated.&lt;br /&gt;
&lt;br /&gt;
== References ==&lt;br /&gt;
{{Appendix|all|&lt;br /&gt;
* {{cite web |url=http://www.opensubscriber.com/message/flightgear-devel@flightgear.org/958955.html |title=&amp;quot;material&amp;quot; animation (and the bo105 as an example) |first=Melchior |last=Franz |date=22 March 2005 |work=FlightGear-devel mailinglist }}&lt;br /&gt;
* {{cite web |url=http://www.mail-archive.com/flightgear-devel@lists.sourceforge.net/msg01546.html |title=flash animation |first=Frederic |last=Bouvier |date=22 Feb 2006 |work=FlightGear-devel mailinglist }}&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Related content ==&lt;br /&gt;
=== Wiki articles ===&lt;br /&gt;
* [[MP Fallback models]]&lt;br /&gt;
* [[Howto:Animate gear scissors]]&lt;br /&gt;
* [[Howto:Animate helicopters]]&lt;br /&gt;
* [[Howto:Creating 3D instruments]]&lt;br /&gt;
&lt;br /&gt;
=== Forum topics ===&lt;br /&gt;
* {{forum link|t=37353|title=3d models, how to produce them in an understandable way}} (April 2020-) - Touches on the subject of not using LOD range animations in scenery models.&lt;br /&gt;
* {{forum link|t=36545|title=speedo Drum settings}} (November 2019) - Animating a mechanical multi-digit drum counter&lt;br /&gt;
&lt;br /&gt;
[[Category:Aircraft enhancement|Animate models]]&lt;br /&gt;
[[Category:Howto|Animate models]]&lt;br /&gt;
[[Category:Modeling|Animate models]]&lt;br /&gt;
[[Category:Scenery enhancement|Animate models]]&lt;/div&gt;</summary>
		<author><name>TheEagle</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=User:TheEagle&amp;diff=137546</id>
		<title>User:TheEagle</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=User:TheEagle&amp;diff=137546"/>
		<updated>2023-04-12T01:35:26Z</updated>

		<summary type="html">&lt;p&gt;TheEagle: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;I like to fly in FlightGear, create and enhance scenery, aircraft and addons for FlightGear, write Python apps, hack the FG sources …&lt;br /&gt;
&lt;br /&gt;
=== My aircraft ===&lt;br /&gt;
Cessna 210 ([https://github.com/TheFGFSEagle/c210-family GitHub] | [[Cessna 210 Centurion family|Wiki]])&lt;br /&gt;
&lt;br /&gt;
Cessna 310 ([https://github.com/TheFGFSEagle/c310-family GitHub] | [[Cessna 310 family|Wiki]])&lt;br /&gt;
&lt;br /&gt;
Pilatus PC-6 ([https://github.com/TheFGFSEagle/pilatus-pc6-family GitHub] | Wiki)&lt;br /&gt;
&lt;br /&gt;
Cessna 208 ([https://github.com/TheFGFSEagle/c208-family GitHub] | Wiki)&lt;br /&gt;
&lt;br /&gt;
Mudry CAP10 ([https://github.com/TheFGFSEagle/mudry-cap10-family GitHub] | Wiki)&lt;/div&gt;</summary>
		<author><name>TheEagle</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Nasal_HLA_standalone&amp;diff=137530</id>
		<title>Nasal HLA standalone</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Nasal_HLA_standalone&amp;diff=137530"/>
		<updated>2023-04-08T20:28:22Z</updated>

		<summary type="html">&lt;p&gt;TheEagle: Grammar&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Nasal Internals}}&lt;br /&gt;
&lt;br /&gt;
* Last updated: 06/2013&lt;br /&gt;
* Contributors: Dbelcham, Callahanp, Hooray&lt;br /&gt;
&lt;br /&gt;
== Problem ==&lt;br /&gt;
&lt;br /&gt;
One of the things that is most frustrating and time consuming when working with Nasal scripts is the brute force and manual nature of testing the scripts. A simple misspelling in a custom script can take 5-10, or more, minutes to fix from the point of finding it (shut down FG, change script, startup FG and get back to a point in the sim where the code will execute). Obviously Nasal is tightly coupled to FG at this point and most scripts won't run without access to the property tree. That doesn't mean that it can't be done though.&lt;br /&gt;
&lt;br /&gt;
This article will serve as the guinea pig for writing [[Developing with HLA]], using [[FlightGear HLA support (High Level Architecture)]].&lt;br /&gt;
&lt;br /&gt;
{{FGCquote&lt;br /&gt;
|1= I do like the idea of a stand-alone script interpreter communicating via HLA or some similar IPC, but we still have to consider fragments of script code embedded in models to create or enhance fancy animations.  Not all script fragments make sense to run externally.&lt;br /&gt;
|2= {{cite web&lt;br /&gt;
  | url    = http://forum.flightgear.org/viewtopic.php?p=270412#p270412&lt;br /&gt;
  | title  = &amp;lt;nowiki&amp;gt;Re: FGPython an propose for Python as an nasal alternative&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
  | author = &amp;lt;nowiki&amp;gt;curt&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
  | date   = Dec 28th, 2015&lt;br /&gt;
  | added   = Dec 28th, 2015&lt;br /&gt;
  | script_version = 0.23&lt;br /&gt;
  }}&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{FGCquote&lt;br /&gt;
|1= Regarding Nasal as a scripting language and AW, I'm hopeful that the work I'm doing on HLA will allow us to run Nasal in a separate thread from the FDM and display, so Nasal GC no-longer can impact frame-rates.  It would also allow for writing a weather simulation completely external to the FlightGear instance, which could be quite neat.&lt;br /&gt;
|2= {{cite web&lt;br /&gt;
  | url    = http://forum.flightgear.org/viewtopic.php?p=265721#p265721&lt;br /&gt;
  | title  = &amp;lt;nowiki&amp;gt;Re: the real cost of the Nasal Garbage Collector&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
  | author = &amp;lt;nowiki&amp;gt;stuart&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
  | date   = Nov 24th, 2015&lt;br /&gt;
  | added   = Nov 24th, 2015&lt;br /&gt;
  | script_version = 0.23&lt;br /&gt;
  }}&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{FGCquote&lt;br /&gt;
|1= I suspect that Nasal might give problems as there are a fair amount of assumptions related to code that will run at the rendering frame rate - but this will be aircraft dependent and it's worth considering during the design phase. Whilst it would be ideal to be completely compatible with all existing models I suspect there may have to be some model changes. The move to HLA has great potential to really open up the possibilities. &lt;br /&gt;
|2= {{cite web&lt;br /&gt;
  | url    = http://sourceforge.net/p/flightgear/mailman/message/34653734/&lt;br /&gt;
  | title  = &amp;lt;nowiki&amp;gt;Re: [Flightgear-devel] HLA developments&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
  | author = &amp;lt;nowiki&amp;gt;Richard Harrison&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
  | date   = Nov 28th, 2015&lt;br /&gt;
  | added   = Nov 28th, 2015&lt;br /&gt;
  | script_version = 0.23&lt;br /&gt;
  }}&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{cquote|Another thing that would work well is to proxy all nasal script invocations to a Nasal helper thread - again this assumes scripts  &lt;br /&gt;
basically interact with the sim via properties (which they already do) and that any system functions they call are thread safe - not very  &lt;br /&gt;
hard to do. As more and more functions get moved to nasal, this might become a very easy way to balance the CPU usage.&amp;lt;ref&amp;gt;{{cite web |url=http://www.mail-archive.com/flightgear-devel@lists.sourceforge.net/msg18063.html|title=&amp;lt;nowiki&amp;gt;[Flightgear-devel] multi-threading / CPU usage&amp;lt;/nowiki&amp;gt;|author=James Turner|date=Fri, 03 Oct 2008 06:52:12 -0700}}&amp;lt;/ref&amp;gt;|James Turner}}&lt;br /&gt;
&lt;br /&gt;
{{cquote|you're all hoping for a better FG. A large redesign, so we can make use of multi-core systems, can even distribute parts across multiple &lt;br /&gt;
machines. Can separate the GUI. Get Nasal outside the main simulation loop.&amp;lt;ref&amp;gt;{{cite web |url=http://www.mail-archive.com/flightgear-devel@lists.sourceforge.net/msg32832.html|title=&amp;lt;nowiki&amp;gt;Re: [Flightgear-devel] Heads up: scenery download / built-in&amp;lt;/nowiki&amp;gt;|author=ThorstenB|date=Wed, 15 Jun 2011 16:06:00 -0700}}&amp;lt;/ref&amp;gt;|ThorstenB}}&lt;br /&gt;
&lt;br /&gt;
{{FGCquote&lt;br /&gt;
|1= I've always wondered what would be involved in adopting HLA. Could we start using it today, i.e. for a very small proof of concept? Do we need an implementation? Who would be able to do it? What does the code already in SimGear do? A little message passing doesn't sound like a bad idea to me, if I understand it right.&lt;br /&gt;
|2= {{cite web&lt;br /&gt;
  | url    = http://forum.flightgear.org/viewtopic.php?p=214348#p214348&lt;br /&gt;
  | title  = &amp;lt;nowiki&amp;gt;Re: &amp;lt;/nowiki&amp;gt;&lt;br /&gt;
  | author = &amp;lt;nowiki&amp;gt;Philosopher&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
  | date   = Jul 8th, 2014&lt;br /&gt;
  | added   = Jul 8th, 2014&lt;br /&gt;
  | script_version = 0.23&lt;br /&gt;
  }}&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{FGCquote&lt;br /&gt;
|1= Actually, I have an idea for subsystems: unless there's some restrictions with using the network (like if we have to run a request in another thread), we could still use the subsystem manager and insert proxy systems. So geodinfo() would call the proxy system which would run an HLA request to access the real system in the master thread (or the scenery thread, or whatever). Would that work? It might be a bit of work to implement it for each subsystem that we need to do it for, but it might work really well &lt;br /&gt;
|2= {{cite web&lt;br /&gt;
  | url    = http://forum.flightgear.org/viewtopic.php?p=214484#p214484&lt;br /&gt;
  | title  = &amp;lt;nowiki&amp;gt;Re: &amp;lt;/nowiki&amp;gt;&lt;br /&gt;
  | author = &amp;lt;nowiki&amp;gt;Philosopher&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
  | date   = Jul 10th, 2014&lt;br /&gt;
  | added   = Jul 10th, 2014&lt;br /&gt;
  | script_version = 0.23&lt;br /&gt;
  }}&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Status 06/2013 ==&lt;br /&gt;
* HLA property tree support {{done}}&lt;br /&gt;
* standalone nasal interpreter {{done}}&lt;br /&gt;
* modify the build system to link in the required libs {{done}}&lt;br /&gt;
* add the f_setprop/f_getprop code from $FG_SRC/Scripting/NasalSys.cxx to the standalone interpreter {{done}}&lt;br /&gt;
* add the HLA/OpenRTI boilerplate to the standalone interpreter so that it can use HLA and act as a federate (see $FG_SRC/Network/HLA/hla.cxx and $FG_SRC/utils/fgai) {{progressbar|30}}&lt;br /&gt;
** fgai::AIManager federate: {{flightgear source|path=utils/fgai/AIManager.hxx|line=27}}&lt;br /&gt;
** fgviewer: {{flightgear source|path=utils/fgviewer/fgviewer.cxx|line=212}}&lt;br /&gt;
** fgfs: {{flightgear source|path=src/Network/HLA/hla.cxx|line=841}}&lt;br /&gt;
** ba-hla: https://github.com/chiemseesurfer/ba-hla&lt;br /&gt;
* next, add a simple &amp;quot;ScriptingHost&amp;quot; federate {{not done}}&lt;br /&gt;
* setprop()/getprop() implemented through HLAPropertyDataElement {{progressbar|20}}&lt;br /&gt;
* $FG_SRC/Scripting/nasal-props.?xx wrapped through HLAPropertyDataElement {{Not done}}&lt;br /&gt;
* Extend cppbind to support HLA (HLAObjectInstance etc) {{Not done}}&lt;br /&gt;
* port nasal-props.cxx to use cppbind&lt;br /&gt;
* maybe support some more FG/Nasal APIs (see {{flightgear source|path=src/Scripting/NasalSys.cxx|line=707}} ) {{Not done}}&lt;br /&gt;
** fgcommand() &lt;br /&gt;
** setlistener()&lt;br /&gt;
** settimer()&lt;br /&gt;
** maketimer()&lt;br /&gt;
&lt;br /&gt;
== Contributing ==&lt;br /&gt;
* know C++&lt;br /&gt;
* be able to build custom SG/FG versions&lt;br /&gt;
* use Git&lt;br /&gt;
* have a wiki account &lt;br /&gt;
* work through some of the HLA tutorials at [[FlightGear HLA support (High Level Architecture)#Resources]]&lt;br /&gt;
&lt;br /&gt;
== A standalone interpreter ==&lt;br /&gt;
&lt;br /&gt;
Providing a standalone Nasal binary that has its own property tree instance would not be very difficult, it would be mostly copy/paste from SimGear/FlightGear ($SG_SRC/nasal and $FG_SRC/Scripting). &lt;br /&gt;
&lt;br /&gt;
You could definitely add the FG/SG property tree code to the standalone interpreter - it would not even be very difficult, if you know C++, it should only take a couple of hours to make it work, but that will just ensure that your scripts have their own property tree, it doesn't provide any of the other FG/Nasal APIs or subsystems, such as the autopilot etc.&lt;br /&gt;
&lt;br /&gt;
Philosopher is for example using a heavily customized standalone Nasal interpreter for development purposes that has many features which FG doesn't have (yet). But that doesn't make it any easier to develop scripts FOR FlightGear. Most scripts in FlightGear use a plethora of APIs and FG-specific Nasal modules ($FG_ROOT/Nasal), so FG has become a runtime dependency (APIs, data structures like the property tree, and &amp;quot;live&amp;quot; state),&lt;br /&gt;
&lt;br /&gt;
Now, testing scripts is so time-consuming because of various issues: 1) Nasal being a dynamically typed language, where errors often only show up at runtime, 2) the lexer/parser is not particularly robust - so that error messages are not very informative, and may only show up fairly late. Philosopher has been working on improving things a little. 3) The main issue is FlightGear's way of loading Nasal scripts just once during startup, i.e. there's no design/mechanism in place to do Nasal housekeeping, so that the interpreter cannot be re-initialized at runtime, which is why most scripts are not safe to reload currently. &lt;br /&gt;
&lt;br /&gt;
To reload Nasal scripts, the whole process must be terminated and restarted currently. You can only reload scripts manually if you explicitly design them to support this use-case, which includes releasing resources like running callbacks (loops, listeners, timers) or files and threads. Even if the FG design were to be fixed, I'd expect that most scripts would need to be changed.&lt;br /&gt;
&lt;br /&gt;
Thus, a standalone Nasal interpreter in and of itself would not be particularly useful for developing/testing Nasal code for use in FlightGear, that would require some way of IPC (think CORBA or HLA)- so that the interpreter can run in its own thread/process and still hook into the fgfs process to access other subsystems, like the property tree.&lt;br /&gt;
&lt;br /&gt;
== High Level Architecture ==&lt;br /&gt;
&lt;br /&gt;
We were recently talking about using HLA to support a standaloe Nasal interpreter it in another thread, and because Nasal has some performance issues (garbage collection) that are having an impact on framerate/latency under certain circumstances. &lt;br /&gt;
&lt;br /&gt;
Thus, providing an option to run /some/ scripts separately, outside the main loop and connect things through HLA woul seem like a straightforward first step - especially because the SimGear machinery for exposing the full property tree through HLA is already in place (so not much coding needed, just understanding/applying) and there are some more low-level wrappers available in simgear/hla to expose C++ classes, objects etc - so basically it works a lot like CORBA/RPC through a central &amp;quot;broker&amp;quot; (openRTI).&lt;br /&gt;
&lt;br /&gt;
At some point, the ongoing HLA work should facilitate a scripting API exposed via HLA, so that scripting languages like Nasal (or Python or any other language) would be able to call FG APIs (like extension functions) via HLA&lt;br /&gt;
&lt;br /&gt;
Also, there's a standalone interpreter for unit testing: {{gitorious source|proj=fg|repo=hoorays-simgear}}&lt;br /&gt;
&lt;br /&gt;
So we already do have a standalone Nasal interpreter that cannot currently talk to the fgfs process - making it talk to fgfs through HLA would be a first step, and the setprop/getprop APIs (or whole props module eventually) would be an obvious candidate, because the property tree infrastructure is already in place im simgear/hla (see HLAPropertyDataElement). &lt;br /&gt;
&lt;br /&gt;
One of the goals could be to support multiple standalone Nasal processes which run setprop()/getprop(), which in turn is marshalled through HLA/OpenRTI to fgfs. That would not seem overly complicated, but like a pretty simple and useful first use-case to allow people to run certain stuff outside the main process&lt;br /&gt;
&lt;br /&gt;
Obviously, one would need to expose other FG/Nasal APIs eventually, but we should probably learn to walk before we run&lt;br /&gt;
&lt;br /&gt;
At the time of writing, The SimGear repository already contains all required HLA primitives to map the property tree and other C++ classes to HLA. For example, see:&lt;br /&gt;
* {{simgear source|path=simgear/hla/HLAPropertyDataElement.cxx}}&lt;br /&gt;
* {{simgear source|path=simgear/hla/HLAObjectClass.cxx}}&lt;br /&gt;
* {{simgear source|path=simgear/hla/HLAObjectInstance.cxx}}&lt;br /&gt;
&lt;br /&gt;
Demo code at: &lt;br /&gt;
* {{flightgear source|path=src/Network/HLA/hla.cxx}}&lt;br /&gt;
* {{flightgear source|path=utils/fgai/}}&lt;br /&gt;
&lt;br /&gt;
== HLA support through cppbind ==&lt;br /&gt;
&lt;br /&gt;
In the long run, one would need to implement the full interface of the HLAObjectClass, for example as part of the cppbind framework in simgear/nasal/cppbind- that way, cppbind APIs would become accessible through HLA, so that all cppbind APIs could also be used from a standalone interpreter that would not need to run inside the fgfs main loop anymore.&lt;br /&gt;
&lt;br /&gt;
To add HLA support to cppbind, we'd need to provide corresponding {{simgear source|path=simgear/nasal/cppbind/detail/from_nasal_helper.cxx|text=from_nasal_helper()}} and {{simgear source|path=simgear/nasal/cppbind/detail/to_nasal_helper.cxx|text=to_nasal_helper()}} templates to convert various HLA types to their corresponding Nasal equivalents and vice versa.&lt;br /&gt;
&lt;br /&gt;
All the SimGear machinery to help with marshalling should be in place in {{simgear source|path=simgear/hla|pre=$SG_SRC}}&lt;br /&gt;
&lt;br /&gt;
Tom can probably help clarify what else may be missing currently ?&lt;br /&gt;
&lt;br /&gt;
&amp;lt;references/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Category:HLA]]&lt;br /&gt;
[[Category:Developer Plans]]&lt;/div&gt;</summary>
		<author><name>TheEagle</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Nasal_library/props&amp;diff=137529</id>
		<title>Nasal library/props</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Nasal_library/props&amp;diff=137529"/>
		<updated>2023-04-08T20:23:49Z</updated>

		<summary type="html">&lt;p&gt;TheEagle: Remove double word&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Nasal Navigation|nocat=1}}&lt;br /&gt;
This page contains documentation for the '''&amp;lt;code&amp;gt;props&amp;lt;/code&amp;gt; namespace''' in [[Nasal]]. This namespace provides APIs for working with property trees (including the main [[Property Tree]]) via {{API Link|simgear|class|SGPropertyNode}}. The &amp;lt;code&amp;gt;props&amp;lt;/code&amp;gt; namespace is sourced from {{fgdata file|Nasal/props.nas}} and {{flightgear file|src/Scripting/nasal-props.cxx}}.&lt;br /&gt;
&lt;br /&gt;
== Class ==&lt;br /&gt;
=== Node ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|mode = class&lt;br /&gt;
|text = The main class, used widely for manipulating property trees.&lt;br /&gt;
}}&lt;br /&gt;
==== new() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.new([values]);&lt;br /&gt;
|text = Constructor function. Returns a new &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; instance.&lt;br /&gt;
|param1 = values&lt;br /&gt;
|param1text = An optional hash that will be the initial property structure.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
props.dump(node);&lt;br /&gt;
|example2 = var tree = {&lt;br /&gt;
    a: 1,&lt;br /&gt;
    b: [&amp;quot;a&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c&amp;quot;],&lt;br /&gt;
    c: {&lt;br /&gt;
        d: 1 * 4&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
var node = props.Node.new(tree);&lt;br /&gt;
props.dump(node);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== addChild() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.addChild(name[, min_idx[, append]]);&lt;br /&gt;
|version = 2.10&lt;br /&gt;
|commit = {{fgdata commit|a15d58|t=commit}}&lt;br /&gt;
|text = Add a new, blank child to the node. Returns the newly-created node.&lt;br /&gt;
|param1 = name&lt;br /&gt;
|param1text = The name of the node as a string.&lt;br /&gt;
|param2 = min_idx&lt;br /&gt;
|param2text = This specifies the minimum index to add the new one to. This takes precedence over '''append'''. Defaults to 0.&lt;br /&gt;
|param3 = append&lt;br /&gt;
|param3text = If there is already one or more children with the same name and this argument is 1 (true), the new child will be added after the child with the highest index. If 0 (false), the new child will be added at the lowest available index with the limit of '''min_idx'''. Defaults to 1 (true).&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
props.dump(node);&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;, 1);&lt;br /&gt;
props.dump(node); # a[1]&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;, 1);&lt;br /&gt;
props.dump(node); # a[1]&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;, 0, 0);&lt;br /&gt;
props.dump(node); # a[1] and a[0]&lt;br /&gt;
|example4 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;, 1);&lt;br /&gt;
props.dump(node); # a[1]&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;, 0, 1);&lt;br /&gt;
props.dump(node); # a[1] and a[2]&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== addChildren() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.addChildren(name, count[, min_idx[, append]]);&lt;br /&gt;
|version = 2.10&lt;br /&gt;
|commit = {{fgdata commit|7f1117|t=commit}}&lt;br /&gt;
|text = Adds multiple children with the same name to this node. Returns &amp;lt;code&amp;gt;'''nil&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param1 = name&lt;br /&gt;
|param1text = The name of the nodes as a string.&lt;br /&gt;
|param2 = count&lt;br /&gt;
|param2text = Number of new children to add.&lt;br /&gt;
|param3 = min_idx&lt;br /&gt;
|param3text = Performs the same function as in &amp;lt;code&amp;gt;[[#addChild.28.29|Node.addChild()]]&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param4 = append&lt;br /&gt;
|param4text = Performs the same function as in &amp;lt;code&amp;gt;[[#addChild.28.29|Node.addChild()]]&amp;lt;/code&amp;gt;.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.addChildren(&amp;quot;a&amp;quot;, 2);&lt;br /&gt;
props.dump(node); # a[0] and a[1]&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.addChildren(&amp;quot;a&amp;quot;, 2, 1);&lt;br /&gt;
props.dump(node); # a[1] and a[2]&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;, 2);&lt;br /&gt;
props.dump(node); # a[2]&lt;br /&gt;
node.addChildren(&amp;quot;a&amp;quot;, 2, 0, 0);&lt;br /&gt;
props.dump(node); # a[2], a[0], and a[1]&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== adjustValue() (since FG 2020.1) ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.adjustValue(delta);&lt;br /&gt;
|text = Adds delta (numeric) to current value respecting the node type. &lt;br /&gt;
|param1 = delta&lt;br /&gt;
|param1text = Numeric value (can be negative) to add to current node value.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== alias() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.alias(node);&lt;br /&gt;
|text = Aliases this node to another one. Returns 1 on success and 0 on failure.&lt;br /&gt;
|param1 = node&lt;br /&gt;
|param1text = The node to alias to. Can be one of:&lt;br /&gt;
* A path to a property in the [[Property Tree]] as a string.&lt;br /&gt;
* A &amp;lt;code&amp;gt;prop&amp;lt;/code&amp;gt; ghost.&lt;br /&gt;
* A &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.alias(&amp;quot;/position/altitude-ft&amp;quot;);&lt;br /&gt;
props.dump(node); # equals the current altitude&lt;br /&gt;
|example2 = var node1 = props.Node.new();&lt;br /&gt;
node1.setDoubleValue(2.34);&lt;br /&gt;
var node2 = props.Node.new();&lt;br /&gt;
node2.alias(node1);&lt;br /&gt;
props.dump(node2); # equals 2.34&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== clearValue() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.clearValue();&lt;br /&gt;
|text = Clears the value and type of the node.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.setDoubleValue(2.34);&lt;br /&gt;
props.dump(node); # prints &amp;quot;{DOUBLE} = 2.35&amp;quot;&lt;br /&gt;
node.clearValue();&lt;br /&gt;
props.dump(node); # prints &amp;quot;{NONE} = nil&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== decrement() (since FG 2020.1) ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.decrement(n = 1);&lt;br /&gt;
|text = Decrements integer property by n (default: n = 1)&lt;br /&gt;
|param1 = n&lt;br /&gt;
|param1text = Value to subtract, will be converted to int, defaults to 1&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== equals() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.equals(node);&lt;br /&gt;
|version = 2.12&lt;br /&gt;
|commit = {{fgdata commit|d80722|t=commit}}&lt;br /&gt;
|text = Checks whether the node refers to the same one as another. Returns 1 (true) if it is, and 0 (false) if otherwise.&lt;br /&gt;
|param1 = node&lt;br /&gt;
|param1text = Node to check against. May be either a &amp;lt;code&amp;gt;prop&amp;lt;/code&amp;gt; ghost or a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object.&lt;br /&gt;
|example1 = var n = props.Node.new();&lt;br /&gt;
var a = n;&lt;br /&gt;
print(a.equals(n)); # prints &amp;quot;1&amp;quot; (true)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getAliasTarget() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getAliasTarget();&lt;br /&gt;
|text = Returns the alias target of a node as another &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; instance.&lt;br /&gt;
|example1 = setprop(&amp;quot;/test&amp;quot;, 2.35);&lt;br /&gt;
var node = props.Node.new();&lt;br /&gt;
node.alias(&amp;quot;/test&amp;quot;);&lt;br /&gt;
var tgt = node.getAliasTarget();&lt;br /&gt;
print(tgt.getPath()); # prints &amp;quot;/test&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getAttribute() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getAttribute([rel_path, ]name);&lt;br /&gt;
props.Node.getAttribute();&lt;br /&gt;
|text = Returns an attribute. If no arguments are given, the function will return an integer specifying the attributes set for the node (see {{simgear file|simgear/props/props.hxx|l=767}} for a list). A list of attributes are below&lt;br /&gt;
{{{!}} class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! String !! Return value&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} last {{!!}} The highest used attribute code (should be 128). See for {{simgear file|simgear/props/props.hxx|l=767}} the codes.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} children {{!!}} Number of child nodes.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} listeners {{!!}} Number of listeners connected to this node.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} references {{!!}} Number of times the node has previously been referenced.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} tied {{!!}} Whether the node is tied.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} alias {{!!}} Whether the node is aliased.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} readable {{!!}} Whether the node can be read.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} writable {{!!}} Whether the node can be written to.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} archive {{!!}} Whether the node will be saved when the &amp;quot;save&amp;quot; [[fgcommands|fgcommand]] is triggered.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} trace-read {{!!}} Whether the reading of the node will be logged when &amp;lt;code&amp;gt;--log-level=info&amp;lt;/code&amp;gt;.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} trace-write {{!!}} Whether the writing to the node will be logged when &amp;lt;code&amp;gt;--log-level=info&amp;lt;/code&amp;gt;.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} userarchive {{!!}} Whether the node will be saved to the [[FlightGear configuration via XML#autosave.xml|autosave file]] (only works for actual properties).&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} preserve {{!!}} Whether the value of node will be preserved during resets (only works for actual properties).&lt;br /&gt;
{{!}}}&lt;br /&gt;
|param1 = rel_path&lt;br /&gt;
|param1text = Optional relative path as a string.&lt;br /&gt;
|param2 = name&lt;br /&gt;
|param2text = Attribute as a string. See the above table for a full list.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
var child = node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
print(node.getAttribute(&amp;quot;children&amp;quot;)); # prints &amp;quot;1&amp;quot;&lt;br /&gt;
|example2text = Example using relative path&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
var child = node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
print(node.getAttribute(&amp;quot;a&amp;quot;, &amp;quot;readable&amp;quot;)); # prints &amp;quot;1&amp;quot; (node can be read from)&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
var node2 = props.Node.new();&lt;br /&gt;
node2.alias(node);&lt;br /&gt;
print(node2.getAttribute(&amp;quot;alias&amp;quot;)); # prints &amp;quot;1&amp;quot; (true)&lt;br /&gt;
|example4 = print(props.globals.getNode(&amp;quot;/sim/signals/fdm-initialized&amp;quot;).getAttribute(&amp;quot;listeners&amp;quot;)); # prints the number of listeners&lt;br /&gt;
|example5 = print(props.globals.getNode(&amp;quot;/sim/time/elapsed-sec&amp;quot;).getAttribute(&amp;quot;tied&amp;quot;)); # prints &amp;quot;1&amp;quot; (true), meaning it is tied&lt;br /&gt;
|example6 = var node = props.Node.new();&lt;br /&gt;
print(node.getAttribute(&amp;quot;writable&amp;quot;)); # prints &amp;quot;1&amp;quot; (true), meaning the node can be written to&lt;br /&gt;
|example7text = Example using no arguments&lt;br /&gt;
|example7 = var node = props.Node.new();&lt;br /&gt;
print(node.getAttribute()); # prints &amp;quot;3&amp;quot; (true), meaning the node can be read from (1) and written to (2)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getBoolValue() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getBoolValue();&lt;br /&gt;
|text = Returns the value of a node converted to a boolean. If the node is a number type, 0  will return false, while 1 will return true. If the node is a string or unspecified type and the value is &amp;lt;code&amp;gt;&amp;quot;false&amp;quot;&amp;lt;/code&amp;gt;, false will be returned. Otherwise, true will be returned. Remember that boolean values are represented in Nasal as 1 (true) and 0 (false).&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.setBoolValue(1);&lt;br /&gt;
print(node.getBoolValue() ? &amp;quot;true&amp;quot; : &amp;quot;false&amp;quot;); # prints &amp;quot;true&amp;quot;&lt;br /&gt;
node.setBoolValue(0);&lt;br /&gt;
print(node.getBoolValue() ? &amp;quot;true&amp;quot; : &amp;quot;false&amp;quot;); # prints &amp;quot;false&amp;quot;&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.setDoubleValue(1.23);&lt;br /&gt;
print(node.getBoolValue() ? &amp;quot;true&amp;quot; : &amp;quot;false&amp;quot;); # prints &amp;quot;true&amp;quot;&lt;br /&gt;
node.setDoubleValue(-1.23);&lt;br /&gt;
print(node.getBoolValue() ? &amp;quot;true&amp;quot; : &amp;quot;false&amp;quot;); # prints &amp;quot;true&amp;quot;&lt;br /&gt;
node.setDoubleValue(0.0);&lt;br /&gt;
print(node.getBoolValue() ? &amp;quot;true&amp;quot; : &amp;quot;false&amp;quot;); # prints &amp;quot;false&amp;quot;&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
node.setIntValue(2);&lt;br /&gt;
print(node.getBoolValue() ? &amp;quot;true&amp;quot; : &amp;quot;false&amp;quot;); # prints &amp;quot;true&amp;quot;&lt;br /&gt;
node.setIntValue(-2);&lt;br /&gt;
print(node.getBoolValue() ? &amp;quot;true&amp;quot; : &amp;quot;false&amp;quot;); # prints &amp;quot;true&amp;quot;&lt;br /&gt;
node.setIntValue(0);&lt;br /&gt;
print(node.getBoolValue() ? &amp;quot;true&amp;quot; : &amp;quot;false&amp;quot;); # prints &amp;quot;false&amp;quot;&lt;br /&gt;
|example4 = var node = props.Node.new();&lt;br /&gt;
node.setValue(&amp;quot;Hello, World!&amp;quot;);&lt;br /&gt;
print(node.getBoolValue() ? &amp;quot;true&amp;quot; : &amp;quot;false&amp;quot;); # prints &amp;quot;true&amp;quot;&lt;br /&gt;
node.setValue(&amp;quot;false&amp;quot;);&lt;br /&gt;
print(node.getBoolValue() ? &amp;quot;true&amp;quot; : &amp;quot;false&amp;quot;); # prints &amp;quot;false&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getChild() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getChild(rel_path[, idx[, create]]);&lt;br /&gt;
|text = Returns a child of a node as another &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; instance.&lt;br /&gt;
|param1 = rel_path&lt;br /&gt;
|param1text = Relative path to the child node as a string.&lt;br /&gt;
|param2 = idx&lt;br /&gt;
|param2text = Optional index for the child node as an integer.&lt;br /&gt;
|param3 = create&lt;br /&gt;
|param3text = If set to 1 (true), a new child will be created if it does not exist. If set to 0 (false), the function will not create a new child and the function will return &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if no child exists. Defaults to 0.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
var c = node.getChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.addChildren(&amp;quot;a&amp;quot;, 3);&lt;br /&gt;
node.getNode(&amp;quot;a[1]&amp;quot;).setDoubleValue(2.35);&lt;br /&gt;
var c = node.getChild(&amp;quot;a&amp;quot;, 1);&lt;br /&gt;
print(c.getValue()); # prints &amp;quot;2.35&amp;quot;&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
var c = node.getChild(&amp;quot;a&amp;quot;, 1, 1);&lt;br /&gt;
props.dump(node); # new child a[1] will have appeared&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getChildren() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getChildren([name]);&lt;br /&gt;
|text = Returns a vector of child nodes, optionally those with a certain name, as &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; instances.&lt;br /&gt;
|param1 = name&lt;br /&gt;
|param1text = Optional name of the child nodes as a string. If not given, all children will be returned.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.addChildren(&amp;quot;a&amp;quot;, 3);&lt;br /&gt;
node.addChildren(&amp;quot;b&amp;quot;, 3);&lt;br /&gt;
debug.dump(node.getChildren()); # all child nodes in the vector&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.addChildren(&amp;quot;a&amp;quot;, 3);&lt;br /&gt;
node.addChildren(&amp;quot;b&amp;quot;, 3);&lt;br /&gt;
debug.dump(node.getChildren(&amp;quot;b&amp;quot;)); # only children with the name &amp;quot;b&amp;quot; in the vector&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getIndex() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getIndex();&lt;br /&gt;
|text = Returns the index of a node as an integer.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.addChildren(&amp;quot;a&amp;quot;, 3);&lt;br /&gt;
print(node.getChild(&amp;quot;a&amp;quot;, 1).getIndex()); # prints &amp;quot;1&amp;quot;&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;b&amp;quot;);&lt;br /&gt;
print(node.getChild(&amp;quot;b&amp;quot;).getIndex()); # prints &amp;quot;0&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getName() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getName();&lt;br /&gt;
|text = Returns the name of the node as a string.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
var c = node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
debug.dump(c.getName()); # prints &amp;quot;a&amp;quot;&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.addChildren(&amp;quot;a&amp;quot;, 3);&lt;br /&gt;
debug.dump(node.getChild(&amp;quot;a&amp;quot;, 2).getName()); # prints &amp;quot;a&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getNode() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getNode(rel_path[, create]);&lt;br /&gt;
|text = Returns a subnode as another &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; instance.&lt;br /&gt;
|param1 = rel_path&lt;br /&gt;
|param1text = Relative path to the subnode as a string.&lt;br /&gt;
|param2 = create&lt;br /&gt;
|param2text = If 1 (true), the node will be created if it does not exist. If 0 (false) and the node does not exist, the function will return &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt;. Default to 0 (false).&lt;br /&gt;
|example1 = var tree = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 1,&lt;br /&gt;
    &amp;quot;b&amp;quot;: &amp;quot;Hi&amp;quot;,&lt;br /&gt;
    &amp;quot;c&amp;quot;: {&lt;br /&gt;
        &amp;quot;d&amp;quot;: &amp;quot;Hello, World!&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new(tree);&lt;br /&gt;
print(node.getNode(&amp;quot;c/d&amp;quot;).getValue()); # prints &amp;quot;Hello, World!&amp;quot;&lt;br /&gt;
|example2 = var tree = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 1,&lt;br /&gt;
    &amp;quot;b&amp;quot;: &amp;quot;Hi&amp;quot;,&lt;br /&gt;
    &amp;quot;c&amp;quot;: {}&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new(tree);&lt;br /&gt;
node.getNode(&amp;quot;c/d&amp;quot;, 1).setDoubleValue(2.35);&lt;br /&gt;
props.dump(node); # c/d now exists&lt;br /&gt;
|example3 = var ac = props.globals.getNode(&amp;quot;sim/aircraft&amp;quot;);&lt;br /&gt;
print(&amp;quot;Current aircraft is: &amp;quot;, ac.getValue());&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getParent() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getParent();&lt;br /&gt;
|text = Returns the parent of a node, or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if there is no parent.&lt;br /&gt;
|example1 = var tree = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 1,&lt;br /&gt;
    &amp;quot;b&amp;quot;: &amp;quot;Hi&amp;quot;,&lt;br /&gt;
    &amp;quot;c&amp;quot;: {&lt;br /&gt;
        &amp;quot;d&amp;quot;: &amp;quot;Hello, World!&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new(tree);&lt;br /&gt;
props.dump(node.getNode(&amp;quot;c/d&amp;quot;).getParent()); # dumps &amp;quot;c&amp;quot;&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
debug.dump(node.getParent()); # prints nil&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getPath() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getPath();&lt;br /&gt;
|text = Returns the path of the node as a string.&lt;br /&gt;
|example1 = var tree = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 1,&lt;br /&gt;
    &amp;quot;b&amp;quot;: &amp;quot;Hi&amp;quot;,&lt;br /&gt;
    &amp;quot;c&amp;quot;: {&lt;br /&gt;
        &amp;quot;d&amp;quot;: &amp;quot;Hello, World!&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new(tree);&lt;br /&gt;
print(node.getNode(&amp;quot;c/d&amp;quot;).getPath()); # prints &amp;quot;/c/d&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getType() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getType();&lt;br /&gt;
|text = Returns node's type as a string. It should be one of &amp;quot;NONE&amp;quot;, &amp;quot;ALIAS&amp;quot;, &amp;quot;BOOL&amp;quot;, &amp;quot;INT&amp;quot;, &amp;quot;LONG&amp;quot; (long integer), &amp;quot;FLOAT&amp;quot;, &amp;quot;DOUBLE&amp;quot;, &amp;quot;STRING&amp;quot;, &amp;quot;VEC3D&amp;quot;, &amp;quot;VEC4D&amp;quot;, &amp;quot;UNSPECIFIED&amp;quot;.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
print(node.getType()); # prints &amp;quot;NONE&amp;quot;&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.setIntValue(12);&lt;br /&gt;
print(node.getType()); # prints &amp;quot;INT&amp;quot;&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
node.setValue([0.4, 0.2, 1]);&lt;br /&gt;
debug.dump(node.getValue()); # prints &amp;quot;[0.4, 0.2, 1]&amp;quot;&lt;br /&gt;
print(node.getType()); # prints &amp;quot;VEC3D&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getValue() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getValue([rel_path]);&lt;br /&gt;
|text = {{hatnote|See also {{func link|getBoolValue()|page=this}}.}}&lt;br /&gt;
&lt;br /&gt;
Returns the value of the node.&lt;br /&gt;
|param1 = rel_path&lt;br /&gt;
|param1text = Optional relative path to a subnode as a string, which may contain '/' characters. If the subnode does not exist, we return nil.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.setDoubleValue(2.35);&lt;br /&gt;
print(node.getValue()); # prints &amp;quot;2.35&amp;quot;&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.setValue(&amp;quot;Hi&amp;quot;);&lt;br /&gt;
print(node.getValue()); # prints &amp;quot;Hi&amp;quot;&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;).setValue(&amp;quot;Hi&amp;quot;);&lt;br /&gt;
print(node.getValue(&amp;quot;a&amp;quot;)); # prints &amp;quot;Hi&amp;quot;&lt;br /&gt;
|example4 = var node = props.Node.new();&lt;br /&gt;
node.setValue([0, 0.5, 1]);&lt;br /&gt;
debug.dump(node.getValue()); # prints &amp;quot;[0, 0.5, 1]&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== getValues() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.getValues();&lt;br /&gt;
|text = Returns the node tree as a hash, with all the various subnodes, etc. If the node has no children, the result is equivalent to {{func link|getValue()|page=this}}. Subnodes that are indexed will be combined into one key and their values placed in a vector (see example 2).&lt;br /&gt;
|example1 = var tree = {&lt;br /&gt;
    &amp;quot;string&amp;quot;: &amp;quot;Hi&amp;quot;,&lt;br /&gt;
    &amp;quot;number&amp;quot;: 1.2,&lt;br /&gt;
    &amp;quot;subnode&amp;quot;: {&lt;br /&gt;
        &amp;quot;idx-node&amp;quot;: [1, 2, 3]&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new(tree);&lt;br /&gt;
node.addChild(&amp;quot;bool&amp;quot;).setBoolValue(1);&lt;br /&gt;
props.dump(node); # dump to node tree&lt;br /&gt;
debug.dump(node.getValues()); # dump the node converted to hash&lt;br /&gt;
|example2 = var tree = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: [1, 2, 3]&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new(tree);&lt;br /&gt;
props.dump(node); # a[0] = 1, a[1] = 2, a[2] = 3&lt;br /&gt;
debug.dump(node.getValues()); # a: [1, 2, 3]&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== increment() (since FG 2020.1) ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.increment(n = 1);&lt;br /&gt;
|text = Increments integer property by n (default: n = 1)&lt;br /&gt;
|param1 = n&lt;br /&gt;
|param1text = Value to add, will be converted to int, defaults to 1&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== initNode() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.initNode([path[, value[, type[, force]]]]);&lt;br /&gt;
|text = Initializes a node if it doesn't exist and returns that node as a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object.&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Optional path to a subnode as a string. If not given, the node itself will be initialized.&lt;br /&gt;
|param2 = value&lt;br /&gt;
|param2text = Optional default value to initialize the node with.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = Optional string that will set the type of the node. Must be one of &amp;lt;code&amp;gt;&amp;quot;DOUBLE&amp;quot;&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;&amp;quot;INT&amp;quot;&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;&amp;quot;BOOL&amp;quot;&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;&amp;quot;STRING&amp;quot;&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param4 = force&lt;br /&gt;
|param4text = If set to 1 (true), the node's type will be forced to change.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
var a = node.initNode(&amp;quot;a&amp;quot;);&lt;br /&gt;
props.dump(a);&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
var a = node.initNode(&amp;quot;a&amp;quot;, &amp;quot;Hi&amp;quot;);&lt;br /&gt;
props.dump(a);&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
var a = node.initNode(&amp;quot;a&amp;quot;, 1.25, &amp;quot;INT&amp;quot;);&lt;br /&gt;
props.dump(a); # a = 1&lt;br /&gt;
|example4 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;).setBoolValue(0);&lt;br /&gt;
props.dump(node.getChild(&amp;quot;a&amp;quot;)); # a = 0 (type: bool)&lt;br /&gt;
var a = node.initNode(&amp;quot;a&amp;quot;, 1.25, &amp;quot;INT&amp;quot;, 1);&lt;br /&gt;
props.dump(a); # a = 0 (type: int)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== isInt() (since FG 2020.1) ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.isInt();&lt;br /&gt;
|text = Returns true (1) if node '''type''' is &amp;quot;INT&amp;quot; or &amp;quot;LONG&amp;quot; (long integer) otherwise false (0).&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== isNumeric() (since FG 2020.1) ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.isNumeric();&lt;br /&gt;
|text = Returns true (1) if node '''type''' is &amp;quot;INT&amp;quot;, &amp;quot;LONG&amp;quot;, &amp;quot;FLOAT&amp;quot; or &amp;quot;DOUBLE&amp;quot; otherwise false (0).&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== remove() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.remove();&lt;br /&gt;
|text = Removes the node and returns the removed node.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
props.dump(node);&lt;br /&gt;
node.getChild(&amp;quot;a&amp;quot;).remove();&lt;br /&gt;
props.dump(node); # child &amp;quot;a&amp;quot; does not exist anymore&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== removeAllChildren() ====&lt;br /&gt;
{{Caution|Be careful when  using this API in conjunction with the Canvas system and element specific properties, for details please see [[Howto:Canvas Path Benchmarking]]}}&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.removeAllChildren();&lt;br /&gt;
|version = 3.2&lt;br /&gt;
|commit = {{fgdata commit|4766ed|t=commit}}&lt;br /&gt;
|text = Removes all child nodes and returns the node.&lt;br /&gt;
|example1 = var tree = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 1,&lt;br /&gt;
    &amp;quot;b&amp;quot;: 2,&lt;br /&gt;
    &amp;quot;c&amp;quot;: 3&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new(tree);&lt;br /&gt;
props.dump(node);&lt;br /&gt;
node.removeAllChildren();&lt;br /&gt;
props.dump(node); # all children have been removed&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== removeChild() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.removeChild(rel_path, idx);&lt;br /&gt;
|text = Removes a given child node child nodes and returns the node.&lt;br /&gt;
|param1 = rel_path&lt;br /&gt;
|param1text = Relative path to a subnode as a string.&lt;br /&gt;
|param2 = idx&lt;br /&gt;
|param2text = Index of the subnode to remove as an integer.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
props.dump(node);&lt;br /&gt;
node.removeChild(&amp;quot;a&amp;quot;, 0);&lt;br /&gt;
props.dump(node); # child &amp;quot;a&amp;quot; has been removed&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.addChildren(&amp;quot;a&amp;quot;, 2);&lt;br /&gt;
props.dump(node);&lt;br /&gt;
node.removeChild(&amp;quot;a&amp;quot;, 0);&lt;br /&gt;
props.dump(node); # just a[1] remains&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== removeChildren() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.removeChildren([name]);&lt;br /&gt;
|text = Removes all children with a specified name. If no arguments are given, all children will be removed (see also {{func link|removeAllChildren()|page=this}}).&lt;br /&gt;
|param1 = name&lt;br /&gt;
|param1text = Optional name of children to remove as a string.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.addChildren(&amp;quot;a&amp;quot;, 2);&lt;br /&gt;
node.addChildren(&amp;quot;b&amp;quot;, 2);&lt;br /&gt;
props.dump(node);&lt;br /&gt;
node.removeChildren(&amp;quot;a&amp;quot;);&lt;br /&gt;
props.dump(node); # just children named &amp;quot;b&amp;quot; remain&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.addChildren(&amp;quot;a&amp;quot;, 2);&lt;br /&gt;
node.addChildren(&amp;quot;b&amp;quot;, 2);&lt;br /&gt;
props.dump(node);&lt;br /&gt;
node.removeChildren();&lt;br /&gt;
props.dump(node); # all children removed&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== setAttribute() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.setAttribute([rel_path, ]attr, value);&lt;br /&gt;
props.Node.setAttribute(attrs);&lt;br /&gt;
|text = Sets an attribute or multiple attributes. A list of attributes and their codes are below. For a brand new node, the default attributes are ''readable'' and ''writable''. Returns an integer specifying the old attributes.&lt;br /&gt;
{{{!}} class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! String !! Description&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} readable {{!!}} Whether the node can be read.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} writable {{!!}} Whether the node can be written to.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} archive {{!!}} Whether the node will be saved when the &amp;quot;save&amp;quot; [[fgcommands|fgcommand]] is triggered.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} trace-read {{!!}} Whether the reading of the node will be logged when &amp;lt;code&amp;gt;--log-level=info&amp;lt;/code&amp;gt;.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} trace-write {{!!}} Whether the writing to the node will be logged when &amp;lt;code&amp;gt;--log-level=info&amp;lt;/code&amp;gt;.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} userarchive {{!!}} Whether the node will be saved to the [[FlightGear configuration via XML#autosave.xml|autosave file]] (only works for actual properties).&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} preserve {{!!}} Whether the value of node will be preserved during resets (only works for actual properties).&lt;br /&gt;
{{!}}}&lt;br /&gt;
|param1 = rel_path&lt;br /&gt;
|param1text = Optional relative path as a string.&lt;br /&gt;
|param2 = attr&lt;br /&gt;
|param2text = Name of attribute to set as a string. See above.&lt;br /&gt;
|param3 = value&lt;br /&gt;
|param3text = Boolean value to set the property to.&lt;br /&gt;
|param4 = attrs&lt;br /&gt;
|param4text = When the function is used in its second form, this argument is used. This argument should be an integer specifying which arguments are set to true. See {{simgear file|simgear/props/props.hxx|l=767}} for the full list of codes. Simply add codes of the desired attributes together.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.setAttribute(&amp;quot;trace-write&amp;quot;, 1);&lt;br /&gt;
node.setIntValue(12); # will be traced&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.setAttribute(&amp;quot;readable&amp;quot;, 0);&lt;br /&gt;
var val = node.getValue();&lt;br /&gt;
debug.dump(val); # prints &amp;quot;nil&amp;quot;&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
node.setAttribute(&amp;quot;a&amp;quot;, &amp;quot;trace-write&amp;quot;, 1);&lt;br /&gt;
node.getChild(&amp;quot;a&amp;quot;).setIntValue(12); # will be traced&lt;br /&gt;
|example4 = var node = props.Node.new();&lt;br /&gt;
node.setAttribute(35); # read + write + trace-write&lt;br /&gt;
node.setIntValue(12); # will be traced&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== setBoolValue() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.setBoolValue([rel_path, ]value);&lt;br /&gt;
|text = {{note|For setting the values of [[Property Tree]] nodes, it is recommended to use {{func link|setprop()}} if possible, due to performance differences.}}&lt;br /&gt;
&lt;br /&gt;
Sets a node to a boolean value. If the node has no type, it will be set to a bool type. If the node is already a number type, it will be set to either 1 or 0. If it is a string, it will be set to either &amp;quot;true&amp;quot; or &amp;quot;false&amp;quot;. Returns 1 (true) if the operation was successful.&lt;br /&gt;
|param1 = rel_path&lt;br /&gt;
|param1text = Optional relative path as a string.&lt;br /&gt;
|param2 = value&lt;br /&gt;
|param2text = Value to set the node to, will be interpreted into a boolean. If it is &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt;, it will be false. If it is a string, it will be false. If it is a number, 0 will be false, while other numbers will be true. All other cases will be interpreted as 0.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.setBoolValue(nil);&lt;br /&gt;
props.dump(node); # node = 0 (false)&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.setBoolValue(&amp;quot;Hi&amp;quot;);&lt;br /&gt;
props.dump(node); # node = 1 (true)&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
node.setBoolValue(0);&lt;br /&gt;
props.dump(node); # node = 0 (false)&lt;br /&gt;
node.setBoolValue(1.25);&lt;br /&gt;
props.dump(node); # node = 1 (true)&lt;br /&gt;
|example4 = var node = props.Node.new();&lt;br /&gt;
node.setValue(&amp;quot;String&amp;quot;);&lt;br /&gt;
props.dump(node); # node = &amp;quot;String&amp;quot; (type: string)&lt;br /&gt;
node.setBoolValue(1);&lt;br /&gt;
props.dump(node); # node = &amp;quot;true&amp;quot;&lt;br /&gt;
|example5 = var node = props.Node.new();&lt;br /&gt;
node.setDoubleValue(12.32);&lt;br /&gt;
props.dump(node); # node = 12.32 (type: double)&lt;br /&gt;
node.setBoolValue(1);&lt;br /&gt;
props.dump(node); # node = 1&lt;br /&gt;
|example6 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
node.setBoolValue(&amp;quot;a&amp;quot;, 1);&lt;br /&gt;
props.dump(node); # /a = 1&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== setDoubleValue() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.setDoubleValue([rel_path, ]value);&lt;br /&gt;
|text = {{note|For setting the values of [[Property Tree]] nodes, it is recommended to use {{func link|setprop()}} if possible, due to performance differences.}}&lt;br /&gt;
&lt;br /&gt;
Sets a node to a double value. If the node has no type, it will be set to a double type. If the node is a bool, values different from zero will be interpreted as true. If the node is a string, the value will be converted to a string. If the node is an integer type, it will be truncated to the closest integer to zero. Returns 1 (true) if the operation was successful.&lt;br /&gt;
|param1 = rel_path&lt;br /&gt;
|param1text = Optional relative path as a string.&lt;br /&gt;
|param2 = value&lt;br /&gt;
|param2text = Value to set the node to, will be interpreted into a double number. It must be a valid number or a string that can be converted to a number.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.setDoubleValue(1.1);&lt;br /&gt;
props.dump(node); # node = 1.1 (type: double)&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.setDoubleValue(&amp;quot;1.1&amp;quot;);&lt;br /&gt;
props.dump(node); # node = 1.1 (type: double)&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
node.setBoolValue(1);&lt;br /&gt;
node.setDoubleValue(&amp;quot;1.1&amp;quot;);&lt;br /&gt;
props.dump(node); # node = 1 (type: bool)&lt;br /&gt;
node.setDoubleValue(0.0);&lt;br /&gt;
props.dump(node); # node = 0 (type: bool)&lt;br /&gt;
|example4 = var node = props.Node.new();&lt;br /&gt;
node.setIntValue(1);&lt;br /&gt;
node.setDoubleValue(1.1);&lt;br /&gt;
props.dump(node); # node = 1 (type: int)&lt;br /&gt;
node.setDoubleValue(&amp;quot;-1.1&amp;quot;);&lt;br /&gt;
props.dump(node); # node = -1 (type: int)&lt;br /&gt;
|example5 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
node.setDoubleValue(&amp;quot;a&amp;quot;, 12.2);&lt;br /&gt;
props.dump(node); # /a = 12.2&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== setIntValue() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.setIntValue([rel_path, ]value);&lt;br /&gt;
|text = {{note|For setting the values of [[Property Tree]] nodes, it is recommended to use {{func link|setprop()}} if possible, due to performance differences.}}&lt;br /&gt;
&lt;br /&gt;
Sets a node to an integer value. If the node has no type, it will be set to a integer type. If the node is a bool, values different from zero will be interpreted as true. If the node is a string, the value will be converted to a string. Returns 1 (true) if the operation was successful.&lt;br /&gt;
|param1 = rel_path&lt;br /&gt;
|param1text = Optional relative path as a string.&lt;br /&gt;
|param2 = value&lt;br /&gt;
|param2text = Value to set the node to, will be interpreted into a integer. It must be a valid number or a string that can be converted to a number. If the number is a double, it will be truncated to the closest integer to zero.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.setIntValue(12);&lt;br /&gt;
props.dump(node); # node = 12&lt;br /&gt;
node.setIntValue(&amp;quot;6&amp;quot;);&lt;br /&gt;
props.dump(node); # node = 6&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.setIntValue(12.2);&lt;br /&gt;
props.dump(node); # node = 12&lt;br /&gt;
node.setIntValue(-12.2);&lt;br /&gt;
props.dump(node); # node = 12&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
node.setBoolValue(1);&lt;br /&gt;
node.setIntValue(12.5);&lt;br /&gt;
props.dump(node); # node = 1 (type: bool)&lt;br /&gt;
node.setIntValue(0);&lt;br /&gt;
props.dump(node); # node = 0 (type: bool)&lt;br /&gt;
|example4 = var node = props.Node.new();&lt;br /&gt;
node.setValue(&amp;quot;Hi&amp;quot;);&lt;br /&gt;
node.setIntValue(12);&lt;br /&gt;
props.dump(node); # node = &amp;quot;12&amp;quot; (type: string)&lt;br /&gt;
|example5 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
node.setIntValue(&amp;quot;a&amp;quot;, 12);&lt;br /&gt;
props.dump(node); # /a = 12&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== setValue() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.setValue([rel_path, ]value);&lt;br /&gt;
|text = {{hatnote|See also {{func link|setBoolValue()|page=this}}, {{func link|setDoubleValue()|page=this}}, and {{func link|setIntValue()|page=this}}.}}&lt;br /&gt;
&lt;br /&gt;
{{note|For setting the values of [[Property Tree]] nodes, it is recommended to use {{func link|setprop()}} if possible, due to performance differences.}}&lt;br /&gt;
&lt;br /&gt;
Sets a node to a given value. See table below for conversions and effects. Note that vec3d and vec4d types are not fully integrated into SGPropertyNode. Returns 1 (true) if the operation was successful.&lt;br /&gt;
&lt;br /&gt;
{{{!}} class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
{{!}} colspan=&amp;quot;2&amp;quot; style=&amp;quot;text-align:right&amp;quot; {{!}} '''value''' type → &lt;br /&gt;
! rowspan=&amp;quot;2&amp;quot; {{!}} Number&lt;br /&gt;
! rowspan=&amp;quot;2&amp;quot; {{!}} String &lt;br /&gt;
! rowspan=&amp;quot;2&amp;quot; {{!}} Vector&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} style=&amp;quot;text-align:left&amp;quot; {{!}} Current node&amp;lt;br&amp;gt;type ↓&lt;br /&gt;
{{!}} style=&amp;quot;text-align:right&amp;quot; {{!}} Result ↘&lt;br /&gt;
{{!-}}&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} None/unspecified&lt;br /&gt;
{{!}}&lt;br /&gt;
* Type set to &amp;lt;code&amp;gt;double&amp;lt;/code&amp;gt;&lt;br /&gt;
* Node set to '''value'''.&lt;br /&gt;
{{!}}&lt;br /&gt;
* Type set to &amp;lt;code&amp;gt;string&amp;lt;/code&amp;gt;&lt;br /&gt;
* Node set to '''value'''.&lt;br /&gt;
{{!}}&lt;br /&gt;
* Type set to &amp;lt;code&amp;gt;vec*d&amp;lt;/code&amp;gt;&lt;br /&gt;
* Node set to '''value'''.&lt;br /&gt;
{{!-}}&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} Bool&lt;br /&gt;
{{!}}&lt;br /&gt;
* If '''value''' != 0, node set to true.&lt;br /&gt;
* If '''value''' == 0, node set to false.&lt;br /&gt;
{{!}}&lt;br /&gt;
* If '''value''' == &amp;quot;true&amp;quot;, node set to true.&lt;br /&gt;
* If '''value''' can be converted to an integer,&amp;lt;br&amp;gt;and != 0, node set to true.&lt;br /&gt;
{{!}} Node set to true.&lt;br /&gt;
{{!-}}&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} Integer&lt;br /&gt;
{{!}} Node set to truncated '''value'''&lt;br /&gt;
{{!}} Node set to '''value ''' converted and truncated to an integer.&lt;br /&gt;
{{!}} Node set to 1.&lt;br /&gt;
{{!-}}&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} Double&lt;br /&gt;
{{!}} Node set to '''value'''.&lt;br /&gt;
{{!}} Node set to '''value''' converted to number.&lt;br /&gt;
{{!}} Throws an error (vector is not a number).&lt;br /&gt;
{{!-}}&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} String&lt;br /&gt;
{{!}} Node set to '''value''' converted to string. {{!!}} Node set to '''value'''. {{!!}} Node not set.&lt;br /&gt;
{{!}}}&lt;br /&gt;
|param1 = rel_path&lt;br /&gt;
|param1text = Optional relative path as a string.&lt;br /&gt;
|param2 = value&lt;br /&gt;
|param2text = Value to set the node to. Must be a string, a valid number, or a vector consisting of 3 or 4 numbers. See table above for conversions and effects.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
node.setValue(&amp;quot;Hi&amp;quot;);&lt;br /&gt;
props.dump(node); # node = &amp;quot;Hi&amp;quot;&lt;br /&gt;
|example2 = var node = props.Node.new();&lt;br /&gt;
node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
node.setValue(&amp;quot;a&amp;quot;, &amp;quot;Hi&amp;quot;);&lt;br /&gt;
props.dump(node); # \a = &amp;quot;Hi&amp;quot;&lt;br /&gt;
|example3 = var node = props.Node.new();&lt;br /&gt;
node.setValue([0.4, 0.2, 1]);&lt;br /&gt;
debug.dump(node.getValue()); # prints &amp;quot;[0.4, 0.2, 1]&amp;quot;&lt;br /&gt;
print(node.getType()); # prints &amp;quot;VEC3D&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== setValues() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.setValues(val);&lt;br /&gt;
|text = {{hatnote|See also {{func link|getValues()|page=this}}.}}&lt;br /&gt;
&lt;br /&gt;
Sets the nodes property tree from a Nasal hash. Scalars will become nodes in the tree and hashes will become named subnodes. Vectors will be converted into indexed nodes, with the values in the vector becoming their values (see examples below).&lt;br /&gt;
|param1 = val&lt;br /&gt;
|param1text = A hash that will become the property tree.&lt;br /&gt;
|example1 = var val = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 100 # &amp;quot;a&amp;quot; will become the subnode's name, and 100 its value&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new();&lt;br /&gt;
node.setValues(val);&lt;br /&gt;
props.dump(node); # dump tree&lt;br /&gt;
|example2 = var val = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 1,&lt;br /&gt;
    &amp;quot;b&amp;quot;: &amp;quot;Hi&amp;quot;,&lt;br /&gt;
    &amp;quot;c&amp;quot;: {&lt;br /&gt;
        &amp;quot;d&amp;quot;: [1, 2, 3]&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new();&lt;br /&gt;
node.setValues(val);&lt;br /&gt;
props.dump(node); # dump tree&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== unalias() ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.Node.unalias();&lt;br /&gt;
|text = Un-aliases the node and returns it to a blank state. Returns 1 on success and 0 on failure (e.g., when used on a tied property).&lt;br /&gt;
|example2 = var node1 = props.Node.new();&lt;br /&gt;
node1.setDoubleValue(2.35);&lt;br /&gt;
var node2 = props.Node.new();&lt;br /&gt;
node2.alias(node1);&lt;br /&gt;
&lt;br /&gt;
props.dump(node2); # equals 2.35&lt;br /&gt;
node2.unalias();&lt;br /&gt;
props.dump(node2); # no value or type&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==== toggleBoolValue() (since FG 2020.1) ====&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = toggleBoolValue();&lt;br /&gt;
|text = Toggle a boolean property. You have to make sure the property is of type bool!&lt;br /&gt;
|example1 = var b = props.Node.new().initNode(&amp;quot;/_test/bool&amp;quot;, 1, &amp;quot;BOOL&amp;quot;);&lt;br /&gt;
print(&amp;quot;bool &amp;quot;, b.getValue());&lt;br /&gt;
b.toggleBoolValue();&lt;br /&gt;
print(&amp;quot;after toggleBoolValue &amp;quot;, b.getValue());&lt;br /&gt;
&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Functions ==&lt;br /&gt;
=== compileCondition() ===&lt;br /&gt;
{{see also|Conditions}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.compileCondition(node);&lt;br /&gt;
|version = 3.2&lt;br /&gt;
|commit = {{fgdata commit|43f8ce|t=commit}}&lt;br /&gt;
|text = Compiles a [[conditions|condition]] property branch and returns a &amp;lt;code&amp;gt;Condition&amp;lt;/code&amp;gt; ghost object or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; on error. This ghost will contain a &amp;lt;code&amp;gt;test()&amp;lt;/code&amp;gt; function that will return the result of the condition as either 1 (true) or 0 (false).&lt;br /&gt;
|param1 = node&lt;br /&gt;
|param1text = Either a props.Node containing the condition, or a string specifying a place in the [[Property Tree]] where there is a condition branch.&lt;br /&gt;
|example1 = var tree = {&lt;br /&gt;
    &amp;quot;equals&amp;quot;: {&lt;br /&gt;
        &amp;quot;property&amp;quot;: '/test',&lt;br /&gt;
        &amp;quot;value&amp;quot;: 12&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new(tree);&lt;br /&gt;
setprop(&amp;quot;/test&amp;quot;, 12);&lt;br /&gt;
&lt;br /&gt;
var cond = props.compileCondition(node);&lt;br /&gt;
print(cond.test()); # prints &amp;quot;1&amp;quot; (true)&lt;br /&gt;
setprop(&amp;quot;/test&amp;quot;, 15);&lt;br /&gt;
print(cond.test()); # prints &amp;quot;0&amp;quot; (false)&lt;br /&gt;
|example2 = var tree = {&lt;br /&gt;
    &amp;quot;equals&amp;quot;: {&lt;br /&gt;
        &amp;quot;property&amp;quot;: '/test',&lt;br /&gt;
        &amp;quot;value&amp;quot;: 12&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
props.globals.getNode(&amp;quot;test2/condition&amp;quot;, 1).setValues(tree); # place it in the Property Tree&lt;br /&gt;
setprop(&amp;quot;/test&amp;quot;, 12);&lt;br /&gt;
&lt;br /&gt;
var cond = props.compileCondition(node);&lt;br /&gt;
print(cond.test()); # prints &amp;quot;1&amp;quot; (true)&lt;br /&gt;
setprop(&amp;quot;/test&amp;quot;, 15);&lt;br /&gt;
print(cond.test()); # prints &amp;quot;0&amp;quot; (false)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== condition() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.condition(node);&lt;br /&gt;
|text = Evaluates a [[conditions|condition]] property branch and returns the result as a boolean.&lt;br /&gt;
|param1 = node&lt;br /&gt;
|param1text = Either a props.Node containing the condition, or a string specifying a place in the [[Property Tree]] where there is a condition branch.&lt;br /&gt;
|example1 = var tree = {&lt;br /&gt;
    &amp;quot;equals&amp;quot;: {&lt;br /&gt;
        &amp;quot;property&amp;quot;: '/test',&lt;br /&gt;
        &amp;quot;value&amp;quot;: 12&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new(tree);&lt;br /&gt;
setprop(&amp;quot;/test&amp;quot;, 12);&lt;br /&gt;
&lt;br /&gt;
print(props.condition(node)); # prints &amp;quot;1&amp;quot; (true)&lt;br /&gt;
setprop(&amp;quot;/test&amp;quot;, 15);&lt;br /&gt;
print(props.condition(node)); # prints &amp;quot;0&amp;quot; (false)&lt;br /&gt;
|example2 = var tree = {&lt;br /&gt;
    &amp;quot;equals&amp;quot;: {&lt;br /&gt;
        &amp;quot;property&amp;quot;: '/test',&lt;br /&gt;
        &amp;quot;value&amp;quot;: 12&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
props.globals.getNode(&amp;quot;test2/condition&amp;quot;, 1).setValues(tree); # place it in the Property Tree&lt;br /&gt;
setprop(&amp;quot;/test&amp;quot;, 12);&lt;br /&gt;
&lt;br /&gt;
print(props.condition(node)); # prints &amp;quot;1&amp;quot; (true)&lt;br /&gt;
setprop(&amp;quot;/test&amp;quot;, 15);&lt;br /&gt;
print(props.condition(node)); # prints &amp;quot;0&amp;quot; (false)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== copy() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.copy(src, dest[, attr]);&lt;br /&gt;
|text = Copies the property tree of the source into the destination node. Note that aliased properties will not be copied.&lt;br /&gt;
|param1 = src&lt;br /&gt;
|param1text = Source &amp;lt;code&amp;gt;props.Node object&amp;lt;/code&amp;gt; to copy from.&lt;br /&gt;
|param2 = dest&lt;br /&gt;
|param2text = Destination &amp;lt;code&amp;gt;props.Node object&amp;lt;/code&amp;gt; to copy to.&lt;br /&gt;
|param3 = attr&lt;br /&gt;
|param3text = If set to 1 (true), attributes will also be copied. Defaults to 0 (false).&lt;br /&gt;
|example1 = var tree = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 1.5,&lt;br /&gt;
    &amp;quot;b&amp;quot;: &amp;quot;Hi&amp;quot;,&lt;br /&gt;
    &amp;quot;c&amp;quot;: {&lt;br /&gt;
        &amp;quot;d&amp;quot;: [1, 2, 3]&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
var src = props.Node.new(tree);&lt;br /&gt;
var dest = props.Node.new();&lt;br /&gt;
props.copy(src, dest);&lt;br /&gt;
props.dump(dest);&lt;br /&gt;
|example2 = var src = props.Node.new();&lt;br /&gt;
var a = src.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
a.setAttribute(&amp;quot;trace-write&amp;quot;, 1);&lt;br /&gt;
a.setIntValue(12);&lt;br /&gt;
var dest = props.Node.new();&lt;br /&gt;
props.copy(src, dest, 1);&lt;br /&gt;
print(dest.getNode(&amp;quot;a&amp;quot;).getAttribute(&amp;quot;trace-write&amp;quot;)); # prints &amp;quot;1&amp;quot; (true)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== dump() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.dump(node);&lt;br /&gt;
|text = Recursively dump the state of a node into the console, showing value and type of each node. Note that as of 10/2016, the value of vec*d type nodes cannot be dumped.&lt;br /&gt;
|param1 = node&lt;br /&gt;
|param1text = Node to dump.&lt;br /&gt;
|example1 = var node = var tree = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 12,&lt;br /&gt;
    &amp;quot;b&amp;quot;: &amp;quot;Hi&amp;quot;,&lt;br /&gt;
    &amp;quot;c&amp;quot;: {&lt;br /&gt;
        &amp;quot;d&amp;quot;: [1, 2, 3]&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
var node = props.Node.new(tree);&lt;br /&gt;
props.dump(node); # dump into console&lt;br /&gt;
|example2 = # Dump the entire Property Tree&lt;br /&gt;
# Warning! This is an intensive operation!&lt;br /&gt;
props.dump(props.globals);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== getNode() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.getNode();&lt;br /&gt;
|version = 3.2&lt;br /&gt;
|commit = {{fgdata commit|807062|t=commit}}&lt;br /&gt;
|text = Shortcut for &amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; inline&amp;gt;props.globals.getNode()&amp;lt;/syntaxhighlight&amp;gt;. See {{func link|getNode()||Node|page=this}} for full documentation.&lt;br /&gt;
|example1 = print(&amp;quot;Current aircraft is: &amp;quot;, props.getNode(&amp;quot;/sim/aircraft&amp;quot;).getValue());&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== nodeList() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.nodeList(arg[, arg[, ...]]);&lt;br /&gt;
|text = Converts its arguments into a vector of node objects if possible and returns that vector. &lt;br /&gt;
|param1 = arg&lt;br /&gt;
|param1text = Object to operate on. Must be a node object, string, vector, hash, function, or &amp;lt;code&amp;gt;prop&amp;lt;/code&amp;gt; ghost. Vectors and hashes must contain any of the other acceptable types, functions must return any of the other types, strings will be assumed to be paths to global properties, and ghosts will be converted into node objects. There may be any number of arguments.&lt;br /&gt;
|example1 = var node = props.Node.new();&lt;br /&gt;
var f = func(){&lt;br /&gt;
    var n = props.Node.new();&lt;br /&gt;
    return n._g;&lt;br /&gt;
}&lt;br /&gt;
var list = props.nodeList(node,&lt;br /&gt;
    &amp;quot;/sim/aircraft&amp;quot;,&lt;br /&gt;
    [&amp;quot;/sim/fg-root&amp;quot;],&lt;br /&gt;
    { &amp;quot;path&amp;quot;: &amp;quot;/sim/fg-home&amp;quot; },&lt;br /&gt;
    f&lt;br /&gt;
);&lt;br /&gt;
debug.dump(list); # dump list&lt;br /&gt;
|example2 = var root = &amp;quot;/sim/version/&amp;quot;;&lt;br /&gt;
var info = [&lt;br /&gt;
    root ~ &amp;quot;build-id&amp;quot;,&lt;br /&gt;
    root ~ &amp;quot;build-number&amp;quot;,&lt;br /&gt;
    root ~ &amp;quot;flightgear&amp;quot;,&lt;br /&gt;
    root ~ &amp;quot;hla-support&amp;quot;,&lt;br /&gt;
    root ~ &amp;quot;openscenegraph&amp;quot;,&lt;br /&gt;
    root ~ &amp;quot;revision&amp;quot;,&lt;br /&gt;
    root ~ &amp;quot;simgear&amp;quot;&lt;br /&gt;
];&lt;br /&gt;
info = props.nodeList(info); # turn into list of nodes&lt;br /&gt;
foreach(var n; info){&lt;br /&gt;
    print(n.getValue()); # dump info&lt;br /&gt;
}&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== runBinding() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.runBinding(node[, module]);&lt;br /&gt;
|text = Runs a [[Bindings|binding]] element in a node object. Returns 1 (true) on success and 0 (false) on failure.&lt;br /&gt;
|param1 = node&lt;br /&gt;
|param1text = A {{tag|binding}} element as a node object.&lt;br /&gt;
|param2 = module&lt;br /&gt;
|param2text = Optional string specifying a module to run Nasal scripts in if the command is &amp;lt;code&amp;gt;nasal&amp;lt;/code&amp;gt;. This argument will not override any {{tag|module}} element in the '''node'''&lt;br /&gt;
|example1 = var tree = {&lt;br /&gt;
    &amp;quot;command&amp;quot;: &amp;quot;dialog-show&amp;quot;,&lt;br /&gt;
    &amp;quot;dialog-name&amp;quot;: &amp;quot;map&amp;quot; # open map&lt;br /&gt;
};&lt;br /&gt;
var binding = props.Node.new(tree);&lt;br /&gt;
props.runBinding(binding);&lt;br /&gt;
|example2 = var tree = {&lt;br /&gt;
    &amp;quot;command&amp;quot;: &amp;quot;nasal&amp;quot;,&lt;br /&gt;
    &amp;quot;script&amp;quot;: 'print(pi)' # prints value of math.pi&lt;br /&gt;
};&lt;br /&gt;
var binding = props.Node.new(tree);&lt;br /&gt;
props.runBinding(binding, &amp;quot;math&amp;quot;);&lt;br /&gt;
|example3 = var tree = {&lt;br /&gt;
    &amp;quot;command&amp;quot;: &amp;quot;nasal&amp;quot;,&lt;br /&gt;
    &amp;quot;script&amp;quot;: 'print(pi)', # prints value of math.pi&lt;br /&gt;
    &amp;quot;module&amp;quot;: &amp;quot;math&amp;quot; # this is used&lt;br /&gt;
};&lt;br /&gt;
var binding = props.Node.new(tree);&lt;br /&gt;
props.runBinding(binding, &amp;quot;debug&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== setAll() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.setAll(base, child, value);&lt;br /&gt;
|text = Sets indexed subnodes in the Property Tree with the same name to the same value.&lt;br /&gt;
|param1 = base&lt;br /&gt;
|param1text = Base path to the nodes.&lt;br /&gt;
|param2 = child&lt;br /&gt;
|param2text = Path to child nodes.&lt;br /&gt;
|param3 = value&lt;br /&gt;
|param3text = Value to set the subnodes to.&lt;br /&gt;
|example1 = # apply 50% throttle to all engines&lt;br /&gt;
props.setAll(&amp;quot;/controls/engines/engine&amp;quot;, &amp;quot;throttle&amp;quot;, 0.5);&lt;br /&gt;
|example2 = var nodes = props.globals.addChildren(&amp;quot;/test&amp;quot;, 3);&lt;br /&gt;
foreach(var node; nodes){&lt;br /&gt;
    node.addChild(&amp;quot;a&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
props.setAll(&amp;quot;/test&amp;quot;, &amp;quot;a&amp;quot;, &amp;quot;Hi&amp;quot;); # set all children (test[*]/a) to &amp;quot;Hi&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== wrap() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.wrap(node);&lt;br /&gt;
|text = Turns &amp;lt;code&amp;gt;prop&amp;lt;/code&amp;gt; ghosts, either in a vector or single, into &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; objects.&lt;br /&gt;
|param1 = node&lt;br /&gt;
|param1text = &amp;lt;code&amp;gt;prop&amp;lt;/code&amp;gt; ghost or vector of such ghosts.&lt;br /&gt;
|example1 = var ghost = canvas._newCanvasGhost();&lt;br /&gt;
var node = props.wrap(ghost._node_ghost);&lt;br /&gt;
props.dump(node);&lt;br /&gt;
|example2 = var vector = [canvas._newCanvasGhost()._node_ghost, props.Node.new()._g];&lt;br /&gt;
var nodes = props.wrap(vector);&lt;br /&gt;
foreach(var node; nodes){&lt;br /&gt;
    props.dump(node);&lt;br /&gt;
    print(&amp;quot;----&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== wrapNode() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.wrapNode(node);&lt;br /&gt;
|text = Turns a &amp;lt;code&amp;gt;prop&amp;lt;/code&amp;gt; ghost into a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object.&lt;br /&gt;
|param1 = node&lt;br /&gt;
|param1text = &amp;lt;code&amp;gt;prop&amp;lt;/code&amp;gt; ghost to convert.&lt;br /&gt;
|example1 = var ghost = canvas._newCanvasGhost();&lt;br /&gt;
var node = props.wrapNode(ghost._node_ghost);&lt;br /&gt;
props.dump(node);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Variable ==&lt;br /&gt;
=== globals ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = props.globals;&lt;br /&gt;
|text = Exposes the [[Property Tree]] as a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object.&lt;br /&gt;
&lt;br /&gt;
|example1 = print(&amp;quot;Current aircraft: &amp;quot;, props.globals.getNode(&amp;quot;/sim/aircraft&amp;quot;).getValue());&lt;br /&gt;
|example2text = Alternative using {{func link|getprop()}}.&lt;br /&gt;
|example2 = print(&amp;quot;Current aircraft: &amp;quot;, getprop(&amp;quot;/sim/aircraft&amp;quot;));&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal namespaces}}&lt;/div&gt;</summary>
		<author><name>TheEagle</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Template:Canvas_Widget_Matrix&amp;diff=137150</id>
		<title>Template:Canvas Widget Matrix</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Template:Canvas_Widget_Matrix&amp;diff=137150"/>
		<updated>2023-01-19T03:39:08Z</updated>

		<summary type="html">&lt;p&gt;TheEagle: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Caution|This table hasn't been updated in a while, as of 09/2022 there is a growing set of widgets available: {{Fgdata commit|459dea77e5ceac592d0d5d58a059ccad1fce6daa|text=(see &amp;quot;Add more Nasal skeleton for new UI objects&amp;quot; )}} Please get involved in helping update this table! If you'd like to get involved in working on widgets or creating new ones, please get in touch via the devel-mailing list to coordinate related efforts with James Turner first.}}&lt;br /&gt;
The following tablet represents a list of Canvas GUI widgets, with the goal being to implement a sufficient number of widgets to re-implement basic [[PUI]] functionality by using the new {{Fgdata file|Nasal/gui/XMLDialog.nas}} module that translates existing PUI/XML dialogs into Canvas dialogs dynamically (i.e. at runtime). To learn more about the rationale, please see [[PUI#Replacement status]], [[Unifying the 2D rendering backend via canvas]] and [[Hackathon Proposal:Canvas Widgets]].&lt;br /&gt;
&lt;br /&gt;
For a (somewhat outdated) overview of PUI widgets used by FlightGear, see {{Readme file|gui}}.&lt;br /&gt;
To learn more about undocumented widgets, see: [[Template:PUI widget]]&lt;br /&gt;
&lt;br /&gt;
For an authoritative list of PUI based widgets used by FlightGear, please refer to {{Flightgear source|src/GUI/FGPUIDialog.cxx|line=882}}.&lt;br /&gt;
&lt;br /&gt;
== Status ==&lt;br /&gt;
'''Last updated: 01/2023&lt;br /&gt;
'''&lt;br /&gt;
* Implementation of missing widgets to emulate [[PUI]] {{Progressbar|50}} (10/20 missing)&lt;br /&gt;
* Introduce a dialog to preview dialogs generated by XMLDialog.nas, so that more contributors can get involved in previewing/testing existing dialogs (including thouse outside fgdata) {{Not done}}&lt;br /&gt;
* C++ additions:&lt;br /&gt;
** expose SGLOG stream for the loglist widget {{Not done}}&lt;br /&gt;
** expose translations for the menubar {{not done}}&lt;br /&gt;
* Introduce meta information at the PropertyList/XML level to be able to version dialogs, for future changes to the underlying format&lt;br /&gt;
* Clean up the existing UI: &lt;br /&gt;
** especially the introduction of submenu support will make it possible to declutter the menubar (debug menu etc) {{Not done}}&lt;br /&gt;
** furthermore, introducing proper {{tag|tab}} support will make it possible to simplify a few existing dialogs {{Not done}}&lt;br /&gt;
* Optimizing and simplifying existing dialogs by introducing new layouting/widget primitives {{Not done}}&lt;br /&gt;
&lt;br /&gt;
== Widgets ==&lt;br /&gt;
&lt;br /&gt;
The majority of missing widgets can be implemented/approximated by using existing ones or adapting/extending those as needed. To get started with widget development, it's a good idea to look at some of the really simple ones first, and then take it from there - for instance, the Label widget is a comparatively simple widget:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Status !!PUI widget {{Flightgear source|src/GUI/FGPUIDialog.cxx|line=882}} !! Complexity (0-10) !! Priority !! Canvas widget !! Remarks&lt;br /&gt;
|-&lt;br /&gt;
| {{Done}} || {{tag|frame}} || 1 || not critical || {{Canvas Widget|widget=Frame}} || see {{Fgdata commit|459dea77e5ceac592d0d5d58a059ccad1fce6daa}}&lt;br /&gt;
|-&lt;br /&gt;
| {{Done}} || {{tag|hrule}}/{{tag|vrule}}|| 1 || not critical || {{Canvas Widget|widget=Rule}} || {{Fgdata commit|459dea77e5ceac592d0d5d58a059ccad1fce6daa}}&lt;br /&gt;
|-&lt;br /&gt;
| {{Progressbar|70}} || {{tag|list}} || 5|| high || {{Canvas Widget|widget=List}} || list, combo and select should probably be tackled together, there's plenty of common/similar functionality&lt;br /&gt;
|-&lt;br /&gt;
| {{Not done}} || {{tag|airport-list}} || 5|| || {{N/a|Not implemented}} ||trivial using navdb APIs and ScrollArea with buttons/labels for each entry and a corresponding event handler [[Howto:Processing_legacy_PUI_dialogs_using_Canvas#Use_Case:_airports.xml]]&lt;br /&gt;
|-&lt;br /&gt;
| {{Not done}} || {{tag|property-list}} || 5|| || {{N/a|Not implemented}} || [[Property Browser]], [[Howto:Processing_legacy_PUI_dialogs_using_Canvas#Coming_up_with_a_property-list|rough prototype]] exists already, basically mapping 3 props.nas APIs to a ScrollArea with buttons for each property/node, allowing the tree to be travsersed interactively. Could be also implemented by using text labels and ASCII art, analogous to [[Nasal_Browser]] or using the new ListView widget&lt;br /&gt;
|-&lt;br /&gt;
| {{Not done}} || {{tag|waypointlist}} || 6|| || {{N/a|Not implemented}} || trivial using navdb APIs and ScrollArea with buttons&lt;br /&gt;
|-&lt;br /&gt;
| {{Not done}} || {{tag|loglist}} || 4|| || {{N/a|Not implemented}} || needs [[SG_LOG]] logstream exposed via [[Nasal/CppBind]]&lt;br /&gt;
|-&lt;br /&gt;
| {{Done}} || {{tag|input}} || 3 || || {{fgdata file|Nasal/canvas/gui/widgets/LineEdit.nas|t=LineEdit}} ||&lt;br /&gt;
|-&lt;br /&gt;
| {{Done}} || {{tag|text}} || 6|| || {{fgdata file|Nasal/canvas/gui/widgets/Label.nas|t=Label}} ||&lt;br /&gt;
|-&lt;br /&gt;
| {{Done}} || {{tag|checkbox}} || 4|| || {{fgdata file|Nasal/canvas/gui/widgets/Checkbox.nas|t=Checkbox}} ||&lt;br /&gt;
|-&lt;br /&gt;
| {{Done}} || {{tag|radio}} || 2|| || {{Canvas Widget|widget=RadioButton}} ||&lt;br /&gt;
|-&lt;br /&gt;
| {{Done}} || {{tag|button}} || 2|| || {{fgdata file|Nasal/canvas/gui/widgets/Button.nas|t=Button}} ||&lt;br /&gt;
|-&lt;br /&gt;
| {{Not done}} || {{tag|map}} || 5|| || {{N/a|Not implemented, see also [[Canvas MapStructure]]}} || {{Progressbar|20}} [[Howto:Creating_a_Canvas_GUI_Widget#Implementing_a_Map_widget]]&lt;br /&gt;
|-&lt;br /&gt;
| {{Not done}} || {{tag|canvas}} || 4|| || Not needed (for obvious reasons) || While we don't necessarily need a canvas widget, we need to map the PUI widget to a corresponding equivalent, i.e. an embedded Canvas with its own scripting block to animate the whole thing [[Howto:Processing_legacy_PUI_dialogs_using_Canvas#Embedded_Canvas]]&lt;br /&gt;
|-&lt;br /&gt;
| {{Not done}} || {{tag|combo}} || 6|| || {{N/a|Not implemented}} || list, combo and select can be unified&lt;br /&gt;
|-&lt;br /&gt;
| {{Not done}} || {{tag|select}} || 7|| {{N/a|Not implemented}} ||list, combo and select can be unified&lt;br /&gt;
|-&lt;br /&gt;
| {{Done}} || {{tag|slider}} || 4|| || {{fgdata file|Nasal/canvas/gui/widgets/Slider.nas|t=Slider}} ||&lt;br /&gt;
|-&lt;br /&gt;
| {{Done}} || {{tag|dial}} || 3|| || {{fgdata file|Nasal/canvas/gui/widgets/Dial.nas|t=Dial}} ||&lt;br /&gt;
|-&lt;br /&gt;
| {{Not done}} || {{tag|textbox}} || 6|| || {{fgdata file|Nasal/canvas/gui/widgets/LineEdit.nas|t=LineEdit}} and/or {{fgdata file|Nasal/canvas/gui/widgets/Label.nas|t=Label}} ||&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Menubar ==&lt;br /&gt;
&lt;br /&gt;
Furthermore, we need to replace the [[Menubar]] itself, which is also implemented via PUI - for that purpose, there are some special widgets needed:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!   Status || Complexity (0-10) !! Priority !! Canvas widget !! Remarks&lt;br /&gt;
|-&lt;br /&gt;
|  {{Done}} || 3 || 10 || {{Canvas Widget|widget=MenuBar}} || see {{Fgdata commit|459dea77e5ceac592d0d5d58a059ccad1fce6daa}}&lt;br /&gt;
|-&lt;br /&gt;
|  {{Done}} || 3 || 10 || {{Canvas Widget|widget=PopupMenu}} || see {{Merge-request|project=fgdata|id=305|text=Merge request:Implement canvas menus}}&lt;br /&gt;
see {{Fgdata commit|459dea77e5ceac592d0d5d58a059ccad1fce6daa}} &amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37756296/&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37756390/&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37756347/&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|  {{Done}}|| 3 || 10 || {{Canvas Widget|widget=MenuItem}} || the design  goal of MenuItem is to allow both simple items (text) but also check-ables ones, and in the future, simple widgets to be inside an item (eg, a slider or +/- controls in a menu item, or a multi-state selector of a few choices)&lt;br /&gt;
&lt;br /&gt;
So, the aim was to have a constructor taking either a text string, or a callback which creates the widget content on demand (when the menu is shown)&lt;br /&gt;
&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37756408/&amp;lt;/ref&amp;gt;  It's using an&lt;br /&gt;
item-orientated approach, and the menu has an addItem method to add an&lt;br /&gt;
arbitrary widget (in most cases this will be a MenuItem instance, but you&lt;br /&gt;
could use a slider just as well) and a createItem method that takes a text&lt;br /&gt;
and a callback and an optional icon, from which a new MenuItem is created.&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37756436/&amp;lt;/ref&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|  {{Done}}||  || 10 || {{Canvas Widget|widget=Menu}} || see {{Fgdata commit|459dea77e5ceac592d0d5d58a059ccad1fce6daa}}&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
In addition, there are some Canvas specific widgets that are not currently supported by PUI:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Complexity (0-10) !! Priority !! Canvas widget !! Remarks&lt;br /&gt;
|-&lt;br /&gt;
|  6 || || {{fgdata file|Nasal/canvas/gui/widgets/ScrollArea.nas|t=ScrollArea}} || serves as the generic building block for any PUI list-type (waypoints/airports) with labels/buttons added for each entry shown&lt;br /&gt;
|-&lt;br /&gt;
|  4|| || {{fgdata file|Nasal/canvas/gui/widgets/TabWidget.nas|t=TabWidget}} || A [https://sourceforge.net/p/flightgear/mailman/message/37754597/ tab widget] as everyone knows it from their web browsr (pending review as of 12/2022 see {{Merge-request|project=fgdata|id=303|text=Merge request:Canvas tab widget implementation}})&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Cleaning up dialogs ==&lt;br /&gt;
&lt;br /&gt;
Once the PUI/XML widget set is fully supported by the XMLDialog parser, it will make sense to review existing dialogs to identify opportunities to introduce new widgets and simplify existing dialogs. &lt;br /&gt;
As a matter of fact, a number of PUI/XML dialogs are currently working around PUI limitations by using awkward hacks to implement custom widgets. These show up by making excessive use of Nasal blobs as well as usage of the cmdarg() API, and they're a pain to maintain:&lt;br /&gt;
&lt;br /&gt;
{{collapsible script&lt;br /&gt;
| title  = PUI/XML dialogs making excessive use of embedded Nasal&lt;br /&gt;
| script = &lt;br /&gt;
* $FG_ROOT/gui/dialogs/airports.xml Total Nasal LOC: 415&lt;br /&gt;
* $FG_ROOT/gui/dialogs/advanced-graphics-options.xml Total Nasal LOC: 413&lt;br /&gt;
* $FG_ROOT/gui/dialogs/checklist.xml Total Nasal LOC: 399&lt;br /&gt;
* $FG_ROOT/gui/dialogs/weather.xml Total Nasal LOC: 298&lt;br /&gt;
* $FG_ROOT/gui/dialogs/chat-menu.xml Total Nasal LOC: 297&lt;br /&gt;
* $FG_ROOT/gui/dialogs/static-lod.xml Total Nasal LOC: 270&lt;br /&gt;
* $FG_ROOT/gui/dialogs/route-manager.xml Total Nasal LOC: 263&lt;br /&gt;
* $FG_ROOT/gui/dialogs/map-canvas.xml Total Nasal LOC: 188&lt;br /&gt;
* $FG_ROOT/gui/dialogs/file-select.xml Total Nasal LOC: 161&lt;br /&gt;
* $FG_ROOT/gui/dialogs/joystick-config.xml Total Nasal LOC: 159&lt;br /&gt;
* $FG_ROOT/gui/dialogs/location-in-air.xml Total Nasal LOC: 153&lt;br /&gt;
* $FG_ROOT/gui/dialogs/gps.xml Total Nasal LOC: 138&lt;br /&gt;
* $FG_ROOT/gui/dialogs/nasal-console.xml Total Nasal LOC: 132&lt;br /&gt;
* $FG_ROOT/gui/dialogs/button-config.xml Total Nasal LOC: 124&lt;br /&gt;
* $FG_ROOT/gui/dialogs/property-browser.xml Total Nasal LOC: 121&lt;br /&gt;
* $FG_ROOT/gui/dialogs/replay.xml Total Nasal LOC: 115&lt;br /&gt;
* $FG_ROOT/gui/dialogs/marker-adjust.xml Total Nasal LOC: 105&lt;br /&gt;
* $FG_ROOT/gui/dialogs/jetways-adjust.xml Total Nasal LOC: 103&lt;br /&gt;
* $FG_ROOT/gui/dialogs/multiplayer.xml Total Nasal LOC: 98&lt;br /&gt;
* $FG_ROOT/gui/dialogs/autopilot.xml Total Nasal LOC: 97&lt;br /&gt;
* $FG_ROOT/gui/dialogs/flight-recorder-load.xml Total Nasal LOC: 89&lt;br /&gt;
* $FG_ROOT/gui/dialogs/fgcom.xml Total Nasal LOC: 85&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Introducing new Widgets ==&lt;br /&gt;
There is great scope to make /better/ Nasal APIs for items such as combo-boxes and pickers, especially ICAO and radio frequency pickers, but that's all 'improving the GUI' work than can happen once we've ditched PLIB and have something hackable.&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/29583931/&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
It's been repeatedly pointed out by a number of contributors, that the sheer amount of Nasal blobs embedded in PUI/XML dialogs is making UI development quite a hassle (especially those outside bindings, i.e. inside open/close blocks and the canvas load equivalent). Thus, it will make sense to review which new widgets could be introduced to get rid of these Nasal blobs:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Status || Complexity (0-10) !! Priority !! Canvas widget !! Remarks&lt;br /&gt;
|-&lt;br /&gt;
|  {{Not done}} || 4|| low || AirportView  ||  this would help simplify airports.xml&lt;br /&gt;
|-&lt;br /&gt;
|  {{Not done}} || 4|| low || ChecklistWidget  ||  this would help simplify checklists.xml&lt;br /&gt;
|-&lt;br /&gt;
|  {{Not done}} || 4|| low || PilotList  ||  pilot list dialog&lt;br /&gt;
|-&lt;br /&gt;
|  {{Not done}} || 4|| low || SubsystemList  ||  performance monitor&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Meta info for dialogs ==&lt;br /&gt;
&lt;br /&gt;
These changes will also simplify development of the Phi/mongoose based UI, because we would be introducting dedicated widgets, which could just as well be implemented in JavaScript. However, for these changes to be introduced, we need to make some additions to the PropertyList/XML format used by GUI/XML files, namely:&lt;br /&gt;
&lt;br /&gt;
* introducing a version attribute for the {{tag|PropertyList}} root&lt;br /&gt;
* introducing a {{tag|FileType}} node to specify that the file is a GUI/XML description: &amp;lt;code&amp;gt;&amp;lt;FileType&amp;gt;GUI&amp;lt;/FileType&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
* introducing a {{tag|version}} node to be able to make breaking changes in the future&lt;br /&gt;
&lt;br /&gt;
This way, we can easily update widgets/layouting primitives or introduce new ones, without having to be concerned about existing UI resources.&lt;br /&gt;
&lt;br /&gt;
For instance, here's $FG_ROOT/gui/dialogs/scenery_loaded.xml with additional meta information, so that we can safely make changes in the future:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot;?&amp;gt;&lt;br /&gt;
&amp;lt;PropertyList version=&amp;quot;1.0&amp;quot;&amp;gt;&lt;br /&gt;
  &amp;lt;FileType&amp;gt;GUIResource&amp;lt;/FileType&amp;gt;&lt;br /&gt;
  &amp;lt;version&amp;gt;1.0&amp;lt;/version&amp;gt;&lt;br /&gt;
  &amp;lt;name&amp;gt;scenery_loading&amp;lt;/name&amp;gt;&lt;br /&gt;
  &amp;lt;layout&amp;gt;vbox&amp;lt;/layout&amp;gt;&lt;br /&gt;
  &amp;lt;modal&amp;gt;true&amp;lt;/modal&amp;gt;&lt;br /&gt;
  &amp;lt;text&amp;gt;&lt;br /&gt;
    &amp;lt;label&amp;gt;Scenery Loading...&amp;lt;/label&amp;gt;&lt;br /&gt;
    &amp;lt;padding&amp;gt;30&amp;lt;/padding&amp;gt;&lt;br /&gt;
  &amp;lt;/text&amp;gt;&lt;br /&gt;
&amp;lt;/PropertyList&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Reusability ==&lt;br /&gt;
Once our existing PUI widgets and dialogs are fully emulated, and dialogs have become better optimized (=less Nasal blobs). It will be a good idea to revisit some concerns  and ideas expressed by Torsten D. based on his experience creating Phi:&lt;br /&gt;
&lt;br /&gt;
Torsten once stated rightly that it would be benefical for any new UI to separate the logic included in most dialogs from the presentation anyway to make it reusable.&lt;br /&gt;
His vision was to have some kind of service implemented in FlightGear that wraps all complex tasks into service calls and responses. The command system is a&lt;br /&gt;
good start to trigger something, but it does not yet return anything.&lt;br /&gt;
&lt;br /&gt;
Currently, all dialogs are a mixture of calling fg-commands and setting properties directly.&lt;br /&gt;
&lt;br /&gt;
Phi already has some basic support for aircraft specific elements. &amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/34532860/&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We should discuss and prototype a new type of asynchronous fgcommand layer that may also return properties/values, this could benefit both: The Canvas UI, but also Phi. In the future, Canvas UI dialogs may thus also use these same fgcommands which would be running asynchronously (possibly over the same/existing non-blocking HTTP back-end already used by Phi).&lt;br /&gt;
&lt;br /&gt;
At that point it will become increasingly feasible to also provide a module for Phi to interpret these new PUI/XML dialogs, because these would be primarily XML-based, should contain zero (or very little) code. And the new could be that Nasal code that still needs to stay inside such dialogs, will always need to be registered with the SGCommandMgr via the addCommand() API - that way, both front-ends would be able to interpret/execute such dialogs, which would mean that aircraft developers would not need to port/maintain two different sets of dialogs to support both front-ends. &lt;br /&gt;
&lt;br /&gt;
Also, future updates to the UI will become tremendously easier once our dialogs are purely declarative/XML.&lt;br /&gt;
&lt;br /&gt;
{{Appendix}}&lt;/div&gt;</summary>
		<author><name>TheEagle</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Canvas_widget_matrix&amp;diff=137149</id>
		<title>Canvas widget matrix</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Canvas_widget_matrix&amp;diff=137149"/>
		<updated>2023-01-18T16:38:13Z</updated>

		<summary type="html">&lt;p&gt;TheEagle: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Stub}}&lt;br /&gt;
{{Canvas Navigation}}&lt;br /&gt;
&lt;br /&gt;
{{Main article|Howto:Processing legacy PUI dialogs using Canvas}}&lt;br /&gt;
&lt;br /&gt;
This is intended to become a table with existing Canvas widgets, as well as a side-by-side comparison with [[PUI]] widgets to list missing features, progress/completion status, including links to all relevant resources (docs, source code pointers)&lt;br /&gt;
&amp;lt;!--&lt;br /&gt;
* {{Canvas Widget|widget=Rule}} (since 09/2022) {{Fgdata commit|459dea77e5ceac592d0d5d58a059ccad1fce6daa|text=(see &amp;quot;Add more Nasal skeleton for new UI objects&amp;quot; )}}&lt;br /&gt;
* {{Canvas Widget|widget=Button}}&lt;br /&gt;
* {{Canvas Widget|widget=Slider}} (since 09/2022) {{Fgdata commit|459dea77e5ceac592d0d5d58a059ccad1fce6daa|text=(see &amp;quot;Add more Nasal skeleton for new UI objects&amp;quot; )}}&lt;br /&gt;
* {{Canvas Widget|widget=Dial}} (since 09/2022) {{Fgdata commit|459dea77e5ceac592d0d5d58a059ccad1fce6daa|text=(see &amp;quot;Add more Nasal skeleton for new UI objects&amp;quot; )}}&lt;br /&gt;
* {{Canvas Widget|widget=Frame}} (since 09/2022) {{Fgdata commit|459dea77e5ceac592d0d5d58a059ccad1fce6daa|text=(see &amp;quot;Add more Nasal skeleton for new UI objects&amp;quot; )}}&lt;br /&gt;
* {{Canvas Widget|widget=RadioButton}} (since 09/2022) {{Fgdata commit|459dea77e5ceac592d0d5d58a059ccad1fce6daa|text=(see &amp;quot;Add more Nasal skeleton for new UI objects&amp;quot; )}}&lt;br /&gt;
* {{Canvas Widget|widget=CheckBox}}&lt;br /&gt;
* {{Canvas Widget|widget=Label}}&lt;br /&gt;
* {{Canvas Widget|widget=LineEdit}} (editable text box) &lt;br /&gt;
* {{Canvas Widget|widget=Menu}} (since 09/2022) {{Fgdata commit|459dea77e5ceac592d0d5d58a059ccad1fce6daa|text=(see &amp;quot;Add more Nasal skeleton for new UI objects&amp;quot; )}}&lt;br /&gt;
* {{Canvas Widget|widget=PopupMenu}} (since 09/2022) {{Fgdata commit|459dea77e5ceac592d0d5d58a059ccad1fce6daa|text=(see &amp;quot;Add more Nasal skeleton for new UI objects&amp;quot; )}}&lt;br /&gt;
* {{Canvas Widget|widget=MenuBar}} (since 09/2022) {{Fgdata commit|459dea77e5ceac592d0d5d58a059ccad1fce6daa|text=(see &amp;quot;Add more Nasal skeleton for new UI objects&amp;quot; )}}&lt;br /&gt;
* {{Canvas Widget|widget=ScrollArea}} (for scrollable content)&lt;br /&gt;
--&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Upcoming widgets (currently pending review)&lt;br /&gt;
* list widget (12/2022 &amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37754894/&amp;lt;/ref&amp;gt;) see {{Merge-request|project=fgdata|id=304|text=Merge request:Canvas ListView implementation}} {{Ongoing}}&lt;br /&gt;
&lt;br /&gt;
{{Canvas Widget Matrix}}&lt;br /&gt;
&lt;br /&gt;
[[Category:Canvas GUI]]&lt;/div&gt;</summary>
		<author><name>TheEagle</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Template:Canvas_Widget_Matrix&amp;diff=137148</id>
		<title>Template:Canvas Widget Matrix</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Template:Canvas_Widget_Matrix&amp;diff=137148"/>
		<updated>2023-01-18T16:38:04Z</updated>

		<summary type="html">&lt;p&gt;TheEagle: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Caution|This table hasn't been updated in a while, as of 09/2022 there is a growing set of widgets available: {{Fgdata commit|459dea77e5ceac592d0d5d58a059ccad1fce6daa|text=(see &amp;quot;Add more Nasal skeleton for new UI objects&amp;quot; )}} Please get involved in helping update this table! If you'd like to get involved in working on widgets or creating new ones, please get in touch via the devel-mailing list to coordinate related efforts with James Turner first.}}&lt;br /&gt;
The following tablet represents a list of Canvas GUI widgets, with the goal being to implement a sufficient number of widgets to re-implement basic [[PUI]] functionality by using the new {{Fgdata file|Nasal/gui/XMLDialog.nas}} module that translates existing PUI/XML dialogs into Canvas dialogs dynamically (i.e. at runtime). To learn more about the rationale, please see [[PUI#Replacement status]], [[Unifying the 2D rendering backend via canvas]] and [[Hackathon Proposal:Canvas Widgets]].&lt;br /&gt;
&lt;br /&gt;
For a (somewhat outdated) overview of PUI widgets used by FlightGear, see {{Readme file|gui}}.&lt;br /&gt;
To learn more about undocumented widgets, see: [[Template:PUI widget]]&lt;br /&gt;
&lt;br /&gt;
For an authoritative list of PUI based widgets used by FlightGear, please refer to {{Flightgear source|src/GUI/FGPUIDialog.cxx|line=882}}.&lt;br /&gt;
&lt;br /&gt;
== Status ==&lt;br /&gt;
'''Last updated: 01/2023&lt;br /&gt;
'''&lt;br /&gt;
* Implementation of missing widgets to emulate [[PUI]] {{Progressbar|50}} (10/20 missing)&lt;br /&gt;
* Introduce a dialog to preview dialogs generated by XMLDialog.nas, so that more contributors can get involved in previewing/testing existing dialogs (including thouse outside fgdata) {{Not done}}&lt;br /&gt;
* C++ additions:&lt;br /&gt;
** expose SGLOG stream for the loglist widget {{Not done}}&lt;br /&gt;
** expose translations for the menubar {{not done}}&lt;br /&gt;
* Introduce meta information at the PropertyList/XML level to be able to version dialogs, for future changes to the underlying format&lt;br /&gt;
* Clean up the existing UI: &lt;br /&gt;
** especially the introduction of submenu support will make it possible to declutter the menubar (debug menu etc) {{Not done}}&lt;br /&gt;
** furthermore, introducing proper {{tag|tab}} support will make it possible to simplify a few existing dialogs {{Not done}}&lt;br /&gt;
* Optimizing and simplifying existing dialogs by introducing new layouting/widget primitives {{Not done}}&lt;br /&gt;
&lt;br /&gt;
== Widgets ==&lt;br /&gt;
&lt;br /&gt;
The majority of missing widgets can be implemented/approximated by using existing ones or adapting/extending those as needed. To get started with widget development, it's a good idea to look at some of the really simple ones first, and then take it from there - for instance, the Label widget is a comparatively simple widget:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Status !!PUI widget {{Flightgear source|src/GUI/FGPUIDialog.cxx|line=882}} !! Complexity (0-10) !! Priority !! Canvas widget !! Remarks&lt;br /&gt;
|-&lt;br /&gt;
| {{Done}} || {{tag|frame}} || 1 || not critical || {{Canvas Widget|widget=Frame}} || see {{Fgdata commit|459dea77e5ceac592d0d5d58a059ccad1fce6daa}}&lt;br /&gt;
|-&lt;br /&gt;
| {{Done}} || {{tag|hrule}}/{{tag|vrule}}|| 1 || not critical || {{Canvas Widget|widget=Rule}} || {{Fgdata commit|459dea77e5ceac592d0d5d58a059ccad1fce6daa}}&lt;br /&gt;
|-&lt;br /&gt;
| {{Progressbar|70}} || {{tag|list}} || 5|| high || {{Progressbar|70}}&amp;lt;p/&amp;gt;{{Merge-request|project=fgdata|id=304|text=Merge request:Canvas ListView implementation}}&amp;lt;p/&amp;gt;being developed as part of the new log book addon &amp;lt;ref&amp;gt;https://forum.flightgear.org/viewtopic.php?f=89&amp;amp;t=41070&amp;amp;p=408121&amp;amp;hilit=list#p408071&amp;lt;/ref&amp;gt; || list, combo and select should probably be tackled together, there's plenty of common/similar functionality&lt;br /&gt;
|-&lt;br /&gt;
| {{Not done}} || {{tag|airport-list}} || 5|| || {{N/a|Not implemented}} ||trivial using navdb APIs and ScrollArea with buttons/labels for each entry and a corresponding event handler [[Howto:Processing_legacy_PUI_dialogs_using_Canvas#Use_Case:_airports.xml]]&lt;br /&gt;
|-&lt;br /&gt;
| {{Not done}} || {{tag|property-list}} || 5|| || {{N/a|Not implemented}} || [[Property Browser]], [[Howto:Processing_legacy_PUI_dialogs_using_Canvas#Coming_up_with_a_property-list|rough prototype]] exists already, basically mapping 3 props.nas APIs to a ScrollArea with buttons for each property/node, allowing the tree to be travsersed interactively. Could be also implemented by using text labels and ASCII art, analogous to [[Nasal_Browser]] or using the new ListView widget&lt;br /&gt;
|-&lt;br /&gt;
| {{Not done}} || {{tag|waypointlist}} || 6|| || {{N/a|Not implemented}} || trivial using navdb APIs and ScrollArea with buttons&lt;br /&gt;
|-&lt;br /&gt;
| {{Not done}} || {{tag|loglist}} || 4|| || {{N/a|Not implemented}} || needs [[SG_LOG]] logstream exposed via [[Nasal/CppBind]]&lt;br /&gt;
|-&lt;br /&gt;
| {{Done}} || {{tag|input}} || 3 || || {{fgdata file|Nasal/canvas/gui/widgets/LineEdit.nas|t=LineEdit}} ||&lt;br /&gt;
|-&lt;br /&gt;
| {{Done}} || {{tag|text}} || 6|| || {{fgdata file|Nasal/canvas/gui/widgets/Label.nas|t=Label}} ||&lt;br /&gt;
|-&lt;br /&gt;
| {{Done}} || {{tag|checkbox}} || 4|| || {{fgdata file|Nasal/canvas/gui/widgets/Checkbox.nas|t=Checkbox}} ||&lt;br /&gt;
|-&lt;br /&gt;
| {{Done}} || {{tag|radio}} || 2|| || {{Canvas Widget|widget=RadioButton}} ||&lt;br /&gt;
|-&lt;br /&gt;
| {{Done}} || {{tag|button}} || 2|| || {{fgdata file|Nasal/canvas/gui/widgets/Button.nas|t=Button}} ||&lt;br /&gt;
|-&lt;br /&gt;
| {{Not done}} || {{tag|map}} || 5|| || {{N/a|Not implemented, see also [[Canvas MapStructure]]}} || {{Progressbar|20}} [[Howto:Creating_a_Canvas_GUI_Widget#Implementing_a_Map_widget]]&lt;br /&gt;
|-&lt;br /&gt;
| {{Not done}} || {{tag|canvas}} || 4|| || Not needed (for obvious reasons) || While we don't necessarily need a canvas widget, we need to map the PUI widget to a corresponding equivalent, i.e. an embedded Canvas with its own scripting block to animate the whole thing [[Howto:Processing_legacy_PUI_dialogs_using_Canvas#Embedded_Canvas]]&lt;br /&gt;
|-&lt;br /&gt;
| {{Not done}} || {{tag|combo}} || 6|| || {{N/a|Not implemented}} || list, combo and select can be unified&lt;br /&gt;
|-&lt;br /&gt;
| {{Not done}} || {{tag|select}} || 7|| {{N/a|Not implemented}} ||list, combo and select can be unified&lt;br /&gt;
|-&lt;br /&gt;
| {{Done}} || {{tag|slider}} || 4|| || {{fgdata file|Nasal/canvas/gui/widgets/Slider.nas|t=Slider}} ||&lt;br /&gt;
|-&lt;br /&gt;
| {{Done}} || {{tag|dial}} || 3|| || {{fgdata file|Nasal/canvas/gui/widgets/Dial.nas|t=Dial}} ||&lt;br /&gt;
|-&lt;br /&gt;
| {{Not done}} || {{tag|textbox}} || 6|| || {{fgdata file|Nasal/canvas/gui/widgets/LineEdit.nas|t=LineEdit}} and/or {{fgdata file|Nasal/canvas/gui/widgets/Label.nas|t=Label}} ||&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Menubar ==&lt;br /&gt;
&lt;br /&gt;
Furthermore, we need to replace the [[Menubar]] itself, which is also implemented via PUI - for that purpose, there are some special widgets needed:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!   Status || Complexity (0-10) !! Priority !! Canvas widget !! Remarks&lt;br /&gt;
|-&lt;br /&gt;
|  {{Done}} || 3 || 10 || {{Canvas Widget|widget=MenuBar}} || see {{Fgdata commit|459dea77e5ceac592d0d5d58a059ccad1fce6daa}}&lt;br /&gt;
|-&lt;br /&gt;
|  {{Done}} || 3 || 10 || {{Canvas Widget|widget=PopupMenu}} || see {{Merge-request|project=fgdata|id=305|text=Merge request:Implement canvas menus}}&lt;br /&gt;
see {{Fgdata commit|459dea77e5ceac592d0d5d58a059ccad1fce6daa}} &amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37756296/&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37756390/&amp;lt;/ref&amp;gt;&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37756347/&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|  {{Done}}|| 3 || 10 || {{Canvas Widget|widget=MenuItem}} || the design  goal of MenuItem is to allow both simple items (text) but also check-ables ones, and in the future, simple widgets to be inside an item (eg, a slider or +/- controls in a menu item, or a multi-state selector of a few choices)&lt;br /&gt;
&lt;br /&gt;
So, the aim was to have a constructor taking either a text string, or a callback which creates the widget content on demand (when the menu is shown)&lt;br /&gt;
&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37756408/&amp;lt;/ref&amp;gt;  It's using an&lt;br /&gt;
item-orientated approach, and the menu has an addItem method to add an&lt;br /&gt;
arbitrary widget (in most cases this will be a MenuItem instance, but you&lt;br /&gt;
could use a slider just as well) and a createItem method that takes a text&lt;br /&gt;
and a callback and an optional icon, from which a new MenuItem is created.&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37756436/&amp;lt;/ref&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|  {{Done}}||  || 10 || {{Canvas Widget|widget=Menu}} || see {{Fgdata commit|459dea77e5ceac592d0d5d58a059ccad1fce6daa}}&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
In addition, there are some Canvas specific widgets that are not currently supported by PUI:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Complexity (0-10) !! Priority !! Canvas widget !! Remarks&lt;br /&gt;
|-&lt;br /&gt;
|  6 || || {{fgdata file|Nasal/canvas/gui/widgets/ScrollArea.nas|t=ScrollArea}} || serves as the generic building block for any PUI list-type (waypoints/airports) with labels/buttons added for each entry shown&lt;br /&gt;
|-&lt;br /&gt;
|  4|| || {{fgdata file|Nasal/canvas/gui/widgets/TabWidget.nas|t=TabWidget}} || A [https://sourceforge.net/p/flightgear/mailman/message/37754597/ tab widget] as everyone knows it from their web browsr (pending review as of 12/2022 see {{Merge-request|project=fgdata|id=303|text=Merge request:Canvas tab widget implementation}})&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Cleaning up dialogs ==&lt;br /&gt;
&lt;br /&gt;
Once the PUI/XML widget set is fully supported by the XMLDialog parser, it will make sense to review existing dialogs to identify opportunities to introduce new widgets and simplify existing dialogs. &lt;br /&gt;
As a matter of fact, a number of PUI/XML dialogs are currently working around PUI limitations by using awkward hacks to implement custom widgets. These show up by making excessive use of Nasal blobs as well as usage of the cmdarg() API, and they're a pain to maintain:&lt;br /&gt;
&lt;br /&gt;
{{collapsible script&lt;br /&gt;
| title  = PUI/XML dialogs making excessive use of embedded Nasal&lt;br /&gt;
| script = &lt;br /&gt;
* $FG_ROOT/gui/dialogs/airports.xml Total Nasal LOC: 415&lt;br /&gt;
* $FG_ROOT/gui/dialogs/advanced-graphics-options.xml Total Nasal LOC: 413&lt;br /&gt;
* $FG_ROOT/gui/dialogs/checklist.xml Total Nasal LOC: 399&lt;br /&gt;
* $FG_ROOT/gui/dialogs/weather.xml Total Nasal LOC: 298&lt;br /&gt;
* $FG_ROOT/gui/dialogs/chat-menu.xml Total Nasal LOC: 297&lt;br /&gt;
* $FG_ROOT/gui/dialogs/static-lod.xml Total Nasal LOC: 270&lt;br /&gt;
* $FG_ROOT/gui/dialogs/route-manager.xml Total Nasal LOC: 263&lt;br /&gt;
* $FG_ROOT/gui/dialogs/map-canvas.xml Total Nasal LOC: 188&lt;br /&gt;
* $FG_ROOT/gui/dialogs/file-select.xml Total Nasal LOC: 161&lt;br /&gt;
* $FG_ROOT/gui/dialogs/joystick-config.xml Total Nasal LOC: 159&lt;br /&gt;
* $FG_ROOT/gui/dialogs/location-in-air.xml Total Nasal LOC: 153&lt;br /&gt;
* $FG_ROOT/gui/dialogs/gps.xml Total Nasal LOC: 138&lt;br /&gt;
* $FG_ROOT/gui/dialogs/nasal-console.xml Total Nasal LOC: 132&lt;br /&gt;
* $FG_ROOT/gui/dialogs/button-config.xml Total Nasal LOC: 124&lt;br /&gt;
* $FG_ROOT/gui/dialogs/property-browser.xml Total Nasal LOC: 121&lt;br /&gt;
* $FG_ROOT/gui/dialogs/replay.xml Total Nasal LOC: 115&lt;br /&gt;
* $FG_ROOT/gui/dialogs/marker-adjust.xml Total Nasal LOC: 105&lt;br /&gt;
* $FG_ROOT/gui/dialogs/jetways-adjust.xml Total Nasal LOC: 103&lt;br /&gt;
* $FG_ROOT/gui/dialogs/multiplayer.xml Total Nasal LOC: 98&lt;br /&gt;
* $FG_ROOT/gui/dialogs/autopilot.xml Total Nasal LOC: 97&lt;br /&gt;
* $FG_ROOT/gui/dialogs/flight-recorder-load.xml Total Nasal LOC: 89&lt;br /&gt;
* $FG_ROOT/gui/dialogs/fgcom.xml Total Nasal LOC: 85&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Introducing new Widgets ==&lt;br /&gt;
There is great scope to make /better/ Nasal APIs for items such as combo-boxes and pickers, especially ICAO and radio frequency pickers, but that's all 'improving the GUI' work than can happen once we've ditched PLIB and have something hackable.&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/29583931/&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
It's been repeatedly pointed out by a number of contributors, that the sheer amount of Nasal blobs embedded in PUI/XML dialogs is making UI development quite a hassle (especially those outside bindings, i.e. inside open/close blocks and the canvas load equivalent). Thus, it will make sense to review which new widgets could be introduced to get rid of these Nasal blobs:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Status || Complexity (0-10) !! Priority !! Canvas widget !! Remarks&lt;br /&gt;
|-&lt;br /&gt;
|  {{Not done}} || 4|| low || AirportView  ||  this would help simplify airports.xml&lt;br /&gt;
|-&lt;br /&gt;
|  {{Not done}} || 4|| low || ChecklistWidget  ||  this would help simplify checklists.xml&lt;br /&gt;
|-&lt;br /&gt;
|  {{Not done}} || 4|| low || PilotList  ||  pilot list dialog&lt;br /&gt;
|-&lt;br /&gt;
|  {{Not done}} || 4|| low || SubsystemList  ||  performance monitor&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Meta info for dialogs ==&lt;br /&gt;
&lt;br /&gt;
These changes will also simplify development of the Phi/mongoose based UI, because we would be introducting dedicated widgets, which could just as well be implemented in JavaScript. However, for these changes to be introduced, we need to make some additions to the PropertyList/XML format used by GUI/XML files, namely:&lt;br /&gt;
&lt;br /&gt;
* introducing a version attribute for the {{tag|PropertyList}} root&lt;br /&gt;
* introducing a {{tag|FileType}} node to specify that the file is a GUI/XML description: &amp;lt;code&amp;gt;&amp;lt;FileType&amp;gt;GUI&amp;lt;/FileType&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
* introducing a {{tag|version}} node to be able to make breaking changes in the future&lt;br /&gt;
&lt;br /&gt;
This way, we can easily update widgets/layouting primitives or introduce new ones, without having to be concerned about existing UI resources.&lt;br /&gt;
&lt;br /&gt;
For instance, here's $FG_ROOT/gui/dialogs/scenery_loaded.xml with additional meta information, so that we can safely make changes in the future:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot;?&amp;gt;&lt;br /&gt;
&amp;lt;PropertyList version=&amp;quot;1.0&amp;quot;&amp;gt;&lt;br /&gt;
  &amp;lt;FileType&amp;gt;GUIResource&amp;lt;/FileType&amp;gt;&lt;br /&gt;
  &amp;lt;version&amp;gt;1.0&amp;lt;/version&amp;gt;&lt;br /&gt;
  &amp;lt;name&amp;gt;scenery_loading&amp;lt;/name&amp;gt;&lt;br /&gt;
  &amp;lt;layout&amp;gt;vbox&amp;lt;/layout&amp;gt;&lt;br /&gt;
  &amp;lt;modal&amp;gt;true&amp;lt;/modal&amp;gt;&lt;br /&gt;
  &amp;lt;text&amp;gt;&lt;br /&gt;
    &amp;lt;label&amp;gt;Scenery Loading...&amp;lt;/label&amp;gt;&lt;br /&gt;
    &amp;lt;padding&amp;gt;30&amp;lt;/padding&amp;gt;&lt;br /&gt;
  &amp;lt;/text&amp;gt;&lt;br /&gt;
&amp;lt;/PropertyList&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Reusability ==&lt;br /&gt;
Once our existing PUI widgets and dialogs are fully emulated, and dialogs have become better optimized (=less Nasal blobs). It will be a good idea to revisit some concerns  and ideas expressed by Torsten D. based on his experience creating Phi:&lt;br /&gt;
&lt;br /&gt;
Torsten once stated rightly that it would be benefical for any new UI to separate the logic included in most dialogs from the presentation anyway to make it reusable.&lt;br /&gt;
His vision was to have some kind of service implemented in FlightGear that wraps all complex tasks into service calls and responses. The command system is a&lt;br /&gt;
good start to trigger something, but it does not yet return anything.&lt;br /&gt;
&lt;br /&gt;
Currently, all dialogs are a mixture of calling fg-commands and setting properties directly.&lt;br /&gt;
&lt;br /&gt;
Phi already has some basic support for aircraft specific elements. &amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/34532860/&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We should discuss and prototype a new type of asynchronous fgcommand layer that may also return properties/values, this could benefit both: The Canvas UI, but also Phi. In the future, Canvas UI dialogs may thus also use these same fgcommands which would be running asynchronously (possibly over the same/existing non-blocking HTTP back-end already used by Phi).&lt;br /&gt;
&lt;br /&gt;
At that point it will become increasingly feasible to also provide a module for Phi to interpret these new PUI/XML dialogs, because these would be primarily XML-based, should contain zero (or very little) code. And the new could be that Nasal code that still needs to stay inside such dialogs, will always need to be registered with the SGCommandMgr via the addCommand() API - that way, both front-ends would be able to interpret/execute such dialogs, which would mean that aircraft developers would not need to port/maintain two different sets of dialogs to support both front-ends. &lt;br /&gt;
&lt;br /&gt;
Also, future updates to the UI will become tremendously easier once our dialogs are purely declarative/XML.&lt;br /&gt;
&lt;br /&gt;
{{Appendix}}&lt;/div&gt;</summary>
		<author><name>TheEagle</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Nasal_library&amp;diff=137063</id>
		<title>Nasal library</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Nasal_library&amp;diff=137063"/>
		<updated>2023-01-10T17:04:49Z</updated>

		<summary type="html">&lt;p&gt;TheEagle: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Nasal Navigation|nocat=1}}&lt;br /&gt;
This page documents the global '''library functions and variables''' of FlightGear's built-in scripting language, [[Nasal]]. This includes ''[[#Core library functions|core library functions]]'', which were included in Nasal before its integration into FlightGear, the ''[[#Extension functions|extension functions]]'', which have been subsequently added, and are specifically designed for FlightGear, and the ''[[#variables|global variables]]'', which are conversion variables, added with extension functions, for converting between units. The relevant folders in [[Git]] are:&lt;br /&gt;
* {{flightgear file|src/Scripting}}&lt;br /&gt;
* {{simgear file|simgear/nasal}}&lt;br /&gt;
&lt;br /&gt;
All these functions and variables are in the global namespace, that is, they are directly accessible (e.g., one can call &amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; inline&amp;gt;magvar()&amp;lt;/syntaxhighlight&amp;gt; instead of &amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; inline&amp;gt;namespace.magvar()&amp;lt;/syntaxhighlight&amp;gt;). However, if a namespace must be used, &amp;lt;code&amp;gt;globals&amp;lt;/code&amp;gt; is the correct namespace, but using it is not recommended. For a more complete explanation, see [[Nasal Namespaces in-depth]].&lt;br /&gt;
&lt;br /&gt;
{{tip|Copy &amp;amp; paste the examples into your [[Nasal Console]] and execute them to see what they do.|width=70%}}&lt;br /&gt;
&lt;br /&gt;
== Core library functions ==&lt;br /&gt;
This is the list of the basic '''core library functions.''' Most of these functions were part of the original Nasal library (before its integration in to FlightGear), while some have been added or changed over time.  See also:&lt;br /&gt;
* http://plausible.org/nasal/lib.html ([http://web.archive.org/web/20101010094553/http://plausible.org/nasal/lib.html archive])&lt;br /&gt;
* {{simgear file|simgear/nasal/lib.c}} ([http://sourceforge.net/p/flightgear/simgear/ci/next/log/?path=/simgear/nasal/lib.c history])&lt;br /&gt;
&lt;br /&gt;
=== append() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = append(vector, element[, element[, ...]]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=42|t=Source}}&lt;br /&gt;
|text = This function appends, or adds, the given element(s) to the end of the vector given in the first argument.  Returns the vector operated on.&lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector to which the arguments will be appended.&lt;br /&gt;
|param2 = element&lt;br /&gt;
|param2text = An element to be added to the vector.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize the vector&lt;br /&gt;
append(vector, 4); # Append the number 4 to the end of the vector&lt;br /&gt;
debug.dump(vector); # Print the contents of the vector&lt;br /&gt;
|example2 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize the vector&lt;br /&gt;
append(vector, 4, 5, 6); # Append the numbers 4, 5, and 6 to the end of the vector&lt;br /&gt;
debug.dump(vector); # Print the contents of the vector&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== bind() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = bind(function, locals[, outer_scope]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=502|t=Source}}&lt;br /&gt;
|text = This creates a new function object. A function in Nasal is three things: the actual code, a hash/namespace of local variables available to the function namespace, and the closure object of that namespace. These correspond to the three arguments respectively.&lt;br /&gt;
|param1 = function&lt;br /&gt;
|param1text = Function to evaluate.&lt;br /&gt;
|param2 = locals&lt;br /&gt;
|param2text = Hash containing values that will become the namespace (first closure) for the function.&lt;br /&gt;
|param3 = outer_scope&lt;br /&gt;
|param3text = Optional function which is bound to the next closure. This can be bound to yet another, making a linked list.&lt;br /&gt;
|example1 = # This is a namespace/hash with a single member, named &amp;quot;key,&amp;quot; which is initialized to 12 &lt;br /&gt;
var Namespace = {&lt;br /&gt;
    key: 12&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
# This is different namespace/hash containing a function&lt;br /&gt;
# dividing a variable &amp;quot;key&amp;quot; (which is unavailable/nil in this namespace) by 2&lt;br /&gt;
var AnotherNamespace = {&lt;br /&gt;
    ret: func {&lt;br /&gt;
        key /= 2;&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
# To see that key is not available, try to call AnotherNamespace.ret() first&lt;br /&gt;
call(AnotherNamespace.ret, [], nil, nil, var errors = []);&lt;br /&gt;
if(size(errors)){&lt;br /&gt;
    print(&amp;quot;Key could not be divided/resolved!&amp;quot;);&lt;br /&gt;
    debug.printerror(errors);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
# Associate the AnotherNamespace.ret() function with the first namespace&lt;br /&gt;
# so that &amp;quot;key&amp;quot; is now available&lt;br /&gt;
var function = bind(AnotherNamespace.ret, Namespace);&lt;br /&gt;
&lt;br /&gt;
# Invoke the new function&lt;br /&gt;
function();&lt;br /&gt;
&lt;br /&gt;
# Print out the value of Namespace.key&lt;br /&gt;
# It was changed to 12 from 6 by AnotherNamespace.ret()&lt;br /&gt;
print(Namespace.key);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== call() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = call(func[, args[, me[, locals[, error]]]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=247|t=Source}}&lt;br /&gt;
|text = Calls the given function with the given arguments and returns the result.  This function is very useful as it allows much more control over function calls and catches any errors or {{func link|die()}} calls that would normally trigger run-time errors cancelling execution of the script otherwise. &lt;br /&gt;
|param1 = func&lt;br /&gt;
|param1text = Function to execute.&lt;br /&gt;
|param2 = args&lt;br /&gt;
|param2text = Vector containing arguments to give to the called function.&lt;br /&gt;
|param3 = me&lt;br /&gt;
|param3text = &amp;lt;code&amp;gt;'''me'''&amp;lt;/code&amp;gt; reference for the function call (i.e., for method calls). If given, this will override any &amp;lt;code&amp;gt;'''me'''&amp;lt;/code&amp;gt; value existing in the namespace (locals argument).&lt;br /&gt;
|param4 = locals&lt;br /&gt;
|param4text = A hash with key/value pairs that will be available to the called function, typically used as the namespace for the function to be called.&lt;br /&gt;
|param5 = error&lt;br /&gt;
|param5text = A vector to append errors to.  If the called function generates an error, the error, place, and line will be written to this.  These errors can be printed using {{func link|printerror()|debug}}.&lt;br /&gt;
|example1 =&lt;br /&gt;
# prints &amp;quot;Called from call()&amp;quot;&lt;br /&gt;
call(func {&lt;br /&gt;
    print(&amp;quot;Called from call()&amp;quot;);&lt;br /&gt;
});&lt;br /&gt;
|example2 =&lt;br /&gt;
# prints &amp;quot;a = 1 : b = 2&lt;br /&gt;
call(func(a, b){&lt;br /&gt;
        print(&amp;quot;a = &amp;quot;, a, &amp;quot; : b = &amp;quot;, b);&lt;br /&gt;
    },&lt;br /&gt;
    [1, 2]&lt;br /&gt;
);&lt;br /&gt;
|example3 =&lt;br /&gt;
var Hash = {&lt;br /&gt;
    new: func {&lt;br /&gt;
        var m = { parents: [Hash] };&lt;br /&gt;
&lt;br /&gt;
        m.el1 = &amp;quot;string1&amp;quot;;&lt;br /&gt;
        m.el2 = &amp;quot;string2&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
        return m;&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
# prints &amp;quot;me.el1 = string1&amp;quot;, then &amp;quot;me.el2 = string2&amp;quot; on the next line&lt;br /&gt;
call(func(a, b){        &lt;br /&gt;
        print(&amp;quot;me.el&amp;quot;, a, &amp;quot; = &amp;quot;, me[&amp;quot;el&amp;quot; ~ a]);      &lt;br /&gt;
        print(&amp;quot;me.el&amp;quot;, b, &amp;quot; = &amp;quot;, me[&amp;quot;el&amp;quot; ~ b]);&lt;br /&gt;
    },&lt;br /&gt;
    [1, 2],&lt;br /&gt;
    Hash.new()&lt;br /&gt;
);&lt;br /&gt;
|example4 =&lt;br /&gt;
# prints the value of math.pi&lt;br /&gt;
call(func {&lt;br /&gt;
        print(pi);&lt;br /&gt;
    }, nil, nil, &lt;br /&gt;
    math&lt;br /&gt;
);&lt;br /&gt;
|example5 =&lt;br /&gt;
call(func {&lt;br /&gt;
        print(math.ip); # math.ip doesn't exist&lt;br /&gt;
    }, nil, nil, nil,&lt;br /&gt;
    var errs = []&lt;br /&gt;
);&lt;br /&gt;
debug.printerror(errs); # The error is caught and printed using debug.printerror()&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== caller() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = caller([level]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=404|t=Source}}&lt;br /&gt;
|text = Returns a vector containing a record from the current call stack.  The level numbering starts from the currently executing function (level 0).  Level 1 (the default) is the caller of the current function, and so on.&lt;br /&gt;
&lt;br /&gt;
The result is a four-element vector containing '''[0]''' a hash of local variables, '''[1]''' the function object, '''[2]''' the full source file name (incl. path) and '''[3]''' the line number. &lt;br /&gt;
|param1 = level&lt;br /&gt;
|param1text = Optional integer specifying the stack level to return a result from.  Defaults to 1 (i.e. the caller of the currently executing function).&lt;br /&gt;
|example1 =&lt;br /&gt;
var myFunction = func(a, b){&lt;br /&gt;
    debug.dump(caller(0)[0]); # prints a hash of local variables, including arguments a and b&lt;br /&gt;
    return 2 * 2;&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
print(&amp;quot;2 x 2 = &amp;quot;, myFunction(2, 2));&lt;br /&gt;
|example2 =&lt;br /&gt;
var get_arg_value = func(){&lt;br /&gt;
    print(&amp;quot;Argument to myFunc = &amp;quot;, caller(1)[0]['a']); # print the value of myFunc's single argument, using caller()&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
var myFunc = func(a){&lt;br /&gt;
    get_arg_value();&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
myFunc(3);&lt;br /&gt;
|example3text = This is a real example taken from {{fgdata file|Nasal/canvas/MapStructure.nas}}.  Function &amp;lt;code&amp;gt;r()&amp;lt;/code&amp;gt; (above the TODOs) returns a hash with the key/value pairs as per its arguments. For example, something like this is returned: &amp;lt;code&amp;gt;{ name: &amp;quot;&amp;lt;name&amp;gt;&amp;quot;, vis: 1, zindex: nil }&amp;lt;/code&amp;gt;.&lt;br /&gt;
|example3 =&lt;br /&gt;
var MapStructure_selfTest = func() {&lt;br /&gt;
	var temp = {};&lt;br /&gt;
	temp.dlg = canvas.Window.new([600,400],&amp;quot;dialog&amp;quot;);&lt;br /&gt;
	temp.canvas = temp.dlg.createCanvas().setColorBackground(1,1,1,0.5);&lt;br /&gt;
	temp.root = temp.canvas.createGroup();&lt;br /&gt;
	var TestMap = temp.root.createChild(&amp;quot;map&amp;quot;);&lt;br /&gt;
	TestMap.setController(&amp;quot;Aircraft position&amp;quot;);&lt;br /&gt;
	TestMap.setRange(25); # TODO: implement zooming/panning via mouse/wheel here, for lack of buttons :-/&lt;br /&gt;
	TestMap.setTranslation(&lt;br /&gt;
		temp.canvas.get(&amp;quot;view[0]&amp;quot;)/2,&lt;br /&gt;
		temp.canvas.get(&amp;quot;view[1]&amp;quot;)/2&lt;br /&gt;
	);&lt;br /&gt;
	var r = func(name,vis=1,zindex=nil) return caller(0)[0];&lt;br /&gt;
	# TODO: we'll need some z-indexing here, right now it's just random&lt;br /&gt;
	# TODO: use foreach/keys to show all layers in this case by traversing SymbolLayer.registry direclty ?&lt;br /&gt;
	# maybe encode implicit z-indexing for each lcontroller ctor call ? - i.e. preferred above/below order ?&lt;br /&gt;
	foreach(var type; [r('TFC',0),r('APT'),r('DME'),r('VOR'),r('NDB'),r('FIX',0),r('RTE'),r('WPT'),r('FLT'),r('WXR'),r('APS'), ] ) &lt;br /&gt;
		TestMap.addLayer(factory: canvas.SymbolLayer, type_arg: type.name,&lt;br /&gt;
					visible: type.vis, priority: type.zindex,&lt;br /&gt;
		);&lt;br /&gt;
}; # MapStructure_selfTest&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== chr() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = chr(code);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=175|t=Source}}&lt;br /&gt;
|text = Returns a character as per the single argument. Extended ASCII is supported (see http://www.asciitable.com/ for a list of supported characters), although this may vary between different systems.  For a list of the most commonly used characters, see the {{wikipedia|ASCII#ASCII printable code chart|ASCII printable code chart}} ('''Dec''' column). The following table lists supported control characters, along with their equivalent control characters in Nasal strings.  {{Note|In Nasal, only strings enclosed with double-quotes (&amp;lt;code&amp;gt;&amp;quot;string&amp;quot;&amp;lt;/code&amp;gt;) supports control chracters.  Strings in single quotes (&amp;lt;code&amp;gt;'string'&amp;lt;/code&amp;gt;) do not.}}&lt;br /&gt;
{{{!}} class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Code !! Name !! Equivalent to&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 10 {{!!}} {{Wikipedia|Newline}} {{!!}} &amp;lt;code&amp;gt;\n&amp;lt;/code&amp;gt;&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 9 {{!!}} {{Wikipedia|Tab key#Tab characters|Horizontal tab}} {{!!}} &amp;lt;code&amp;gt;\t&amp;lt;/code&amp;gt;&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 13 {{!!}} {{Wikipedia|Carriage return}} {{!!}} &amp;lt;code&amp;gt;\r&amp;lt;/code&amp;gt;&lt;br /&gt;
{{!}}}&lt;br /&gt;
|param1 = code&lt;br /&gt;
|param1text = Integer character code for the desired glyph.&lt;br /&gt;
|example1 = print(&amp;quot;Code 65 = &amp;quot;, chr(65)); # prints &amp;quot;Code 65 = A&amp;quot;&lt;br /&gt;
|example2text = This example displays all of the characters in a list, in the format &amp;lt;code&amp;gt;Code '''n''' = &amp;gt;'''char'''&amp;lt;&amp;lt;/code&amp;gt;, '''n''' being the index, and '''char''' being the character.&lt;br /&gt;
|example2 =&lt;br /&gt;
for(var i = 0; i &amp;lt;= 255; i += 1){&lt;br /&gt;
    print(&amp;quot;Code &amp;quot;, i, &amp;quot; = &amp;gt;&amp;quot;, chr(i), &amp;quot;&amp;lt;&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== closure() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = closure(func[, level]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=421|t=Source}}&lt;br /&gt;
|text = Returns the hash table containing the lexical namespace of the given function. The level numbering start with level 0 being the namespace of '''func'''. &lt;br /&gt;
|param1 = func&lt;br /&gt;
|param1text = Function to evaluate.&lt;br /&gt;
|param2 = level&lt;br /&gt;
|param2text = Optional integer specifying the scope level.  Defaults to 0 (the namespace of '''func''').&lt;br /&gt;
|example1 =&lt;br /&gt;
var get_math_e = func {&lt;br /&gt;
    return e; # return the value of math.e&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var myFunction = bind(get_math_e, math); # bind get_math_e to the math namespace, so that math.e is immediately available to get_math_e&lt;br /&gt;
debug.dump(closure(myFunction)); # print the namespace of get_math_e&lt;br /&gt;
&lt;br /&gt;
print(myFunction());&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== cmp() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = cmp(a, b);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=112|t=Source}}&lt;br /&gt;
|text = Compares two strings, returning -1 if '''a''' is less than '''b''', 0 if they are identical and 1 if '''a''' is greater than '''b'''. &lt;br /&gt;
|param1 = a&lt;br /&gt;
|param1text = First string argument for comparison.&lt;br /&gt;
|param2 = b&lt;br /&gt;
|param2text = Second string argument for comparison.&lt;br /&gt;
|example1 = print(cmp(&amp;quot;1&amp;quot;, &amp;quot;two&amp;quot;)); # prints -1&lt;br /&gt;
|example2 = print(cmp(&amp;quot;string&amp;quot;, &amp;quot;string&amp;quot;)); # prints 0&lt;br /&gt;
|example3 = print(cmp(&amp;quot;one&amp;quot;, &amp;quot;2&amp;quot;)); # prints 1&lt;br /&gt;
|example4 = print(cmp(&amp;quot;string1&amp;quot;, &amp;quot;string2&amp;quot;)); # prints -1&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== compile() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = compile(code[, filename]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=220|t=Source}}&lt;br /&gt;
|text = Compiles the specified code string and returns a function object bound to the current lexical context.  If there is an error, the function dies, with the argument to {{func link|die()}} being '''filename'''.&lt;br /&gt;
|param1 = code&lt;br /&gt;
|param1text = String containing Nasal code to be compiled.&lt;br /&gt;
|param2 = filename&lt;br /&gt;
|param2text = Optional string used for error messages/logging. Defaults to &amp;lt;code&amp;gt;&amp;lt;compile&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
|example1 = &lt;br /&gt;
var myCode = 'print(&amp;quot;hello&amp;quot;);';&lt;br /&gt;
var helloFunc = compile(myCode, &amp;quot;myCode&amp;quot;);&lt;br /&gt;
helloFunc();&lt;br /&gt;
|example2text = &amp;lt;code&amp;gt;compile&amp;lt;/code&amp;gt; is very convenient to support Nasal loaded from other files.  For instance, [[PropertyList XML files]] (such as GUI dialogs) may contain embedded Nasal sections that need to be parsed, processed and compiled.  For an example of how to do this, save the below XML code as &amp;lt;tt&amp;gt;''[[$FG_ROOT]]/gui/dialogs/test.xml''&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PropertyList&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nasal&amp;gt;&amp;lt;![CDATA[&lt;br /&gt;
print(&amp;quot;You have FlightGear v&amp;quot;, getprop(&amp;quot;/sim/version/flightgear&amp;quot;));&lt;br /&gt;
]]&amp;gt;&amp;lt;/nasal&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/PropertyList&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Now, start FlightGear and execute this code in the [[Nasal Console]].&lt;br /&gt;
|example2 =&lt;br /&gt;
# Build the path&lt;br /&gt;
var FGRoot = getprop(&amp;quot;/sim/fg-root&amp;quot;);&lt;br /&gt;
var filename = &amp;quot;/gui/dialogs/test.xml&amp;quot;;&lt;br /&gt;
var path = FGRoot ~ filename;&lt;br /&gt;
&lt;br /&gt;
var blob = io.read_properties(path);&lt;br /&gt;
var script = blob.getValues().nasal; # Get the nasal string&lt;br /&gt;
&lt;br /&gt;
# Compile the script.  We're passing the filename here for better runtime diagnostics &lt;br /&gt;
var code = call(func {&lt;br /&gt;
    compile(script, filename);&lt;br /&gt;
}, nil, nil, var compilation_errors = []);&lt;br /&gt;
&lt;br /&gt;
if(size(compilation_errors)){&lt;br /&gt;
    die(&amp;quot;Error compiling code in: &amp;quot; ~ filename);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
# Invoke the compiled script, equivalent to code(); &lt;br /&gt;
# We're using call() here to detect errors:&lt;br /&gt;
call(code, [], nil, nil, var runtime_errors = []);&lt;br /&gt;
&lt;br /&gt;
if(size(runtime_errors)){&lt;br /&gt;
    die(&amp;quot;Error calling code compiled loaded from: &amp;quot; ~ filename);&lt;br /&gt;
}&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== contains() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = contains(hash, key);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=184|t=Source}}&lt;br /&gt;
|text = Returns 1 (True) if the hash contains the specified key, or 0 (False) if not.&lt;br /&gt;
|param1 = hash&lt;br /&gt;
|param1text = The hash to search in.&lt;br /&gt;
|param2 = key&lt;br /&gt;
|param2text = The scalar to be searched for, contained as a key in the hash.&lt;br /&gt;
|example1 =&lt;br /&gt;
# Initialize a hash&lt;br /&gt;
var hash = {&lt;br /&gt;
    element: &amp;quot;value&amp;quot;&lt;br /&gt;
};&lt;br /&gt;
print(contains(hash, &amp;quot;element&amp;quot;) ? &amp;quot;Yes&amp;quot; : &amp;quot;No&amp;quot;); # This will print &amp;quot;Yes&amp;quot;&lt;br /&gt;
|example2 =&lt;br /&gt;
# Initialize a hash&lt;br /&gt;
var hash = {&lt;br /&gt;
    element: &amp;quot;value&amp;quot;&lt;br /&gt;
};&lt;br /&gt;
print(contains(hash, &amp;quot;element2&amp;quot;) ? &amp;quot;Yes&amp;quot; : &amp;quot;No&amp;quot;); # This will print &amp;quot;No&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===contains()===&lt;br /&gt;
{{Note|Vector support of contains is currently only available on SimGear git next, starting commit ee39abbd3b70c9b6d5e3a1c4ccedddaac1a92b11}}{{Nasal doc&lt;br /&gt;
|syntax = contains(vector, item);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=184|t=Source}}&lt;br /&gt;
|text = Returns 1 (True) if the vector contains the specified item, or 0 (False) if not.&lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector to search in.&lt;br /&gt;
|param2 = item&lt;br /&gt;
|param2text = The object to be searched for in the vector.&lt;br /&gt;
|example1 =&lt;br /&gt;
# Initialize a hash&lt;br /&gt;
var vec = [&amp;quot;element&amp;quot;, &amp;quot;foo&amp;quot;];&lt;br /&gt;
print(contains(hash, &amp;quot;element&amp;quot;) ? &amp;quot;Yes&amp;quot; : &amp;quot;No&amp;quot;); # This will print &amp;quot;Yes&amp;quot;&lt;br /&gt;
|example2 =&lt;br /&gt;
# Initialize a hash&lt;br /&gt;
var vec = [&amp;quot;element&amp;quot;, &amp;quot;foo&amp;quot;];&lt;br /&gt;
print(contains(hash, &amp;quot;element2&amp;quot;) ? &amp;quot;Yes&amp;quot; : &amp;quot;No&amp;quot;); # This will print &amp;quot;No&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===delete() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = delete(hash, key);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=83|t=Source}}&lt;br /&gt;
|text = Deletes the key from the hash if it exists. Operationally, this is NOT identical to setting the hash value specified by the key to &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; as the key will stay in the hash (at least for a while). This variant potentially frees storage by deleting the reference to the key and by shrinking the hash.  Returns the hash that has been operated on.&lt;br /&gt;
|param1 = hash&lt;br /&gt;
|param1text = The hash from which to delete the key.&lt;br /&gt;
|param2 = key&lt;br /&gt;
|param2text = The scalar to be deleted, contained as a key in the hash.&lt;br /&gt;
|example1 =&lt;br /&gt;
# Initialize the hash&lt;br /&gt;
var hash = {&lt;br /&gt;
    element1: &amp;quot;value1&amp;quot;,&lt;br /&gt;
    element2: &amp;quot;value2&amp;quot;&lt;br /&gt;
};&lt;br /&gt;
delete(hash, &amp;quot;element1&amp;quot;); # Delete element1&lt;br /&gt;
debug.dump(hash); # prints the hash, which is now minus element1&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===die()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = die(error);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=288|t=Source}}&lt;br /&gt;
|text = Terminates execution and unwinds the stack.  The place and the line will be added to the '''error'''.  This invokes the same internal exception handler used for internal runtime errors. Use this to signal fatal errors, or to implement exception handling. The error thrown (including internal runtime errors) can be caught with {{func link|call()}}.&lt;br /&gt;
|param1 = error&lt;br /&gt;
|param1text = String describing the error.&lt;br /&gt;
:{{inote|This parameter is technically optional, but it is highly recommended to use it.}}&lt;br /&gt;
|example1 = &lt;br /&gt;
print(&amp;quot;Will print&amp;quot;);&lt;br /&gt;
die(&amp;quot;Don't go any further!&amp;quot;); &lt;br /&gt;
print(&amp;quot;Won't print&amp;quot;); # Will not be printed because die() stops the process&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== find()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = find(needle, haystack);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=450|t=Source}}&lt;br /&gt;
|text = Finds and returns the index of the first occurrence of the string '''needle''' in the string '''haystack''', or -1 if no such occurrence was found.&lt;br /&gt;
|param1 = needle&lt;br /&gt;
|param1text = String to search for.&lt;br /&gt;
|param2 = haystack&lt;br /&gt;
|param2text = String to search in.&lt;br /&gt;
|example1 = print(find(&amp;quot;c&amp;quot;, &amp;quot;abcdef&amp;quot;)); # prints 2&lt;br /&gt;
|example2 = print(find(&amp;quot;x&amp;quot;, &amp;quot;abcdef&amp;quot;)); # prints -1&lt;br /&gt;
|example3 = print(find(&amp;quot;cd&amp;quot;, &amp;quot;abcdef&amp;quot;)); # prints 2&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===ghosttype()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = ghosttype(ghost);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=207|t=Source}}&lt;br /&gt;
|text = Returns a string containing either a descriptive name of a ghost (a raw C/C++ object), or a unique id (the pointer to the C/C++ &amp;lt;code&amp;gt;naGhostType&amp;lt;/code&amp;gt; instance) if no name has been set.  Ghost is an acronym that stands for '''G'''arbage-collected '''H'''andle to '''O'''ut'''S'''ide '''T'''hingy.&lt;br /&gt;
|param1 = ghost&lt;br /&gt;
|param1text = Ghost to return a description for.&lt;br /&gt;
|example1 = print(ghosttype(airportinfo())); # prints &amp;quot;airport&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===id()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = id(object);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=570|t=Source}}&lt;br /&gt;
|text = Returns a string containing information on the type and ID of the object provided in the single argument.  The information is returned in the form of &amp;lt;code&amp;gt;'''&amp;lt;type&amp;gt;''':'''&amp;lt;id&amp;gt;'''&amp;lt;/code&amp;gt;, where '''&amp;lt;type&amp;gt;''' is the type of object, and '''&amp;lt;id&amp;gt;''' is the ID.&lt;br /&gt;
|param1 = object&lt;br /&gt;
|param1text = Can be either of a string, a vector, a hash, a code, a function, or a ghost.&lt;br /&gt;
|example1 = print(id(&amp;quot;A&amp;quot;)); # prints &amp;quot;str:000000001624A590&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===int() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = int(number);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=90|t=Source}}&lt;br /&gt;
|text = Returns the integer part of the numeric value of the single argument, or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if none exists.&lt;br /&gt;
|param1 = number&lt;br /&gt;
|param1text = Number or string with just a number in it to return an integer from.&lt;br /&gt;
|example1 = print(int(23)); # prints &amp;quot;23&amp;quot;&lt;br /&gt;
|example2 = print(int(23.123)); # prints &amp;quot;23&amp;quot;&lt;br /&gt;
|example3 = debug.dump(int(&amp;quot;string&amp;quot;)); # prints &amp;quot;nil&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===keys()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = keys(hash);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=33|t=Source}}&lt;br /&gt;
|text = Returns a vector containing the list of keys found in the single hash argument. &lt;br /&gt;
|param1 = hash&lt;br /&gt;
|param1text = The hash to return the keys from.&lt;br /&gt;
|example1 = &lt;br /&gt;
# Initialize a hash&lt;br /&gt;
var hash = {&lt;br /&gt;
    element1: &amp;quot;value&amp;quot;,&lt;br /&gt;
    element2: &amp;quot;value&amp;quot;&lt;br /&gt;
};&lt;br /&gt;
debug.dump(keys(hash)); # print the vector&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===left()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = left(string, length);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=149|t=Source}}&lt;br /&gt;
|version = 2.12&lt;br /&gt;
|commit = {{simgear commit|bd7163|t=commit}}&lt;br /&gt;
|text = Returns a substring of '''string''', starting from the left.&lt;br /&gt;
|param1 = string&lt;br /&gt;
|param1text = String to return part of.&lt;br /&gt;
|param2 = length&lt;br /&gt;
|param2text = Integer specifying the length of the substring to return.&lt;br /&gt;
|example1 = print(left(&amp;quot;string&amp;quot;, 2)); # prints &amp;quot;st&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== num()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = num(number);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=102|t=Source}}&lt;br /&gt;
|text = Returns the numerical value of the single string argument, or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if none exists. &lt;br /&gt;
|param1 = number&lt;br /&gt;
|param1text = String with just a number in it to return a number from.&lt;br /&gt;
|example1 = print(num(&amp;quot;23&amp;quot;)); # prints &amp;quot;23&amp;quot;&lt;br /&gt;
|example2 = print(num(&amp;quot;23.123&amp;quot;)); # prints &amp;quot;23.123&amp;quot;&lt;br /&gt;
|example3 = debug.dump(num(&amp;quot;string&amp;quot;)); # prints &amp;quot;nil&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===pop()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = pop(vector);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=50|t=Source}}&lt;br /&gt;
|text = Removes and returns the last element of the single vector argument, or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if the vector is empty. &lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = Vector to remove an element from.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
pop(vector);&lt;br /&gt;
debug.dump(vector); # prints &amp;quot;[1, 2]&amp;quot;&lt;br /&gt;
|example2 = &lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
debug.dump(pop(vector)); # prints &amp;quot;3&amp;quot;&lt;br /&gt;
|example3 = &lt;br /&gt;
var vector = [];&lt;br /&gt;
debug.dump(pop(vector)); # prints &amp;quot;nil&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===right()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = right(string, length);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=161|t=Source}}&lt;br /&gt;
|version = 2.12&lt;br /&gt;
|commit = {{simgear commit|bd7163|t=commit}}&lt;br /&gt;
|text = Returns a substring of '''string''', starting from the right.&lt;br /&gt;
|param1 = string&lt;br /&gt;
|param1text = String to return part of.&lt;br /&gt;
|param2 = length&lt;br /&gt;
|param2text = Integer specifying the length of the substring to return.&lt;br /&gt;
|example1 = print(right(&amp;quot;string&amp;quot;, 2)); # prints &amp;quot;ng&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== setsize()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = setsize(vector, size);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=56|t=Source}}&lt;br /&gt;
|text = Sets the size of a vector. The first argument specifies a vector, the second a number representing the desired size of that vector. If the vector is currently larger than the specified size, it is truncated. If it is smaller, it is padded with &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; entries. Returns the vector operated upon. &lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector to be operated on.&lt;br /&gt;
|param2 = size&lt;br /&gt;
|param2text = The desired size of the vector in number of entries.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize a vector&lt;br /&gt;
setsize(vector, 4);&lt;br /&gt;
debug.dump(vector); # print the vector&lt;br /&gt;
|example2 = &lt;br /&gt;
var vector = [1, 2, 3]; # Initialize a vector&lt;br /&gt;
setsize(vector, 2);&lt;br /&gt;
debug.dump(vector); # print the vector&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===size()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = size(object);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=23|t=Source}}&lt;br /&gt;
|text = Returns the size of the single argument. For strings, this is the length in bytes. For vectors, this is the number of elements. For hashes, it is the number of key/value pairs. If the argument is &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; or a number, this error will be thrown: &amp;lt;code&amp;gt;object has no size()&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param1 = object&lt;br /&gt;
|param1text = Object to find the size of.  Must be a string, a vector or a hash.&lt;br /&gt;
|example1 = &lt;br /&gt;
var string = &amp;quot;string&amp;quot;;&lt;br /&gt;
print(size(string)); # prints &amp;quot;6&amp;quot;&lt;br /&gt;
|example2 =&lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
print(size(vector)); # prints &amp;quot;3&amp;quot;&lt;br /&gt;
|example3 =&lt;br /&gt;
var hash = {&lt;br /&gt;
    element1: &amp;quot;value1&amp;quot;,&lt;br /&gt;
    element2: &amp;quot;value2&amp;quot;,&lt;br /&gt;
    element3: &amp;quot;value3&amp;quot;&lt;br /&gt;
};&lt;br /&gt;
print(size(hash)); # prints &amp;quot;3&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== sort()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = sort(vector, function);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=542|t=Source}}&lt;br /&gt;
|text = Returns a vector containing the elements in the input '''vector''' sorted in according to the rule given by '''function'''. Implemented with the ANSI C {{func link|qsort()|link=http://www.cplusplus.com/reference/cstdlib/qsort/}}, &amp;lt;code&amp;gt;sort()&amp;lt;/code&amp;gt; is stable.  This means that if the rules in the first example are used, equal elements in the output vector will appear in the same relative order as they do in the input.  It is run in a loop, so '''function''' is run several times.&lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = Input vector to sort.&lt;br /&gt;
|param2 = function&lt;br /&gt;
|param2text = Function according to which the elements will be sorted by.  It should take two arguments and should return one of 1, 0, or -1.&lt;br /&gt;
{{{!}} class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Return value !! Meaning&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} less than 0 {{!!}} first argument should go before second argument&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 0 {{!!}} first argument equals second argument&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} greater than 0 {{!!}} first argument should go after second argument&lt;br /&gt;
{{!}}}&lt;br /&gt;
&lt;br /&gt;
|example1text = This example sorts elements from smallest to greatest.&lt;br /&gt;
|example1 = &lt;br /&gt;
var sort_rules = func(a, b){&lt;br /&gt;
    if(a &amp;lt; b){&lt;br /&gt;
        return -1; # A should before b in the returned vector&lt;br /&gt;
    }elsif(a == b){&lt;br /&gt;
        return 0; # A is equivalent to b &lt;br /&gt;
    }else{&lt;br /&gt;
        return 1; # A should after b in the returned vector&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
debug.dump(sort([3, 2, 5, 6, 4, 1], sort_rules)); # prints &amp;quot;[1, 2, 3, 4, 5, 6]&amp;quot;&lt;br /&gt;
|example2text = This example sorts elements from greatest to smallest.&lt;br /&gt;
|example2 = &lt;br /&gt;
# Outputs the elements in reverse order (greatest to smallest)&lt;br /&gt;
var sort_rules = func(a, b){&lt;br /&gt;
    if(a &amp;lt; b){&lt;br /&gt;
        return 1; # -1 in the above example&lt;br /&gt;
    }elsif(a == b){&lt;br /&gt;
        return 0;&lt;br /&gt;
    }else{&lt;br /&gt;
        return -1; # 1 in the above example&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
debug.dump(sort([3, 2, 5, 6, 4, 1], sort_rules)); # prints &amp;quot;[6, 5, 4, 3, 2, 1]&amp;quot;&lt;br /&gt;
|example3text = This example sorts a vector of strings (runways for example) from smallest to greatest.&lt;br /&gt;
|example3 = &lt;br /&gt;
var runways = [&amp;quot;09R&amp;quot;,&amp;quot;27R&amp;quot;,&amp;quot;26L&amp;quot;,&amp;quot;09L&amp;quot;,&amp;quot;15&amp;quot;];&lt;br /&gt;
var rwy = sort(runways,func(a,b) cmp(a,b));&lt;br /&gt;
debug.dump(rwy); # prints ['09L','09R','15','26L','27R']&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== split()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = split(delimiter, string);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=460|t=Source}}&lt;br /&gt;
|text = Splits the input string into a vector of substrings bounded by occurrences of the delimiter substring.&lt;br /&gt;
|param1 = delimiter&lt;br /&gt;
|param1text = String that will split the substrings in the returned vector.&lt;br /&gt;
|param2 = string&lt;br /&gt;
|param2text = String to split up.&lt;br /&gt;
|example1 = debug.dump(split(&amp;quot;cd&amp;quot;, &amp;quot;abcdef&amp;quot;)); # prints &amp;quot;['ab', 'ef']&amp;quot;&lt;br /&gt;
|example2 = debug.dump(split(&amp;quot;.&amp;quot;, &amp;quot;3.2.0&amp;quot;)); # prints &amp;quot;[3, 2, 0]&amp;quot;&lt;br /&gt;
|example3 = debug.dump(split(&amp;quot;/&amp;quot;, &amp;quot;path/to/file&amp;quot;)); # prints &amp;quot;['path', 'to', 'file']&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===sprintf()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;sprintf(format[, arg[, arg, [...]]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=355|t=Source}}&lt;br /&gt;
|text = Creates and returns a string formatted using ANSI C {{func link|vsnprintf()|link=http://en.cppreference.com/w/c/io/vfprintf}} &amp;lt;ref&amp;gt;&lt;br /&gt;
{{Cite web&lt;br /&gt;
|url = http://sourceforge.net/p/flightgear/simgear/ci/next/tree/simgear/nasal/lib.c#l308&lt;br /&gt;
|title = fgdata/simgear/simgear/nasal/lib.c, line 308&lt;br /&gt;
|accessdate = October 2015&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;/ref&amp;gt;.  Below is a table of supported format specifiers.&lt;br /&gt;
{{{!}} class=&amp;quot;wikitable&amp;quot; width=&amp;quot;75%&amp;quot;&lt;br /&gt;
{{!}}+ %[flags][width][.precision]specifier&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} Flags&lt;br /&gt;
{{!-}}&lt;br /&gt;
! Flag !! Output&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;+&amp;lt;/code&amp;gt; {{!!}} Forces to precede the result with a plus or minus sign ('''+''' or '''-''') even for positive numbers. By default, only negative numbers are preceded with a '''-''' sign.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} ''space'' {{!!}} Prefixes non-signed numbers with a space.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;-&amp;lt;/code&amp;gt; {{!!}} Left-align the output of this placeholder (the default is to right-align the output) when the width option is specified.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; {{!!}} Use 0 instead of spaces to pad a field when the width option is specified.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;#&amp;lt;/code&amp;gt; {{!!}} Used with &amp;lt;code&amp;gt;o&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;x&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;X&amp;lt;/code&amp;gt; specifiers the value is preceded with &amp;lt;tt&amp;gt;0&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;0x&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;0X&amp;lt;/tt&amp;gt; respectively for values different than zero. Used with &amp;lt;code&amp;gt;e&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;E&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;f&amp;lt;/code&amp;gt;, it forces the written output to contain a decimal point even if no digits would follow. By default, if no digits follow, no decimal point is written. Used with &amp;lt;code&amp;gt;g&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;G&amp;lt;/code&amp;gt; the result is the same as with &amp;lt;code&amp;gt;e&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;E&amp;lt;/code&amp;gt; but trailing zeros are not removed.&lt;br /&gt;
{{!-}}&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} Width&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} colspan=&amp;quot;2&amp;quot; {{!}} Integer specifying the minimum number of characters to be returned. This includes the decimal point and decimal fraction as well as + or - signs.&lt;br /&gt;
{{!-}}&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} Precision&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} colspan=&amp;quot;2&amp;quot; {{!}} Integer preceded by a dot specifying the number of decimal places to be written.&lt;br /&gt;
{{!-}}&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; {{!}} Specifiers&lt;br /&gt;
{{!-}}&lt;br /&gt;
! Specifier !! Output&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;d&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;i&amp;lt;/code&amp;gt; {{!!}} Signed decimal number.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;s&amp;lt;/code&amp;gt; {{!!}} A string&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;%&amp;lt;/code&amp;gt; {{!!}} Percent (%) character.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;c&amp;lt;/code&amp;gt; {{!!}} A single character assigned to a character code, the code given in an integer argument.  See http://www.asciitable.com/ for a list of supported characters and their codes.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;o&amp;lt;/code&amp;gt; {{!!}} Unsigned integer as an octal number.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;u&amp;lt;/code&amp;gt; {{!!}} Unsigned decimal integer.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;x&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;X&amp;lt;/code&amp;gt; {{!!}} Unsigned integer as a hexadecimal number.  If &amp;lt;code&amp;gt;x&amp;lt;/code&amp;gt; is used, any letters in the number are lowercase, while &amp;lt;code&amp;gt;X&amp;lt;/code&amp;gt; gives uppercase.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;e&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;E&amp;lt;/code&amp;gt; {{!!}} Double value in scientific notation (i.e., ''[-]ddd.ddd'''e'''[+/-]ddd''), with an exponent being denoted by &amp;lt;tt&amp;gt;e&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;E&amp;lt;/tt&amp;gt; depending on whether an upper or lowercase is used respectively.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;f&amp;lt;/code&amp;gt; {{!!}} Floating-point number, in fixed decimal notation, by default with 6 decimal places.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;F&amp;lt;/code&amp;gt; {{!!}} Appears to be available&amp;lt;ref&amp;gt;&lt;br /&gt;
{{Cite web&lt;br /&gt;
|url = http://sourceforge.net/p/flightgear/simgear/ci/next/tree/simgear/nasal/lib.c#l389&lt;br /&gt;
|title = fgdata/simgear/simgear/nasal/lib.c, line 389&lt;br /&gt;
|accessdate = October 2015&lt;br /&gt;
}}&lt;br /&gt;
&amp;lt;/ref&amp;gt;, but doesn't work.&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} &amp;lt;code&amp;gt;g&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;G&amp;lt;/code&amp;gt; {{!!}} Double in either normal or exponential notation, whichever is more appropriate for its magnitude. &amp;lt;code&amp;gt;g&amp;lt;/code&amp;gt; uses lower-case letters, &amp;lt;code&amp;gt;G&amp;lt;/code&amp;gt; uses upper-case letters. This type differs slightly from fixed-point notation in that insignificant zeroes to the right of the decimal point are not included.  Also, the decimal point is not included on whole numbers.&lt;br /&gt;
{{!}}}&lt;br /&gt;
&lt;br /&gt;
|param1 = format&lt;br /&gt;
|param1text = String specifying the format.  Can be used with or without a format specifiers.  See below for examples.&lt;br /&gt;
|param2 = arg&lt;br /&gt;
|param2text = Argument specifying a value to replace a format placeholder (such as &amp;lt;code&amp;gt;%d&amp;lt;/code&amp;gt;) in the format string.  Not required if there are no format specifiers.&lt;br /&gt;
&lt;br /&gt;
|example1 = print(sprintf(&amp;quot;%i&amp;quot;, 54)); # prints &amp;quot;54&amp;quot;&lt;br /&gt;
|example2 = print(sprintf(&amp;quot;Pi = %+.10f&amp;quot;, math.pi)); # prints &amp;quot;Pi = +3.1415926536&amp;quot;&lt;br /&gt;
|example3 = &lt;br /&gt;
print(sprintf(&amp;quot;%6d&amp;quot;, 23)); # prints &amp;quot;    23&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;%06d&amp;quot;, 23)); # prints &amp;quot;000023&amp;quot;&lt;br /&gt;
|example4 =&lt;br /&gt;
var FGVer = getprop(&amp;quot;/sim/version/flightgear&amp;quot;);&lt;br /&gt;
print(sprintf(&amp;quot;You have FlightGear v%s&amp;quot;, FGVer)); # prints &amp;quot;You have FlightGear v&amp;lt;your version&amp;gt;&amp;quot;&lt;br /&gt;
|example5 = &lt;br /&gt;
print(sprintf(&amp;quot;Hexadecimal 100000 = %X&amp;quot;, 100000)); # prints &amp;quot;Hexadecimal 100000 = 186A0&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;Hexadecimal 100000 = %x&amp;quot;, 100000)); # prints &amp;quot;Hexadecimal 100000 = 186a0&amp;quot;&lt;br /&gt;
|example6 = print(sprintf(&amp;quot;Code 65 is %c&amp;quot;, 65)); # prints &amp;quot;Code 65 is A&amp;quot;&lt;br /&gt;
|example7 = &lt;br /&gt;
print(sprintf(&amp;quot;%e&amp;quot;, 54)); # prints &amp;quot;5.400000e+001&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;%E&amp;quot;, 54)); # prints &amp;quot;5.400000E+001&amp;quot;&lt;br /&gt;
|example8 = print(sprintf(&amp;quot;%o&amp;quot;, 54)); # prints &amp;quot;66&amp;quot;&lt;br /&gt;
|example9 = print(sprintf(&amp;quot;50%% of 100 is %i&amp;quot;, 100 / 2)); # prints &amp;quot;50% of 100 is 50&amp;quot;&lt;br /&gt;
|example10 =&lt;br /&gt;
print(sprintf(&amp;quot;%.2f&amp;quot;, 1.4));   #prints &amp;quot;1.40&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;%.1f&amp;quot;, 1.4));   #prints &amp;quot;1.4&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;% 4.1f&amp;quot;, 1.4)); #prints &amp;quot; 1.4&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;%04.1f&amp;quot;, 1.4)); #prints &amp;quot;01.4&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;% 6.1f&amp;quot;, 1.4)); #prints &amp;quot;   1.4&amp;quot;&lt;br /&gt;
print(sprintf(&amp;quot;%06.1f&amp;quot;, 1.4)); #prints &amp;quot;0001.4&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===streq()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = streq(a, b);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=129|t=Source}}&lt;br /&gt;
|text = Tests the string values of the two arguments for equality. This function is needed because the &amp;lt;code&amp;gt;'''=='''&amp;lt;/code&amp;gt; operator (see [[Nasal_Operators#Equality|Nasal Operators]]) tests for numeric equality first.  If either or both the arguments are not strings, 0 (False) will be returned.  Returns either 0 (False) or 1 (True).  {{Note|This function is rarely required in typical code.}}&lt;br /&gt;
|param1 = a&lt;br /&gt;
|param1text = First argument for testing equality.&lt;br /&gt;
|param2 = b&lt;br /&gt;
|param2text = Second argument for testing equality.&lt;br /&gt;
|example1 = print(streq(&amp;quot;0&amp;quot;, &amp;quot;0&amp;quot;)); # prints &amp;quot;1&amp;quot; (True)&lt;br /&gt;
|example2 = &lt;br /&gt;
print(0 == 0.0); # prints &amp;quot;1&amp;quot; (True)&lt;br /&gt;
print(streq(&amp;quot;0&amp;quot;, &amp;quot;0.0&amp;quot;)); # prints &amp;quot;0&amp;quot; (False)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===substr()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = substr(string, start [, length]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=129|t=Source}}&lt;br /&gt;
|text = Similar the {{func link|subvec()}}, but operates on strings. Computes and returns a substring. The first argument specifies a string, the second is the index of the start of a substring, the optional third argument specifies a length (the default is to return the rest of the string from the start).&lt;br /&gt;
|param1 = string&lt;br /&gt;
|param1text = String to return a substring from.&lt;br /&gt;
|param2 = start&lt;br /&gt;
|param2text = Integer specifying the start of a substring. Negative values specify a position from the end of the string.&lt;br /&gt;
|param3 = length&lt;br /&gt;
|param3text = Optional argument specifying the length of the substring. Defaults to the end of the string.&lt;br /&gt;
|example1 = print(substr(&amp;quot;abcde&amp;quot;, 1, 3)); # prints &amp;quot;bcd&amp;quot;&lt;br /&gt;
|example2 = print(substr(&amp;quot;abcde&amp;quot;, 1)); # prints &amp;quot;bcde&amp;quot;&lt;br /&gt;
|example3 = print(substr(&amp;quot;abcde&amp;quot;, 2, 1)); # prints &amp;quot;c&amp;quot;&lt;br /&gt;
|example4 = print(substr(&amp;quot;abcde&amp;quot;, -2)); # prints &amp;quot;de&amp;quot;&lt;br /&gt;
|example5 = print(substr(&amp;quot;abcde&amp;quot;, -3, 2)); # prints &amp;quot;cd&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===subvec()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = subvec(vector, start[, length]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=63|t=Source}}&lt;br /&gt;
|text = Returns a sub-range of a vector. The first argument specifies a vector, the second a starting index, and the optional third argument indicates a length (the default is to the end of the vector). &lt;br /&gt;
|param1 = vector&lt;br /&gt;
|param1text = The vector to take the sub-vector from.&lt;br /&gt;
|param2 = start&lt;br /&gt;
|param2text = The starting point of the sub-vector within the given vector.&lt;br /&gt;
|param3 = length&lt;br /&gt;
|param3text = Optional argument specifying the length of the sub-vector, from the starting point.&lt;br /&gt;
'''Notes:'''&lt;br /&gt;
* Omitting the ''vector'' and ''start'' arguments is not an error (possibly it should be) but the return value is ''nil''.&lt;br /&gt;
* A negative ''start'' argument ''is'' an error. This seems wrong. Perhaps the language designer could comment.&lt;br /&gt;
* A value of ''start'' greater than ''size(vector)'' causes an error. A value equal to ''size(vector)'' returns an empty vector.&lt;br /&gt;
* If the value of ''length'' is greater than ''size(vector) - start'' then it is ignored. That is, all elements from ''start'' to the end of ''vector'' are returned. If ''length'' is zero then an empty vector is returned. A negative value of ''length'' causes an error.&lt;br /&gt;
|example1 = &lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
debug.dump(subvec(vector, 0)); # prints &amp;quot;[1, 2, 3]&amp;quot;&lt;br /&gt;
|example2 = &lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
debug.dump(subvec(vector, 1)); # prints &amp;quot;[2, 3]&amp;quot;&lt;br /&gt;
|example3 = &lt;br /&gt;
var vector = [1, 2, 3];&lt;br /&gt;
debug.dump(subvec(vector, 1, 1)); # prints &amp;quot;[2]&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== typeof()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = typeof(object);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|l=193|t=Source}}&lt;br /&gt;
|text = Returns a string indicating the whether the object is &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt;, a scalar (number or string), a vector, a hash, a function, or a ghost.&lt;br /&gt;
|param1 = object&lt;br /&gt;
|param1text = Object to return the type of.&lt;br /&gt;
|example1 = &lt;br /&gt;
var object = nil;&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;nil&amp;quot;&lt;br /&gt;
|example2 = &lt;br /&gt;
var object = &amp;quot;Hello world!&amp;quot;;&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;scalar&amp;quot;&lt;br /&gt;
|example3 = &lt;br /&gt;
var object = math.pi;&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;scalar&amp;quot;&lt;br /&gt;
|example4 = &lt;br /&gt;
var object = [1, 2, 3];&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;vector&amp;quot;&lt;br /&gt;
|example5 = &lt;br /&gt;
var object = {};&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;hash&amp;quot;&lt;br /&gt;
|example6 = &lt;br /&gt;
var object = func {};&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;func&amp;quot;&lt;br /&gt;
|example7 =&lt;br /&gt;
var object = airportinfo();&lt;br /&gt;
print(typeof(object)); # prints &amp;quot;ghost&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- == Extension modules ==&lt;br /&gt;
=== thread ===&lt;br /&gt;
{{WIP}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.newthread(func);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = start a new worker thread&lt;br /&gt;
|example1 = thread.newthread( func() {} );&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.newlock();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = create a new lock&lt;br /&gt;
|example1 = var lock = thread.newlock()&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.lock();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = lock a lock&lt;br /&gt;
|example1 = var lock = thread.newlock()&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.unlock();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = unlock a lock&lt;br /&gt;
|example1 = var lock = thread.unlock()&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.newsem();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = create a new {{Wikipedia|semaphore}}&lt;br /&gt;
|example1 = var semaphore = thread.newsem()&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.semdown();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = semaphore down&lt;br /&gt;
|example1 = thread.semdown(semaphore)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thread.semup();&lt;br /&gt;
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}&lt;br /&gt;
|text = &lt;br /&gt;
|example1text = semaphore up&lt;br /&gt;
|example1 = thread.semup(semaphore)&lt;br /&gt;
}} --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Extension functions==&lt;br /&gt;
The '''extension functions''' are global functions that have been added to Nasal since its integration into FlightGear. Unlike the core library functions, they are generally specifically designed to interact directly with FlightGear. Extension functions come from three source files:&lt;br /&gt;
*{{flightgear file|src/Scripting/NasalPositioned.cxx}}&lt;br /&gt;
*{{flightgear file|src/Scripting/NasalSys.cxx}}&lt;br /&gt;
*{{fgdata file|Nasal/globals.nas}}&lt;br /&gt;
&lt;br /&gt;
===abort()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = abort();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=565|t=Source}}&lt;br /&gt;
|text = This function is a wrapper for the C++ {{func link|abort()|link=http://www.cplusplus.com/reference/cstdlib/abort/}} function. It simply aborts FlightGear with an error, which varies depending on the operating system. This function should not really be used; instead, please use the &amp;quot;exit&amp;quot; [[Fgcommands|fgcommand]], which will exit FlightGear more gracefully (see example below).&lt;br /&gt;
|example1text = This example will immediately stop FlightGear with an error, such as &amp;quot;FlightGear has stopped working.&amp;quot;&lt;br /&gt;
|example1 = abort();&lt;br /&gt;
|example2text = For exiting FlightGear in a better way, please use the following code:&lt;br /&gt;
|example2 = fgcommand(&amp;quot;exit&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== abs() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = abs(number);&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = This simple function returns the {{wikipedia|absolute value|noicon=1}} of the provided number.&lt;br /&gt;
|param1 = number&lt;br /&gt;
|param1text = This argument is required and should be a number.&lt;br /&gt;
|example1 = print(abs(1)); # prints &amp;quot;1&amp;quot;&lt;br /&gt;
|example2 = print(abs(-1)); # prints &amp;quot;1&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===aircraftToCart() ===&lt;br /&gt;
This new function in FG 2017.2.1 takes coordinates in aircraft structural coordinate system, and translate them into geocentric coordinates.&lt;br /&gt;
Example for (5,6,7):&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
var pos = aircraftToCart({x: -5, y: 6, z: -7});&lt;br /&gt;
var coord = geo.Coord.new();&lt;br /&gt;
coord.set_xyz(pos.x, pos.y, pos.z);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Notice: x and z is inverted sign on purpose.&lt;br /&gt;
if you want lat. lon, alt from that, just call: (degrees and meters)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
coord.lat()&lt;br /&gt;
coord.lon()&lt;br /&gt;
coord.alt()&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===addcommand() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = addcommand(name, code);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=659|t=Source}}&lt;br /&gt;
|version = 2.12&lt;br /&gt;
|commit = {{flightgear commit|7b663c|t=commit}}&lt;br /&gt;
|text = {{see also|Howto:Add new fgcommands to FlightGear}}&lt;br /&gt;
&lt;br /&gt;
This function enables the addition of a new custom [[fgcommands|fgcommand]] to FlightGear from within Nasal. An fgcommand created using this method can be used in exactly the same way as the built-in fgcommands. Also, an fgcommand created via this method will always return True or 1, like all other fgcommands.&lt;br /&gt;
|param1 = name&lt;br /&gt;
|param1text = This will become the name of the new fgcommand. Must be a string.&lt;br /&gt;
|param2 = code&lt;br /&gt;
|param2text = The code that will be executed when the fgcommand is run. Must be a function.&lt;br /&gt;
|example1text = This example adds a new fgcommand and then runs it. Although it executes a simple {{func link|print()}} statement, any valid Nasal code can be used.&lt;br /&gt;
|example1 = addcommand(&amp;quot;myFGCmd&amp;quot;, func(node) {&lt;br /&gt;
    print(&amp;quot;fgcommand 'myFGCmd' has been run.&amp;quot;);&lt;br /&gt;
    props.dump( node );&lt;br /&gt;
});&lt;br /&gt;
fgcommand(&amp;quot;myFGCmd&amp;quot;, props.Node.new({foo:1, bar:2}) );&lt;br /&gt;
|example2text = This example demonstrates how parameters are defined in a new fgcommand.&lt;br /&gt;
|example2 = addcommand(&amp;quot;myFGCmd&amp;quot;, func(node){&lt;br /&gt;
    print(node.getNode(&amp;quot;number&amp;quot;).getValue()); # prints the value of &amp;quot;number,&amp;quot; which is 12&lt;br /&gt;
});&lt;br /&gt;
fgcommand(&amp;quot;myFGCmd&amp;quot;, props.Node.new({&amp;quot;number&amp;quot;: 12}));&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===airportinfo()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = airportinfo();&lt;br /&gt;
airportinfo(type);&lt;br /&gt;
airportinfo(id);&lt;br /&gt;
airportinfo(lat, lon[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1024|t=Source}}&lt;br /&gt;
|text = Function for retrieval of airport, heliport, or seaplane base information. It returns a Nasal ghost; however, its structure is like that of a Nasal hash. The following information is returned:&lt;br /&gt;
* '''parents''': A vector containing a hash of various functions to access information about the runway. See {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2659}} for full list.&lt;br /&gt;
* '''lon''': Longitude of the location.&lt;br /&gt;
* '''lat''': Latitude of the location.&lt;br /&gt;
* '''has_metar''': True or false depending whether the airport has a [[METAR]] code defined for it.&lt;br /&gt;
* '''elevation''': Elevation of the location in metres.&lt;br /&gt;
* '''id''': ICAO code of the airport (or ID of the seaplane base/heliport).&lt;br /&gt;
* '''name''': Name of the airport/heliport/seaplane base.&lt;br /&gt;
* '''runways'''&lt;br /&gt;
** '''&amp;lt;runway name&amp;gt;'''&lt;br /&gt;
*** '''id''': Name of runway.&lt;br /&gt;
*** '''lat''': Latitude of the runway.&lt;br /&gt;
*** '''lon''': Longitude of the runway.&lt;br /&gt;
*** '''heading''': Heading of the runway.&lt;br /&gt;
*** '''length''': Length of the runway in metres.&lt;br /&gt;
*** '''width''': Width of the runway in metres.&lt;br /&gt;
*** '''surface''': Runway surface type.&lt;br /&gt;
*** '''threshold''': Length of the runway's {{wikipedia|displaced threshold}} in metres. Will return 0 if there is none.&lt;br /&gt;
*** '''stopway''': Length of the runway's stopway (the area before the threshold) in metres. Will return 0 if there is none.&lt;br /&gt;
*** '''reciprocal''': &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt; ghost of the reciprocal runway.&lt;br /&gt;
*** '''ils_frequency_mhz''': ILS frequency in megahertz.&lt;br /&gt;
*** '''ils''': &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost of the ILS transmitter.&lt;br /&gt;
* '''helipads'''&lt;br /&gt;
** '''&amp;lt;helipad name&amp;gt;'''&lt;br /&gt;
*** '''id''': Name of helipad.&lt;br /&gt;
*** '''lat''': Latitude of the helipad.&lt;br /&gt;
*** '''lon''': Longitude of the helipad.&lt;br /&gt;
*** '''heading''': Heading of the helipad.&lt;br /&gt;
*** '''length''': Length of the helipad in metres.&lt;br /&gt;
*** '''width''': Width of the helipad in metres.&lt;br /&gt;
*** '''surface''': Helipad surface type.&lt;br /&gt;
* '''taxiways'''&lt;br /&gt;
** '''&amp;lt;taxiway name&amp;gt;'''&lt;br /&gt;
*** '''id''': Name of taxiway.&lt;br /&gt;
*** '''lat''': Latitude of the taxiway.&lt;br /&gt;
*** '''lon''': Longitude of the taxiway.&lt;br /&gt;
*** '''heading''': Heading of the taxiway.&lt;br /&gt;
*** '''length''': Length of the taxiway in metres.&lt;br /&gt;
*** '''width''': Width of the taxiway in metres.&lt;br /&gt;
*** '''surface''': Taxiway surface type.&lt;br /&gt;
&lt;br /&gt;
Information is extracted in the same way as accessing members of a Nasal hash. For example:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
# prints to lengths of the runways of the nearest airport in feet and metres&lt;br /&gt;
var info = airportinfo();&lt;br /&gt;
print(&amp;quot;-- Lengths of the runways at &amp;quot;, info.name, &amp;quot; (&amp;quot;, info.id, &amp;quot;) --&amp;quot;);&lt;br /&gt;
foreach(var rwy; keys(info.runways)){&lt;br /&gt;
    print(rwy, &amp;quot;: &amp;quot;, math.round(info.runways[rwy].length * M2FT), &amp;quot; ft (&amp;quot;, info.runways[rwy].length, &amp;quot; m)&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that searches for locations that are a long way away (e.g., the nearest seaplane base to the middle of the Sahara) may cause FlightGear to pause for an amount of time.&lt;br /&gt;
|param1 = id&lt;br /&gt;
|param1text = The {{wikipedia|International Civil Aviation Organization airport code|ICAO code|noicon=1}} of an airport to retrieve information about.&lt;br /&gt;
|param2 = type&lt;br /&gt;
|param2text = When this argument is used, the function will return the closest airport of a certain type. Can be one of &amp;quot;heliport,&amp;quot; &amp;quot;seaport,&amp;quot; or &amp;quot;airport&amp;quot; (default).&lt;br /&gt;
: {{inote|Running this function without any parameters is equivalent to this:&lt;br /&gt;
: &amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
airportinfo(&amp;quot;airport&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
|param3 = lat ''and'' lon&lt;br /&gt;
|param3text = When these parameters are used, the function will return information on the nearest airport, heliport or seaplane base (depending on the '''type''' parameter) to those coordinates.&lt;br /&gt;
|example1 = var info = airportinfo();&lt;br /&gt;
print(&amp;quot;Nearest airport: &amp;quot;, info.name, &amp;quot; (&amp;quot;, info.id, &amp;quot;)&amp;quot;); # prints the name and ICAO code of the nearest airport&lt;br /&gt;
|example2 = var info = airportinfo(&amp;quot;heliport&amp;quot;);&lt;br /&gt;
print(&amp;quot;Elevation of the nearest heliport: &amp;quot;, math.round(info.elevation * M2FT), &amp;quot; ft&amp;quot;); # prints the elevation and name of the nearest heliport&lt;br /&gt;
|example3 = var info = airportinfo(&amp;quot;KSQL&amp;quot;);&lt;br /&gt;
print(&amp;quot;-- Runways of &amp;quot;, info.name, &amp;quot; (&amp;quot;, info.id, &amp;quot;): --&amp;quot;);&lt;br /&gt;
foreach(var rwy; keys(info.runways)) {&lt;br /&gt;
    print(rwy); # prints the runways of KSQL&lt;br /&gt;
}&lt;br /&gt;
|example4 = var info = airportinfo(37.81909385, -122.4722484);&lt;br /&gt;
print(&amp;quot;Coordinates of the nearest airport: &amp;quot;, info.lat, &amp;quot;, &amp;quot;, info.lon); # print the name and ICAO of the nearest airport to the Golden Gate Bridge&lt;br /&gt;
|example5 = var info = airportinfo(37.81909385, -122.4722484, &amp;quot;seaport&amp;quot;);&lt;br /&gt;
print(&amp;quot;Nearest seaplane base: &amp;quot;, info.name, &amp;quot; (&amp;quot;, info.id, &amp;quot;)&amp;quot;); # print the name and ID of the nearest seaplane base to the Golden Gate Bridge&lt;br /&gt;
|example6text = This example prints the all information from an &amp;lt;code&amp;gt;airportinfo()&amp;lt;/code&amp;gt; call.&lt;br /&gt;
|example6 = var info = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
print(info.name);&lt;br /&gt;
print(info.id);&lt;br /&gt;
print(info.lat);&lt;br /&gt;
print(info.lon);&lt;br /&gt;
print(info.has_metar);&lt;br /&gt;
print(info.elevation);&lt;br /&gt;
foreach(var rwy; keys(info.runways)){&lt;br /&gt;
    print(&amp;quot;-- &amp;quot;, rwy, &amp;quot; --&amp;quot;);&lt;br /&gt;
    print(info.runways[rwy].lat);&lt;br /&gt;
    print(info.runways[rwy].lon);&lt;br /&gt;
    print(info.runways[rwy].length);&lt;br /&gt;
    print(info.runways[rwy].width);&lt;br /&gt;
    print(info.runways[rwy].heading);&lt;br /&gt;
    print(info.runways[rwy].stopway);&lt;br /&gt;
    print(info.runways[rwy].threshold);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===airwaysRoute() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = airwaysRoute(start, end[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1933|t=Source}}&lt;br /&gt;
|text = {{see also|Nasal Flightplan}}&lt;br /&gt;
This function returns a vector containing waypoints between two given waypoints. The returned waypoints are ghosts, but can be accessed in the same way as a Nasal hash. See [[Nasal Flightplan]] for more information.&lt;br /&gt;
|param1 = start&lt;br /&gt;
|param1text = Start waypoint, in the form of a waypoint ghost, such as that provided by {{func link|flightplan()}}.&lt;br /&gt;
|param2 = end&lt;br /&gt;
|param2text = Same as above.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = Instructs the function to compute a high level route (when set to &amp;quot;highlevel&amp;quot;), or a low level route (when set to &amp;quot;lowlevel&amp;quot;). Defaults to &amp;quot;highlevel.&amp;quot;&lt;br /&gt;
|example1text = In the [[route manager]] dialog, add two waypoints to the flightplan, ideally ones that are far apart (tip: use the [[Map]] for this). Then run this code in your Nasal Console.&lt;br /&gt;
|example1 = var fp = flightplan();&lt;br /&gt;
var start = fp.getWP(0);&lt;br /&gt;
var end = fp.getWP(fp.getPlanSize() - 1);&lt;br /&gt;
var rt = airwaysRoute(start, end);&lt;br /&gt;
foreach(var wp; rt){&lt;br /&gt;
    print(wp.wp_name); # print the waypoints in the computed route&lt;br /&gt;
}&lt;br /&gt;
|example2text = Exactly the same as above, but computes a low level path.&lt;br /&gt;
|example2 = var fp = flightplan();&lt;br /&gt;
var start = fp.getWP(0);&lt;br /&gt;
var end = fp.getWP(fp.getPlanSize() - 1);&lt;br /&gt;
var rt = airwaysRoute(start, end, &amp;quot;lowlevel&amp;quot;);&lt;br /&gt;
foreach(var wp; rt){&lt;br /&gt;
    print(wp.wp_name); # print the waypoints in the computed route&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===airway()===&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = airway(ident [, pos]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2644|t=Source}}&lt;br /&gt;
|text = {{see also|Nasal Flightplan}}&lt;br /&gt;
This function returns a ghost containing an airway of a specified id.&lt;br /&gt;
|param1 = ident&lt;br /&gt;
|param1text = a Positioned ghost (leg, navaid, airport) and so on, passed to the search function.&lt;br /&gt;
|param2 = pos&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===assert()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = assert(condition[, message]);&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|version = 3.2&lt;br /&gt;
|commit = {{fgdata commit|8b16a7|t=commit}}&lt;br /&gt;
|text = Returns either true if the condition evaluates as true, or aborts with a {{func link|die()}} call, which can be customised.&lt;br /&gt;
|param1 = condition&lt;br /&gt;
|param1text = Condition to evaluate.&lt;br /&gt;
|param2 = message&lt;br /&gt;
|param2text = Optional message that will be used in any {{func link|die()}} call. Defaults to &amp;quot;assertion failed!&amp;quot;&lt;br /&gt;
|example1 = var a = 1;&lt;br /&gt;
var b = 2;&lt;br /&gt;
print(assert(a &amp;lt; b)); # prints &amp;quot;1&amp;quot; (true)&lt;br /&gt;
|example2 = var a = 1;&lt;br /&gt;
var b = 2;&lt;br /&gt;
assert(a &amp;gt; b, 'a is not greater than b'); # aborts with a custom error message&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===carttogeod()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = carttogeod(x, y, z);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=945|t=Source}}&lt;br /&gt;
|text = Converts {{wikipedia|ECEF|Earth-centered, Earth-fixed}} coordinates (x, y and z) to {{wikipedia|geodetic coordinates}} (latitude, longitude, and altitude). A vector is returned containing latitude and longitude, both in degrees, and altitude, which is returned in metres above the equatorial radius of Earth as defined by the {{wikipedia|WGS 84}} (6,378,137 metres).&amp;lt;ref&amp;gt;{{simgear file|simgear/math/sg_geodesy.hxx|l=43}}&amp;lt;/ref&amp;gt;&lt;br /&gt;
|param1 = x&lt;br /&gt;
|param1text = Mandatory x-axis value, in metres.&lt;br /&gt;
|param2 = y&lt;br /&gt;
|param2text = Mandatory y-axis value, in metres.&lt;br /&gt;
|param3 = z&lt;br /&gt;
|param3text = Mandatory z-axis value, in metres.&lt;br /&gt;
|example1 = var (lat, lon, alt) = carttogeod(6378137, 0, 0); # point is the intersection of the prime meridian and equator.&lt;br /&gt;
print(&amp;quot;Latitude: &amp;quot;, lat); # prints lat, lon and alt, which are all zero, see above&lt;br /&gt;
print(&amp;quot;Longitude: &amp;quot;, lon);&lt;br /&gt;
print(&amp;quot;Altitude: &amp;quot;, alt);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===cmdarg()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|private = _cmdarg()&lt;br /&gt;
|syntax = cmdarg();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=513|t=Part 1}} {{!}} {{fgdata file|Nasal/globals.nas|t=Part 2}}&lt;br /&gt;
|text = &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; returns the property root of certain types of XML files. These could be nodes in the [[Property Tree]], or temporary and/or non-public nodes outside the Property tree. &lt;br /&gt;
It is used by Nasal scripts embedded in XML files. It returns a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object (see {{fgdata file|Nasal/props.nas}}), and you can use all of its methods on the returned value. &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; should only be used in four types/places of XML files:&lt;br /&gt;
* Bindings: This is needed so that the value of a joystick's axis can be accessed internally.&lt;br /&gt;
* Dialogs: This will return the root of the dialog in the Property Tree. This is useful for dialogs that are created/modified procedurally (e.g. for populating/changing widgets while loading the dialog). &lt;br /&gt;
* Embedded Canvases: The Nasal code behind [[Canvas]] windows [[Howto:Adding a canvas to a GUI dialog|embedded in PUI dialogs]] can use it to accessing the root directory of their Canvas.&lt;br /&gt;
* Animation XML files: If the animation XML file is used in an AI/MP model, &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; will return the root of the AI model in the &amp;lt;code&amp;gt;/ai/models/&amp;lt;/code&amp;gt; directory. Examples: &amp;lt;code&amp;gt;/ai/models/aircraft[3]/&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;/ai/models/multiplayer[1]/&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You should not use &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; in places other than those stated above. Although it won't cause an error, it will return the value of the last legitimate &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; call. &lt;br /&gt;
&lt;br /&gt;
Also, you should not delay &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; using {{func link|maketimer()}}, {{func link|settimer()}} or {{func link|setlistener()}}, because it will return an unrelated property.&lt;br /&gt;
|example1 = fgcommand(&amp;quot;dialog-show&amp;quot;, {&amp;quot;dialog-name&amp;quot;: &amp;quot;cmdarg-demo&amp;quot;});&lt;br /&gt;
|example1text = &amp;lt;br&amp;gt;This example demonstrates the usage of &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; in a binding.  Save the below XML snippet as &amp;lt;tt&amp;gt;[[$FG_ROOT]]/gui/dialogs/cmdarg-demo.xml&amp;lt;/tt&amp;gt;. Then run the Nasal snippet below in your [[Nasal Console]]. Upon clicking {{button|Close}}, a message will be printed sowing the root of the binding in the Property Tree.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PropertyList&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;name&amp;gt;cmdarg-demo&amp;lt;/name&amp;gt;&lt;br /&gt;
&amp;lt;layout&amp;gt;vbox&amp;lt;/layout&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;text&amp;gt;&lt;br /&gt;
  &amp;lt;label&amp;gt;Click &amp;quot;Close&amp;quot; to activate the demonstration (a message in the console).&amp;lt;/label&amp;gt;&lt;br /&gt;
&amp;lt;/text&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;button&amp;gt;&lt;br /&gt;
  &amp;lt;legend&amp;gt;Close&amp;lt;/legend&amp;gt;&lt;br /&gt;
  &amp;lt;binding&amp;gt;&lt;br /&gt;
    &amp;lt;command&amp;gt;nasal&amp;lt;/command&amp;gt;&lt;br /&gt;
    &amp;lt;script&amp;gt;print(&amp;quot;Button binding root: '&amp;quot; ~ cmdarg().getPath() ~ &amp;quot;'&amp;quot;);&amp;lt;/script&amp;gt;&lt;br /&gt;
  &amp;lt;/binding&amp;gt;&lt;br /&gt;
  &amp;lt;binding&amp;gt;&lt;br /&gt;
    &amp;lt;command&amp;gt;dialog-close&amp;lt;/command&amp;gt;&lt;br /&gt;
  &amp;lt;/binding&amp;gt;&lt;br /&gt;
&amp;lt;/button&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/PropertyList&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|example2text = This example demonstrates the usage of &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; in Nasal code within dialogs.  Open &amp;lt;tt&amp;gt;[[$FG_ROOT]]/gui/dialogs/cmdarg-demo.xml&amp;lt;/tt&amp;gt; from the previous example, copy &amp;amp; paste the code below, and save it. Then run the same Nasal snippet as the previous example in your Nasal Console. If you click {{button|Click me!}}, the button's label will change to &amp;quot;I've been changed!&amp;quot;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PropertyList&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;name&amp;gt;cmdarg-demo&amp;lt;/name&amp;gt;&lt;br /&gt;
&amp;lt;layout&amp;gt;vbox&amp;lt;/layout&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;text&amp;gt;&lt;br /&gt;
  &amp;lt;label&amp;gt;Click &amp;quot;Click me!&amp;quot; to activate the demonstration (the button's label will change).&amp;lt;/label&amp;gt;&lt;br /&gt;
&amp;lt;/text&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;button&amp;gt;&lt;br /&gt;
  &amp;lt;legend&amp;gt;Click me!&amp;lt;/legend&amp;gt;&lt;br /&gt;
  &amp;lt;binding&amp;gt;&lt;br /&gt;
    &amp;lt;command&amp;gt;nasal&amp;lt;/command&amp;gt;&lt;br /&gt;
    &amp;lt;script&amp;gt;change_label();&amp;lt;/script&amp;gt;&lt;br /&gt;
  &amp;lt;/binding&amp;gt;&lt;br /&gt;
&amp;lt;/button&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;button&amp;gt;&lt;br /&gt;
  &amp;lt;legend&amp;gt;Close&amp;lt;/legend&amp;gt;&lt;br /&gt;
  &amp;lt;binding&amp;gt;&lt;br /&gt;
    &amp;lt;command&amp;gt;dialog-close&amp;lt;/command&amp;gt;&lt;br /&gt;
  &amp;lt;/binding&amp;gt;&lt;br /&gt;
&amp;lt;/button&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nasal&amp;gt;&lt;br /&gt;
  &amp;lt;open&amp;gt;&amp;lt;![CDATA[&lt;br /&gt;
    var dlg_root = cmdarg();&lt;br /&gt;
    var dlg_name = {&amp;quot;dialog-name&amp;quot;: &amp;quot;cmdarg-demo&amp;quot;};&lt;br /&gt;
    var change_label = func {&lt;br /&gt;
        dlg_root.getNode(&amp;quot;button[0]/legend&amp;quot;).setValue(&amp;quot;I've been changed!&amp;quot;);&lt;br /&gt;
        fgcommand(&amp;quot;dialog-close&amp;quot;, dlg_name);&lt;br /&gt;
        fgcommand(&amp;quot;dialog-show&amp;quot;, dlg_name);&lt;br /&gt;
    }&lt;br /&gt;
  ]]&amp;gt;&amp;lt;/open&amp;gt;&lt;br /&gt;
&amp;lt;/nasal&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/PropertyList&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|example3text = For an example of &amp;lt;code&amp;gt;cmdarg()&amp;lt;/code&amp;gt; used with Canvas, please see [[Howto:Adding a canvas to a GUI dialog#FGPlot|Howto:Adding a canvas to a GUI dialog]].&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===courseAndDistance()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = courseAndDistance(to);&lt;br /&gt;
courseAndDistance(from, to);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1668|t=Source}}&lt;br /&gt;
|text = Returns a vector containing the course from one point to another and the distance between them in nautical miles. The course is the initial bearing (see [http://www.movable-type.co.uk/scripts/latlong.html#bearing here]), and is in the range 0–360. Both arguments can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object with geodetic coordinates (cartesian coordinates will not be accepted)&lt;br /&gt;
|param1 = from&lt;br /&gt;
|param1text = Optional parameter defining the from where the function should calculate its results. If the function is given one argument ('''to'''), the aircraft's current position will be used. As well as the argument types as defined above, this argument can be two numbers separated with a comma, as if the function is taking three arguments. See example 5 below.&lt;br /&gt;
|param2 = to&lt;br /&gt;
|param2text = Like the first parameter, but defines the second point.&lt;br /&gt;
|example1text = This example demonstrates the usage of the function with the &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt; ghost type.&lt;br /&gt;
|example1 = var from = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var to = airportinfo(&amp;quot;KSQL&amp;quot;);&lt;br /&gt;
var (course, dist) = courseAndDistance(from, to);&lt;br /&gt;
print(course); # prints course from KSFO to KSQL&lt;br /&gt;
print(dist); # prints distance in nm from KSFO to KSQL&lt;br /&gt;
|example2text = This example demonstrates the usage of the function with hashes containing ''lat'' and ''lon''.&lt;br /&gt;
|example2 = var from = {lat: 0, lon: 0};&lt;br /&gt;
var to = {lat: 1, lon: 1};&lt;br /&gt;
var (course, dist) = courseAndDistance(from, to);&lt;br /&gt;
print(course);&lt;br /&gt;
print(dist);&lt;br /&gt;
|example3text = This example demonstrates usage of a geo.Coord object.&lt;br /&gt;
|example3 = var from = geo.Coord.new().set_latlon(0, 0);&lt;br /&gt;
var to = geo.Coord.new().set_latlon(1, 1);&lt;br /&gt;
var (course, dist) = courseAndDistance(from, to);&lt;br /&gt;
print(course);&lt;br /&gt;
print(dist);&lt;br /&gt;
|example4text = This example demonstrates usage of differing parameter types.&lt;br /&gt;
|example4 = var from = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var to = geo.Coord.new().set_latlon(0, 0);&lt;br /&gt;
var (course, dist) = courseAndDistance(from, to);&lt;br /&gt;
print(course);&lt;br /&gt;
print(dist);&lt;br /&gt;
|example5text = The same as above, but the other way round.&lt;br /&gt;
|example5 = var to = {lat: 1, lon: 1};&lt;br /&gt;
var (course, dist) = courseAndDistance(0, 0, to);&lt;br /&gt;
print(course);&lt;br /&gt;
print(dist);&lt;br /&gt;
|example6text = Usage of just one parameter.&lt;br /&gt;
|example6 = var dest = airportinfo(&amp;quot;KSQL&amp;quot;);&lt;br /&gt;
var (course, dist) = courseAndDistance(dest);&lt;br /&gt;
print(&amp;quot;Turn to heading &amp;quot;, math.round(course), &amp;quot;. You have &amp;quot;, sprintf(&amp;quot;%.2f&amp;quot;, dist), &amp;quot; nm to go&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===createFlightplan()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = createFlightplan(path);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2331|t=Source}}&lt;br /&gt;
|text = Creates an empty flightplan object. It accepts one argument, ''path'' passed an absolute path to a .fgfp / .gpx file, it will populate the flightplan with waypoints from the file.&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Optional parameter defining the file from which a flightplan will be populated.&lt;br /&gt;
|example1 = &lt;br /&gt;
var path = getprop(&amp;quot;/sim/fg-home&amp;quot;) ~ &amp;quot;/Export/test.fgfp&amp;quot;;&lt;br /&gt;
var flightplan = createFlightplan(path);&lt;br /&gt;
debug.dump(flightplan);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===createDiscontinuity()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = createDiscontinuity();&lt;br /&gt;
|text = Returns a &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost object. A route discontinuity is inserted by an {{abbr|FMS|Flight Management System}} when it is unsure how to connect two waypoints.&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2045|t=Source}}&lt;br /&gt;
|version = 2016.1&lt;br /&gt;
|commit = {{flightgear commit|caead6|t=commit}}&lt;br /&gt;
}}&lt;br /&gt;
===createViaTo()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = createViaTo(airway, waypoint);&lt;br /&gt;
|text = Returns a &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost object. It represents a route &amp;quot;via '''airway''' to '''waypoint'''&amp;quot;.&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2009|t=Source}}&lt;br /&gt;
|version = 2016.1&lt;br /&gt;
|commit = {{flightgear commit|caead6|t=commit}}&lt;br /&gt;
|param1 = airway&lt;br /&gt;
|param1text = The name of an airway.&lt;br /&gt;
|param2 = waypoint&lt;br /&gt;
|param2text = Must be in the airway and one of:&lt;br /&gt;
* The name of a waypoint.&lt;br /&gt;
* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt; ghost object.&lt;br /&gt;
}}&lt;br /&gt;
===createWP()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = createWP(pos, name[, flag]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1964|t=Source}}&lt;br /&gt;
|text = Creates a new waypoint ghost object.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Dictates the position of the new waypoint. It can be one of the following:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. See example 4 below.&lt;br /&gt;
|param2 = name&lt;br /&gt;
|param2text = String that will become the name of the new waypoint.&lt;br /&gt;
|param3 = flag&lt;br /&gt;
|param3text = Optional string that will tell FlightGear what type of waypoint it is. Must be one of &amp;quot;sid,&amp;quot; &amp;quot;star,&amp;quot; &amp;quot;approach,&amp;quot; &amp;quot;missed,&amp;quot; or &amp;quot;pseudo.&amp;quot;&lt;br /&gt;
|example1text = Creates a waypoint directly in front and 1 km away and appends it to the flight plan.&lt;br /&gt;
|example1 = var pos = geo.aircraft_position().apply_course_distance(getprop(&amp;quot;/orientation/heading-deg&amp;quot;), 1000);&lt;br /&gt;
var wp = createWP(pos, &amp;quot;NEWWP&amp;quot;);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
|example2 = var pos = geo.aircraft_position().apply_course_distance(getprop(&amp;quot;/orientation/heading-deg&amp;quot;), 1000);&lt;br /&gt;
var wp = createWP({lat: pos.lat(), lon: pos.lon()}, &amp;quot;NEWWP&amp;quot;);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
|example3 = var apt = airportinfo();&lt;br /&gt;
var wp = createWP(apt, &amp;quot;NEWWP&amp;quot;);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
|example4 = var pos = geo.aircraft_position().apply_course_distance(getprop(&amp;quot;/orientation/heading-deg&amp;quot;), 1000);&lt;br /&gt;
var wp = createWP(pos.lat(), pos.lon(), &amp;quot;NEWWP&amp;quot;);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
|example5text = Creates a new waypoint and adds it to the flight plan. Waypoints of the type &amp;quot;pseudo&amp;quot; are then removed from the flight plan, including the new waypoint. The {{func link|print()}} statements show this.&lt;br /&gt;
|example5 = var pos = geo.aircraft_position().apply_course_distance(getprop(&amp;quot;/orientation/heading-deg&amp;quot;), 1000);&lt;br /&gt;
var wp = createWP(pos, &amp;quot;NEWWP&amp;quot;, &amp;quot;pseudo&amp;quot;);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
print(fp.getPlanSize());&lt;br /&gt;
fp.clearWPType(&amp;quot;pseudo&amp;quot;);&lt;br /&gt;
print(fp.getPlanSize());&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===createWPFrom()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = createWPFrom(object[, flag]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1989|t=Source}}&lt;br /&gt;
|text = Creates a new waypoint object from another object.&lt;br /&gt;
|param1 = object&lt;br /&gt;
|param1text = A ghost object. Must be a ghost type that is one of &amp;quot;airport,&amp;quot; &amp;quot;navaid,&amp;quot; &amp;quot;runway,&amp;quot; or &amp;quot;fix.&amp;quot;&lt;br /&gt;
|param2 = flag&lt;br /&gt;
|param2text = Optional string that will tell FlightGear what type of waypoint it is. Must be one of &amp;quot;sid,&amp;quot; &amp;quot;star,&amp;quot; &amp;quot;approach,&amp;quot; &amp;quot;missed,&amp;quot; or &amp;quot;pseudo.&amp;quot;&lt;br /&gt;
|example1text = Creates a new waypoint and appends it to the flight plan.&lt;br /&gt;
|example1 = var apt = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var wp = createWPFrom(apt);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
|example2text = Creates a new waypoint and appends it to the flight plan. This way point is then removed; the {{func link|print()}} statements prove this.&lt;br /&gt;
|example2 = var apt = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var wp = createWPFrom(apt, &amp;quot;pseudo&amp;quot;);&lt;br /&gt;
print(wp.wp_name);&lt;br /&gt;
var fp = flightplan();&lt;br /&gt;
fp.appendWP(wp);&lt;br /&gt;
print(fp.getPlanSize());&lt;br /&gt;
fp.clearWPType(&amp;quot;pseudo&amp;quot;);&lt;br /&gt;
print(fp.getPlanSize());&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===defined()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = defined(symbol);&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Returns 1 (true) or 0 (false) depending on whether a variable exists.&lt;br /&gt;
|param1 = symbol&lt;br /&gt;
|param1text = A string that will be what the function searches for.&lt;br /&gt;
|example1 = var number = 12;&lt;br /&gt;
var check_exist = func {&lt;br /&gt;
    print(&amp;quot;Variable 'number' &amp;quot;, defined(&amp;quot;number&amp;quot;) == 1 ? &amp;quot;exists&amp;quot; : &amp;quot;does not exist&amp;quot;); # 'number' does exist&lt;br /&gt;
    print(&amp;quot;Variable 'number2' &amp;quot;, defined(&amp;quot;number2&amp;quot;) == 1 ? &amp;quot;exists&amp;quot; : &amp;quot;does not exist&amp;quot;); # 'number2' does not exist&lt;br /&gt;
}&lt;br /&gt;
check_exist();&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===directory()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = directory(path);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=572|t=Source}}&lt;br /&gt;
|text = Returns a vector containing a list of the folders and files in a given file path or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if the path doesn't exist. Hidden folders and files are not added to the vector.&lt;br /&gt;
{{inote|The first two elements of the vector will be &amp;lt;code&amp;gt;'.'&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;'..'&amp;lt;/code&amp;gt;. These are for navigating back up the file tree, but have no use in Nasal. They can be safely removed from the vector.}}&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Absolute file path.&lt;br /&gt;
|example1text = Gets the folders and files in [[$FG_ROOT]], and then removes the extra first two elements (see note above). &lt;br /&gt;
|example1 = var dir = directory(getprop(&amp;quot;/sim/fg-root&amp;quot;)); # get directory&lt;br /&gt;
dir = subvec(dir, 2); # strips off the first two elements&lt;br /&gt;
debug.dump(dir); # dump the vector&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===fgcommand()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = fgcommand(cmd[, args]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=456|t=Part 1}} {{!}} {{fgdata file|Nasal/globals.nas|t=Part 2}}&lt;br /&gt;
|text = Runs an fgcommand. See also {{readme file|commands}} and [[Bindings]] for more information. See {{flightgear file|src/Main/fg_commands.cxx|l=1425}} for the full list of fgcommands. Note that fgcommands generated by {{func link|addcommand()}} can also be run using this function. Also, the full list of fgcommands depends on the version of FlightGear you have. Returns 1 (true) if the fgcommand succeeded or 0 (false) if it failed.&lt;br /&gt;
|param1 = cmd&lt;br /&gt;
|param1text = String that is the name of the command that is to be run.&lt;br /&gt;
|param2 = args&lt;br /&gt;
|param2text = If the fgcommand takes arguments, they are inputted using this argument. Can either be a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object, or a hash (see examples below).&lt;br /&gt;
|example1 = fgcommand(&amp;quot;null&amp;quot;); # does nothing&lt;br /&gt;
|example2 = var args = props.Node.new({'script': 'print(&amp;quot;Running fgcommand&amp;quot;);'});&lt;br /&gt;
if (fgcommand(&amp;quot;nasal&amp;quot;, args)) { # prints &amp;quot;Running fgcommand&amp;quot; and then one of these print statements&lt;br /&gt;
    print(&amp;quot;Fgcommand succeeded&amp;quot;);&lt;br /&gt;
} else {&lt;br /&gt;
    print(&amp;quot;Fgcommand encountered a problem&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
|example3 = var args = { 'dialog-name': 'about' };&lt;br /&gt;
fgcommand(&amp;quot;dialog-show&amp;quot;, args); # shows the 'about' dialog&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findAirportsByICAO() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findAirportsByICAO(search[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1096|t=Source}}&lt;br /&gt;
|text = Returns a vector containing &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt; ghost objects which are (by default) airports whose ICAO code matches the search string. The results are sorted by range from closest to furthest.&lt;br /&gt;
|param1 = search&lt;br /&gt;
|param1text = Search string for the function. Can either be a partial or a full ICAO code.&lt;br /&gt;
:{{icaution|The more matches there are for the given code, the longer the function will take. Passing just one character (e.g., &amp;quot;K&amp;quot;), might make FlightGear hang for a certain amount of time.}}&lt;br /&gt;
|param2 = type&lt;br /&gt;
|param2text = This will narrow the search to airports of a certain type. By default, only airports are searched for. May be one of &amp;quot;airport,&amp;quot; &amp;quot;heliport,&amp;quot; or &amp;quot;seaport.&amp;quot;&lt;br /&gt;
|example1 = var apts = findAirportsByICAO(&amp;quot;KSF&amp;quot;); # finds all airports matching &amp;quot;KSF&amp;quot;&lt;br /&gt;
foreach(var apt; apts){&lt;br /&gt;
    print(apt.name, &amp;quot; (&amp;quot;, apt.id, &amp;quot;)&amp;quot;); # prints them&lt;br /&gt;
}&lt;br /&gt;
|example2 = var apts = findAirportsByICAO(&amp;quot;SP0&amp;quot;, &amp;quot;seaport&amp;quot;); # finds all seaplane bases matching &amp;quot;SP0&amp;quot;&lt;br /&gt;
foreach(var apt; apts){&lt;br /&gt;
    print(apt.name, &amp;quot; (&amp;quot;, apt.id, &amp;quot;)&amp;quot;); # prints them&lt;br /&gt;
}&lt;br /&gt;
|example3 = var apt = findAirportsByICAO(&amp;quot;XBET&amp;quot;); # one way to check if an airport does exist&amp;quot;&lt;br /&gt;
if (size(apt) == 0) {&lt;br /&gt;
    print(&amp;quot;Airport does not exist&amp;quot;); # this one will be printed&lt;br /&gt;
} else {&lt;br /&gt;
    print(&amp;quot;Airport does exist&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findAirportsWithinRange()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findAirportsWithinRange([pos, ]range[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1066|t=Source}}&lt;br /&gt;
|text = Returns a vector of &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt; ghost object which are (by default) airports that are within a given range of a given position, or the aircraft's current position. The results are sorted by range from closest to furthest.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findAirportsWithinRange(lat, lon, range, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = range&lt;br /&gt;
|param2text = Mandatory number giving the range in nautical miles within which to search for airports/heliports/seaplane bases.only airports are searched for.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = This will narrow the search to airports of a certain type. By default, only airports are searched for. May be one of &amp;quot;airport,&amp;quot; &amp;quot;heliport,&amp;quot; or &amp;quot;seaport.&amp;quot;&lt;br /&gt;
|example1text = Searches for airports within 10 nm of [[KSFO]].&lt;br /&gt;
|example1 = var pos = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var apts = findAirportsWithinRange(pos, 10);&lt;br /&gt;
foreach(var apt; apts){&lt;br /&gt;
    print(apt.name, &amp;quot; (&amp;quot;, apt.id, &amp;quot;)&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
|example2text = Searches for seaplane bases within 15 nm of [[KSFO]].&lt;br /&gt;
|example2 = var pos = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var apts = findAirportsWithinRange(pos, 15, &amp;quot;seaport&amp;quot;);&lt;br /&gt;
foreach(var apt; apts){&lt;br /&gt;
    print(apt.name, &amp;quot; (&amp;quot;, apt.id, &amp;quot;)&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
|example3text = Searches for airports within 10 nm of your current position.&lt;br /&gt;
|example3 = var apts = findAirportsWithinRange(10);&lt;br /&gt;
foreach(var apt; apts){&lt;br /&gt;
    print(apt.name, &amp;quot; (&amp;quot;, apt.id, &amp;quot;)&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findFixesByID()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findFixesByID([pos, ]id);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1627|t=Source}}&lt;br /&gt;
|text = Returns a vector containing &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt; ghost objects matching a given ID, sorted by range from a certain position.&lt;br /&gt;
{{inote|Fixes are (usually) also known as waypoints.}}&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findFixesByID(lat, lon, id);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = id&lt;br /&gt;
|param2text = Full or partial ID of the fix to search for.&lt;br /&gt;
:{{inote|1=Inputting a partial ID does not work correctly (see [http://forum.flightgear.org/viewtopic.php?f=30&amp;amp;t=28129 here]). It is best to just input a full ID.}}&lt;br /&gt;
|example1 = var fixes = findFixesByID(&amp;quot;POGIC&amp;quot;);&lt;br /&gt;
foreach(var fix; fixes){&lt;br /&gt;
    print(fix.id, &amp;quot; - lat: &amp;quot;, fix.lat, &amp;quot; {{!}} lon: &amp;quot;, fix.lon); # prints information about POGIC&lt;br /&gt;
}&lt;br /&gt;
|example2 = var fix = findFixesByID(&amp;quot;ZUNAP&amp;quot;);&lt;br /&gt;
fix = fix[0];&lt;br /&gt;
var (course, dist) = courseAndDistance(fix);&lt;br /&gt;
print(&amp;quot;Turn to heading &amp;quot;, math.round(course), &amp;quot;. You have &amp;quot;, sprintf(&amp;quot;%.2f&amp;quot;, dist), &amp;quot; nm to go to reach &amp;quot;, fixes[0].id);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findNavaidByFrequency()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findNavaidByFrequency([pos, ]freq[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1547|t=Source}}&lt;br /&gt;
|text = Returns a &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost object for a navaid matching a given frequency. If there is more than one navaid with that frequency, the nearest station is returned.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findNavaidByFrequency(lat, lon, freq, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = freq&lt;br /&gt;
|param2text = Frequency, in megahertz, of the navaid to search for.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = This will narrow the search to navaids of a certain type. Defaults to &amp;quot;all.&amp;quot; For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.&lt;br /&gt;
|example1 = var navaid = findNavaidByFrequency(11.17);&lt;br /&gt;
print(&amp;quot;ID: &amp;quot;, navaid.id); # prints info about the navaid&lt;br /&gt;
print(&amp;quot;Name: &amp;quot;, navaid.name);&lt;br /&gt;
print(&amp;quot;Latitude: &amp;quot;, navaid.lat);&lt;br /&gt;
print(&amp;quot;Longitude: &amp;quot;, navaid.lon);&lt;br /&gt;
print(&amp;quot;Elevation (AMSL): &amp;quot;, navaid.elevation, &amp;quot; m&amp;quot;);&lt;br /&gt;
print(&amp;quot;Type: &amp;quot;, navaid.type);&lt;br /&gt;
print(&amp;quot;Frequency: &amp;quot;, sprintf(&amp;quot;%.3f&amp;quot;, navaid.frequency / 1000), &amp;quot; Mhz&amp;quot;);&lt;br /&gt;
print(&amp;quot;Range: &amp;quot;, navaid.range_nm, &amp;quot; nm&amp;quot;);&lt;br /&gt;
if(navaid.course) print(&amp;quot;Course: &amp;quot;, navaid.course);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== findNavaidsByFrequency()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findNavaidsByFrequency([pos, ]freq[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1572|t=Source}}&lt;br /&gt;
|text = Returns a vector conatining &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost objects for navaids that match a given frequency, sorted from nearest to furthest.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findNavaidsByFrequency(lat, lon, freq, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = freq&lt;br /&gt;
|param2text = Frequency, in megahertz, of the navaid to search for.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = This will narrow the search to navaids of a certain type. Defaults to &amp;quot;all.&amp;quot; For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.&lt;br /&gt;
|example1 = var navaids = findNavaidsByFrequency(10.955);&lt;br /&gt;
foreach(var navaid; navaids){&lt;br /&gt;
    print(&amp;quot;--&amp;quot;);&lt;br /&gt;
    print(&amp;quot;ID: &amp;quot;, navaid.id); # prints info about the navaid&lt;br /&gt;
    print(&amp;quot;Name: &amp;quot;, navaid.name);&lt;br /&gt;
    print(&amp;quot;Latitude: &amp;quot;, navaid.lat);&lt;br /&gt;
    print(&amp;quot;Longitude: &amp;quot;, navaid.lon);&lt;br /&gt;
    print(&amp;quot;Elevation (AMSL): &amp;quot;, navaid.elevation, &amp;quot; m&amp;quot;);&lt;br /&gt;
    print(&amp;quot;Type: &amp;quot;, navaid.type);&lt;br /&gt;
    print(&amp;quot;Frequency: &amp;quot;, sprintf(&amp;quot;%.3f&amp;quot;, navaid.frequency / 1000), &amp;quot; Mhz&amp;quot;);&lt;br /&gt;
    print(&amp;quot;Range: &amp;quot;, navaid.range_nm, &amp;quot; nm&amp;quot;);&lt;br /&gt;
    if(navaid.course) print(&amp;quot;Course: &amp;quot;, navaid.course);&lt;br /&gt;
    print(&amp;quot;--&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findNavaidsByID()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findNavaidsByID([pos, ]id[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1600|t=Source}}&lt;br /&gt;
|text = Returns a vector containing &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost objects matching a given ID, sorted by range from a certain position.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findNavaidsByID(lat, lon, id, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = id&lt;br /&gt;
|param2text = Full or partial ID of the fix to search for.&lt;br /&gt;
:{{inote|1=Inputting a partial ID does not work correctly (see [http://forum.flightgear.org/viewtopic.php?f=30&amp;amp;t=28129 here]). It is best to just input a full ID.}}&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = This will narrow the search to navaids of a certain type. Defaults to &amp;quot;all.&amp;quot; For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.&lt;br /&gt;
|example1 = var navaid = findNavaidsByID(&amp;quot;MXW&amp;quot;);&lt;br /&gt;
navaid = navaid[0];&lt;br /&gt;
print(&amp;quot;ID: &amp;quot;, navaid.id); # prints info about 'MXW' (a VOR station)&lt;br /&gt;
print(&amp;quot;Name: &amp;quot;, navaid.name);&lt;br /&gt;
print(&amp;quot;Latitude: &amp;quot;, navaid.lat);&lt;br /&gt;
print(&amp;quot;Longitude: &amp;quot;, navaid.lon);&lt;br /&gt;
print(&amp;quot;Elevation (AMSL): &amp;quot;, navaid.elevation, &amp;quot; m&amp;quot;);&lt;br /&gt;
print(&amp;quot;Type: &amp;quot;, navaid.type);&lt;br /&gt;
print(&amp;quot;Frequency: &amp;quot;, sprintf(&amp;quot;%.3f&amp;quot;, navaid.frequency / 1000), &amp;quot; Mhz&amp;quot;);&lt;br /&gt;
print(&amp;quot;Range: &amp;quot;, navaid.range_nm, &amp;quot; nm&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===findNavaidsWithinRange()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = findNavaidsWithinRange([pos, ]range[, type]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1518|t=Source}}&lt;br /&gt;
|text = Returns a vector of &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost objects which are within a given range of a given position (by default the aircraft's current position). The results are sorted from closest to furthest.&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost type&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A geo.Coord object&lt;br /&gt;
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: &amp;lt;code&amp;gt;findNavaidsWithinRange(lat, lon, range, type);&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = range&lt;br /&gt;
|param2text = Mandatory number giving the range in nautical miles within which to search for navaids. Defaults to &amp;quot;all.&amp;quot; For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.&lt;br /&gt;
|param3 = type&lt;br /&gt;
|param3text = This will narrow the search to navaids of a certain type.&lt;br /&gt;
|example1text = Searches for navaids within 10 nm of [[KSFO]].&lt;br /&gt;
|example1 = var pos = airportinfo(&amp;quot;KSFO&amp;quot;);&lt;br /&gt;
var navs = findNavaidsWithinRange(pos, 10);&lt;br /&gt;
foreach(var nav; navs){&lt;br /&gt;
    print(nav.name, &amp;quot; (ID: &amp;quot;, nav.id, &amp;quot;)&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
|example2text = Searches for navaids within 10 nm of your current position.&lt;br /&gt;
|example2 = var navs = findNavaidsWithinRange(10);&lt;br /&gt;
foreach(var nav; navs){&lt;br /&gt;
    print(nav.name, &amp;quot; (ID: &amp;quot;, nav.id, &amp;quot;)&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===finddata()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = finddata(path);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=603|t=Source}}&lt;br /&gt;
|text = Takes a relative path and tries to return an absolute one. It works by appending the relative path to some paths and testing to see if they exist. As of FlightGear v3.7, these paths are the TerraSync directory (tested first) and [[$FG_ROOT]]. &lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = A relative path as a string.&lt;br /&gt;
|example1 = var path = finddata(&amp;quot;Aircraft/Generic&amp;quot;);&lt;br /&gt;
print(path); # prints the absolute path to $FG_ROOT/Aircraft/Generic&lt;br /&gt;
|example2 = var path = finddata(&amp;quot;Airports&amp;quot;);&lt;br /&gt;
print(path); # prints the absolute path to &amp;lt;TerraSync dir&amp;gt;/Airports&lt;br /&gt;
|example3 = var path = finddata(&amp;quot;preferences.xml&amp;quot;);&lt;br /&gt;
print(path); # prints the absolute path to $FG_ROOT/preferences.xml&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===flightplan()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = flightplan([path]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1738|t=Source}}&lt;br /&gt;
|text = {{see also|Nasal Flightplan}}&lt;br /&gt;
Returns a flight plan object, either one for the current flight plan, or one loaded from a given path.&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Optional path to flight plan XML file.&lt;br /&gt;
|example1text = Gets the active flight plan and gets the ID of the current waypoint. Note that this example requires a flight plan to be set in the [[Route Manager]] first.&lt;br /&gt;
|example1 = var fp = flightplan();&lt;br /&gt;
print(fp.getWP(fp.current).id);&lt;br /&gt;
|example2text = Creates a new flight plan from an XML file and prints the IDs of the waypoints. Note that this example requires a flight plan to have been created and saved as &amp;lt;tt&amp;gt;''[[$FG_HOME]]/fp-demo.xml''&amp;lt;/tt&amp;gt;.&lt;br /&gt;
|example2 = var path = getprop('/sim/fg-home') ~ '/fp-demo.xml';&lt;br /&gt;
var fp = flightplan(path);&lt;br /&gt;
for(var i = 0; i &amp;lt; fp.getPlanSize(); i += 1){&lt;br /&gt;
    print(fp.getWP(i).id);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===geodinfo()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = geodinfo(lat, lon[, max_alt]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=981|t=Source}}&lt;br /&gt;
|text = Returns a vector containing two entries or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if no information could be obtained because the terrain tile wasn't loaded. The first entry in the vector is the elevation (in meters) for the given point, and the second is a hash with information about the assigned material (as defined in &amp;lt;tt&amp;gt;''[[$FG_ROOT]]/Materials''&amp;lt;/tt&amp;gt;), or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if there was no material information available (for example, because there is an untextured building at that location). The structure of the hash is as follows (see also {{readme file|materials}}):&lt;br /&gt;
* '''light_coverage:''' The coverage of a single point of light in m&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;.&lt;br /&gt;
* '''bumpiness:''' Normalized bumpiness factor for the material.&lt;br /&gt;
* '''load_resistance:''' The amount of pressure in N/m&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt; the material can withstand without deformation.&lt;br /&gt;
* '''solid:''' 1 (true) or false (0) depending on whether the material is solid or not.&lt;br /&gt;
* '''names:''' Vector of scenery types (usually generated by [[TerraGear]]) that will use this material. &lt;br /&gt;
* '''friction_factor:''' Normalized friction factor of the material.&lt;br /&gt;
* '''rolling_friction:''' The rolling friction coefficient of the material.&lt;br /&gt;
&lt;br /&gt;
Note that this function is a ''very'' CPU-intensive operation, particularly in FlightGear v2.4 and earlier. It is advised to use this function as little as possible.&lt;br /&gt;
|param1 = lat&lt;br /&gt;
|param1text = Latitude, inputted as a number.&lt;br /&gt;
|param2 = lon&lt;br /&gt;
|param2text = Longitude, inputted as a number.&lt;br /&gt;
|param3 = max_alt&lt;br /&gt;
|param3text = The altitude, in metres, from which the function will begin searching for the height of the terrain. Defaults to 10,000 metres. If the terrain is higher than this argument specifies, &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; will be returned.&lt;br /&gt;
|example1text = Dumps information about ground underneath the aircraft.&lt;br /&gt;
|example1 = var pos = geo.aircraft_position();&lt;br /&gt;
var info = geodinfo(pos.lat(), pos.lon());&lt;br /&gt;
debug.dump(info);&lt;br /&gt;
|example2text = Prints whether the ground underneath the aircraft is solid or is water.&lt;br /&gt;
|example2 = var pos = geo.aircraft_position();&lt;br /&gt;
var info = geodinfo(pos.lat(), pos.lon());&lt;br /&gt;
if (info != nil and info[1] != nil) {&lt;br /&gt;
    print(&amp;quot;The ground underneath the aircraft is &amp;quot;, info[1].solid == 1 ? &amp;quot;solid.&amp;quot; : &amp;quot;water.&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== geodtocart() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = geodtocart(lat, lon, alt);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=962|t=Source}}&lt;br /&gt;
|text = Converts {{wikipedia|geodetic coordinates}} (latitude, longitude, and altitude) to {{wikipedia|ECEF|Earth-centered, Earth-fixed}} coordinates (x, y and z). A vector is returned containing x, y, and z in metres. The equatorial radius of earth used is that defined by the {{wikipedia|WGS 84}} (6,378,137 metres). All argument are mandatory.&lt;br /&gt;
|param1 = lat&lt;br /&gt;
|param1text = Latitude, in degrees.&lt;br /&gt;
|param2 = lon&lt;br /&gt;
|param2text = Longitude, in degrees.&lt;br /&gt;
|param3 = alt&lt;br /&gt;
|param3text = Altitude, in metres.&lt;br /&gt;
|example1 = var (x, y, z) = geodtocart(0, 0, 0); # point is the intersection of the prime meridian and equator.&lt;br /&gt;
print(&amp;quot;x: &amp;quot;, x); # prints &amp;quot;x: 6378137&amp;quot;&lt;br /&gt;
print(&amp;quot;y: &amp;quot;, y); # prints &amp;quot;y: 0&amp;quot;&lt;br /&gt;
print(&amp;quot;z: &amp;quot;, z); # prints &amp;quot;y: 0&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== get_cart_ground_intersection()===&lt;br /&gt;
Introduced in 2017.2.1, see [[Terrain Detection]].&lt;br /&gt;
&lt;br /&gt;
===getprop()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;getprop(path[, path[, ...]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=345|t=Source}}&lt;br /&gt;
|text = Returns the value of a node in the [[Property Tree]] or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; if the node does not exist or the value is not a number (NaN).&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = There needs to be at least one argument, but there is no limit to the maximum amount of arguments that can be given. The arguments will be concatenated together to form a property tree path. The arguments must be strings, but in FlightGear v3.2 onwards, there is also support (added by {{flightgear commit|34ed79}}) for numeric arguments as indices. See example 2 below.&lt;br /&gt;
|example1 = print(&amp;quot;You have FlightGear v&amp;quot;, getprop(&amp;quot;/sim/version/flightgear&amp;quot;)); # prints FlightGear version&lt;br /&gt;
|example2text = Note that the example below will only work in FlightGear 3.2 and above.&lt;br /&gt;
|example2 = for(var i = 0; i &amp;lt; 8; i += 1){&lt;br /&gt;
    print(&amp;quot;View #&amp;quot;, i + 1, &amp;quot; is named &amp;quot;, getprop(&amp;quot;/sim/view&amp;quot;, i, &amp;quot;name&amp;quot;));&lt;br /&gt;
}&lt;br /&gt;
|example3text = Same as above, but is supported by all versions of FlightGear.&lt;br /&gt;
|example3 = for(var i = 0; i &amp;lt; 8; i += 1){&lt;br /&gt;
    print(&amp;quot;View #&amp;quot;, i + 1, &amp;quot; is named &amp;quot;, getprop(&amp;quot;/sim/view[&amp;quot; ~ i ~ &amp;quot;]/name&amp;quot;));&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
==== See also====&lt;br /&gt;
{{note| If you have to read/write the same property multiple times (e.g. in an update loop), it is more efficient to use a node object: &lt;br /&gt;
To get a Node rather than its value, use &amp;lt;code&amp;gt;props.globals.getNode()&amp;lt;/code&amp;gt; - see [[Nasal_library/props]]. }}&lt;br /&gt;
&lt;br /&gt;
===greatCircleMove()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = greatCircleMove([pos, ]course, dist);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1681|t=Source}}&lt;br /&gt;
|text = Calculates a new set of geodetic coordinates using inputs of course and distance, either from the aircraft's current position (by default) or from another set of coordinates. Returns a hash containing two members, ''lat'' and ''lon'' (latitude and longitude respectively).&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to calculate from. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost object.&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A &amp;lt;code&amp;gt;geo.Coord&amp;lt;/code&amp;gt; object&lt;br /&gt;
:* A lat/lon pair, that is, a pair of numbers (latitude followed by longitude) separated by a comma: &amp;lt;code&amp;gt;greatCircleMove(lat,lon, ...)&amp;lt;/code&amp;gt;.&lt;br /&gt;
|param2 = course&lt;br /&gt;
|param2text = Course to new set of coordinates, in degrees (in the range 0–360).&lt;br /&gt;
|param3 = dist&lt;br /&gt;
|param3text = Distance in nautical miles to the new set of coordinates.&lt;br /&gt;
|example1 = var pos = greatCircleMove(0,0, 0, 1);&lt;br /&gt;
debug.dump(pos); # print hash with coordinates&lt;br /&gt;
|example2 = var fix = findFixesByID(&amp;quot;POGIC&amp;quot;);&lt;br /&gt;
fix = fix[0];&lt;br /&gt;
var pos = greatCircleMove(fix, 45, 10);&lt;br /&gt;
debug.dump(pos); # print hash with coordinates&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===interpolate()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|private = _interpolate()&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;interpolate(prop, value1, time1[, value2, time2[, ...]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=522|t=Part 1}} {{!}} {{fgdata file|Nasal/globals.nas|t=Part 2}}&lt;br /&gt;
|text = Linearly interpolates a node in the property tree to a given value in a specified time. The value/time pairs will be run one after the other in the order that they are passed to the function. Note that the interpolation will continue even when the simulation is paused.&lt;br /&gt;
|param1 = prop&lt;br /&gt;
|param1text = String or &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object that indicates a node in the property tree to be used.&lt;br /&gt;
|param2 = value''n''&lt;br /&gt;
|param2text = Target value to change the property to in the set amount of time. This should be a number.&lt;br /&gt;
|param3 = time''n''&lt;br /&gt;
|param3text = Time in seconds, that will be taken for the interpolation.&lt;br /&gt;
|example1text = Paste the code below into the Nasal Console and execute. Then, open the Property Browser and look for the property. Finally, run the code again, and watch the value of the property change.&lt;br /&gt;
|example1 = setprop(&amp;quot;/test&amp;quot;, 0); # (re-)set property&lt;br /&gt;
interpolate(&amp;quot;/test&amp;quot;,&lt;br /&gt;
    50, 5, # interpolate to 50 in 5 seconds&lt;br /&gt;
    10, 2, # interpolate to 10 in 2 seconds&lt;br /&gt;
    0, 5); # interpolate to 0 in 5 seconds&lt;br /&gt;
|example2 = # Apply the left brake at 20% per second&lt;br /&gt;
var prop = &amp;quot;controls/gear/brake-left&amp;quot;;&lt;br /&gt;
var dist = 1 - getprop(prop);&lt;br /&gt;
if (dist == 1) {&lt;br /&gt;
    interpolate(prop, 1, dist / 0.2);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===isa()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = isa(object, class);&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Checks if an object is an instance of, or inherits from, a second object (or class), returning 1 (true) if it is and 0 (false) if otherwise.&lt;br /&gt;
|param1 = object&lt;br /&gt;
|param1text = Object to check.&lt;br /&gt;
|param2 = class&lt;br /&gt;
|param2text = Class/object to check that '''object''' inherits from or is an instance of.&lt;br /&gt;
|example1 = var coord = geo.Coord.new();&lt;br /&gt;
if(isa(coord, geo.Coord)){&lt;br /&gt;
    print(&amp;quot;Variable 'coord' is an instance of class 'geo.Coord'&amp;quot;); # this one will be printed&lt;br /&gt;
} else {&lt;br /&gt;
    print(&amp;quot;Variable 'coord' is not an instance of class 'geo.Coord'&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
|example2 = var coord = geo.Coord.new();&lt;br /&gt;
if(isa(coord, props.Node)){&lt;br /&gt;
    print(&amp;quot;Variable 'coord' is an instance of class 'props.Node'&amp;quot;);&lt;br /&gt;
} else {&lt;br /&gt;
    print(&amp;quot;Variable 'coord' is not an instance of class 'props.Node'&amp;quot;); # this one will be printed&lt;br /&gt;
}&lt;br /&gt;
|example3text = The example below demonstrates checking of inheritance.&lt;br /&gt;
|example3 = var Const = {&lt;br /&gt;
    constant: 2,&lt;br /&gt;
    getConst: func {&lt;br /&gt;
        return me.constant;&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
var Add = {&lt;br /&gt;
    new: func {&lt;br /&gt;
        return { parents: [Add, Const] };&lt;br /&gt;
    },&lt;br /&gt;
&lt;br /&gt;
    addToConst: func(a){&lt;br /&gt;
        return a * me.getConst();&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
var m = Add.new();&lt;br /&gt;
print(m.addToConst(4));&lt;br /&gt;
&lt;br /&gt;
if(isa(m, Add)) print(&amp;quot;Variable 'm' is an instance of class 'Add'&amp;quot;); # will be printed&lt;br /&gt;
if(isa(m, Const)) print(&amp;quot;Variable 'm' is an instance of class 'Const'&amp;quot;); # will also be printed&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===logprint()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;logprint(priority[, msg[, msg[, ...]]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=431|t=Source}}&lt;br /&gt;
|text = Concatenates a message and logs it with a given priority level. Unlike {{func link|print()}} and {{func link|printlog()}}, message outputted by this function will be logged in your &amp;lt;code&amp;gt;[[Commonly used debugging tools#fgfs.log|fgfs.log]]&amp;lt;/code&amp;gt; file as coming from the Nasal file itself rather than from {{flightgear file|src/Scripting/NasalSys.cxx}}.&lt;br /&gt;
|param1 = priority&lt;br /&gt;
|param1text = Number specifying the priority level of the outputted message:&lt;br /&gt;
:{{{!}} class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Number !! Debug type&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 1 {{!!}} Bulk&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 2 {{!!}} Debug&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 3 {{!!}} Info&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 4 {{!!}} Warn&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 5 {{!!}} Alert&lt;br /&gt;
{{!}}}&lt;br /&gt;
|param2 = msg&lt;br /&gt;
|param2text = The message. There is no limit to the arguments you give give. They will be concatenated together before logging.&lt;br /&gt;
|example1 = # logs the value of pi to three decimal places with log level 3&lt;br /&gt;
logprint(3, &amp;quot;pi = &amp;quot;, sprintf(&amp;quot;%.3f&amp;quot;, math.pi));&lt;br /&gt;
|example2 = logprint(5, &amp;quot;Alert! This is an important message!&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
{{note| &lt;br /&gt;
The following constants have been added to the development branch of FlightGear (&amp;quot;next&amp;quot;) and will be releases with FG 2020.1 so you won't have to remember the numbers anymore:&lt;br /&gt;
&lt;br /&gt;
LOG_BULK, LOG_WARN, LOG_DEBUG, LOG_INFO, LOG_ALERT, DEV_WARN, DEV_ALERT }}&lt;br /&gt;
&lt;br /&gt;
===magvar() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = magvar([pos]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1642|t=Source}}&lt;br /&gt;
|text = Returns the {{wikipedia|magnetic variation}} at a given set of coordinates. The table below gives the magnetic model used depending on the version of FlightGear.&lt;br /&gt;
{{{!}} class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! FlightGear versions !! Model !! Reference date&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 3.6 and above {{!!}} {{wikipedia|World Magnetic Model}} (WMM) 2015 {{!!}} 1 January 2015&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 0.9.11-pre1 to 3.4 {{!!}} WMM 2005 {{!!}} 1 January 2005&lt;br /&gt;
{{!-}}&lt;br /&gt;
{{!}} 0.7.3 to 0.9.10 {{!!}} WMM 2000 {{!!}} 1 January 2000&lt;br /&gt;
{{!}}}&lt;br /&gt;
|param1 = pos&lt;br /&gt;
|param1text = Optional position to calculate from. If not given, the aircraft's current position will be used. Can be one of:&lt;br /&gt;
:* An &amp;lt;code&amp;gt;airport&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;runway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;taxiway&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fix&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;waypoint&amp;lt;/code&amp;gt; ghost object.&lt;br /&gt;
:* A hash with ''lat'' and ''lon'' members&lt;br /&gt;
:* A &amp;lt;code&amp;gt;geo.Coord&amp;lt;/code&amp;gt; object&lt;br /&gt;
:* A lat/lon pair, that is, a pair of numbers (latitude followed by longitude) separated by a comma: &amp;lt;code&amp;gt;magvar(lat,lon)&amp;lt;/code&amp;gt;.&lt;br /&gt;
|example1 = print(magvar(0, 0)); # prints the magnetic variation at 0, 0&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===maketimer() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = maketimer(interval[, self], function);&lt;br /&gt;
|source = ''Implemented using the {{API Link|flightgear|class|TimerObj}} class.''&amp;lt;br&amp;gt;{{flightgear file|src/Scripting/NasalSys.cxx|l=90|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=533|t=Part 2}}&lt;br /&gt;
|version = 2.12&lt;br /&gt;
|commit = {{flightgear commit|ab939f|t=commit}}&lt;br /&gt;
|text = Returns a timer object containing the following methods and members:&lt;br /&gt;
* '''start()''': Starts the timer.&lt;br /&gt;
* '''stop()''': Stops the timer.&lt;br /&gt;
* '''restart(interval)''': Restarts the timer with the given interval.&lt;br /&gt;
* '''singleShot''': Bool showing whether the timer is only to be run once, or continuously until told to stop. Can be both set and read from (see examples).&lt;br /&gt;
* '''isRunning''': Read-only bool telling whether the timer is currently running.&lt;br /&gt;
* '''simulatedTime''': (FG 2017.1+; {{flightgear commit|0af316|t=commit}}) Bool telling whether the timer is using simulated time (which accounts for pause, etc.). Defaults to false (use real time). Can be both read and set. This cannot be changed while the timer is running.&lt;br /&gt;
Unlike {{func link|settimer()}}, which it replaces, &amp;lt;code&amp;gt;maketimer()&amp;lt;/code&amp;gt; provides more control over the timer. In addition, it can help reduce memory usage.&lt;br /&gt;
|param1 = interval&lt;br /&gt;
|param1text = Interval in seconds for the timer.&lt;br /&gt;
|param2 = self&lt;br /&gt;
|param2text = Optional parameter specifying what any &amp;lt;code&amp;gt;'''me'''&amp;lt;/code&amp;gt; references in the function being called will refer to.&lt;br /&gt;
|param3 = function&lt;br /&gt;
|param3text = Function to be called at the given interval.&lt;br /&gt;
|example1 = var timer = maketimer(1, func(){&lt;br /&gt;
    print(&amp;quot;Hello, World!&amp;quot;); # print &amp;quot;Hello, World!&amp;quot; once every second (call timer.stop() to stop it)&lt;br /&gt;
});&lt;br /&gt;
timer.start();&lt;br /&gt;
|example2 = var timer = maketimer(1, math, func(){&lt;br /&gt;
    print(me.math); # 'me' reference is the 'math' namespace&lt;br /&gt;
});&lt;br /&gt;
timer.singleShot = 1; # timer will only be run once&lt;br /&gt;
timer.start();&lt;br /&gt;
|example3 = var timer = maketimer(1, func(){&lt;br /&gt;
    print(&amp;quot;Hello, World!&amp;quot;); # print &amp;quot;Hello, World!&amp;quot; once every second (call timer.stop() to stop it)&lt;br /&gt;
});&lt;br /&gt;
timer.start();&lt;br /&gt;
print(timer.isRunning); # prints 1&lt;br /&gt;
|example4text = In the example below, &amp;quot;Hello, World!&amp;quot; will be printed after one second the first time, and after two seconds thereafter.&lt;br /&gt;
|example4 = var update = func(){&lt;br /&gt;
    print(&amp;quot;Hello, World!&amp;quot;);&lt;br /&gt;
    timer.restart(2); # restarts the timer with a two second interval&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var timer = maketimer(1, update);&lt;br /&gt;
timer.singleShot = 1;&lt;br /&gt;
timer.start();&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===maketimestamp()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = maketimestamp()&lt;br /&gt;
|source = ''Implemented using the {{API Link|flightgear|class|TimeStampObj}} class.''&amp;lt;br&amp;gt;{{flightgear file|src/Scripting/NasalSys.cxx|l=214|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=589|t=Part 2}}&lt;br /&gt;
|version = 2019.2&lt;br /&gt;
|commit = {{flightgear commit|7db06300|t=commit}}&lt;br /&gt;
|text = Returns a time stamp object to allow high resolution timing of Nasal operations. When created the timer will automatically be stamped. The object has the following methods:&lt;br /&gt;
* '''stamp()''': Resets the timing operation. Call this first.&lt;br /&gt;
* '''elapsedMSec()''': returns number of milliseconds elapsed since stamp() called. Resolution may vary depending on platform but is usually at least millisecond accuracy.&lt;br /&gt;
* '''elapsedUSec()''': returns number of microseconds elapsed since stamp() called. Resolution may vary depending on platform but is usually at least millisecond accuracy.&lt;br /&gt;
|&lt;br /&gt;
|example1text = In the example below the number of milliseconds elapsed will be printed.&lt;br /&gt;
|example1 = var timestamp = maketimestamp();&lt;br /&gt;
timestamp.stamp();&lt;br /&gt;
print(timestamp.elapsedMSec(), &amp;quot;ms elapsed&amp;quot;);&lt;br /&gt;
print(timestamp.elapsedMSec(), &amp;quot;ms elapsed&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===md5()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = md5(string);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=758|t=Source}}&lt;br /&gt;
|version = 3.2&lt;br /&gt;
|commit = {{flightgear commit|cfbf9e|t=commit}}&lt;br /&gt;
|text = Returns a the {{wikipedia|MD5}} hash (as a string) of the inputted string.&lt;br /&gt;
|param1 = string&lt;br /&gt;
|param1text = String the generate the hash of. Mandatory.&lt;br /&gt;
|example1text = The below code should output &amp;lt;code&amp;gt;65a8e27d8879283831b664bd8b7f0ad4&amp;lt;/code&amp;gt;.&lt;br /&gt;
|example1 = print(md5(&amp;quot;Hello, World!&amp;quot;));&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===navinfo()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = navinfo(lat, lon, type, id);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1453|t=Source}}&lt;br /&gt;
|text = Returns vector &amp;lt;code&amp;gt;navaid&amp;lt;/code&amp;gt; ghost objects matching the given '''type''' and '''id''' or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; on error.&lt;br /&gt;
|param1 = lat ''and'' lon&lt;br /&gt;
|param1text = If given, the returned navaids will be put into order of ascending distance from the location.&lt;br /&gt;
|param2 = type&lt;br /&gt;
|param2text = Narrows the search to the given type. Must be one of &amp;quot;any,&amp;quot; &amp;quot;fix,&amp;quot; &amp;quot;vor,&amp;quot; &amp;quot;ndb,&amp;quot; &amp;quot;ils,&amp;quot; &amp;quot;dme,&amp;quot; or &amp;quot;tacan.&amp;quot; Defaults to the equivalent of &amp;quot;any.&amp;quot;&lt;br /&gt;
|param3 = id&lt;br /&gt;
|param3text = ID to search for. Note that, although all the parameters are technically optional, this parameter must be given, otherwise an empty vector will be returned.&lt;br /&gt;
|example1 = navinfo(&amp;quot;vor&amp;quot;); # returns all VORs&lt;br /&gt;
|example2 = navinfo(&amp;quot;HAM&amp;quot;); # return all navaids whose names start with &amp;quot;HAM&amp;quot;&lt;br /&gt;
|example3 = navinfo(&amp;quot;vor&amp;quot;, &amp;quot;HAM&amp;quot;); # return all VORs whose names start with &amp;quot;HAM&amp;quot;&lt;br /&gt;
|example4 = navinfo(34,48,&amp;quot;vor&amp;quot;,&amp;quot;HAM&amp;quot;); # return all VORs whose names start with &amp;quot;HAM&amp;quot; and sorted by distance relative to 34°, 48°&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===parse_markdown()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = parse_markdown(markdown);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=745|t=Part 1}} {{!}} {{simgear file|simgear/misc/SimpleMarkdown.cxx|t=Part 2}} &lt;br /&gt;
|version = 3.2&lt;br /&gt;
|text = Parses a string containing {{wikipedia|Markdown}} and returns the result as a string. As of FlightGear 2016.1, it is just a simple parser, and does the following:&lt;br /&gt;
* It strips whitespace from the beginning of the string.&lt;br /&gt;
* It supports [http://daringfireball.net/projects/markdown/syntax#p paragraphs and line breaks].&lt;br /&gt;
* It collapses whitespace.&lt;br /&gt;
* It converts unordered [http://daringfireball.net/projects/markdown/syntax#list lists] to use a bullet character (&amp;amp;bull;). Note that the bullet character is implemented in hexadecimal UTF-8 character bytes (&amp;lt;code&amp;gt;E2 80 A2&amp;lt;/code&amp;gt;), as so may not work properly when the being displayed in an encoding other than UTF-8.&lt;br /&gt;
|param1 = markdown&lt;br /&gt;
|param1text = String containing Markdown to be parsed.&lt;br /&gt;
|example1text = &lt;br /&gt;
Save the below code as &amp;lt;tt&amp;gt;''[[$FG_ROOT]]/gui/dialogs/test.xml''&amp;lt;/tt&amp;gt;, then run the Nasal code below it to open the dialog. To change the markdown to be parsed, simply change the code in the highlighted section, save it, and reload the GUI (&amp;lt;tt&amp;gt;Debug &amp;gt; Reload GUI&amp;lt;/tt&amp;gt;).&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot; highlight=&amp;quot;41-50&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;PropertyList&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;name&amp;gt;test&amp;lt;/name&amp;gt;&lt;br /&gt;
&amp;lt;layout&amp;gt;vbox&amp;lt;/layout&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;group&amp;gt;&lt;br /&gt;
    &amp;lt;layout&amp;gt;hbox&amp;lt;/layout&amp;gt;&lt;br /&gt;
    &lt;br /&gt;
    &amp;lt;empty&amp;gt;&lt;br /&gt;
        &amp;lt;stretch&amp;gt;true&amp;lt;/stretch&amp;gt;&lt;br /&gt;
    &amp;lt;/empty&amp;gt;&lt;br /&gt;
    &lt;br /&gt;
    &amp;lt;text&amp;gt;&lt;br /&gt;
        &amp;lt;label&amp;gt;parse_markdown() test dialog&amp;lt;/label&amp;gt;&lt;br /&gt;
    &amp;lt;/text&amp;gt;&lt;br /&gt;
    &lt;br /&gt;
    &amp;lt;empty&amp;gt;&lt;br /&gt;
        &amp;lt;stretch&amp;gt;true&amp;lt;/stretch&amp;gt;&lt;br /&gt;
    &amp;lt;/empty&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;button&amp;gt;&lt;br /&gt;
        &amp;lt;legend&amp;gt;&amp;lt;/legend&amp;gt;&lt;br /&gt;
        &amp;lt;pref-width&amp;gt;16&amp;lt;/pref-width&amp;gt;&lt;br /&gt;
        &amp;lt;pref-height&amp;gt;16&amp;lt;/pref-height&amp;gt;&lt;br /&gt;
        &amp;lt;binding&amp;gt;&lt;br /&gt;
            &amp;lt;command&amp;gt;dialog-close&amp;lt;/command&amp;gt;&lt;br /&gt;
        &amp;lt;/binding&amp;gt;&lt;br /&gt;
    &amp;lt;/button&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/group&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;canvas&amp;gt;&lt;br /&gt;
    &amp;lt;name&amp;gt;Canvas plot&amp;lt;/name&amp;gt;&lt;br /&gt;
    &amp;lt;stretch&amp;gt;true&amp;lt;/stretch&amp;gt;&lt;br /&gt;
    &amp;lt;pref-width&amp;gt;400&amp;lt;/pref-width&amp;gt;&lt;br /&gt;
    &amp;lt;pref-height&amp;gt;300&amp;lt;/pref-height&amp;gt;&lt;br /&gt;
    &amp;lt;nasal&amp;gt;&lt;br /&gt;
        &amp;lt;load&amp;gt;&amp;lt;![CDATA[&lt;br /&gt;
var text = 'Items:&lt;br /&gt;
* apples&lt;br /&gt;
* oranges&lt;br /&gt;
* pears&lt;br /&gt;
&lt;br /&gt;
Some text.&lt;br /&gt;
Some more items:&lt;br /&gt;
* apples&lt;br /&gt;
* oranges&lt;br /&gt;
* pears';&lt;br /&gt;
&lt;br /&gt;
var parsed = parse_markdown(text);&lt;br /&gt;
&lt;br /&gt;
var root_canvas = canvas.get(cmdarg());&lt;br /&gt;
root_canvas.setColorBackground(255, 255, 255);&lt;br /&gt;
var root = root_canvas.createGroup();&lt;br /&gt;
&lt;br /&gt;
var text_dis = root.createChild(&amp;quot;text&amp;quot;)&lt;br /&gt;
    .setText(parsed)&lt;br /&gt;
    .setTranslation(5, 5)&lt;br /&gt;
    .setFont(&amp;quot;LiberationFonts\LiberationSans-Regular.ttf&amp;quot;)&lt;br /&gt;
    .setFontSize(15)&lt;br /&gt;
    .setColor(0, 0, 0)&lt;br /&gt;
    .setDrawMode(canvas.Text.TEXT)&lt;br /&gt;
    .setAlignment(&amp;quot;left-top&amp;quot;);&lt;br /&gt;
        ]]&amp;gt;&amp;lt;/load&amp;gt;&lt;br /&gt;
    &amp;lt;/nasal&amp;gt;&lt;br /&gt;
&amp;lt;/canvas&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/PropertyList&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|example1 = fgcommand(&amp;quot;dialog-show&amp;quot;, {&amp;quot;dialog-name&amp;quot;: &amp;quot;test&amp;quot;});&lt;br /&gt;
|example2text = The example below parses Markdown and outputs it in a HTML document. The parsed text is placed in &amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot; inline&amp;gt;&amp;lt;pre&amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/syntaxhighlight&amp;gt; tags. To change the Markdown to be parsed, simply edit the variable &amp;lt;tt&amp;gt;markdown&amp;lt;/tt&amp;gt; at the top of the code.&lt;br /&gt;
|example2 = &amp;lt;nowiki&amp;gt;var markdown = 'Items:&lt;br /&gt;
* apples&lt;br /&gt;
* oranges&lt;br /&gt;
* pears&lt;br /&gt;
&lt;br /&gt;
Some text.&lt;br /&gt;
Some more items:&lt;br /&gt;
* apples&lt;br /&gt;
* oranges&lt;br /&gt;
* pears';&lt;br /&gt;
&lt;br /&gt;
var parsed = parse_markdown(markdown);&lt;br /&gt;
&lt;br /&gt;
debug.dump(parsed);&lt;br /&gt;
&lt;br /&gt;
var path = string.normpath(getprop(&amp;quot;/sim/fg-home&amp;quot;) ~ '/Export/parse_markdown()-test.html');&lt;br /&gt;
&lt;br /&gt;
var file = io.open(path, &amp;quot;w&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
var html = &amp;quot;&amp;lt;!DOCTYPE html&amp;gt;\n\n&amp;lt;html&amp;gt;\n\n&amp;lt;head&amp;gt;\n\t&amp;lt;meta charset=\&amp;quot;UTF-8\&amp;quot;&amp;gt;\n\t&amp;lt;title&amp;gt;parse_markdown() test generated by Nasal&amp;lt;/title&amp;gt;\n&amp;lt;/head&amp;gt;\n\n&amp;lt;body&amp;gt;\n\t&amp;lt;pre&amp;gt;&amp;quot; ~ parsed ~ &amp;quot;&amp;lt;/pre&amp;gt;\n&amp;lt;/body&amp;gt;\n\n&amp;lt;/html&amp;gt;&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
io.write(file, html);&lt;br /&gt;
io.close(file);&lt;br /&gt;
print(&amp;quot;Done, file ready for viewing (&amp;quot; ~ path ~ &amp;quot;)&amp;quot;);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===parsexml()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;parsexml(path[, start[, end[, data[, pro_ins]]]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=710|t=Source}}&lt;br /&gt;
|text = This function is an interface into the built-in [http://expat.sourceforge.net/ Expat XML parser]. The absolute path to the file is returned as string, or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; is returned on error.&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Mandatory absolute path to the XML file to be parsed.&lt;br /&gt;
|param2 = start&lt;br /&gt;
|param2text = Optional callback function that will be called for every starting tag. The function should take two argument: the tag name and a hash containing attributes.&lt;br /&gt;
|param3 = end&lt;br /&gt;
|param3text = Optional callback function that will be called for every ending tag. The function should take one argument: the tag name.&lt;br /&gt;
|param4 = data&lt;br /&gt;
|param4text = Optional callback function that will be called for every piece of data within a set of tags. The function should take one argument: the data as a string.&lt;br /&gt;
|param5 = pro_ins&lt;br /&gt;
|param5text = Optional callback function that will be called for every {{wikipedia|Processing Instruction|processing instruction}}. The function should take two argument: the target and the data string.&lt;br /&gt;
|example1text = Save the below XML code in &amp;lt;tt&amp;gt;''[[$FG_HOME]]/Export/demo.xml''&amp;lt;/tt&amp;gt;. Then, execute the Nasal code below in the Nasal Console. The XML will be parsed and each bit of the code will be printed.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;?xml-stylesheet type=&amp;quot;text/xsl&amp;quot; href=&amp;quot;style.xsl&amp;quot;?&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;foo&amp;gt;&lt;br /&gt;
  &amp;lt;blah type=&amp;quot;string&amp;quot;&amp;gt;&amp;lt;![CDATA[ &amp;lt;sender&amp;gt;John Smith&amp;lt;/sender&amp;gt; ]]&amp;gt;&amp;lt;/blah&amp;gt;&lt;br /&gt;
  &amp;lt;blah2 type=&amp;quot;string&amp;quot;&amp;gt;Orange &amp;amp;amp; lemons&amp;lt;/blah2&amp;gt;&lt;br /&gt;
&amp;lt;/foo&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|example1 = var start = func(name, attr){&lt;br /&gt;
    print(&amp;quot;Starting tag: '&amp;quot;, name, &amp;quot;'&amp;quot;);&lt;br /&gt;
    foreach(var a; keys(attr)){&lt;br /&gt;
        print(&amp;quot;\twith attribute &amp;quot;, a, '=&amp;quot;', attr[a], '&amp;quot;');&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var end = func(name){&lt;br /&gt;
    print(&amp;quot;Ending tag: '&amp;quot;, name, &amp;quot;'&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var data = func(data){&lt;br /&gt;
    print(&amp;quot;Data = '&amp;quot;, data, &amp;quot;'&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var pro_instr = func(target, data){&lt;br /&gt;
    print(&amp;quot;Processing instruction: target = '&amp;quot;, target, &amp;quot;', data = '&amp;quot;, data, &amp;quot;'&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
parsexml(getprop(&amp;quot;/sim/fg-home&amp;quot;) ~ '/Export/demo.xml', start, end, data, pro_instr);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===print()===&lt;br /&gt;
{{Note|As of 07/2020, we are in the process of slowly deprecating and removing Nasal print() (in fgdata) where possible in favor of log [[#logprint()]] which takes a priority level.&lt;br /&gt;
&lt;br /&gt;
This is part of the goal that we achieve zero output at log-level at alert, and everything shown at ‘warn; is really a warning, not just information.&amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37042224/&amp;lt;/ref&amp;gt;}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;print(data[, data[, ...]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=415|t=Source}}&lt;br /&gt;
|text = Concatenates its arguments and then prints it to the terminal and the [[Commonly used debugging tools#fgfs.log|log]]. Note that a newline is automatically added.&lt;br /&gt;
|param1 = data&lt;br /&gt;
|param1text = Data to print. Only strings and numbers can be printed; other data types will not be. There many be any number of arguments; they will just be concatenated together.&lt;br /&gt;
|example1 = print(&amp;quot;Just&amp;quot;, &amp;quot; a &amp;quot;, &amp;quot;test&amp;quot;); # prints &amp;quot;Just a test&amp;quot;&lt;br /&gt;
|example2 = print(&amp;quot;pi = &amp;quot;, math.pi); # prints &amp;quot;pi = 3.141592...&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== printf() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;printf(format[, arg[, arg, [...]]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Creates and prints a formatted string. For a description of its arguments, see {{func link|sprintf()}} (it is, in fact, implemented using &amp;lt;code&amp;gt;sprintf()&amp;lt;/code&amp;gt;).&lt;br /&gt;
|example1 = printf(&amp;quot;In hexadecimal, 100000 = %X&amp;quot;, 186A0); # prints &amp;quot;In hexadecimal, 100000 = 186A0&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===printlog()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;printlog(level, data[, data[, ...]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Prints the given message with the given log level. If the log level is higher or equal to &amp;lt;code&amp;gt;/sim/logging/priority&amp;lt;/code&amp;gt;, it is printed.&lt;br /&gt;
|param1 = level&lt;br /&gt;
|param1text = Mandatory log level as a string. Must be one of &amp;quot;none,&amp;quot; &amp;quot;bulk,&amp;quot; &amp;quot;debug,&amp;quot; &amp;quot;info,&amp;quot; &amp;quot;warn,&amp;quot; or &amp;quot;alert.&amp;quot; Note that &amp;quot;none&amp;quot; will mean that the message will never be printed.&lt;br /&gt;
|param2 = data&lt;br /&gt;
|param2text = Data to be printed. Only strings and numbers will be printed; others will not be. There may be any number of arguments; they will just be concatenated together.&lt;br /&gt;
|example1 = printlog(&amp;quot;alert&amp;quot;, &amp;quot;This is an alert&amp;quot;); # message will be printed&lt;br /&gt;
|example2 = printlog(&amp;quot;info&amp;quot;, &amp;quot;Just informing you about something&amp;quot;); # message will be printed only if log level is set to &amp;quot;info&amp;quot; or less&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===rand()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = rand();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=554|t=Source}}&lt;br /&gt;
|text = Returns a random floating point number between 0 (inclusive) and 1 (exclusive). It takes no arguments.&lt;br /&gt;
|example1 = print(rand()); # prints random number&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===registerFlightPlanDelegate()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = registerFlightPlanDelegate(init_func);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1879|t=Source}}&lt;br /&gt;
|text = Registers a flight plan delegate. See &amp;lt;tt&amp;gt;''{{fgdata file|Nasal/route_manager.nas|t=$FG_ROOT/Nasal/route_manager.nas}}''&amp;lt;/tt&amp;gt; for examples.&lt;br /&gt;
|param1 = init_func&lt;br /&gt;
|param1text = Initialization function which will be called during FlightGear's startup.&lt;br /&gt;
}}&lt;br /&gt;
===removecommand()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = removecommand(cmd);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=668|t=Source}}&lt;br /&gt;
|text = Removes the given fgcommand. Returns &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt;.&lt;br /&gt;
{{caution|This will remove '''any''' [[fgcommands|fgcommand]], even those implemented in C++, so use with caution!}}&lt;br /&gt;
|param1 = cmd&lt;br /&gt;
|param1text = String specifying the name of the command to remove.&lt;br /&gt;
|example1 = addcommand(&amp;quot;hello&amp;quot;, func(){&lt;br /&gt;
    print(&amp;quot;Hello&amp;quot;);&lt;br /&gt;
});&lt;br /&gt;
fgcommand(&amp;quot;hello&amp;quot;); # &amp;quot;Hello&amp;quot; will be printed&lt;br /&gt;
removecommand(&amp;quot;hello&amp;quot;); # removes it&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===removelistener()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = removelistener(id);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=1384|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=506|t=Part 2}}&lt;br /&gt;
|text = Removes and deactivates the given listener and returns the number of listeners left or &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt; on error.&lt;br /&gt;
{{note|It is good practice to remove listeners when they are not required anymore. This prevents the listeners reducing FlightGear's run performance.}}&lt;br /&gt;
|param1 = id&lt;br /&gt;
|param1text = ID of listener as returned by {{func link|setlistener()}}.&lt;br /&gt;
|example1 = var ls = setlistener(&amp;quot;/sim/test&amp;quot;, func(){&lt;br /&gt;
    print(&amp;quot;Property '/sim/test' has been changed&amp;quot;);&lt;br /&gt;
});&lt;br /&gt;
setprop(&amp;quot;/sim/test&amp;quot;, &amp;quot;blah&amp;quot;); # trigger listener&lt;br /&gt;
var rem = removelistener(ls); # remove listener&lt;br /&gt;
print(&amp;quot;There are &amp;quot;, rem, &amp;quot; listeners remaining&amp;quot;);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===resolvepath()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = resolvepath(path);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=604|t=Source}}&lt;br /&gt;
|text = Takes a relative path as a string and uses [[SimGear]]'s path-resolving framework to return an absolute path as a string. If the path could not be resolved, an empty string is returned. See [[Resolving Paths]] for a detailed description of the algorithm. This function can also be used to check if a file exists.&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = Relative path to be completed.&lt;br /&gt;
|example1 = print(resolvepath(&amp;quot;Nasal/globals.nas&amp;quot;)); # prints the equivalent of $FG_ROOT/Nasal/globals.nas&lt;br /&gt;
|example2 = print(resolvepath(&amp;quot;blah&amp;quot;)); # prints nothing; could not be resolved&lt;br /&gt;
|example3 = var file_path = resolvepath(&amp;quot;Aircraft/SenecaII/some-file&amp;quot;);&lt;br /&gt;
if (file_path != &amp;quot;&amp;quot;){&lt;br /&gt;
    gui.popupTip(&amp;quot;some-file found&amp;quot;, 2);&lt;br /&gt;
} else {&lt;br /&gt;
    gui.popupTip(&amp;quot;some-file not found&amp;quot;, 2);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===setlistener()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;setlistener(node, code[, init[, type]]);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|private = _setlistener()&lt;br /&gt;
|source = {{flightgear file|src/Scripting/Nasal|l=499|t=Part 1}} {{!}} {{flightgear file|src/Scripting/Nasal|l=1350|t=Part 2}}&amp;lt;br&amp;gt;''Listener implemented using the {{API Link|flightgear|class|FGNasalListener}} class.''&lt;br /&gt;
|text = Creates a listener which will be triggered when the given property is changed (depending on the '''type'''). A unique integer ID is returned; this can later be used as the argument to {{func link|removelistener()}}.&lt;br /&gt;
{{note|Listeners are known to be a source of resource leaks. To avoid this, please take measures such as:&lt;br /&gt;
* Using {{func link|removelistener()}} when they are not needed any more.&lt;br /&gt;
* Using a single initialization listener.&lt;br /&gt;
* Avoiding tying listeners to properties that are rapidly updated (e.g., many times per frame).&lt;br /&gt;
}}&lt;br /&gt;
|param1 = node&lt;br /&gt;
|param1text = Mandatory string or &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object pointing to a property in the [[Property Tree]].&lt;br /&gt;
|param2 = code&lt;br /&gt;
|param2text = Mandatory callback function to execute when the listener is triggered. The function can take up to four arguments in the following order:&lt;br /&gt;
* '''changed''': a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object pointing to the changed node.&lt;br /&gt;
* '''listen''': a &amp;lt;code&amp;gt;props.Node&amp;lt;/code&amp;gt; object pointing to the listened-to node. Note that this argument maybe different depending on the '''type'''.&lt;br /&gt;
* '''mode''': an integer telling how the listener was triggered. 0 means that the value was changed. 1 means that a child property was added. -1 means that a child property was removed.&lt;br /&gt;
* '''is_child''': boolean telling whether '''changed''' is a child property of the listened-to '''node''' or not. 1 (true) if it is, 0 (false) otherwise.&lt;br /&gt;
|param3 = init&lt;br /&gt;
|param3text = If set to 1 (true), the listener will additionally be triggered when it is created. This argument is optional and defaults to 0 (false).&lt;br /&gt;
|param4 = type&lt;br /&gt;
|param4text = Integer specifying the listener's behavior. 0 means that the listener will only trigger when the property is changed. 1 means that the trigger will always be triggered when the property is written to. 2 will mean that the listener will be triggered even if child properties are modified. This argument is optional and defaults to 1.&lt;br /&gt;
|example1 = var prop = props.globals.initNode(&amp;quot;/sim/test&amp;quot;, &amp;quot;&amp;quot;, &amp;quot;STRING&amp;quot;); # create property&lt;br /&gt;
var id = setlistener(&amp;quot;/sim/test&amp;quot;, func(n){ # create listener&lt;br /&gt;
    print(&amp;quot;Value: &amp;quot;, n.getValue());&lt;br /&gt;
});&lt;br /&gt;
setprop(&amp;quot;/sim/test&amp;quot;, &amp;quot;blah&amp;quot;); # trigger listener&lt;br /&gt;
removelistener(id); # remove listener&lt;br /&gt;
prop.remove(); # remove property&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===setprop()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = &amp;lt;nowiki&amp;gt;setprop(path[, path[, ...]], value);&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=385|t=Source}}&lt;br /&gt;
|text = Sets the value of a property in the [[Property Tree]]. If the property does not exist, it will be created. Returns 1 (true) on success or 0 (false) if there was an error.&lt;br /&gt;
{{note|If you want to remove a property, you will have to use one of the &amp;lt;code&amp;gt;props&amp;lt;/code&amp;gt; helpers:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
props.globals.getNode(&amp;quot;foo/bar&amp;quot;).remove(); # take out the complete node&lt;br /&gt;
props.globals.getNode(&amp;quot;foo&amp;quot;).removeChild(&amp;quot;bar&amp;quot;); # take out a certain child node&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
|param1 = path&lt;br /&gt;
|param1text = There needs to be at least one '''path''' argument, but there is no limit to the maximum amount that can be given. They will be concatenated together to form a property tree path. The arguments must be strings, but in FlightGear v3.2 onwards, there also is support (added by {{flightgear commit|34ed79}}) for numeric arguments as indices. See example 2 below.&lt;br /&gt;
|param2 = value&lt;br /&gt;
|param2text = Value to write to the given property. Must be either a string or a number.&lt;br /&gt;
|example1 = setprop(&amp;quot;/sim/demo&amp;quot;, &amp;quot;This is a demo&amp;quot;);&lt;br /&gt;
|example2text = Note that the example below will only work in FlightGear 3.2 and above.&lt;br /&gt;
|example2 = for(var i = 0; i &amp;lt; 3; i += 1){&lt;br /&gt;
    setprop(&amp;quot;/sim/demo&amp;quot;, i, &amp;quot;Demo #&amp;quot; ~ i));&lt;br /&gt;
}&lt;br /&gt;
|example3text = Same as above, but is supported by all versions of FlightGear.&lt;br /&gt;
|example3 = for(var i = 0; i &amp;lt; 3; i += 1){&lt;br /&gt;
    setprop(&amp;quot;/sim/demo[&amp;quot; ~ i ~ &amp;quot;]&amp;quot;, &amp;quot;Demo #&amp;quot; ~ i));&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
====See also====&lt;br /&gt;
{{note| If you have to read/write the same property multiple times (e.g. in an update loop), it is more efficient to use a node object: &lt;br /&gt;
To get a Node rather than its value, use &amp;lt;code&amp;gt;props.globals.getNode()&amp;lt;/code&amp;gt; - see [[Nasal_library/props]]. }}&lt;br /&gt;
&lt;br /&gt;
===settimer() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = settimer(function, delta[, realtime]);&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=499|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=1286|t=Part 2}}&lt;br /&gt;
|text = Runs the given function a specified amount of seconds after the current time. Returns &amp;lt;code&amp;gt;'''nil'''&amp;lt;/code&amp;gt;.&lt;br /&gt;
{{note|Improper use of &amp;lt;code&amp;gt;settimer()&amp;lt;/code&amp;gt; may cause resource leaks. It is also highly recommended that the newer {{func link|maketimer()}} should be used instead of this function.}} &lt;br /&gt;
|param1 = function&lt;br /&gt;
|param1text = Mandatory function that will be called. It may be necessary to enclose code in an anonymous function (see example).&lt;br /&gt;
|param2 = delta&lt;br /&gt;
|param2text = Mandatory amount of time in seconds after which the function will be called.&lt;br /&gt;
|param3 = realtime&lt;br /&gt;
|param3text = If 1 (true), &amp;quot;real time&amp;quot; will be used instead of &amp;quot;simulation time.&amp;quot; Defaults to 0 (false). Note that if &amp;quot;simulation time&amp;quot; is used, the timer will not run while FlightGear is paused.&lt;br /&gt;
|example1 = var myFunc = func(){&lt;br /&gt;
    print(&amp;quot;Hello&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
settimer(myFunc, 2); # runs myFunc after 2 seconds&lt;br /&gt;
|example2 = var sqr = func(a){&lt;br /&gt;
    return a * a;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
settimer(func(){&lt;br /&gt;
    print(sqr(2)); # will print 4 after 2 seconds&lt;br /&gt;
}, 2);&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===srand() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = srand();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=559|t=Source}}&lt;br /&gt;
|text = Makes the pseudorandom number generator (see {{func link|rand()}}) generate a new {{wikipedia|random seed|noicon=1}} based on time. Returns 0.&lt;br /&gt;
}}&lt;br /&gt;
===systime()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = systime();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=770|t=Source}}&lt;br /&gt;
|text = Returns the {{wikipedia|Unix time}} (seconds since since 00:00:00 UTC, 1/1/1970) as a floating point number with high resolution. This function is useful for benchmarking purposes (see example 2).&lt;br /&gt;
{{note|1=High resolution timers under Windows can produce inaccurate or fixed sub-millisecond results.&amp;lt;ref&amp;gt;{{cite web|url=http://forum.flightgear.org/viewtopic.php?f=30&amp;amp;t=29259|title=Nasal: systime() ??!?|author=Necolatis|date=Apr 2nd, 2016}}&amp;lt;/ref&amp;gt; This is due to the underlying {{func link|GetSystemTimeAsFileTime()|link=https://msdn.microsoft.com/en-us/library/windows/desktop/ms724397(v=vs.85).aspx}} API call, which depends on hardware availability of suitable high resolution timers. See also [https://msdn.microsoft.com/en-us/library/windows/desktop/dn553408(v=vs.85).aspx Acquiring high-resolution time stamps]}}&lt;br /&gt;
|example1 = print(&amp;quot;Unix time: &amp;quot;, systime()); # prints Unix time&lt;br /&gt;
|example2 = var myFunc = func(){&lt;br /&gt;
    for(var i = 0; i &amp;lt;= 10; i += 1){&lt;br /&gt;
        print(&amp;quot;Interation #&amp;quot;, i);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
var t = systime(); # record time&lt;br /&gt;
myFunc(); # run function&lt;br /&gt;
var t2 = systime(); # record new time&lt;br /&gt;
print(&amp;quot;myFunc() took &amp;quot;, t2 - t, &amp;quot; seconds&amp;quot;); # print result&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===thisfunc()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = thisfunc();&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Returns the function from which this function is called. This allows a function to reliably and safely call itself from within a closure.&lt;br /&gt;
|example1 = var stringify_vec = func(input){&lt;br /&gt;
    if (typeof(input) == &amp;quot;scalar&amp;quot;){&lt;br /&gt;
        return sprintf(&amp;quot;%s&amp;quot;, input);&lt;br /&gt;
    } elsif (typeof(input) == &amp;quot;vector&amp;quot;) {&lt;br /&gt;
        if (size(input) == 0) return &amp;quot;[]&amp;quot;;&lt;br /&gt;
        var this = thisfunc();&lt;br /&gt;
        var buffer = &amp;quot;[&amp;quot;;&lt;br /&gt;
        for(var i = 0; i &amp;lt; size(input); i += 1){&lt;br /&gt;
            buffer ~= this(input[i]);&lt;br /&gt;
            if (i == size(input) - 1) {&lt;br /&gt;
                buffer ~= &amp;quot;]&amp;quot;;&lt;br /&gt;
            } else {&lt;br /&gt;
                buffer ~= &amp;quot;, &amp;quot;;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        return buffer;&lt;br /&gt;
    } else {&lt;br /&gt;
        die(&amp;quot;stringify_vec(): Error! Invalid input. Must be a vector or scalar&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var test_vec = [&amp;quot;a&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c&amp;quot;, 1, 2, 3];&lt;br /&gt;
debug.dump(stringify_vec(test_vec)); # prints &amp;quot;[a, b, c, 1, 2, 3]&amp;quot;&lt;br /&gt;
test_vec = [];&lt;br /&gt;
debug.dump(stringify_vec(test_vec)); # prints &amp;quot;[]&amp;quot;&lt;br /&gt;
test_vec = {};&lt;br /&gt;
debug.dump(stringify_vec(test_vec)); # will throw an error&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===tileIndex() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = tileIndex();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1720|t=Source}}&lt;br /&gt;
|text = Returns the index of the tile at the aircraft's current position as a string. This corresponds to the name of the STG file of the tile. For example, at [[KSFO]], this would be &amp;lt;code&amp;gt;942050&amp;lt;/code&amp;gt;, corresponding to &amp;lt;tt&amp;gt;''[[$FG_SCENERY]]/Terrain/w130n30/w123n3/942050.stg''&amp;lt;/tt&amp;gt;.&lt;br /&gt;
|example1 = print(tileIndex()); # print index&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== tilePath()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = tilePath();&lt;br /&gt;
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1712|t=Source}}&lt;br /&gt;
|text = Returns the base path of the tile at the aircraft's current position as a string. For example, at KSFO, this would be &amp;lt;code&amp;gt;w130n30/w123n3&amp;lt;/code&amp;gt;, corresponding to &amp;lt;tt&amp;gt;''[[$FG_SCENERY]]/Terrain/w130n30/w123n3''&amp;lt;/tt&amp;gt;.&lt;br /&gt;
|example1 = print(tilePath()); # print path&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== values()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = values(hash);&lt;br /&gt;
|source = {{fgdata file|Nasal/globals.nas|t=Source}}&lt;br /&gt;
|text = Returns a vector containing the values of the given hash.&lt;br /&gt;
|param1 = hash&lt;br /&gt;
|param1text = Mandatory hash to get the values of.&lt;br /&gt;
|example1 = var hash = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 1,&lt;br /&gt;
    &amp;quot;b&amp;quot;: 2,&lt;br /&gt;
    &amp;quot;c&amp;quot;: 3&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
foreach(var val; values(hash)){&lt;br /&gt;
    print(val);&lt;br /&gt;
}&lt;br /&gt;
|example2text = The below example does exactly the same thing as the above example, but does not use &amp;lt;code&amp;gt;values()&amp;lt;/code&amp;gt;:&lt;br /&gt;
|example2 = var hash = {&lt;br /&gt;
    &amp;quot;a&amp;quot;: 1,&lt;br /&gt;
    &amp;quot;b&amp;quot;: 2,&lt;br /&gt;
    &amp;quot;c&amp;quot;: 3&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
foreach(var key; keys(hash)){&lt;br /&gt;
    print(hash[key]);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==Extension functions new in FG 2020.1 ==&lt;br /&gt;
The following functions have been added to the nasal core library and will be released with FlightGear version 2020.1. &lt;br /&gt;
Before the release they are available in the development branch &amp;quot;next&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
===isfunc()===&lt;br /&gt;
Returns 1 if type or argument is a function, otherwise 0.&lt;br /&gt;
&lt;br /&gt;
===isghost()===&lt;br /&gt;
Returns 1 if type or argument is a ghost, otherwise 0.&lt;br /&gt;
&lt;br /&gt;
=== ishash()===&lt;br /&gt;
Returns 1 if type or argument is a hash, otherwise 0.&lt;br /&gt;
&lt;br /&gt;
===isint()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = isint(x);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}&lt;br /&gt;
|text = Returns 1 if argument has a numeric value and x == floor(x), e.g. integer.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===isnum()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = isnum(x);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}&lt;br /&gt;
|text = Returns 1 if typeof(x) is &amp;quot;scalar&amp;quot; and x has a numeric value otherwise 0. &lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===isscalar()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = isscalar(x);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}&lt;br /&gt;
|text = Returns 1 if type or argument is a scalar (string or numeric), otherwise (vector, hash, func, ...) it returns 0. This is useful to check if a variable can be converted to string e.g. when useing the string concat operator &amp;quot;~&amp;quot;. &lt;br /&gt;
|example1 = var a = &amp;quot;foo&amp;quot;; &lt;br /&gt;
var b=42;&lt;br /&gt;
if (isscalar(a) and isscalar(b)) print(a~b);&lt;br /&gt;
if (isstr(a)) print(&amp;quot;a is a string&amp;quot;);&lt;br /&gt;
if (isint(b)) print(&amp;quot;b is an integer&amp;quot;);&lt;br /&gt;
# if (isscalar(a))... is equivalent to if (typeof(a) == &amp;quot;scalar&amp;quot;)...&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
===isstr()===&lt;br /&gt;
Returns 1 if type or argument is a string, otherwise 0. &lt;br /&gt;
&lt;br /&gt;
===isvec()===&lt;br /&gt;
Returns 1 if type or argument is a vector, otherwise 0.&lt;br /&gt;
&lt;br /&gt;
===vecindex()===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = vecindex(vector, value);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}&lt;br /&gt;
|text = Returns the index of value or nil, if value is not found in vector.&lt;br /&gt;
|example1=&lt;br /&gt;
var myvector = [&amp;quot;apple&amp;quot;, &amp;quot;bananna&amp;quot;, &amp;quot;coconut&amp;quot;];&lt;br /&gt;
# to check if a vector contains a certain value compare vecindex to nil&lt;br /&gt;
if (vecindex(myvector, &amp;quot;apple&amp;quot;) != nil) {&lt;br /&gt;
    print(&amp;quot;found apple&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
# WARNING: this will not work as desired because index is 0&lt;br /&gt;
if (vecindex(myvector, &amp;quot;apple&amp;quot;)) {&lt;br /&gt;
    print(&amp;quot;found apple&amp;quot;);&lt;br /&gt;
&amp;lt;nowiki&amp;gt;}&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==Variables==&lt;br /&gt;
Various global constants (technically variables) are provided for use in converting between different units. They are all found in {{fgdata file|Nasal/globals.nas|text=$FG_ROOT/Nasal/globals.nas}}.&lt;br /&gt;
&lt;br /&gt;
===D2R===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var radians = degrees * D2R;&lt;br /&gt;
|text = Converts an angle from degrees to radians when multiplied by the angle in degrees. Equal to &amp;lt;code&amp;gt;π / 180&amp;lt;/code&amp;gt;.&lt;br /&gt;
}}&lt;br /&gt;
===FPS2KT===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var knots = feet_per_second * FPS2KT;&lt;br /&gt;
|text = Converts a velocity from feet per second to knots when multiplied by the velocity in feet per second. Approximately equal to 0.5925.&lt;br /&gt;
}}&lt;br /&gt;
===FT2M===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var metres = feet * FT2M;&lt;br /&gt;
|text = Converts a length from feet to metres when multiplied by the length in feet. Equal to 0.3048.&lt;br /&gt;
}}&lt;br /&gt;
===GAL2L ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var litres = gallons * GAL2L;&lt;br /&gt;
|text = Converts a volume from US liquid gallons to litres when multiplied by the volume in gallons. Approximately equal to 3.7854.&lt;br /&gt;
}}&lt;br /&gt;
===IN2M===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var metres = inches * IN2M;&lt;br /&gt;
|text = Converts a length from inches to metres when multiplied by the length in inches. Equal to 0.0254.&lt;br /&gt;
}}&lt;br /&gt;
===KG2LB===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var pounds = kilograms * KG2LB;&lt;br /&gt;
|text = Converts a mass from kilograms to pounds when multiplied by the mass in kilograms. Approximately equal to 2.2046.&lt;br /&gt;
}}&lt;br /&gt;
===KT2FPS===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var feet_per_second = knots * KT2FPS;&lt;br /&gt;
|text = Converts a velocity from knots to feet per second when multiplied by the velocity in knots. Approximately equal to 1.6878.&lt;br /&gt;
}}&lt;br /&gt;
===KT2MPS===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var metres_per_second = knots * KT2MPS;&lt;br /&gt;
|text = Converts a velocity from knots to metres per second when multiplied by the velocity in knots. Approximately equal to 0.5144.&lt;br /&gt;
}}&lt;br /&gt;
===L2GAL===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var gallons = litres * L2GAL;&lt;br /&gt;
|text = Converts a volume from litres to US liquid gallons when multiplied by the volume in litres. Approximately equal to 0.2642.&lt;br /&gt;
}}&lt;br /&gt;
===LB2KG===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var kilograms = pounds * LB2KG;&lt;br /&gt;
|text = Converts a mass from pounds to kilograms when multiplied by the mass in pounds. Approximately equal to 0.4536.&lt;br /&gt;
}}&lt;br /&gt;
===M2FT ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var feet = metres * M2FT;&lt;br /&gt;
|text = Converts a length from metres to feet when multiplied by the length in metres. Approximately equal to 3.2808.&lt;br /&gt;
}}&lt;br /&gt;
===M2IN===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var inches = metres * M2IN;&lt;br /&gt;
|text = Converts a length from metres to inches when multiplied by the length in metres. Approximately equal to 39.3701.&lt;br /&gt;
}}&lt;br /&gt;
===M2NM ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var nautical_miles = metres * M2NM;&lt;br /&gt;
|text = Converts a length from metres to nautical miles when multiplied by the length in metres. Approximately equal to 0.00054.&lt;br /&gt;
}}&lt;br /&gt;
===MPS2KT ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var knots = metres_per_second * MPS2KT;&lt;br /&gt;
|text = Converts a velocity from metres per second to knots when multiplied by the velocity in metres per second. Approximately equal to 1.9438.&lt;br /&gt;
}}&lt;br /&gt;
===NM2M ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var metres = nautical_miles * NM2M;&lt;br /&gt;
|text = Converts a length from nautical miles to metres when multiplied by the length in nautical miles. Equal to 1,852.&lt;br /&gt;
}}&lt;br /&gt;
===R2D===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = var degrees = radians * R2D;&lt;br /&gt;
|text = Converts an angle from radians to degrees when multiplied by the angle in radians. Equal to &amp;lt;code&amp;gt;180 / π&amp;lt;/code&amp;gt;.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Appendix}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{Nasal namespaces}}&lt;/div&gt;</summary>
		<author><name>TheEagle</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Canvas_snippets&amp;diff=136956</id>
		<title>Canvas snippets</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Canvas_snippets&amp;diff=136956"/>
		<updated>2023-01-02T21:33:20Z</updated>

		<summary type="html">&lt;p&gt;TheEagle: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Stub}}&lt;br /&gt;
{{-}}&lt;br /&gt;
This article is meant to be a collection of self-contained code snippets for doing Nasal/Canvas coding without having to spend too much time working through APIs, documentation and existing code - the idea is to provide snippets that contain comments and explicit assumptions, so that these snippets can be easily adapted by people without necessarily having to be very familiar with Nasal/Canvas coding. &lt;br /&gt;
&lt;br /&gt;
In the long term, we're hoping to grow a library of useful code snippets to cover most use-cases, while also covering more complex scenarios, e.g. using Canvas-specific helper frameworks like [[Canvas MapStructure]], the [[NavDisplay]] or the recent CDU work.&lt;br /&gt;
&lt;br /&gt;
Canvas itself is designed to be front-end agnostic, meaning that it doesn't matter where a Canvas is used/displayed - this is accomplished by so called &amp;quot;placements&amp;quot;, which determine where a Canvas texture is shown. Thus, the back-end logic can remain the same usually, so that GUI dialogs may show avionics, but also so that avionics may show GUI widgets, which also applies to HUDs, liveries and even placements within the FlightGear scenery.&lt;br /&gt;
&lt;br /&gt;
We encourage people to use the snippets listed below to get started with Nasal/Canvas coding, but also to provide feedback on extending/improving this article to make it even more accessible and useful. Equally, if you're aware of any Canvas-related efforts that may contain useful code snippets, please do feel free to add those snippets here. Even people who don't have any interest in coding itself are encouraged to get involved by helping test the snippets listed below, for which you only need to the [[Nasal Console]], and while you're at it, please also help provide/update screen shots for each code snippet.&lt;br /&gt;
&lt;br /&gt;
Contributions added to this article should ideally satisfy some requirements:&lt;br /&gt;
* be entirely self-contained&lt;br /&gt;
* contain good/clear comments&lt;br /&gt;
* have pointers/references to related code/use-cases&lt;br /&gt;
* make assumptions explicit&lt;br /&gt;
* contain screen shots for each example&lt;br /&gt;
* avoid overloaded symbol names to ensure that examples can be merged and adapted easily&lt;br /&gt;
* dependencies (svg files, images/textures etc) should always be chosen such that snippets always work using $FG_ROOT&lt;br /&gt;
* hard-coded assumptions (e.g. texture dimensions in terms of width/height etc) should be encapsulated using variables&lt;br /&gt;
 &lt;br /&gt;
{{Canvas Navigation}}&lt;br /&gt;
&lt;br /&gt;
== Creating a standalone Canvas ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[Special:UploadWizard|Upload requested]] || &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
var (width, height) = (512,512);&lt;br /&gt;
# Create a standalone Canvas (not attached to any GUI dialog/aircraft etc) &lt;br /&gt;
var myCanvas = canvas.new({&lt;br /&gt;
  &amp;quot;name&amp;quot;: &amp;quot;Livery Test&amp;quot;,   # The name is optional but allow for easier identification&lt;br /&gt;
  &amp;quot;size&amp;quot;: [width, height], # Size of the underlying texture (should be a power of 2, required) [Resolution]&lt;br /&gt;
  &amp;quot;view&amp;quot;: [width, height],  # Virtual resolution (Defines the coordinate system of the canvas [Dimensions]&lt;br /&gt;
                        # which will be stretched the size of the texture, required)&lt;br /&gt;
  &amp;quot;mipmapping&amp;quot;: 1       # Enable mipmapping (optional)&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
# set background color&lt;br /&gt;
myCanvas.set(&amp;quot;background&amp;quot;, canvas.style.getColor(&amp;quot;bg_color&amp;quot;));&lt;br /&gt;
&lt;br /&gt;
# creating the top-level/root group which will contain all other elements/group&lt;br /&gt;
var root = myCanvas.createGroup();&lt;br /&gt;
&lt;br /&gt;
# OPTIONAL: Create a Canvas dialog window to hold the canvas and show that it's working&lt;br /&gt;
# the Canvas is now standalone, i.e. continues to live once the dialog is closed!&lt;br /&gt;
var window = canvas.Window.new([width,height],&amp;quot;dialog&amp;quot;);&lt;br /&gt;
window.setCanvas(myCanvas);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Accessing the Canvas Desktop ==&lt;br /&gt;
Sometimes you may want to render to the main screen, without creating a separate Canvas window - this can be accomplished by using the Canvas desktop, note that the following example is fully self-contained, i.e. does not require any code to be added to work:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[Special:UploadWizard|Upload requested]] || &lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
var myNode = canvas.getDesktop().createChild(&amp;quot;text&amp;quot;)&lt;br /&gt;
      .setText(&amp;quot;Hello Canvas Desktop&amp;quot;)&lt;br /&gt;
      .setFontSize(25, 1.0)          # font size (in texels) and font aspect ratio&lt;br /&gt;
      .setColor(1,0,0,1)             # red, fully opaque&lt;br /&gt;
      .setAlignment(&amp;quot;center-center&amp;quot;) # how the text is aligned to where you place it&lt;br /&gt;
      .setTranslation(160, 80);     # where to place the text&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Creating Tooltips ==&lt;br /&gt;
== Creating Popups ==&lt;br /&gt;
== Creating a Canvas GUI Window ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Snippets-canvas-dialog.png|left|thumb|This is what the [[Nasal]]/[[Canvas]] snippet will look like once you pasted it into the [[Nasal Console]] and click &amp;quot;Execute&amp;quot;.]] || {{Note|This example uses so called method chaining, if you're not familiar with the concept, please see: [[Object_Oriented_Programming_with_Nasal#More_on_methods:_Chaining|Method Chaining]].}}&lt;br /&gt;
&lt;br /&gt;
{{Canvas Snippets Boilerplate}}&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Adding Raster Images ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code &lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas Snippets-raster image.png|thumb|Canvas snippet demonstrating how to load a raster image]] ||&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# path is relative to $FG_ROOT (base package)&lt;br /&gt;
var path = &amp;quot;Textures/Splash1.png&amp;quot;;&lt;br /&gt;
# create an image child for the texture&lt;br /&gt;
var child = root.createChild(&amp;quot;image&amp;quot;)&lt;br /&gt;
    .setFile(path)&lt;br /&gt;
    .setTranslation(100, 10)&lt;br /&gt;
    .setSize(130, 130);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Adding Raster Images downloaded on demand ==&lt;br /&gt;
&lt;br /&gt;
The '''path''' could also just as well be a URL, i.e. a raster image retrieved via http - for example, the following snippet is entirely self-contained and can be pasted into the [[Nasal Console]] and directly executed &amp;quot;as is&amp;quot;: &lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-raster-images-via-url.png|thumb|screen shot demonstrating how Nasal and Canvas can be used to display raster images downloaded on demand]] ||&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# create a new window, dimensions are 400 x 200, using the dialog decoration (i.e. titlebar)&lt;br /&gt;
var window = canvas.Window.new([400,200],&amp;quot;dialog&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
# adding a canvas to the new window and setting up background colors/transparency&lt;br /&gt;
var myCanvas = window.createCanvas().set(&amp;quot;background&amp;quot;, canvas.style.getColor(&amp;quot;bg_color&amp;quot;));&lt;br /&gt;
&lt;br /&gt;
# Using specific css colors would also be possible:&lt;br /&gt;
# myCanvas.set(&amp;quot;background&amp;quot;, &amp;quot;#ffaac0&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
# creating the top-level/root group which will contain all other elements/group&lt;br /&gt;
var root = myCanvas.createGroup();&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# path now a URL&lt;br /&gt;
var url = &amp;quot;http://www.worldwidetelescope.org/docs/Images/MapOfEarth.jpg&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
# create an image child for the texture&lt;br /&gt;
var child=root.createChild(&amp;quot;image&amp;quot;)&lt;br /&gt;
    .setFile( url ) &lt;br /&gt;
    .setTranslation(45,22) # centered, in relation to dialog coordinates&lt;br /&gt;
    .setSize(310,155); # image dimensions&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Clipping ==&lt;br /&gt;
{{Note|{{FGCquote&lt;br /&gt;
|1= Scaling or any other type of transformation or changing the coordinates of individual points is definitely more efficient than clipping (which requires to change the OpenGL clip planes for every rendered object with a different clipping rectangle).&lt;br /&gt;
|2= {{cite web&lt;br /&gt;
  | url    = http://forum.flightgear.org/viewtopic.php?p=277062#p277062&lt;br /&gt;
  | title  = &amp;lt;nowiki&amp;gt;Re: Space Shuttle&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
  | author = &amp;lt;nowiki&amp;gt;TheTom&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
  | date   = Feb 21st, 2016&lt;br /&gt;
  | added   = Feb 21st, 2016&lt;br /&gt;
  | script_version = 0.25&lt;br /&gt;
  }}&lt;br /&gt;
}}&lt;br /&gt;
}}&lt;br /&gt;
I would suggest to refer to api.nas and look for &amp;quot;clip&amp;quot; and/or &amp;quot;rect&amp;quot; - IIRC, you need to set up a clipping rectangle by setting some kind of &amp;quot;clip&amp;quot; property and setting it to a rect value in the form of rect(...)&lt;br /&gt;
{{FGCquote&lt;br /&gt;
|1= For details, see the clipping example at: [[Canvas Nasal API#set 2]] and  [[Canvas Element#clip.28.29]]&lt;br /&gt;
|2= {{cite web&lt;br /&gt;
  | url    = http://forum.flightgear.org/viewtopic.php?p=276965#p276965&lt;br /&gt;
  | title  = &amp;lt;nowiki&amp;gt;Re: Space Shuttle&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
  | author = &amp;lt;nowiki&amp;gt;Hooray&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
  | date   = Feb 21st, 2016&lt;br /&gt;
  | added   = Feb 21st, 2016&lt;br /&gt;
  | script_version = 0.25&lt;br /&gt;
  }}&lt;br /&gt;
}}&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:MapStructure_dialog_with_clipping_and_event_handling_applied.png|thumb]] ||&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# create a new window, dimensions are 400 x 200, using the dialog decoration (i.e. titlebar)&lt;br /&gt;
var window = canvas.Window.new([400,200],&amp;quot;dialog&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
# adding a canvas to the new window and setting up background colors/transparency&lt;br /&gt;
var myCanvas = window.createCanvas().set(&amp;quot;background&amp;quot;, canvas.style.getColor(&amp;quot;bg_color&amp;quot;));&lt;br /&gt;
&lt;br /&gt;
# Using specific css colors would also be possible:&lt;br /&gt;
# myCanvas.set(&amp;quot;background&amp;quot;, &amp;quot;#ffaac0&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
# creating the top-level/root group which will contain all other elements/group&lt;br /&gt;
var root = myCanvas.createGroup();&lt;br /&gt;
&lt;br /&gt;
# create an image child for the texture&lt;br /&gt;
var child=root.createChild(&amp;quot;.........&amp;quot;)&lt;br /&gt;
    .setFile( url ) &lt;br /&gt;
    .setTranslation(45,22) # centered, in relation to dialog coordinates&lt;br /&gt;
    .setSize(310,155); # image dimensions&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Adding OpenVG Paths ==&lt;br /&gt;
{{Main article|How to manipulate Canvas elements}}&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Openvg-via-canvas.png|thumb|screen shot showing a simple Canvas GUI dialog demonstrating how to use OpenVG-path drawing via Nasal and Canvas]] &lt;br /&gt;
| {{Note|This assumes that you already have a top-level root group set up, and named it '''root'''.}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
var graph = root.createChild(&amp;quot;group&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
var x_axis = graph.createChild(&amp;quot;path&amp;quot;, &amp;quot;x-axis&amp;quot;)&lt;br /&gt;
.moveTo(10, height/2)&lt;br /&gt;
.lineTo(width-10, height/2)&lt;br /&gt;
.setColor(1,0,0)&lt;br /&gt;
.setStrokeLineWidth(3);&lt;br /&gt;
&lt;br /&gt;
var y_axis = graph.createChild(&amp;quot;path&amp;quot;, &amp;quot;y-axis&amp;quot;)&lt;br /&gt;
.moveTo(10, 10)&lt;br /&gt;
.lineTo(10, height-10)&lt;br /&gt;
.setColor(0,0,1)&lt;br /&gt;
.setStrokeLineWidth(2);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-openvg-quadTo.png|thumb|screen shot showing a simple Canvas GUI dialog demonstrating how to use OpenVG-path drawing via Nasal and Canvas (Canvas/OpenVG quadTo API for drawing curves)]] &lt;br /&gt;
| {{Note|This assumes that you are appending this to the snippet shown above.}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
var points = [	60,height-20, &lt;br /&gt;
		230,height-100,&lt;br /&gt;
		];&lt;br /&gt;
&lt;br /&gt;
var track = graph.createChild(&amp;quot;path&amp;quot;, &amp;quot;track&amp;quot;)&lt;br /&gt;
.moveTo(10, height/2)&lt;br /&gt;
.quadTo( points )&lt;br /&gt;
.setColor(0,1,0)&lt;br /&gt;
.setStrokeLineWidth(4);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-openvg-cubicTo.png|thumb|screen shot showing a simple Canvas GUI dialog demonstrating how to use OpenVG-path drawing via Nasal and Canvas (Canvas/OpenVG cubicTo API for drawing curves)]] &lt;br /&gt;
| {{Note|This assumes that you are appending this to the snippet shown above.}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
var points = [	60,height-20, &lt;br /&gt;
		120,height-120,&lt;br /&gt;
		230,height-100,&lt;br /&gt;
		];&lt;br /&gt;
&lt;br /&gt;
var track = graph.createChild(&amp;quot;path&amp;quot;, &amp;quot;track&amp;quot;)&lt;br /&gt;
.moveTo(10, height/2)&lt;br /&gt;
.cubicTo( points )&lt;br /&gt;
.setColor(0,1,0)&lt;br /&gt;
.setStrokeLineWidth(4);&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Adding Vector Images ==&lt;br /&gt;
&lt;br /&gt;
[[Category:Canvas SVG]]&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-svg-support.png|thumb|screen shot demonstrating how the scripted Nasal-based SVG parser can be used to dynamically turn SVG files into OpenVG instructions understood by Canvas]] &lt;br /&gt;
| {{Note|This assumes that you already have a top-level root group set up, and named it '''root'''.}}&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
# change the background color &lt;br /&gt;
myCanvas.set(&amp;quot;background&amp;quot;, &amp;quot;#ffaac0&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
# creating the top-level/root group which will contain all other elements/group&lt;br /&gt;
var root = myCanvas.createGroup();&lt;br /&gt;
&lt;br /&gt;
var filename = &amp;quot;/Nasal/canvas/map/Images/boeingAirplane.svg&amp;quot;;&lt;br /&gt;
var svg_symbol = root.createChild('group');&lt;br /&gt;
canvas.parsesvg(svg_symbol, filename);&lt;br /&gt;
&lt;br /&gt;
svg_symbol.setTranslation(width/2,height/2);&lt;br /&gt;
&lt;br /&gt;
#svg_symbol.setScale(0.2);&lt;br /&gt;
#svg_symbol.setRotation(radians)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Using the SymbolCache ==&lt;br /&gt;
Whenever a symbol may need to be shown/instanced multiple times (possibly using different styling), it makes sense to use caching - otherwise, identical symbols would be treated as separate OpenVG groups, all of which would need to be rasterized/rendedered separately (i.e. 100 identical symbols would be updated/rendered one by one). &lt;br /&gt;
&lt;br /&gt;
Typically, a map may display multiple instances of an otherwise identical symbol (think VOR, NDB, DME etc) - equally, a multiplayer map may showing multiple aircraft symbols at the same time. In these cases, it makes sense to use the SymbolCache framework, which will render symbols into a separate Canvas texture and provide a texture map that can be treated as a lookup map, which even supports styling for otherwise identical symbols. To learn more, please refer to [[Canvas MapStructure#The_SymbolCache|SymbolCache]]&lt;br /&gt;
... &lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-SymbolCache-Instancing.png|thumb|Screen shot showing a Canvas based GUI dialog that is using the SymbolCache for instancing multiple symbols (including support for styling)]] &lt;br /&gt;
| &amp;lt;!--{{Caution|This is currently untested code}}--&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
##&lt;br /&gt;
# this is the function that draws a symbol using OpenVG paths&lt;br /&gt;
# it accepts a group to draw to and returns the rendered group &lt;br /&gt;
# to the caller&lt;br /&gt;
var drawVOR = func(group) {&lt;br /&gt;
    return group.createChild(&amp;quot;path&amp;quot;)&lt;br /&gt;
        .moveTo(-15,0)&lt;br /&gt;
        .lineTo(-7.5,12.5)&lt;br /&gt;
        .lineTo(7.5,12.5)&lt;br /&gt;
        .lineTo(15,0)&lt;br /&gt;
        .lineTo(7.5,-12.5)&lt;br /&gt;
        .lineTo(-7.5,-12.5)&lt;br /&gt;
        .close()&lt;br /&gt;
        .setStrokeLineWidth(line_width) # style-able&lt;br /&gt;
        .setColor(color); # style-able&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
var style = { # styling related attributes (as per the draw* function above)&lt;br /&gt;
    line_width: 3,&lt;br /&gt;
    scale_factor: 1,&lt;br /&gt;
    color: [1,0,0],&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
# create a new cache entry for the styled symbol&lt;br /&gt;
var myCachedSymbol = canvas.StyleableCacheable.new(&lt;br /&gt;
    name:'myVOR', draw_func: drawVOR,&lt;br /&gt;
    cache: canvas.SymbolCache32x32, # the cache to be used&lt;br /&gt;
    draw_mode: canvas.SymbolCache.DRAW_CENTERED,&lt;br /&gt;
    relevant_keys: ['line_width', 'color'], # styling related attributes&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
var target = root.createChild('group');&lt;br /&gt;
&lt;br /&gt;
var x=0;&lt;br /&gt;
var y=height/2;&lt;br /&gt;
&lt;br /&gt;
var xoffset=50;&lt;br /&gt;
&lt;br /&gt;
# render 5 instanced symbols using the style specified above&lt;br /&gt;
for (var i=0;i&amp;lt;5;i+=1) {&lt;br /&gt;
# look up the raster image for the symbol&lt;br /&gt;
# render it using the passed style and adjust scaling&lt;br /&gt;
var instanced = myCachedSymbol.render(target, style)&lt;br /&gt;
  .setScale(style.scale_factor)&lt;br /&gt;
  .setTranslation(x+=xoffset,y);&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The example shown above uses a fixed symbol/icon cache that is set up while booting Flightgear - sometimes, we may need cache for different purposes. So, let's assume, we need a new/custom cache with a different resolution for each entry in the cache (e.g. 256x256), we can easily accomplish that by setting up a new cache like this: &lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
var mySymbolCache256x256 = canvas.SymbolCache.new(1024,256);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Adding Text Elements ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:CanvasText-Hello-World.png|thumb|Screen shot showing the CanvasText example contributed by Necolatis]]&lt;br /&gt;
| {{Note|This assumes that you already have a top-level root group set up, and named it '''root'''.}}&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
var myText = root.createChild(&amp;quot;text&amp;quot;)&lt;br /&gt;
      .setText(&amp;quot;Hello world!&amp;quot;)&lt;br /&gt;
      .setFontSize(20, 0.9)          # font size (in texels) and font aspect ratio&lt;br /&gt;
      .setColor(1,0,0,1)             # red, fully opaque&lt;br /&gt;
      .setAlignment(&amp;quot;center-center&amp;quot;) # how the text is aligned to where you place it&lt;br /&gt;
      .setTranslation(160, 80);     # where to place the text&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Adding GUI Labels ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-Layout-Label-example-by-Necolatis.png|thumb|Canvas demo: Layouts and Labels (by Necolatis)]]&lt;br /&gt;
| {{Note|This assumes that you already have a top-level root group set up, and named it '''root''', just change this variable if you want it to be rendered elsewhere.}}&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
# create a new layout&lt;br /&gt;
var myLayout = canvas.HBoxLayout.new();&lt;br /&gt;
# assign it to the Canvas&lt;br /&gt;
myCanvas.setLayout(myLayout);&lt;br /&gt;
&lt;br /&gt;
var label = canvas.gui.widgets.Label.new(root, canvas.style, {wordWrap: 0}); # wordwrap: 0 will disable wordwrapping, to enable it use 1 instead&lt;br /&gt;
label.setText(&amp;quot;Hello World!&amp;quot;);&lt;br /&gt;
myLayout.addItem(label);&lt;br /&gt;
&lt;br /&gt;
var label2 = canvas.gui.widgets.Label.new(root, canvas.style, {wordWrap: 0}); # wordwrap: 0 will disable wordwrapping, to enable it use 1 instead&lt;br /&gt;
label2.setText(&amp;quot;Hello FlightGear&amp;quot;);&lt;br /&gt;
myLayout.addItem(label2);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Adding GUI Buttons (Layouts)==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-demo-layouts-and-buttons-by-Necolatis.png|thumb|Canvas snippet: buttons and layouts (by Necolatis)]] &lt;br /&gt;
|&lt;br /&gt;
&lt;br /&gt;
{{Note|This assumes that you already have a top-level root group set up, and named it '''root''', just change this variable if you want it to be rendered elsewhere.}}&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
# create a new layout&lt;br /&gt;
var myLayout = canvas.HBoxLayout.new();&lt;br /&gt;
# assign it to the Canvas&lt;br /&gt;
myCanvas.setLayout(myLayout);&lt;br /&gt;
&lt;br /&gt;
# click button&lt;br /&gt;
&lt;br /&gt;
var button = canvas.gui.widgets.Button.new(root, canvas.style, {})&lt;br /&gt;
	.setText(&amp;quot;Click on me&amp;quot;)&lt;br /&gt;
	.setFixedSize(75, 25);&lt;br /&gt;
&lt;br /&gt;
button.listen(&amp;quot;clicked&amp;quot;, func {&lt;br /&gt;
        # add code here to react on click on button.&lt;br /&gt;
print(&amp;quot;Button clicked !&amp;quot;);&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
myLayout.addItem(button);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-toggle-button-snippet-by-Necolatis.png|thumb|Canvas toggle button demo by Necolatis]]&lt;br /&gt;
| &amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
# create a new layout&lt;br /&gt;
var myLayout = canvas.HBoxLayout.new();&lt;br /&gt;
# assign it to the Canvas&lt;br /&gt;
myCanvas.setLayout(myLayout);&lt;br /&gt;
&lt;br /&gt;
var button = canvas.gui.widgets.Button.new(root, canvas.style, {})&lt;br /&gt;
        .setText(&amp;quot;Toggle me&amp;quot;)&lt;br /&gt;
        .setCheckable(1) # this indicates that is should be a toggle button&lt;br /&gt;
        .setChecked(0) # depressed by default&lt;br /&gt;
        .setFixedSize(75, 25);&lt;br /&gt;
&lt;br /&gt;
button.listen(&amp;quot;toggled&amp;quot;, func (e) {&lt;br /&gt;
        if( e.detail.checked ) {&lt;br /&gt;
            # add code here to react on button being depressed.&lt;br /&gt;
        } else {&lt;br /&gt;
            # add code here to react on button not being depressed.&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
myLayout.addItem(button);&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Canvas Input Dialog ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
|[[File:Snippets-canvas-input-dialog.png|thumb|Canvas input dialog]]&lt;br /&gt;
||&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
# create a new InputDialog with a title, label, and a callback&lt;br /&gt;
canvas.InputDialog.getText(&amp;quot;Input Dialog Title&amp;quot;, &amp;quot;Please enter some text&amp;quot;, func(btn,value) {&lt;br /&gt;
    if (value) gui.popupTip(&amp;quot;You entered: &amp;quot;~value);&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Canvas ScrollArea ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
|[[File:Canvas-snippets-scrollArea-demo.png|thumb|Screen shot showing a Canvas ScrollArea populated with different splash screens, loaded from $FG_ROOT/Textures]]&lt;br /&gt;
||&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
var vbox = canvas.VBoxLayout.new();&lt;br /&gt;
myCanvas.setLayout(vbox);&lt;br /&gt;
&lt;br /&gt;
 var scroll = canvas.gui.widgets.ScrollArea.new(root, canvas.style, {size: [96, 128]}).move(20, 100);&lt;br /&gt;
 vbox.addItem(scroll, 1);&lt;br /&gt;
&lt;br /&gt;
var scrollContent =&lt;br /&gt;
      scroll.getContent()&lt;br /&gt;
            .set(&amp;quot;font&amp;quot;, &amp;quot;LiberationFonts/LiberationSans-Bold.ttf&amp;quot;)&lt;br /&gt;
            .set(&amp;quot;character-size&amp;quot;, 16)&lt;br /&gt;
            .set(&amp;quot;alignment&amp;quot;, &amp;quot;left-center&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
var list = canvas.VBoxLayout.new();&lt;br /&gt;
scroll.setLayout(list);&lt;br /&gt;
&lt;br /&gt;
for (var i=1;i&amp;lt;=5;i+=1) {&lt;br /&gt;
var label = canvas.gui.widgets.Label.new(scrollContent, canvas.style, {wordWrap: 0}); &lt;br /&gt;
label.setImage(&amp;quot;Textures/Splash&amp;quot;~i~&amp;quot;.png&amp;quot;);&lt;br /&gt;
label.setFixedSize(256,256);&lt;br /&gt;
list.addItem(label);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Using TabWidgets ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+&lt;br /&gt;
!Screenshot&lt;br /&gt;
!Code&lt;br /&gt;
|-&lt;br /&gt;
|[[File:Canvas TabWidget example.png|alt=Canvas TabWIdget example|thumb|Canvas TabWidget example showing the three default splash screens]]&lt;br /&gt;
|&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
var window = canvas.Window.new([300, 300], &amp;quot;dialog&amp;quot;);&lt;br /&gt;
var myCanvas = window.createCanvas().set(&amp;quot;background&amp;quot;, canvas.style.getColor(&amp;quot;bg_color&amp;quot;));&lt;br /&gt;
var root = myCanvas.createGroup();&lt;br /&gt;
var vbox = canvas.VBoxLayout.new();&lt;br /&gt;
myCanvas.setLayout(vbox);&lt;br /&gt;
 &lt;br /&gt;
var tabs = canvas.gui.widgets.TabWidget.new(root, canvas.style, {});&lt;br /&gt;
var tabsContent = tabs.getContent();&lt;br /&gt;
vbox.addItem(tabs);&lt;br /&gt;
&lt;br /&gt;
var tab1 = canvas.VBoxLayout.new();&lt;br /&gt;
var image1 = canvas.gui.widgets.Label.new(tabsContent, canvas.style, {})&lt;br /&gt;
				.setImage(&amp;quot;Textures/Splash1.png&amp;quot;)&lt;br /&gt;
				.setFixedSize(128, 128);&lt;br /&gt;
tab1.addItem(image1);&lt;br /&gt;
var text1 = canvas.gui.widgets.Label.new(tabsContent, canvas.style, {})&lt;br /&gt;
				.setText(&amp;quot;Texture 1&amp;quot;);&lt;br /&gt;
tab1.addItem(text1);&lt;br /&gt;
tabs.addTab(&amp;quot;tab1&amp;quot;, &amp;quot;Texture 1&amp;quot;, tab1);&lt;br /&gt;
&lt;br /&gt;
var tab2 = canvas.VBoxLayout.new();&lt;br /&gt;
var image2 = canvas.gui.widgets.Label.new(tabsContent, canvas.style, {})&lt;br /&gt;
				.setImage(&amp;quot;Textures/Splash2.png&amp;quot;)&lt;br /&gt;
				.setFixedSize(128, 128);&lt;br /&gt;
tab2.addItem(image2);&lt;br /&gt;
var text2 = canvas.gui.widgets.Label.new(tabsContent, canvas.style, {})&lt;br /&gt;
				.setText(&amp;quot;Texture 2&amp;quot;);&lt;br /&gt;
tab2.addItem(text2);&lt;br /&gt;
tabs.addTab(&amp;quot;tab2&amp;quot;, &amp;quot;Texture 2&amp;quot;, tab2);&lt;br /&gt;
&lt;br /&gt;
var tab3 = canvas.VBoxLayout.new();&lt;br /&gt;
var image3 = canvas.gui.widgets.Label.new(tabsContent, canvas.style, {})&lt;br /&gt;
				.setImage(&amp;quot;Textures/Splash3.png&amp;quot;)&lt;br /&gt;
				.setFixedSize(128, 128);&lt;br /&gt;
tab3.addItem(image3);&lt;br /&gt;
var text3 = canvas.gui.widgets.Label.new(tabsContent, canvas.style, {})&lt;br /&gt;
				.setText(&amp;quot;Texture 3&amp;quot;);&lt;br /&gt;
tab3.addItem(text3);&lt;br /&gt;
tabs.addTab(&amp;quot;tab3&amp;quot;, &amp;quot;Texture 3&amp;quot;, tab3);&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Using Layouts ==&lt;br /&gt;
&lt;br /&gt;
== Using Styling ==&lt;br /&gt;
== Adding a HUD ==&lt;br /&gt;
== Adding a 2D Instrument ==&lt;br /&gt;
== Adding a 2D Panel ==&lt;br /&gt;
== Adding a PFD ==&lt;br /&gt;
&lt;br /&gt;
== Adding a Failure Mgmt Widget ==&lt;br /&gt;
{{Note|This kind of widget will typically be useful for dialogs requiring a method for managing aircraft specific system failures (e.g. an instructor console).}}&lt;br /&gt;
&lt;br /&gt;
== Adding a MapStructure map to a Canvas  ==&lt;br /&gt;
{{Main article|Canvas MapStructure Layers}}&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code &lt;br /&gt;
|-&lt;br /&gt;
| [[File:Snippets-canvas-mapstructure-dialog.png|thumb|[[MapStructure]] layers shown in a Canvas GUI dialog]]&lt;br /&gt;
| {{Note|This assumes that you already have a top-level root group set up, and named it '''root''', just change this variable if you want it to be rendered elsewhere. Equally, this snippet assumes that your canvas is named '''myCanvas'''.}}&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
var TestMap = root.createChild(&amp;quot;map&amp;quot;);&lt;br /&gt;
TestMap.setController(&amp;quot;Aircraft position&amp;quot;);&lt;br /&gt;
TestMap.setRange(25); &lt;br /&gt;
 &lt;br /&gt;
TestMap.setTranslation(    myCanvas.get(&amp;quot;view[0]&amp;quot;)/2,&lt;br /&gt;
                           myCanvas.get(&amp;quot;view[1]&amp;quot;)/2&lt;br /&gt;
                        );&lt;br /&gt;
var r = func(name,vis=1,zindex=nil) return caller(0)[0];&lt;br /&gt;
&lt;br /&gt;
# APT and VOR are the layer names&lt;br /&gt;
foreach(var type; [r('APT'), r('VOR') ] )&lt;br /&gt;
 TestMap.addLayer(factory: canvas.SymbolLayer, type_arg: type.name, visible: type.vis, priority: type.zindex,);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Customizing MapStructure Styling  ==&lt;br /&gt;
{{See also|Canvas MapStructure#Styling}}&lt;br /&gt;
&lt;br /&gt;
In general MapStructure symbols contain their own styles using hard-coded defaults, however these can be overridden by providing a hash with keys (fields) to customize these hard-coded defaults. &lt;br /&gt;
&lt;br /&gt;
This means that anything that may be specific to a single style (colors, fonts, images etc) should be encoded in the form of variables that are looked up using the styles hash - this provides a great deal of freedom to customize an existing symbol. In addition, MapStructure layers can be set up to even customize/override the default drawing routines, at which point you are free to do whatever you want basically, because the existing draw routine in .symbol file is ignored &lt;br /&gt;
&lt;br /&gt;
{{WIP}}&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code &lt;br /&gt;
|-&lt;br /&gt;
| [[File:Layers style change.png|thumb|[[MapStructure]] layers shown in a Canvas GUI dialog. Active VOR and radial in red color, inactive VOR in green color]]&lt;br /&gt;
| {{Note|This assumes that you already have a top-level root group set up, and named it '''root''', just change this variable if you want it to be rendered elsewhere. Equally, this snippet assumes that your canvas is named '''myCanvas'''.}}&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
var TestMap = root.createChild(&amp;quot;map&amp;quot;);&lt;br /&gt;
TestMap.setController(&amp;quot;Aircraft position&amp;quot;);&lt;br /&gt;
TestMap.setRange(25); &lt;br /&gt;
 &lt;br /&gt;
TestMap.setTranslation(    myCanvas.get(&amp;quot;view[0]&amp;quot;)/2,&lt;br /&gt;
                           myCanvas.get(&amp;quot;view[1]&amp;quot;)/2&lt;br /&gt;
                        );&lt;br /&gt;
var r = func(name,vis=1,zindex=nil) return caller(0)[0];&lt;br /&gt;
var type = r('APT');&lt;br /&gt;
# a hash that contains variables that are supported by the relevant_keys vector in APT.symbol&lt;br /&gt;
var style_apt = {&lt;br /&gt;
    scale_factor:0.5, # 50 %&lt;br /&gt;
    color_default:[0,1,0.9], # rgb&lt;br /&gt;
    line_width:4, # thickness&lt;br /&gt;
    label_font_color:[0,1,0.9], # rgb&lt;br /&gt;
    label_font_size:30 # font size&lt;br /&gt;
};&lt;br /&gt;
TestMap.addLayer(factory: canvas.SymbolLayer, type_arg: type.name, visible: type.vis, priority: type.zindex,style:style_apt);&lt;br /&gt;
&lt;br /&gt;
var type = r('VOR');&lt;br /&gt;
# a hash that contains variables that are supported by the relevant_keys vector in VOR.symbol&lt;br /&gt;
var style_vor = {&lt;br /&gt;
    scale_factor:0.6,&lt;br /&gt;
    active_color:[1,0,0],&lt;br /&gt;
    inactive_color:[0,1,0],&lt;br /&gt;
    line_width:4&lt;br /&gt;
};&lt;br /&gt;
TestMap.addLayer(factory: canvas.SymbolLayer, type_arg: type.name, visible: type.vis, priority: type.zindex,style:style_vor);&lt;br /&gt;
&lt;br /&gt;
### See $FG_ROOT/Nasal/canvas/map/APT and $FG_ROOT/Nasal/canvas/map/VOR.sy,bolfor the style variables that can be configured.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Using MapStructure and Overlays ==&lt;br /&gt;
{{FGCquote&lt;br /&gt;
  |Support for overlays using geo-referenced raster images isn't too well-developed currently, so would need to work with a few hard-coded assumptions (e.g. being specific to a certain raster image and map layout) - but maybe TheTom can provide a few more ideas on how to proceed from here - otherwise, the Map is normally not aware of any non-Map items, i.e. as long as the overlay is added &amp;quot;outside&amp;quot; the Map, it isn't even aware of the raster image - and when it is added as a map element, it would probably be rotated. So there's still some work needed here. But generally, this should work well enough even in its current form (the screen shot is purely based on the Canvas Snippets article, i.e. it just displays a Canvas GUI dialog, adds the downloaded raster image and then adds the MapStructure APS layer - without the Map being aware of the overlay/geo-referencing that is needed currently).&lt;br /&gt;
  |{{cite web |url=http://forum.flightgear.org/viewtopic.php?p=238316#p238316&lt;br /&gt;
     |title=&amp;lt;nowiki&amp;gt;Re: Using Canvas for visualizing orbital flights (cont'd PM)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |author=&amp;lt;nowiki&amp;gt;Hooray&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |date=&amp;lt;nowiki&amp;gt;Fri Apr 10&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   }}&lt;br /&gt;
}}&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code &lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-overlay-with-mapstructure.png|thumb|Using the [[MapStructure]] framework in conjunction with raster images as overlays]]&lt;br /&gt;
| {{Note|This assumes that you already have a top-level root group set up, and named it '''root''', just change this variable if you want it to be rendered elsewhere. Equally, this snippet assumes that your canvas is named '''myCanvas'''.}}&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== A simple tile map ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code &lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas - Tile map demo.png|thumb|A simple, canvas based tile map which is centered around the aircraft.]]&lt;br /&gt;
| &lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
var (width,height) = (768,512);&lt;br /&gt;
var tile_size = 256;&lt;br /&gt;
&lt;br /&gt;
var window = canvas.Window.new([width, height],&amp;quot;dialog&amp;quot;).set('title', &amp;quot;Tile map demo&amp;quot;);&lt;br /&gt;
var g = window.getCanvas(1).createGroup();&lt;br /&gt;
&lt;br /&gt;
# Simple user interface (Buttons for zoom and label for displaying it)&lt;br /&gt;
var zoom = 10;&lt;br /&gt;
var type = &amp;quot;intl&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
var ui_root = window.getCanvas().createGroup();&lt;br /&gt;
var vbox = canvas.VBoxLayout.new();&lt;br /&gt;
window.setLayout(vbox);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
var button_in = canvas.gui.widgets.Button.new(ui_root, canvas.style, {}).setText(&amp;quot;+&amp;quot;).listen(&amp;quot;clicked&amp;quot;, func changeZoom(1));&lt;br /&gt;
var button_out = canvas.gui.widgets.Button.new(ui_root, canvas.style, {}).setText(&amp;quot;-&amp;quot;).listen(&amp;quot;clicked&amp;quot;, func changeZoom(-1));&lt;br /&gt;
button_in.setSizeHint([32, 32]);&lt;br /&gt;
button_out.setSizeHint([32, 32]);&lt;br /&gt;
&lt;br /&gt;
var label_zoom = canvas.gui.widgets.Label.new(ui_root, canvas.style, {});&lt;br /&gt;
&lt;br /&gt;
var button_box = canvas.HBoxLayout.new();&lt;br /&gt;
button_box.addItem(button_in);&lt;br /&gt;
button_box.addItem(label_zoom);&lt;br /&gt;
button_box.addItem(button_out);&lt;br /&gt;
button_box.addStretch(1);&lt;br /&gt;
&lt;br /&gt;
vbox.addItem(button_box);&lt;br /&gt;
vbox.addStretch(1);&lt;br /&gt;
&lt;br /&gt;
var changeZoom = func(d)&lt;br /&gt;
{&lt;br /&gt;
  zoom = math.max(2, math.min(19, zoom + d));&lt;br /&gt;
  label_zoom.setText(&amp;quot;Zoom &amp;quot; ~ zoom);&lt;br /&gt;
  updateTiles();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
# http://polymaps.org/docs/&lt;br /&gt;
# https://github.com/simplegeo/polymaps&lt;br /&gt;
# https://github.com/Leaflet/Leaflet&lt;br /&gt;
&lt;br /&gt;
var maps_base = getprop(&amp;quot;/sim/fg-home&amp;quot;) ~ '/cache/maps';&lt;br /&gt;
&lt;br /&gt;
# http://otile1.mqcdn.com/tiles/1.0.0/map&lt;br /&gt;
# http://otile1.mqcdn.com/tiles/1.0.0/sat&lt;br /&gt;
# (also see http://wiki.openstreetmap.org/wiki/Tile_usage_policy)&lt;br /&gt;
var makeUrl =&lt;br /&gt;
string.compileTemplate('https://maps.wikimedia.org/osm-{type}/{z}/{x}/{y}.png');&lt;br /&gt;
  #https://maps.wikimedia.org/osm-intl/${z}/${x}/${y}.png&lt;br /&gt;
  var makePath =&lt;br /&gt;
  string.compileTemplate(maps_base ~ '/osm-{type}/{z}/{x}/{y}.png');&lt;br /&gt;
  var num_tiles = [4, 3];&lt;br /&gt;
&lt;br /&gt;
  var center_tile_offset = [&lt;br /&gt;
  (num_tiles[0] - 1) / 2,&lt;br /&gt;
  (num_tiles[1] - 1) / 2&lt;br /&gt;
  ];&lt;br /&gt;
&lt;br /&gt;
# simple aircraft icon at current position/center of the map&lt;br /&gt;
g.createChild(&amp;quot;path&amp;quot;)&lt;br /&gt;
.moveTo( tile_size * center_tile_offset[0] - 10,&lt;br /&gt;
  tile_size * center_tile_offset[1] )&lt;br /&gt;
.horiz(20)&lt;br /&gt;
.move(-10,-10)&lt;br /&gt;
.vert(20)&lt;br /&gt;
.set(&amp;quot;stroke&amp;quot;, &amp;quot;red&amp;quot;)&lt;br /&gt;
.set(&amp;quot;stroke-width&amp;quot;, 2)&lt;br /&gt;
.set(&amp;quot;z-index&amp;quot;, 1);&lt;br /&gt;
&lt;br /&gt;
##&lt;br /&gt;
# initialize the map by setting up&lt;br /&gt;
# a grid of raster images  &lt;br /&gt;
&lt;br /&gt;
var tiles = setsize([], num_tiles[0]);&lt;br /&gt;
for(var x = 0; x &amp;lt; num_tiles[0]; x += 1)&lt;br /&gt;
{&lt;br /&gt;
  tiles[x] = setsize([], num_tiles[1]);&lt;br /&gt;
  for(var y = 0; y &amp;lt; num_tiles[1]; y += 1)&lt;br /&gt;
  tiles[x][y] = g.createChild(&amp;quot;image&amp;quot;, &amp;quot;map-tile&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var last_tile = [-1,-1];&lt;br /&gt;
var last_type = type;&lt;br /&gt;
&lt;br /&gt;
##&lt;br /&gt;
# this is the callback that will be regularly called by the timer&lt;br /&gt;
# to update the map&lt;br /&gt;
var updateTiles = func()&lt;br /&gt;
{&lt;br /&gt;
  # get current position&lt;br /&gt;
  var lat = getprop('/position/latitude-deg');&lt;br /&gt;
  var lon = getprop('/position/longitude-deg');&lt;br /&gt;
&lt;br /&gt;
  var n = math.pow(2, zoom);&lt;br /&gt;
  var offset = [&lt;br /&gt;
  n * ((lon + 180) / 360) - center_tile_offset[0],&lt;br /&gt;
  (1 - math.ln(math.tan(lat * math.pi/180) + 1 / math.cos(lat * math.pi/180)) / math.pi) / 2 * n - center_tile_offset[1]&lt;br /&gt;
  ];&lt;br /&gt;
  var tile_index = [int(offset[0]), int(offset[1])];&lt;br /&gt;
&lt;br /&gt;
  var ox = tile_index[0] - offset[0];&lt;br /&gt;
  var oy = tile_index[1] - offset[1];&lt;br /&gt;
&lt;br /&gt;
  for(var x = 0; x &amp;lt; num_tiles[0]; x += 1)&lt;br /&gt;
  for(var y = 0; y &amp;lt; num_tiles[1]; y += 1)&lt;br /&gt;
  tiles[x][y].setTranslation(int((ox + x) * tile_size + 0.5), int((oy + y) * tile_size + 0.5));&lt;br /&gt;
&lt;br /&gt;
  if(    tile_index[0] != last_tile[0]&lt;br /&gt;
    or tile_index[1] != last_tile[1]&lt;br /&gt;
    or type != last_type )&lt;br /&gt;
  {&lt;br /&gt;
    for(var x = 0; x &amp;lt; num_tiles[0]; x += 1)&lt;br /&gt;
    for(var y = 0; y &amp;lt; num_tiles[1]; y += 1)&lt;br /&gt;
    {&lt;br /&gt;
      var pos = {&lt;br /&gt;
        z: zoom,&lt;br /&gt;
        x: int(offset[0] + x),&lt;br /&gt;
        y: int(offset[1] + y),&lt;br /&gt;
        type: type&lt;br /&gt;
      };&lt;br /&gt;
&lt;br /&gt;
      (func {&lt;br /&gt;
        var img_path = makePath(pos);&lt;br /&gt;
        var tile = tiles[x][y];&lt;br /&gt;
&lt;br /&gt;
        if( io.stat(img_path) == nil )&lt;br /&gt;
        { # image not found, save in $FG_HOME&lt;br /&gt;
          var img_url = makeUrl(pos);&lt;br /&gt;
          print('requesting ' ~ img_url);&lt;br /&gt;
          http.save(img_url, img_path)&lt;br /&gt;
          .done(func {print('received image ' ~ img_path); tile.set(&amp;quot;src&amp;quot;, img_path);})&lt;br /&gt;
          .fail(func (r) print('Failed to get image ' ~ img_path ~ ' ' ~ r.status ~ ': ' ~ r.reason));&lt;br /&gt;
        }&lt;br /&gt;
        else # cached image found, reusing&lt;br /&gt;
        {&lt;br /&gt;
          print('loading ' ~ img_path);&lt;br /&gt;
          tile.set(&amp;quot;src&amp;quot;, img_path)&lt;br /&gt;
        }&lt;br /&gt;
        })();&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      last_tile = tile_index;&lt;br /&gt;
      last_type = type;&lt;br /&gt;
    }&lt;br /&gt;
  };&lt;br /&gt;
&lt;br /&gt;
##&lt;br /&gt;
# set up a timer that will invoke updateTiles at 2-second intervals&lt;br /&gt;
var update_timer = maketimer(2, updateTiles);&lt;br /&gt;
# actually start the timer&lt;br /&gt;
update_timer.start();&lt;br /&gt;
&lt;br /&gt;
##&lt;br /&gt;
# set up default zoom level&lt;br /&gt;
changeZoom(0);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
###&lt;br /&gt;
# The following lines were recently added and have not yet been tested&lt;br /&gt;
# (if in doubt, remove them)&lt;br /&gt;
window.del = func()&lt;br /&gt;
{&lt;br /&gt;
  print(&amp;quot;Cleaning up window:&amp;quot;, ,&amp;quot;\n&amp;quot;);&lt;br /&gt;
  update_timer.stop();&lt;br /&gt;
# explanation for the call() technique at: http://wiki.flightgear.org/Object_oriented_programming_in_Nasal#Making_safer_base-class_calls&lt;br /&gt;
call(canvas.Window.del, [], me);&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Adding a NavDisplay ==&lt;br /&gt;
&lt;br /&gt;
== Adding a CDU ==&lt;br /&gt;
&lt;br /&gt;
== Adding a Multi-Function Display (MFD) ==&lt;/div&gt;</summary>
		<author><name>TheEagle</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Canvas_snippets&amp;diff=136890</id>
		<title>Canvas snippets</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Canvas_snippets&amp;diff=136890"/>
		<updated>2022-12-30T12:14:39Z</updated>

		<summary type="html">&lt;p&gt;TheEagle: Added tab widget example screenshot&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Stub}}&lt;br /&gt;
{{-}}&lt;br /&gt;
This article is meant to be a collection of self-contained code snippets for doing Nasal/Canvas coding without having to spend too much time working through APIs, documentation and existing code - the idea is to provide snippets that contain comments and explicit assumptions, so that these snippets can be easily adapted by people without necessarily having to be very familiar with Nasal/Canvas coding. &lt;br /&gt;
&lt;br /&gt;
In the long term, we're hoping to grow a library of useful code snippets to cover most use-cases, while also covering more complex scenarios, e.g. using Canvas-specific helper frameworks like [[Canvas MapStructure]], the [[NavDisplay]] or the recent CDU work.&lt;br /&gt;
&lt;br /&gt;
Canvas itself is designed to be front-end agnostic, meaning that it doesn't matter where a Canvas is used/displayed - this is accomplished by so called &amp;quot;placements&amp;quot;, which determine where a Canvas texture is shown. Thus, the back-end logic can remain the same usually, so that GUI dialogs may show avionics, but also so that avionics may show GUI widgets, which also applies to HUDs, liveries and even placements within the FlightGear scenery.&lt;br /&gt;
&lt;br /&gt;
We encourage people to use the snippets listed below to get started with Nasal/Canvas coding, but also to provide feedback on extending/improving this article to make it even more accessible and useful. Equally, if you're aware of any Canvas-related efforts that may contain useful code snippets, please do feel free to add those snippets here. Even people who don't have any interest in coding itself are encouraged to get involved by helping test the snippets listed below, for which you only need to the [[Nasal Console]], and while you're at it, please also help provide/update screen shots for each code snippet.&lt;br /&gt;
&lt;br /&gt;
Contributions added to this article should ideally satisfy some requirements:&lt;br /&gt;
* be entirely self-contained&lt;br /&gt;
* contain good/clear comments&lt;br /&gt;
* have pointers/references to related code/use-cases&lt;br /&gt;
* make assumptions explicit&lt;br /&gt;
* contain screen shots for each example&lt;br /&gt;
* avoid overloaded symbol names to ensure that examples can be merged and adapted easily&lt;br /&gt;
* dependencies (svg files, images/textures etc) should always be chosen such that snippets always work using $FG_ROOT&lt;br /&gt;
* hard-coded assumptions (e.g. texture dimensions in terms of width/height etc) should be encapsulated using variables&lt;br /&gt;
 &lt;br /&gt;
{{Canvas Navigation}}&lt;br /&gt;
&lt;br /&gt;
== Creating a standalone Canvas ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[Special:UploadWizard|Upload requested]] || &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
var (width, height) = (512,512);&lt;br /&gt;
# Create a standalone Canvas (not attached to any GUI dialog/aircraft etc) &lt;br /&gt;
var myCanvas = canvas.new({&lt;br /&gt;
  &amp;quot;name&amp;quot;: &amp;quot;Livery Test&amp;quot;,   # The name is optional but allow for easier identification&lt;br /&gt;
  &amp;quot;size&amp;quot;: [width, height], # Size of the underlying texture (should be a power of 2, required) [Resolution]&lt;br /&gt;
  &amp;quot;view&amp;quot;: [width, height],  # Virtual resolution (Defines the coordinate system of the canvas [Dimensions]&lt;br /&gt;
                        # which will be stretched the size of the texture, required)&lt;br /&gt;
  &amp;quot;mipmapping&amp;quot;: 1       # Enable mipmapping (optional)&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
# set background color&lt;br /&gt;
myCanvas.set(&amp;quot;background&amp;quot;, canvas.style.getColor(&amp;quot;bg_color&amp;quot;));&lt;br /&gt;
&lt;br /&gt;
# creating the top-level/root group which will contain all other elements/group&lt;br /&gt;
var root = myCanvas.createGroup();&lt;br /&gt;
&lt;br /&gt;
# OPTIONAL: Create a Canvas dialog window to hold the canvas and show that it's working&lt;br /&gt;
# the Canvas is now standalone, i.e. continues to live once the dialog is closed!&lt;br /&gt;
var window = canvas.Window.new([width,height],&amp;quot;dialog&amp;quot;);&lt;br /&gt;
window.setCanvas(myCanvas);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Accessing the Canvas Desktop ==&lt;br /&gt;
Sometimes you may want to render to the main screen, without creating a separate Canvas window - this can be accomplished by using the Canvas desktop, note that the following example is fully self-contained, i.e. does not require any code to be added to work:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[Special:UploadWizard|Upload requested]] || &lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
var myNode = canvas.getDesktop().createChild(&amp;quot;text&amp;quot;)&lt;br /&gt;
      .setText(&amp;quot;Hello Canvas Desktop&amp;quot;)&lt;br /&gt;
      .setFontSize(25, 1.0)          # font size (in texels) and font aspect ratio&lt;br /&gt;
      .setColor(1,0,0,1)             # red, fully opaque&lt;br /&gt;
      .setAlignment(&amp;quot;center-center&amp;quot;) # how the text is aligned to where you place it&lt;br /&gt;
      .setTranslation(160, 80);     # where to place the text&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Creating Tooltips ==&lt;br /&gt;
== Creating Popups ==&lt;br /&gt;
== Creating a Canvas GUI Window ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Snippets-canvas-dialog.png|left|thumb|This is what the [[Nasal]]/[[Canvas]] snippet will look like once you pasted it into the [[Nasal Console]] and click &amp;quot;Execute&amp;quot;.]] || {{Note|This example uses so called method chaining, if you're not familiar with the concept, please see: [[Object_Oriented_Programming_with_Nasal#More_on_methods:_Chaining|Method Chaining]].}}&lt;br /&gt;
&lt;br /&gt;
{{Canvas Snippets Boilerplate}}&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Adding Raster Images ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code &lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas Snippets-raster image.png|thumb|Canvas snippet demonstrating how to load a raster image]] ||&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# path is relative to $FG_ROOT (base package)&lt;br /&gt;
var path = &amp;quot;Textures/Splash1.png&amp;quot;;&lt;br /&gt;
# create an image child for the texture&lt;br /&gt;
var child = root.createChild(&amp;quot;image&amp;quot;)&lt;br /&gt;
    .setFile(path)&lt;br /&gt;
    .setTranslation(100, 10)&lt;br /&gt;
    .setSize(130, 130);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Adding Raster Images downloaded on demand ==&lt;br /&gt;
&lt;br /&gt;
The '''path''' could also just as well be a URL, i.e. a raster image retrieved via http - for example, the following snippet is entirely self-contained and can be pasted into the [[Nasal Console]] and directly executed &amp;quot;as is&amp;quot;: &lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-raster-images-via-url.png|thumb|screen shot demonstrating how Nasal and Canvas can be used to display raster images downloaded on demand]] ||&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# create a new window, dimensions are 400 x 200, using the dialog decoration (i.e. titlebar)&lt;br /&gt;
var window = canvas.Window.new([400,200],&amp;quot;dialog&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
# adding a canvas to the new window and setting up background colors/transparency&lt;br /&gt;
var myCanvas = window.createCanvas().set(&amp;quot;background&amp;quot;, canvas.style.getColor(&amp;quot;bg_color&amp;quot;));&lt;br /&gt;
&lt;br /&gt;
# Using specific css colors would also be possible:&lt;br /&gt;
# myCanvas.set(&amp;quot;background&amp;quot;, &amp;quot;#ffaac0&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
# creating the top-level/root group which will contain all other elements/group&lt;br /&gt;
var root = myCanvas.createGroup();&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# path now a URL&lt;br /&gt;
var url = &amp;quot;http://www.worldwidetelescope.org/docs/Images/MapOfEarth.jpg&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
# create an image child for the texture&lt;br /&gt;
var child=root.createChild(&amp;quot;image&amp;quot;)&lt;br /&gt;
    .setFile( url ) &lt;br /&gt;
    .setTranslation(45,22) # centered, in relation to dialog coordinates&lt;br /&gt;
    .setSize(310,155); # image dimensions&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Clipping ==&lt;br /&gt;
{{Note|{{FGCquote&lt;br /&gt;
|1= Scaling or any other type of transformation or changing the coordinates of individual points is definitely more efficient than clipping (which requires to change the OpenGL clip planes for every rendered object with a different clipping rectangle).&lt;br /&gt;
|2= {{cite web&lt;br /&gt;
  | url    = http://forum.flightgear.org/viewtopic.php?p=277062#p277062&lt;br /&gt;
  | title  = &amp;lt;nowiki&amp;gt;Re: Space Shuttle&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
  | author = &amp;lt;nowiki&amp;gt;TheTom&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
  | date   = Feb 21st, 2016&lt;br /&gt;
  | added   = Feb 21st, 2016&lt;br /&gt;
  | script_version = 0.25&lt;br /&gt;
  }}&lt;br /&gt;
}}&lt;br /&gt;
}}&lt;br /&gt;
I would suggest to refer to api.nas and look for &amp;quot;clip&amp;quot; and/or &amp;quot;rect&amp;quot; - IIRC, you need to set up a clipping rectangle by setting some kind of &amp;quot;clip&amp;quot; property and setting it to a rect value in the form of rect(...)&lt;br /&gt;
{{FGCquote&lt;br /&gt;
|1= For details, see the clipping example at: [[Canvas Nasal API#set 2]] and  [[Canvas Element#clip.28.29]]&lt;br /&gt;
|2= {{cite web&lt;br /&gt;
  | url    = http://forum.flightgear.org/viewtopic.php?p=276965#p276965&lt;br /&gt;
  | title  = &amp;lt;nowiki&amp;gt;Re: Space Shuttle&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
  | author = &amp;lt;nowiki&amp;gt;Hooray&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
  | date   = Feb 21st, 2016&lt;br /&gt;
  | added   = Feb 21st, 2016&lt;br /&gt;
  | script_version = 0.25&lt;br /&gt;
  }}&lt;br /&gt;
}}&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:MapStructure_dialog_with_clipping_and_event_handling_applied.png|thumb]] ||&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# create a new window, dimensions are 400 x 200, using the dialog decoration (i.e. titlebar)&lt;br /&gt;
var window = canvas.Window.new([400,200],&amp;quot;dialog&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
# adding a canvas to the new window and setting up background colors/transparency&lt;br /&gt;
var myCanvas = window.createCanvas().set(&amp;quot;background&amp;quot;, canvas.style.getColor(&amp;quot;bg_color&amp;quot;));&lt;br /&gt;
&lt;br /&gt;
# Using specific css colors would also be possible:&lt;br /&gt;
# myCanvas.set(&amp;quot;background&amp;quot;, &amp;quot;#ffaac0&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
# creating the top-level/root group which will contain all other elements/group&lt;br /&gt;
var root = myCanvas.createGroup();&lt;br /&gt;
&lt;br /&gt;
# create an image child for the texture&lt;br /&gt;
var child=root.createChild(&amp;quot;.........&amp;quot;)&lt;br /&gt;
    .setFile( url ) &lt;br /&gt;
    .setTranslation(45,22) # centered, in relation to dialog coordinates&lt;br /&gt;
    .setSize(310,155); # image dimensions&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Adding OpenVG Paths ==&lt;br /&gt;
{{Main article|How to manipulate Canvas elements}}&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Openvg-via-canvas.png|thumb|screen shot showing a simple Canvas GUI dialog demonstrating how to use OpenVG-path drawing via Nasal and Canvas]] &lt;br /&gt;
| {{Note|This assumes that you already have a top-level root group set up, and named it '''root'''.}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
var graph = root.createChild(&amp;quot;group&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
var x_axis = graph.createChild(&amp;quot;path&amp;quot;, &amp;quot;x-axis&amp;quot;)&lt;br /&gt;
.moveTo(10, height/2)&lt;br /&gt;
.lineTo(width-10, height/2)&lt;br /&gt;
.setColor(1,0,0)&lt;br /&gt;
.setStrokeLineWidth(3);&lt;br /&gt;
&lt;br /&gt;
var y_axis = graph.createChild(&amp;quot;path&amp;quot;, &amp;quot;y-axis&amp;quot;)&lt;br /&gt;
.moveTo(10, 10)&lt;br /&gt;
.lineTo(10, height-10)&lt;br /&gt;
.setColor(0,0,1)&lt;br /&gt;
.setStrokeLineWidth(2);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-openvg-quadTo.png|thumb|screen shot showing a simple Canvas GUI dialog demonstrating how to use OpenVG-path drawing via Nasal and Canvas (Canvas/OpenVG quadTo API for drawing curves)]] &lt;br /&gt;
| {{Note|This assumes that you are appending this to the snippet shown above.}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
var points = [	60,height-20, &lt;br /&gt;
		230,height-100,&lt;br /&gt;
		];&lt;br /&gt;
&lt;br /&gt;
var track = graph.createChild(&amp;quot;path&amp;quot;, &amp;quot;track&amp;quot;)&lt;br /&gt;
.moveTo(10, height/2)&lt;br /&gt;
.quadTo( points )&lt;br /&gt;
.setColor(0,1,0)&lt;br /&gt;
.setStrokeLineWidth(4);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-openvg-cubicTo.png|thumb|screen shot showing a simple Canvas GUI dialog demonstrating how to use OpenVG-path drawing via Nasal and Canvas (Canvas/OpenVG cubicTo API for drawing curves)]] &lt;br /&gt;
| {{Note|This assumes that you are appending this to the snippet shown above.}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
var points = [	60,height-20, &lt;br /&gt;
		120,height-120,&lt;br /&gt;
		230,height-100,&lt;br /&gt;
		];&lt;br /&gt;
&lt;br /&gt;
var track = graph.createChild(&amp;quot;path&amp;quot;, &amp;quot;track&amp;quot;)&lt;br /&gt;
.moveTo(10, height/2)&lt;br /&gt;
.cubicTo( points )&lt;br /&gt;
.setColor(0,1,0)&lt;br /&gt;
.setStrokeLineWidth(4);&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Adding Vector Images ==&lt;br /&gt;
&lt;br /&gt;
[[Category:Canvas SVG]]&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-svg-support.png|thumb|screen shot demonstrating how the scripted Nasal-based SVG parser can be used to dynamically turn SVG files into OpenVG instructions understood by Canvas]] &lt;br /&gt;
| {{Note|This assumes that you already have a top-level root group set up, and named it '''root'''.}}&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
# change the background color &lt;br /&gt;
myCanvas.set(&amp;quot;background&amp;quot;, &amp;quot;#ffaac0&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
# creating the top-level/root group which will contain all other elements/group&lt;br /&gt;
var root = myCanvas.createGroup();&lt;br /&gt;
&lt;br /&gt;
var filename = &amp;quot;/Nasal/canvas/map/Images/boeingAirplane.svg&amp;quot;;&lt;br /&gt;
var svg_symbol = root.createChild('group');&lt;br /&gt;
canvas.parsesvg(svg_symbol, filename);&lt;br /&gt;
&lt;br /&gt;
svg_symbol.setTranslation(width/2,height/2);&lt;br /&gt;
&lt;br /&gt;
#svg_symbol.setScale(0.2);&lt;br /&gt;
#svg_symbol.setRotation(radians)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Using the SymbolCache ==&lt;br /&gt;
Whenever a symbol may need to be shown/instanced multiple times (possibly using different styling), it makes sense to use caching - otherwise, identical symbols would be treated as separate OpenVG groups, all of which would need to be rasterized/rendedered separately (i.e. 100 identical symbols would be updated/rendered one by one). &lt;br /&gt;
&lt;br /&gt;
Typically, a map may display multiple instances of an otherwise identical symbol (think VOR, NDB, DME etc) - equally, a multiplayer map may showing multiple aircraft symbols at the same time. In these cases, it makes sense to use the SymbolCache framework, which will render symbols into a separate Canvas texture and provide a texture map that can be treated as a lookup map, which even supports styling for otherwise identical symbols. To learn more, please refer to [[Canvas MapStructure#The_SymbolCache|SymbolCache]]&lt;br /&gt;
... &lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-SymbolCache-Instancing.png|thumb|Screen shot showing a Canvas based GUI dialog that is using the SymbolCache for instancing multiple symbols (including support for styling)]] &lt;br /&gt;
| &amp;lt;!--{{Caution|This is currently untested code}}--&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
##&lt;br /&gt;
# this is the function that draws a symbol using OpenVG paths&lt;br /&gt;
# it accepts a group to draw to and returns the rendered group &lt;br /&gt;
# to the caller&lt;br /&gt;
var drawVOR = func(group) {&lt;br /&gt;
    return group.createChild(&amp;quot;path&amp;quot;)&lt;br /&gt;
        .moveTo(-15,0)&lt;br /&gt;
        .lineTo(-7.5,12.5)&lt;br /&gt;
        .lineTo(7.5,12.5)&lt;br /&gt;
        .lineTo(15,0)&lt;br /&gt;
        .lineTo(7.5,-12.5)&lt;br /&gt;
        .lineTo(-7.5,-12.5)&lt;br /&gt;
        .close()&lt;br /&gt;
        .setStrokeLineWidth(line_width) # style-able&lt;br /&gt;
        .setColor(color); # style-able&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
var style = { # styling related attributes (as per the draw* function above)&lt;br /&gt;
    line_width: 3,&lt;br /&gt;
    scale_factor: 1,&lt;br /&gt;
    color: [1,0,0],&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
# create a new cache entry for the styled symbol&lt;br /&gt;
var myCachedSymbol = canvas.StyleableCacheable.new(&lt;br /&gt;
    name:'myVOR', draw_func: drawVOR,&lt;br /&gt;
    cache: canvas.SymbolCache32x32, # the cache to be used&lt;br /&gt;
    draw_mode: canvas.SymbolCache.DRAW_CENTERED,&lt;br /&gt;
    relevant_keys: ['line_width', 'color'], # styling related attributes&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
var target = root.createChild('group');&lt;br /&gt;
&lt;br /&gt;
var x=0;&lt;br /&gt;
var y=height/2;&lt;br /&gt;
&lt;br /&gt;
var xoffset=50;&lt;br /&gt;
&lt;br /&gt;
# render 5 instanced symbols using the style specified above&lt;br /&gt;
for (var i=0;i&amp;lt;5;i+=1) {&lt;br /&gt;
# look up the raster image for the symbol&lt;br /&gt;
# render it using the passed style and adjust scaling&lt;br /&gt;
var instanced = myCachedSymbol.render(target, style)&lt;br /&gt;
  .setScale(style.scale_factor)&lt;br /&gt;
  .setTranslation(x+=xoffset,y);&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The example shown above uses a fixed symbol/icon cache that is set up while booting Flightgear - sometimes, we may need cache for different purposes. So, let's assume, we need a new/custom cache with a different resolution for each entry in the cache (e.g. 256x256), we can easily accomplish that by setting up a new cache like this: &lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
var mySymbolCache256x256 = canvas.SymbolCache.new(1024,256);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Adding Text Elements ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:CanvasText-Hello-World.png|thumb|Screen shot showing the CanvasText example contributed by Necolatis]]&lt;br /&gt;
| {{Note|This assumes that you already have a top-level root group set up, and named it '''root'''.}}&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
var myText = root.createChild(&amp;quot;text&amp;quot;)&lt;br /&gt;
      .setText(&amp;quot;Hello world!&amp;quot;)&lt;br /&gt;
      .setFontSize(20, 0.9)          # font size (in texels) and font aspect ratio&lt;br /&gt;
      .setColor(1,0,0,1)             # red, fully opaque&lt;br /&gt;
      .setAlignment(&amp;quot;center-center&amp;quot;) # how the text is aligned to where you place it&lt;br /&gt;
      .setTranslation(160, 80);     # where to place the text&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Adding GUI Labels ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-Layout-Label-example-by-Necolatis.png|thumb|Canvas demo: Layouts and Labels (by Necolatis)]]&lt;br /&gt;
| {{Note|This assumes that you already have a top-level root group set up, and named it '''root''', just change this variable if you want it to be rendered elsewhere. It also assumes you have a Layout item setup and called '''myLayoutItem'''.}}&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
# create a new layout&lt;br /&gt;
var myLayout = canvas.HBoxLayout.new();&lt;br /&gt;
# assign it to the Canvas&lt;br /&gt;
myCanvas.setLayout(myLayout);&lt;br /&gt;
&lt;br /&gt;
var label = canvas.gui.widgets.Label.new(root, canvas.style, {wordWrap: 0}); # wordwrap: 0 will disable wordwrapping, to enable it use 1 instead&lt;br /&gt;
label.setText(&amp;quot;Hello World!&amp;quot;);&lt;br /&gt;
myLayout.addItem(label);&lt;br /&gt;
&lt;br /&gt;
var label2 = canvas.gui.widgets.Label.new(root, canvas.style, {wordWrap: 0}); # wordwrap: 0 will disable wordwrapping, to enable it use 1 instead&lt;br /&gt;
label2.setText(&amp;quot;Hello FlightGear&amp;quot;);&lt;br /&gt;
myLayout.addItem(label2);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Adding GUI Buttons (Layouts)==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-demo-layouts-and-buttons-by-Necolatis.png|thumb|Canvas snippet: buttons and layouts (by Necolatis)]] &lt;br /&gt;
|&lt;br /&gt;
&lt;br /&gt;
{{Note|This assumes that you already have a top-level root group set up, and named it '''root''', just change this variable if you want it to be rendered elsewhere. It also assumes you have a Layout item setup and called '''myLayoutItem'''.}}&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
# create a new layout&lt;br /&gt;
var myLayout = canvas.HBoxLayout.new();&lt;br /&gt;
# assign it to the Canvas&lt;br /&gt;
myCanvas.setLayout(myLayout);&lt;br /&gt;
&lt;br /&gt;
# click button&lt;br /&gt;
&lt;br /&gt;
var button = canvas.gui.widgets.Button.new(root, canvas.style, {})&lt;br /&gt;
	.setText(&amp;quot;Click on me&amp;quot;)&lt;br /&gt;
	.setFixedSize(75, 25);&lt;br /&gt;
&lt;br /&gt;
button.listen(&amp;quot;clicked&amp;quot;, func {&lt;br /&gt;
        # add code here to react on click on button.&lt;br /&gt;
print(&amp;quot;Button clicked !&amp;quot;);&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
myLayout.addItem(button);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-toggle-button-snippet-by-Necolatis.png|thumb|Canvas toggle button demo by Necolatis]]&lt;br /&gt;
| &amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
# create a new layout&lt;br /&gt;
var myLayout = canvas.HBoxLayout.new();&lt;br /&gt;
# assign it to the Canvas&lt;br /&gt;
myCanvas.setLayout(myLayout);&lt;br /&gt;
&lt;br /&gt;
var button = canvas.gui.widgets.Button.new(root, canvas.style, {})&lt;br /&gt;
        .setText(&amp;quot;Toggle me&amp;quot;)&lt;br /&gt;
        .setCheckable(1) # this indicates that is should be a toggle button&lt;br /&gt;
        .setChecked(0) # depressed by default&lt;br /&gt;
        .setFixedSize(75, 25);&lt;br /&gt;
&lt;br /&gt;
button.listen(&amp;quot;toggled&amp;quot;, func (e) {&lt;br /&gt;
        if( e.detail.checked ) {&lt;br /&gt;
            # add code here to react on button being depressed.&lt;br /&gt;
        } else {&lt;br /&gt;
            # add code here to react on button not being depressed.&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
myLayout.addItem(button);&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Canvas Input Dialog ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
|[[File:Snippets-canvas-input-dialog.png|thumb|Canvas input dialog]]&lt;br /&gt;
||&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
# create a new InputDialog with a title, label, and a callback&lt;br /&gt;
canvas.InputDialog.getText(&amp;quot;Input Dialog Title&amp;quot;, &amp;quot;Please enter some text&amp;quot;, func(btn,value) {&lt;br /&gt;
    if (value) gui.popupTip(&amp;quot;You entered: &amp;quot;~value);&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Canvas ScrollArea ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
|[[File:Canvas-snippets-scrollArea-demo.png|thumb|Screen shot showing a Canvas ScrollArea populated with different splash screens, loaded from $FG_ROOT/Textures]]&lt;br /&gt;
||&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
var vbox = canvas.VBoxLayout.new();&lt;br /&gt;
myCanvas.setLayout(vbox);&lt;br /&gt;
&lt;br /&gt;
 var scroll = canvas.gui.widgets.ScrollArea.new(root, canvas.style, {size: [96, 128]}).move(20, 100);&lt;br /&gt;
 vbox.addItem(scroll, 1);&lt;br /&gt;
&lt;br /&gt;
var scrollContent =&lt;br /&gt;
      scroll.getContent()&lt;br /&gt;
            .set(&amp;quot;font&amp;quot;, &amp;quot;LiberationFonts/LiberationSans-Bold.ttf&amp;quot;)&lt;br /&gt;
            .set(&amp;quot;character-size&amp;quot;, 16)&lt;br /&gt;
            .set(&amp;quot;alignment&amp;quot;, &amp;quot;left-center&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
var list = canvas.VBoxLayout.new();&lt;br /&gt;
scroll.setLayout(list);&lt;br /&gt;
&lt;br /&gt;
for (var i=1;i&amp;lt;=5;i+=1) {&lt;br /&gt;
var label = canvas.gui.widgets.Label.new(scrollContent, canvas.style, {wordWrap: 0}); &lt;br /&gt;
label.setImage(&amp;quot;Textures/Splash&amp;quot;~i~&amp;quot;.png&amp;quot;);&lt;br /&gt;
label.setFixedSize(256,256);&lt;br /&gt;
list.addItem(label);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Using TabWidgets ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+&lt;br /&gt;
!Screenshot&lt;br /&gt;
!Code&lt;br /&gt;
|-&lt;br /&gt;
|[[File:Canvas TabWidget example.png|alt=Canvas TabWIdget example|thumb|Canvas TabWidget example showing the three default splash screens]]&lt;br /&gt;
|&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
var window = canvas.Window.new([300, 300], &amp;quot;dialog&amp;quot;);&lt;br /&gt;
var myCanvas = window.createCanvas().set(&amp;quot;background&amp;quot;, canvas.style.getColor(&amp;quot;bg_color&amp;quot;));&lt;br /&gt;
var root = myCanvas.createGroup();&lt;br /&gt;
var vbox = canvas.VBoxLayout.new();&lt;br /&gt;
myCanvas.setLayout(vbox);&lt;br /&gt;
 &lt;br /&gt;
var tabs = canvas.gui.widgets.TabWidget.new(root, canvas.style, {});&lt;br /&gt;
var tabsContent = tabs.getContent();&lt;br /&gt;
vbox.addItem(tabs);&lt;br /&gt;
&lt;br /&gt;
var tab1 = canvas.VBoxLayout.new();&lt;br /&gt;
var image1 = canvas.gui.widgets.Label.new(tabsContent, canvas.style, {})&lt;br /&gt;
				.setImage(&amp;quot;Textures/Splash1.png&amp;quot;)&lt;br /&gt;
				.setFixedSize(128, 128);&lt;br /&gt;
tab1.addItem(image1);&lt;br /&gt;
var text1 = canvas.gui.widgets.Label.new(tabsContent, canvas.style, {})&lt;br /&gt;
				.setText(&amp;quot;Texture 1&amp;quot;);&lt;br /&gt;
tab1.addItem(text1);&lt;br /&gt;
tabs.addTab(&amp;quot;tab1&amp;quot;, &amp;quot;Texture 1&amp;quot;, tab1);&lt;br /&gt;
&lt;br /&gt;
var tab2 = canvas.VBoxLayout.new();&lt;br /&gt;
var image2 = canvas.gui.widgets.Label.new(tabsContent, canvas.style, {})&lt;br /&gt;
				.setImage(&amp;quot;Textures/Splash2.png&amp;quot;)&lt;br /&gt;
				.setFixedSize(128, 128);&lt;br /&gt;
tab2.addItem(image2);&lt;br /&gt;
var text2 = canvas.gui.widgets.Label.new(tabsContent, canvas.style, {})&lt;br /&gt;
				.setText(&amp;quot;Texture 2&amp;quot;);&lt;br /&gt;
tab2.addItem(text2);&lt;br /&gt;
tabs.addTab(&amp;quot;tab2&amp;quot;, &amp;quot;Texture 2&amp;quot;, tab2);&lt;br /&gt;
&lt;br /&gt;
var tab3 = canvas.VBoxLayout.new();&lt;br /&gt;
var image3 = canvas.gui.widgets.Label.new(tabsContent, canvas.style, {})&lt;br /&gt;
				.setImage(&amp;quot;Textures/Splash3.png&amp;quot;)&lt;br /&gt;
				.setFixedSize(128, 128);&lt;br /&gt;
tab3.addItem(image3);&lt;br /&gt;
var text3 = canvas.gui.widgets.Label.new(tabsContent, canvas.style, {})&lt;br /&gt;
				.setText(&amp;quot;Texture 3&amp;quot;);&lt;br /&gt;
tab3.addItem(text3);&lt;br /&gt;
tabs.addTab(&amp;quot;tab3&amp;quot;, &amp;quot;Texture 3&amp;quot;, tab3);&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Using Layouts ==&lt;br /&gt;
&lt;br /&gt;
== Using Styling ==&lt;br /&gt;
== Adding a HUD ==&lt;br /&gt;
== Adding a 2D Instrument ==&lt;br /&gt;
== Adding a 2D Panel ==&lt;br /&gt;
== Adding a PFD ==&lt;br /&gt;
&lt;br /&gt;
== Adding a Failure Mgmt Widget ==&lt;br /&gt;
{{Note|This kind of widget will typically be useful for dialogs requiring a method for managing aircraft specific system failures (e.g. an instructor console).}}&lt;br /&gt;
&lt;br /&gt;
== Adding a MapStructure map to a Canvas  ==&lt;br /&gt;
{{Main article|Canvas MapStructure Layers}}&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code &lt;br /&gt;
|-&lt;br /&gt;
| [[File:Snippets-canvas-mapstructure-dialog.png|thumb|[[MapStructure]] layers shown in a Canvas GUI dialog]]&lt;br /&gt;
| {{Note|This assumes that you already have a top-level root group set up, and named it '''root''', just change this variable if you want it to be rendered elsewhere. Equally, this snippet assumes that your canvas is named '''myCanvas'''.}}&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
var TestMap = root.createChild(&amp;quot;map&amp;quot;);&lt;br /&gt;
TestMap.setController(&amp;quot;Aircraft position&amp;quot;);&lt;br /&gt;
TestMap.setRange(25); &lt;br /&gt;
 &lt;br /&gt;
TestMap.setTranslation(    myCanvas.get(&amp;quot;view[0]&amp;quot;)/2,&lt;br /&gt;
                           myCanvas.get(&amp;quot;view[1]&amp;quot;)/2&lt;br /&gt;
                        );&lt;br /&gt;
var r = func(name,vis=1,zindex=nil) return caller(0)[0];&lt;br /&gt;
&lt;br /&gt;
# APT and VOR are the layer names&lt;br /&gt;
foreach(var type; [r('APT'), r('VOR') ] )&lt;br /&gt;
 TestMap.addLayer(factory: canvas.SymbolLayer, type_arg: type.name, visible: type.vis, priority: type.zindex,);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Customizing MapStructure Styling  ==&lt;br /&gt;
{{See also|Canvas MapStructure#Styling}}&lt;br /&gt;
&lt;br /&gt;
In general MapStructure symbols contain their own styles using hard-coded defaults, however these can be overridden by providing a hash with keys (fields) to customize these hard-coded defaults. &lt;br /&gt;
&lt;br /&gt;
This means that anything that may be specific to a single style (colors, fonts, images etc) should be encoded in the form of variables that are looked up using the styles hash - this provides a great deal of freedom to customize an existing symbol. In addition, MapStructure layers can be set up to even customize/override the default drawing routines, at which point you are free to do whatever you want basically, because the existing draw routine in .symbol file is ignored &lt;br /&gt;
&lt;br /&gt;
{{WIP}}&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code &lt;br /&gt;
|-&lt;br /&gt;
| [[File:Layers style change.png|thumb|[[MapStructure]] layers shown in a Canvas GUI dialog. Active VOR and radial in red color, inactive VOR in green color]]&lt;br /&gt;
| {{Note|This assumes that you already have a top-level root group set up, and named it '''root''', just change this variable if you want it to be rendered elsewhere. Equally, this snippet assumes that your canvas is named '''myCanvas'''.}}&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
var TestMap = root.createChild(&amp;quot;map&amp;quot;);&lt;br /&gt;
TestMap.setController(&amp;quot;Aircraft position&amp;quot;);&lt;br /&gt;
TestMap.setRange(25); &lt;br /&gt;
 &lt;br /&gt;
TestMap.setTranslation(    myCanvas.get(&amp;quot;view[0]&amp;quot;)/2,&lt;br /&gt;
                           myCanvas.get(&amp;quot;view[1]&amp;quot;)/2&lt;br /&gt;
                        );&lt;br /&gt;
var r = func(name,vis=1,zindex=nil) return caller(0)[0];&lt;br /&gt;
var type = r('APT');&lt;br /&gt;
# a hash that contains variables that are supported by the relevant_keys vector in APT.symbol&lt;br /&gt;
var style_apt = {&lt;br /&gt;
    scale_factor:0.5, # 50 %&lt;br /&gt;
    color_default:[0,1,0.9], # rgb&lt;br /&gt;
    line_width:4, # thickness&lt;br /&gt;
    label_font_color:[0,1,0.9], # rgb&lt;br /&gt;
    label_font_size:30 # font size&lt;br /&gt;
};&lt;br /&gt;
TestMap.addLayer(factory: canvas.SymbolLayer, type_arg: type.name, visible: type.vis, priority: type.zindex,style:style_apt);&lt;br /&gt;
&lt;br /&gt;
var type = r('VOR');&lt;br /&gt;
# a hash that contains variables that are supported by the relevant_keys vector in VOR.symbol&lt;br /&gt;
var style_vor = {&lt;br /&gt;
    scale_factor:0.6,&lt;br /&gt;
    active_color:[1,0,0],&lt;br /&gt;
    inactive_color:[0,1,0],&lt;br /&gt;
    line_width:4&lt;br /&gt;
};&lt;br /&gt;
TestMap.addLayer(factory: canvas.SymbolLayer, type_arg: type.name, visible: type.vis, priority: type.zindex,style:style_vor);&lt;br /&gt;
&lt;br /&gt;
### See $FG_ROOT/Nasal/canvas/map/APT and $FG_ROOT/Nasal/canvas/map/VOR.sy,bolfor the style variables that can be configured.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Using MapStructure and Overlays ==&lt;br /&gt;
{{FGCquote&lt;br /&gt;
  |Support for overlays using geo-referenced raster images isn't too well-developed currently, so would need to work with a few hard-coded assumptions (e.g. being specific to a certain raster image and map layout) - but maybe TheTom can provide a few more ideas on how to proceed from here - otherwise, the Map is normally not aware of any non-Map items, i.e. as long as the overlay is added &amp;quot;outside&amp;quot; the Map, it isn't even aware of the raster image - and when it is added as a map element, it would probably be rotated. So there's still some work needed here. But generally, this should work well enough even in its current form (the screen shot is purely based on the Canvas Snippets article, i.e. it just displays a Canvas GUI dialog, adds the downloaded raster image and then adds the MapStructure APS layer - without the Map being aware of the overlay/geo-referencing that is needed currently).&lt;br /&gt;
  |{{cite web |url=http://forum.flightgear.org/viewtopic.php?p=238316#p238316&lt;br /&gt;
     |title=&amp;lt;nowiki&amp;gt;Re: Using Canvas for visualizing orbital flights (cont'd PM)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |author=&amp;lt;nowiki&amp;gt;Hooray&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |date=&amp;lt;nowiki&amp;gt;Fri Apr 10&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   }}&lt;br /&gt;
}}&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code &lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-overlay-with-mapstructure.png|thumb|Using the [[MapStructure]] framework in conjunction with raster images as overlays]]&lt;br /&gt;
| {{Note|This assumes that you already have a top-level root group set up, and named it '''root''', just change this variable if you want it to be rendered elsewhere. Equally, this snippet assumes that your canvas is named '''myCanvas'''.}}&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== A simple tile map ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code &lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas - Tile map demo.png|thumb|A simple, canvas based tile map which is centered around the aircraft.]]&lt;br /&gt;
| &lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
var (width,height) = (768,512);&lt;br /&gt;
var tile_size = 256;&lt;br /&gt;
&lt;br /&gt;
var window = canvas.Window.new([width, height],&amp;quot;dialog&amp;quot;).set('title', &amp;quot;Tile map demo&amp;quot;);&lt;br /&gt;
var g = window.getCanvas(1).createGroup();&lt;br /&gt;
&lt;br /&gt;
# Simple user interface (Buttons for zoom and label for displaying it)&lt;br /&gt;
var zoom = 10;&lt;br /&gt;
var type = &amp;quot;intl&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
var ui_root = window.getCanvas().createGroup();&lt;br /&gt;
var vbox = canvas.VBoxLayout.new();&lt;br /&gt;
window.setLayout(vbox);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
var button_in = canvas.gui.widgets.Button.new(ui_root, canvas.style, {}).setText(&amp;quot;+&amp;quot;).listen(&amp;quot;clicked&amp;quot;, func changeZoom(1));&lt;br /&gt;
var button_out = canvas.gui.widgets.Button.new(ui_root, canvas.style, {}).setText(&amp;quot;-&amp;quot;).listen(&amp;quot;clicked&amp;quot;, func changeZoom(-1));&lt;br /&gt;
button_in.setSizeHint([32, 32]);&lt;br /&gt;
button_out.setSizeHint([32, 32]);&lt;br /&gt;
&lt;br /&gt;
var label_zoom = canvas.gui.widgets.Label.new(ui_root, canvas.style, {});&lt;br /&gt;
&lt;br /&gt;
var button_box = canvas.HBoxLayout.new();&lt;br /&gt;
button_box.addItem(button_in);&lt;br /&gt;
button_box.addItem(label_zoom);&lt;br /&gt;
button_box.addItem(button_out);&lt;br /&gt;
button_box.addStretch(1);&lt;br /&gt;
&lt;br /&gt;
vbox.addItem(button_box);&lt;br /&gt;
vbox.addStretch(1);&lt;br /&gt;
&lt;br /&gt;
var changeZoom = func(d)&lt;br /&gt;
{&lt;br /&gt;
  zoom = math.max(2, math.min(19, zoom + d));&lt;br /&gt;
  label_zoom.setText(&amp;quot;Zoom &amp;quot; ~ zoom);&lt;br /&gt;
  updateTiles();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
# http://polymaps.org/docs/&lt;br /&gt;
# https://github.com/simplegeo/polymaps&lt;br /&gt;
# https://github.com/Leaflet/Leaflet&lt;br /&gt;
&lt;br /&gt;
var maps_base = getprop(&amp;quot;/sim/fg-home&amp;quot;) ~ '/cache/maps';&lt;br /&gt;
&lt;br /&gt;
# http://otile1.mqcdn.com/tiles/1.0.0/map&lt;br /&gt;
# http://otile1.mqcdn.com/tiles/1.0.0/sat&lt;br /&gt;
# (also see http://wiki.openstreetmap.org/wiki/Tile_usage_policy)&lt;br /&gt;
var makeUrl =&lt;br /&gt;
string.compileTemplate('https://maps.wikimedia.org/osm-{type}/{z}/{x}/{y}.png');&lt;br /&gt;
  #https://maps.wikimedia.org/osm-intl/${z}/${x}/${y}.png&lt;br /&gt;
  var makePath =&lt;br /&gt;
  string.compileTemplate(maps_base ~ '/osm-{type}/{z}/{x}/{y}.png');&lt;br /&gt;
  var num_tiles = [4, 3];&lt;br /&gt;
&lt;br /&gt;
  var center_tile_offset = [&lt;br /&gt;
  (num_tiles[0] - 1) / 2,&lt;br /&gt;
  (num_tiles[1] - 1) / 2&lt;br /&gt;
  ];&lt;br /&gt;
&lt;br /&gt;
# simple aircraft icon at current position/center of the map&lt;br /&gt;
g.createChild(&amp;quot;path&amp;quot;)&lt;br /&gt;
.moveTo( tile_size * center_tile_offset[0] - 10,&lt;br /&gt;
  tile_size * center_tile_offset[1] )&lt;br /&gt;
.horiz(20)&lt;br /&gt;
.move(-10,-10)&lt;br /&gt;
.vert(20)&lt;br /&gt;
.set(&amp;quot;stroke&amp;quot;, &amp;quot;red&amp;quot;)&lt;br /&gt;
.set(&amp;quot;stroke-width&amp;quot;, 2)&lt;br /&gt;
.set(&amp;quot;z-index&amp;quot;, 1);&lt;br /&gt;
&lt;br /&gt;
##&lt;br /&gt;
# initialize the map by setting up&lt;br /&gt;
# a grid of raster images  &lt;br /&gt;
&lt;br /&gt;
var tiles = setsize([], num_tiles[0]);&lt;br /&gt;
for(var x = 0; x &amp;lt; num_tiles[0]; x += 1)&lt;br /&gt;
{&lt;br /&gt;
  tiles[x] = setsize([], num_tiles[1]);&lt;br /&gt;
  for(var y = 0; y &amp;lt; num_tiles[1]; y += 1)&lt;br /&gt;
  tiles[x][y] = g.createChild(&amp;quot;image&amp;quot;, &amp;quot;map-tile&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var last_tile = [-1,-1];&lt;br /&gt;
var last_type = type;&lt;br /&gt;
&lt;br /&gt;
##&lt;br /&gt;
# this is the callback that will be regularly called by the timer&lt;br /&gt;
# to update the map&lt;br /&gt;
var updateTiles = func()&lt;br /&gt;
{&lt;br /&gt;
  # get current position&lt;br /&gt;
  var lat = getprop('/position/latitude-deg');&lt;br /&gt;
  var lon = getprop('/position/longitude-deg');&lt;br /&gt;
&lt;br /&gt;
  var n = math.pow(2, zoom);&lt;br /&gt;
  var offset = [&lt;br /&gt;
  n * ((lon + 180) / 360) - center_tile_offset[0],&lt;br /&gt;
  (1 - math.ln(math.tan(lat * math.pi/180) + 1 / math.cos(lat * math.pi/180)) / math.pi) / 2 * n - center_tile_offset[1]&lt;br /&gt;
  ];&lt;br /&gt;
  var tile_index = [int(offset[0]), int(offset[1])];&lt;br /&gt;
&lt;br /&gt;
  var ox = tile_index[0] - offset[0];&lt;br /&gt;
  var oy = tile_index[1] - offset[1];&lt;br /&gt;
&lt;br /&gt;
  for(var x = 0; x &amp;lt; num_tiles[0]; x += 1)&lt;br /&gt;
  for(var y = 0; y &amp;lt; num_tiles[1]; y += 1)&lt;br /&gt;
  tiles[x][y].setTranslation(int((ox + x) * tile_size + 0.5), int((oy + y) * tile_size + 0.5));&lt;br /&gt;
&lt;br /&gt;
  if(    tile_index[0] != last_tile[0]&lt;br /&gt;
    or tile_index[1] != last_tile[1]&lt;br /&gt;
    or type != last_type )&lt;br /&gt;
  {&lt;br /&gt;
    for(var x = 0; x &amp;lt; num_tiles[0]; x += 1)&lt;br /&gt;
    for(var y = 0; y &amp;lt; num_tiles[1]; y += 1)&lt;br /&gt;
    {&lt;br /&gt;
      var pos = {&lt;br /&gt;
        z: zoom,&lt;br /&gt;
        x: int(offset[0] + x),&lt;br /&gt;
        y: int(offset[1] + y),&lt;br /&gt;
        type: type&lt;br /&gt;
      };&lt;br /&gt;
&lt;br /&gt;
      (func {&lt;br /&gt;
        var img_path = makePath(pos);&lt;br /&gt;
        var tile = tiles[x][y];&lt;br /&gt;
&lt;br /&gt;
        if( io.stat(img_path) == nil )&lt;br /&gt;
        { # image not found, save in $FG_HOME&lt;br /&gt;
          var img_url = makeUrl(pos);&lt;br /&gt;
          print('requesting ' ~ img_url);&lt;br /&gt;
          http.save(img_url, img_path)&lt;br /&gt;
          .done(func {print('received image ' ~ img_path); tile.set(&amp;quot;src&amp;quot;, img_path);})&lt;br /&gt;
          .fail(func (r) print('Failed to get image ' ~ img_path ~ ' ' ~ r.status ~ ': ' ~ r.reason));&lt;br /&gt;
        }&lt;br /&gt;
        else # cached image found, reusing&lt;br /&gt;
        {&lt;br /&gt;
          print('loading ' ~ img_path);&lt;br /&gt;
          tile.set(&amp;quot;src&amp;quot;, img_path)&lt;br /&gt;
        }&lt;br /&gt;
        })();&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      last_tile = tile_index;&lt;br /&gt;
      last_type = type;&lt;br /&gt;
    }&lt;br /&gt;
  };&lt;br /&gt;
&lt;br /&gt;
##&lt;br /&gt;
# set up a timer that will invoke updateTiles at 2-second intervals&lt;br /&gt;
var update_timer = maketimer(2, updateTiles);&lt;br /&gt;
# actually start the timer&lt;br /&gt;
update_timer.start();&lt;br /&gt;
&lt;br /&gt;
##&lt;br /&gt;
# set up default zoom level&lt;br /&gt;
changeZoom(0);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
###&lt;br /&gt;
# The following lines were recently added and have not yet been tested&lt;br /&gt;
# (if in doubt, remove them)&lt;br /&gt;
window.del = func()&lt;br /&gt;
{&lt;br /&gt;
  print(&amp;quot;Cleaning up window:&amp;quot;, ,&amp;quot;\n&amp;quot;);&lt;br /&gt;
  update_timer.stop();&lt;br /&gt;
# explanation for the call() technique at: http://wiki.flightgear.org/Object_oriented_programming_in_Nasal#Making_safer_base-class_calls&lt;br /&gt;
call(canvas.Window.del, [], me);&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Adding a NavDisplay ==&lt;br /&gt;
&lt;br /&gt;
== Adding a CDU ==&lt;br /&gt;
&lt;br /&gt;
== Adding a Multi-Function Display (MFD) ==&lt;/div&gt;</summary>
		<author><name>TheEagle</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=File:Canvas_TabWidget_example.png&amp;diff=136889</id>
		<title>File:Canvas TabWidget example.png</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=File:Canvas_TabWidget_example.png&amp;diff=136889"/>
		<updated>2022-12-30T12:13:16Z</updated>

		<summary type="html">&lt;p&gt;TheEagle: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;A dialog showing the three default splash screens from $FGDATA in a tab widget, with only one being visible at a time. The user can view another by clicking on one of the three tab buttons.&lt;/div&gt;</summary>
		<author><name>TheEagle</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Canvas_snippets&amp;diff=136888</id>
		<title>Canvas snippets</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Canvas_snippets&amp;diff=136888"/>
		<updated>2022-12-30T10:01:45Z</updated>

		<summary type="html">&lt;p&gt;TheEagle: Added snippet for the new TabWidget&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Stub}}&lt;br /&gt;
{{-}}&lt;br /&gt;
This article is meant to be a collection of self-contained code snippets for doing Nasal/Canvas coding without having to spend too much time working through APIs, documentation and existing code - the idea is to provide snippets that contain comments and explicit assumptions, so that these snippets can be easily adapted by people without necessarily having to be very familiar with Nasal/Canvas coding. &lt;br /&gt;
&lt;br /&gt;
In the long term, we're hoping to grow a library of useful code snippets to cover most use-cases, while also covering more complex scenarios, e.g. using Canvas-specific helper frameworks like [[Canvas MapStructure]], the [[NavDisplay]] or the recent CDU work.&lt;br /&gt;
&lt;br /&gt;
Canvas itself is designed to be front-end agnostic, meaning that it doesn't matter where a Canvas is used/displayed - this is accomplished by so called &amp;quot;placements&amp;quot;, which determine where a Canvas texture is shown. Thus, the back-end logic can remain the same usually, so that GUI dialogs may show avionics, but also so that avionics may show GUI widgets, which also applies to HUDs, liveries and even placements within the FlightGear scenery.&lt;br /&gt;
&lt;br /&gt;
We encourage people to use the snippets listed below to get started with Nasal/Canvas coding, but also to provide feedback on extending/improving this article to make it even more accessible and useful. Equally, if you're aware of any Canvas-related efforts that may contain useful code snippets, please do feel free to add those snippets here. Even people who don't have any interest in coding itself are encouraged to get involved by helping test the snippets listed below, for which you only need to the [[Nasal Console]], and while you're at it, please also help provide/update screen shots for each code snippet.&lt;br /&gt;
&lt;br /&gt;
Contributions added to this article should ideally satisfy some requirements:&lt;br /&gt;
* be entirely self-contained&lt;br /&gt;
* contain good/clear comments&lt;br /&gt;
* have pointers/references to related code/use-cases&lt;br /&gt;
* make assumptions explicit&lt;br /&gt;
* contain screen shots for each example&lt;br /&gt;
* avoid overloaded symbol names to ensure that examples can be merged and adapted easily&lt;br /&gt;
* dependencies (svg files, images/textures etc) should always be chosen such that snippets always work using $FG_ROOT&lt;br /&gt;
* hard-coded assumptions (e.g. texture dimensions in terms of width/height etc) should be encapsulated using variables&lt;br /&gt;
 &lt;br /&gt;
{{Canvas Navigation}}&lt;br /&gt;
&lt;br /&gt;
== Creating a standalone Canvas ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[Special:UploadWizard|Upload requested]] || &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
var (width, height) = (512,512);&lt;br /&gt;
# Create a standalone Canvas (not attached to any GUI dialog/aircraft etc) &lt;br /&gt;
var myCanvas = canvas.new({&lt;br /&gt;
  &amp;quot;name&amp;quot;: &amp;quot;Livery Test&amp;quot;,   # The name is optional but allow for easier identification&lt;br /&gt;
  &amp;quot;size&amp;quot;: [width, height], # Size of the underlying texture (should be a power of 2, required) [Resolution]&lt;br /&gt;
  &amp;quot;view&amp;quot;: [width, height],  # Virtual resolution (Defines the coordinate system of the canvas [Dimensions]&lt;br /&gt;
                        # which will be stretched the size of the texture, required)&lt;br /&gt;
  &amp;quot;mipmapping&amp;quot;: 1       # Enable mipmapping (optional)&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
# set background color&lt;br /&gt;
myCanvas.set(&amp;quot;background&amp;quot;, canvas.style.getColor(&amp;quot;bg_color&amp;quot;));&lt;br /&gt;
&lt;br /&gt;
# creating the top-level/root group which will contain all other elements/group&lt;br /&gt;
var root = myCanvas.createGroup();&lt;br /&gt;
&lt;br /&gt;
# OPTIONAL: Create a Canvas dialog window to hold the canvas and show that it's working&lt;br /&gt;
# the Canvas is now standalone, i.e. continues to live once the dialog is closed!&lt;br /&gt;
var window = canvas.Window.new([width,height],&amp;quot;dialog&amp;quot;);&lt;br /&gt;
window.setCanvas(myCanvas);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Accessing the Canvas Desktop ==&lt;br /&gt;
Sometimes you may want to render to the main screen, without creating a separate Canvas window - this can be accomplished by using the Canvas desktop, note that the following example is fully self-contained, i.e. does not require any code to be added to work:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[Special:UploadWizard|Upload requested]] || &lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
var myNode = canvas.getDesktop().createChild(&amp;quot;text&amp;quot;)&lt;br /&gt;
      .setText(&amp;quot;Hello Canvas Desktop&amp;quot;)&lt;br /&gt;
      .setFontSize(25, 1.0)          # font size (in texels) and font aspect ratio&lt;br /&gt;
      .setColor(1,0,0,1)             # red, fully opaque&lt;br /&gt;
      .setAlignment(&amp;quot;center-center&amp;quot;) # how the text is aligned to where you place it&lt;br /&gt;
      .setTranslation(160, 80);     # where to place the text&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Creating Tooltips ==&lt;br /&gt;
== Creating Popups ==&lt;br /&gt;
== Creating a Canvas GUI Window ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Snippets-canvas-dialog.png|left|thumb|This is what the [[Nasal]]/[[Canvas]] snippet will look like once you pasted it into the [[Nasal Console]] and click &amp;quot;Execute&amp;quot;.]] || {{Note|This example uses so called method chaining, if you're not familiar with the concept, please see: [[Object_Oriented_Programming_with_Nasal#More_on_methods:_Chaining|Method Chaining]].}}&lt;br /&gt;
&lt;br /&gt;
{{Canvas Snippets Boilerplate}}&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Adding Raster Images ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code &lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas Snippets-raster image.png|thumb|Canvas snippet demonstrating how to load a raster image]] ||&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# path is relative to $FG_ROOT (base package)&lt;br /&gt;
var path = &amp;quot;Textures/Splash1.png&amp;quot;;&lt;br /&gt;
# create an image child for the texture&lt;br /&gt;
var child = root.createChild(&amp;quot;image&amp;quot;)&lt;br /&gt;
    .setFile(path)&lt;br /&gt;
    .setTranslation(100, 10)&lt;br /&gt;
    .setSize(130, 130);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Adding Raster Images downloaded on demand ==&lt;br /&gt;
&lt;br /&gt;
The '''path''' could also just as well be a URL, i.e. a raster image retrieved via http - for example, the following snippet is entirely self-contained and can be pasted into the [[Nasal Console]] and directly executed &amp;quot;as is&amp;quot;: &lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-raster-images-via-url.png|thumb|screen shot demonstrating how Nasal and Canvas can be used to display raster images downloaded on demand]] ||&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# create a new window, dimensions are 400 x 200, using the dialog decoration (i.e. titlebar)&lt;br /&gt;
var window = canvas.Window.new([400,200],&amp;quot;dialog&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
# adding a canvas to the new window and setting up background colors/transparency&lt;br /&gt;
var myCanvas = window.createCanvas().set(&amp;quot;background&amp;quot;, canvas.style.getColor(&amp;quot;bg_color&amp;quot;));&lt;br /&gt;
&lt;br /&gt;
# Using specific css colors would also be possible:&lt;br /&gt;
# myCanvas.set(&amp;quot;background&amp;quot;, &amp;quot;#ffaac0&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
# creating the top-level/root group which will contain all other elements/group&lt;br /&gt;
var root = myCanvas.createGroup();&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# path now a URL&lt;br /&gt;
var url = &amp;quot;http://www.worldwidetelescope.org/docs/Images/MapOfEarth.jpg&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
# create an image child for the texture&lt;br /&gt;
var child=root.createChild(&amp;quot;image&amp;quot;)&lt;br /&gt;
    .setFile( url ) &lt;br /&gt;
    .setTranslation(45,22) # centered, in relation to dialog coordinates&lt;br /&gt;
    .setSize(310,155); # image dimensions&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Clipping ==&lt;br /&gt;
{{Note|{{FGCquote&lt;br /&gt;
|1= Scaling or any other type of transformation or changing the coordinates of individual points is definitely more efficient than clipping (which requires to change the OpenGL clip planes for every rendered object with a different clipping rectangle).&lt;br /&gt;
|2= {{cite web&lt;br /&gt;
  | url    = http://forum.flightgear.org/viewtopic.php?p=277062#p277062&lt;br /&gt;
  | title  = &amp;lt;nowiki&amp;gt;Re: Space Shuttle&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
  | author = &amp;lt;nowiki&amp;gt;TheTom&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
  | date   = Feb 21st, 2016&lt;br /&gt;
  | added   = Feb 21st, 2016&lt;br /&gt;
  | script_version = 0.25&lt;br /&gt;
  }}&lt;br /&gt;
}}&lt;br /&gt;
}}&lt;br /&gt;
I would suggest to refer to api.nas and look for &amp;quot;clip&amp;quot; and/or &amp;quot;rect&amp;quot; - IIRC, you need to set up a clipping rectangle by setting some kind of &amp;quot;clip&amp;quot; property and setting it to a rect value in the form of rect(...)&lt;br /&gt;
{{FGCquote&lt;br /&gt;
|1= For details, see the clipping example at: [[Canvas Nasal API#set 2]] and  [[Canvas Element#clip.28.29]]&lt;br /&gt;
|2= {{cite web&lt;br /&gt;
  | url    = http://forum.flightgear.org/viewtopic.php?p=276965#p276965&lt;br /&gt;
  | title  = &amp;lt;nowiki&amp;gt;Re: Space Shuttle&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
  | author = &amp;lt;nowiki&amp;gt;Hooray&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
  | date   = Feb 21st, 2016&lt;br /&gt;
  | added   = Feb 21st, 2016&lt;br /&gt;
  | script_version = 0.25&lt;br /&gt;
  }}&lt;br /&gt;
}}&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:MapStructure_dialog_with_clipping_and_event_handling_applied.png|thumb]] ||&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
# create a new window, dimensions are 400 x 200, using the dialog decoration (i.e. titlebar)&lt;br /&gt;
var window = canvas.Window.new([400,200],&amp;quot;dialog&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
# adding a canvas to the new window and setting up background colors/transparency&lt;br /&gt;
var myCanvas = window.createCanvas().set(&amp;quot;background&amp;quot;, canvas.style.getColor(&amp;quot;bg_color&amp;quot;));&lt;br /&gt;
&lt;br /&gt;
# Using specific css colors would also be possible:&lt;br /&gt;
# myCanvas.set(&amp;quot;background&amp;quot;, &amp;quot;#ffaac0&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
# creating the top-level/root group which will contain all other elements/group&lt;br /&gt;
var root = myCanvas.createGroup();&lt;br /&gt;
&lt;br /&gt;
# create an image child for the texture&lt;br /&gt;
var child=root.createChild(&amp;quot;.........&amp;quot;)&lt;br /&gt;
    .setFile( url ) &lt;br /&gt;
    .setTranslation(45,22) # centered, in relation to dialog coordinates&lt;br /&gt;
    .setSize(310,155); # image dimensions&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Adding OpenVG Paths ==&lt;br /&gt;
{{Main article|How to manipulate Canvas elements}}&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Openvg-via-canvas.png|thumb|screen shot showing a simple Canvas GUI dialog demonstrating how to use OpenVG-path drawing via Nasal and Canvas]] &lt;br /&gt;
| {{Note|This assumes that you already have a top-level root group set up, and named it '''root'''.}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
var graph = root.createChild(&amp;quot;group&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
var x_axis = graph.createChild(&amp;quot;path&amp;quot;, &amp;quot;x-axis&amp;quot;)&lt;br /&gt;
.moveTo(10, height/2)&lt;br /&gt;
.lineTo(width-10, height/2)&lt;br /&gt;
.setColor(1,0,0)&lt;br /&gt;
.setStrokeLineWidth(3);&lt;br /&gt;
&lt;br /&gt;
var y_axis = graph.createChild(&amp;quot;path&amp;quot;, &amp;quot;y-axis&amp;quot;)&lt;br /&gt;
.moveTo(10, 10)&lt;br /&gt;
.lineTo(10, height-10)&lt;br /&gt;
.setColor(0,0,1)&lt;br /&gt;
.setStrokeLineWidth(2);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-openvg-quadTo.png|thumb|screen shot showing a simple Canvas GUI dialog demonstrating how to use OpenVG-path drawing via Nasal and Canvas (Canvas/OpenVG quadTo API for drawing curves)]] &lt;br /&gt;
| {{Note|This assumes that you are appending this to the snippet shown above.}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
var points = [	60,height-20, &lt;br /&gt;
		230,height-100,&lt;br /&gt;
		];&lt;br /&gt;
&lt;br /&gt;
var track = graph.createChild(&amp;quot;path&amp;quot;, &amp;quot;track&amp;quot;)&lt;br /&gt;
.moveTo(10, height/2)&lt;br /&gt;
.quadTo( points )&lt;br /&gt;
.setColor(0,1,0)&lt;br /&gt;
.setStrokeLineWidth(4);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-openvg-cubicTo.png|thumb|screen shot showing a simple Canvas GUI dialog demonstrating how to use OpenVG-path drawing via Nasal and Canvas (Canvas/OpenVG cubicTo API for drawing curves)]] &lt;br /&gt;
| {{Note|This assumes that you are appending this to the snippet shown above.}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
var points = [	60,height-20, &lt;br /&gt;
		120,height-120,&lt;br /&gt;
		230,height-100,&lt;br /&gt;
		];&lt;br /&gt;
&lt;br /&gt;
var track = graph.createChild(&amp;quot;path&amp;quot;, &amp;quot;track&amp;quot;)&lt;br /&gt;
.moveTo(10, height/2)&lt;br /&gt;
.cubicTo( points )&lt;br /&gt;
.setColor(0,1,0)&lt;br /&gt;
.setStrokeLineWidth(4);&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Adding Vector Images ==&lt;br /&gt;
&lt;br /&gt;
[[Category:Canvas SVG]]&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-svg-support.png|thumb|screen shot demonstrating how the scripted Nasal-based SVG parser can be used to dynamically turn SVG files into OpenVG instructions understood by Canvas]] &lt;br /&gt;
| {{Note|This assumes that you already have a top-level root group set up, and named it '''root'''.}}&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
# change the background color &lt;br /&gt;
myCanvas.set(&amp;quot;background&amp;quot;, &amp;quot;#ffaac0&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
# creating the top-level/root group which will contain all other elements/group&lt;br /&gt;
var root = myCanvas.createGroup();&lt;br /&gt;
&lt;br /&gt;
var filename = &amp;quot;/Nasal/canvas/map/Images/boeingAirplane.svg&amp;quot;;&lt;br /&gt;
var svg_symbol = root.createChild('group');&lt;br /&gt;
canvas.parsesvg(svg_symbol, filename);&lt;br /&gt;
&lt;br /&gt;
svg_symbol.setTranslation(width/2,height/2);&lt;br /&gt;
&lt;br /&gt;
#svg_symbol.setScale(0.2);&lt;br /&gt;
#svg_symbol.setRotation(radians)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Using the SymbolCache ==&lt;br /&gt;
Whenever a symbol may need to be shown/instanced multiple times (possibly using different styling), it makes sense to use caching - otherwise, identical symbols would be treated as separate OpenVG groups, all of which would need to be rasterized/rendedered separately (i.e. 100 identical symbols would be updated/rendered one by one). &lt;br /&gt;
&lt;br /&gt;
Typically, a map may display multiple instances of an otherwise identical symbol (think VOR, NDB, DME etc) - equally, a multiplayer map may showing multiple aircraft symbols at the same time. In these cases, it makes sense to use the SymbolCache framework, which will render symbols into a separate Canvas texture and provide a texture map that can be treated as a lookup map, which even supports styling for otherwise identical symbols. To learn more, please refer to [[Canvas MapStructure#The_SymbolCache|SymbolCache]]&lt;br /&gt;
... &lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-SymbolCache-Instancing.png|thumb|Screen shot showing a Canvas based GUI dialog that is using the SymbolCache for instancing multiple symbols (including support for styling)]] &lt;br /&gt;
| &amp;lt;!--{{Caution|This is currently untested code}}--&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
##&lt;br /&gt;
# this is the function that draws a symbol using OpenVG paths&lt;br /&gt;
# it accepts a group to draw to and returns the rendered group &lt;br /&gt;
# to the caller&lt;br /&gt;
var drawVOR = func(group) {&lt;br /&gt;
    return group.createChild(&amp;quot;path&amp;quot;)&lt;br /&gt;
        .moveTo(-15,0)&lt;br /&gt;
        .lineTo(-7.5,12.5)&lt;br /&gt;
        .lineTo(7.5,12.5)&lt;br /&gt;
        .lineTo(15,0)&lt;br /&gt;
        .lineTo(7.5,-12.5)&lt;br /&gt;
        .lineTo(-7.5,-12.5)&lt;br /&gt;
        .close()&lt;br /&gt;
        .setStrokeLineWidth(line_width) # style-able&lt;br /&gt;
        .setColor(color); # style-able&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
var style = { # styling related attributes (as per the draw* function above)&lt;br /&gt;
    line_width: 3,&lt;br /&gt;
    scale_factor: 1,&lt;br /&gt;
    color: [1,0,0],&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
# create a new cache entry for the styled symbol&lt;br /&gt;
var myCachedSymbol = canvas.StyleableCacheable.new(&lt;br /&gt;
    name:'myVOR', draw_func: drawVOR,&lt;br /&gt;
    cache: canvas.SymbolCache32x32, # the cache to be used&lt;br /&gt;
    draw_mode: canvas.SymbolCache.DRAW_CENTERED,&lt;br /&gt;
    relevant_keys: ['line_width', 'color'], # styling related attributes&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
var target = root.createChild('group');&lt;br /&gt;
&lt;br /&gt;
var x=0;&lt;br /&gt;
var y=height/2;&lt;br /&gt;
&lt;br /&gt;
var xoffset=50;&lt;br /&gt;
&lt;br /&gt;
# render 5 instanced symbols using the style specified above&lt;br /&gt;
for (var i=0;i&amp;lt;5;i+=1) {&lt;br /&gt;
# look up the raster image for the symbol&lt;br /&gt;
# render it using the passed style and adjust scaling&lt;br /&gt;
var instanced = myCachedSymbol.render(target, style)&lt;br /&gt;
  .setScale(style.scale_factor)&lt;br /&gt;
  .setTranslation(x+=xoffset,y);&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The example shown above uses a fixed symbol/icon cache that is set up while booting Flightgear - sometimes, we may need cache for different purposes. So, let's assume, we need a new/custom cache with a different resolution for each entry in the cache (e.g. 256x256), we can easily accomplish that by setting up a new cache like this: &lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot;&amp;gt;&lt;br /&gt;
var mySymbolCache256x256 = canvas.SymbolCache.new(1024,256);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Adding Text Elements ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:CanvasText-Hello-World.png|thumb|Screen shot showing the CanvasText example contributed by Necolatis]]&lt;br /&gt;
| {{Note|This assumes that you already have a top-level root group set up, and named it '''root'''.}}&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
var myText = root.createChild(&amp;quot;text&amp;quot;)&lt;br /&gt;
      .setText(&amp;quot;Hello world!&amp;quot;)&lt;br /&gt;
      .setFontSize(20, 0.9)          # font size (in texels) and font aspect ratio&lt;br /&gt;
      .setColor(1,0,0,1)             # red, fully opaque&lt;br /&gt;
      .setAlignment(&amp;quot;center-center&amp;quot;) # how the text is aligned to where you place it&lt;br /&gt;
      .setTranslation(160, 80);     # where to place the text&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Adding GUI Labels ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-Layout-Label-example-by-Necolatis.png|thumb|Canvas demo: Layouts and Labels (by Necolatis)]]&lt;br /&gt;
| {{Note|This assumes that you already have a top-level root group set up, and named it '''root''', just change this variable if you want it to be rendered elsewhere. It also assumes you have a Layout item setup and called '''myLayoutItem'''.}}&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
# create a new layout&lt;br /&gt;
var myLayout = canvas.HBoxLayout.new();&lt;br /&gt;
# assign it to the Canvas&lt;br /&gt;
myCanvas.setLayout(myLayout);&lt;br /&gt;
&lt;br /&gt;
var label = canvas.gui.widgets.Label.new(root, canvas.style, {wordWrap: 0}); # wordwrap: 0 will disable wordwrapping, to enable it use 1 instead&lt;br /&gt;
label.setText(&amp;quot;Hello World!&amp;quot;);&lt;br /&gt;
myLayout.addItem(label);&lt;br /&gt;
&lt;br /&gt;
var label2 = canvas.gui.widgets.Label.new(root, canvas.style, {wordWrap: 0}); # wordwrap: 0 will disable wordwrapping, to enable it use 1 instead&lt;br /&gt;
label2.setText(&amp;quot;Hello FlightGear&amp;quot;);&lt;br /&gt;
myLayout.addItem(label2);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Adding GUI Buttons (Layouts)==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-demo-layouts-and-buttons-by-Necolatis.png|thumb|Canvas snippet: buttons and layouts (by Necolatis)]] &lt;br /&gt;
|&lt;br /&gt;
&lt;br /&gt;
{{Note|This assumes that you already have a top-level root group set up, and named it '''root''', just change this variable if you want it to be rendered elsewhere. It also assumes you have a Layout item setup and called '''myLayoutItem'''.}}&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
# create a new layout&lt;br /&gt;
var myLayout = canvas.HBoxLayout.new();&lt;br /&gt;
# assign it to the Canvas&lt;br /&gt;
myCanvas.setLayout(myLayout);&lt;br /&gt;
&lt;br /&gt;
# click button&lt;br /&gt;
&lt;br /&gt;
var button = canvas.gui.widgets.Button.new(root, canvas.style, {})&lt;br /&gt;
	.setText(&amp;quot;Click on me&amp;quot;)&lt;br /&gt;
	.setFixedSize(75, 25);&lt;br /&gt;
&lt;br /&gt;
button.listen(&amp;quot;clicked&amp;quot;, func {&lt;br /&gt;
        # add code here to react on click on button.&lt;br /&gt;
print(&amp;quot;Button clicked !&amp;quot;);&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
myLayout.addItem(button);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-toggle-button-snippet-by-Necolatis.png|thumb|Canvas toggle button demo by Necolatis]]&lt;br /&gt;
| &amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
# create a new layout&lt;br /&gt;
var myLayout = canvas.HBoxLayout.new();&lt;br /&gt;
# assign it to the Canvas&lt;br /&gt;
myCanvas.setLayout(myLayout);&lt;br /&gt;
&lt;br /&gt;
var button = canvas.gui.widgets.Button.new(root, canvas.style, {})&lt;br /&gt;
        .setText(&amp;quot;Toggle me&amp;quot;)&lt;br /&gt;
        .setCheckable(1) # this indicates that is should be a toggle button&lt;br /&gt;
        .setChecked(0) # depressed by default&lt;br /&gt;
        .setFixedSize(75, 25);&lt;br /&gt;
&lt;br /&gt;
button.listen(&amp;quot;toggled&amp;quot;, func (e) {&lt;br /&gt;
        if( e.detail.checked ) {&lt;br /&gt;
            # add code here to react on button being depressed.&lt;br /&gt;
        } else {&lt;br /&gt;
            # add code here to react on button not being depressed.&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
myLayout.addItem(button);&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Canvas Input Dialog ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
|[[File:Snippets-canvas-input-dialog.png|thumb|Canvas input dialog]]&lt;br /&gt;
||&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
# create a new InputDialog with a title, label, and a callback&lt;br /&gt;
canvas.InputDialog.getText(&amp;quot;Input Dialog Title&amp;quot;, &amp;quot;Please enter some text&amp;quot;, func(btn,value) {&lt;br /&gt;
    if (value) gui.popupTip(&amp;quot;You entered: &amp;quot;~value);&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Canvas ScrollArea ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code&lt;br /&gt;
|-&lt;br /&gt;
|[[File:Canvas-snippets-scrollArea-demo.png|thumb|Screen shot showing a Canvas ScrollArea populated with different splash screens, loaded from $FG_ROOT/Textures]]&lt;br /&gt;
||&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
var vbox = canvas.VBoxLayout.new();&lt;br /&gt;
myCanvas.setLayout(vbox);&lt;br /&gt;
&lt;br /&gt;
 var scroll = canvas.gui.widgets.ScrollArea.new(root, canvas.style, {size: [96, 128]}).move(20, 100);&lt;br /&gt;
 vbox.addItem(scroll, 1);&lt;br /&gt;
&lt;br /&gt;
var scrollContent =&lt;br /&gt;
      scroll.getContent()&lt;br /&gt;
            .set(&amp;quot;font&amp;quot;, &amp;quot;LiberationFonts/LiberationSans-Bold.ttf&amp;quot;)&lt;br /&gt;
            .set(&amp;quot;character-size&amp;quot;, 16)&lt;br /&gt;
            .set(&amp;quot;alignment&amp;quot;, &amp;quot;left-center&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
var list = canvas.VBoxLayout.new();&lt;br /&gt;
scroll.setLayout(list);&lt;br /&gt;
&lt;br /&gt;
for (var i=1;i&amp;lt;=5;i+=1) {&lt;br /&gt;
var label = canvas.gui.widgets.Label.new(scrollContent, canvas.style, {wordWrap: 0}); &lt;br /&gt;
label.setImage(&amp;quot;Textures/Splash&amp;quot;~i~&amp;quot;.png&amp;quot;);&lt;br /&gt;
label.setFixedSize(256,256);&lt;br /&gt;
list.addItem(label);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Using TabWidgets ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|+&lt;br /&gt;
!Screenshot&lt;br /&gt;
!Code&lt;br /&gt;
|-&lt;br /&gt;
|Will be added shortly …&lt;br /&gt;
|&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
window = canvas.Window.new([300, 300], &amp;quot;dialog&amp;quot;);&lt;br /&gt;
myCanvas = window.createCanvas();&lt;br /&gt;
root = myCanvas.createGroup();&lt;br /&gt;
var vbox = canvas.VBoxLayout.new();&lt;br /&gt;
myCanvas.setLayout(vbox);&lt;br /&gt;
&lt;br /&gt;
var tabs = canvas.gui.widgets.TabWidget.new(root, canvas.style, {});&lt;br /&gt;
var tabsContent = tabs.getContent();&lt;br /&gt;
vbox.addItem(tabs);&lt;br /&gt;
for (var i=1; i&amp;lt;=5; i+=1) {&lt;br /&gt;
    var tab = canvas.VBoxLayout.new();&lt;br /&gt;
    var image = canvas.gui.widgets.Label.new(tabsContent, canvas.style, {}); &lt;br /&gt;
    image.setImage(&amp;quot;Textures/Splash&amp;quot; ~ i ~ &amp;quot;.png&amp;quot;);&lt;br /&gt;
    image.setFixedSize(128, 128);&lt;br /&gt;
    tab.addItem(image);&lt;br /&gt;
    var text = canvas.gui.widgets.Label.new(tabsContent, canvas.style, {})&lt;br /&gt;
       .setText(&amp;quot;Texture &amp;quot; ~ i);&lt;br /&gt;
    tab.addItem(text);&lt;br /&gt;
    tabs.addTab(&amp;quot;tab&amp;quot; ~ i, &amp;quot;Texture &amp;quot; ~ i, tab);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Using Layouts ==&lt;br /&gt;
&lt;br /&gt;
== Using Styling ==&lt;br /&gt;
== Adding a HUD ==&lt;br /&gt;
== Adding a 2D Instrument ==&lt;br /&gt;
== Adding a 2D Panel ==&lt;br /&gt;
== Adding a PFD ==&lt;br /&gt;
&lt;br /&gt;
== Adding a Failure Mgmt Widget ==&lt;br /&gt;
{{Note|This kind of widget will typically be useful for dialogs requiring a method for managing aircraft specific system failures (e.g. an instructor console).}}&lt;br /&gt;
&lt;br /&gt;
== Adding a MapStructure map to a Canvas  ==&lt;br /&gt;
{{Main article|Canvas MapStructure Layers}}&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code &lt;br /&gt;
|-&lt;br /&gt;
| [[File:Snippets-canvas-mapstructure-dialog.png|thumb|[[MapStructure]] layers shown in a Canvas GUI dialog]]&lt;br /&gt;
| {{Note|This assumes that you already have a top-level root group set up, and named it '''root''', just change this variable if you want it to be rendered elsewhere. Equally, this snippet assumes that your canvas is named '''myCanvas'''.}}&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
var TestMap = root.createChild(&amp;quot;map&amp;quot;);&lt;br /&gt;
TestMap.setController(&amp;quot;Aircraft position&amp;quot;);&lt;br /&gt;
TestMap.setRange(25); &lt;br /&gt;
 &lt;br /&gt;
TestMap.setTranslation(    myCanvas.get(&amp;quot;view[0]&amp;quot;)/2,&lt;br /&gt;
                           myCanvas.get(&amp;quot;view[1]&amp;quot;)/2&lt;br /&gt;
                        );&lt;br /&gt;
var r = func(name,vis=1,zindex=nil) return caller(0)[0];&lt;br /&gt;
&lt;br /&gt;
# APT and VOR are the layer names&lt;br /&gt;
foreach(var type; [r('APT'), r('VOR') ] )&lt;br /&gt;
 TestMap.addLayer(factory: canvas.SymbolLayer, type_arg: type.name, visible: type.vis, priority: type.zindex,);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Customizing MapStructure Styling  ==&lt;br /&gt;
{{See also|Canvas MapStructure#Styling}}&lt;br /&gt;
&lt;br /&gt;
In general MapStructure symbols contain their own styles using hard-coded defaults, however these can be overridden by providing a hash with keys (fields) to customize these hard-coded defaults. &lt;br /&gt;
&lt;br /&gt;
This means that anything that may be specific to a single style (colors, fonts, images etc) should be encoded in the form of variables that are looked up using the styles hash - this provides a great deal of freedom to customize an existing symbol. In addition, MapStructure layers can be set up to even customize/override the default drawing routines, at which point you are free to do whatever you want basically, because the existing draw routine in .symbol file is ignored &lt;br /&gt;
&lt;br /&gt;
{{WIP}}&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code &lt;br /&gt;
|-&lt;br /&gt;
| [[File:Layers style change.png|thumb|[[MapStructure]] layers shown in a Canvas GUI dialog. Active VOR and radial in red color, inactive VOR in green color]]&lt;br /&gt;
| {{Note|This assumes that you already have a top-level root group set up, and named it '''root''', just change this variable if you want it to be rendered elsewhere. Equally, this snippet assumes that your canvas is named '''myCanvas'''.}}&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
var TestMap = root.createChild(&amp;quot;map&amp;quot;);&lt;br /&gt;
TestMap.setController(&amp;quot;Aircraft position&amp;quot;);&lt;br /&gt;
TestMap.setRange(25); &lt;br /&gt;
 &lt;br /&gt;
TestMap.setTranslation(    myCanvas.get(&amp;quot;view[0]&amp;quot;)/2,&lt;br /&gt;
                           myCanvas.get(&amp;quot;view[1]&amp;quot;)/2&lt;br /&gt;
                        );&lt;br /&gt;
var r = func(name,vis=1,zindex=nil) return caller(0)[0];&lt;br /&gt;
var type = r('APT');&lt;br /&gt;
# a hash that contains variables that are supported by the relevant_keys vector in APT.symbol&lt;br /&gt;
var style_apt = {&lt;br /&gt;
    scale_factor:0.5, # 50 %&lt;br /&gt;
    color_default:[0,1,0.9], # rgb&lt;br /&gt;
    line_width:4, # thickness&lt;br /&gt;
    label_font_color:[0,1,0.9], # rgb&lt;br /&gt;
    label_font_size:30 # font size&lt;br /&gt;
};&lt;br /&gt;
TestMap.addLayer(factory: canvas.SymbolLayer, type_arg: type.name, visible: type.vis, priority: type.zindex,style:style_apt);&lt;br /&gt;
&lt;br /&gt;
var type = r('VOR');&lt;br /&gt;
# a hash that contains variables that are supported by the relevant_keys vector in VOR.symbol&lt;br /&gt;
var style_vor = {&lt;br /&gt;
    scale_factor:0.6,&lt;br /&gt;
    active_color:[1,0,0],&lt;br /&gt;
    inactive_color:[0,1,0],&lt;br /&gt;
    line_width:4&lt;br /&gt;
};&lt;br /&gt;
TestMap.addLayer(factory: canvas.SymbolLayer, type_arg: type.name, visible: type.vis, priority: type.zindex,style:style_vor);&lt;br /&gt;
&lt;br /&gt;
### See $FG_ROOT/Nasal/canvas/map/APT and $FG_ROOT/Nasal/canvas/map/VOR.sy,bolfor the style variables that can be configured.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Using MapStructure and Overlays ==&lt;br /&gt;
{{FGCquote&lt;br /&gt;
  |Support for overlays using geo-referenced raster images isn't too well-developed currently, so would need to work with a few hard-coded assumptions (e.g. being specific to a certain raster image and map layout) - but maybe TheTom can provide a few more ideas on how to proceed from here - otherwise, the Map is normally not aware of any non-Map items, i.e. as long as the overlay is added &amp;quot;outside&amp;quot; the Map, it isn't even aware of the raster image - and when it is added as a map element, it would probably be rotated. So there's still some work needed here. But generally, this should work well enough even in its current form (the screen shot is purely based on the Canvas Snippets article, i.e. it just displays a Canvas GUI dialog, adds the downloaded raster image and then adds the MapStructure APS layer - without the Map being aware of the overlay/geo-referencing that is needed currently).&lt;br /&gt;
  |{{cite web |url=http://forum.flightgear.org/viewtopic.php?p=238316#p238316&lt;br /&gt;
     |title=&amp;lt;nowiki&amp;gt;Re: Using Canvas for visualizing orbital flights (cont'd PM)&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |author=&amp;lt;nowiki&amp;gt;Hooray&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
     |date=&amp;lt;nowiki&amp;gt;Fri Apr 10&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
   }}&lt;br /&gt;
}}&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code &lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas-overlay-with-mapstructure.png|thumb|Using the [[MapStructure]] framework in conjunction with raster images as overlays]]&lt;br /&gt;
| {{Note|This assumes that you already have a top-level root group set up, and named it '''root''', just change this variable if you want it to be rendered elsewhere. Equally, this snippet assumes that your canvas is named '''myCanvas'''.}}&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== A simple tile map ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Screenshot !! Code &lt;br /&gt;
|-&lt;br /&gt;
| [[File:Canvas - Tile map demo.png|thumb|A simple, canvas based tile map which is centered around the aircraft.]]&lt;br /&gt;
| &lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;nasal&amp;quot; enclose=&amp;quot;div&amp;quot;&amp;gt;&lt;br /&gt;
var (width,height) = (768,512);&lt;br /&gt;
var tile_size = 256;&lt;br /&gt;
&lt;br /&gt;
var window = canvas.Window.new([width, height],&amp;quot;dialog&amp;quot;).set('title', &amp;quot;Tile map demo&amp;quot;);&lt;br /&gt;
var g = window.getCanvas(1).createGroup();&lt;br /&gt;
&lt;br /&gt;
# Simple user interface (Buttons for zoom and label for displaying it)&lt;br /&gt;
var zoom = 10;&lt;br /&gt;
var type = &amp;quot;intl&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
var ui_root = window.getCanvas().createGroup();&lt;br /&gt;
var vbox = canvas.VBoxLayout.new();&lt;br /&gt;
window.setLayout(vbox);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
var button_in = canvas.gui.widgets.Button.new(ui_root, canvas.style, {}).setText(&amp;quot;+&amp;quot;).listen(&amp;quot;clicked&amp;quot;, func changeZoom(1));&lt;br /&gt;
var button_out = canvas.gui.widgets.Button.new(ui_root, canvas.style, {}).setText(&amp;quot;-&amp;quot;).listen(&amp;quot;clicked&amp;quot;, func changeZoom(-1));&lt;br /&gt;
button_in.setSizeHint([32, 32]);&lt;br /&gt;
button_out.setSizeHint([32, 32]);&lt;br /&gt;
&lt;br /&gt;
var label_zoom = canvas.gui.widgets.Label.new(ui_root, canvas.style, {});&lt;br /&gt;
&lt;br /&gt;
var button_box = canvas.HBoxLayout.new();&lt;br /&gt;
button_box.addItem(button_in);&lt;br /&gt;
button_box.addItem(label_zoom);&lt;br /&gt;
button_box.addItem(button_out);&lt;br /&gt;
button_box.addStretch(1);&lt;br /&gt;
&lt;br /&gt;
vbox.addItem(button_box);&lt;br /&gt;
vbox.addStretch(1);&lt;br /&gt;
&lt;br /&gt;
var changeZoom = func(d)&lt;br /&gt;
{&lt;br /&gt;
  zoom = math.max(2, math.min(19, zoom + d));&lt;br /&gt;
  label_zoom.setText(&amp;quot;Zoom &amp;quot; ~ zoom);&lt;br /&gt;
  updateTiles();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
# http://polymaps.org/docs/&lt;br /&gt;
# https://github.com/simplegeo/polymaps&lt;br /&gt;
# https://github.com/Leaflet/Leaflet&lt;br /&gt;
&lt;br /&gt;
var maps_base = getprop(&amp;quot;/sim/fg-home&amp;quot;) ~ '/cache/maps';&lt;br /&gt;
&lt;br /&gt;
# http://otile1.mqcdn.com/tiles/1.0.0/map&lt;br /&gt;
# http://otile1.mqcdn.com/tiles/1.0.0/sat&lt;br /&gt;
# (also see http://wiki.openstreetmap.org/wiki/Tile_usage_policy)&lt;br /&gt;
var makeUrl =&lt;br /&gt;
string.compileTemplate('https://maps.wikimedia.org/osm-{type}/{z}/{x}/{y}.png');&lt;br /&gt;
  #https://maps.wikimedia.org/osm-intl/${z}/${x}/${y}.png&lt;br /&gt;
  var makePath =&lt;br /&gt;
  string.compileTemplate(maps_base ~ '/osm-{type}/{z}/{x}/{y}.png');&lt;br /&gt;
  var num_tiles = [4, 3];&lt;br /&gt;
&lt;br /&gt;
  var center_tile_offset = [&lt;br /&gt;
  (num_tiles[0] - 1) / 2,&lt;br /&gt;
  (num_tiles[1] - 1) / 2&lt;br /&gt;
  ];&lt;br /&gt;
&lt;br /&gt;
# simple aircraft icon at current position/center of the map&lt;br /&gt;
g.createChild(&amp;quot;path&amp;quot;)&lt;br /&gt;
.moveTo( tile_size * center_tile_offset[0] - 10,&lt;br /&gt;
  tile_size * center_tile_offset[1] )&lt;br /&gt;
.horiz(20)&lt;br /&gt;
.move(-10,-10)&lt;br /&gt;
.vert(20)&lt;br /&gt;
.set(&amp;quot;stroke&amp;quot;, &amp;quot;red&amp;quot;)&lt;br /&gt;
.set(&amp;quot;stroke-width&amp;quot;, 2)&lt;br /&gt;
.set(&amp;quot;z-index&amp;quot;, 1);&lt;br /&gt;
&lt;br /&gt;
##&lt;br /&gt;
# initialize the map by setting up&lt;br /&gt;
# a grid of raster images  &lt;br /&gt;
&lt;br /&gt;
var tiles = setsize([], num_tiles[0]);&lt;br /&gt;
for(var x = 0; x &amp;lt; num_tiles[0]; x += 1)&lt;br /&gt;
{&lt;br /&gt;
  tiles[x] = setsize([], num_tiles[1]);&lt;br /&gt;
  for(var y = 0; y &amp;lt; num_tiles[1]; y += 1)&lt;br /&gt;
  tiles[x][y] = g.createChild(&amp;quot;image&amp;quot;, &amp;quot;map-tile&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var last_tile = [-1,-1];&lt;br /&gt;
var last_type = type;&lt;br /&gt;
&lt;br /&gt;
##&lt;br /&gt;
# this is the callback that will be regularly called by the timer&lt;br /&gt;
# to update the map&lt;br /&gt;
var updateTiles = func()&lt;br /&gt;
{&lt;br /&gt;
  # get current position&lt;br /&gt;
  var lat = getprop('/position/latitude-deg');&lt;br /&gt;
  var lon = getprop('/position/longitude-deg');&lt;br /&gt;
&lt;br /&gt;
  var n = math.pow(2, zoom);&lt;br /&gt;
  var offset = [&lt;br /&gt;
  n * ((lon + 180) / 360) - center_tile_offset[0],&lt;br /&gt;
  (1 - math.ln(math.tan(lat * math.pi/180) + 1 / math.cos(lat * math.pi/180)) / math.pi) / 2 * n - center_tile_offset[1]&lt;br /&gt;
  ];&lt;br /&gt;
  var tile_index = [int(offset[0]), int(offset[1])];&lt;br /&gt;
&lt;br /&gt;
  var ox = tile_index[0] - offset[0];&lt;br /&gt;
  var oy = tile_index[1] - offset[1];&lt;br /&gt;
&lt;br /&gt;
  for(var x = 0; x &amp;lt; num_tiles[0]; x += 1)&lt;br /&gt;
  for(var y = 0; y &amp;lt; num_tiles[1]; y += 1)&lt;br /&gt;
  tiles[x][y].setTranslation(int((ox + x) * tile_size + 0.5), int((oy + y) * tile_size + 0.5));&lt;br /&gt;
&lt;br /&gt;
  if(    tile_index[0] != last_tile[0]&lt;br /&gt;
    or tile_index[1] != last_tile[1]&lt;br /&gt;
    or type != last_type )&lt;br /&gt;
  {&lt;br /&gt;
    for(var x = 0; x &amp;lt; num_tiles[0]; x += 1)&lt;br /&gt;
    for(var y = 0; y &amp;lt; num_tiles[1]; y += 1)&lt;br /&gt;
    {&lt;br /&gt;
      var pos = {&lt;br /&gt;
        z: zoom,&lt;br /&gt;
        x: int(offset[0] + x),&lt;br /&gt;
        y: int(offset[1] + y),&lt;br /&gt;
        type: type&lt;br /&gt;
      };&lt;br /&gt;
&lt;br /&gt;
      (func {&lt;br /&gt;
        var img_path = makePath(pos);&lt;br /&gt;
        var tile = tiles[x][y];&lt;br /&gt;
&lt;br /&gt;
        if( io.stat(img_path) == nil )&lt;br /&gt;
        { # image not found, save in $FG_HOME&lt;br /&gt;
          var img_url = makeUrl(pos);&lt;br /&gt;
          print('requesting ' ~ img_url);&lt;br /&gt;
          http.save(img_url, img_path)&lt;br /&gt;
          .done(func {print('received image ' ~ img_path); tile.set(&amp;quot;src&amp;quot;, img_path);})&lt;br /&gt;
          .fail(func (r) print('Failed to get image ' ~ img_path ~ ' ' ~ r.status ~ ': ' ~ r.reason));&lt;br /&gt;
        }&lt;br /&gt;
        else # cached image found, reusing&lt;br /&gt;
        {&lt;br /&gt;
          print('loading ' ~ img_path);&lt;br /&gt;
          tile.set(&amp;quot;src&amp;quot;, img_path)&lt;br /&gt;
        }&lt;br /&gt;
        })();&lt;br /&gt;
      }&lt;br /&gt;
&lt;br /&gt;
      last_tile = tile_index;&lt;br /&gt;
      last_type = type;&lt;br /&gt;
    }&lt;br /&gt;
  };&lt;br /&gt;
&lt;br /&gt;
##&lt;br /&gt;
# set up a timer that will invoke updateTiles at 2-second intervals&lt;br /&gt;
var update_timer = maketimer(2, updateTiles);&lt;br /&gt;
# actually start the timer&lt;br /&gt;
update_timer.start();&lt;br /&gt;
&lt;br /&gt;
##&lt;br /&gt;
# set up default zoom level&lt;br /&gt;
changeZoom(0);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
###&lt;br /&gt;
# The following lines were recently added and have not yet been tested&lt;br /&gt;
# (if in doubt, remove them)&lt;br /&gt;
window.del = func()&lt;br /&gt;
{&lt;br /&gt;
  print(&amp;quot;Cleaning up window:&amp;quot;, ,&amp;quot;\n&amp;quot;);&lt;br /&gt;
  update_timer.stop();&lt;br /&gt;
# explanation for the call() technique at: http://wiki.flightgear.org/Object_oriented_programming_in_Nasal#Making_safer_base-class_calls&lt;br /&gt;
call(canvas.Window.del, [], me);&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Adding a NavDisplay ==&lt;br /&gt;
&lt;br /&gt;
== Adding a CDU ==&lt;br /&gt;
&lt;br /&gt;
== Adding a Multi-Function Display (MFD) ==&lt;/div&gt;</summary>
		<author><name>TheEagle</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Canvas_GUI_API&amp;diff=136831</id>
		<title>Canvas GUI API</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Canvas_GUI_API&amp;diff=136831"/>
		<updated>2022-12-28T07:50:59Z</updated>

		<summary type="html">&lt;p&gt;TheEagle: Undo revision 136830 by TheEagle (talk)&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Template:Canvas Navigation}}&lt;br /&gt;
&lt;br /&gt;
gui.nas implements the classes gui, WindowButton and Window. It loads nasal files from $FGROOT/Nasal/canvas/gui for widgets and styles.&lt;br /&gt;
&lt;br /&gt;
See inside the current (Git) API {{fgdata source|Nasal/canvas/gui.nas|text=here}}.&lt;br /&gt;
&lt;br /&gt;
'''For the Canvas API see [[Canvas Nasal API]]'''&lt;br /&gt;
&lt;br /&gt;
== gui ==&lt;br /&gt;
Container class. Stores widgets and the focused window (if any).&lt;br /&gt;
&lt;br /&gt;
== gui.Widget ==&lt;br /&gt;
Template class that implements common methods, used by widget classes Button, CheckBox, Lable, LineEdit, ScrollArea&lt;br /&gt;
&lt;br /&gt;
=== new: func(derived) ===&lt;br /&gt;
'''derived''' class, e.g. gui.widgets.Button&lt;br /&gt;
&lt;br /&gt;
=== setFixedSize: func(x, y) ===&lt;br /&gt;
&lt;br /&gt;
=== setEnabled: func(enabled) ===&lt;br /&gt;
&lt;br /&gt;
=== move: func(x, y) ===&lt;br /&gt;
&lt;br /&gt;
=== setSize: func(w, h) ===&lt;br /&gt;
&lt;br /&gt;
=== setGeometry: func(geom) ===&lt;br /&gt;
&lt;br /&gt;
=== setFocus: func ===&lt;br /&gt;
&lt;br /&gt;
=== clearFocus: func ===&lt;br /&gt;
&lt;br /&gt;
=== listen: func(type, cb) ===&lt;br /&gt;
&lt;br /&gt;
=== onRemove: func ===&lt;br /&gt;
&lt;br /&gt;
=== _onStateChange: func ===&lt;br /&gt;
&lt;br /&gt;
=== visibilityChanged: func(visible) ===&lt;br /&gt;
&lt;br /&gt;
=== _setView: func(view) ===&lt;br /&gt;
&lt;br /&gt;
=== _trigger: func(type, data = nil) ===&lt;br /&gt;
&lt;br /&gt;
=== _windowFocus: func ===&lt;br /&gt;
'''returns:''' bool, focused&lt;br /&gt;
&lt;br /&gt;
== Config ==&lt;br /&gt;
Trivial container class to store (key,value) pairs&lt;br /&gt;
&lt;br /&gt;
=== new: func(cfg) ===&lt;br /&gt;
'''cfg:''' hash, used to initialize the config object data&lt;br /&gt;
&lt;br /&gt;
'''returns:''' object&lt;br /&gt;
&lt;br /&gt;
=== get: func(key, default = nil) ===&lt;br /&gt;
'''returns:''' the value for key or default if that one is nil&lt;br /&gt;
&lt;br /&gt;
=== set: func(key, value) ===&lt;br /&gt;
'''returns:''' me&lt;br /&gt;
&lt;br /&gt;
== DefaultStyle ==&lt;br /&gt;
This class stors widget factories&lt;br /&gt;
&lt;br /&gt;
=== new: func(name, name_icon_theme) ===&lt;br /&gt;
parameters are forwarded to gui.Style.new constructor&lt;br /&gt;
&lt;br /&gt;
'''returns:'''' object&lt;br /&gt;
&lt;br /&gt;
=== createWidget: func(parent, type, cfg) ===&lt;br /&gt;
&lt;br /&gt;
== DefaultStyle.widgets ==&lt;br /&gt;
At the time of writing there are the following widgets:&lt;br /&gt;
&lt;br /&gt;
button, checkbox, lable, line-edit, scroll-area&lt;br /&gt;
&lt;br /&gt;
They have the following common methods:&lt;br /&gt;
=== new: func(parent, cfg) ===&lt;br /&gt;
'''parent:''' parent canvas element (e.g. group)&lt;br /&gt;
'''cfg: ''' a config object &lt;br /&gt;
&lt;br /&gt;
=== update: func(model) ===&lt;br /&gt;
'''model''' canvas.Window - strange name... no idea why somebody named it model&lt;br /&gt;
&lt;br /&gt;
Aparently this method is called on events like mouse move/click so each widget can show an appropriate reaction.&lt;br /&gt;
&lt;br /&gt;
== DefaultStyle.widgets.button  ==&lt;br /&gt;
&lt;br /&gt;
=== setSize: func(model, w, h) ===&lt;br /&gt;
&lt;br /&gt;
=== setText: func(model, text) ===&lt;br /&gt;
&lt;br /&gt;
== DefaultStyle.widgets.checkbox  ==&lt;br /&gt;
&lt;br /&gt;
=== setSize: func(model, w, h) ===&lt;br /&gt;
&lt;br /&gt;
=== setText: func(model, text) ===&lt;br /&gt;
&lt;br /&gt;
== DefaultStyle.widgets.label  ==&lt;br /&gt;
&lt;br /&gt;
=== setSize: func(model, w, h) ===&lt;br /&gt;
&lt;br /&gt;
=== setText: func(model, text) ===&lt;br /&gt;
&lt;br /&gt;
=== setImage: func(model, img) ===&lt;br /&gt;
&lt;br /&gt;
=== setBackground: func(model, bg) ===&lt;br /&gt;
&lt;br /&gt;
=== heightForWidth: func(w) ===&lt;br /&gt;
&lt;br /&gt;
=== _createElement: func(name, type) ===&lt;br /&gt;
&lt;br /&gt;
=== _deleteElement: func(name) ===&lt;br /&gt;
&lt;br /&gt;
== DefaultStyle.widgets.line-edit  ==&lt;br /&gt;
A one line text input field&lt;br /&gt;
&lt;br /&gt;
=== setSize: func(model, w, h) ===&lt;br /&gt;
&lt;br /&gt;
=== setText: func(model, text) ===&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== DefaultStyle.widgets.scroll-area  ==&lt;br /&gt;
&lt;br /&gt;
=== setColorBackground: func ===&lt;br /&gt;
&lt;br /&gt;
=== _newScroll: func(el, orient) ===&lt;br /&gt;
private&lt;br /&gt;
&lt;br /&gt;
=== _updateScrollMetrics: func(model, dir) ===&lt;br /&gt;
private&lt;br /&gt;
&lt;br /&gt;
== WindowButton ==&lt;br /&gt;
This class manages a button implemented as canvas.image element. &lt;br /&gt;
&lt;br /&gt;
=== new(parent, name) ===&lt;br /&gt;
'''parent:'''&lt;br /&gt;
&lt;br /&gt;
'''name:''' string, used to identify the WindowButton and load decoration image files from $FGROOT/gui/style/&amp;lt;StyleName&amp;gt;/decoration/&lt;br /&gt;
&lt;br /&gt;
e.g. for a button named &amp;quot;close&amp;quot;, you should have files close_focused_normal.png, close_focused_prelight.png, close_focused_pressed.png, close_unfocused_normal.png, close_unfocused_prelight.png, close_unfocused_pressed.png&lt;br /&gt;
&lt;br /&gt;
=== protected _onStateChange() ===&lt;br /&gt;
Different images are sourced e.g. for hover, focus etc. according to the active style.&lt;br /&gt;
&lt;br /&gt;
== Window == &lt;br /&gt;
=== new: func(size, type = nil, id = nil) ===&lt;br /&gt;
'''size:''' vector [x,y], initial size of the window&lt;br /&gt;
&lt;br /&gt;
'''type:''' ? set property &amp;quot;type&amp;quot;&lt;br /&gt;
&lt;br /&gt;
'''id:''' ? used in var ghost = _newWindowGhost(id);&lt;br /&gt;
&lt;br /&gt;
Constructor to create a new instance of canvas.Window&lt;br /&gt;
&lt;br /&gt;
'''returns:''' Window object&lt;br /&gt;
&lt;br /&gt;
=== del: func ===&lt;br /&gt;
Destructor to clean up. Canvas will not be deleted if it has other &amp;quot;placements&amp;quot;&lt;br /&gt;
&lt;br /&gt;
=== setTitle: func(title) ===&lt;br /&gt;
'''title:''' string&lt;br /&gt;
&lt;br /&gt;
Set property &amp;quot;title&amp;quot; (e.g. text on the titlebar)&lt;br /&gt;
&lt;br /&gt;
'''returns:''' Window object (me)&lt;br /&gt;
&lt;br /&gt;
=== createCanvas: func() ===&lt;br /&gt;
Create the canvas to be used for this Window, add placement and mousedown listener.&lt;br /&gt;
&lt;br /&gt;
'''returns:''' The new canvas&lt;br /&gt;
&lt;br /&gt;
=== setCanvas: func(canvas_) ===&lt;br /&gt;
'''canvas_''': canvas object&lt;br /&gt;
&lt;br /&gt;
Replace canvas of this window with '''canvas_''' and remove onRezize method so window cannot be resized anymore.&lt;br /&gt;
&lt;br /&gt;
=== getCanvas: func(create = 0) ===&lt;br /&gt;
'''create:''' bool, create canvas for this window if it does not exist&lt;br /&gt;
&lt;br /&gt;
'''returns:''' canvas object of this window&lt;br /&gt;
&lt;br /&gt;
=== getCanvasDecoration: func() ===&lt;br /&gt;
'''returns:''' canvas object of the window decoration (?)&lt;br /&gt;
&lt;br /&gt;
=== setLayout: func(l) ===&lt;br /&gt;
'''l''' (?)&lt;br /&gt;
&lt;br /&gt;
Call me._ghost.setLayout(l);&lt;br /&gt;
&lt;br /&gt;
'''returns:''' Window object (me)&lt;br /&gt;
&lt;br /&gt;
=== setFocus: func ===&lt;br /&gt;
Set this window focused and tell gui class about it.&lt;br /&gt;
&lt;br /&gt;
'''returns:''' Window object (me)&lt;br /&gt;
&lt;br /&gt;
=== clearFocus: func ===&lt;br /&gt;
Clear focus from this window and tell gui class about it.&lt;br /&gt;
&lt;br /&gt;
'''returns:''' Window object (me)&lt;br /&gt;
&lt;br /&gt;
=== setPosition: func (arg...) ===&lt;br /&gt;
'''arg:''' either a vector [x, y] or two parameters x,y&lt;br /&gt;
&lt;br /&gt;
Set window position to (x,y) where (0,0) is (left, top)&lt;br /&gt;
&lt;br /&gt;
'''returns:''' Window object (me)&lt;br /&gt;
&lt;br /&gt;
=== setSize: func(arg...) ===&lt;br /&gt;
'''arg:''' either a vector [width, height] or two parameters width, height&lt;br /&gt;
&lt;br /&gt;
Set content-size to width, height and call me.onResize if exists.&lt;br /&gt;
&lt;br /&gt;
'''returns:''' Window object (me)&lt;br /&gt;
&lt;br /&gt;
=== move: func ===&lt;br /&gt;
'''arg:''' either a vector [x, y] or two parameters x,y&lt;br /&gt;
&lt;br /&gt;
Moves window by x to the right and y down. &lt;br /&gt;
&lt;br /&gt;
'''returns:''' Window object (me)&lt;br /&gt;
&lt;br /&gt;
=== raise: func() ===&lt;br /&gt;
Update z-index and call setFocus()&lt;br /&gt;
&lt;br /&gt;
'''returns:''' Window object (me)&lt;br /&gt;
&lt;br /&gt;
=== onResize: func() ===&lt;br /&gt;
Update canvas size and view to Window.content-size&lt;br /&gt;
&lt;br /&gt;
'''returns:''' Window object (me) or nil if window has no canvas.&lt;br /&gt;
&lt;br /&gt;
'''Private methods'''&lt;br /&gt;
The following methods should NOT be called directly, they shall be used ONLY by the methods above.&lt;br /&gt;
&lt;br /&gt;
=== _onStateChange: func ===&lt;br /&gt;
Used in setFocus(), clearFocus() and _updateDecoration().&lt;br /&gt;
&lt;br /&gt;
=== _propCallback: func(child, mode) ===&lt;br /&gt;
Used in Window.new (m is the window object under creation):&lt;br /&gt;
&lt;br /&gt;
arg is a vector [child, listener_node, mode, is_child_event]&lt;br /&gt;
&lt;br /&gt;
setlistener(m._node, func m._propCallback(arg[0], arg[2]), 0, 2);&lt;br /&gt;
&lt;br /&gt;
=== _handlePositionAbsolute: func(child, mode, name, index) ===&lt;br /&gt;
used only in _propCallback&lt;br /&gt;
&lt;br /&gt;
=== _updatePos: func(index, name) ===&lt;br /&gt;
used only in _handlePositionAbsolute&lt;br /&gt;
&lt;br /&gt;
=== _handleResize: func(child, name) ===&lt;br /&gt;
used only in _propCallback&lt;br /&gt;
&lt;br /&gt;
=== _updateDecoration: func() ===&lt;br /&gt;
used only in _propCallback&lt;br /&gt;
&lt;br /&gt;
=== _resizeDecoration: func() ===&lt;br /&gt;
used by _propCallback and _updateDecoration&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Dialog (depricated) ==&lt;br /&gt;
canvas.Dialog is deprectated! Use canvas.Window instead.&lt;/div&gt;</summary>
		<author><name>TheEagle</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Canvas_GUI_API&amp;diff=136830</id>
		<title>Canvas GUI API</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Canvas_GUI_API&amp;diff=136830"/>
		<updated>2022-12-28T07:50:27Z</updated>

		<summary type="html">&lt;p&gt;TheEagle: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Template:Canvas Navigation}}&lt;br /&gt;
&lt;br /&gt;
gui.nas implements the classes gui, WindowButton and Window. It loads nasal files from $FGROOT/Nasal/canvas/gui for widgets and styles.&lt;br /&gt;
&lt;br /&gt;
See inside the current (Git) API {{fgdata source|Nasal/canvas/gui.nas|text=here}}.&lt;br /&gt;
&lt;br /&gt;
'''For the Canvas API see [[Canvas Nasal API]]'''&lt;br /&gt;
&lt;br /&gt;
== gui ==&lt;br /&gt;
Container class. Stores widgets and the focused window (if any).&lt;br /&gt;
&lt;br /&gt;
== gui.Widget ==&lt;br /&gt;
Template class that implements common methods, used by widget classes Button, CheckBox, Lable, LineEdit, ScrollArea&lt;br /&gt;
&lt;br /&gt;
{{Nasal method documentation&lt;br /&gt;
| name = neww&lt;br /&gt;
| description = create new&lt;br /&gt;
| return-type = gui.Widget&lt;br /&gt;
| return-value = gui.Widget instance&lt;br /&gt;
| positional-argument = derived&lt;br /&gt;
| keyword-argument = &lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== new: func(derived) ===&lt;br /&gt;
'''derived''' class, e.g. gui.widgets.Button&lt;br /&gt;
&lt;br /&gt;
=== setFixedSize: func(x, y) ===&lt;br /&gt;
&lt;br /&gt;
=== setEnabled: func(enabled) ===&lt;br /&gt;
&lt;br /&gt;
=== move: func(x, y) ===&lt;br /&gt;
&lt;br /&gt;
=== setSize: func(w, h) ===&lt;br /&gt;
&lt;br /&gt;
=== setGeometry: func(geom) ===&lt;br /&gt;
&lt;br /&gt;
=== setFocus: func ===&lt;br /&gt;
&lt;br /&gt;
=== clearFocus: func ===&lt;br /&gt;
&lt;br /&gt;
=== listen: func(type, cb) ===&lt;br /&gt;
&lt;br /&gt;
=== onRemove: func ===&lt;br /&gt;
&lt;br /&gt;
=== _onStateChange: func ===&lt;br /&gt;
&lt;br /&gt;
=== visibilityChanged: func(visible) ===&lt;br /&gt;
&lt;br /&gt;
=== _setView: func(view) ===&lt;br /&gt;
&lt;br /&gt;
=== _trigger: func(type, data = nil) ===&lt;br /&gt;
&lt;br /&gt;
=== _windowFocus: func ===&lt;br /&gt;
'''returns:''' bool, focused&lt;br /&gt;
&lt;br /&gt;
== Config ==&lt;br /&gt;
Trivial container class to store (key,value) pairs&lt;br /&gt;
&lt;br /&gt;
=== new: func(cfg) ===&lt;br /&gt;
'''cfg:''' hash, used to initialize the config object data&lt;br /&gt;
&lt;br /&gt;
'''returns:''' object&lt;br /&gt;
&lt;br /&gt;
=== get: func(key, default = nil) ===&lt;br /&gt;
'''returns:''' the value for key or default if that one is nil&lt;br /&gt;
&lt;br /&gt;
=== set: func(key, value) ===&lt;br /&gt;
'''returns:''' me&lt;br /&gt;
&lt;br /&gt;
== DefaultStyle ==&lt;br /&gt;
This class stors widget factories&lt;br /&gt;
&lt;br /&gt;
=== new: func(name, name_icon_theme) ===&lt;br /&gt;
parameters are forwarded to gui.Style.new constructor&lt;br /&gt;
&lt;br /&gt;
'''returns:'''' object&lt;br /&gt;
&lt;br /&gt;
=== createWidget: func(parent, type, cfg) ===&lt;br /&gt;
&lt;br /&gt;
== DefaultStyle.widgets ==&lt;br /&gt;
At the time of writing there are the following widgets:&lt;br /&gt;
&lt;br /&gt;
button, checkbox, lable, line-edit, scroll-area&lt;br /&gt;
&lt;br /&gt;
They have the following common methods:&lt;br /&gt;
=== new: func(parent, cfg) ===&lt;br /&gt;
'''parent:''' parent canvas element (e.g. group)&lt;br /&gt;
'''cfg: ''' a config object &lt;br /&gt;
&lt;br /&gt;
=== update: func(model) ===&lt;br /&gt;
'''model''' canvas.Window - strange name... no idea why somebody named it model&lt;br /&gt;
&lt;br /&gt;
Aparently this method is called on events like mouse move/click so each widget can show an appropriate reaction.&lt;br /&gt;
&lt;br /&gt;
== DefaultStyle.widgets.button  ==&lt;br /&gt;
&lt;br /&gt;
=== setSize: func(model, w, h) ===&lt;br /&gt;
&lt;br /&gt;
=== setText: func(model, text) ===&lt;br /&gt;
&lt;br /&gt;
== DefaultStyle.widgets.checkbox  ==&lt;br /&gt;
&lt;br /&gt;
=== setSize: func(model, w, h) ===&lt;br /&gt;
&lt;br /&gt;
=== setText: func(model, text) ===&lt;br /&gt;
&lt;br /&gt;
== DefaultStyle.widgets.label  ==&lt;br /&gt;
&lt;br /&gt;
=== setSize: func(model, w, h) ===&lt;br /&gt;
&lt;br /&gt;
=== setText: func(model, text) ===&lt;br /&gt;
&lt;br /&gt;
=== setImage: func(model, img) ===&lt;br /&gt;
&lt;br /&gt;
=== setBackground: func(model, bg) ===&lt;br /&gt;
&lt;br /&gt;
=== heightForWidth: func(w) ===&lt;br /&gt;
&lt;br /&gt;
=== _createElement: func(name, type) ===&lt;br /&gt;
&lt;br /&gt;
=== _deleteElement: func(name) ===&lt;br /&gt;
&lt;br /&gt;
== DefaultStyle.widgets.line-edit  ==&lt;br /&gt;
A one line text input field&lt;br /&gt;
&lt;br /&gt;
=== setSize: func(model, w, h) ===&lt;br /&gt;
&lt;br /&gt;
=== setText: func(model, text) ===&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== DefaultStyle.widgets.scroll-area  ==&lt;br /&gt;
&lt;br /&gt;
=== setColorBackground: func ===&lt;br /&gt;
&lt;br /&gt;
=== _newScroll: func(el, orient) ===&lt;br /&gt;
private&lt;br /&gt;
&lt;br /&gt;
=== _updateScrollMetrics: func(model, dir) ===&lt;br /&gt;
private&lt;br /&gt;
&lt;br /&gt;
== WindowButton ==&lt;br /&gt;
This class manages a button implemented as canvas.image element. &lt;br /&gt;
&lt;br /&gt;
=== new(parent, name) ===&lt;br /&gt;
'''parent:'''&lt;br /&gt;
&lt;br /&gt;
'''name:''' string, used to identify the WindowButton and load decoration image files from $FGROOT/gui/style/&amp;lt;StyleName&amp;gt;/decoration/&lt;br /&gt;
&lt;br /&gt;
e.g. for a button named &amp;quot;close&amp;quot;, you should have files close_focused_normal.png, close_focused_prelight.png, close_focused_pressed.png, close_unfocused_normal.png, close_unfocused_prelight.png, close_unfocused_pressed.png&lt;br /&gt;
&lt;br /&gt;
=== protected _onStateChange() ===&lt;br /&gt;
Different images are sourced e.g. for hover, focus etc. according to the active style.&lt;br /&gt;
&lt;br /&gt;
== Window == &lt;br /&gt;
=== new: func(size, type = nil, id = nil) ===&lt;br /&gt;
'''size:''' vector [x,y], initial size of the window&lt;br /&gt;
&lt;br /&gt;
'''type:''' ? set property &amp;quot;type&amp;quot;&lt;br /&gt;
&lt;br /&gt;
'''id:''' ? used in var ghost = _newWindowGhost(id);&lt;br /&gt;
&lt;br /&gt;
Constructor to create a new instance of canvas.Window&lt;br /&gt;
&lt;br /&gt;
'''returns:''' Window object&lt;br /&gt;
&lt;br /&gt;
=== del: func ===&lt;br /&gt;
Destructor to clean up. Canvas will not be deleted if it has other &amp;quot;placements&amp;quot;&lt;br /&gt;
&lt;br /&gt;
=== setTitle: func(title) ===&lt;br /&gt;
'''title:''' string&lt;br /&gt;
&lt;br /&gt;
Set property &amp;quot;title&amp;quot; (e.g. text on the titlebar)&lt;br /&gt;
&lt;br /&gt;
'''returns:''' Window object (me)&lt;br /&gt;
&lt;br /&gt;
=== createCanvas: func() ===&lt;br /&gt;
Create the canvas to be used for this Window, add placement and mousedown listener.&lt;br /&gt;
&lt;br /&gt;
'''returns:''' The new canvas&lt;br /&gt;
&lt;br /&gt;
=== setCanvas: func(canvas_) ===&lt;br /&gt;
'''canvas_''': canvas object&lt;br /&gt;
&lt;br /&gt;
Replace canvas of this window with '''canvas_''' and remove onRezize method so window cannot be resized anymore.&lt;br /&gt;
&lt;br /&gt;
=== getCanvas: func(create = 0) ===&lt;br /&gt;
'''create:''' bool, create canvas for this window if it does not exist&lt;br /&gt;
&lt;br /&gt;
'''returns:''' canvas object of this window&lt;br /&gt;
&lt;br /&gt;
=== getCanvasDecoration: func() ===&lt;br /&gt;
'''returns:''' canvas object of the window decoration (?)&lt;br /&gt;
&lt;br /&gt;
=== setLayout: func(l) ===&lt;br /&gt;
'''l''' (?)&lt;br /&gt;
&lt;br /&gt;
Call me._ghost.setLayout(l);&lt;br /&gt;
&lt;br /&gt;
'''returns:''' Window object (me)&lt;br /&gt;
&lt;br /&gt;
=== setFocus: func ===&lt;br /&gt;
Set this window focused and tell gui class about it.&lt;br /&gt;
&lt;br /&gt;
'''returns:''' Window object (me)&lt;br /&gt;
&lt;br /&gt;
=== clearFocus: func ===&lt;br /&gt;
Clear focus from this window and tell gui class about it.&lt;br /&gt;
&lt;br /&gt;
'''returns:''' Window object (me)&lt;br /&gt;
&lt;br /&gt;
=== setPosition: func (arg...) ===&lt;br /&gt;
'''arg:''' either a vector [x, y] or two parameters x,y&lt;br /&gt;
&lt;br /&gt;
Set window position to (x,y) where (0,0) is (left, top)&lt;br /&gt;
&lt;br /&gt;
'''returns:''' Window object (me)&lt;br /&gt;
&lt;br /&gt;
=== setSize: func(arg...) ===&lt;br /&gt;
'''arg:''' either a vector [width, height] or two parameters width, height&lt;br /&gt;
&lt;br /&gt;
Set content-size to width, height and call me.onResize if exists.&lt;br /&gt;
&lt;br /&gt;
'''returns:''' Window object (me)&lt;br /&gt;
&lt;br /&gt;
=== move: func ===&lt;br /&gt;
'''arg:''' either a vector [x, y] or two parameters x,y&lt;br /&gt;
&lt;br /&gt;
Moves window by x to the right and y down. &lt;br /&gt;
&lt;br /&gt;
'''returns:''' Window object (me)&lt;br /&gt;
&lt;br /&gt;
=== raise: func() ===&lt;br /&gt;
Update z-index and call setFocus()&lt;br /&gt;
&lt;br /&gt;
'''returns:''' Window object (me)&lt;br /&gt;
&lt;br /&gt;
=== onResize: func() ===&lt;br /&gt;
Update canvas size and view to Window.content-size&lt;br /&gt;
&lt;br /&gt;
'''returns:''' Window object (me) or nil if window has no canvas.&lt;br /&gt;
&lt;br /&gt;
'''Private methods'''&lt;br /&gt;
The following methods should NOT be called directly, they shall be used ONLY by the methods above.&lt;br /&gt;
&lt;br /&gt;
=== _onStateChange: func ===&lt;br /&gt;
Used in setFocus(), clearFocus() and _updateDecoration().&lt;br /&gt;
&lt;br /&gt;
=== _propCallback: func(child, mode) ===&lt;br /&gt;
Used in Window.new (m is the window object under creation):&lt;br /&gt;
&lt;br /&gt;
arg is a vector [child, listener_node, mode, is_child_event]&lt;br /&gt;
&lt;br /&gt;
setlistener(m._node, func m._propCallback(arg[0], arg[2]), 0, 2);&lt;br /&gt;
&lt;br /&gt;
=== _handlePositionAbsolute: func(child, mode, name, index) ===&lt;br /&gt;
used only in _propCallback&lt;br /&gt;
&lt;br /&gt;
=== _updatePos: func(index, name) ===&lt;br /&gt;
used only in _handlePositionAbsolute&lt;br /&gt;
&lt;br /&gt;
=== _handleResize: func(child, name) ===&lt;br /&gt;
used only in _propCallback&lt;br /&gt;
&lt;br /&gt;
=== _updateDecoration: func() ===&lt;br /&gt;
used only in _propCallback&lt;br /&gt;
&lt;br /&gt;
=== _resizeDecoration: func() ===&lt;br /&gt;
used by _propCallback and _updateDecoration&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Dialog (depricated) ==&lt;br /&gt;
canvas.Dialog is deprectated! Use canvas.Window instead.&lt;/div&gt;</summary>
		<author><name>TheEagle</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Template:Nasal_method_documentation&amp;diff=136829</id>
		<title>Template:Nasal method documentation</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Template:Nasal_method_documentation&amp;diff=136829"/>
		<updated>2022-12-28T07:33:32Z</updated>

		<summary type="html">&lt;p&gt;TheEagle: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
&amp;lt;noinclude&amp;gt;&lt;br /&gt;
&amp;lt;templatedata&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
	&amp;quot;params&amp;quot;: {&lt;br /&gt;
		&amp;quot;description&amp;quot;: {&lt;br /&gt;
			&amp;quot;label&amp;quot;: &amp;quot;Description&amp;quot;,&lt;br /&gt;
			&amp;quot;description&amp;quot;: &amp;quot;A description of the function / method being documented.&amp;quot;,&lt;br /&gt;
			&amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;,&lt;br /&gt;
			&amp;quot;autovalue&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
			&amp;quot;required&amp;quot;: true&lt;br /&gt;
		},&lt;br /&gt;
		&amp;quot;return-type&amp;quot;: {&lt;br /&gt;
			&amp;quot;label&amp;quot;: &amp;quot;Return type&amp;quot;,&lt;br /&gt;
			&amp;quot;description&amp;quot;: &amp;quot;Type of the returned value (if any)&amp;quot;,&lt;br /&gt;
			&amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;,&lt;br /&gt;
			&amp;quot;default&amp;quot;: &amp;quot;nil&amp;quot;,&lt;br /&gt;
			&amp;quot;autovalue&amp;quot;: &amp;quot;nil&amp;quot;,&lt;br /&gt;
			&amp;quot;required&amp;quot;: true&lt;br /&gt;
		},&lt;br /&gt;
		&amp;quot;name&amp;quot;: {&lt;br /&gt;
			&amp;quot;label&amp;quot;: &amp;quot;Name&amp;quot;,&lt;br /&gt;
			&amp;quot;description&amp;quot;: &amp;quot;The name of the function / method (without parentheses)&amp;quot;,&lt;br /&gt;
			&amp;quot;example&amp;quot;: &amp;quot;cmdarg&amp;quot;,&lt;br /&gt;
			&amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;,&lt;br /&gt;
			&amp;quot;autovalue&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
			&amp;quot;required&amp;quot;: true&lt;br /&gt;
		},&lt;br /&gt;
		&amp;quot;return-value&amp;quot;: {&lt;br /&gt;
			&amp;quot;label&amp;quot;: &amp;quot;Return value&amp;quot;,&lt;br /&gt;
			&amp;quot;description&amp;quot;: &amp;quot;The value returned by the function / method.&amp;quot;,&lt;br /&gt;
			&amp;quot;example&amp;quot;: &amp;quot;Boolean value of the property node.&amp;quot;,&lt;br /&gt;
			&amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;,&lt;br /&gt;
			&amp;quot;default&amp;quot;: &amp;quot;nil&amp;quot;,&lt;br /&gt;
			&amp;quot;autovalue&amp;quot;: &amp;quot;nil&amp;quot;,&lt;br /&gt;
			&amp;quot;required&amp;quot;: true&lt;br /&gt;
		},&lt;br /&gt;
		&amp;quot;positional-argument&amp;quot;: {&lt;br /&gt;
			&amp;quot;label&amp;quot;: &amp;quot;Positional argument&amp;quot;,&lt;br /&gt;
			&amp;quot;description&amp;quot;: &amp;quot;A mandatory argument&amp;quot;,&lt;br /&gt;
			&amp;quot;type&amp;quot;: &amp;quot;wiki-template-name&amp;quot;,&lt;br /&gt;
			&amp;quot;autovalue&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
			&amp;quot;suggested&amp;quot;: true&lt;br /&gt;
		},&lt;br /&gt;
		&amp;quot;keyword-argument&amp;quot;: {&lt;br /&gt;
			&amp;quot;label&amp;quot;: &amp;quot;Keyword argument&amp;quot;,&lt;br /&gt;
			&amp;quot;description&amp;quot;: &amp;quot;An argument with a default value&amp;quot;,&lt;br /&gt;
			&amp;quot;type&amp;quot;: &amp;quot;wiki-template-name&amp;quot;,&lt;br /&gt;
			&amp;quot;suggested&amp;quot;: true&lt;br /&gt;
		}&lt;br /&gt;
	},&lt;br /&gt;
	&amp;quot;paramOrder&amp;quot;: [&lt;br /&gt;
		&amp;quot;name&amp;quot;,&lt;br /&gt;
		&amp;quot;description&amp;quot;,&lt;br /&gt;
		&amp;quot;return-type&amp;quot;,&lt;br /&gt;
		&amp;quot;return-value&amp;quot;,&lt;br /&gt;
		&amp;quot;positional-argument&amp;quot;,&lt;br /&gt;
		&amp;quot;keyword-argument&amp;quot;&lt;br /&gt;
	],&lt;br /&gt;
	&amp;quot;format&amp;quot;: &amp;quot;block&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/templatedata&amp;gt;&lt;br /&gt;
&amp;lt;/noinclude&amp;gt;&lt;/div&gt;</summary>
		<author><name>TheEagle</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Template:Nasal_method_documentation&amp;diff=136828</id>
		<title>Template:Nasal method documentation</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Template:Nasal_method_documentation&amp;diff=136828"/>
		<updated>2022-12-28T07:31:07Z</updated>

		<summary type="html">&lt;p&gt;TheEagle: Created page with &amp;quot; &amp;lt;noinclude&amp;gt; &amp;lt;templatedata&amp;gt; { 	&amp;quot;params&amp;quot;: { 		&amp;quot;description&amp;quot;: { 			&amp;quot;label&amp;quot;: &amp;quot;Description&amp;quot;, 			&amp;quot;description&amp;quot;: &amp;quot;A description of the function / method being documented.&amp;quot;, 			&amp;quot;type...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
&amp;lt;noinclude&amp;gt;&lt;br /&gt;
&amp;lt;templatedata&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
	&amp;quot;params&amp;quot;: {&lt;br /&gt;
		&amp;quot;description&amp;quot;: {&lt;br /&gt;
			&amp;quot;label&amp;quot;: &amp;quot;Description&amp;quot;,&lt;br /&gt;
			&amp;quot;description&amp;quot;: &amp;quot;A description of the function / method being documented.&amp;quot;,&lt;br /&gt;
			&amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;,&lt;br /&gt;
			&amp;quot;autovalue&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
			&amp;quot;required&amp;quot;: true&lt;br /&gt;
		},&lt;br /&gt;
		&amp;quot;return-type&amp;quot;: {&lt;br /&gt;
			&amp;quot;label&amp;quot;: &amp;quot;Return type&amp;quot;,&lt;br /&gt;
			&amp;quot;description&amp;quot;: &amp;quot;Type of the returned value (if any)&amp;quot;,&lt;br /&gt;
			&amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;,&lt;br /&gt;
			&amp;quot;default&amp;quot;: &amp;quot;nil&amp;quot;,&lt;br /&gt;
			&amp;quot;autovalue&amp;quot;: &amp;quot;nil&amp;quot;,&lt;br /&gt;
			&amp;quot;required&amp;quot;: true&lt;br /&gt;
		},&lt;br /&gt;
		&amp;quot;name&amp;quot;: {&lt;br /&gt;
			&amp;quot;label&amp;quot;: &amp;quot;Name&amp;quot;,&lt;br /&gt;
			&amp;quot;description&amp;quot;: &amp;quot;The name of the function / method (without parentheses)&amp;quot;,&lt;br /&gt;
			&amp;quot;example&amp;quot;: &amp;quot;cmdarg&amp;quot;,&lt;br /&gt;
			&amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;,&lt;br /&gt;
			&amp;quot;autovalue&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
			&amp;quot;required&amp;quot;: true&lt;br /&gt;
		},&lt;br /&gt;
		&amp;quot;return-value&amp;quot;: {&lt;br /&gt;
			&amp;quot;label&amp;quot;: &amp;quot;Return value&amp;quot;,&lt;br /&gt;
			&amp;quot;description&amp;quot;: &amp;quot;The value returned by the function / method.&amp;quot;,&lt;br /&gt;
			&amp;quot;example&amp;quot;: &amp;quot;Boolean value of the property node.&amp;quot;,&lt;br /&gt;
			&amp;quot;type&amp;quot;: &amp;quot;string&amp;quot;,&lt;br /&gt;
			&amp;quot;default&amp;quot;: &amp;quot;nil&amp;quot;,&lt;br /&gt;
			&amp;quot;autovalue&amp;quot;: &amp;quot;nil&amp;quot;,&lt;br /&gt;
			&amp;quot;required&amp;quot;: true&lt;br /&gt;
		},&lt;br /&gt;
		&amp;quot;positional-argument&amp;quot;: {&lt;br /&gt;
			&amp;quot;label&amp;quot;: &amp;quot;Positional argument&amp;quot;,&lt;br /&gt;
			&amp;quot;description&amp;quot;: &amp;quot;A mandatory argument&amp;quot;,&lt;br /&gt;
			&amp;quot;type&amp;quot;: &amp;quot;wiki-template-name&amp;quot;,&lt;br /&gt;
			&amp;quot;autovalue&amp;quot;: &amp;quot;&amp;quot;,&lt;br /&gt;
			&amp;quot;suggested&amp;quot;: true&lt;br /&gt;
		},&lt;br /&gt;
		&amp;quot;keyword-argument&amp;quot;: {&lt;br /&gt;
			&amp;quot;label&amp;quot;: &amp;quot;Keyword argument&amp;quot;,&lt;br /&gt;
			&amp;quot;description&amp;quot;: &amp;quot;An argument with a default value&amp;quot;,&lt;br /&gt;
			&amp;quot;type&amp;quot;: &amp;quot;wiki-template-name&amp;quot;,&lt;br /&gt;
			&amp;quot;suggested&amp;quot;: true&lt;br /&gt;
		}&lt;br /&gt;
	},&lt;br /&gt;
	&amp;quot;paramOrder&amp;quot;: [&lt;br /&gt;
		&amp;quot;name&amp;quot;,&lt;br /&gt;
		&amp;quot;description&amp;quot;,&lt;br /&gt;
		&amp;quot;return-type&amp;quot;,&lt;br /&gt;
		&amp;quot;return-value&amp;quot;,&lt;br /&gt;
		&amp;quot;positional-argument&amp;quot;,&lt;br /&gt;
		&amp;quot;keyword-argument&amp;quot;&lt;br /&gt;
	],&lt;br /&gt;
	&amp;quot;format&amp;quot;: &amp;quot;inline&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/templatedata&amp;gt;&lt;br /&gt;
&amp;lt;/noinclude&amp;gt;&lt;/div&gt;</summary>
		<author><name>TheEagle</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Template:Canvas_Widget_Matrix&amp;diff=136824</id>
		<title>Template:Canvas Widget Matrix</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Template:Canvas_Widget_Matrix&amp;diff=136824"/>
		<updated>2022-12-27T17:27:11Z</updated>

		<summary type="html">&lt;p&gt;TheEagle: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Caution|This table hasn't been updated in a while, as of 09/2022 there is a growing set of widgets available: {{Fgdata commit|459dea77e5ceac592d0d5d58a059ccad1fce6daa|text=(see &amp;quot;Add more Nasal skeleton for new UI objects&amp;quot; )}} Please get involved in helping update this table!}}&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! PUI widget !! Priority !! Canvas widget !! Remarks&lt;br /&gt;
|-&lt;br /&gt;
| {{tag|hrule}}/{{tag|vrule}} || not critical || {{Progressbar|80}} ||&lt;br /&gt;
|-&lt;br /&gt;
| {{tag|list}} || || {{N/a|Not implemented}} || list, combo and select should probably be tackled together, there's plenty of common/similar functionality&lt;br /&gt;
|-&lt;br /&gt;
| {{tag|airport-list}} || || {{N/a|Not implemented}} ||trivial using navdb APIs and ScrollArea with buttons/labels for each entry and a corresponding event handler [[Howto:Processing_legacy_PUI_dialogs_using_Canvas#Use_Case:_airports.xml]]&lt;br /&gt;
|-&lt;br /&gt;
| {{tag|property-list}} || || {{N/a|Not implemented}} || [[Property Browser]], rough prototype exists already, basically mapping 3 props.nas APIs to a ScrollArea with buttons for each property/node, allowing the tree to be travsersed interactively&lt;br /&gt;
|-&lt;br /&gt;
| {{tag|input}} || || {{fgdata file|Nasal/canvas/gui/widgets/LineEdit.nas|t=LineEdit}} ||&lt;br /&gt;
|-&lt;br /&gt;
| {{tag|text}} || || {{fgdata file|Nasal/canvas/gui/widgets/Label.nas|t=Label}} ||&lt;br /&gt;
|-&lt;br /&gt;
| {{tag|checkbox}} || || {{fgdata file|Nasal/canvas/gui/widgets/Checkbox.nas|t=Checkbox}} ||&lt;br /&gt;
|-&lt;br /&gt;
| {{tag|radio}} ||  || {{Progressbar|80}} ||&lt;br /&gt;
|-&lt;br /&gt;
| {{tag|button}} || || {{fgdata file|Nasal/canvas/gui/widgets/Button.nas|t=Button}} ||&lt;br /&gt;
|-&lt;br /&gt;
| {{tag|map}} || || {{N/a|Not implemented, see also [[Canvas MapStructure]]}} || {{Progressbar|20}} [[Howto:Creating_a_Canvas_GUI_Widget#Implementing_a_Map_widget]]&lt;br /&gt;
|-&lt;br /&gt;
| {{tag|canvas}} || || Not needed (for obvious reasons) || While we don't necessarily need a canvas widget, we need to map the PUI widget to a corresponding equivalent, i.e. an embedded Canvas with its own scripting block to animate the whole thing [[Howto:Processing_legacy_PUI_dialogs_using_Canvas#Embedded_Canvas]]&lt;br /&gt;
|-&lt;br /&gt;
| {{tag|combo}} || || {{N/a|Not implemented}} || list, combo and select can be unified&lt;br /&gt;
|-&lt;br /&gt;
| {{tag|slider}} || || {{Progressbar|80}} || can be implemented as a button with drag'n drop support, placed between two outer buttons in the form of &amp;lt;code&amp;gt;[&amp;lt;] [button] [&amp;gt;]&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| {{tag|dial}} || || {{N/a|Not implemented}} ||&lt;br /&gt;
|-&lt;br /&gt;
| {{tag|textbox}} || || {{fgdata file|Nasal/canvas/gui/widgets/LineEdit.nas|t=LineEdit}} and/or {{fgdata file|Nasal/canvas/gui/widgets/Label.nas|t=Label}} ||&lt;br /&gt;
|-&lt;br /&gt;
| {{tag|select}} || || {{N/a|Not implemented}} ||list, combo and select can be unified&lt;br /&gt;
|-&lt;br /&gt;
| {{tag|waypointlist}} || || {{N/a|Not implemented}} || trivial using navdb APIs and ScrollArea with buttons&lt;br /&gt;
|-&lt;br /&gt;
| {{tag|loglist}} || || {{N/a|Not implemented}} || needs [[SGLOG]] logstream exposed via [[Nasal/CppBind]]&lt;br /&gt;
|-&lt;br /&gt;
| {{N/a|No equivalent}} || || {{fgdata file|Nasal/canvas/gui/widgets/ScrollArea.nas|t=ScrollArea}} || serves as the generic building block for any PUI list-type (waypoints/airports) with labels/buttons added for each entry shown&lt;br /&gt;
|-&lt;br /&gt;
| {{N/a|No equivalent}} || || {{fgdata file|Nasal/canvas/gui/widgets/TabWidget.nas|t=TabWidget}} || A tab widget as everyone knows it from their web browsr&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>TheEagle</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Howto:Animate_models&amp;diff=135945</id>
		<title>Howto:Animate models</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Howto:Animate_models&amp;diff=135945"/>
		<updated>2022-11-12T01:57:22Z</updated>

		<summary type="html">&lt;p&gt;TheEagle: Add note about added axis-object support for slider and locked-track animations on Git next.&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The real world is full of motion. To simulate this in [[FlightGear]], '''models must be animated'''.&lt;br /&gt;
&lt;br /&gt;
FlightGear allows you to animate models in response to property changes: for example, the propellers can spin when the engine is on and the elevators can move up and down with your controller. There is no fixed limit on what parts can be animated: the only requirements are that the part is named in the 3D model file, and that there is a property in the main tree that you can use to get the positioning information. &lt;br /&gt;
&lt;br /&gt;
This document provides basic information for all kind of animations. When animating your model, it is very helpful to find an aircraft with parts similar to yours and use it as an example. Cut and paste the code into your wrapper file and then edit to suit.&lt;br /&gt;
&lt;br /&gt;
== Notes ==&lt;br /&gt;
=== File name of main model and animation XML file ===&lt;br /&gt;
{{main article|Aircraft-set.xml#Not used for loading multiplayer aircraft}}&lt;br /&gt;
The file name of the main model and animation XML file, or the .ac file if there is no XML file, (in essence the property &amp;lt;code&amp;gt;/sim/model/path&amp;lt;/code&amp;gt;) will be the name of the aircraft that is transmitted when using [[multiplayer]] and will also be used for loading multiplayer aircraft.&lt;br /&gt;
&lt;br /&gt;
There is also a mechanism to substitute a full aircraft model with a simpler AI aircraft model if one is available at the same file path (including for example &amp;lt;code&amp;gt;Models/Boeing-797-800.xml&amp;lt;/code&amp;gt;), but in &amp;lt;code&amp;gt;[[$FG_ROOT]]/'''AI'''/Aircraft/&amp;lt;/code&amp;gt; instead of &amp;lt;code&amp;gt;$FG_ROOT/Aircraft/&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== .ac files ===&lt;br /&gt;
{{Main article|AC files: Understanding and changing .ac code#Identifying an object}}&lt;br /&gt;
&lt;br /&gt;
When referring to an .ac file in your xml animation, it is important that the &amp;lt;code&amp;gt;&amp;lt;object-name&amp;gt;&amp;lt;/code&amp;gt; exactly matches the object named in the .ac file (this includes cases!). &lt;br /&gt;
&lt;br /&gt;
'''Note for SketchUp users:''' The spatial reference X/Y/Z used in animation to locate an object or a point are different from the ones in AC3D ie X values are the same in both but Y in animation must be matched to AC3D's -Z (Z value but opposite sign) and Z value in animation must be matched to AC3D's Y value. &lt;br /&gt;
&lt;br /&gt;
'''Note for SketchUp users:''' when exporting to AC3D in Sketchup, the .ac file will name the objects in your model to &amp;quot;blah&amp;quot; by default. You need to amend the relevant object names in your .ac file using text edit, so that the xml will work.&lt;br /&gt;
&lt;br /&gt;
=== Animation order ===&lt;br /&gt;
Animations are executed by FlightGear in the order that they are read in the model's .xml file. Therefore, it is very important to pay attention to the order, especially when multiple animations are applied to the same object(s). Wrong ordering of animations might cause [[Howto:Animate models#Timed|timed]] animations (used to create flashing lights) to not work. For further details see this [https://sourceforge.net/p/flightgear/mailman/message/37090714/ thread] on the development mailing list. Updated [https://scenery.flightgear.org/app.php?c=Models&amp;amp;a=browse&amp;amp;shared=18 shared Effects models] will be available very soon.&lt;br /&gt;
&lt;br /&gt;
Similar problems can be encountered with the dist-scale instead of the timed animation.&lt;br /&gt;
== Tags used in most animations ==&lt;br /&gt;
=== Name ===&lt;br /&gt;
With a name animation, you can group multiple objects. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;name&amp;gt;Collection1&amp;lt;/name&amp;gt;&lt;br /&gt;
  &amp;lt;object-name&amp;gt;Object1&amp;lt;/object-name&amp;gt;&lt;br /&gt;
  &amp;lt;object-name&amp;gt;Object2&amp;lt;/object-name&amp;gt;&lt;br /&gt;
  &amp;lt;object-name&amp;gt;Object3&amp;lt;/object-name&amp;gt;&lt;br /&gt;
 &amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The example above creates a &amp;quot;virtual object&amp;quot; with the name Collection1. In animation, we can animate this group of objects, by using:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
 &amp;lt;object-name&amp;gt;Collection1&amp;lt;/object-name&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Object-name ===&lt;br /&gt;
These names are set in the 3D model. Each single object has a unique name; for easy identification it is advised to use descriptive names (LeftElevator, Rudder etc.). Animations are only applied to those objects that are mentioned in an object-name line (one object per line!). Animations lacking those, will be applied to the entire model.&lt;br /&gt;
&lt;br /&gt;
=== Property ===&lt;br /&gt;
Each animation must be associated with exactly one property from the main FlightGear property tree (remember that the properties in the wrapper file are not part of the main tree), using &amp;lt;code&amp;gt;&amp;lt;property&amp;gt;&amp;lt;/code&amp;gt; to provide the property path:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;type&amp;gt;rotate&amp;lt;/type&amp;gt;&lt;br /&gt;
  &amp;lt;object-name&amp;gt;Rudder&amp;lt;/object-name&amp;gt;&lt;br /&gt;
  &amp;lt;property&amp;gt;controls/rudder&amp;lt;/property&amp;gt;&lt;br /&gt;
 &amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note the omission of the leading slash '/' when referring to the property. This assures that when the model is used for AI or multiplayer traffic the animations will follow that of the AI controller instead of that of the user.&lt;br /&gt;
&lt;br /&gt;
=== Axis ===&lt;br /&gt;
An axis part is required in every animation that involves a rotating or moving thing.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
  &amp;lt;axis&amp;gt;&lt;br /&gt;
   &amp;lt;x&amp;gt;0&amp;lt;/x&amp;gt;&lt;br /&gt;
   &amp;lt;y&amp;gt;1&amp;lt;/y&amp;gt;&lt;br /&gt;
   &amp;lt;z&amp;gt;0&amp;lt;/z&amp;gt;&lt;br /&gt;
  &amp;lt;/axis&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The axis are similar to the ones of the 3D model. There is a difference between rotation and translation:&lt;br /&gt;
* In rotation animations, the axis part defines around what axis the object rotates. Negative/positive values make the difference between counterclockwise and clockwise rotations.&lt;br /&gt;
* In translate animations, the part defines along what axis the object moves. If the x-axis is poiting backwards, an x-value of -1 will result in forward motion.&lt;br /&gt;
&lt;br /&gt;
You could also define two points, between which FlightGear will calculate the correct axis. This makes the use of a [[#Center|&amp;lt;nowiki&amp;gt;&amp;lt;center&amp;gt;&amp;lt;/nowiki&amp;gt;]] tag redundant! Such coordinates are extremely useful for animating control surfaces (rudder, elevators etc.).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
  &amp;lt;axis&amp;gt;   &lt;br /&gt;
   &amp;lt;x1-m&amp;gt; 4.9&amp;lt;/x1-m&amp;gt;&lt;br /&gt;
   &amp;lt;y1-m&amp;gt; 7.1&amp;lt;/y1-m&amp;gt;&lt;br /&gt;
   &amp;lt;z1-m&amp;gt;-1.0&amp;lt;/z1-m&amp;gt;&lt;br /&gt;
   &amp;lt;x2-m&amp;gt; 5.9&amp;lt;/x2-m&amp;gt;&lt;br /&gt;
   &amp;lt;y2-m&amp;gt;11.2&amp;lt;/y2-m&amp;gt;&lt;br /&gt;
   &amp;lt;z2-m&amp;gt;-0.5&amp;lt;/z2-m&amp;gt;&lt;br /&gt;
  &amp;lt;/axis&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Center ===&lt;br /&gt;
Various animations ([[#Rotate|rotate]], [[#Spin|spin]], [[#Scale|scale]]) move around a center point.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
  &amp;lt;center&amp;gt;&lt;br /&gt;
   &amp;lt;x-m&amp;gt;-1.50&amp;lt;/x-m&amp;gt;&lt;br /&gt;
   &amp;lt;y-m&amp;gt; 1   &amp;lt;/y-m&amp;gt;&lt;br /&gt;
   &amp;lt;z-m&amp;gt; 0.25&amp;lt;/z-m&amp;gt;&lt;br /&gt;
  &amp;lt;/center&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The axis are similar to the ones of the 3D model, so finding coordinates is easily done in 3D modeling software.&lt;br /&gt;
&lt;br /&gt;
=== Using a geometry object for axis and centre (2017.2) ===&lt;br /&gt;
&lt;br /&gt;
Added in V2017.2 is support to allow a geometry object (a line segment with two vertices) that is used to define both the centre and the axis for an animation. This will work with rotate, spin, translate and knob animations.&lt;br /&gt;
&lt;br /&gt;
{{Note|Since SimGear commit 6ca9141083e62be1161d0ca2d1e3b918494dd6ad on next, this feature can also be used for slider (usage equivalent to that for a translate animation) and for any of the &amp;lt;*-center&amp;gt; / &amp;lt;*-axis&amp;gt; tags of a locked-track animation.}}&lt;br /&gt;
&lt;br /&gt;
When used for translate animations, the axis line should be normalized (i.e. be 1 meter long) as its length acts as a factor for the translation distance.&lt;br /&gt;
&lt;br /&gt;
The XML syntax for this is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
&amp;lt;axis&amp;gt;&lt;br /&gt;
    &amp;lt;object-name&amp;gt;some-object&amp;lt;/object-name&amp;gt;&lt;br /&gt;
&amp;lt;/axis&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the &amp;lt;code&amp;gt;&amp;lt;axis&amp;gt;...&amp;lt;/axis&amp;gt;&amp;lt;/code&amp;gt; section is omitted entirely, &amp;lt;code&amp;gt;{object-name}-axis&amp;lt;/code&amp;gt; will be used by default, where &amp;lt;code&amp;gt;{object-name}&amp;lt;/code&amp;gt; is the name of the object being animated (if we are animating more than one object, the first object is used). In the earlier example this would be &amp;lt;code&amp;gt;Rudder-axis&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
In the '''.ac''' file, specify a SURF with type (bottom 4 bits, 0=polygon, 1=closedline, 2=line) set to 2, and two vertices that define the axis. For example:&lt;br /&gt;
&lt;br /&gt;
    OBJECT poly&lt;br /&gt;
    name &amp;quot;aileron.l-axis&amp;quot;&lt;br /&gt;
    numvert 2&lt;br /&gt;
    3.2077502191170844 0.18160835055097943 4.055616960642423&lt;br /&gt;
    2.6758650763079 0.28024033462188946 6.477876098622225&lt;br /&gt;
    numsurf 1&lt;br /&gt;
    SURF 0x12&lt;br /&gt;
    mat 0&lt;br /&gt;
    refs 2&lt;br /&gt;
    0 0 0&lt;br /&gt;
    1 0 0&lt;br /&gt;
    kids 0&lt;br /&gt;
&lt;br /&gt;
Once the object-name used for the axis has been processed the geometry object will be hidden. This also allows a visual check for any axis objects that are not yet assigned.&lt;br /&gt;
&lt;br /&gt;
It is possible to reuse the same object definition multiple times within a single XML file. &lt;br /&gt;
&lt;br /&gt;
[[File:Canopy-animation-axis-object.png|small|Illustration of where an axis object (2017.2) can be placed for a canopy]]&lt;br /&gt;
&lt;br /&gt;
[[File:Gauges-knobs-animation-axis-object.png|small|Illustration of where an axis object (2017.2) can be placed for cockpit elements]]&lt;br /&gt;
&lt;br /&gt;
== Additional tags that can be used in most animations ==&lt;br /&gt;
=== Conditions ===&lt;br /&gt;
Multiple animations can make use of a conditional. Check &amp;lt;tt&amp;gt;$FGDATA/Docs/README.conditions&amp;lt;/tt&amp;gt; for some more details.&lt;br /&gt;
&lt;br /&gt;
* '''equals:''' property value (or second property) is equal to value/(first)property.&lt;br /&gt;
* '''greater-than:''' property value (or second property) is larger than value/(first)property.&lt;br /&gt;
* '''greater-than-equals:''' property value (or second property) is greater than or equal to value/(first)property.&lt;br /&gt;
* '''less-than:''' property value (or second property) is smaller than value/(first)property.&lt;br /&gt;
* '''less-than-equals:''' property value (or second property) is smaller than or equal to value/(first)property.&lt;br /&gt;
&lt;br /&gt;
The example below is true when n1 has a value greater than 25.&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
  &amp;lt;condition&amp;gt;&lt;br /&gt;
   &amp;lt;greater-than&amp;gt;&lt;br /&gt;
    &amp;lt;property&amp;gt;engines/engine[1]/n1&amp;lt;/property&amp;gt;&lt;br /&gt;
    &amp;lt;value&amp;gt;25&amp;lt;/value&amp;gt;&lt;br /&gt;
   &amp;lt;/greater-than&amp;gt;&lt;br /&gt;
  &amp;lt;/condition&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then there are some special tags:&lt;br /&gt;
&lt;br /&gt;
* '''and:'''&lt;br /&gt;
* '''not:'''&lt;br /&gt;
* '''or:'''&lt;br /&gt;
&lt;br /&gt;
In the example below, the condition is true when either n1 is greater than 25% or equal to 0%.&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
  &amp;lt;condition&amp;gt;&lt;br /&gt;
   &amp;lt;or&amp;gt;&lt;br /&gt;
    &amp;lt;greater-than&amp;gt;&lt;br /&gt;
     &amp;lt;property&amp;gt;engines/engine[1]/n1&amp;lt;/property&amp;gt;&lt;br /&gt;
     &amp;lt;value&amp;gt;25&amp;lt;/value&amp;gt;&lt;br /&gt;
    &amp;lt;/greater-than&amp;gt;&lt;br /&gt;
    &amp;lt;equals&amp;gt;&lt;br /&gt;
     &amp;lt;property&amp;gt;engines/engine[1]/n1&amp;lt;/property&amp;gt;&lt;br /&gt;
     &amp;lt;value&amp;gt;0&amp;lt;/value&amp;gt;&lt;br /&gt;
    &amp;lt;/equals&amp;gt;&lt;br /&gt;
   &amp;lt;/or&amp;gt;&lt;br /&gt;
  &amp;lt;/condition&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
An example of implementation into an animation looks as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;object-name&amp;gt;Object&amp;lt;/object-name&amp;gt;&lt;br /&gt;
  &amp;lt;type&amp;gt;rotate&amp;lt;/type&amp;gt;&lt;br /&gt;
  &amp;lt;property&amp;gt;suface-positions/left-aileron-pos-norm&amp;lt;/property&amp;gt;&lt;br /&gt;
  &amp;lt;factor&amp;gt;25&amp;lt;/factor&amp;gt;&lt;br /&gt;
  &amp;lt;condition&amp;gt;&lt;br /&gt;
   &amp;lt;greater-than&amp;gt;&lt;br /&gt;
    &amp;lt;property&amp;gt;suface-positions/left-aileron-pos-norm&amp;lt;/property&amp;gt;&lt;br /&gt;
    &amp;lt;value&amp;gt;10&amp;lt;/value&amp;gt;&lt;br /&gt;
   &amp;lt;/greater-than&amp;gt;&lt;br /&gt;
  &amp;lt;/condition&amp;gt;&lt;br /&gt;
  &amp;lt;center&amp;gt;&lt;br /&gt;
   &amp;lt;x-m&amp;gt;-1.50&amp;lt;/x-m&amp;gt;&lt;br /&gt;
   &amp;lt;y-m&amp;gt; 1   &amp;lt;/y-m&amp;gt;&lt;br /&gt;
   &amp;lt;z-m&amp;gt; 0.25&amp;lt;/z-m&amp;gt;&lt;br /&gt;
  &amp;lt;/center&amp;gt;&lt;br /&gt;
  &amp;lt;axis&amp;gt;&lt;br /&gt;
   &amp;lt;x&amp;gt;0&amp;lt;/x&amp;gt;&lt;br /&gt;
   &amp;lt;y&amp;gt;1&amp;lt;/y&amp;gt;&lt;br /&gt;
   &amp;lt;z&amp;gt;0&amp;lt;/z&amp;gt;&lt;br /&gt;
  &amp;lt;/axis&amp;gt;&lt;br /&gt;
 &amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Interpolation ===&lt;br /&gt;
For non-fixed factors, an interpolation &amp;quot;table&amp;quot; can be created. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
  &amp;lt;interpolation&amp;gt;&lt;br /&gt;
   &amp;lt;entry&amp;gt;&lt;br /&gt;
    &amp;lt;ind&amp;gt; 0.0&amp;lt;/ind&amp;gt;&lt;br /&gt;
    &amp;lt;dep&amp;gt; 0.0&amp;lt;/dep&amp;gt;&lt;br /&gt;
   &amp;lt;/entry&amp;gt;&lt;br /&gt;
   &amp;lt;entry&amp;gt;&lt;br /&gt;
    &amp;lt;ind&amp;gt; 0.667&amp;lt;/ind&amp;gt;&lt;br /&gt;
    &amp;lt;dep&amp;gt; 0.0&amp;lt;/dep&amp;gt;&lt;br /&gt;
   &amp;lt;/entry&amp;gt;&lt;br /&gt;
   &amp;lt;entry&amp;gt;&lt;br /&gt;
    &amp;lt;ind&amp;gt; 1.0&amp;lt;/ind&amp;gt;&lt;br /&gt;
    &amp;lt;dep&amp;gt; 0.5&amp;lt;/dep&amp;gt;&lt;br /&gt;
   &amp;lt;/entry&amp;gt;&lt;br /&gt;
  &amp;lt;/interpolation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The lines above represent the following table:&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
!Input&lt;br /&gt;
!Output&lt;br /&gt;
|-&lt;br /&gt;
|0.0&lt;br /&gt;
|0.0&lt;br /&gt;
|-&lt;br /&gt;
|0.667&lt;br /&gt;
|0.0&lt;br /&gt;
|-&lt;br /&gt;
|1.0&lt;br /&gt;
|0.5&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
You can add as many entries as you need. Interpolation tables are often used for gear animations (eg. to open doors during gear-movements and close them again once the gear is either retracted or fully extended).&lt;br /&gt;
&lt;br /&gt;
=== Expressions ===&lt;br /&gt;
For some animations it is possible to define complex animations by using [[Expressions|Expressions]]. This even allows to drive the animation from multiple properties without the need for additional Nasal scripts. Here is an example for a translate animation depending on two properties and the cosine function:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt;&lt;br /&gt;
     &amp;lt;type&amp;gt;translate&amp;lt;/type&amp;gt;&lt;br /&gt;
     &amp;lt;expression&amp;gt;&lt;br /&gt;
       &amp;lt;product&amp;gt;&lt;br /&gt;
         &amp;lt;property&amp;gt;/my/factor-property&amp;lt;/property&amp;gt;&lt;br /&gt;
         &amp;lt;cos&amp;gt;&lt;br /&gt;
           &amp;lt;deg2rad&amp;gt;&lt;br /&gt;
             &amp;lt;property&amp;gt;/my/angular-property&amp;lt;/property&amp;gt;&lt;br /&gt;
           &amp;lt;/deg2rad&amp;gt;&lt;br /&gt;
         &amp;lt;/cos&amp;gt;&lt;br /&gt;
       &amp;lt;/product&amp;gt;&lt;br /&gt;
     &amp;lt;/expression&amp;gt;&lt;br /&gt;
     [..]more elements[..]&lt;br /&gt;
 &amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Animations which can utilize [[Expressions|Expressions]] are: &lt;br /&gt;
* [[Howto:Animate_models#Translate|Translate]]&lt;br /&gt;
* [[Howto:Animate_models#Rotate|Rotate]]&lt;br /&gt;
* [[Howto:Animate_models#Scale|Scale]]&lt;br /&gt;
* [[Howto:Animate_models#Range|Range]]&lt;br /&gt;
* [[Howto:Animate_models#Blend|Blend]]&lt;br /&gt;
&lt;br /&gt;
See more detailed info at [[Expressions|Expressions]]&lt;br /&gt;
&lt;br /&gt;
== Lights ==&lt;br /&gt;
As of January 2021 FlightGear supports multiple light sources just like Project Rembrandt has always done.&lt;br /&gt;
[[Compositor#Lights|Adding lights to a model]]&lt;br /&gt;
&lt;br /&gt;
== Object animations ==&lt;br /&gt;
=== Alpha-test ===&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;type&amp;gt;alpha-test&amp;lt;/type&amp;gt;&lt;br /&gt;
  &amp;lt;object-name&amp;gt;Object&amp;lt;/object-name&amp;gt;&lt;br /&gt;
  &amp;lt;alpha-factor&amp;gt;0.01&amp;lt;/alpha-factor&amp;gt;&lt;br /&gt;
 &amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
This &amp;quot;animation&amp;quot; is a way to set an alpha test on a model branch. The effect is to avoid depth buffer writing of pixel that are not seen because they are transparent. This is particulary useful when modeling a metallic structure or a tree with a billboard. The threshold of transparency is set with the &amp;lt;alpha-factor&amp;gt; element.  See also [[Pixel testing in effects]].&lt;br /&gt;
&lt;br /&gt;
=== Blend ===&lt;br /&gt;
Blends an object with the surrounding. Comparable to a translucency animation. A value of 0 corresponds to no transparency, i.e. and ordinary solid object, and a value of 1 makes the object fully transparent, i.e., not visible at all.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;type&amp;gt;blend&amp;lt;/type&amp;gt;&lt;br /&gt;
  &amp;lt;property&amp;gt;/velocities/airspeed-kt&amp;lt;/property&amp;gt;&lt;br /&gt;
  &amp;lt;factor&amp;gt;0.00025&amp;lt;/factor&amp;gt;&lt;br /&gt;
  &amp;lt;min&amp;gt;0.2&amp;lt;/min&amp;gt;&lt;br /&gt;
  &amp;lt;max&amp;gt;0.7&amp;lt;/max&amp;gt;&lt;br /&gt;
 &amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* '''property:'''&lt;br /&gt;
* '''factor:'''&lt;br /&gt;
* '''min:'''&lt;br /&gt;
* '''max:'''&lt;br /&gt;
* '''[[Howto:Animate_models#Expressions|expression]]:''' is optional. For more details see [[Expressions|Expressions]]&lt;br /&gt;
&lt;br /&gt;
Note that when using the Project Rembrandt renderer, all transparent and translucent objects must be registered to display properly.  [[Project_Rembrandt#Registering_all_translucent_surfaces|More information here.]]&lt;br /&gt;
&lt;br /&gt;
=== Billboard ===&lt;br /&gt;
This faces an object towards the viewer. Often used on 2D objects, like clouds, trees and lights.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;type&amp;gt;billboard&amp;lt;/type&amp;gt;&lt;br /&gt;
  &amp;lt;object-name&amp;gt;Object&amp;lt;/object-name&amp;gt;&lt;br /&gt;
  &amp;lt;spherical type=&amp;quot;bool&amp;quot;&amp;gt;true&amp;lt;/spherical&amp;gt;&lt;br /&gt;
 &amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* '''spherical:'''&lt;br /&gt;
&lt;br /&gt;
=== Dist-scale ===&lt;br /&gt;
Used to scale an object, based on the distance to the viewer. &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;&amp;lt;ind&amp;gt;&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;&amp;lt;dep&amp;gt;&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; (independent and dependent) are the distance in meters and the scale at that distance.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;type&amp;gt;dist-scale&amp;lt;/type&amp;gt;&lt;br /&gt;
  &amp;lt;object-name&amp;gt;Object&amp;lt;/object-name&amp;gt;&lt;br /&gt;
  &amp;lt;interpolation&amp;gt;&lt;br /&gt;
   &amp;lt;entry&amp;gt;&lt;br /&gt;
    &amp;lt;ind&amp;gt;0&amp;lt;/ind&amp;gt;&lt;br /&gt;
    &amp;lt;dep&amp;gt;1&amp;lt;/dep&amp;gt;&lt;br /&gt;
   &amp;lt;/entry&amp;gt;&lt;br /&gt;
   &amp;lt;entry&amp;gt;&lt;br /&gt;
    &amp;lt;ind&amp;gt;300&amp;lt;/ind&amp;gt;&lt;br /&gt;
    &amp;lt;dep&amp;gt;4&amp;lt;/dep&amp;gt;&lt;br /&gt;
   &amp;lt;/entry&amp;gt;&lt;br /&gt;
   &amp;lt;entry&amp;gt;&lt;br /&gt;
    &amp;lt;ind&amp;gt;1500&amp;lt;/ind&amp;gt;&lt;br /&gt;
    &amp;lt;dep&amp;gt;8&amp;lt;/dep&amp;gt;&lt;br /&gt;
   &amp;lt;/entry&amp;gt;&lt;br /&gt;
  &amp;lt;/interpolation&amp;gt;&lt;br /&gt;
 &amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can optionally add [[#Center|&amp;amp;lt;center&amp;amp;gt;]] coordinates, to scale the object around that point.&lt;br /&gt;
&lt;br /&gt;
=== Flash ===&lt;br /&gt;
&lt;br /&gt;
Used to scale an object based on the cosine of the angle between the axis provided in the animation and the view vector.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;type&amp;gt;flash&amp;lt;/type&amp;gt;&lt;br /&gt;
  &amp;lt;object-name&amp;gt;Object&amp;lt;/object-name&amp;gt;&lt;br /&gt;
  &amp;lt;offset&amp;gt;0.0&amp;lt;/offset&amp;gt;&lt;br /&gt;
  &amp;lt;factor&amp;gt;1.0&amp;lt;/factor&amp;gt;&lt;br /&gt;
  &amp;lt;power&amp;gt;2&amp;lt;/power&amp;gt;&lt;br /&gt;
  &amp;lt;two-sides type=&amp;quot;bool&amp;quot;&amp;gt;false&amp;lt;/two-sides&amp;gt;&lt;br /&gt;
  &amp;lt;min&amp;gt;0.0&amp;lt;/min&amp;gt;&lt;br /&gt;
  &amp;lt;max&amp;gt;1.0&amp;lt;/max&amp;gt;&lt;br /&gt;
  &amp;lt;center&amp;gt;&lt;br /&gt;
   &amp;lt;x-m&amp;gt;0.0&amp;lt;/x-m&amp;gt;&lt;br /&gt;
   &amp;lt;y-m&amp;gt;0.0&amp;lt;/y-m&amp;gt;&lt;br /&gt;
   &amp;lt;z-m&amp;gt;0.0&amp;lt;/z-m&amp;gt;&lt;br /&gt;
  &amp;lt;/center&amp;gt;&lt;br /&gt;
  &amp;lt;axis&amp;gt;&lt;br /&gt;
   &amp;lt;x&amp;gt;0.0&amp;lt;/x&amp;gt;&lt;br /&gt;
   &amp;lt;y&amp;gt;-1&amp;lt;/y&amp;gt;&lt;br /&gt;
   &amp;lt;z&amp;gt;0.1&amp;lt;/z&amp;gt;&lt;br /&gt;
  &amp;lt;/axis&amp;gt;&lt;br /&gt;
 &amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* '''offset:'''&lt;br /&gt;
* '''factor:'''&lt;br /&gt;
* '''power:'''&lt;br /&gt;
* '''two-sides:''' if false, nothing is drawn if the cosine is negative.&lt;br /&gt;
* '''min:'''&lt;br /&gt;
* '''max:'''&lt;br /&gt;
&lt;br /&gt;
scale = factor * pow( cosine, power ) + offset&lt;br /&gt;
&lt;br /&gt;
scale is then clamped between min and max.&lt;br /&gt;
&lt;br /&gt;
and this scale factor is applied to the object, from the center specified. It works best if scale is less than 1. Otherwise, there will be clipping issues.&lt;br /&gt;
&lt;br /&gt;
=== Noshadow ===&lt;br /&gt;
This animation is used to make sure an object will cast no shadow.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;type&amp;gt;noshadow&amp;lt;/type&amp;gt;&lt;br /&gt;
  &amp;lt;object-name&amp;gt;Object&amp;lt;/object-name&amp;gt;&lt;br /&gt;
 &amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Range ===&lt;br /&gt;
: ''See also [[Modeling - Getting Started#Level of Detail (LOD)]].''&lt;br /&gt;
&lt;br /&gt;
To prevent objects -like instruments- being drawn when the aircraft is actually too far away for them to be seen anyway, a range animation is used. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;type&amp;gt;range&amp;lt;/type&amp;gt;&lt;br /&gt;
  &amp;lt;min-m&amp;gt;0&amp;lt;/min-m&amp;gt;&lt;br /&gt;
  &amp;lt;max-m&amp;gt;30&amp;lt;/max-m&amp;gt;&lt;br /&gt;
 &amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* '''min-m:''' the shortest distance (in meters) from the object center at which it is visible.&lt;br /&gt;
* '''max-m:''' the largest distance (in meters) from the object center at which it is visible.&lt;br /&gt;
&lt;br /&gt;
You could also use the generic level of detail (LOD) properties, which can be set by the user through View &amp;gt; Adjust LOD rangers: &lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Property&lt;br /&gt;
! Description&lt;br /&gt;
! Default value&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;tt&amp;gt;/sim/rendering/static-lod/bare&amp;lt;/tt&amp;gt;&lt;br /&gt;
| only a rough exterior model&lt;br /&gt;
| 30,000 m&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;tt&amp;gt;/sim/rendering/static-lod/rough&amp;lt;/tt&amp;gt; &lt;br /&gt;
| most should be visible&lt;br /&gt;
| 9,000 m&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;tt&amp;gt;/sim/rendering/static-lod/detailed&amp;lt;/tt&amp;gt; &lt;br /&gt;
| all details should be visible&lt;br /&gt;
| 1,500 m&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
The animation code will look like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot;&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;type&amp;gt;range&amp;lt;/type&amp;gt;&lt;br /&gt;
  &amp;lt;min-m&amp;gt;0&amp;lt;/min-m&amp;gt;&lt;br /&gt;
  &amp;lt;max-property&amp;gt;sim/rendering/static-lod/bare&amp;lt;/max-property&amp;gt;&lt;br /&gt;
 &amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can have both ranges (max and min) bound to a property, or just one of them.&lt;br /&gt;
* '''min-property:''' &lt;br /&gt;
* '''max-property:'''&lt;br /&gt;
* '''[[Howto:Animate_models#Expressions|expression]]:''' is optional. For more details see [[Expressions|Expressions]]&lt;br /&gt;
&lt;br /&gt;
=== Rotate ===&lt;br /&gt;
One of the most important and frequently used animations of all. It rotates an object to an absolute position in degrees, as provided by the property-value.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;object-name&amp;gt;Object&amp;lt;/object-name&amp;gt;&lt;br /&gt;
  &amp;lt;type&amp;gt;rotate&amp;lt;/type&amp;gt;&lt;br /&gt;
  &amp;lt;property&amp;gt;surface-positions/left-aileron-pos-norm&amp;lt;/property&amp;gt;&lt;br /&gt;
  &amp;lt;factor&amp;gt;25&amp;lt;/factor&amp;gt;&lt;br /&gt;
  &amp;lt;offset-deg&amp;gt;25&amp;lt;/offset-deg&amp;gt;&lt;br /&gt;
  &amp;lt;center&amp;gt;&lt;br /&gt;
   &amp;lt;x-m&amp;gt;-1.50&amp;lt;/x-m&amp;gt;&lt;br /&gt;
   &amp;lt;y-m&amp;gt; 1   &amp;lt;/y-m&amp;gt;&lt;br /&gt;
   &amp;lt;z-m&amp;gt; 0.25&amp;lt;/z-m&amp;gt;&lt;br /&gt;
  &amp;lt;/center&amp;gt;&lt;br /&gt;
  &amp;lt;axis&amp;gt;&lt;br /&gt;
   &amp;lt;x&amp;gt;0&amp;lt;/x&amp;gt;&lt;br /&gt;
   &amp;lt;y&amp;gt;1&amp;lt;/y&amp;gt;&lt;br /&gt;
   &amp;lt;z&amp;gt;0&amp;lt;/z&amp;gt;&lt;br /&gt;
  &amp;lt;/axis&amp;gt;&lt;br /&gt;
 &amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* '''factor:''' is optional.&lt;br /&gt;
* '''offset-deg:''' is optional. Offset in degrees.&lt;br /&gt;
* '''[[Howto:Animate_models#Expressions|expression]]:''' is optional. For more details see [[Expressions|Expressions]]&lt;br /&gt;
&lt;br /&gt;
=== Scale ===&lt;br /&gt;
A scale animation scales (resizes) an object. This can be either property-value dependant (first example) or a fixed scale (second example).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;type&amp;gt;scale&amp;lt;/type&amp;gt;&lt;br /&gt;
  &amp;lt;object-name&amp;gt;Object&amp;lt;/object-name&amp;gt;&lt;br /&gt;
  &amp;lt;property&amp;gt;sim/time/sun-angle-rad&amp;lt;/property&amp;gt;&lt;br /&gt;
  &amp;lt;x-min&amp;gt;1.0&amp;lt;/x-min&amp;gt;&lt;br /&gt;
  &amp;lt;y-min&amp;gt;1.0&amp;lt;/y-min&amp;gt;&lt;br /&gt;
  &amp;lt;z-min&amp;gt;1.0&amp;lt;/z-min&amp;gt;&lt;br /&gt;
  &amp;lt;x-factor&amp;gt;1.4&amp;lt;/x-factor&amp;gt;&lt;br /&gt;
  &amp;lt;y-factor&amp;gt;1.4&amp;lt;/y-factor&amp;gt;&lt;br /&gt;
  &amp;lt;z-factor&amp;gt;2.0&amp;lt;/z-factor&amp;gt;&lt;br /&gt;
 &amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* ?-min: the mimimum scale factor for each axis. If the property value would result in a smaller factor than this setting, the scale animation will hold.&lt;br /&gt;
* ?-factor: the scale factor for each axis (factor*property=scale factor).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;type&amp;gt;scale&amp;lt;/type&amp;gt;&lt;br /&gt;
  &amp;lt;x-offset&amp;gt;0.5&amp;lt;/x-offset&amp;gt;&lt;br /&gt;
  &amp;lt;y-offset&amp;gt;0.5&amp;lt;/y-offset&amp;gt;&lt;br /&gt;
  &amp;lt;z-offset&amp;gt;0.5&amp;lt;/z-offset&amp;gt;&lt;br /&gt;
 &amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* x.offset: the scale factor.&lt;br /&gt;
* Add [[#Center|&amp;amp;lt;center&amp;amp;gt;]] coordinates, to scale the object around that point.&lt;br /&gt;
* '''You can optionally use an [[Howto:Animate_models#Expressions|expression]] in the &amp;lt;factor&amp;gt; or &amp;lt;offset&amp;gt; inputs.''' For more details see [[Expressions|Expressions]]&lt;br /&gt;
&lt;br /&gt;
=== Select ===&lt;br /&gt;
This animation selects (or unselects) objects when certain conditions are true (or false). The example below shows the object when the n1 of engine[1] is higher than 25%.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;object-name&amp;gt;Object&amp;lt;/object-name&amp;gt;&lt;br /&gt;
  &amp;lt;type&amp;gt;select&amp;lt;/type&amp;gt;&lt;br /&gt;
  &amp;lt;condition&amp;gt;&lt;br /&gt;
   &amp;lt;greater-than&amp;gt;&lt;br /&gt;
    &amp;lt;property&amp;gt;engines/engine[0]/n1&amp;lt;/property&amp;gt;&lt;br /&gt;
    &amp;lt;value&amp;gt;25&amp;lt;/value&amp;gt;&lt;br /&gt;
   &amp;lt;/greater-than&amp;gt;&lt;br /&gt;
  &amp;lt;/condition&amp;gt;&lt;br /&gt;
 &amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Shader ===&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;type&amp;gt;shader&amp;lt;/type&amp;gt;&lt;br /&gt;
  &amp;lt;shader&amp;gt;chrome&amp;lt;/shader&amp;gt;&lt;br /&gt;
  &amp;lt;texture&amp;gt;chrome2.png&amp;lt;/texture&amp;gt;&lt;br /&gt;
  &amp;lt;object-name&amp;gt;Object&amp;lt;/object-name&amp;gt;&lt;br /&gt;
 &amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* '''shader:''' &lt;br /&gt;
* '''texture:''' path to the texture used by the shader.&lt;br /&gt;
&lt;br /&gt;
=== Spin ===&lt;br /&gt;
Very similar to [[#Rotate|rotate]], but the property provides a value in revolutions per minute (RPM) rather than an absolute position in degrees, and offset cannot be used.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;object-name&amp;gt;Object&amp;lt;/object-name&amp;gt;&lt;br /&gt;
  &amp;lt;type&amp;gt;spin&amp;lt;/type&amp;gt;&lt;br /&gt;
  &amp;lt;property&amp;gt;engines/engine[0]/n1&amp;lt;/property&amp;gt;&lt;br /&gt;
  &amp;lt;factor&amp;gt;25&amp;lt;/factor&amp;gt;&lt;br /&gt;
  &amp;lt;center&amp;gt;&lt;br /&gt;
   &amp;lt;x-m&amp;gt;-1.50&amp;lt;/x-m&amp;gt;&lt;br /&gt;
   &amp;lt;y-m&amp;gt; 1   &amp;lt;/y-m&amp;gt;&lt;br /&gt;
   &amp;lt;z-m&amp;gt; 0.25&amp;lt;/z-m&amp;gt;&lt;br /&gt;
  &amp;lt;/center&amp;gt;&lt;br /&gt;
  &amp;lt;axis&amp;gt;&lt;br /&gt;
   &amp;lt;x&amp;gt;0&amp;lt;/x&amp;gt;&lt;br /&gt;
   &amp;lt;y&amp;gt;1&amp;lt;/y&amp;gt;&lt;br /&gt;
   &amp;lt;z&amp;gt;0&amp;lt;/z&amp;gt;&lt;br /&gt;
  &amp;lt;/axis&amp;gt;&lt;br /&gt;
 &amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* '''factor:''' is optional.&lt;br /&gt;
&lt;br /&gt;
=== Timed ===&lt;br /&gt;
Swtiches between objects at specified intervals. This example switches between a lights-on model and a lights-off model. Lights on are shown 0.2 seconds, while lights off are displayed for 0.8 seconds.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;type&amp;gt;timed&amp;lt;/type&amp;gt;&lt;br /&gt;
  &amp;lt;object-name&amp;gt;BacklightOn&amp;lt;/object-name&amp;gt;&lt;br /&gt;
  &amp;lt;object-name&amp;gt;BacklightOff&amp;lt;/object-name&amp;gt;&lt;br /&gt;
  &amp;lt;use-personality type=&amp;quot;bool&amp;quot;&amp;gt;true&amp;lt;/use-personality&amp;gt;&lt;br /&gt;
  &amp;lt;branch-duration-sec&amp;gt;0.8&amp;lt;/branch-duration-sec&amp;gt;&lt;br /&gt;
  &amp;lt;branch-duration-sec&amp;gt;0.2&amp;lt;/branch-duration-sec&amp;gt;&lt;br /&gt;
 &amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Tracking ===&lt;br /&gt;
A ''locked-track animation'' can do exactly the same thing as the [https://docs.blender.org/manual/en/latest/animation/constraints/tracking/locked_track.html Locked Track constraint] available in Blender and can also be used to simulate simple inverse kinematic systems consisting of two bones connected with a revolute joint (aka hinge). For details see: [[Howto:Animate gear scissors using the tracking animation]]&lt;br /&gt;
&lt;br /&gt;
=== Translate ===&lt;br /&gt;
The same as [[#Textranslate|textranslate]], but this animation moves a whole object (so including fixed textures). The example below will move an object in the y-direction:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; border=&amp;quot;0&amp;quot; cellspacing=&amp;quot;0&amp;quot; &lt;br /&gt;
!Property value&lt;br /&gt;
!Output&lt;br /&gt;
|-&lt;br /&gt;
| -1&lt;br /&gt;
| -2.5&lt;br /&gt;
|-&lt;br /&gt;
| 0&lt;br /&gt;
| 2.5&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 7.5&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;type&amp;gt;translate&amp;lt;/type&amp;gt;&lt;br /&gt;
  &amp;lt;object-name&amp;gt;Object&amp;lt;/object-name&amp;gt;&lt;br /&gt;
  &amp;lt;property&amp;gt;controls/seat/pilot/position-norm&amp;lt;/property&amp;gt;&lt;br /&gt;
  &amp;lt;factor&amp;gt;5&amp;lt;/factor&amp;gt;&lt;br /&gt;
  &amp;lt;offset-m&amp;gt;2.5&amp;lt;/offset-m&amp;gt;&lt;br /&gt;
  &amp;lt;axis&amp;gt;&lt;br /&gt;
   &amp;lt;x&amp;gt;0&amp;lt;/x&amp;gt;&lt;br /&gt;
   &amp;lt;y&amp;gt;1&amp;lt;/y&amp;gt;&lt;br /&gt;
   &amp;lt;z&amp;gt;0&amp;lt;/z&amp;gt;&lt;br /&gt;
  &amp;lt;/axis&amp;gt;&lt;br /&gt;
 &amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When using interpolation tables such as:&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
&amp;lt;interpolation&amp;gt;&lt;br /&gt;
	&amp;lt;entry&amp;gt;&amp;lt;ind&amp;gt;0.0&amp;lt;/ind&amp;gt;&amp;lt;dep&amp;gt; 0&amp;lt;/dep&amp;gt;&amp;lt;/entry&amp;gt;&lt;br /&gt;
	&amp;lt;entry&amp;gt;&amp;lt;ind&amp;gt;0.666&amp;lt;/ind&amp;gt;&amp;lt;dep&amp;gt;0.18&amp;lt;/dep&amp;gt;&amp;lt;/entry&amp;gt;&lt;br /&gt;
	&amp;lt;entry&amp;gt;&amp;lt;ind&amp;gt;1.0&amp;lt;/ind&amp;gt;&amp;lt;dep&amp;gt;0.27&amp;lt;/dep&amp;gt;&amp;lt;/entry&amp;gt;&lt;br /&gt;
&amp;lt;/interpolation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
The first figure (&amp;lt;ind&amp;gt; refers to the value of the property associated with the object and the second figure (in &amp;lt;dep&amp;gt;) refers to the amount that the object moves in metres. For example, in this case, the object moves 18cm when the value reads 0.66 and 27cm when the value is 1.&lt;br /&gt;
&lt;br /&gt;
'''IF used WTIHOUT property''' : The object is placed at some (Value of Offset) meters away from its original position, along the virtual axis formed by said original position and the point with coordinates x/y/z defined in the &amp;lt;axis&amp;gt; tag.&lt;br /&gt;
Mathematically, assuming the Object to translate is (in the model space) placed at point A (x1, y1, z1) and you want to relocate it to point B (x2, y2, z2) then x2,y2,z2 are the values in the &amp;lt;axis&amp;gt; tag of the Translate animation and &amp;lt;offset&amp;gt; can be computed as SQRT((x2-x1)^2+(y2-y1)^2+(z2-z1)^2)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* '''factor:''' is optional.&lt;br /&gt;
* '''offset-m:''' is optional. Offset in meters.&lt;br /&gt;
* '''[[Howto:Animate_models#Expressions|expression]]:''' is optional. For more details see [[Expressions|Expressions]]&lt;br /&gt;
&lt;br /&gt;
== Material animation ==&lt;br /&gt;
An animation type that can be used in various ways. Of course you can combine the below mentiond systems into one (big) animation.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt; &lt;br /&gt;
  &amp;lt;type&amp;gt;material&amp;lt;/type&amp;gt; &lt;br /&gt;
  &amp;lt;object-name&amp;gt;Object&amp;lt;/object-name&amp;gt;&lt;br /&gt;
  &amp;lt;property-base&amp;gt;sim/model/c172p/material&amp;lt;/property-base&amp;gt;&lt;br /&gt;
  &amp;lt;global type=&amp;quot;bool&amp;quot;&amp;gt;true&amp;lt;/global&amp;gt; &amp;lt;!-- This tag is no longer supported --&amp;gt;&lt;br /&gt;
  ...&lt;br /&gt;
  lines as mentioned below&lt;br /&gt;
  ...&lt;br /&gt;
 &amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Optional:'''&lt;br /&gt;
* '''property-base:''' when using prop(erties), you might want to set a property-base. All props will be relative to this path.&lt;br /&gt;
* '''global (deprecated):''' by setting this to &amp;lt;tt&amp;gt;true&amp;lt;/tt&amp;gt;, all objects using the same material as the defined object(s) (via &amp;lt;tt&amp;gt;&amp;lt;object-name&amp;gt;&amp;lt;/tt&amp;gt;) will be affected by the animation. This is preferred to listing several objects in &amp;lt;object-name&amp;gt; tags. It's not only faster, but also doesn't break animations by forcing objects together. &amp;lt;span style=&amp;quot;color:red; text-decoration: underline;&amp;quot;&amp;gt;This tag is no longer supported&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
'''Notes:'''&lt;br /&gt;
* Numbers are clamped to 0.0-1.0, except &amp;quot;shininess&amp;quot;, which is clamped to 0-128.&lt;br /&gt;
* By appending &amp;lt;tt&amp;gt;-prop&amp;lt;/tt&amp;gt; each of the material properties can read its value from another property.&lt;br /&gt;
&lt;br /&gt;
=== Ambient ===&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
  &amp;lt;ambient&amp;gt;&lt;br /&gt;
   &amp;lt;red&amp;gt;1.0&amp;lt;/red&amp;gt;&lt;br /&gt;
   &amp;lt;green&amp;gt;0.2&amp;lt;/green&amp;gt;&lt;br /&gt;
   &amp;lt;blue&amp;gt;0.0&amp;lt;/blue&amp;gt;&lt;br /&gt;
  &amp;lt;/ambient&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Diffuse ===&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
  &amp;lt;diffuse&amp;gt;&lt;br /&gt;
   &amp;lt;red&amp;gt;1.0&amp;lt;/red&amp;gt;&lt;br /&gt;
   &amp;lt;green&amp;gt;0.2&amp;lt;/green&amp;gt;&lt;br /&gt;
   &amp;lt;blue&amp;gt;0.0&amp;lt;/blue&amp;gt;&lt;br /&gt;
  &amp;lt;/diffuse&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Emission ===&lt;br /&gt;
{{Main article|Howto: Illuminate faces}}&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
  &amp;lt;emission&amp;gt;&lt;br /&gt;
   &amp;lt;red&amp;gt;1.0&amp;lt;/red&amp;gt;&lt;br /&gt;
   &amp;lt;green&amp;gt;0.2&amp;lt;/green&amp;gt;&lt;br /&gt;
   &amp;lt;blue&amp;gt;0.0&amp;lt;/blue&amp;gt;&lt;br /&gt;
   &amp;lt;factor-prop&amp;gt;controls/lighting/panel-norm&amp;lt;/factor-prop&amp;gt;&lt;br /&gt;
  &amp;lt;/emission&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Emission colors are multiplied by the factor-prop value. 1 is maximum color intensity, while 0 is the minimum. Colors are calculated according to the [http://en.wikipedia.org/wiki/RGB_color_model RGB color model].&lt;br /&gt;
&lt;br /&gt;
=== Shininess ===&lt;br /&gt;
Shininess is clamped to 0-128.&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
  &amp;lt;shininess&amp;gt;105&amp;lt;/shininess&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Specular ===&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
  &amp;lt;specular&amp;gt;&lt;br /&gt;
   &amp;lt;red&amp;gt;1.0&amp;lt;/red&amp;gt;&lt;br /&gt;
   &amp;lt;green&amp;gt;0.2&amp;lt;/green&amp;gt;&lt;br /&gt;
   &amp;lt;blue&amp;gt;0.0&amp;lt;/blue&amp;gt;&lt;br /&gt;
  &amp;lt;/specular&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Texture ===&lt;br /&gt;
Used for the [[Livery over MP]] system.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
  &amp;lt;property-base&amp;gt;sim/model/livery&amp;lt;/property-base&amp;gt; &lt;br /&gt;
  &amp;lt;texture-prop&amp;gt;engine&amp;lt;/texture-prop&amp;gt; &lt;br /&gt;
  &amp;lt;texture&amp;gt;KLM.png&amp;lt;/texture&amp;gt; &lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Transparency ===&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
  &amp;lt;transparency&amp;gt;&lt;br /&gt;
   &amp;lt;alpha-prop&amp;gt;rotors/tail/rpm&amp;lt;/alpha-prop&amp;gt;&lt;br /&gt;
   &amp;lt;factor&amp;gt;-0.0015&amp;lt;/factor&amp;gt;&lt;br /&gt;
   &amp;lt;offset&amp;gt;1&amp;lt;/offset&amp;gt;&lt;br /&gt;
  &amp;lt;/transparency&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Threshold ===&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
  &amp;lt;threshold&amp;gt;0.001&amp;lt;/threshold&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Texture Animations ==&lt;br /&gt;
&lt;br /&gt;
Applying different matrix transformations to the textures of an object.&lt;br /&gt;
&lt;br /&gt;
=== Textranslate ===&lt;br /&gt;
A very important animation for cockpits! This animation moves textures over a surface.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;type&amp;gt;textranslate&amp;lt;/type&amp;gt;&lt;br /&gt;
  &amp;lt;object-name&amp;gt;Object&amp;lt;/object-name&amp;gt;&lt;br /&gt;
  &amp;lt;property&amp;gt;autopilot/settings/target-speed-kt&amp;lt;/property&amp;gt;&lt;br /&gt;
  &amp;lt;bias&amp;gt;0.0001&amp;lt;/bias&amp;gt;&lt;br /&gt;
  &amp;lt;factor&amp;gt;0.001&amp;lt;/factor&amp;gt;&lt;br /&gt;
  &amp;lt;step&amp;gt;100&amp;lt;/step&amp;gt;&lt;br /&gt;
  &amp;lt;axis&amp;gt;&lt;br /&gt;
   &amp;lt;x&amp;gt;0&amp;lt;/x&amp;gt;&lt;br /&gt;
   &amp;lt;y&amp;gt;1&amp;lt;/y&amp;gt;&lt;br /&gt;
  &amp;lt;/axis&amp;gt;&lt;br /&gt;
 &amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* '''bias:''' Adds an offset to the property before factor/step. A small value is needed to compensate for [http://en.wikipedia.org/wiki/Floating_point#Accuracy_problems floating point accuracy].&lt;br /&gt;
* '''factor:''' property * factor * texture width/height = the amount of pixels that the texture should be translated. If your texture is 256 pixels, an textranslate of 0.1 will result in the texture moving with 26 pixels, into the direction specified by the axis settings.&lt;br /&gt;
* '''step:''' the step size at which the texture is translated. If this is set to 0.1, the texture will only be translated at 0.1, 0.2, 0.3 etc.&lt;br /&gt;
* '''axis:''' the direction in which the texture is translated. Y is up/down, while X is left/right.&lt;br /&gt;
&lt;br /&gt;
=== Texrotate ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
&amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;object-name&amp;gt;Object&amp;lt;/object-name&amp;gt;&lt;br /&gt;
  &amp;lt;type&amp;gt;texrotate&amp;lt;/type&amp;gt;&lt;br /&gt;
  &amp;lt;property&amp;gt;some/property/path&amp;lt;/property&amp;gt;&lt;br /&gt;
  &amp;lt;factor&amp;gt;25&amp;lt;/factor&amp;gt;&lt;br /&gt;
  &amp;lt;offset-deg&amp;gt;25&amp;lt;/offset-deg&amp;gt;&lt;br /&gt;
  &amp;lt;center&amp;gt;&lt;br /&gt;
    &amp;lt;x&amp;gt;0.5&amp;lt;/x&amp;gt;&lt;br /&gt;
    &amp;lt;y&amp;gt;0.5&amp;lt;/y&amp;gt;&lt;br /&gt;
    &amp;lt;z&amp;gt;0&amp;lt;/z&amp;gt;&lt;br /&gt;
  &amp;lt;/center&amp;gt;&lt;br /&gt;
  &amp;lt;axis&amp;gt;&lt;br /&gt;
    &amp;lt;x&amp;gt;0&amp;lt;/x&amp;gt;&lt;br /&gt;
    &amp;lt;y&amp;gt;0&amp;lt;/y&amp;gt;&lt;br /&gt;
    &amp;lt;z&amp;gt;1&amp;lt;/z&amp;gt;&lt;br /&gt;
  &amp;lt;/axis&amp;gt;&lt;br /&gt;
&amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Textrapezoid ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
&amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;type&amp;gt;textrapezoid&amp;lt;/type&amp;gt;&lt;br /&gt;
  &amp;lt;object-name&amp;gt;HUD.l.canvas&amp;lt;/object-name&amp;gt;&lt;br /&gt;
  &amp;lt;property&amp;gt;/hud/trapezoid-correction&amp;lt;/property&amp;gt;&lt;br /&gt;
  &amp;lt;side&amp;gt;bottom&amp;lt;/side&amp;gt;&lt;br /&gt;
&amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* '''side''': side of quad which should be scaled (''top'' (default)/''right''/''bottom''/''left'')&lt;br /&gt;
&lt;br /&gt;
=== Texmultiple ===&lt;br /&gt;
&lt;br /&gt;
Only one texture matrix can be applied to each object. With ''textmultiple'' multiple texture animations can be combined into a single matrix, applied to the specified object.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
&amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;type&amp;gt;texmultiple&amp;lt;/type&amp;gt;&lt;br /&gt;
  &amp;lt;object-name&amp;gt;HUD.l.canvas&amp;lt;/object-name&amp;gt;&lt;br /&gt;
  &amp;lt;transform&amp;gt;&lt;br /&gt;
    &amp;lt;subtype&amp;gt;textranslate&amp;lt;/subtype&amp;gt;&lt;br /&gt;
    &amp;lt;property&amp;gt;/hud/offset-x&amp;lt;/property&amp;gt;&lt;br /&gt;
    &amp;lt;axis&amp;gt;&lt;br /&gt;
     &amp;lt;x&amp;gt;1&amp;lt;/x&amp;gt;&lt;br /&gt;
     &amp;lt;y&amp;gt;0&amp;lt;/y&amp;gt;&lt;br /&gt;
     &amp;lt;z&amp;gt;0&amp;lt;/z&amp;gt;&lt;br /&gt;
   &amp;lt;/axis&amp;gt;&lt;br /&gt;
  &amp;lt;/transform&amp;gt;&lt;br /&gt;
  &amp;lt;transform&amp;gt;&lt;br /&gt;
    &amp;lt;subtype&amp;gt;textranslate&amp;lt;/subtype&amp;gt;&lt;br /&gt;
    &amp;lt;property&amp;gt;/hud/offset-y&amp;lt;/property&amp;gt;&lt;br /&gt;
    &amp;lt;axis&amp;gt;&lt;br /&gt;
     &amp;lt;x&amp;gt;0&amp;lt;/x&amp;gt;&lt;br /&gt;
     &amp;lt;y&amp;gt;1&amp;lt;/y&amp;gt;&lt;br /&gt;
     &amp;lt;z&amp;gt;0&amp;lt;/z&amp;gt;&lt;br /&gt;
   &amp;lt;/axis&amp;gt;&lt;br /&gt;
  &amp;lt;/transform&amp;gt;&lt;br /&gt;
  &amp;lt;transform&amp;gt;&lt;br /&gt;
    &amp;lt;subtype&amp;gt;textrapezoid&amp;lt;/subtype&amp;gt;&lt;br /&gt;
    &amp;lt;property&amp;gt;/hud/trapezoid-correction&amp;lt;/property&amp;gt;&lt;br /&gt;
  &amp;lt;/transform&amp;gt;&lt;br /&gt;
&amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Object interaction animations ==&lt;br /&gt;
=== Enable-hot ===&lt;br /&gt;
Scenery objects are automatically defined as solid by FlightGear, meaning that an aircraft can taxi on them and/or crash when touching. For certain objects (groundmarkings, beacon light-beams etc.) this might be an unwanted feature. The solidness can be disabled with the following animation:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt;&lt;br /&gt;
  &amp;lt;object-name&amp;gt;Object&amp;lt;/object-name&amp;gt;&lt;br /&gt;
  &amp;lt;enable-hot type=&amp;quot;bool&amp;quot;&amp;gt;false&amp;lt;/enable-hot&amp;gt;&lt;br /&gt;
 &amp;lt;/animation&amp;gt;&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* '''enable-hot:''' can be either true or false. Remember that objects are automatically solid, so it should not be necessary to set this at all when wanting solidness.&lt;br /&gt;
&lt;br /&gt;
=== Interactions ===&lt;br /&gt;
&amp;lt;source&amp;gt;&lt;br /&gt;
 &amp;lt;animation&amp;gt; &lt;br /&gt;
  &amp;lt;type&amp;gt;interaction&amp;lt;/type&amp;gt; &lt;br /&gt;
  &amp;lt;object-name&amp;gt;Object&amp;lt;/object-name&amp;gt; &lt;br /&gt;
  &amp;lt;interaction-type&amp;gt;carrier-wire&amp;lt;/interaction-type&amp;gt; &lt;br /&gt;
 &amp;lt;/animation&amp;gt; &lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* '''interaction-type:''' can have the following values:&lt;br /&gt;
**'''carrier-catapult:'''&lt;br /&gt;
** '''carrier-wire:''' makes the object act as an arresting wire, as used on [[aircraft carrier]]s.&lt;br /&gt;
&lt;br /&gt;
== Direct manipulation animations ==&lt;br /&gt;
=== Knob / slider (v. 2.11-) ===&lt;br /&gt;
{{Main article|Knob / slider animation}}&lt;br /&gt;
&lt;br /&gt;
=== Pick ===&lt;br /&gt;
{{Main article|Howto: Make a clickable panel#Pick}}&lt;br /&gt;
&lt;br /&gt;
=== Touch ===&lt;br /&gt;
&lt;br /&gt;
The touch animation provides the normalized coordinates of a touch (or click) event on a 2d surface. The coordinates are passed in the argument and can be accessed using cmdarg() in Nasal.&lt;br /&gt;
&lt;br /&gt;
* Touch animation is designed to work with a quad that is being used as a Canvas placement (display).&lt;br /&gt;
* The touch animation must not be combined with a pick animation on the same object.&lt;br /&gt;
* More info here: [[Touch Animation]]&lt;br /&gt;
&lt;br /&gt;
==== touch example ====&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;animation&amp;gt;&lt;br /&gt;
        &amp;lt;type&amp;gt;touch&amp;lt;/type&amp;gt;&lt;br /&gt;
        &amp;lt;visible&amp;gt;true&amp;lt;/visible&amp;gt;&lt;br /&gt;
        &amp;lt;object-name&amp;gt;CanvasPlacement&amp;lt;/object-name&amp;gt;&lt;br /&gt;
        &amp;lt;action&amp;gt;&lt;br /&gt;
            &amp;lt;touch&amp;gt;0&amp;lt;/touch&amp;gt;&lt;br /&gt;
            &amp;lt;repeatable&amp;gt;false&amp;lt;/repeatable&amp;gt;&lt;br /&gt;
            &amp;lt;binding&amp;gt;&lt;br /&gt;
                &amp;lt;command&amp;gt;nasal&amp;lt;/command&amp;gt;&lt;br /&gt;
                &amp;lt;script&amp;gt;print(&amp;quot;touch input (&amp;quot;,cmdarg().getNode(&amp;quot;x&amp;quot;).getValue(),&amp;quot;,&amp;quot;,cmdarg().getNode(&amp;quot;y&amp;quot;).getValue())&amp;lt;/script&amp;gt;&lt;br /&gt;
            &amp;lt;/binding&amp;gt;&lt;br /&gt;
        &amp;lt;/action&amp;gt;&lt;br /&gt;
    &amp;lt;/animation&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Shadow Handling ==&lt;br /&gt;
There exist several possibilites for handling of shadows. &amp;lt;br /&amp;gt;&lt;br /&gt;
See '''[[ALS_technical_notes|ALS Technical Notes]]''' and more specific '''[[ALS_technical_notes#ALS_fuselage_shadow_effect|Fuselage Shadow Effect with ALS]]''' for a relatively simple shadow handling.&amp;lt;br /&amp;gt;&lt;br /&gt;
See '''[[Project Rembrandt]]''' which - amongst other functionality - implements a very realistic shadow mapping.&lt;br /&gt;
As of January 2021 Project Rembrandt was replaced by the '''[[Compositor]]''' renderer which combines both Project Rembrandt and ALS in a single rendering engine. This also means that the Fuselage Shadow Effect with ALS is now deprecated.&lt;br /&gt;
&lt;br /&gt;
== References ==&lt;br /&gt;
{{Appendix|all|&lt;br /&gt;
* {{cite web |url=http://www.opensubscriber.com/message/flightgear-devel@flightgear.org/958955.html |title=&amp;quot;material&amp;quot; animation (and the bo105 as an example) |first=Melchior |last=Franz |date=22 March 2005 |work=FlightGear-devel mailinglist }}&lt;br /&gt;
* {{cite web |url=http://www.mail-archive.com/flightgear-devel@lists.sourceforge.net/msg01546.html |title=flash animation |first=Frederic |last=Bouvier |date=22 Feb 2006 |work=FlightGear-devel mailinglist }}&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Related content ==&lt;br /&gt;
=== Wiki articles ===&lt;br /&gt;
* [[MP Fallback models]]&lt;br /&gt;
* [[Howto:Animate gear scissors]]&lt;br /&gt;
* [[Howto:Animate helicopters]]&lt;br /&gt;
* [[Howto:Creating 3D instruments]]&lt;br /&gt;
&lt;br /&gt;
=== Forum topics ===&lt;br /&gt;
* {{forum link|t=37353|title=3d models, how to produce them in an understandable way}} (April 2020-) - Touches on the subject of not using LOD range animations in scenery models.&lt;br /&gt;
* {{forum link|t=36545|title=speedo Drum settings}} (November 2019) - Animating a mechanical multi-digit drum counter&lt;br /&gt;
&lt;br /&gt;
[[Category:Aircraft enhancement|Animate models]]&lt;br /&gt;
[[Category:Howto|Animate models]]&lt;br /&gt;
[[Category:Modeling|Animate models]]&lt;br /&gt;
[[Category:Scenery enhancement|Animate models]]&lt;/div&gt;</summary>
		<author><name>TheEagle</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Nasal_library/math&amp;diff=135746</id>
		<title>Nasal library/math</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Nasal_library/math&amp;diff=135746"/>
		<updated>2022-10-26T14:04:49Z</updated>

		<summary type="html">&lt;p&gt;TheEagle: Fix inverted x, y params for math.atan2&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Nasal Navigation|nocat=1}}&lt;br /&gt;
This page contains documentation for the '''&amp;lt;code&amp;gt;math&amp;lt;/code&amp;gt; namespace''' in [[Nasal]]. This namespace provides various mathematical [[#Functions|functions]] and [[#Variables|variables]]. The &amp;lt;code&amp;gt;math&amp;lt;/code&amp;gt; namespace is sourced from two files:&lt;br /&gt;
* {{simgear file|simgear/nasal/mathlib.c}}&lt;br /&gt;
* {{fgdata file|Nasal/math.nas}}&lt;br /&gt;
&lt;br /&gt;
{{tip|Copy &amp;amp; paste the examples into your [[Nasal Console]] and execute them to see what they do.|width=70%}}&lt;br /&gt;
&lt;br /&gt;
== Functions ==&lt;br /&gt;
=== abs() ===&lt;br /&gt;
&amp;lt;!-- &lt;br /&gt;
Duplicate of [[Nasal library#abs()]]&lt;br /&gt;
--&amp;gt;{{Nasal doc&lt;br /&gt;
|syntax = math.abs(x);&lt;br /&gt;
|source = {{fgdata file|Nasal/math.nas|t=Source}}&lt;br /&gt;
|text = This simple function returns the {{wikipedia|absolute value|noicon=1}} of the provided number.&lt;br /&gt;
|param1 = x&lt;br /&gt;
|param1text = This argument is required and should be a number.&lt;br /&gt;
|example1 = print(math.abs(1)); # prints &amp;quot;1&amp;quot;&lt;br /&gt;
|example2 = print(math.abs(-1)); # prints &amp;quot;1&amp;quot;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== acos() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = math.acos(x);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/mathlib.c|l=196|t=Source}}&lt;br /&gt;
|text = Implements the arccosine trigonometric function. Returns an angle in radians from the given ratio.&lt;br /&gt;
|param1 = x&lt;br /&gt;
|param1text = Mandatory number that should be a ratio in the range -1 ≤ x ≤ 1.&lt;br /&gt;
|example1 = var ratio = 1 / 1.414;&lt;br /&gt;
print(math.acos(ratio) * R2D); # prints the angle in degrees (approximately 45)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== asin() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = math.asin(x);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/mathlib.c|l=187|t=Source}}&lt;br /&gt;
|text = Implements the arcsine trigonometric function. Returns an angle in radians from the given ratio.&lt;br /&gt;
|param1 = x&lt;br /&gt;
|param1text = Mandatory number that should be a ratio in the range -1 ≤ x ≤ 1.&lt;br /&gt;
|example1 = var ratio = 1 / 1.414;&lt;br /&gt;
print(math.asin(ratio) * R2D); # prints the angle in degrees (approximately 45)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== atan2() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = math.atan2(x, y);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/mathlib.c|l=78|t=Source}}&lt;br /&gt;
|text = Implements the {{wikipedia|Atan2|two-argument version}} of the arctangent trigonometric function. Returns an angle in radians between the positive x-axis of a plane and the point given by the coordinates (x, y) on it.&lt;br /&gt;
|param1 = x&lt;br /&gt;
|param1text = Mandatory x-axis coordinate as a number.&lt;br /&gt;
|param2 = y&lt;br /&gt;
|param2text = Mandatory y-axis coordinate as a number.&lt;br /&gt;
|example1 = var x = 1;&lt;br /&gt;
var y = 1;&lt;br /&gt;
print(math.atan2(x, y) * R2D); # prints the angle in degrees (45)&lt;br /&gt;
|example2 = var x = -1;&lt;br /&gt;
var y = -1;&lt;br /&gt;
print(math.atan2(x, y) * R2D); # prints the angle in degrees (-135)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== avg() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = math.avg(x[, y[, z[, ...]]]);&lt;br /&gt;
|source = {{fgdata file|Nasal/math.nas|t=Source}}&lt;br /&gt;
|version = 2.4&lt;br /&gt;
|commit = {{fgdata commit|36c48c|t=Commit}}&lt;br /&gt;
|text = Returns the average of the given numbers. There must be at least one argument, but there may be as many as you like.&lt;br /&gt;
|example1 = print(math.avg(1, 2, 3, 4)); # prints 2.5&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== ceil() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = math.ceil(x);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/mathlib.c|l=97|t=Source}}&lt;br /&gt;
|version = 2.10&lt;br /&gt;
|commit = {{simgear commit|830bc3|t=Commit}}&lt;br /&gt;
|text = Returns the {{wikipedia|Ceiling function|ceiling}} of a number, that is, the smallest following integer.&lt;br /&gt;
|param1 = x&lt;br /&gt;
|param1text = Number to return the ceiling of.&lt;br /&gt;
|example1 = print(math.ceil(2)); # prints 2&lt;br /&gt;
|example2 = print(math.ceil(2.1)); # prints 3&lt;br /&gt;
|example3 = print(math.ceil(-2.9)); # prints -2&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== clamp() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = math.clamp(x, min, max);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/mathlib.c|l=117|t=Source}}&lt;br /&gt;
|version = 2.10&lt;br /&gt;
|commit = {{simgear commit|830bc3|t=Commit}}&lt;br /&gt;
|text = Clamps a number so that is within the range given. All arguments are mandatory and must be numbers.&lt;br /&gt;
{{Note|Up until FG v2016.2, the clamping algorithm was not correct, meaning that the function will not behave correctly in FlightGear versions below 2016.2. It was fixed by {{simgear commit|bba11c}}}}.&lt;br /&gt;
|param1 = x&lt;br /&gt;
|param1text = The number to be clamped.&lt;br /&gt;
|param2 = min&lt;br /&gt;
|param2text = Lower limit of the clamping range.&lt;br /&gt;
|param3 = max&lt;br /&gt;
|param3text = Upper limit of the clamping range.&lt;br /&gt;
|example1text = Note that none of these examples will work properly in FlightGear versions below 2016.2.&lt;br /&gt;
|example1 = print(math.clamp(5, 1, 10)); # prints 5&lt;br /&gt;
|example2 = print(math.clamp(0, 1, 10)); # prints 1&lt;br /&gt;
|example3 = print(math.clamp(12, 1, 10)); # prints 10&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== cos() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = math.cos(x);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/mathlib.c|l=32|t=Source}}&lt;br /&gt;
|text = Implements the cosine trigonometric function. Returns a ratio from a given angle in radians.&lt;br /&gt;
|param1 = x&lt;br /&gt;
|param1text = Mandatory number that should be an angle in radians.&lt;br /&gt;
|example1 = var angle = 180 * D2R;&lt;br /&gt;
print(math.cos(angle)); # prints the ratio (-1)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== exp() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = math.exp(x);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/mathlib.c|l=41|t=Source}}&lt;br /&gt;
|text = Returns the value of ''{{wikipedia|E (mathematical constant)|e|noicon=1}}'' raised to the given poweer.&lt;br /&gt;
|param1 = x&lt;br /&gt;
|param1text = Mandatory number which is the value of the exponent.&lt;br /&gt;
|example1 = printf(&amp;quot;%.4f&amp;quot;, math.exp(2)); # prints 7.3891&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== floor() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = math.floor(x);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/mathlib.c|l=88|t=Source}}&lt;br /&gt;
|version = 2.10&lt;br /&gt;
|commit = {{simgear commit|830bc3|t=Commit}}&lt;br /&gt;
|text = Returns the {{wikipedia|Floor function|floor}} of a number, that is, the largest previous integer.&lt;br /&gt;
|param1 = x&lt;br /&gt;
|param1text = Number to return the floor of.&lt;br /&gt;
|example1 = print(math.floor(2)); # prints 2&lt;br /&gt;
|example2 = print(math.floor(2.1)); # prints 2&lt;br /&gt;
|example3 = print(math.floor(-2.9)); # prints -3&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== fmod() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = math.fmod(x, y);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/mathlib.c|l=106|t=Source}}&lt;br /&gt;
|version = 2.10&lt;br /&gt;
|commit = {{simgear commit|830bc3|t=Commit}}&lt;br /&gt;
|text = Returns the result of the modulo operation of the given numbers, that is, is returns the remainder of '''x''' divided by '''y'''. Unlike {{func link|mod()|page=this}}, this function uses the C library function {{func link|fmod()|link=http://www.cplusplus.com/reference/cmath/fmod/}}, and has different (and more correct) behavior when negative arguments are passed to it (compare example 3 with example 3 of {{func link|mod()|page=this}}). Both arguments are mandatory and must be numbers.&lt;br /&gt;
{{Note|This function was initially added &amp;lt;code&amp;gt;math.mod()&amp;lt;/code&amp;gt;, but this was overridden by [[#mod.28.29|another version]] which already existed in FGData. In FlightGear 3.0, this function was {{simgear commit|ad83e7|t=renamed}} to &amp;lt;code&amp;gt;fmod()&amp;lt;/code&amp;gt;.}}&lt;br /&gt;
|param1 = x&lt;br /&gt;
|param1text = The dividend.&lt;br /&gt;
|param2 = y&lt;br /&gt;
|param2text = The divisor. If this argument is 0, a floating point error will be generated.&lt;br /&gt;
|example1 = print(math.fmod(5, 2)); # prints 1 (2 goes into 5 twice with a remainder of 1; 2 * 2 + 1 = 5)&lt;br /&gt;
|example2 = print(math.fmod(10, 5)); # prints 0 (5 goes into 10 twice with a remainder of 0; 5 * 2 + 0 = 10)&lt;br /&gt;
|example3 = print(math.fmod(-5, 2)); # prints -1 (2 goes into -5 -2 times with a remainder of -1; 2 * -2 + -1 = -5)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== ln() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = math.ln(x);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/mathlib.c|l=50|t=Source}}&lt;br /&gt;
|text = Returns the natural (base ''e'') logarithm of the given number.&lt;br /&gt;
|param1 = x&lt;br /&gt;
|param1text = Number to return the logarithm of. Must be greater than 0.&lt;br /&gt;
|example1 = printf(&amp;quot;%.4f&amp;quot;, math.ln(60)); # prints 4.0943&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== log10() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = math.log10(x);&lt;br /&gt;
|source = {{fgdata file|Nasal/math.nas|t=Source}}&lt;br /&gt;
|text = Returns the common (base 10) logarithm of the given number.&lt;br /&gt;
|param1 = x&lt;br /&gt;
|param1text = Number to return the logarithm of. Must be greater than 0.&lt;br /&gt;
|example1 = printf(&amp;quot;%g&amp;quot;, math.log10(1000)); # prints 3&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== max() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = math.max(x[, y[, z[, ...]]]);&lt;br /&gt;
|source = {{fgdata file|Nasal/math.nas|t=Source}}&lt;br /&gt;
|version = 2.4&lt;br /&gt;
|commit = {{fgdata commit|36c48c|t=Commit}}&lt;br /&gt;
|text = Returns the highest number of the given numbers. There must be at least one argument, but there may be as many as you like.&lt;br /&gt;
|example1 = print(math.max(1, 2, 3, 4)); # prints 4&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== min() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = math.min(x[, y[, z[, ...]]]);&lt;br /&gt;
|source = {{fgdata file|Nasal/math.nas|t=Source}}&lt;br /&gt;
|version = 2.4&lt;br /&gt;
|commit = {{fgdata commit|36c48c|t=Commit}}&lt;br /&gt;
|text = Returns the lowest number of the given numbers. There must be at least one argument, but there may be as many as you like.&lt;br /&gt;
|example1 = print(math.min(1, 2, 3, 4)); # prints 1&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== mod() ===&lt;br /&gt;
{{See also|Nasal library#fmod()}}&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = math.mod(x, y);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/mathlib.c|l=106|t=Source}}&lt;br /&gt;
|text = Returns the result of the modulo operation of the given numbers, that is, is returns the remainder of '''x''' divided by '''y'''. Note that this function does not not return correct answers when either or both the arguments are negative (see example 3), unlike &amp;lt;code&amp;gt;[[#fmod.28.29|fmod()]]&amp;lt;/code&amp;gt;. Both arguments are mandatory and must be numbers.&lt;br /&gt;
|param1 = x&lt;br /&gt;
|param1text = The dividend.&lt;br /&gt;
|param2 = y&lt;br /&gt;
|param2text = The divisor. If this argument is 0, infinity will be returned (printed as &amp;lt;code&amp;gt;0e-00&amp;lt;/code&amp;gt;).&lt;br /&gt;
|example1 = print(math.mod(5, 2)); # prints 1 (2 goes into 5 twice with a remainder of 1; 2 * 2 + 1 = 5)&lt;br /&gt;
|example2 = print(math.mod(10, 5)); # prints 0 (5 goes into 10 twice with a remainder of 0; 5 * 2 + 0 = 10)&lt;br /&gt;
|example3 = print(math.mod(-5, 4)); # prints 3 (incorrect, should be -1)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== periodic() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = math.periodic(min, max, x);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/mathlib.c|l=130|t=Source}}&lt;br /&gt;
|version = 2.10&lt;br /&gt;
|commit = {{simgear commit|830bc3|t=Commit}}&lt;br /&gt;
|text = Normalizes the given number (e.g., a bearing) to be within a set periodic range (e.g., compass bearings, which go from 0 to 360). All the arguments are mandatory and must be numbers.&lt;br /&gt;
|param1 = min&lt;br /&gt;
|param1text = Lower boundary of the periodic range.&lt;br /&gt;
|param2 = max&lt;br /&gt;
|param2text = Upper boundary of the periodic range&lt;br /&gt;
|param3 = x&lt;br /&gt;
|param3text = Number to normalize.&lt;br /&gt;
|example1 = var norm_360 = func(a){&lt;br /&gt;
    return math.periodic(0, 360, a);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
print(norm_360(-90)); # prints 270&lt;br /&gt;
print(norm_360(45)); # prints 45&lt;br /&gt;
|example2 = var norm_180 = func(a){&lt;br /&gt;
    return math.periodic(-180, 180, a);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
print(norm_180(45)); # prints 45&lt;br /&gt;
print(norm_180(270)); # prints -90&lt;br /&gt;
|example3 = print(math.periodic(0, 10, 15)); # prints 5&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== pow() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = math.pow(b, n);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/mathlib.c|l=68|t=Source}}&lt;br /&gt;
|text = Returns the base '''b''' raised to the '''n''' power. Both arguments are mandatory and must be numbers.&lt;br /&gt;
|param1 = b&lt;br /&gt;
|param1text = Base.&lt;br /&gt;
|param2 = n&lt;br /&gt;
|param2text = Exponent.&lt;br /&gt;
|example1 = print(math.pow(2, 2)); # prints 4&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== round() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = math.round(x[, p]);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/mathlib.c|l=153|t=Source}}&lt;br /&gt;
|version = 3.0&lt;br /&gt;
|commit = {{simgear commit|ad83e7|t=Commit}}&lt;br /&gt;
|text = Rounds '''x''' to the optional precision ('''p'''). If the fractional part of '''x''' is 0.5, it will always be rounded up. Both argument must be numbers. &lt;br /&gt;
|param1 = x&lt;br /&gt;
|param1text = Mandatory number to round.&lt;br /&gt;
|param2 = p&lt;br /&gt;
|param2text = Optional precision to round to. For example, if this is set to 10, '''x''' will be rounded to the nearest 10. Defaults to 1, and should be greater than or equal to 1 for correct output. For rounding to decimal places, it is better to use {{func link|sprintf}}.&lt;br /&gt;
|example1 = print(math.round(4.2)); # prints 4&lt;br /&gt;
|example2 = print(math.round(4.7)); # prints 5&lt;br /&gt;
|example3 = print(math.round(4.5)); # prints 5&lt;br /&gt;
|example4 = print(math.round(127, 10)); # prints 130&lt;br /&gt;
|example5 = print(math.round(456, 100)); # prints 500&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== sin() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = math.sin(x);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/mathlib.c|l=23|t=Source}}&lt;br /&gt;
|text = Implements the sine trigonometric function. Returns a ratio from a given angle in radians.&lt;br /&gt;
|param1 = x&lt;br /&gt;
|param1text = Mandatory number that should be an angle in radians.&lt;br /&gt;
|example1 = var angle = 90 * D2R;&lt;br /&gt;
print(math.sin(angle)); # prints the ratio (1)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== sgn() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = math.sgn(x);&lt;br /&gt;
|source = {{fgdata file|Nasal/math.nas|t=Source}}&lt;br /&gt;
|text = Returns the result of the sign function on the given number. If '''x''' is less than 0, -1 one is returned. If '''x''' equals 0, 0 is returned. If '''x''' is greater than 0, 1 is returned.&lt;br /&gt;
|param1 = x&lt;br /&gt;
|param1text = Mandatory number to return the sign of.&lt;br /&gt;
|example1 = print(math.sgn(-6)); # prints -1&lt;br /&gt;
|example2 = print(math.sgn(0)); # prints 0&lt;br /&gt;
|example3 = print(math.sgn(12)); # prints 1&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== sqrt() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = math.sqrt(x);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/mathlib.c|l=59|t=Source}}&lt;br /&gt;
|text = Returns the square root of the given number.&lt;br /&gt;
|param1 = x&lt;br /&gt;
|param1text = Mandatory number to return the square root of. Must be greater than or equal to 0.&lt;br /&gt;
|example1 = print(math.sqrt(9)); # prints 3&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== tan() ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = math.tan(x);&lt;br /&gt;
|source = {{simgear file|simgear/nasal/mathlib.c|l=177|t=Source}}&lt;br /&gt;
|text = Implements the tangent trigonometric function. Returns a ratio from a given angle in radians.&lt;br /&gt;
|param1 = x&lt;br /&gt;
|param1text = Mandatory number that should be an angle in radians.&lt;br /&gt;
|example1 = var angle = 45 * D2R;&lt;br /&gt;
print(math.tan(angle)); # prints the ratio (1)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Variables ==&lt;br /&gt;
=== e ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = math.e;&lt;br /&gt;
|source = {{simgear file|simgear/nasal/mathlib.c|l=232|t=Source}}&lt;br /&gt;
|text = Important mathematical constant (see {{wikipedia|e (mathematical constant)}}). Value in simulation: 2.7182818284590452354&lt;br /&gt;
}}&lt;br /&gt;
=== pi ===&lt;br /&gt;
{{Nasal doc&lt;br /&gt;
|syntax = math.pi;&lt;br /&gt;
|source = {{simgear file|simgear/nasal/mathlib.c|l=231|t=Source}}&lt;br /&gt;
|text = Important mathematical constant (see {{wikipedia|pi (mathematical constant)}}). Value in simulation: 3.14159265358979323846&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Nasal namespaces}}&lt;/div&gt;</summary>
		<author><name>TheEagle</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=User:TheEagle&amp;diff=135678</id>
		<title>User:TheEagle</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=User:TheEagle&amp;diff=135678"/>
		<updated>2022-10-03T14:10:40Z</updated>

		<summary type="html">&lt;p&gt;TheEagle: Add wiki link for the C310&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;I like to fly in FlightGear, create and enhance scenery, aircraft and addons for FlightGear, write Python apps …&lt;br /&gt;
&lt;br /&gt;
=== My aircraft ===&lt;br /&gt;
Cessna 210 ([https://github.com/TheFGFSEagle/c210-family GitHub] | [[Cessna 210 Centurion family|Wiki]])&lt;br /&gt;
&lt;br /&gt;
Cessna 310 ([https://github.com/TheFGFSEagle/c310-family GitHub] | [[Cessna 310 family|Wiki]])&lt;br /&gt;
&lt;br /&gt;
Pilatus PC-6 ([https://github.com/TheFGFSEagle/pilatus-pc6-family GitHub] | Wiki)&lt;br /&gt;
&lt;br /&gt;
Cessna 208 ([https://github.com/TheFGFSEagle/c208-family GitHub] | Wiki)&lt;br /&gt;
&lt;br /&gt;
Mudry CAP10 ([https://github.com/TheFGFSEagle/mudry-cap10-family GitHub] | Wiki)&lt;/div&gt;</summary>
		<author><name>TheEagle</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=User:TheEagle&amp;diff=135676</id>
		<title>User:TheEagle</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=User:TheEagle&amp;diff=135676"/>
		<updated>2022-10-03T14:09:08Z</updated>

		<summary type="html">&lt;p&gt;TheEagle: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;I like to fly in FlightGear, create and enhance scenery, aircraft and addons for FlightGear, write Python apps …&lt;br /&gt;
&lt;br /&gt;
=== My aircraft ===&lt;br /&gt;
Cessna 210 ([https://github.com/TheFGFSEagle/c210-family GitHub] | [[Cessna 210 Centurion family|Wiki]])&lt;br /&gt;
&lt;br /&gt;
Cessna 310 ([https://github.com/TheFGFSEagle/c310-family GitHub] | Wiki)&lt;br /&gt;
&lt;br /&gt;
Pilatus PC-6 ([https://github.com/TheFGFSEagle/pilatus-pc6-family GitHub] | Wiki)&lt;br /&gt;
&lt;br /&gt;
Cessna 208 ([https://github.com/TheFGFSEagle/c208-family GitHub] | Wiki)&lt;br /&gt;
&lt;br /&gt;
Mudry CAP10 ([https://github.com/TheFGFSEagle/mudry-cap10-family GitHub] | Wiki)&lt;/div&gt;</summary>
		<author><name>TheEagle</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Cessna_310_family&amp;diff=135616</id>
		<title>Cessna 310 family</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Cessna_310_family&amp;diff=135616"/>
		<updated>2022-09-19T00:18:03Z</updated>

		<summary type="html">&lt;p&gt;TheEagle: Created page&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The '''Cessna 310''' is an American four-to-six-seat, low-wing, twin-engine monoplane produced by Cessna between 1954 and 1980. It was the first twin-engine aircraft that Cessna put into production after World War II.&lt;br /&gt;
&lt;br /&gt;
== Status in FlightGear ==&lt;br /&gt;
The model is not very far advanced yet (thus not available on FGAddon), but work in progress.&lt;br /&gt;
&lt;br /&gt;
== Variants ==&lt;br /&gt;
At the moment, the 310A variant is being developed - further variants are planned.&lt;br /&gt;
&lt;br /&gt;
== Web links ==&lt;br /&gt;
Discord channel: https://discord.gg/8eFTtaBVAT then go to Cessna 310 #general.&lt;br /&gt;
&lt;br /&gt;
Matrix space: https://matrix.to/#/!fEOTalzMFXdlqnGjUM:matrix.org&lt;br /&gt;
&lt;br /&gt;
GitHub repo: https://github.com/TheFGFSEagle/c310-family&lt;br /&gt;
&lt;br /&gt;
Forum development topic: https://forum.flightgear.org/viewtopic.php?f=4&amp;amp;t=40501&lt;/div&gt;</summary>
		<author><name>TheEagle</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Hackathon_Proposal:Canvas_Widgets&amp;diff=135501</id>
		<title>Hackathon Proposal:Canvas Widgets</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Hackathon_Proposal:Canvas_Widgets&amp;diff=135501"/>
		<updated>2022-09-06T15:55:56Z</updated>

		<summary type="html">&lt;p&gt;TheEagle: Remove double primarily&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{WIP}}&lt;br /&gt;
&lt;br /&gt;
{{Hackathon Proposal&lt;br /&gt;
|year=2022&lt;br /&gt;
|title= Getting rid of PUI using the Canvas&lt;br /&gt;
|image = About-dialog-rendered-by-canvas.png&lt;br /&gt;
|imginfo = about.xml parsed and rendered by the Canvas GUI system&lt;br /&gt;
|status = RFC&lt;br /&gt;
|sponsor=  |supporters= please add yourself if you are interested in working on this&lt;br /&gt;
|summary = {{Main article|PUI#Replacement_status}}&lt;br /&gt;
The [[PUI]] replacement [[QtQuick_use_in_FlightGear#Background|was going to be Qt]] but it started to get very complicated with changes in Qt 5.15 + Qt 6, so James is going with a more light-weight Canvas based approach now.&lt;br /&gt;
&lt;br /&gt;
Qt added support for Vulkan / Metal / D3D starting in 5.15, but FlightGear / OpenSceneGraph can’t support those, so integrating the two renderers went from being ‘complicated but ok’  to ‘very very complicated’. &lt;br /&gt;
&lt;br /&gt;
So now James is going with something much more lightweight using some C++ compatibility code, some Nasal for styling and the existing [[Canvas widgets|Canvas widget rendering]] from Thomas Geymayer (TheTom) with some extensions and additions, based on the [[Howto:Processing_legacy_PUI_dialogs_using_Canvas#Original_Discussion|plans originally discussed when the Canvas GUI system was added to FlightGear]]: some pieces are in FlightGear &amp;amp; FGData already. &lt;br /&gt;
&lt;br /&gt;
James has basic dialogs working okay but not the more complex ones and everything looks kind of ugly, he needs to improve the visual look before he shares screenshots to avoid everyone freaking out :) The disadvantage of this approach is James is far from expert at creating visual appearances this way, so it’s kind on unrewarding and slow for him. If someone likes messing with CSS-type styling, border-images and hover-states, ping him since we could probably move things also faster &amp;lt;ref&amp;gt;https://sourceforge.net/p/flightgear/mailman/message/37701750/&amp;lt;/ref&amp;gt;&lt;br /&gt;
|background= {{See also|Core Profile support|Unifying the 2D rendering backend via canvas}}&lt;br /&gt;
The bigger issue here is we need to ditch [[PUI]] (which is in progress) and some OpenGL 1.0 code (HUD, 2D panels especially - can be #ifdef for now) so we can enable Core profile on Mac - since Mac 4.x support (we only hit about 4.3 alas, but with some extensions to get in sight of 4.5) is Core profile only, no Compatability mode.&lt;br /&gt;
&lt;br /&gt;
Improving the frame-rate and modernised 3D rendering, can’t be worked on until the PUI code, 2D panels and Shiva are removed, but doing so is a frustrating slow path&amp;lt;ref&amp;gt;{{cite web&lt;br /&gt;
  |url    =  https://sourceforge.net/p/flightgear/mailman/message/35623408/ &lt;br /&gt;
  |title  =  &amp;lt;nowiki&amp;gt; Re: [Flightgear-devel] canvas non svg-elements broken &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |author =  &amp;lt;nowiki&amp;gt; James Turner &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |date   =  Jan 24th, 2017 &lt;br /&gt;
  |added  =  Jan 24th, 2017 &lt;br /&gt;
  |script_version = 0.40 &lt;br /&gt;
  }}&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Given that with many graphics drivers PUI doesn't render correctly when higher shader quality is on, many people are convinced PUI needs to be replaced.&amp;lt;ref&amp;gt;{{cite web&lt;br /&gt;
  |url    =  https://sourceforge.net/p/flightgear/mailman/message/35624918/ &lt;br /&gt;
  |title  =  &amp;lt;nowiki&amp;gt; Re: [Flightgear-devel] canvas non svg-elements broken &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |author =  &amp;lt;nowiki&amp;gt; Thorsten Renk &amp;lt;/nowiki&amp;gt; &lt;br /&gt;
  |date   =  Jan 25th, 2017 &lt;br /&gt;
  |added  =  Jan 25th, 2017 &lt;br /&gt;
  |script_version = 0.40 &lt;br /&gt;
  }}&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|details={{Main article|Howto:Creating a Canvas GUI Widget}}&lt;br /&gt;
&lt;br /&gt;
It’s split between Simgear (see classes with widget / layout in the name) and in FGData. (Eg widgets/Button.nas) To be able to use the existing dialog XML files un-modified (which is a design goal), James is extending the widget types with many additional ones (eg PUI has slider, dial, combo-box, checkbox, all of which need to be created, see [[Canvas widget matrix]].&lt;br /&gt;
&lt;br /&gt;
This is about 30% done and is the bit he's very slow at). Thomas’s canvas widgets have a very good separation of API + state from appearance, so all styling is in its own file, and James is being very strict about maintaining this separation, so we also retain the re-styling feature of the PUI UI, which many people also rely on. This does make the process of adding new widgets more complex, however.&lt;br /&gt;
&lt;br /&gt;
|ideas = {{See also|Canvas_widget_matrix}}&lt;br /&gt;
There are only ~5 widgets in the Canvas GUI: currently&lt;br /&gt;
* {{Canvas Widget|widget=Button}}&lt;br /&gt;
* {{Canvas Widget|widget=CheckBox}}&lt;br /&gt;
* {{Canvas Widget|widget=Label}}&lt;br /&gt;
* {{Canvas Widget|widget=LineEdit}} (editable text box)&lt;br /&gt;
* {{Canvas Widget|widget=ScrollArea}} (for scrollable content)&lt;br /&gt;
&lt;br /&gt;
In contrast, the [[PUI]] subsystem in FlightGear supports ~15 documented widgets (see {{readme file|gui|line=219}} for a list of widgets made available to FlightGear via PUI/XML).&lt;br /&gt;
For a complete list (including custom/undocumented widgets), refer to &amp;lt;code&amp;gt;FGPUIDialog::makeObject()&amp;lt;/code&amp;gt; in {{flightgear file|src/GUI/FGPUIDialog.cxx|l=850}}&lt;br /&gt;
&lt;br /&gt;
In addition, FlightGear introduces a handful of custom PUI widgets implemented in C++ space, that not even PUI itself supports directly:&lt;br /&gt;
* {{PUI widget|airport-list}} {{Dialog file|dialog=airports|l=393}}&lt;br /&gt;
* {{PUI widget|waypointlist}} (only used by the [[Route Manager]] i.e. {{Dialog file|dialog=route-manager|l=590}}  &lt;br /&gt;
* {{PUI widget|property-list}} (only used by the [[Property browser]] i.e. {{Dialog file|dialog=property-browser|l=55}}&lt;br /&gt;
&lt;br /&gt;
However, many PUI widgets can be emulated/approximated by using a combination of existing widgets, and/or functionality found in existing widgets, i.e. by referring to their source code.&lt;br /&gt;
&lt;br /&gt;
For instance, all the data needed to obtain a list of airports or waypoints can be queried using the [[Navdata_cache#Accessing_via_Nasal| Navdb APIs]] exposed via [[Nasal/CppBind]], specifically:&lt;br /&gt;
* findAirportsWithinRange()&lt;br /&gt;
* findNavaidsWithinRange()&lt;br /&gt;
&lt;br /&gt;
At this point, we can map arbitrary navdb calls to help populate a ScrollArea with entries.&lt;br /&gt;
&lt;br /&gt;
Thus, what is primarily needed to implement support for arbitrary -list types is a ScrollArea that uses  buttons (or labels) for each entry.&lt;br /&gt;
&lt;br /&gt;
Primarily, the following widgets are needed to help getting rid of PUI (listed in ascending complexity):&lt;br /&gt;
* {{PUI widget|vrule}} (not critical, could  be implemented OpenVG or simply by using a transparent image)&lt;br /&gt;
* {{PUI widget|hrule}} (not critical, could  be implemented OpenVG or simply by using a transparent image)&lt;br /&gt;
* {{PUI widget|radio}} (Canvas images using event handling to dynamically change the image based on mouse events)&lt;br /&gt;
* {{PUI widget|slider}} (basically 3 buttons with the middle button supporting dragging, see also [https://forum.flightgear.org/viewtopic.php?f=71&amp;amp;t=19975])&lt;br /&gt;
* {{PUI widget|dial}} (see also [https://forum.flightgear.org/viewtopic.php?f=71&amp;amp;t=19975])&lt;br /&gt;
* {{PUI widget|combo}} (popup with a scrollArea that has buttons for each item (can be shared with {{tag|select}})&lt;br /&gt;
&lt;br /&gt;
The key ones really being the last 4 in that list (radio, slider, dial and combo/dropdown)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
|skills= [[Property tree]], [[PropertyList XML File]], [[Nasal]], [[Canvas]], [[Timers]], [[Listeners]]&lt;br /&gt;
|opportunities=&amp;lt;/p&amp;gt;&lt;br /&gt;
* [[PUI]]&lt;br /&gt;
* [[Canvas event handling]]&lt;br /&gt;
* [[Canvas widgets]]&lt;br /&gt;
|notes = to learn more about the underlying idea/approach, please refer to [[pui2canvas]]&lt;br /&gt;
}}&lt;/div&gt;</summary>
		<author><name>TheEagle</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=User:TheEagle&amp;diff=135273</id>
		<title>User:TheEagle</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=User:TheEagle&amp;diff=135273"/>
		<updated>2022-07-04T09:29:37Z</updated>

		<summary type="html">&lt;p&gt;TheEagle: Added aircraft list&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;I like to fly in FlightGear, create and enhance scenery, aircraft and addons for FlightGear, …&lt;br /&gt;
&lt;br /&gt;
=== My aircraft ===&lt;br /&gt;
Cessna 210 ([https://github.com/TheFGFSEagle/c210-family GitHub] | [[Cessna 210 Centurion family|Wiki]])&lt;br /&gt;
&lt;br /&gt;
Pilatus PC-6 ([https://github.com/TheFGFSEagle/pilatus-pc6-family GitHub] | Wiki)&lt;br /&gt;
&lt;br /&gt;
Cessna 208 ([https://github.com/TheFGFSEagle/c208-family GitHub] | Wiki)&lt;br /&gt;
&lt;br /&gt;
Mudry CAP10 ([https://github.com/TheFGFSEagle/mudry-cap10-family GitHub] | Wiki)&lt;/div&gt;</summary>
		<author><name>TheEagle</name></author>
	</entry>
	<entry>
		<id>https://wiki.flightgear.org/w/index.php?title=Cessna_210_Centurion_family&amp;diff=135272</id>
		<title>Cessna 210 Centurion family</title>
		<link rel="alternate" type="text/html" href="https://wiki.flightgear.org/w/index.php?title=Cessna_210_Centurion_family&amp;diff=135272"/>
		<updated>2022-07-04T09:26:25Z</updated>

		<summary type="html">&lt;p&gt;TheEagle: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{DISPLAYTITLE:Cessna 210 &amp;quot;Centurion&amp;quot;}}&lt;br /&gt;
&lt;br /&gt;
{{Infobox aircraft|name=Cessna 210 &amp;quot;Centurion&amp;quot;|status-model=4|forumtid=39711|craft=aircraft|wikipedia=Cessna_210_Centurion|devel-repo=https://github.com/TheFGFSEagle/c210-family|status=3|status-cockpit=4|type=Light aircraft|status-systems=4|status-fdm=4|fdm=JSBsim|manufacturer=Cessna|fgname=p210n|author1=TheEagle|propulsion=Piston aircraft/Propeller aircraft|config=High wing aircraft/Retractable gear aircraft/Tricycle landing gear aircraft|image=Cessna_P210N_cruising.png}}&lt;br /&gt;
&lt;br /&gt;
The '''Cessna 210 Centurion''' is a five- to six-seat, high-performance, retractable-gear, single-engine, high-wing general aviation aircraft. First flown in January 1957, it was produced by Cessna until 1986.&lt;br /&gt;
&lt;br /&gt;
== Variants ==&lt;br /&gt;
The Cessna 210 family includes many variants, but only one (the P210N) is modelled for FlightGear so far.&lt;br /&gt;
(Silver Eagle turboprop modification of the P210N by O&amp;amp;N coming soon)&lt;br /&gt;
&lt;br /&gt;
== Information and help==&lt;br /&gt;
&lt;br /&gt;
===Startup===&lt;br /&gt;
Use either Menu -&amp;gt; Cessna 210 -&amp;gt; Autostart, or follow the checklist below:&lt;br /&gt;
# Master BAT: ON&lt;br /&gt;
#NAV lights: ON&lt;br /&gt;
#Mixture: FULL RICH (full forward) (&amp;lt;code&amp;gt;m&amp;lt;/code&amp;gt; key)&lt;br /&gt;
#Prop RPM: HIGH (full forward) (&amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; key)&lt;br /&gt;
#Throttle: IDLE (fully backward) (&amp;lt;code&amp;gt;PgDown&amp;lt;/code&amp;gt; key)&lt;br /&gt;
#Parking brake: APPLIED (lever pointing down) (&amp;lt;code&amp;gt;Shift + b&amp;lt;/code&amp;gt; keys)&lt;br /&gt;
#Fuel selector: LEFT or RIGHT&lt;br /&gt;
#Magneto keys: INSERT&lt;br /&gt;
#Magnetos: BOTH then START (or press &amp;lt;code&amp;gt;}&amp;lt;/code&amp;gt; three times then hold &amp;lt;code&amp;gt;s&amp;lt;/code&amp;gt; for two to three seconds)&lt;br /&gt;
#Master ALT: ON&lt;br /&gt;
#Avionics master: ON&lt;br /&gt;
If it won't start neither by hand nor through the autostart function, follow the checklist but at 9. hold the &amp;lt;code&amp;gt;s&amp;lt;/code&amp;gt; and simultaneously lean (pull out) the mixture until the engine's RPM stabilizes at ~800 RPM.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==ToDo==&lt;br /&gt;
&lt;br /&gt;
*Checklists, tutorials&lt;br /&gt;
*Liveries&lt;br /&gt;
*Damage, icing simulation&lt;br /&gt;
*WXRadar implementation&lt;br /&gt;
&lt;br /&gt;
==External references==&lt;br /&gt;
*[https://forum.flightgear.org/viewtopic.php?f=4&amp;amp;t=39711 Official forum topic]&lt;br /&gt;
*[http://www.marksetcetera.com/N731PJ-P210N-POH.pdf P210N Pilots operating handbook (POH)]&lt;br /&gt;
&lt;br /&gt;
[[Category:Aircraft|Cessna 210 Centurion]]&lt;br /&gt;
[[Category:Single-engine aircraft]]&lt;br /&gt;
{{DEFAULTSORT:Cessna 210 Centurion}}&lt;br /&gt;
__FORCETOC__&lt;br /&gt;
__INDEX__&lt;br /&gt;
__NEWSECTIONLINK__&lt;/div&gt;</summary>
		<author><name>TheEagle</name></author>
	</entry>
</feed>