https://wiki.flightgear.org/w/api.php?action=feedcontributions&user=Jsb&feedformat=atomFlightGear wiki - User contributions [en]2024-03-28T10:59:38ZUser contributionsMediaWiki 1.39.6https://wiki.flightgear.org/w/index.php?title=Aircraft_properties_reference&diff=139587Aircraft properties reference2024-03-28T00:15:02Z<p>Jsb: annunciators: Add hint to aircraft.light</p>
<hr />
<div>{{PropertyTree}}<br />
<br />
This is a generic '''aircraft properties reference'''. There are many properties that are very common in aircraft, and many of them are present even in a very simple aircraft. However, a complete description that matches all the properties is very unlikely to be written, as aircraft can be very different from each other.<br />
<br />
{{TOC limit|2}}<br />
<br />
== What are properties? ==<br />
{{main article|Property tree}}<br />
=== The property tree ===<br />
Most parts of FlightGear communicate with each other through key-value pair properties in the [[property tree]]. The properties represent both the input from the pilot, the values determining the position and velocity of the aircraft, the values used for animating the aircraft, and pretty much anything else.<br />
<br />
While many properties will be common between most aircraft, many properties will also be different between aircraft. This becomes obvious if one consider the many different configurations (aircraft/helicopter/car, control surface and landing gear layout, number and locations of engines, etc) and propulsions systems (types of engines, types of fuels etc.) an aircraft can have. There are also different flight dynamic models (FDMs) that have different needs. In addition many properties will by necessity be aircraft specific, though developers should make a conscious effort to have properties map to more common ones if that is possible.<br />
<br />
=== Where are the properties defined? ===<br />
There are a couple ways that the properties are set, but a fair amount of them just "appear" without being documented anywhere. There are several places to look for properties. One is in the aircraft files, starting from the aircraft specific [[aircraft-set.xml]] file, another is the [[Nasal]] files, and the last place (and often most useful!) is "grepping" (searching) through the C++ code.<br />
<br />
To determine how a property works and what it does often requires looking through any code that uses it. This is a part of FlightGear that we could certainly document better<br />
<br />
== Annunciators [draft] ==<br />
The /instrumentation/annunciators section shall provide a central place for<br />
<br />
* aircrafts to write annunciator status data<br />
* external devices like joysticks, yokes, throttle quadrants or custom made cockpit hardware to read such data and drive status lights etc.<br />
The aircraft.nas module provides aircraft.light which allows easy implementation of blinking lights. Such lights have a bool property 'state' which tells if the light is on or off.<pre><br />
/instrumentation/annunciators/doors<br />
/instrumentation/annunciators/master-caution/state # aircraft.light<br />
/instrumentation/annunciators/master-warning/state # aircraft.light<br />
<br />
/instrumentation/annunciators/ap/enabled<br />
/instrumentation/annunciators/ap/mode/alt<br />
/instrumentation/annunciators/ap/mode/apr<br />
/instrumentation/annunciators/ap/mode/hdg<br />
/instrumentation/annunciators/ap/mode/ias<br />
/instrumentation/annunciators/ap/mode/nav<br />
/instrumentation/annunciators/ap/mode/rev<br />
/instrumentation/annunciators/ap/mode/vs<br />
<br />
/instrumentation/annunciators/engines/<br />
/instrumentation/annunciators/engines/apu<br />
/instrumentation/annunciators/engines/fire<br />
/instrumentation/annunciators/engines/oil-pressure-low<br />
/instrumentation/annunciators/engines/starter<br />
<br />
/instrumentation/annunciators/gear/nose/green<br />
/instrumentation/annunciators/gear/nose/red<br />
/instrumentation/annunciators/gear/left/green<br />
/instrumentation/annunciators/gear/left/red<br />
/instrumentation/annunciators/gear/right/green<br />
/instrumentation/annunciators/gear/right/red<br />
/instrumentation/annunciators/gear/parking-brake<br />
<br />
/instrumentation/annunciators/systems/anti-ice/enabled<br />
/instrumentation/annunciators/systems/fuel/aux-pump<br />
/instrumentation/annunciators/systems/fuel/pressure-low<br />
/instrumentation/annunciators/systems/hyd/pressure-low<br />
/instrumentation/annunciators/systems/vacuum<br />
<br />
</pre><br />
<br />
== Consumables ==<br />
:''See also [[#Fuel]]<br />
<pre><br />
/consumables/fuel/tank[%d]/level-lb<br />
/consumables/fuel/tank[%d]/level-lbs<br />
/consumables/fuel/tank[%d]/level-gal_us<br />
/consumables/fuel/tank[%d]/capacity-gal_us<br />
/consumables/fuel/tank[%d]/density-ppg<br />
/consumables/fuel/total-fuel-lbs<br />
/consumables/fuel/total-gal_us<br />
</pre><br />
<br />
== Controls ==<br />
These properties should, and usually do correspond to how various cockpit controls are set. Consider these the pilot input.<br />
<br />
=== Anti-ice ===<br />
These properties control the various anti-ice properties that may be present in an aircraft.<br />
<br />
<pre><br />
/controls/anti-ice/wing-heat<br />
/controls/anti-ice/pitot-heat<br />
/controls/anti-ice/wiper<br />
/controls/anti-ice/window-heat<br />
/controls/anti-ice/engine[%d]/carb-heat<br />
/controls/anti-ice/engine[%d]/inlet-heat<br />
</pre><br />
TODO - section notes<br />
<br />
=== APU ===<br />
These properties control any auxiliary power unit, in essence a small turbine engine driving generators, hydraulic pumps etc. before and after the aircraft's engines are up an running.<br />
<br />
<pre><br />
/controls/APU/off-start-run<br />
/controls/APU/fire-switch<br />
</pre><br />
TODO - section notes<br />
<br />
=== Armament ===<br />
TODO - explain of section<br />
<pre><br />
/controls/armament/master-arm<br />
/controls/armament/station-select<br />
/controls/armament/release-all<br />
/controls/armament/station[%d]/stick-size<br />
/controls/armament/station[%d]/release-stick<br />
/controls/armament/station[%d]/release-all<br />
/controls/armament/station[%d]/jettison-all<br />
</pre><br />
TODO - section notes<br />
<br />
=== Autoflight ===<br />
These properties control the autopilot.<br />
<br />
TODO - explain of section<br />
<pre><br />
/controls/autoflight/autopilot[%d]/engage<br />
/controls/autoflight/autothrottle-arm<br />
/controls/autoflight/autothrottle-engage<br />
/controls/autoflight/heading-select<br />
/controls/autoflight/altitude-select<br />
/controls/autoflight/bank-angle-select<br />
/controls/autoflight/vertical-speed-select<br />
/controls/autoflight/speed-select<br />
/controls/autoflight/mach-select<br />
/controls/autoflight/vertical-mode<br />
/controls/autoflight/lateral-mode<br />
</pre><br />
TODO - section notes<br />
<br />
=== Electric ===<br />
TODO - explain of section<br />
<pre><br />
/controls/electric/battery-switch<br />
/controls/electric/external-power<br />
/controls/electric/APU-generator<br />
/controls/electric/engine[%d]/generator<br />
/controls/electric/engine[%d]/bus-tie<br />
</pre><br />
TODO - section notes<br />
<br />
=== Engines ===<br />
Engines are numbered engine[0] for a single engine to engine[0] to engine[3] for a 747 for example. (The model allows for up to 12 engines rumour has it ;-)<br />
<br />
<pre><br />
/controls/engines/throttle_idle<br />
/controls/engines/engine[%d]/throttle<br />
/controls/engines/engine[%d]/starter<br />
/controls/engines/engine[%d]/fuel-pump<br />
/controls/engines/engine[%d]/fire-switch<br />
/controls/engines/engine[%d]/fire-bottle-discharge<br />
/controls/engines/engine[%d]/cutoff<br />
/controls/engines/engine[%d]/mixture<br />
/controls/engines/engine[%d]/propeller-pitch<br />
/controls/engines/engine[%d]/magnetos<br />
/controls/engines/engine[%d]/boost<br />
/controls/engines/engine[%d]/WEP<br />
/controls/engines/engine[%d]/cowl-flaps-norm<br />
/controls/engines/engine[%d]/feather<br />
/controls/engines/engine[%d]/ignition<br />
/controls/engines/engine[%d]/augmentation<br />
/controls/engines/engine[%d]/afterburner<br />
/controls/engines/engine[%d]/reverser<br />
/controls/engines/engine[%d]/water-injection<br />
/controls/engines/engine[%d]/condition<br />
</pre><br />
TODO - section notes<br />
<br />
=== Flight controls ===<br />
These properties control the flight controls surfaces, though often through a mechanical, analog or digital flight control system (FCS) that may or may not be modeled.<br />
<br />
<pre><br />
/controls/flight/aileron<br />
/controls/flight/aileron-trim<br />
/controls/flight/elevator<br />
/controls/flight/elevator-trim<br />
/controls/flight/rudder<br />
/controls/flight/rudder-trim<br />
/controls/flight/flaps<br />
/controls/flight/slats<br />
/controls/flight/BLC // Boundary Layer Control<br />
/controls/flight/spoilers<br />
/controls/flight/speedbrake<br />
/controls/flight/wing-sweep<br />
/controls/flight/wing-fold<br />
/controls/flight/drag-chute<br />
</pre><br />
<br />
The positions of the controls are usually put in <code>/surface-positions/</code> at the discretion of the aircraft designer. These usually drive the animations of the control surfaces. They are either normalized (like <code>/surface-positions/elevator-pos-norm</code>) or in degrees, and sometimes the aileron is split left/right.<br />
<br />
=== Fuel ===<br />
TODO - explain of section<br />
<pre><br />
/controls/fuel/dump-valve<br />
/controls/fuel/tank[%d]/fuel_selector<br />
/controls/fuel/tank[%d]/to_engine<br />
/controls/fuel/tank[%d]/to_tank<br />
/controls/fuel/tank[%d]/boost-pump[%d]<br />
</pre><br />
TODO - section notes<br />
<br />
=== Gear ===<br />
TODO - explain of section<br />
<pre><br />
/controls/gear/brake-left<br />
/controls/gear/brake-right<br />
/controls/gear/brake-parking<br />
/controls/gear/steering // Used if rudder is not sufficient for control of steering<br />
/controls/gear/gear-down<br />
/controls/gear/antiskid // Deprecated?<br />
/controls/gear/tailhook<br />
/controls/gear/tailwheel-lock<br />
/controls/gear/wheel[%d]/alternate-extension<br />
</pre><br />
TODO - section notes<br />
<br />
=== Hydraulics ===<br />
TODO - explain of section<br />
<pre><br />
/controls/hydraulic/system[%d]/engine-pump<br />
/controls/hydraulic/system[%d]/electric-pump<br />
</pre><br />
TODO - section notes<br />
<br />
=== Lights ===<br />
TODO - explain of section<br />
<pre><br />
/controls/lighting/landing-lights<br />
/controls/lighting/turn-off-lights<br />
/controls/lighting/formation-lights<br />
/controls/lighting/taxi-light<br />
/controls/lighting/logo-lights<br />
/controls/lighting/nav-lights<br />
/controls/lighting/beacon<br />
/controls/lighting/strobe<br />
/controls/lighting/panel-norm<br />
/controls/lighting/instruments-norm<br />
/controls/lighting/dome-norm<br />
</pre><br />
TODO - section notes<br />
<br />
=== Pneumatic ===<br />
TODO - explain of section<br />
<pre><br />
/controls/pneumatic/APU-bleed<br />
/controls/pneumatic/engine[%d]/bleed<br />
</pre><br />
TODO - section notes<br />
<br />
=== Pressurization ===<br />
TODO - explain of section<br />
<pre><br />
/controls/pressurization/mode<br />
/controls/pressurization/dump<br />
/controls/pressurization/outflow-valve<br />
/controls/pressurization/pack[%d]/pack-on<br />
</pre><br />
TODO - section notes<br />
<br />
=== Seat ===<br />
TODO - explain of section<br />
<pre><br />
/controls/seat/vertical-adjust<br />
/controls/seat/fore-aft-adjust<br />
/controls/seat/cmd_selector_valve<br />
/controls/seat/eject[%d]/initiate<br />
/controls/seat/eject[%d]/status<br />
</pre><br />
TODO - section notes<br />
<br />
== Engines ==<br />
TODO - explain of section<br />
=== Common ===<br />
<pre><br />
/engines/engine[%d]/fuel-flow-gph<br />
/engines/engine[%d]/fuel-flow_pph<br />
/engines/engine[%d]/thrust_lb<br />
/engines/engine[%d]/running<br />
/engines/engine[%d]/starter<br />
/engines/engine[%d]/cranking<br />
</pre><br />
<br />
=== Turbine ===<br />
<pre><br />
/engines/engine[%d]/n1<br />
/engines/engine[%d]/n2<br />
/engines/engine[%d]/epr<br />
/engines/engine[%d]/augmentation<br />
/engines/engine[%d]/water-injection<br />
/engines/engine[%d]/ignition<br />
/engines/engine[%d]/nozzle-pos-norm<br />
/engines/engine[%d]/inlet-pos-norm<br />
/engines/engine[%d]/reversed<br />
/engines/engine[%d]/cutoff<br />
</pre><br />
<br />
=== Piston ===<br />
<pre><br />
/engines/engine[%d]/mp-osi<br />
/engines/engine[%d]/egt-degf<br />
/engines/engine[%d]/oil-temperature-degf<br />
/engines/engine[%d]/oil-pressure-psi<br />
/engines/engine[%d]/cht-degf<br />
/engines/engine[%d]/rpm<br />
</pre><br />
<br />
=== Propeller ===<br />
<pre><br />
/engines/engine[%d]/rpm<br />
/engines/engine[%d]/pitch<br />
/engines/engine[%d]/torque<br />
</pre><br />
TODO - section notes<br />
<br />
== Flight Dynamics Model ==<br />
=== Position ===<br />
This will return the current position of the aircraft within FlightGear. This is also the stuff that is transmitted in [[Howto:Multiplayer|multiplayer]].<br />
<br />
<pre><br />
/position/<br />
/position/altitiude-ft ()<br />
/position/altitude-agl-ft (22.46983965)<br />
/position/altitude-ft (28.24368289)<br />
/position/ground-elev-ft (-0.43513529)<br />
/position/ground-elev-m (-0.1326292364)<br />
/position/latitude-deg (37.61371436)<br />
/position/latitude-string (37*36 49.4N)<br />
/position/longitude-deg (-122.3576508)<br />
/position/longitude-string (-122*21 27.5W)<br />
/position/sea-level-radius-ft (20899648.76)<br />
</pre><br />
<br />
=== Orientation ===<br />
TODO - explain of section<br />
<pre><br />
/orientation/roll-deg<br />
/orientation/pitch-deg<br />
/orientation/heading-deg<br />
<br />
/orientation/roll-rate-degps<br />
/orientation/pitch-rate-degps<br />
/orientation/yaw-rate-degps<br />
<br />
/orientation/side-slip-rad<br />
/orientation/side-slip-deg<br />
/orientation/alpha-deg<br />
</pre><br />
TODO - section notes<br />
<br />
=== Velocities ===<br />
TODO - explain of section<br />
<pre><br />
/velocities/airspeed-kt<br />
/velocities/mach<br />
/velocities/speed-north-fps<br />
/velocities/speed-east-fps<br />
/velocities/speed-down-fps<br />
<br />
/velocities/uBody-fps<br />
/velocities/vBody-fps<br />
/velocities/wBody-fps<br />
<br />
/velocities/vertical-speed-fps<br />
/velocities/glideslope<br />
</pre><br />
TODO - section notes<br />
<br />
=== Acceleration ===<br />
TODO - explain of section<br />
<pre><br />
/accelerations/nlf<br />
<br />
/accelerations/ned/north-accel-fps_sec<br />
/accelerations/ned/east-accel-fps_sec<br />
/accelerations/ned/down-accel-fps_sec<br />
<br />
/accelerations/pilot/x-accel-fps_sec<br />
/accelerations/pilot/y-accel-fps_sec<br />
/accelerations/pilot/z-accel-fps_sec<br />
</pre><br />
TODO - section notes<br />
<br />
== Gear ==<br />
<pre><br />
/gear/serviceable<br />
/gear/gear[%d]/cast-angle-deg // The angle of the wheel where 0 is pointing straight forward<br />
/gear/gear[%d]/compression-m<br />
/gear/gear[%d]/compression-norm<br />
/gear/gear[%d]/ground-friction-factor<br />
/gear/gear[%d]/ground-is-solid<br />
/gear/gear[%d]/has-brake<br />
/gear/gear[%d]/rollspeed-ms // Speed of the wheel's rotation in meters per second<br />
/gear/gear[%d]/wow // Weight-on-wheel<br />
/gear/gear[%d]/xoffset-in<br />
/gear/gear[%d]/yoffset-in<br />
/gear/gear[%d]/zoffset-in<br />
</pre><br />
<br />
== Instrumentation ==<br />
<pre><br />
/instrumentation/adf/<br />
/instrumentation/airspeed-indicator/<br />
/instrumentation/altimeter/<br />
/instrumentation/annunciator/<br />
/instrumentation/attitude-indicator/<br />
/instrumentation/clock/<br />
/instrumentation/comm/<br />
/instrumentation/comm[1]/<br />
/instrumentation/dme/<br />
/instrumentation/efis/<br />
/instrumentation/encoder/<br />
/instrumentation/flightdirector/<br />
/instrumentation/gps/<br />
/instrumentation/gps-annunciator/<br />
/instrumentation/heading-indicator/<br />
/instrumentation/heading-indicator-fg/<br />
/instrumentation/magnetic-compass/<br />
/instrumentation/marker-beacon/<br />
/instrumentation/nav/<br />
/instrumentation/nav[1]/<br />
/instrumentation/radar/<br />
/instrumentation/slip-skid-ball/<br />
/instrumentation/tacan/<br />
/instrumentation/transponder/<br />
/instrumentation/turn-indicator/<br />
/instrumentation/vertical-speed-indicator/<br />
/instrumentation/wxradar/<br />
</pre><br />
<br />
== Rotors (YASim only) ==<br />
<pre><br />
/rotors/gear/torque-sound-filtered // Unused?<br />
/rotors/gear/total-torque<br />
/rotors/{name}/balance<br />
/rotors/{name}/blade[%d]/flap-deg<br />
/rotors/{name}/blade[%d]/incidence-deg<br />
/rotors/{name}/blade[%d]/position-deg // Position relative to model<br />
/rotors/{name}/bladesvisible // Used for animations<br />
/rotors/{name}/cone%d-deg //e.g. cone-deg or cone2-deg<br />
/rotors/{name}/roll-deg<br />
/rotors/{name}/rpm<br />
/rotors/{name}/stall<br />
/rotors/{name}/stall-filtered<br />
/rotors/{name}/tilt<br />
/rotors/{name}/torque<br />
/rotors/{name}/yaw-deg<br />
</pre><br />
<br />
For how to animate rotors using these properties, see [[Howto:Animate helicopters]].<br />
<br />
== Related content ==<br />
=== Wiki articles ===<br />
* [[Aircraft-set.xml]]<br />
* [[Multiplayer protocol]]<br />
* [[Property browser]]<br />
* [[PropertyList XML files]]<br />
* [[Property tree]]<br />
<br />
=== Readme files ===<br />
* {{readme file|properties}}<br />
<br />
=== Source code ===<br />
==== Consumables ====<br />
* {{flightgear file|src/FDM/TankProperties.hxx}}<br />
* {{flightgear file|src/FDM/TankProperties.cxx}}<br />
<br />
==== Controls ====<br />
* {{flightgear file|src/Aircraft/controls.hxx}}<br />
* {{flightgear file|src/Aircraft/controls.cxx}}<br />
<br />
==== Flight Dynamics Model ====<br />
* {{flightgear file|src/FDM/flightProperties.hxx}}<br />
* {{flightgear file|src/FDM/flightProperties.cxx}}<br />
<br />
==== Instrumentation ====<br />
* {{flightgear file|src/Instrumentation}}<br />
<br />
[[Category:Property Tree]]<br />
[[Category:Aircraft enhancement]]</div>Jsbhttps://wiki.flightgear.org/w/index.php?title=Aircraft_properties_reference&diff=139568Aircraft properties reference2024-03-27T12:56:54Z<p>Jsb: Add annunciator draft section</p>
<hr />
<div>{{PropertyTree}}<br />
<br />
This is a generic '''aircraft properties reference'''. There are many properties that are very common in aircraft, and many of them are present even in a very simple aircraft. However, a complete description that matches all the properties is very unlikely to be written, as aircraft can be very different from each other.<br />
<br />
{{TOC limit|2}}<br />
<br />
== What are properties? ==<br />
{{main article|Property tree}}<br />
=== The property tree ===<br />
Most parts of FlightGear communicate with each other through key-value pair properties in the [[property tree]]. The properties represent both the input from the pilot, the values determining the position and velocity of the aircraft, the values used for animating the aircraft, and pretty much anything else.<br />
<br />
While many properties will be common between most aircraft, many properties will also be different between aircraft. This becomes obvious if one consider the many different configurations (aircraft/helicopter/car, control surface and landing gear layout, number and locations of engines, etc) and propulsions systems (types of engines, types of fuels etc.) an aircraft can have. There are also different flight dynamic models (FDMs) that have different needs. In addition many properties will by necessity be aircraft specific, though developers should make a conscious effort to have properties map to more common ones if that is possible.<br />
<br />
=== Where are the properties defined? ===<br />
There are a couple ways that the properties are set, but a fair amount of them just "appear" without being documented anywhere. There are several places to look for properties. One is in the aircraft files, starting from the aircraft specific [[aircraft-set.xml]] file, another is the [[Nasal]] files, and the last place (and often most useful!) is "grepping" (searching) through the C++ code.<br />
<br />
To determine how a property works and what it does often requires looking through any code that uses it. This is a part of FlightGear that we could certainly document better<br />
<br />
== Annunciators [draft] ==<br />
The /instrumentation/annunciators section shall provide a central place<br />
<br />
* for aircrafts to write annunciator status data<br />
* for external devices like joysticks, yokes, throttle quadrants or custom made cockpit hardware to read such data and drive status lights etc.<br />
<pre><br />
/instrumentation/annunciators/doors<br />
/instrumentation/annunciators/master-caution/state # aircraft.light<br />
/instrumentation/annunciators/master-warning/state # aircraft.light<br />
<br />
/instrumentation/annunciators/ap/enabled<br />
/instrumentation/annunciators/ap/mode/alt<br />
/instrumentation/annunciators/ap/mode/apr<br />
/instrumentation/annunciators/ap/mode/hdg<br />
/instrumentation/annunciators/ap/mode/ias<br />
/instrumentation/annunciators/ap/mode/nav<br />
/instrumentation/annunciators/ap/mode/rev<br />
/instrumentation/annunciators/ap/mode/vs<br />
<br />
/instrumentation/annunciators/engines/<br />
/instrumentation/annunciators/engines/apu<br />
/instrumentation/annunciators/engines/fire<br />
/instrumentation/annunciators/engines/oil-pressure-low<br />
/instrumentation/annunciators/engines/starter<br />
<br />
/instrumentation/annunciators/gear/nose/green<br />
/instrumentation/annunciators/gear/nose/red<br />
/instrumentation/annunciators/gear/left/green<br />
/instrumentation/annunciators/gear/left/red<br />
/instrumentation/annunciators/gear/right/green<br />
/instrumentation/annunciators/gear/right/red<br />
/instrumentation/annunciators/gear/parking-brake<br />
<br />
/instrumentation/annunciators/systems/anti-ice/enabled<br />
/instrumentation/annunciators/systems/fuel/aux-pump<br />
/instrumentation/annunciators/systems/fuel/pressure-low<br />
/instrumentation/annunciators/systems/hyd/pressure-low<br />
/instrumentation/annunciators/systems/vacuum<br />
<br />
</pre><br />
<br />
== Consumables ==<br />
:''See also [[#Fuel]]<br />
<pre><br />
/consumables/fuel/tank[%d]/level-lb<br />
/consumables/fuel/tank[%d]/level-lbs<br />
/consumables/fuel/tank[%d]/level-gal_us<br />
/consumables/fuel/tank[%d]/capacity-gal_us<br />
/consumables/fuel/tank[%d]/density-ppg<br />
/consumables/fuel/total-fuel-lbs<br />
/consumables/fuel/total-gal_us<br />
</pre><br />
<br />
== Controls ==<br />
These properties should, and usually do correspond to how various cockpit controls are set. Consider these the pilot input.<br />
<br />
=== Anti-ice ===<br />
These properties control the various anti-ice properties that may be present in an aircraft.<br />
<br />
<pre><br />
/controls/anti-ice/wing-heat<br />
/controls/anti-ice/pitot-heat<br />
/controls/anti-ice/wiper<br />
/controls/anti-ice/window-heat<br />
/controls/anti-ice/engine[%d]/carb-heat<br />
/controls/anti-ice/engine[%d]/inlet-heat<br />
</pre><br />
TODO - section notes<br />
<br />
=== APU ===<br />
These properties control any auxiliary power unit, in essence a small turbine engine driving generators, hydraulic pumps etc. before and after the aircraft's engines are up an running.<br />
<br />
<pre><br />
/controls/APU/off-start-run<br />
/controls/APU/fire-switch<br />
</pre><br />
TODO - section notes<br />
<br />
=== Armament ===<br />
TODO - explain of section<br />
<pre><br />
/controls/armament/master-arm<br />
/controls/armament/station-select<br />
/controls/armament/release-all<br />
/controls/armament/station[%d]/stick-size<br />
/controls/armament/station[%d]/release-stick<br />
/controls/armament/station[%d]/release-all<br />
/controls/armament/station[%d]/jettison-all<br />
</pre><br />
TODO - section notes<br />
<br />
=== Autoflight ===<br />
These properties control the autopilot.<br />
<br />
TODO - explain of section<br />
<pre><br />
/controls/autoflight/autopilot[%d]/engage<br />
/controls/autoflight/autothrottle-arm<br />
/controls/autoflight/autothrottle-engage<br />
/controls/autoflight/heading-select<br />
/controls/autoflight/altitude-select<br />
/controls/autoflight/bank-angle-select<br />
/controls/autoflight/vertical-speed-select<br />
/controls/autoflight/speed-select<br />
/controls/autoflight/mach-select<br />
/controls/autoflight/vertical-mode<br />
/controls/autoflight/lateral-mode<br />
</pre><br />
TODO - section notes<br />
<br />
=== Electric ===<br />
TODO - explain of section<br />
<pre><br />
/controls/electric/battery-switch<br />
/controls/electric/external-power<br />
/controls/electric/APU-generator<br />
/controls/electric/engine[%d]/generator<br />
/controls/electric/engine[%d]/bus-tie<br />
</pre><br />
TODO - section notes<br />
<br />
=== Engines ===<br />
Engines are numbered engine[0] for a single engine to engine[0] to engine[3] for a 747 for example. (The model allows for up to 12 engines rumour has it ;-)<br />
<br />
<pre><br />
/controls/engines/throttle_idle<br />
/controls/engines/engine[%d]/throttle<br />
/controls/engines/engine[%d]/starter<br />
/controls/engines/engine[%d]/fuel-pump<br />
/controls/engines/engine[%d]/fire-switch<br />
/controls/engines/engine[%d]/fire-bottle-discharge<br />
/controls/engines/engine[%d]/cutoff<br />
/controls/engines/engine[%d]/mixture<br />
/controls/engines/engine[%d]/propeller-pitch<br />
/controls/engines/engine[%d]/magnetos<br />
/controls/engines/engine[%d]/boost<br />
/controls/engines/engine[%d]/WEP<br />
/controls/engines/engine[%d]/cowl-flaps-norm<br />
/controls/engines/engine[%d]/feather<br />
/controls/engines/engine[%d]/ignition<br />
/controls/engines/engine[%d]/augmentation<br />
/controls/engines/engine[%d]/afterburner<br />
/controls/engines/engine[%d]/reverser<br />
/controls/engines/engine[%d]/water-injection<br />
/controls/engines/engine[%d]/condition<br />
</pre><br />
TODO - section notes<br />
<br />
=== Flight controls ===<br />
These properties control the flight controls surfaces, though often through a mechanical, analog or digital flight control system (FCS) that may or may not be modeled.<br />
<br />
<pre><br />
/controls/flight/aileron<br />
/controls/flight/aileron-trim<br />
/controls/flight/elevator<br />
/controls/flight/elevator-trim<br />
/controls/flight/rudder<br />
/controls/flight/rudder-trim<br />
/controls/flight/flaps<br />
/controls/flight/slats<br />
/controls/flight/BLC // Boundary Layer Control<br />
/controls/flight/spoilers<br />
/controls/flight/speedbrake<br />
/controls/flight/wing-sweep<br />
/controls/flight/wing-fold<br />
/controls/flight/drag-chute<br />
</pre><br />
<br />
The positions of the controls are usually put in <code>/surface-positions/</code> at the discretion of the aircraft designer. These usually drive the animations of the control surfaces. They are either normalized (like <code>/surface-positions/elevator-pos-norm</code>) or in degrees, and sometimes the aileron is split left/right.<br />
<br />
=== Fuel ===<br />
TODO - explain of section<br />
<pre><br />
/controls/fuel/dump-valve<br />
/controls/fuel/tank[%d]/fuel_selector<br />
/controls/fuel/tank[%d]/to_engine<br />
/controls/fuel/tank[%d]/to_tank<br />
/controls/fuel/tank[%d]/boost-pump[%d]<br />
</pre><br />
TODO - section notes<br />
<br />
=== Gear ===<br />
TODO - explain of section<br />
<pre><br />
/controls/gear/brake-left<br />
/controls/gear/brake-right<br />
/controls/gear/brake-parking<br />
/controls/gear/steering // Used if rudder is not sufficient for control of steering<br />
/controls/gear/gear-down<br />
/controls/gear/antiskid // Deprecated?<br />
/controls/gear/tailhook<br />
/controls/gear/tailwheel-lock<br />
/controls/gear/wheel[%d]/alternate-extension<br />
</pre><br />
TODO - section notes<br />
<br />
=== Hydraulics ===<br />
TODO - explain of section<br />
<pre><br />
/controls/hydraulic/system[%d]/engine-pump<br />
/controls/hydraulic/system[%d]/electric-pump<br />
</pre><br />
TODO - section notes<br />
<br />
=== Lights ===<br />
TODO - explain of section<br />
<pre><br />
/controls/lighting/landing-lights<br />
/controls/lighting/turn-off-lights<br />
/controls/lighting/formation-lights<br />
/controls/lighting/taxi-light<br />
/controls/lighting/logo-lights<br />
/controls/lighting/nav-lights<br />
/controls/lighting/beacon<br />
/controls/lighting/strobe<br />
/controls/lighting/panel-norm<br />
/controls/lighting/instruments-norm<br />
/controls/lighting/dome-norm<br />
</pre><br />
TODO - section notes<br />
<br />
=== Pneumatic ===<br />
TODO - explain of section<br />
<pre><br />
/controls/pneumatic/APU-bleed<br />
/controls/pneumatic/engine[%d]/bleed<br />
</pre><br />
TODO - section notes<br />
<br />
=== Pressurization ===<br />
TODO - explain of section<br />
<pre><br />
/controls/pressurization/mode<br />
/controls/pressurization/dump<br />
/controls/pressurization/outflow-valve<br />
/controls/pressurization/pack[%d]/pack-on<br />
</pre><br />
TODO - section notes<br />
<br />
=== Seat ===<br />
TODO - explain of section<br />
<pre><br />
/controls/seat/vertical-adjust<br />
/controls/seat/fore-aft-adjust<br />
/controls/seat/cmd_selector_valve<br />
/controls/seat/eject[%d]/initiate<br />
/controls/seat/eject[%d]/status<br />
</pre><br />
TODO - section notes<br />
<br />
== Engines ==<br />
TODO - explain of section<br />
=== Common ===<br />
<pre><br />
/engines/engine[%d]/fuel-flow-gph<br />
/engines/engine[%d]/fuel-flow_pph<br />
/engines/engine[%d]/thrust_lb<br />
/engines/engine[%d]/running<br />
/engines/engine[%d]/starter<br />
/engines/engine[%d]/cranking<br />
</pre><br />
<br />
=== Turbine ===<br />
<pre><br />
/engines/engine[%d]/n1<br />
/engines/engine[%d]/n2<br />
/engines/engine[%d]/epr<br />
/engines/engine[%d]/augmentation<br />
/engines/engine[%d]/water-injection<br />
/engines/engine[%d]/ignition<br />
/engines/engine[%d]/nozzle-pos-norm<br />
/engines/engine[%d]/inlet-pos-norm<br />
/engines/engine[%d]/reversed<br />
/engines/engine[%d]/cutoff<br />
</pre><br />
<br />
=== Piston ===<br />
<pre><br />
/engines/engine[%d]/mp-osi<br />
/engines/engine[%d]/egt-degf<br />
/engines/engine[%d]/oil-temperature-degf<br />
/engines/engine[%d]/oil-pressure-psi<br />
/engines/engine[%d]/cht-degf<br />
/engines/engine[%d]/rpm<br />
</pre><br />
<br />
=== Propeller ===<br />
<pre><br />
/engines/engine[%d]/rpm<br />
/engines/engine[%d]/pitch<br />
/engines/engine[%d]/torque<br />
</pre><br />
TODO - section notes<br />
<br />
== Flight Dynamics Model ==<br />
=== Position ===<br />
This will return the current position of the aircraft within FlightGear. This is also the stuff that is transmitted in [[Howto:Multiplayer|multiplayer]].<br />
<br />
<pre><br />
/position/<br />
/position/altitiude-ft ()<br />
/position/altitude-agl-ft (22.46983965)<br />
/position/altitude-ft (28.24368289)<br />
/position/ground-elev-ft (-0.43513529)<br />
/position/ground-elev-m (-0.1326292364)<br />
/position/latitude-deg (37.61371436)<br />
/position/latitude-string (37*36 49.4N)<br />
/position/longitude-deg (-122.3576508)<br />
/position/longitude-string (-122*21 27.5W)<br />
/position/sea-level-radius-ft (20899648.76)<br />
</pre><br />
<br />
=== Orientation ===<br />
TODO - explain of section<br />
<pre><br />
/orientation/roll-deg<br />
/orientation/pitch-deg<br />
/orientation/heading-deg<br />
<br />
/orientation/roll-rate-degps<br />
/orientation/pitch-rate-degps<br />
/orientation/yaw-rate-degps<br />
<br />
/orientation/side-slip-rad<br />
/orientation/side-slip-deg<br />
/orientation/alpha-deg<br />
</pre><br />
TODO - section notes<br />
<br />
=== Velocities ===<br />
TODO - explain of section<br />
<pre><br />
/velocities/airspeed-kt<br />
/velocities/mach<br />
/velocities/speed-north-fps<br />
/velocities/speed-east-fps<br />
/velocities/speed-down-fps<br />
<br />
/velocities/uBody-fps<br />
/velocities/vBody-fps<br />
/velocities/wBody-fps<br />
<br />
/velocities/vertical-speed-fps<br />
/velocities/glideslope<br />
</pre><br />
TODO - section notes<br />
<br />
=== Acceleration ===<br />
TODO - explain of section<br />
<pre><br />
/accelerations/nlf<br />
<br />
/accelerations/ned/north-accel-fps_sec<br />
/accelerations/ned/east-accel-fps_sec<br />
/accelerations/ned/down-accel-fps_sec<br />
<br />
/accelerations/pilot/x-accel-fps_sec<br />
/accelerations/pilot/y-accel-fps_sec<br />
/accelerations/pilot/z-accel-fps_sec<br />
</pre><br />
TODO - section notes<br />
<br />
== Gear ==<br />
<pre><br />
/gear/serviceable<br />
/gear/gear[%d]/cast-angle-deg // The angle of the wheel where 0 is pointing straight forward<br />
/gear/gear[%d]/compression-m<br />
/gear/gear[%d]/compression-norm<br />
/gear/gear[%d]/ground-friction-factor<br />
/gear/gear[%d]/ground-is-solid<br />
/gear/gear[%d]/has-brake<br />
/gear/gear[%d]/rollspeed-ms // Speed of the wheel's rotation in meters per second<br />
/gear/gear[%d]/wow // Weight-on-wheel<br />
/gear/gear[%d]/xoffset-in<br />
/gear/gear[%d]/yoffset-in<br />
/gear/gear[%d]/zoffset-in<br />
</pre><br />
<br />
== Instrumentation ==<br />
<pre><br />
/instrumentation/adf/<br />
/instrumentation/airspeed-indicator/<br />
/instrumentation/altimeter/<br />
/instrumentation/annunciator/<br />
/instrumentation/attitude-indicator/<br />
/instrumentation/clock/<br />
/instrumentation/comm/<br />
/instrumentation/comm[1]/<br />
/instrumentation/dme/<br />
/instrumentation/efis/<br />
/instrumentation/encoder/<br />
/instrumentation/flightdirector/<br />
/instrumentation/gps/<br />
/instrumentation/gps-annunciator/<br />
/instrumentation/heading-indicator/<br />
/instrumentation/heading-indicator-fg/<br />
/instrumentation/magnetic-compass/<br />
/instrumentation/marker-beacon/<br />
/instrumentation/nav/<br />
/instrumentation/nav[1]/<br />
/instrumentation/radar/<br />
/instrumentation/slip-skid-ball/<br />
/instrumentation/tacan/<br />
/instrumentation/transponder/<br />
/instrumentation/turn-indicator/<br />
/instrumentation/vertical-speed-indicator/<br />
/instrumentation/wxradar/<br />
</pre><br />
<br />
== Rotors (YASim only) ==<br />
<pre><br />
/rotors/gear/torque-sound-filtered // Unused?<br />
/rotors/gear/total-torque<br />
/rotors/{name}/balance<br />
/rotors/{name}/blade[%d]/flap-deg<br />
/rotors/{name}/blade[%d]/incidence-deg<br />
/rotors/{name}/blade[%d]/position-deg // Position relative to model<br />
/rotors/{name}/bladesvisible // Used for animations<br />
/rotors/{name}/cone%d-deg //e.g. cone-deg or cone2-deg<br />
/rotors/{name}/roll-deg<br />
/rotors/{name}/rpm<br />
/rotors/{name}/stall<br />
/rotors/{name}/stall-filtered<br />
/rotors/{name}/tilt<br />
/rotors/{name}/torque<br />
/rotors/{name}/yaw-deg<br />
</pre><br />
<br />
For how to animate rotors using these properties, see [[Howto:Animate helicopters]].<br />
<br />
== Related content ==<br />
=== Wiki articles ===<br />
* [[Aircraft-set.xml]]<br />
* [[Multiplayer protocol]]<br />
* [[Property browser]]<br />
* [[PropertyList XML files]]<br />
* [[Property tree]]<br />
<br />
=== Readme files ===<br />
* {{readme file|properties}}<br />
<br />
=== Source code ===<br />
==== Consumables ====<br />
* {{flightgear file|src/FDM/TankProperties.hxx}}<br />
* {{flightgear file|src/FDM/TankProperties.cxx}}<br />
<br />
==== Controls ====<br />
* {{flightgear file|src/Aircraft/controls.hxx}}<br />
* {{flightgear file|src/Aircraft/controls.cxx}}<br />
<br />
==== Flight Dynamics Model ====<br />
* {{flightgear file|src/FDM/flightProperties.hxx}}<br />
* {{flightgear file|src/FDM/flightProperties.cxx}}<br />
<br />
==== Instrumentation ====<br />
* {{flightgear file|src/Instrumentation}}<br />
<br />
[[Category:Property Tree]]<br />
[[Category:Aircraft enhancement]]</div>Jsbhttps://wiki.flightgear.org/w/index.php?title=Nasal_library&diff=138727Nasal library2023-12-02T17:26:10Z<p>Jsb: /* contains() */ Fix wrong variable name in example (copy&paste error)</p>
<hr />
<div>{{Nasal Navigation|nocat=1}}<br />
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:<br />
* {{flightgear file|src/Scripting}}<br />
* {{simgear file|simgear/nasal}}<br />
<br />
All these functions and variables are in the global namespace, that is, they are directly accessible (e.g., one can call <syntaxhighlight lang="nasal" inline>magvar()</syntaxhighlight> instead of <syntaxhighlight lang="nasal" inline>namespace.magvar()</syntaxhighlight>). However, if a namespace must be used, <code>globals</code> is the correct namespace, but using it is not recommended. For a more complete explanation, see [[Nasal Namespaces in-depth]].<br />
<br />
{{tip|Copy & paste the examples into your [[Nasal Console]] and execute them to see what they do.|width=70%}}<br />
<br />
== Core library functions ==<br />
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:<br />
* http://plausible.org/nasal/lib.html ([http://web.archive.org/web/20101010094553/http://plausible.org/nasal/lib.html archive])<br />
* {{simgear file|simgear/nasal/lib.c}} ([http://sourceforge.net/p/flightgear/simgear/ci/next/log/?path=/simgear/nasal/lib.c history])<br />
<br />
=== append() ===<br />
{{Nasal doc<br />
|syntax = append(vector, element[, element[, ...]]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=42|t=Source}}<br />
|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.<br />
|param1 = vector<br />
|param1text = The vector to which the arguments will be appended.<br />
|param2 = element<br />
|param2text = An element to be added to the vector.<br />
|example1 = <br />
var vector = [1, 2, 3]; # Initialize the vector<br />
append(vector, 4); # Append the number 4 to the end of the vector<br />
debug.dump(vector); # Print the contents of the vector<br />
|example2 = <br />
var vector = [1, 2, 3]; # Initialize the vector<br />
append(vector, 4, 5, 6); # Append the numbers 4, 5, and 6 to the end of the vector<br />
debug.dump(vector); # Print the contents of the vector<br />
}}<br />
<br />
=== bind() ===<br />
{{Nasal doc<br />
|syntax = bind(function, locals[, outer_scope]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=502|t=Source}}<br />
|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.<br />
|param1 = function<br />
|param1text = Function to evaluate.<br />
|param2 = locals<br />
|param2text = Hash containing values that will become the namespace (first closure) for the function.<br />
|param3 = outer_scope<br />
|param3text = Optional function which is bound to the next closure. This can be bound to yet another, making a linked list.<br />
|example1 = # This is a namespace/hash with a single member, named "key," which is initialized to 12 <br />
var Namespace = {<br />
key: 12<br />
};<br />
<br />
# This is different namespace/hash containing a function<br />
# dividing a variable "key" (which is unavailable/nil in this namespace) by 2<br />
var AnotherNamespace = {<br />
ret: func {<br />
key /= 2;<br />
}<br />
};<br />
<br />
# To see that key is not available, try to call AnotherNamespace.ret() first<br />
call(AnotherNamespace.ret, [], nil, nil, var errors = []);<br />
if(size(errors)){<br />
print("Key could not be divided/resolved!");<br />
debug.printerror(errors);<br />
}<br />
<br />
# Associate the AnotherNamespace.ret() function with the first namespace<br />
# so that "key" is now available<br />
var function = bind(AnotherNamespace.ret, Namespace);<br />
<br />
# Invoke the new function<br />
function();<br />
<br />
# Print out the value of Namespace.key<br />
# It was changed to 12 from 6 by AnotherNamespace.ret()<br />
print(Namespace.key);<br />
}}<br />
<br />
=== call() ===<br />
{{Nasal doc<br />
|syntax = call(func[, args[, me[, locals[, error]]]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=247|t=Source}}<br />
|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. <br />
|param1 = func<br />
|param1text = Function to execute.<br />
|param2 = args<br />
|param2text = Vector containing arguments to give to the called function.<br />
|param3 = me<br />
|param3text = <code>'''me'''</code> reference for the function call (i.e., for method calls). If given, this will override any <code>'''me'''</code> value existing in the namespace (locals argument).<br />
|param4 = locals<br />
|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.<br />
|param5 = error<br />
|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}}.<br />
|example1 =<br />
# prints "Called from call()"<br />
call(func {<br />
print("Called from call()");<br />
});<br />
|example2 =<br />
# prints "a = 1 : b = 2<br />
call(func(a, b){<br />
print("a = ", a, " : b = ", b);<br />
},<br />
[1, 2]<br />
);<br />
|example3 =<br />
var Hash = {<br />
new: func {<br />
var m = { parents: [Hash] };<br />
<br />
m.el1 = "string1";<br />
m.el2 = "string2";<br />
<br />
return m;<br />
}<br />
};<br />
<br />
# prints "me.el1 = string1", then "me.el2 = string2" on the next line<br />
call(func(a, b){ <br />
print("me.el", a, " = ", me["el" ~ a]); <br />
print("me.el", b, " = ", me["el" ~ b]);<br />
},<br />
[1, 2],<br />
Hash.new()<br />
);<br />
|example4 =<br />
# prints the value of math.pi<br />
call(func {<br />
print(pi);<br />
}, nil, nil, <br />
math<br />
);<br />
|example5 =<br />
call(func {<br />
print(math.ip); # math.ip doesn't exist<br />
}, nil, nil, nil,<br />
var errs = []<br />
);<br />
debug.printerror(errs); # The error is caught and printed using debug.printerror()<br />
}}<br />
<br />
=== caller() ===<br />
{{Nasal doc<br />
|syntax = caller([level]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=404|t=Source}}<br />
|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.<br />
<br />
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. <br />
|param1 = level<br />
|param1text = Optional integer specifying the stack level to return a result from. Defaults to 1 (i.e. the caller of the currently executing function).<br />
|example1 =<br />
var myFunction = func(a, b){<br />
debug.dump(caller(0)[0]); # prints a hash of local variables, including arguments a and b<br />
return 2 * 2;<br />
};<br />
<br />
print("2 x 2 = ", myFunction(2, 2));<br />
|example2 =<br />
var get_arg_value = func(){<br />
print("Argument to myFunc = ", caller(1)[0]['a']); # print the value of myFunc's single argument, using caller()<br />
};<br />
<br />
var myFunc = func(a){<br />
get_arg_value();<br />
};<br />
<br />
myFunc(3);<br />
|example3text = This is a real example taken from {{fgdata file|Nasal/canvas/MapStructure.nas}}. Function <code>r()</code> (above the TODOs) returns a hash with the key/value pairs as per its arguments. For example, something like this is returned: <code>{ name: "<name>", vis: 1, zindex: nil }</code>.<br />
|example3 =<br />
var MapStructure_selfTest = func() {<br />
var temp = {};<br />
temp.dlg = canvas.Window.new([600,400],"dialog");<br />
temp.canvas = temp.dlg.createCanvas().setColorBackground(1,1,1,0.5);<br />
temp.root = temp.canvas.createGroup();<br />
var TestMap = temp.root.createChild("map");<br />
TestMap.setController("Aircraft position");<br />
TestMap.setRange(25); # TODO: implement zooming/panning via mouse/wheel here, for lack of buttons :-/<br />
TestMap.setTranslation(<br />
temp.canvas.get("view[0]")/2,<br />
temp.canvas.get("view[1]")/2<br />
);<br />
var r = func(name,vis=1,zindex=nil) return caller(0)[0];<br />
# TODO: we'll need some z-indexing here, right now it's just random<br />
# TODO: use foreach/keys to show all layers in this case by traversing SymbolLayer.registry direclty ?<br />
# maybe encode implicit z-indexing for each lcontroller ctor call ? - i.e. preferred above/below order ?<br />
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'), ] ) <br />
TestMap.addLayer(factory: canvas.SymbolLayer, type_arg: type.name,<br />
visible: type.vis, priority: type.zindex,<br />
);<br />
}; # MapStructure_selfTest<br />
}}<br />
<br />
=== chr() ===<br />
{{Nasal doc<br />
|syntax = chr(code);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=175|t=Source}}<br />
|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 (<code>"string"</code>) supports control chracters. Strings in single quotes (<code>'string'</code>) do not.}}<br />
{{{!}} class="wikitable"<br />
! Code !! Name !! Equivalent to<br />
{{!-}}<br />
{{!}} 10 {{!!}} {{Wikipedia|Newline}} {{!!}} <code>\n</code><br />
{{!-}}<br />
{{!}} 9 {{!!}} {{Wikipedia|Tab key#Tab characters|Horizontal tab}} {{!!}} <code>\t</code><br />
{{!-}}<br />
{{!}} 13 {{!!}} {{Wikipedia|Carriage return}} {{!!}} <code>\r</code><br />
{{!}}}<br />
|param1 = code<br />
|param1text = Integer character code for the desired glyph.<br />
|example1 = print("Code 65 = ", chr(65)); # prints "Code 65 = A"<br />
|example2text = This example displays all of the characters in a list, in the format <code>Code '''n''' = >'''char'''<</code>, '''n''' being the index, and '''char''' being the character.<br />
|example2 =<br />
for(var i = 0; i <= 255; i += 1){<br />
print("Code ", i, " = >", chr(i), "<");<br />
}<br />
}}<br />
<br />
=== closure() ===<br />
{{Nasal doc<br />
|syntax = closure(func[, level]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=421|t=Source}}<br />
|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'''. <br />
|param1 = func<br />
|param1text = Function to evaluate.<br />
|param2 = level<br />
|param2text = Optional integer specifying the scope level. Defaults to 0 (the namespace of '''func''').<br />
|example1 =<br />
var get_math_e = func {<br />
return e; # return the value of math.e<br />
}<br />
<br />
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<br />
debug.dump(closure(myFunction)); # print the namespace of get_math_e<br />
<br />
print(myFunction());<br />
}}<br />
<br />
=== cmp() ===<br />
{{Nasal doc<br />
|syntax = cmp(a, b);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=112|t=Source}}<br />
|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'''. <br />
|param1 = a<br />
|param1text = First string argument for comparison.<br />
|param2 = b<br />
|param2text = Second string argument for comparison.<br />
|example1 = print(cmp("1", "two")); # prints -1<br />
|example2 = print(cmp("string", "string")); # prints 0<br />
|example3 = print(cmp("one", "2")); # prints 1<br />
|example4 = print(cmp("string1", "string2")); # prints -1<br />
}}<br />
<br />
=== compile() ===<br />
{{Nasal doc<br />
|syntax = compile(code[, filename]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=220|t=Source}}<br />
|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'''.<br />
|param1 = code<br />
|param1text = String containing Nasal code to be compiled.<br />
|param2 = filename<br />
|param2text = Optional string used for error messages/logging. Defaults to <code><compile></code><br />
|example1 = <br />
var myCode = 'print("hello");';<br />
var helloFunc = compile(myCode, "myCode");<br />
helloFunc();<br />
|example2text = <code>compile</code> 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 <tt>''[[$FG_ROOT]]/gui/dialogs/test.xml''</tt>.<br />
<syntaxhighlight lang="xml"><br />
<?xml version="1.0"?><br />
<br />
<PropertyList><br />
<br />
<nasal><![CDATA[<br />
print("You have FlightGear v", getprop("/sim/version/flightgear"));<br />
]]></nasal><br />
<br />
</PropertyList><br />
</syntaxhighlight><br />
Now, start FlightGear and execute this code in the [[Nasal Console]].<br />
|example2 =<br />
# Build the path<br />
var FGRoot = getprop("/sim/fg-root");<br />
var filename = "/gui/dialogs/test.xml";<br />
var path = FGRoot ~ filename;<br />
<br />
var blob = io.read_properties(path);<br />
var script = blob.getValues().nasal; # Get the nasal string<br />
<br />
# Compile the script. We're passing the filename here for better runtime diagnostics <br />
var code = call(func {<br />
compile(script, filename);<br />
}, nil, nil, var compilation_errors = []);<br />
<br />
if(size(compilation_errors)){<br />
die("Error compiling code in: " ~ filename);<br />
}<br />
<br />
# Invoke the compiled script, equivalent to code(); <br />
# We're using call() here to detect errors:<br />
call(code, [], nil, nil, var runtime_errors = []);<br />
<br />
if(size(runtime_errors)){<br />
die("Error calling code compiled loaded from: " ~ filename);<br />
}<br />
}}<br />
<br />
=== contains() ===<br />
{{Nasal doc<br />
|syntax = contains(hash, key);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=184|t=Source}}<br />
|text = Returns 1 (True) if the hash contains the specified key, or 0 (False) if not.<br />
|param1 = hash<br />
|param1text = The hash to search in.<br />
|param2 = key<br />
|param2text = The scalar to be searched for, contained as a key in the hash.<br />
|example1 =<br />
# Initialize a hash<br />
var hash = {<br />
element: "value"<br />
};<br />
print(contains(hash, "element") ? "Yes" : "No"); # This will print "Yes"<br />
|example2 =<br />
# Initialize a hash<br />
var hash = {<br />
element: "value"<br />
};<br />
print(contains(hash, "element2") ? "Yes" : "No"); # This will print "No"<br />
}}<br />
<br />
<br />
===contains()===<br />
{{Requires commit|desc=Vector support|details=see {{Merge-request|project=fgdata|id=305}}|commit=ee39abbd3b70c9b6d5e3a1c4ccedddaac1a92b11|repo=Simgear}}<br />
{{Nasal doc<br />
|syntax = contains(vector, item);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=184|t=Source}}<br />
|text = Returns 1 (True) if the vector contains the specified item, or 0 (False) if not.<br />
|param1 = vector<br />
|param1text = The vector to search in.<br />
|param2 = item<br />
|param2text = The object to be searched for in the vector.<br />
|example1 =<br />
# Initialize a hash<br />
var vec = ["element", "foo"];<br />
print(contains(vec, "element") ? "Yes" : "No"); # This will print "Yes"<br />
|example2 =<br />
# Initialize a hash<br />
var vec = ["element", "foo"];<br />
print(contains(vec, "element2") ? "Yes" : "No"); # This will print "No"<br />
}}<br />
<br />
===delete() ===<br />
{{Nasal doc<br />
|syntax = delete(hash, key);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=83|t=Source}}<br />
|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 <code>'''nil'''</code> 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.<br />
|param1 = hash<br />
|param1text = The hash from which to delete the key.<br />
|param2 = key<br />
|param2text = The scalar to be deleted, contained as a key in the hash.<br />
|example1 =<br />
# Initialize the hash<br />
var hash = {<br />
element1: "value1",<br />
element2: "value2"<br />
};<br />
delete(hash, "element1"); # Delete element1<br />
debug.dump(hash); # prints the hash, which is now minus element1<br />
}}<br />
<br />
===die()===<br />
{{Nasal doc<br />
|syntax = die(error);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=288|t=Source}}<br />
|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()}}.<br />
|param1 = error<br />
|param1text = String describing the error.<br />
:{{inote|This parameter is technically optional, but it is highly recommended to use it.}}<br />
|example1 = <br />
print("Will print");<br />
die("Don't go any further!"); <br />
print("Won't print"); # Will not be printed because die() stops the process<br />
}}<br />
<br />
=== find()===<br />
{{Nasal doc<br />
|syntax = find(needle, haystack);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=450|t=Source}}<br />
|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.<br />
|param1 = needle<br />
|param1text = String to search for.<br />
|param2 = haystack<br />
|param2text = String to search in.<br />
|example1 = print(find("c", "abcdef")); # prints 2<br />
|example2 = print(find("x", "abcdef")); # prints -1<br />
|example3 = print(find("cd", "abcdef")); # prints 2<br />
}}<br />
<br />
===ghosttype()===<br />
{{Nasal doc<br />
|syntax = ghosttype(ghost);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=207|t=Source}}<br />
|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++ <code>naGhostType</code> 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.<br />
|param1 = ghost<br />
|param1text = Ghost to return a description for.<br />
|example1 = print(ghosttype(airportinfo())); # prints "airport"<br />
}}<br />
<br />
===id()===<br />
{{Nasal doc<br />
|syntax = id(object);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=570|t=Source}}<br />
|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 <code>'''<type>''':'''<id>'''</code>, where '''<type>''' is the type of object, and '''<id>''' is the ID.<br />
|param1 = object<br />
|param1text = Can be either of a string, a vector, a hash, a code, a function, or a ghost.<br />
|example1 = print(id("A")); # prints "str:000000001624A590"<br />
}}<br />
<br />
===int() ===<br />
{{Nasal doc<br />
|syntax = int(number);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=90|t=Source}}<br />
|text = Returns the integer part of the numeric value of the single argument, or <code>'''nil'''</code> if none exists.<br />
|param1 = number<br />
|param1text = Number or string with just a number in it to return an integer from.<br />
|example1 = print(int(23)); # prints "23"<br />
|example2 = print(int(23.123)); # prints "23"<br />
|example3 = debug.dump(int("string")); # prints "nil"<br />
}}<br />
<br />
===keys()===<br />
{{Nasal doc<br />
|syntax = keys(hash);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=33|t=Source}}<br />
|text = Returns a vector containing the list of keys found in the single hash argument. <br />
|param1 = hash<br />
|param1text = The hash to return the keys from.<br />
|example1 = <br />
# Initialize a hash<br />
var hash = {<br />
element1: "value",<br />
element2: "value"<br />
};<br />
debug.dump(keys(hash)); # print the vector<br />
}}<br />
<br />
===left()===<br />
{{Nasal doc<br />
|syntax = left(string, length);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=149|t=Source}}<br />
|version = 2.12<br />
|commit = {{simgear commit|bd7163|t=commit}}<br />
|text = Returns a substring of '''string''', starting from the left.<br />
|param1 = string<br />
|param1text = String to return part of.<br />
|param2 = length<br />
|param2text = Integer specifying the length of the substring to return.<br />
|example1 = print(left("string", 2)); # prints "st"<br />
}}<br />
<br />
=== num()===<br />
{{Nasal doc<br />
|syntax = num(number);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=102|t=Source}}<br />
|text = Returns the numerical value of the single string argument, or <code>'''nil'''</code> if none exists. <br />
|param1 = number<br />
|param1text = String with just a number in it to return a number from.<br />
|example1 = print(num("23")); # prints "23"<br />
|example2 = print(num("23.123")); # prints "23.123"<br />
|example3 = debug.dump(num("string")); # prints "nil"<br />
}}<br />
<br />
===pop()===<br />
{{Nasal doc<br />
|syntax = pop(vector);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=50|t=Source}}<br />
|text = Removes and returns the last element of the single vector argument, or <code>'''nil'''</code> if the vector is empty. <br />
|param1 = vector<br />
|param1text = Vector to remove an element from.<br />
|example1 = <br />
var vector = [1, 2, 3];<br />
pop(vector);<br />
debug.dump(vector); # prints "[1, 2]"<br />
|example2 = <br />
var vector = [1, 2, 3];<br />
debug.dump(pop(vector)); # prints "3"<br />
|example3 = <br />
var vector = [];<br />
debug.dump(pop(vector)); # prints "nil"<br />
}}<br />
<br />
===right()===<br />
{{Nasal doc<br />
|syntax = right(string, length);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=161|t=Source}}<br />
|version = 2.12<br />
|commit = {{simgear commit|bd7163|t=commit}}<br />
|text = Returns a substring of '''string''', starting from the right.<br />
|param1 = string<br />
|param1text = String to return part of.<br />
|param2 = length<br />
|param2text = Integer specifying the length of the substring to return.<br />
|example1 = print(right("string", 2)); # prints "ng"<br />
}}<br />
<br />
=== setsize()===<br />
{{Nasal doc<br />
|syntax = setsize(vector, size);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=56|t=Source}}<br />
|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 <code>'''nil'''</code> entries. Returns the vector operated upon. <br />
|param1 = vector<br />
|param1text = The vector to be operated on.<br />
|param2 = size<br />
|param2text = The desired size of the vector in number of entries.<br />
|example1 = <br />
var vector = [1, 2, 3]; # Initialize a vector<br />
setsize(vector, 4);<br />
debug.dump(vector); # print the vector<br />
|example2 = <br />
var vector = [1, 2, 3]; # Initialize a vector<br />
setsize(vector, 2);<br />
debug.dump(vector); # print the vector<br />
}}<br />
<br />
===size()===<br />
{{Nasal doc<br />
|syntax = size(object);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=23|t=Source}}<br />
|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 <code>'''nil'''</code> or a number, this error will be thrown: <code>object has no size()</code>.<br />
|param1 = object<br />
|param1text = Object to find the size of. Must be a string, a vector or a hash.<br />
|example1 = <br />
var string = "string";<br />
print(size(string)); # prints "6"<br />
|example2 =<br />
var vector = [1, 2, 3];<br />
print(size(vector)); # prints "3"<br />
|example3 =<br />
var hash = {<br />
element1: "value1",<br />
element2: "value2",<br />
element3: "value3"<br />
};<br />
print(size(hash)); # prints "3"<br />
}}<br />
<br />
=== sort()===<br />
{{Nasal doc<br />
|syntax = sort(vector, function);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=542|t=Source}}<br />
|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/}}, <code>sort()</code> 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.<br />
|param1 = vector<br />
|param1text = Input vector to sort.<br />
|param2 = function<br />
|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.<br />
{{{!}} class="wikitable"<br />
! Return value !! Meaning<br />
{{!-}}<br />
{{!}} less than 0 {{!!}} first argument should go before second argument<br />
{{!-}}<br />
{{!}} 0 {{!!}} first argument equals second argument<br />
{{!-}}<br />
{{!}} greater than 0 {{!!}} first argument should go after second argument<br />
{{!}}}<br />
<br />
|example1text = This example sorts elements from smallest to greatest.<br />
|example1 = <br />
var sort_rules = func(a, b){<br />
if(a < b){<br />
return -1; # A should before b in the returned vector<br />
}elsif(a == b){<br />
return 0; # A is equivalent to b <br />
}else{<br />
return 1; # A should after b in the returned vector<br />
}<br />
}<br />
debug.dump(sort([3, 2, 5, 6, 4, 1], sort_rules)); # prints "[1, 2, 3, 4, 5, 6]"<br />
|example2text = This example sorts elements from greatest to smallest.<br />
|example2 = <br />
# Outputs the elements in reverse order (greatest to smallest)<br />
var sort_rules = func(a, b){<br />
if(a < b){<br />
return 1; # -1 in the above example<br />
}elsif(a == b){<br />
return 0;<br />
}else{<br />
return -1; # 1 in the above example<br />
}<br />
}<br />
debug.dump(sort([3, 2, 5, 6, 4, 1], sort_rules)); # prints "[6, 5, 4, 3, 2, 1]"<br />
|example3text = This example sorts a vector of strings (runways for example) from smallest to greatest.<br />
|example3 = <br />
var runways = ["09R","27R","26L","09L","15"];<br />
var rwy = sort(runways,func(a,b) cmp(a,b));<br />
debug.dump(rwy); # prints ['09L','09R','15','26L','27R']<br />
}}<br />
<br />
=== split()===<br />
{{Nasal doc<br />
|syntax = split(delimiter, string);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=460|t=Source}}<br />
|text = Splits the input string into a vector of substrings bounded by occurrences of the delimiter substring.<br />
|param1 = delimiter<br />
|param1text = String that will split the substrings in the returned vector.<br />
|param2 = string<br />
|param2text = String to split up.<br />
|example1 = debug.dump(split("cd", "abcdef")); # prints "['ab', 'ef']"<br />
|example2 = debug.dump(split(".", "3.2.0")); # prints "[3, 2, 0]"<br />
|example3 = debug.dump(split("/", "path/to/file")); # prints "['path', 'to', 'file']"<br />
}}<br />
<br />
===sprintf()===<br />
{{Nasal doc<br />
|syntax = <nowiki>sprintf(format[, arg[, arg, [...]]]);</nowiki><br />
|source = {{simgear file|simgear/nasal/lib.c|l=355|t=Source}}<br />
|text = Creates and returns a string formatted using ANSI C {{func link|vsnprintf()|link=http://en.cppreference.com/w/c/io/vfprintf}} <ref><br />
{{Cite web<br />
|url = http://sourceforge.net/p/flightgear/simgear/ci/next/tree/simgear/nasal/lib.c#l308<br />
|title = fgdata/simgear/simgear/nasal/lib.c, line 308<br />
|accessdate = October 2015<br />
}}<br />
</ref>. Below is a table of supported format specifiers.<br />
{{{!}} class="wikitable" width="75%"<br />
{{!}}+ %[flags][width][.precision]specifier<br />
! colspan="2" {{!}} Flags<br />
{{!-}}<br />
! Flag !! Output<br />
{{!-}}<br />
{{!}} <code>+</code> {{!!}} 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.<br />
{{!-}}<br />
{{!}} ''space'' {{!!}} Prefixes non-signed numbers with a space.<br />
{{!-}}<br />
{{!}} <code>-</code> {{!!}} Left-align the output of this placeholder (the default is to right-align the output) when the width option is specified.<br />
{{!-}}<br />
{{!}} <code>0</code> {{!!}} Use 0 instead of spaces to pad a field when the width option is specified.<br />
{{!-}}<br />
{{!}} <code>#</code> {{!!}} Used with <code>o</code>, <code>x</code> or <code>X</code> specifiers the value is preceded with <tt>0</tt>, <tt>0x</tt> or <tt>0X</tt> respectively for values different than zero. Used with <code>e</code>, <code>E</code> and <code>f</code>, 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 <code>g</code> or <code>G</code> the result is the same as with <code>e</code> or <code>E</code> but trailing zeros are not removed.<br />
{{!-}}<br />
! colspan="2" {{!}} Width<br />
{{!-}}<br />
{{!}} colspan="2" {{!}} Integer specifying the minimum number of characters to be returned. This includes the decimal point and decimal fraction as well as + or - signs.<br />
{{!-}}<br />
! colspan="2" {{!}} Precision<br />
{{!-}}<br />
{{!}} colspan="2" {{!}} Integer preceded by a dot specifying the number of decimal places to be written.<br />
{{!-}}<br />
! colspan="2" {{!}} Specifiers<br />
{{!-}}<br />
! Specifier !! Output<br />
{{!-}}<br />
{{!}} <code>d</code>, <code>i</code> {{!!}} Signed decimal number.<br />
{{!-}}<br />
{{!}} <code>s</code> {{!!}} A string<br />
{{!-}}<br />
{{!}} <code>%</code> {{!!}} Percent (%) character.<br />
{{!-}}<br />
{{!}} <code>c</code> {{!!}} 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.<br />
{{!-}}<br />
{{!}} <code>o</code> {{!!}} Unsigned integer as an octal number.<br />
{{!-}}<br />
{{!}} <code>u</code> {{!!}} Unsigned decimal integer.<br />
{{!-}}<br />
{{!}} <code>x</code>, <code>X</code> {{!!}} Unsigned integer as a hexadecimal number. If <code>x</code> is used, any letters in the number are lowercase, while <code>X</code> gives uppercase.<br />
{{!-}}<br />
{{!}} <code>e</code>, <code>E</code> {{!!}} Double value in scientific notation (i.e., ''[-]ddd.ddd'''e'''[+/-]ddd''), with an exponent being denoted by <tt>e</tt> or <tt>E</tt> depending on whether an upper or lowercase is used respectively.<br />
{{!-}}<br />
{{!}} <code>f</code> {{!!}} Floating-point number, in fixed decimal notation, by default with 6 decimal places.<br />
{{!-}}<br />
{{!}} <code>F</code> {{!!}} Appears to be available<ref><br />
{{Cite web<br />
|url = http://sourceforge.net/p/flightgear/simgear/ci/next/tree/simgear/nasal/lib.c#l389<br />
|title = fgdata/simgear/simgear/nasal/lib.c, line 389<br />
|accessdate = October 2015<br />
}}<br />
</ref>, but doesn't work.<br />
{{!-}}<br />
{{!}} <code>g</code>, <code>G</code> {{!!}} Double in either normal or exponential notation, whichever is more appropriate for its magnitude. <code>g</code> uses lower-case letters, <code>G</code> 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.<br />
{{!}}}<br />
<br />
|param1 = format<br />
|param1text = String specifying the format. Can be used with or without a format specifiers. See below for examples.<br />
|param2 = arg<br />
|param2text = Argument specifying a value to replace a format placeholder (such as <code>%d</code>) in the format string. Not required if there are no format specifiers.<br />
<br />
|example1 = print(sprintf("%i", 54)); # prints "54"<br />
|example2 = print(sprintf("Pi = %+.10f", math.pi)); # prints "Pi = +3.1415926536"<br />
|example3 = <br />
print(sprintf("%6d", 23)); # prints " 23"<br />
print(sprintf("%06d", 23)); # prints "000023"<br />
|example4 =<br />
var FGVer = getprop("/sim/version/flightgear");<br />
print(sprintf("You have FlightGear v%s", FGVer)); # prints "You have FlightGear v<your version>"<br />
|example5 = <br />
print(sprintf("Hexadecimal 100000 = %X", 100000)); # prints "Hexadecimal 100000 = 186A0"<br />
print(sprintf("Hexadecimal 100000 = %x", 100000)); # prints "Hexadecimal 100000 = 186a0"<br />
|example6 = print(sprintf("Code 65 is %c", 65)); # prints "Code 65 is A"<br />
|example7 = <br />
print(sprintf("%e", 54)); # prints "5.400000e+001"<br />
print(sprintf("%E", 54)); # prints "5.400000E+001"<br />
|example8 = print(sprintf("%o", 54)); # prints "66"<br />
|example9 = print(sprintf("50%% of 100 is %i", 100 / 2)); # prints "50% of 100 is 50"<br />
|example10 =<br />
print(sprintf("%.2f", 1.4)); #prints "1.40"<br />
print(sprintf("%.1f", 1.4)); #prints "1.4"<br />
print(sprintf("% 4.1f", 1.4)); #prints " 1.4"<br />
print(sprintf("%04.1f", 1.4)); #prints "01.4"<br />
print(sprintf("% 6.1f", 1.4)); #prints " 1.4"<br />
print(sprintf("%06.1f", 1.4)); #prints "0001.4"<br />
}}<br />
<br />
===streq()===<br />
{{Nasal doc<br />
|syntax = streq(a, b);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=129|t=Source}}<br />
|text = Tests the string values of the two arguments for equality. This function is needed because the <code>'''=='''</code> 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.}}<br />
|param1 = a<br />
|param1text = First argument for testing equality.<br />
|param2 = b<br />
|param2text = Second argument for testing equality.<br />
|example1 = print(streq("0", "0")); # prints "1" (True)<br />
|example2 = <br />
print(0 == 0.0); # prints "1" (True)<br />
print(streq("0", "0.0")); # prints "0" (False)<br />
}}<br />
<br />
===substr()===<br />
{{Nasal doc<br />
|syntax = substr(string, start [, length]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=129|t=Source}}<br />
|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).<br />
|param1 = string<br />
|param1text = String to return a substring from.<br />
|param2 = start<br />
|param2text = Integer specifying the start of a substring. Negative values specify a position from the end of the string.<br />
|param3 = length<br />
|param3text = Optional argument specifying the length of the substring. Defaults to the end of the string.<br />
|example1 = print(substr("abcde", 1, 3)); # prints "bcd"<br />
|example2 = print(substr("abcde", 1)); # prints "bcde"<br />
|example3 = print(substr("abcde", 2, 1)); # prints "c"<br />
|example4 = print(substr("abcde", -2)); # prints "de"<br />
|example5 = print(substr("abcde", -3, 2)); # prints "cd"<br />
}}<br />
<br />
===subvec()===<br />
{{Nasal doc<br />
|syntax = subvec(vector, start[, length]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=63|t=Source}}<br />
|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). <br />
|param1 = vector<br />
|param1text = The vector to take the sub-vector from.<br />
|param2 = start<br />
|param2text = The starting point of the sub-vector within the given vector.<br />
|param3 = length<br />
|param3text = Optional argument specifying the length of the sub-vector, from the starting point.<br />
'''Notes:'''<br />
* Omitting the ''vector'' and ''start'' arguments is not an error (possibly it should be) but the return value is ''nil''.<br />
* A negative ''start'' argument ''is'' an error. This seems wrong. Perhaps the language designer could comment.<br />
* A value of ''start'' greater than ''size(vector)'' causes an error. A value equal to ''size(vector)'' returns an empty vector.<br />
* 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.<br />
|example1 = <br />
var vector = [1, 2, 3];<br />
debug.dump(subvec(vector, 0)); # prints "[1, 2, 3]"<br />
|example2 = <br />
var vector = [1, 2, 3];<br />
debug.dump(subvec(vector, 1)); # prints "[2, 3]"<br />
|example3 = <br />
var vector = [1, 2, 3];<br />
debug.dump(subvec(vector, 1, 1)); # prints "[2]"<br />
}}<br />
<br />
=== typeof()===<br />
{{Nasal doc<br />
|syntax = typeof(object);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=193|t=Source}}<br />
|text = Returns a string indicating the whether the object is <code>'''nil'''</code>, a scalar (number or string), a vector, a hash, a function, or a ghost.<br />
|param1 = object<br />
|param1text = Object to return the type of.<br />
|example1 = <br />
var object = nil;<br />
print(typeof(object)); # prints "nil"<br />
|example2 = <br />
var object = "Hello world!";<br />
print(typeof(object)); # prints "scalar"<br />
|example3 = <br />
var object = math.pi;<br />
print(typeof(object)); # prints "scalar"<br />
|example4 = <br />
var object = [1, 2, 3];<br />
print(typeof(object)); # prints "vector"<br />
|example5 = <br />
var object = {};<br />
print(typeof(object)); # prints "hash"<br />
|example6 = <br />
var object = func {};<br />
print(typeof(object)); # prints "func"<br />
|example7 =<br />
var object = airportinfo();<br />
print(typeof(object)); # prints "ghost"<br />
}}<br />
<br />
<!-- == Extension modules ==<br />
=== thread ===<br />
{{WIP}}<br />
<br />
{{Nasal doc<br />
|syntax = thread.newthread(func);<br />
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}<br />
|text = <br />
|example1text = start a new worker thread<br />
|example1 = thread.newthread( func() {} );<br />
}}<br />
<br />
{{Nasal doc<br />
|syntax = thread.newlock();<br />
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}<br />
|text = <br />
|example1text = create a new lock<br />
|example1 = var lock = thread.newlock()<br />
}}<br />
<br />
{{Nasal doc<br />
|syntax = thread.lock();<br />
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}<br />
|text = <br />
|example1text = lock a lock<br />
|example1 = var lock = thread.newlock()<br />
}}<br />
<br />
{{Nasal doc<br />
|syntax = thread.unlock();<br />
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}<br />
|text = <br />
|example1text = unlock a lock<br />
|example1 = var lock = thread.unlock()<br />
}}<br />
<br />
<br />
{{Nasal doc<br />
|syntax = thread.newsem();<br />
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}<br />
|text = <br />
|example1text = create a new {{Wikipedia|semaphore}}<br />
|example1 = var semaphore = thread.newsem()<br />
}}<br />
<br />
<br />
{{Nasal doc<br />
|syntax = thread.semdown();<br />
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}<br />
|text = <br />
|example1text = semaphore down<br />
|example1 = thread.semdown(semaphore)<br />
}}<br />
<br />
<br />
{{Nasal doc<br />
|syntax = thread.semup();<br />
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}<br />
|text = <br />
|example1text = semaphore up<br />
|example1 = thread.semup(semaphore)<br />
}} --><br />
<br />
==Extension functions==<br />
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:<br />
*{{flightgear file|src/Scripting/NasalPositioned.cxx}}<br />
*{{flightgear file|src/Scripting/NasalSys.cxx}}<br />
*{{fgdata file|Nasal/globals.nas}}<br />
<br />
===abort()===<br />
{{Nasal doc<br />
|syntax = abort();<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=565|t=Source}}<br />
|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 "exit" [[Fgcommands|fgcommand]], which will exit FlightGear more gracefully (see example below).<br />
|example1text = This example will immediately stop FlightGear with an error, such as "FlightGear has stopped working."<br />
|example1 = abort();<br />
|example2text = For exiting FlightGear in a better way, please use the following code:<br />
|example2 = fgcommand("exit");<br />
}}<br />
<br />
=== abs() ===<br />
{{Nasal doc<br />
|syntax = abs(number);<br />
|source = {{fgdata file|Nasal/globals.nas|t=Source}}<br />
|text = This simple function returns the {{wikipedia|absolute value|noicon=1}} of the provided number.<br />
|param1 = number<br />
|param1text = This argument is required and should be a number.<br />
|example1 = print(abs(1)); # prints "1"<br />
|example2 = print(abs(-1)); # prints "1"<br />
}}<br />
<br />
===aircraftToCart() ===<br />
This new function in FG 2017.2.1 takes coordinates in aircraft structural coordinate system, and translate them into geocentric coordinates.<br />
Example for (5,6,7):<br />
<syntaxhighlight lang="nasal"><br />
var pos = aircraftToCart({x: -5, y: 6, z: -7});<br />
var coord = geo.Coord.new();<br />
coord.set_xyz(pos.x, pos.y, pos.z);<br />
</syntaxhighlight><br />
Notice: x and z is inverted sign on purpose.<br />
if you want lat. lon, alt from that, just call: (degrees and meters)<br />
<br />
<syntaxhighlight lang="nasal"><br />
coord.lat()<br />
coord.lon()<br />
coord.alt()<br />
</syntaxhighlight><br />
<br />
===addcommand() ===<br />
{{Nasal doc<br />
|syntax = addcommand(name, code);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=659|t=Source}}<br />
|version = 2.12<br />
|commit = {{flightgear commit|7b663c|t=commit}}<br />
|text = {{see also|Howto:Add new fgcommands to FlightGear}}<br />
<br />
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.<br />
|param1 = name<br />
|param1text = This will become the name of the new fgcommand. Must be a string.<br />
|param2 = code<br />
|param2text = The code that will be executed when the fgcommand is run. Must be a function.<br />
|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.<br />
|example1 = addcommand("myFGCmd", func(node) {<br />
print("fgcommand 'myFGCmd' has been run.");<br />
props.dump( node );<br />
});<br />
fgcommand("myFGCmd", props.Node.new({foo:1, bar:2}) );<br />
|example2text = This example demonstrates how parameters are defined in a new fgcommand.<br />
|example2 = addcommand("myFGCmd", func(node){<br />
print(node.getNode("number").getValue()); # prints the value of "number," which is 12<br />
});<br />
fgcommand("myFGCmd", props.Node.new({"number": 12}));<br />
}}<br />
<br />
===airportinfo()===<br />
{{Nasal doc<br />
|syntax = airportinfo();<br />
airportinfo(type);<br />
airportinfo(id);<br />
airportinfo(lat, lon[, type]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1024|t=Source}}<br />
|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:<br />
* '''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.<br />
* '''lon''': Longitude of the location.<br />
* '''lat''': Latitude of the location.<br />
* '''has_metar''': True or false depending whether the airport has a [[METAR]] code defined for it.<br />
* '''elevation''': Elevation of the location in metres.<br />
* '''id''': ICAO code of the airport (or ID of the seaplane base/heliport).<br />
* '''name''': Name of the airport/heliport/seaplane base.<br />
* '''runways'''<br />
** '''<runway name>'''<br />
*** '''id''': Name of runway.<br />
*** '''lat''': Latitude of the runway.<br />
*** '''lon''': Longitude of the runway.<br />
*** '''heading''': Heading of the runway.<br />
*** '''length''': Length of the runway in metres.<br />
*** '''width''': Width of the runway in metres.<br />
*** '''surface''': Runway surface type.<br />
*** '''threshold''': Length of the runway's {{wikipedia|displaced threshold}} in metres. Will return 0 if there is none.<br />
*** '''stopway''': Length of the runway's stopway (the area before the threshold) in metres. Will return 0 if there is none.<br />
*** '''reciprocal''': <code>runway</code> ghost of the reciprocal runway.<br />
*** '''ils_frequency_mhz''': ILS frequency in megahertz.<br />
*** '''ils''': <code>navaid</code> ghost of the ILS transmitter.<br />
* '''helipads'''<br />
** '''<helipad name>'''<br />
*** '''id''': Name of helipad.<br />
*** '''lat''': Latitude of the helipad.<br />
*** '''lon''': Longitude of the helipad.<br />
*** '''heading''': Heading of the helipad.<br />
*** '''length''': Length of the helipad in metres.<br />
*** '''width''': Width of the helipad in metres.<br />
*** '''surface''': Helipad surface type.<br />
* '''taxiways'''<br />
** '''<taxiway name>'''<br />
*** '''id''': Name of taxiway.<br />
*** '''lat''': Latitude of the taxiway.<br />
*** '''lon''': Longitude of the taxiway.<br />
*** '''heading''': Heading of the taxiway.<br />
*** '''length''': Length of the taxiway in metres.<br />
*** '''width''': Width of the taxiway in metres.<br />
*** '''surface''': Taxiway surface type.<br />
<br />
Information is extracted in the same way as accessing members of a Nasal hash. For example:<br />
<syntaxhighlight lang="nasal"><br />
# prints to lengths of the runways of the nearest airport in feet and metres<br />
var info = airportinfo();<br />
print("-- Lengths of the runways at ", info.name, " (", info.id, ") --");<br />
foreach(var rwy; keys(info.runways)){<br />
print(rwy, ": ", math.round(info.runways[rwy].length * M2FT), " ft (", info.runways[rwy].length, " m)");<br />
}<br />
</syntaxhighlight><br />
<br />
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.<br />
|param1 = id<br />
|param1text = The {{wikipedia|International Civil Aviation Organization airport code|ICAO code|noicon=1}} of an airport to retrieve information about.<br />
|param2 = type<br />
|param2text = When this argument is used, the function will return the closest airport of a certain type. Can be one of "heliport," "seaport," or "airport" (default).<br />
: {{inote|Running this function without any parameters is equivalent to this:<br />
: <syntaxhighlight lang="nasal"><br />
airportinfo("airport");<br />
</syntaxhighlight><br />
}}<br />
|param3 = lat ''and'' lon<br />
|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.<br />
|example1 = var info = airportinfo();<br />
print("Nearest airport: ", info.name, " (", info.id, ")"); # prints the name and ICAO code of the nearest airport<br />
|example2 = var info = airportinfo("heliport");<br />
print("Elevation of the nearest heliport: ", math.round(info.elevation * M2FT), " ft"); # prints the elevation and name of the nearest heliport<br />
|example3 = var info = airportinfo("KSQL");<br />
print("-- Runways of ", info.name, " (", info.id, "): --");<br />
foreach(var rwy; keys(info.runways)) {<br />
print(rwy); # prints the runways of KSQL<br />
}<br />
|example4 = var info = airportinfo(37.81909385, -122.4722484);<br />
print("Coordinates of the nearest airport: ", info.lat, ", ", info.lon); # print the name and ICAO of the nearest airport to the Golden Gate Bridge<br />
|example5 = var info = airportinfo(37.81909385, -122.4722484, "seaport");<br />
print("Nearest seaplane base: ", info.name, " (", info.id, ")"); # print the name and ID of the nearest seaplane base to the Golden Gate Bridge<br />
|example6text = This example prints the all information from an <code>airportinfo()</code> call.<br />
|example6 = var info = airportinfo("KSFO");<br />
print(info.name);<br />
print(info.id);<br />
print(info.lat);<br />
print(info.lon);<br />
print(info.has_metar);<br />
print(info.elevation);<br />
foreach(var rwy; keys(info.runways)){<br />
print("-- ", rwy, " --");<br />
print(info.runways[rwy].lat);<br />
print(info.runways[rwy].lon);<br />
print(info.runways[rwy].length);<br />
print(info.runways[rwy].width);<br />
print(info.runways[rwy].heading);<br />
print(info.runways[rwy].stopway);<br />
print(info.runways[rwy].threshold);<br />
<nowiki>}</nowiki><br />
}}<br />
<br />
===airwaysRoute() ===<br />
{{Nasal doc<br />
|syntax = airwaysRoute(start, end[, type]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1933|t=Source}}<br />
|text = {{see also|Nasal Flightplan}}<br />
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.<br />
|param1 = start<br />
|param1text = Start waypoint, in the form of a waypoint ghost, such as that provided by {{func link|flightplan()}}.<br />
|param2 = end<br />
|param2text = Same as above.<br />
|param3 = type<br />
|param3text = Instructs the function to compute a high level route (when set to "highlevel"), or a low level route (when set to "lowlevel"). Defaults to "highlevel."<br />
|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.<br />
|example1 = var fp = flightplan();<br />
var start = fp.getWP(0);<br />
var end = fp.getWP(fp.getPlanSize() - 1);<br />
var rt = airwaysRoute(start, end);<br />
foreach(var wp; rt){<br />
print(wp.wp_name); # print the waypoints in the computed route<br />
}<br />
|example2text = Exactly the same as above, but computes a low level path.<br />
|example2 = var fp = flightplan();<br />
var start = fp.getWP(0);<br />
var end = fp.getWP(fp.getPlanSize() - 1);<br />
var rt = airwaysRoute(start, end, "lowlevel");<br />
foreach(var wp; rt){<br />
print(wp.wp_name); # print the waypoints in the computed route<br />
<nowiki>}</nowiki><br />
}}<br />
<br />
===airway()===<br />
<br />
{{Nasal doc<br />
|syntax = airway(ident [, pos]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2644|t=Source}}<br />
|text = {{see also|Nasal Flightplan}}<br />
This function returns a ghost containing an airway of a specified id.<br />
|param1 = ident<br />
|param1text = a Positioned ghost (leg, navaid, airport) and so on, passed to the search function.<br />
|param2 = pos<br />
}}<br />
<br />
===assert()===<br />
{{Nasal doc<br />
|syntax = assert(condition[, message]);<br />
|source = {{fgdata file|Nasal/globals.nas|t=Source}}<br />
|version = 3.2<br />
|commit = {{fgdata commit|8b16a7|t=commit}}<br />
|text = Returns either true if the condition evaluates as true, or aborts with a {{func link|die()}} call, which can be customised.<br />
|param1 = condition<br />
|param1text = Condition to evaluate.<br />
|param2 = message<br />
|param2text = Optional message that will be used in any {{func link|die()}} call. Defaults to "assertion failed!"<br />
|example1 = var a = 1;<br />
var b = 2;<br />
print(assert(a < b)); # prints "1" (true)<br />
|example2 = var a = 1;<br />
var b = 2;<br />
assert(a > b, 'a is not greater than b'); # aborts with a custom error message<br />
}}<br />
<br />
===carttogeod()===<br />
{{Nasal doc<br />
|syntax = carttogeod(x, y, z);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=945|t=Source}}<br />
|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).<ref>{{simgear file|simgear/math/sg_geodesy.hxx|l=43}}</ref><br />
|param1 = x<br />
|param1text = Mandatory x-axis value, in metres.<br />
|param2 = y<br />
|param2text = Mandatory y-axis value, in metres.<br />
|param3 = z<br />
|param3text = Mandatory z-axis value, in metres.<br />
|example1 = var (lat, lon, alt) = carttogeod(6378137, 0, 0); # point is the intersection of the prime meridian and equator.<br />
print("Latitude: ", lat); # prints lat, lon and alt, which are all zero, see above<br />
print("Longitude: ", lon);<br />
print("Altitude: ", alt);<br />
}}<br />
<br />
===cmdarg()===<br />
{{Nasal doc<br />
|private = _cmdarg()<br />
|syntax = cmdarg();<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=513|t=Part 1}} {{!}} {{fgdata file|Nasal/globals.nas|t=Part 2}}<br />
|text = <code>cmdarg()</code> 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. <br />
It is used by Nasal scripts embedded in XML files. It returns a <code>props.Node</code> object (see {{fgdata file|Nasal/props.nas}}), and you can use all of its methods on the returned value. <code>cmdarg()</code> should only be used in four types/places of XML files:<br />
* Bindings: This is needed so that the value of a joystick's axis can be accessed internally.<br />
* 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). <br />
* 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.<br />
* Animation XML files: If the animation XML file is used in an AI/MP model, <code>cmdarg()</code> will return the root of the AI model in the <code>/ai/models/</code> directory. Examples: <code>/ai/models/aircraft[3]/</code>, <code>/ai/models/multiplayer[1]/</code><br />
<br />
You should not use <code>cmdarg()</code> in places other than those stated above. Although it won't cause an error, it will return the value of the last legitimate <code>cmdarg()</code> call. <br />
<br />
Also, you should not delay <code>cmdarg()</code> using {{func link|maketimer()}}, {{func link|settimer()}} or {{func link|setlistener()}}, because it will return an unrelated property.<br />
|example1 = fgcommand("dialog-show", {"dialog-name": "cmdarg-demo"});<br />
|example1text = <br>This example demonstrates the usage of <code>cmdarg()</code> in a binding. Save the below XML snippet as <tt>[[$FG_ROOT]]/gui/dialogs/cmdarg-demo.xml</tt>. 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.<br />
<syntaxhighlight lang="xml"><br />
<?xml version="1.0" encoding="UTF-8"?><br />
<br />
<PropertyList><br />
<br />
<name>cmdarg-demo</name><br />
<layout>vbox</layout><br />
<br />
<text><br />
<label>Click "Close" to activate the demonstration (a message in the console).</label><br />
</text><br />
<br />
<button><br />
<legend>Close</legend><br />
<binding><br />
<command>nasal</command><br />
<script>print("Button binding root: '" ~ cmdarg().getPath() ~ "'");</script><br />
</binding><br />
<binding><br />
<command>dialog-close</command><br />
</binding><br />
</button><br />
<br />
</PropertyList><br />
</syntaxhighlight><br />
|example2text = This example demonstrates the usage of <code>cmdarg()</code> in Nasal code within dialogs. Open <tt>[[$FG_ROOT]]/gui/dialogs/cmdarg-demo.xml</tt> from the previous example, copy & 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 "I've been changed!"<br />
<syntaxhighlight lang="xml"><br />
<?xml version="1.0" encoding="UTF-8"?><br />
<br />
<PropertyList><br />
<br />
<name>cmdarg-demo</name><br />
<layout>vbox</layout><br />
<br />
<text><br />
<label>Click "Click me!" to activate the demonstration (the button's label will change).</label><br />
</text><br />
<br />
<button><br />
<legend>Click me!</legend><br />
<binding><br />
<command>nasal</command><br />
<script>change_label();</script><br />
</binding><br />
</button><br />
<br />
<button><br />
<legend>Close</legend><br />
<binding><br />
<command>dialog-close</command><br />
</binding><br />
</button><br />
<br />
<nasal><br />
<open><![CDATA[<br />
var dlg_root = cmdarg();<br />
var dlg_name = {"dialog-name": "cmdarg-demo"};<br />
var change_label = func {<br />
dlg_root.getNode("button[0]/legend").setValue("I've been changed!");<br />
fgcommand("dialog-close", dlg_name);<br />
fgcommand("dialog-show", dlg_name);<br />
}<br />
]]></open><br />
</nasal><br />
<br />
</PropertyList><br />
</syntaxhighlight><br />
|example3text = For an example of <code>cmdarg()</code> used with Canvas, please see [[Howto:Adding a canvas to a GUI dialog#FGPlot|Howto:Adding a canvas to a GUI dialog]].<br />
}}<br />
<br />
===courseAndDistance()===<br />
{{Nasal doc<br />
|syntax = courseAndDistance(to);<br />
courseAndDistance(from, to);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1668|t=Source}}<br />
|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:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost type<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A geo.Coord object with geodetic coordinates (cartesian coordinates will not be accepted)<br />
|param1 = from<br />
|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.<br />
|param2 = to<br />
|param2text = Like the first parameter, but defines the second point.<br />
|example1text = This example demonstrates the usage of the function with the <code>airport</code> ghost type.<br />
|example1 = var from = airportinfo("KSFO");<br />
var to = airportinfo("KSQL");<br />
var (course, dist) = courseAndDistance(from, to);<br />
print(course); # prints course from KSFO to KSQL<br />
print(dist); # prints distance in nm from KSFO to KSQL<br />
|example2text = This example demonstrates the usage of the function with hashes containing ''lat'' and ''lon''.<br />
|example2 = var from = {lat: 0, lon: 0};<br />
var to = {lat: 1, lon: 1};<br />
var (course, dist) = courseAndDistance(from, to);<br />
print(course);<br />
print(dist);<br />
|example3text = This example demonstrates usage of a geo.Coord object.<br />
|example3 = var from = geo.Coord.new().set_latlon(0, 0);<br />
var to = geo.Coord.new().set_latlon(1, 1);<br />
var (course, dist) = courseAndDistance(from, to);<br />
print(course);<br />
print(dist);<br />
|example4text = This example demonstrates usage of differing parameter types.<br />
|example4 = var from = airportinfo("KSFO");<br />
var to = geo.Coord.new().set_latlon(0, 0);<br />
var (course, dist) = courseAndDistance(from, to);<br />
print(course);<br />
print(dist);<br />
|example5text = The same as above, but the other way round.<br />
|example5 = var to = {lat: 1, lon: 1};<br />
var (course, dist) = courseAndDistance(0, 0, to);<br />
print(course);<br />
print(dist);<br />
|example6text = Usage of just one parameter.<br />
|example6 = var dest = airportinfo("KSQL");<br />
var (course, dist) = courseAndDistance(dest);<br />
print("Turn to heading ", math.round(course), ". You have ", sprintf("%.2f", dist), " nm to go");<br />
}}<br />
<br />
===createFlightplan()===<br />
{{Nasal doc<br />
|syntax = createFlightplan(path);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2331|t=Source}}<br />
|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.<br />
|param1 = path<br />
|param1text = Optional parameter defining the file from which a flightplan will be populated.<br />
|example1 = <br />
var path = getprop("/sim/fg-home") ~ "/Export/test.fgfp";<br />
var flightplan = createFlightplan(path);<br />
debug.dump(flightplan);<br />
}}<br />
<br />
===createDiscontinuity()===<br />
{{Nasal doc<br />
|syntax = createDiscontinuity();<br />
|text = Returns a <code>waypoint</code> ghost object. A route discontinuity is inserted by an {{abbr|FMS|Flight Management System}} when it is unsure how to connect two waypoints.<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2045|t=Source}}<br />
|version = 2016.1<br />
|commit = {{flightgear commit|caead6|t=commit}}<br />
}}<br />
===createViaTo()===<br />
{{Nasal doc<br />
|syntax = createViaTo(airway, waypoint);<br />
|text = Returns a <code>waypoint</code> ghost object. It represents a route "via '''airway''' to '''waypoint'''".<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2009|t=Source}}<br />
|version = 2016.1<br />
|commit = {{flightgear commit|caead6|t=commit}}<br />
|param1 = airway<br />
|param1text = The name of an airway.<br />
|param2 = waypoint<br />
|param2text = Must be in the airway and one of:<br />
* The name of a waypoint.<br />
* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, or <code>fix</code> ghost object.<br />
}}<br />
===createWP()===<br />
{{Nasal doc<br />
|syntax = createWP(pos, name[, flag]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1964|t=Source}}<br />
|text = Creates a new waypoint ghost object.<br />
|param1 = pos<br />
|param1text = Dictates the position of the new waypoint. It can be one of the following:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost type<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A geo.Coord object<br />
:* Two numbers separated by a comma, as if the function is taking three arguments. See example 4 below.<br />
|param2 = name<br />
|param2text = String that will become the name of the new waypoint.<br />
|param3 = flag<br />
|param3text = Optional string that will tell FlightGear what type of waypoint it is. Must be one of "sid," "star," "approach," "missed," or "pseudo."<br />
|example1text = Creates a waypoint directly in front and 1 km away and appends it to the flight plan.<br />
|example1 = var pos = geo.aircraft_position().apply_course_distance(getprop("/orientation/heading-deg"), 1000);<br />
var wp = createWP(pos, "NEWWP");<br />
var fp = flightplan();<br />
fp.appendWP(wp);<br />
|example2 = var pos = geo.aircraft_position().apply_course_distance(getprop("/orientation/heading-deg"), 1000);<br />
var wp = createWP({lat: pos.lat(), lon: pos.lon()}, "NEWWP");<br />
var fp = flightplan();<br />
fp.appendWP(wp);<br />
|example3 = var apt = airportinfo();<br />
var wp = createWP(apt, "NEWWP");<br />
var fp = flightplan();<br />
fp.appendWP(wp);<br />
|example4 = var pos = geo.aircraft_position().apply_course_distance(getprop("/orientation/heading-deg"), 1000);<br />
var wp = createWP(pos.lat(), pos.lon(), "NEWWP");<br />
var fp = flightplan();<br />
fp.appendWP(wp);<br />
|example5text = Creates a new waypoint and adds it to the flight plan. Waypoints of the type "pseudo" are then removed from the flight plan, including the new waypoint. The {{func link|print()}} statements show this.<br />
|example5 = var pos = geo.aircraft_position().apply_course_distance(getprop("/orientation/heading-deg"), 1000);<br />
var wp = createWP(pos, "NEWWP", "pseudo");<br />
var fp = flightplan();<br />
fp.appendWP(wp);<br />
print(fp.getPlanSize());<br />
fp.clearWPType("pseudo");<br />
print(fp.getPlanSize());<br />
}}<br />
<br />
===createWPFrom()===<br />
{{Nasal doc<br />
|syntax = createWPFrom(object[, flag]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1989|t=Source}}<br />
|text = Creates a new waypoint object from another object.<br />
|param1 = object<br />
|param1text = A ghost object. Must be a ghost type that is one of "airport," "navaid," "runway," or "fix."<br />
|param2 = flag<br />
|param2text = Optional string that will tell FlightGear what type of waypoint it is. Must be one of "sid," "star," "approach," "missed," or "pseudo."<br />
|example1text = Creates a new waypoint and appends it to the flight plan.<br />
|example1 = var apt = airportinfo("KSFO");<br />
var wp = createWPFrom(apt);<br />
var fp = flightplan();<br />
fp.appendWP(wp);<br />
|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.<br />
|example2 = var apt = airportinfo("KSFO");<br />
var wp = createWPFrom(apt, "pseudo");<br />
print(wp.wp_name);<br />
var fp = flightplan();<br />
fp.appendWP(wp);<br />
print(fp.getPlanSize());<br />
fp.clearWPType("pseudo");<br />
print(fp.getPlanSize());<br />
}}<br />
<br />
===defined()===<br />
{{Nasal doc<br />
|syntax = defined(symbol);<br />
|source = {{fgdata file|Nasal/globals.nas|t=Source}}<br />
|text = Returns 1 (true) or 0 (false) depending on whether a variable exists.<br />
|param1 = symbol<br />
|param1text = A string that will be what the function searches for.<br />
|example1 = var number = 12;<br />
var check_exist = func {<br />
print("Variable 'number' ", defined("number") == 1 ? "exists" : "does not exist"); # 'number' does exist<br />
print("Variable 'number2' ", defined("number2") == 1 ? "exists" : "does not exist"); # 'number2' does not exist<br />
}<br />
check_exist();<br />
}}<br />
<br />
===directory()===<br />
{{Nasal doc<br />
|syntax = directory(path);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=572|t=Source}}<br />
|text = Returns a vector containing a list of the folders and files in a given file path or <code>'''nil'''</code> if the path doesn't exist. Hidden folders and files are not added to the vector.<br />
{{inote|The first two elements of the vector will be <code>'.'</code> and <code>'..'</code>. These are for navigating back up the file tree, but have no use in Nasal. They can be safely removed from the vector.}}<br />
|param1 = path<br />
|param1text = Absolute file path.<br />
|example1text = Gets the folders and files in [[$FG_ROOT]], and then removes the extra first two elements (see note above). <br />
|example1 = var dir = directory(getprop("/sim/fg-root")); # get directory<br />
dir = subvec(dir, 2); # strips off the first two elements<br />
debug.dump(dir); # dump the vector<br />
}}<br />
<br />
===fgcommand()===<br />
{{Nasal doc<br />
|syntax = fgcommand(cmd[, args]);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=456|t=Part 1}} {{!}} {{fgdata file|Nasal/globals.nas|t=Part 2}}<br />
|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.<br />
|param1 = cmd<br />
|param1text = String that is the name of the command that is to be run.<br />
|param2 = args<br />
|param2text = If the fgcommand takes arguments, they are inputted using this argument. Can either be a <code>props.Node</code> object, or a hash (see examples below).<br />
|example1 = fgcommand("null"); # does nothing<br />
|example2 = var args = props.Node.new({'script': 'print("Running fgcommand");'});<br />
if (fgcommand("nasal", args)) { # prints "Running fgcommand" and then one of these print statements<br />
print("Fgcommand succeeded");<br />
} else {<br />
print("Fgcommand encountered a problem");<br />
}<br />
|example3 = var args = { 'dialog-name': 'about' };<br />
fgcommand("dialog-show", args); # shows the 'about' dialog<br />
}}<br />
<br />
===findAirportsByICAO() ===<br />
{{Nasal doc<br />
|syntax = findAirportsByICAO(search[, type]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1096|t=Source}}<br />
|text = Returns a vector containing <code>airport</code> ghost objects which are (by default) airports whose ICAO code matches the search string. The results are sorted by range from closest to furthest.<br />
|param1 = search<br />
|param1text = Search string for the function. Can either be a partial or a full ICAO code.<br />
:{{icaution|The more matches there are for the given code, the longer the function will take. Passing just one character (e.g., "K"), might make FlightGear hang for a certain amount of time.}}<br />
|param2 = type<br />
|param2text = This will narrow the search to airports of a certain type. By default, only airports are searched for. May be one of "airport," "heliport," or "seaport."<br />
|example1 = var apts = findAirportsByICAO("KSF"); # finds all airports matching "KSF"<br />
foreach(var apt; apts){<br />
print(apt.name, " (", apt.id, ")"); # prints them<br />
}<br />
|example2 = var apts = findAirportsByICAO("SP0", "seaport"); # finds all seaplane bases matching "SP0"<br />
foreach(var apt; apts){<br />
print(apt.name, " (", apt.id, ")"); # prints them<br />
}<br />
|example3 = var apt = findAirportsByICAO("XBET"); # one way to check if an airport does exist"<br />
if (size(apt) == 0) {<br />
print("Airport does not exist"); # this one will be printed<br />
} else {<br />
print("Airport does exist");<br />
<nowiki>}</nowiki><br />
}}<br />
<br />
===findAirportsWithinRange()===<br />
{{Nasal doc<br />
|syntax = findAirportsWithinRange([pos, ]range[, type]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1066|t=Source}}<br />
|text = Returns a vector of <code>airport</code> 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.<br />
|param1 = pos<br />
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost type<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A geo.Coord object<br />
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: <code>findAirportsWithinRange(lat, lon, range, type);</code>.<br />
|param2 = range<br />
|param2text = Mandatory number giving the range in nautical miles within which to search for airports/heliports/seaplane bases.only airports are searched for.<br />
|param3 = type<br />
|param3text = This will narrow the search to airports of a certain type. By default, only airports are searched for. May be one of "airport," "heliport," or "seaport."<br />
|example1text = Searches for airports within 10 nm of [[KSFO]].<br />
|example1 = var pos = airportinfo("KSFO");<br />
var apts = findAirportsWithinRange(pos, 10);<br />
foreach(var apt; apts){<br />
print(apt.name, " (", apt.id, ")");<br />
}<br />
|example2text = Searches for seaplane bases within 15 nm of [[KSFO]].<br />
|example2 = var pos = airportinfo("KSFO");<br />
var apts = findAirportsWithinRange(pos, 15, "seaport");<br />
foreach(var apt; apts){<br />
print(apt.name, " (", apt.id, ")");<br />
}<br />
|example3text = Searches for airports within 10 nm of your current position.<br />
|example3 = var apts = findAirportsWithinRange(10);<br />
foreach(var apt; apts){<br />
print(apt.name, " (", apt.id, ")");<br />
<nowiki>}</nowiki><br />
}}<br />
<br />
===findCommByFrequencyMHz()===<br />
findCommByFrequencyMHz([pos, ]freq[, type]);<br />
[[sourceforge:p/flightgear/flightgear/ci/next/tree/src/Scripting/NasalPositioned.cxx#l1547|source]]<br />
<br />
Returns a <code>comm</code> ghost object for a station matching a given frequency. If there is more than one station with that frequency, the nearest station is returned.<br />
<br />
'''pos'''<br />
<br />
: Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost type<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A geo.Coord object<br />
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: <code>findCommByFrequencyMHz(lat, lon, freq, type);</code>.<br />
; freq<br />
: Frequency, in megahertz, of the station to search for.<br />
; type<br />
: This will narrow the search to station of a certain type. Defaults to "all." For the full list of accepted type arguments, see flightgear/src/Navaids/positioned.cxx (line 160)<br />
'''Example'''<br />
var com = findCommByFrequencyMHz(123.6);<br />
print("ID: ", com.id); # prints info about the comm station<br />
print("Name: ", com.name);<br />
print("Latitude: ", com.lat);<br />
print("Longitude: ", com.lon);<br />
print("Type: ", com.type);<br />
print("Frequency: ", sprintf("%.3f", com.frequency), " Mhz");<br />
<br />
=== findFixesByID() ===<br />
{{Nasal doc<br />
|syntax = findFixesByID([pos, ]id);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1627|t=Source}}<br />
|text = Returns a vector containing <code>fix</code> ghost objects matching a given ID, sorted by range from a certain position.<br />
{{inote|Fixes are (usually) also known as waypoints.}}<br />
|param1 = pos<br />
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost type<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A geo.Coord object<br />
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: <code>findFixesByID(lat, lon, id);</code>.<br />
|param2 = id<br />
|param2text = Full or partial ID of the fix to search for.<br />
:{{inote|1=Inputting a partial ID does not work correctly (see [http://forum.flightgear.org/viewtopic.php?f=30&t=28129 here]). It is best to just input a full ID.}}<br />
|example1 = var fixes = findFixesByID("POGIC");<br />
foreach(var fix; fixes){<br />
print(fix.id, " - lat: ", fix.lat, " {{!}} lon: ", fix.lon); # prints information about POGIC<br />
}<br />
|example2 = var fix = findFixesByID("ZUNAP");<br />
fix = fix[0];<br />
var (course, dist) = courseAndDistance(fix);<br />
print("Turn to heading ", math.round(course), ". You have ", sprintf("%.2f", dist), " nm to go to reach ", fixes[0].id);<br />
}}<br />
<br />
===findNavaidByFrequencyMHz()===<br />
{{Nasal doc<br />
|syntax = findNavaidByFrequencyMHz([pos, ]freq[, type]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1547|t=Source}}<br />
|text = Returns a <code>navaid</code> ghost object for a navaid matching a given frequency. If there is more than one navaid with that frequency, the nearest station is returned.<br />
|param1 = pos<br />
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost type<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A geo.Coord object<br />
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: <code>findNavaidByFrequencyMHz(lat, lon, freq, type);</code>.<br />
|param2 = freq<br />
|param2text = Frequency, in megahertz, of the navaid to search for.<br />
|param3 = type<br />
|param3text = This will narrow the search to navaids of a certain type. Defaults to "all." For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.<br />
|example1 = var navaid = findNavaidByFrequencyMHz(109.55);<br />
print("ID: ", navaid.id); # prints info about the navaid<br />
print("Name: ", navaid.name);<br />
print("Latitude: ", navaid.lat);<br />
print("Longitude: ", navaid.lon);<br />
print("Elevation (AMSL): ", navaid.elevation, " m");<br />
print("Type: ", navaid.type);<br />
print("Frequency: ", sprintf("%.3f", navaid.frequency / 100), " Mhz");<br />
print("Range: ", navaid.range_nm, " nm");<br />
if(navaid.course) print("Course: ", navaid.course);<br />
}}<br />
<br />
=== findNavaidsByFrequencyMHz()===<br />
{{Nasal doc<br />
|syntax = findNavaidsByFrequencyMHz([pos, ]freq[, type]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1572|t=Source}}<br />
|text = Returns a vector conatining <code>navaid</code> ghost objects for navaids that match a given frequency, sorted from nearest to furthest.<br />
|param1 = pos<br />
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost type<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A geo.Coord object<br />
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: <code>findNavaidsByFrequencyMHz(lat, lon, freq, type);</code>.<br />
|param2 = freq<br />
|param2text = Frequency, in megahertz, of the navaid to search for.<br />
|param3 = type<br />
|param3text = This will narrow the search to navaids of a certain type. Defaults to "all." For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.<br />
|example1 = var navaids = findNavaidsByFrequencyMHz(109.55);<br />
foreach(var navaid; navaids){<br />
print("--");<br />
print("ID: ", navaid.id); # prints info about the navaid<br />
print("Name: ", navaid.name);<br />
print("Latitude: ", navaid.lat);<br />
print("Longitude: ", navaid.lon);<br />
print("Elevation (AMSL): ", navaid.elevation, " m");<br />
print("Type: ", navaid.type);<br />
print("Frequency: ", sprintf("%.3f", navaid.frequency / 100), " Mhz");<br />
print("Range: ", navaid.range_nm, " nm");<br />
if(navaid.course) print("Course: ", navaid.course);<br />
print("--");<br />
<nowiki>}</nowiki><br />
}}<br />
<br />
===findNavaidsByID()===<br />
{{Nasal doc<br />
|syntax = findNavaidsByID([pos, ]id[, type]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1600|t=Source}}<br />
|text = Returns a vector containing <code>navaid</code> ghost objects matching a given ID, sorted by range from a certain position.<br />
|param1 = pos<br />
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost type<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A geo.Coord object<br />
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: <code>findNavaidsByID(lat, lon, id, type);</code>.<br />
|param2 = id<br />
|param2text = Full or partial ID of the fix to search for.<br />
:{{inote|1=Inputting a partial ID does not work correctly (see [http://forum.flightgear.org/viewtopic.php?f=30&t=28129 here]). It is best to just input a full ID.}}<br />
|param3 = type<br />
|param3text = This will narrow the search to navaids of a certain type. Defaults to "all." For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.<br />
|example1 = var navaid = findNavaidsByID("MXW");<br />
navaid = navaid[0];<br />
print("ID: ", navaid.id); # prints info about 'MXW' (a VOR station)<br />
print("Name: ", navaid.name);<br />
print("Latitude: ", navaid.lat);<br />
print("Longitude: ", navaid.lon);<br />
print("Elevation (AMSL): ", navaid.elevation, " m");<br />
print("Type: ", navaid.type);<br />
print("Frequency: ", sprintf("%.3f", navaid.frequency / 1000), " Mhz");<br />
print("Range: ", navaid.range_nm, " nm");<br />
}}<br />
<br />
===findNavaidsWithinRange()===<br />
{{Nasal doc<br />
|syntax = findNavaidsWithinRange([pos, ]range[, type]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1518|t=Source}}<br />
|text = Returns a vector of <code>navaid</code> 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.<br />
|param1 = pos<br />
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost type<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A geo.Coord object<br />
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: <code>findNavaidsWithinRange(lat, lon, range, type);</code>.<br />
|param2 = range<br />
|param2text = Mandatory number giving the range in nautical miles within which to search for navaids. Defaults to "all." For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.<br />
|param3 = type<br />
|param3text = This will narrow the search to navaids of a certain type.<br />
|example1text = Searches for navaids within 10 nm of [[KSFO]].<br />
|example1 = var pos = airportinfo("KSFO");<br />
var navs = findNavaidsWithinRange(pos, 10);<br />
foreach(var nav; navs){<br />
print(nav.name, " (ID: ", nav.id, ")");<br />
}<br />
|example2text = Searches for navaids within 10 nm of your current position.<br />
|example2 = var navs = findNavaidsWithinRange(10);<br />
foreach(var nav; navs){<br />
print(nav.name, " (ID: ", nav.id, ")");<br />
<nowiki>}</nowiki><br />
}}<br />
<br />
===finddata()===<br />
{{Nasal doc<br />
|syntax = finddata(path);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=603|t=Source}}<br />
|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]]. <br />
|param1 = path<br />
|param1text = A relative path as a string.<br />
|example1 = var path = finddata("Aircraft/Generic");<br />
print(path); # prints the absolute path to $FG_ROOT/Aircraft/Generic<br />
|example2 = var path = finddata("Airports");<br />
print(path); # prints the absolute path to <TerraSync dir>/Airports<br />
|example3 = var path = finddata("preferences.xml");<br />
print(path); # prints the absolute path to $FG_ROOT/preferences.xml<br />
}}<br />
<br />
===flightplan()===<br />
{{Nasal doc<br />
|syntax = flightplan([path]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1738|t=Source}}<br />
|text = {{see also|Nasal Flightplan}}<br />
Returns a flight plan object, either one for the current flight plan, or one loaded from a given path.<br />
|param1 = path<br />
|param1text = Optional path to flight plan XML file.<br />
|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.<br />
|example1 = var fp = flightplan();<br />
print(fp.getWP(fp.current).id);<br />
|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 <tt>''[[$FG_HOME]]/fp-demo.xml''</tt>.<br />
|example2 = var path = getprop('/sim/fg-home') ~ '/fp-demo.xml';<br />
var fp = flightplan(path);<br />
for(var i = 0; i < fp.getPlanSize(); i += 1){<br />
print(fp.getWP(i).id);<br />
<nowiki>}</nowiki><br />
}}<br />
<br />
===geodinfo()===<br />
{{Nasal doc<br />
|syntax = geodinfo(lat, lon[, max_alt]);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=981|t=Source}}<br />
|text = Returns a vector containing two entries or <code>'''nil'''</code> 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 <tt>''[[$FG_ROOT]]/Materials''</tt>), or <code>'''nil'''</code> 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}}):<br />
* '''light_coverage:''' The coverage of a single point of light in m<sup>2</sup>.<br />
* '''bumpiness:''' Normalized bumpiness factor for the material.<br />
* '''load_resistance:''' The amount of pressure in N/m<sup>2</sup> the material can withstand without deformation.<br />
* '''solid:''' 1 (true) or false (0) depending on whether the material is solid or not.<br />
* '''names:''' Vector of scenery types (usually generated by [[TerraGear]]) that will use this material. <br />
* '''friction_factor:''' Normalized friction factor of the material.<br />
* '''rolling_friction:''' The rolling friction coefficient of the material.<br />
<br />
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.<br />
|param1 = lat<br />
|param1text = Latitude, inputted as a number.<br />
|param2 = lon<br />
|param2text = Longitude, inputted as a number.<br />
|param3 = max_alt<br />
|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, <code>'''nil'''</code> will be returned.<br />
|example1text = Dumps information about ground underneath the aircraft.<br />
|example1 = var pos = geo.aircraft_position();<br />
var info = geodinfo(pos.lat(), pos.lon());<br />
debug.dump(info);<br />
|example2text = Prints whether the ground underneath the aircraft is solid or is water.<br />
|example2 = var pos = geo.aircraft_position();<br />
var info = geodinfo(pos.lat(), pos.lon());<br />
if (info != nil and info[1] != nil) {<br />
print("The ground underneath the aircraft is ", info[1].solid == 1 ? "solid." : "water.");<br />
<nowiki>}</nowiki><br />
}}<br />
<br />
=== geodtocart() ===<br />
{{Nasal doc<br />
|syntax = geodtocart(lat, lon, alt);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=962|t=Source}}<br />
|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.<br />
|param1 = lat<br />
|param1text = Latitude, in degrees.<br />
|param2 = lon<br />
|param2text = Longitude, in degrees.<br />
|param3 = alt<br />
|param3text = Altitude, in metres.<br />
|example1 = var (x, y, z) = geodtocart(0, 0, 0); # point is the intersection of the prime meridian and equator.<br />
print("x: ", x); # prints "x: 6378137"<br />
print("y: ", y); # prints "y: 0"<br />
print("z: ", z); # prints "y: 0"<br />
}}<br />
<br />
=== get_cart_ground_intersection()===<br />
Introduced in 2017.2.1, see [[Terrain Detection]].<br />
<br />
===getprop()===<br />
{{Nasal doc<br />
|syntax = <nowiki>getprop(path[, path[, ...]]);</nowiki><br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=345|t=Source}}<br />
|text = Returns the value of a node in the [[Property Tree]] or <code>'''nil'''</code> if the node does not exist or the value is not a number (NaN).<br />
|param1 = path<br />
|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.<br />
|example1 = print("You have FlightGear v", getprop("/sim/version/flightgear")); # prints FlightGear version<br />
|example2text = Note that the example below will only work in FlightGear 3.2 and above.<br />
|example2 = for(var i = 0; i < 8; i += 1){<br />
print("View #", i + 1, " is named ", getprop("/sim/view", i, "name"));<br />
}<br />
|example3text = Same as above, but is supported by all versions of FlightGear.<br />
|example3 = for(var i = 0; i < 8; i += 1){<br />
print("View #", i + 1, " is named ", getprop("/sim/view[" ~ i ~ "]/name"));<br />
<nowiki>}</nowiki><br />
}}<br />
==== See also====<br />
{{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: <br />
To get a Node rather than its value, use <code>props.globals.getNode()</code> - see [[Nasal_library/props]]. }}<br />
<br />
===greatCircleMove()===<br />
{{Nasal doc<br />
|syntax = greatCircleMove([pos, ]course, dist);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1681|t=Source}}<br />
|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).<br />
|param1 = pos<br />
|param1text = Optional position to calculate from. If not given, the aircraft's current position will be used. Can be one of:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost object.<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A <code>geo.Coord</code> object<br />
:* A lat/lon pair, that is, a pair of numbers (latitude followed by longitude) separated by a comma: <code>greatCircleMove(lat,lon, ...)</code>.<br />
|param2 = course<br />
|param2text = Course to new set of coordinates, in degrees (in the range 0–360).<br />
|param3 = dist<br />
|param3text = Distance in nautical miles to the new set of coordinates.<br />
|example1 = var pos = greatCircleMove(0,0, 0, 1);<br />
debug.dump(pos); # print hash with coordinates<br />
|example2 = var fix = findFixesByID("POGIC");<br />
fix = fix[0];<br />
var pos = greatCircleMove(fix, 45, 10);<br />
debug.dump(pos); # print hash with coordinates<br />
}}<br />
<br />
===interpolate()===<br />
{{Nasal doc<br />
|private = _interpolate()<br />
|syntax = <nowiki>interpolate(prop, value1, time1[, value2, time2[, ...]]);</nowiki><br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=522|t=Part 1}} {{!}} {{fgdata file|Nasal/globals.nas|t=Part 2}}<br />
|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.<br />
|param1 = prop<br />
|param1text = String or <code>props.Node</code> object that indicates a node in the property tree to be used.<br />
|param2 = value''n''<br />
|param2text = Target value to change the property to in the set amount of time. This should be a number.<br />
|param3 = time''n''<br />
|param3text = Time in seconds, that will be taken for the interpolation.<br />
|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.<br />
|example1 = setprop("/test", 0); # (re-)set property<br />
interpolate("/test",<br />
50, 5, # interpolate to 50 in 5 seconds<br />
10, 2, # interpolate to 10 in 2 seconds<br />
0, 5); # interpolate to 0 in 5 seconds<br />
|example2 = # Apply the left brake at 20% per second<br />
var prop = "controls/gear/brake-left";<br />
var dist = 1 - getprop(prop);<br />
if (dist == 1) {<br />
interpolate(prop, 1, dist / 0.2);<br />
<nowiki>}</nowiki><br />
}}<br />
<br />
===isa()===<br />
{{Nasal doc<br />
|syntax = isa(object, class);<br />
|source = {{fgdata file|Nasal/globals.nas|t=Source}}<br />
|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.<br />
|param1 = object<br />
|param1text = Object to check.<br />
|param2 = class<br />
|param2text = Class/object to check that '''object''' inherits from or is an instance of.<br />
|example1 = var coord = geo.Coord.new();<br />
if(isa(coord, geo.Coord)){<br />
print("Variable 'coord' is an instance of class 'geo.Coord'"); # this one will be printed<br />
} else {<br />
print("Variable 'coord' is not an instance of class 'geo.Coord'");<br />
}<br />
|example2 = var coord = geo.Coord.new();<br />
if(isa(coord, props.Node)){<br />
print("Variable 'coord' is an instance of class 'props.Node'");<br />
} else {<br />
print("Variable 'coord' is not an instance of class 'props.Node'"); # this one will be printed<br />
}<br />
|example3text = The example below demonstrates checking of inheritance.<br />
|example3 = var Const = {<br />
constant: 2,<br />
getConst: func {<br />
return me.constant;<br />
}<br />
};<br />
<br />
var Add = {<br />
new: func {<br />
return { parents: [Add, Const] };<br />
},<br />
<br />
addToConst: func(a){<br />
return a * me.getConst();<br />
}<br />
};<br />
<br />
var m = Add.new();<br />
print(m.addToConst(4));<br />
<br />
if(isa(m, Add)) print("Variable 'm' is an instance of class 'Add'"); # will be printed<br />
if(isa(m, Const)) print("Variable 'm' is an instance of class 'Const'"); # will also be printed<br />
}}<br />
<br />
===logprint()===<br />
{{Nasal doc<br />
|syntax = <nowiki>logprint(priority[, msg[, msg[, ...]]]);</nowiki><br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=431|t=Source}}<br />
|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 <code>[[Commonly used debugging tools#fgfs.log|fgfs.log]]</code> file as coming from the Nasal file itself rather than from {{flightgear file|src/Scripting/NasalSys.cxx}}.<br />
|param1 = priority<br />
|param1text = Number specifying the priority level of the outputted message:<br />
:{{{!}} class="wikitable"<br />
! Number !! Debug type<br />
{{!-}}<br />
{{!}} 1 {{!!}} Bulk<br />
{{!-}}<br />
{{!}} 2 {{!!}} Debug<br />
{{!-}}<br />
{{!}} 3 {{!!}} Info<br />
{{!-}}<br />
{{!}} 4 {{!!}} Warn<br />
{{!-}}<br />
{{!}} 5 {{!!}} Alert<br />
{{!}}}<br />
|param2 = msg<br />
|param2text = The message. There is no limit to the arguments you give give. They will be concatenated together before logging.<br />
|example1 = # logs the value of pi to three decimal places with log level 3<br />
logprint(3, "pi = ", sprintf("%.3f", math.pi));<br />
|example2 = logprint(5, "Alert! This is an important message!");<br />
}}<br />
{{note| <br />
The following constants have been added to the development branch of FlightGear ("next") and will be releases with FG 2020.1 so you won't have to remember the numbers anymore:<br />
<br />
LOG_BULK, LOG_WARN, LOG_DEBUG, LOG_INFO, LOG_ALERT, DEV_WARN, DEV_ALERT }}<br />
<br />
===magvar() ===<br />
{{Nasal doc<br />
|syntax = magvar([pos]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1642|t=Source}}<br />
|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.<br />
{{{!}} class="wikitable"<br />
! FlightGear versions !! Model !! Reference date<br />
{{!-}}<br />
{{!}} 3.6 and above {{!!}} {{wikipedia|World Magnetic Model}} (WMM) 2015 {{!!}} 1 January 2015<br />
{{!-}}<br />
{{!}} 0.9.11-pre1 to 3.4 {{!!}} WMM 2005 {{!!}} 1 January 2005<br />
{{!-}}<br />
{{!}} 0.7.3 to 0.9.10 {{!!}} WMM 2000 {{!!}} 1 January 2000<br />
{{!}}}<br />
|param1 = pos<br />
|param1text = Optional position to calculate from. If not given, the aircraft's current position will be used. Can be one of:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost object.<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A <code>geo.Coord</code> object<br />
:* A lat/lon pair, that is, a pair of numbers (latitude followed by longitude) separated by a comma: <code>magvar(lat,lon)</code>.<br />
|example1 = print(magvar(0, 0)); # prints the magnetic variation at 0, 0<br />
}}<br />
<br />
===maketimer() ===<br />
{{Nasal doc<br />
|syntax = maketimer(interval[, self], function);<br />
|source = ''Implemented using the {{API Link|flightgear|class|TimerObj}} class.''<br>{{flightgear file|src/Scripting/NasalSys.cxx|l=90|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=533|t=Part 2}}<br />
|version = 2.12<br />
|commit = {{flightgear commit|ab939f|t=commit}}<br />
|text = Returns a timer object containing the following methods and members:<br />
* '''start()''': Starts the timer.<br />
* '''stop()''': Stops the timer.<br />
* '''restart(interval)''': Restarts the timer with the given interval.<br />
* '''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).<br />
* '''isRunning''': Read-only bool telling whether the timer is currently running.<br />
* '''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.<br />
Unlike {{func link|settimer()}}, which it replaces, <code>maketimer()</code> provides more control over the timer. In addition, it can help reduce memory usage.<br />
|param1 = interval<br />
|param1text = Interval in seconds for the timer.<br />
|param2 = self<br />
|param2text = Optional parameter specifying what any <code>'''me'''</code> references in the function being called will refer to.<br />
|param3 = function<br />
|param3text = Function to be called at the given interval.<br />
|example1 = var timer = maketimer(1, func(){<br />
print("Hello, World!"); # print "Hello, World!" once every second (call timer.stop() to stop it)<br />
});<br />
timer.start();<br />
|example2 = var timer = maketimer(1, math, func(){<br />
print(me.math); # 'me' reference is the 'math' namespace<br />
});<br />
timer.singleShot = 1; # timer will only be run once<br />
timer.start();<br />
|example3 = var timer = maketimer(1, func(){<br />
print("Hello, World!"); # print "Hello, World!" once every second (call timer.stop() to stop it)<br />
});<br />
timer.start();<br />
print(timer.isRunning); # prints 1<br />
|example4text = In the example below, "Hello, World!" will be printed after one second the first time, and after two seconds thereafter.<br />
|example4 = var update = func(){<br />
print("Hello, World!");<br />
timer.restart(2); # restarts the timer with a two second interval<br />
}<br />
<br />
var timer = maketimer(1, update);<br />
timer.singleShot = 1;<br />
timer.start();<br />
}}<br />
<br />
===maketimestamp()===<br />
{{Nasal doc<br />
|syntax = maketimestamp()<br />
|source = ''Implemented using the {{API Link|flightgear|class|TimeStampObj}} class.''<br>{{flightgear file|src/Scripting/NasalSys.cxx|l=214|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=589|t=Part 2}}<br />
|version = 2019.2<br />
|commit = {{flightgear commit|7db06300|t=commit}}<br />
|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:<br />
* '''stamp()''': Resets the timing operation. Call this first.<br />
* '''elapsedMSec()''': returns number of milliseconds elapsed since stamp() called. Resolution may vary depending on platform but is usually at least millisecond accuracy.<br />
* '''elapsedUSec()''': returns number of microseconds elapsed since stamp() called. Resolution may vary depending on platform but is usually at least millisecond accuracy.<br />
|<br />
|example1text = In the example below the number of milliseconds elapsed will be printed.<br />
|example1 = var timestamp = maketimestamp();<br />
timestamp.stamp();<br />
print(timestamp.elapsedMSec(), "ms elapsed");<br />
print(timestamp.elapsedMSec(), "ms elapsed");<br />
}}<br />
<br />
===md5()===<br />
{{Nasal doc<br />
|syntax = md5(string);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=758|t=Source}}<br />
|version = 3.2<br />
|commit = {{flightgear commit|cfbf9e|t=commit}}<br />
|text = Returns a the {{wikipedia|MD5}} hash (as a string) of the inputted string.<br />
|param1 = string<br />
|param1text = String the generate the hash of. Mandatory.<br />
|example1text = The below code should output <code>65a8e27d8879283831b664bd8b7f0ad4</code>.<br />
|example1 = print(md5("Hello, World!"));<br />
}}<br />
<br />
===navinfo()===<br />
{{Nasal doc<br />
|syntax = navinfo(lat, lon, type, id);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1453|t=Source}}<br />
|text = Returns vector <code>navaid</code> ghost objects matching the given '''type''' and '''id''' or <code>'''nil'''</code> on error.<br />
|param1 = lat ''and'' lon<br />
|param1text = If given, the returned navaids will be put into order of ascending distance from the location.<br />
|param2 = type<br />
|param2text = Narrows the search to the given type. Must be one of "any," "fix," "vor," "ndb," "ils," "dme," or "tacan." Defaults to the equivalent of "any."<br />
|param3 = id<br />
|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.<br />
|example1 = navinfo("vor"); # returns all VORs<br />
|example2 = navinfo("HAM"); # return all navaids whose names start with "HAM"<br />
|example3 = navinfo("vor", "HAM"); # return all VORs whose names start with "HAM"<br />
|example4 = navinfo(34,48,"vor","HAM"); # return all VORs whose names start with "HAM" and sorted by distance relative to 34°, 48°<br />
}}<br />
<br />
===parse_markdown()===<br />
{{Nasal doc<br />
|syntax = parse_markdown(markdown);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=745|t=Part 1}} {{!}} {{simgear file|simgear/misc/SimpleMarkdown.cxx|t=Part 2}} <br />
|version = 3.2<br />
|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:<br />
* It strips whitespace from the beginning of the string.<br />
* It supports [http://daringfireball.net/projects/markdown/syntax#p paragraphs and line breaks].<br />
* It collapses whitespace.<br />
* It converts unordered [http://daringfireball.net/projects/markdown/syntax#list lists] to use a bullet character (&bull;). Note that the bullet character is implemented in hexadecimal UTF-8 character bytes (<code>E2 80 A2</code>), as so may not work properly when the being displayed in an encoding other than UTF-8.<br />
|param1 = markdown<br />
|param1text = String containing Markdown to be parsed.<br />
|example1text = <br />
Save the below code as <tt>''[[$FG_ROOT]]/gui/dialogs/test.xml''</tt>, 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 (<tt>Debug > Reload GUI</tt>).<br />
<syntaxhighlight lang="xml" highlight="41-50"><br />
<?xml version="1.0" encoding="UTF-8"?><br />
<br />
<PropertyList><br />
<br />
<name>test</name><br />
<layout>vbox</layout><br />
<br />
<group><br />
<layout>hbox</layout><br />
<br />
<empty><br />
<stretch>true</stretch><br />
</empty><br />
<br />
<text><br />
<label>parse_markdown() test dialog</label><br />
</text><br />
<br />
<empty><br />
<stretch>true</stretch><br />
</empty><br />
<br />
<button><br />
<legend></legend><br />
<pref-width>16</pref-width><br />
<pref-height>16</pref-height><br />
<binding><br />
<command>dialog-close</command><br />
</binding><br />
</button><br />
<br />
</group><br />
<br />
<canvas><br />
<name>Canvas plot</name><br />
<stretch>true</stretch><br />
<pref-width>400</pref-width><br />
<pref-height>300</pref-height><br />
<nasal><br />
<load><![CDATA[<br />
var text = 'Items:<br />
* apples<br />
* oranges<br />
* pears<br />
<br />
Some text.<br />
Some more items:<br />
* apples<br />
* oranges<br />
* pears';<br />
<br />
var parsed = parse_markdown(text);<br />
<br />
var root_canvas = canvas.get(cmdarg());<br />
root_canvas.setColorBackground(255, 255, 255);<br />
var root = root_canvas.createGroup();<br />
<br />
var text_dis = root.createChild("text")<br />
.setText(parsed)<br />
.setTranslation(5, 5)<br />
.setFont("LiberationFonts\LiberationSans-Regular.ttf")<br />
.setFontSize(15)<br />
.setColor(0, 0, 0)<br />
.setDrawMode(canvas.Text.TEXT)<br />
.setAlignment("left-top");<br />
]]></load><br />
</nasal><br />
</canvas><br />
<br />
</PropertyList><br />
</syntaxhighlight><br />
|example1 = fgcommand("dialog-show", {"dialog-name": "test"});<br />
|example2text = The example below parses Markdown and outputs it in a HTML document. The parsed text is placed in <syntaxhighlight lang="xml" inline><pre></pre></syntaxhighlight> tags. To change the Markdown to be parsed, simply edit the variable <tt>markdown</tt> at the top of the code.<br />
|example2 = <nowiki>var markdown = 'Items:<br />
* apples<br />
* oranges<br />
* pears<br />
<br />
Some text.<br />
Some more items:<br />
* apples<br />
* oranges<br />
* pears';<br />
<br />
var parsed = parse_markdown(markdown);<br />
<br />
debug.dump(parsed);<br />
<br />
var path = string.normpath(getprop("/sim/fg-home") ~ '/Export/parse_markdown()-test.html');<br />
<br />
var file = io.open(path, "w");<br />
<br />
var html = "<!DOCTYPE html>\n\n<html>\n\n<head>\n\t<meta charset=\"UTF-8\">\n\t<title>parse_markdown() test generated by Nasal</title>\n</head>\n\n<body>\n\t<pre>" ~ parsed ~ "</pre>\n</body>\n\n</html>";<br />
<br />
io.write(file, html);<br />
io.close(file);<br />
print("Done, file ready for viewing (" ~ path ~ ")");</nowiki><br />
}}<br />
<br />
===parsexml()===<br />
{{Nasal doc<br />
|syntax = <nowiki>parsexml(path[, start[, end[, data[, pro_ins]]]]);</nowiki><br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=710|t=Source}}<br />
|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 <code>'''nil'''</code> is returned on error.<br />
|param1 = path<br />
|param1text = Mandatory absolute path to the XML file to be parsed.<br />
|param2 = start<br />
|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.<br />
|param3 = end<br />
|param3text = Optional callback function that will be called for every ending tag. The function should take one argument: the tag name.<br />
|param4 = data<br />
|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.<br />
|param5 = pro_ins<br />
|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.<br />
|example1text = Save the below XML code in <tt>''[[$FG_HOME]]/Export/demo.xml''</tt>. Then, execute the Nasal code below in the Nasal Console. The XML will be parsed and each bit of the code will be printed.<br />
<syntaxhighlight lang="xml"><br />
<?xml version="1.0" encoding="UTF-8"?><br />
<br />
<?xml-stylesheet type="text/xsl" href="style.xsl"?><br />
<br />
<foo><br />
<blah type="string"><![CDATA[ <sender>John Smith</sender> ]]></blah><br />
<blah2 type="string">Orange &amp; lemons</blah2><br />
</foo><br />
</syntaxhighlight><br />
|example1 = var start = func(name, attr){<br />
print("Starting tag: '", name, "'");<br />
foreach(var a; keys(attr)){<br />
print("\twith attribute ", a, '="', attr[a], '"');<br />
}<br />
}<br />
<br />
var end = func(name){<br />
print("Ending tag: '", name, "'");<br />
}<br />
<br />
var data = func(data){<br />
print("Data = '", data, "'");<br />
}<br />
<br />
var pro_instr = func(target, data){<br />
print("Processing instruction: target = '", target, "', data = '", data, "'");<br />
}<br />
<br />
parsexml(getprop("/sim/fg-home") ~ '/Export/demo.xml', start, end, data, pro_instr);<br />
}}<br />
<br />
===print()===<br />
{{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.<br />
<br />
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.<ref>https://sourceforge.net/p/flightgear/mailman/message/37042224/</ref>}}<br />
<br />
{{Nasal doc<br />
|syntax = <nowiki>print(data[, data[, ...]]);</nowiki><br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=415|t=Source}}<br />
|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.<br />
|param1 = data<br />
|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.<br />
|example1 = print("Just", " a ", "test"); # prints "Just a test"<br />
|example2 = print("pi = ", math.pi); # prints "pi = 3.141592..."<br />
}}<br />
<br />
=== printf() ===<br />
{{Nasal doc<br />
|syntax = <nowiki>printf(format[, arg[, arg, [...]]]);</nowiki><br />
|source = {{fgdata file|Nasal/globals.nas|t=Source}}<br />
|text = Creates and prints a formatted string. For a description of its arguments, see {{func link|sprintf()}} (it is, in fact, implemented using <code>sprintf()</code>).<br />
|example1 = printf("In hexadecimal, 100000 = %X", 186A0); # prints "In hexadecimal, 100000 = 186A0"<br />
}}<br />
<br />
===printlog()===<br />
{{Nasal doc<br />
|syntax = <nowiki>printlog(level, data[, data[, ...]]);</nowiki><br />
|source = {{fgdata file|Nasal/globals.nas|t=Source}}<br />
|text = Prints the given message with the given log level. If the log level is higher or equal to <code>/sim/logging/priority</code>, it is printed.<br />
|param1 = level<br />
|param1text = Mandatory log level as a string. Must be one of "none," "bulk," "debug," "info," "warn," or "alert." Note that "none" will mean that the message will never be printed.<br />
|param2 = data<br />
|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.<br />
|example1 = printlog("alert", "This is an alert"); # message will be printed<br />
|example2 = printlog("info", "Just informing you about something"); # message will be printed only if log level is set to "info" or less<br />
}}<br />
<br />
===rand()===<br />
{{Nasal doc<br />
|syntax = rand();<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=554|t=Source}}<br />
|text = Returns a random floating point number between 0 (inclusive) and 1 (exclusive). It takes no arguments.<br />
|example1 = print(rand()); # prints random number<br />
}}<br />
<br />
===registerFlightPlanDelegate()===<br />
{{Nasal doc<br />
|syntax = registerFlightPlanDelegate(init_func);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1879|t=Source}}<br />
|text = Registers a flight plan delegate. See <tt>''{{fgdata file|Nasal/route_manager.nas|t=$FG_ROOT/Nasal/route_manager.nas}}''</tt> for examples.<br />
|param1 = init_func<br />
|param1text = Initialization function which will be called during FlightGear's startup.<br />
}}<br />
===removecommand()===<br />
{{Nasal doc<br />
|syntax = removecommand(cmd);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=668|t=Source}}<br />
|text = Removes the given fgcommand. Returns <code>'''nil'''</code>.<br />
{{caution|This will remove '''any''' [[fgcommands|fgcommand]], even those implemented in C++, so use with caution!}}<br />
|param1 = cmd<br />
|param1text = String specifying the name of the command to remove.<br />
|example1 = addcommand("hello", func(){<br />
print("Hello");<br />
});<br />
fgcommand("hello"); # "Hello" will be printed<br />
removecommand("hello"); # removes it<br />
}}<br />
<br />
===removelistener()===<br />
{{Nasal doc<br />
|syntax = removelistener(id);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=1384|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=506|t=Part 2}}<br />
|text = Removes and deactivates the given listener and returns the number of listeners left or <code>'''nil'''</code> on error.<br />
{{note|It is good practice to remove listeners when they are not required anymore. This prevents the listeners reducing FlightGear's run performance.}}<br />
|param1 = id<br />
|param1text = ID of listener as returned by {{func link|setlistener()}}.<br />
|example1 = var ls = setlistener("/sim/test", func(){<br />
print("Property '/sim/test' has been changed");<br />
});<br />
setprop("/sim/test", "blah"); # trigger listener<br />
var rem = removelistener(ls); # remove listener<br />
print("There are ", rem, " listeners remaining");<br />
}}<br />
<br />
===resolvepath()===<br />
{{Nasal doc<br />
|syntax = resolvepath(path);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=604|t=Source}}<br />
|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.<br />
|param1 = path<br />
|param1text = Relative path to be completed.<br />
|example1 = print(resolvepath("Nasal/globals.nas")); # prints the equivalent of $FG_ROOT/Nasal/globals.nas<br />
|example2 = print(resolvepath("blah")); # prints nothing; could not be resolved<br />
|example3 = var file_path = resolvepath("Aircraft/SenecaII/some-file");<br />
if (file_path != ""){<br />
gui.popupTip("some-file found", 2);<br />
} else {<br />
gui.popupTip("some-file not found", 2);<br />
<nowiki>}</nowiki><br />
}}<br />
<br />
===setlistener()===<br />
{{Nasal doc<br />
|syntax = <nowiki>setlistener(node, code[, init[, type]]);</nowiki><br />
|private = _setlistener()<br />
|source = {{flightgear file|src/Scripting/Nasal|l=499|t=Part 1}} {{!}} {{flightgear file|src/Scripting/Nasal|l=1350|t=Part 2}}<br>''Listener implemented using the {{API Link|flightgear|class|FGNasalListener}} class.''<br />
|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()}}.<br />
{{note|Listeners are known to be a source of resource leaks. To avoid this, please take measures such as:<br />
* Using {{func link|removelistener()}} when they are not needed any more.<br />
* Using a single initialization listener.<br />
* Avoiding tying listeners to properties that are rapidly updated (e.g., many times per frame).<br />
}}<br />
|param1 = node<br />
|param1text = Mandatory string or <code>props.Node</code> object pointing to a property in the [[Property Tree]].<br />
|param2 = code<br />
|param2text = Mandatory callback function to execute when the listener is triggered. The function can take up to four arguments in the following order:<br />
* '''changed''': a <code>props.Node</code> object pointing to the changed node.<br />
* '''listen''': a <code>props.Node</code> object pointing to the listened-to node. Note that this argument maybe different depending on the '''type'''.<br />
* '''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.<br />
* '''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.<br />
|param3 = init<br />
|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).<br />
|param4 = type<br />
|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.<br />
|example1 = var prop = props.globals.initNode("/sim/test", "", "STRING"); # create property<br />
var id = setlistener("/sim/test", func(n){ # create listener<br />
print("Value: ", n.getValue());<br />
});<br />
setprop("/sim/test", "blah"); # trigger listener<br />
removelistener(id); # remove listener<br />
prop.remove(); # remove property<br />
}}<br />
<br />
===setprop()===<br />
{{Nasal doc<br />
|syntax = <nowiki>setprop(path[, path[, ...]], value);</nowiki><br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=385|t=Source}}<br />
|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.<br />
{{note|If you want to remove a property, you will have to use one of the <code>props</code> helpers:<br />
<syntaxhighlight lang="nasal"><br />
props.globals.getNode("foo/bar").remove(); # take out the complete node<br />
props.globals.getNode("foo").removeChild("bar"); # take out a certain child node<br />
</syntaxhighlight><br />
}}<br />
|param1 = path<br />
|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.<br />
|param2 = value<br />
|param2text = Value to write to the given property. Must be either a string or a number.<br />
|example1 = setprop("/sim/demo", "This is a demo");<br />
|example2text = Note that the example below will only work in FlightGear 3.2 and above.<br />
|example2 = for(var i = 0; i < 3; i += 1){<br />
setprop("/sim/demo", i, "Demo #" ~ i));<br />
}<br />
|example3text = Same as above, but is supported by all versions of FlightGear.<br />
|example3 = for(var i = 0; i < 3; i += 1){<br />
setprop("/sim/demo[" ~ i ~ "]", "Demo #" ~ i));<br />
<nowiki>}</nowiki><br />
}}<br />
====See also====<br />
{{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: <br />
To get a Node rather than its value, use <code>props.globals.getNode()</code> - see [[Nasal_library/props]]. }}<br />
<br />
===settimer() ===<br />
{{Nasal doc<br />
|syntax = settimer(function, delta[, realtime]);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=499|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=1286|t=Part 2}}<br />
|text = Runs the given function a specified amount of seconds after the current time. Returns <code>'''nil'''</code>.<br />
{{note|Improper use of <code>settimer()</code> may cause resource leaks. It is also highly recommended that the newer {{func link|maketimer()}} should be used instead of this function.}} <br />
|param1 = function<br />
|param1text = Mandatory function that will be called. It may be necessary to enclose code in an anonymous function (see example).<br />
|param2 = delta<br />
|param2text = Mandatory amount of time in seconds after which the function will be called.<br />
|param3 = realtime<br />
|param3text = If 1 (true), "real time" will be used instead of "simulation time." Defaults to 0 (false). Note that if "simulation time" is used, the timer will not run while FlightGear is paused.<br />
|example1 = var myFunc = func(){<br />
print("Hello");<br />
}<br />
<br />
settimer(myFunc, 2); # runs myFunc after 2 seconds<br />
|example2 = var sqr = func(a){<br />
return a * a;<br />
}<br />
<br />
settimer(func(){<br />
print(sqr(2)); # will print 4 after 2 seconds<br />
}, 2);<br />
}}<br />
<br />
===srand() ===<br />
{{Nasal doc<br />
|syntax = srand();<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=559|t=Source}}<br />
|text = Makes the pseudorandom number generator (see {{func link|rand()}}) generate a new {{wikipedia|random seed|noicon=1}} based on time. Returns 0.<br />
}}<br />
===systime()===<br />
{{Nasal doc<br />
|syntax = systime();<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=770|t=Source}}<br />
|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).<br />
{{note|1=High resolution timers under Windows can produce inaccurate or fixed sub-millisecond results.<ref>{{cite web|url=http://forum.flightgear.org/viewtopic.php?f=30&t=29259|title=Nasal: systime() ??!?|author=Necolatis|date=Apr 2nd, 2016}}</ref> 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]}}<br />
|example1 = print("Unix time: ", systime()); # prints Unix time<br />
|example2 = var myFunc = func(){<br />
for(var i = 0; i <= 10; i += 1){<br />
print("Interation #", i);<br />
}<br />
}<br />
var t = systime(); # record time<br />
myFunc(); # run function<br />
var t2 = systime(); # record new time<br />
print("myFunc() took ", t2 - t, " seconds"); # print result<br />
}}<br />
<br />
===thisfunc()===<br />
{{Nasal doc<br />
|syntax = thisfunc();<br />
|source = {{fgdata file|Nasal/globals.nas|t=Source}}<br />
|text = Returns the function from which this function is called. This allows a function to reliably and safely call itself from within a closure.<br />
|example1 = var stringify_vec = func(input){<br />
if (typeof(input) == "scalar"){<br />
return sprintf("%s", input);<br />
} elsif (typeof(input) == "vector") {<br />
if (size(input) == 0) return "[]";<br />
var this = thisfunc();<br />
var buffer = "[";<br />
for(var i = 0; i < size(input); i += 1){<br />
buffer ~= this(input[i]);<br />
if (i == size(input) - 1) {<br />
buffer ~= "]";<br />
} else {<br />
buffer ~= ", ";<br />
}<br />
}<br />
return buffer;<br />
} else {<br />
die("stringify_vec(): Error! Invalid input. Must be a vector or scalar");<br />
}<br />
}<br />
<br />
var test_vec = ["a", "b", "c", 1, 2, 3];<br />
debug.dump(stringify_vec(test_vec)); # prints "[a, b, c, 1, 2, 3]"<br />
test_vec = [];<br />
debug.dump(stringify_vec(test_vec)); # prints "[]"<br />
test_vec = {};<br />
debug.dump(stringify_vec(test_vec)); # will throw an error<br />
}}<br />
<br />
===tileIndex() ===<br />
{{Nasal doc<br />
|syntax = tileIndex();<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1720|t=Source}}<br />
|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 <code>942050</code>, corresponding to <tt>''[[$FG_SCENERY]]/Terrain/w130n30/w123n3/942050.stg''</tt>.<br />
|example1 = print(tileIndex()); # print index<br />
}}<br />
<br />
=== tilePath()===<br />
{{Nasal doc<br />
|syntax = tilePath();<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1712|t=Source}}<br />
|text = Returns the base path of the tile at the aircraft's current position as a string. For example, at KSFO, this would be <code>w130n30/w123n3</code>, corresponding to <tt>''[[$FG_SCENERY]]/Terrain/w130n30/w123n3''</tt>.<br />
|example1 = print(tilePath()); # print path<br />
}}<br />
<br />
=== values()===<br />
{{Nasal doc<br />
|syntax = values(hash);<br />
|source = {{fgdata file|Nasal/globals.nas|t=Source}}<br />
|text = Returns a vector containing the values of the given hash.<br />
|param1 = hash<br />
|param1text = Mandatory hash to get the values of.<br />
|example1 = var hash = {<br />
"a": 1,<br />
"b": 2,<br />
"c": 3<br />
};<br />
<br />
foreach(var val; values(hash)){<br />
print(val);<br />
}<br />
|example2text = The below example does exactly the same thing as the above example, but does not use <code>values()</code>:<br />
|example2 = var hash = {<br />
"a": 1,<br />
"b": 2,<br />
"c": 3<br />
};<br />
<br />
foreach(var key; keys(hash)){<br />
print(hash[key]);<br />
<nowiki>}</nowiki><br />
}}<br />
<br />
==Extension functions new in FG 2020.1 ==<br />
The following functions have been added to the nasal core library and will be released with FlightGear version 2020.1. <br />
Before the release they are available in the development branch "next".<br />
<br />
===isfunc()===<br />
Returns 1 if type or argument is a function, otherwise 0.<br />
<br />
===isghost()===<br />
Returns 1 if type or argument is a ghost, otherwise 0.<br />
<br />
=== ishash()===<br />
Returns 1 if type or argument is a hash, otherwise 0.<br />
<br />
===isint()===<br />
{{Nasal doc<br />
|syntax = isint(x);<br />
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}<br />
|text = Returns 1 if argument has a numeric value and x == floor(x), e.g. integer.<br />
}}<br />
<br />
===isnum()===<br />
{{Nasal doc<br />
|syntax = isnum(x);<br />
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}<br />
|text = Returns 1 if typeof(x) is "scalar" and x has a numeric value otherwise 0. <br />
}}<br />
<br />
===isscalar()===<br />
{{Nasal doc<br />
|syntax = isscalar(x);<br />
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}<br />
|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 "~". <br />
|example1 = var a = "foo"; <br />
var b=42;<br />
if (isscalar(a) and isscalar(b)) print(a~b);<br />
if (isstr(a)) print("a is a string");<br />
if (isint(b)) print("b is an integer");<br />
# if (isscalar(a))... is equivalent to if (typeof(a) == "scalar")...<br />
}}<br />
<br />
===isstr()===<br />
Returns 1 if type or argument is a string, otherwise 0. <br />
<br />
===isvec()===<br />
Returns 1 if type or argument is a vector, otherwise 0.<br />
<br />
===vecindex()===<br />
{{Nasal doc<br />
|syntax = vecindex(vector, value);<br />
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}<br />
|text = Returns the index of value or nil, if value is not found in vector.<br />
|example1=<br />
var myvector = ["apple", "bananna", "coconut"];<br />
# to check if a vector contains a certain value compare vecindex to nil<br />
if (vecindex(myvector, "apple") != nil) {<br />
print("found apple");<br />
}<br />
# WARNING: this will not work as desired because index is 0<br />
if (vecindex(myvector, "apple")) {<br />
print("found apple");<br />
<nowiki>}</nowiki><br />
<br />
}}<br />
<br />
==Variables==<br />
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}}.<br />
<br />
===D2R===<br />
{{Nasal doc<br />
|syntax = var radians = degrees * D2R;<br />
|text = Converts an angle from degrees to radians when multiplied by the angle in degrees. Equal to <code>π / 180</code>.<br />
}}<br />
===FPS2KT===<br />
{{Nasal doc<br />
|syntax = var knots = feet_per_second * FPS2KT;<br />
|text = Converts a velocity from feet per second to knots when multiplied by the velocity in feet per second. Approximately equal to 0.5925.<br />
}}<br />
===FT2M===<br />
{{Nasal doc<br />
|syntax = var metres = feet * FT2M;<br />
|text = Converts a length from feet to metres when multiplied by the length in feet. Equal to 0.3048.<br />
}}<br />
===GAL2L ===<br />
{{Nasal doc<br />
|syntax = var litres = gallons * GAL2L;<br />
|text = Converts a volume from US liquid gallons to litres when multiplied by the volume in gallons. Approximately equal to 3.7854.<br />
}}<br />
===IN2M===<br />
{{Nasal doc<br />
|syntax = var metres = inches * IN2M;<br />
|text = Converts a length from inches to metres when multiplied by the length in inches. Equal to 0.0254.<br />
}}<br />
===KG2LB===<br />
{{Nasal doc<br />
|syntax = var pounds = kilograms * KG2LB;<br />
|text = Converts a mass from kilograms to pounds when multiplied by the mass in kilograms. Approximately equal to 2.2046.<br />
}}<br />
===KT2FPS===<br />
{{Nasal doc<br />
|syntax = var feet_per_second = knots * KT2FPS;<br />
|text = Converts a velocity from knots to feet per second when multiplied by the velocity in knots. Approximately equal to 1.6878.<br />
}}<br />
===KT2MPS===<br />
{{Nasal doc<br />
|syntax = var metres_per_second = knots * KT2MPS;<br />
|text = Converts a velocity from knots to metres per second when multiplied by the velocity in knots. Approximately equal to 0.5144.<br />
}}<br />
===L2GAL===<br />
{{Nasal doc<br />
|syntax = var gallons = litres * L2GAL;<br />
|text = Converts a volume from litres to US liquid gallons when multiplied by the volume in litres. Approximately equal to 0.2642.<br />
}}<br />
===LB2KG===<br />
{{Nasal doc<br />
|syntax = var kilograms = pounds * LB2KG;<br />
|text = Converts a mass from pounds to kilograms when multiplied by the mass in pounds. Approximately equal to 0.4536.<br />
}}<br />
===M2FT ===<br />
{{Nasal doc<br />
|syntax = var feet = metres * M2FT;<br />
|text = Converts a length from metres to feet when multiplied by the length in metres. Approximately equal to 3.2808.<br />
}}<br />
===M2IN===<br />
{{Nasal doc<br />
|syntax = var inches = metres * M2IN;<br />
|text = Converts a length from metres to inches when multiplied by the length in metres. Approximately equal to 39.3701.<br />
}}<br />
===M2NM ===<br />
{{Nasal doc<br />
|syntax = var nautical_miles = metres * M2NM;<br />
|text = Converts a length from metres to nautical miles when multiplied by the length in metres. Approximately equal to 0.00054.<br />
}}<br />
===MPS2KT ===<br />
{{Nasal doc<br />
|syntax = var knots = metres_per_second * MPS2KT;<br />
|text = Converts a velocity from metres per second to knots when multiplied by the velocity in metres per second. Approximately equal to 1.9438.<br />
}}<br />
===NM2M ===<br />
{{Nasal doc<br />
|syntax = var metres = nautical_miles * NM2M;<br />
|text = Converts a length from nautical miles to metres when multiplied by the length in nautical miles. Equal to 1,852.<br />
}}<br />
===R2D===<br />
{{Nasal doc<br />
|syntax = var degrees = radians * R2D;<br />
|text = Converts an angle from radians to degrees when multiplied by the angle in radians. Equal to <code>180 / π</code>.<br />
}}<br />
<br />
{{Appendix}}<br />
<br />
<br />
{{Nasal namespaces}}</div>Jsbhttps://wiki.flightgear.org/w/index.php?title=Howto:Add_aircraft_lights&diff=137472Howto:Add aircraft lights2023-03-29T19:28:10Z<p>Jsb: Clarification: illuminator addon was written for compositor lights.</p>
<hr />
<div>In order to '''add''' (the right) '''lights to your aircraft''', you need to know what kind of lights [[Aircraft|planes]] are equipped with. Therefore it is suggested to read [[aircraft lighting]] first.<br />
<br />
== Anti-Collision Beacon lights ==<br />
=== Nasal ===<br />
<source lang="nasal"><br />
var beacon_switch = props.globals.getNode("controls/switches/beacon", 2);<br />
var beacon = aircraft.light.new( "/sim/model/lights/beacon", [0, 3], "/controls/lighting/beacon" );<br />
</source><br />
<br />
=== Model ===<br />
<source lang="xml"><br />
<animation><br />
<type>select</type><br />
<object-name>Light</object-name><br />
<condition><br />
<property>/sim/model/lights/beacon/state</property><br />
</condition><br />
</animation><br />
</source><br />
<br />
== Strobe lights ==<br />
=== Nasal ===<br />
<source lang="nasal"><br />
var strobe_switch = props.globals.getNode("controls/switches/strobe", 2);<br />
var strobe = aircraft.light.new( "/sim/model/lights/strobe", [0, 3], "/controls/lighting/strobe" );<br />
</source><br />
<br />
=== Model ===<br />
<source lang="xml"><br />
<animation><br />
<type>select</type><br />
<object-name>Light</object-name><br />
<condition><br />
<property>/sim/model/lights/strobe/state</property><br />
</condition><br />
</animation><br />
</source><br />
<br />
== Landing lights ==<br />
=== Model ===<br />
<source lang="xml"><br />
<animation><br />
<type>select</type><br />
<object-name>Light</object-name><br />
<condition><br />
<property>controls/lighting/landing-light</property><br />
</condition><br />
</animation><br />
</source><br />
<br />
If the lights are located on the nosegear, use the one below instead. It will only show the lights when the landinggear is out. Change the value to make the light switch on when gear is out (vary from plane to plane, you can use the [[property browser]] to find the right value). <br />
<source lang="xml"><br />
<animation><br />
<type>select</type><br />
<object-name>Light</object-name><br />
<condition><br />
<and><br />
<property>controls/lighting/landing-light</property><br />
<greater-than><br />
<property>gear/gear/position-norm</property><br />
<value>0.05</value><br />
</greater-than><br />
</and><br />
</condition><br />
</animation><br />
</source><br />
<br />
== Combined ALS procedural lights and Compositor lights ==<br />
Flightgear comes with three light effects which historically had to be configured separately. The original sprite lights, the [[ALS technical notes#ALS procedural lights|ALS procedural light]] which simulates the effect when looking at the light source, and the [[Compositor#Lights|Compositor lights]] which illuminate the surrounding.<br />
<br />
As of January 30th 2022 it is possible to configure all light effects simultaneously by using the XML overlay system and referencing spotlight.xml or pointlight.xml in the Aircraft/Generic directory.<syntaxhighlight lang="xml"><br />
<model><br />
<name>RedLight</name><br />
<path>Aircraft/Generic/spotlight.xml</path><br />
<offsets><br />
<x-m>-0.42</x-m><br />
<y-m>-5.28</y-m><br />
<z-m> 0.67</z-m><br />
</offsets><br />
<overlay><br />
<params><br />
<name>RedLight</name><br />
<power-source>/controls/lighting/nav-lights</power-source><br />
<direction><br />
<x>0.5</x><br />
<y>0.5</y><br />
<z>0</z><br />
</direction><br />
<ambient><br />
<r>0.5</r><br />
<g>0</g><br />
<b>0</b><br />
</ambient><br />
<specular><br />
<r>1.0</r><br />
<g>0.7</g><br />
<b>0.5</b><br />
</specular><br />
</params><br />
</overlay><br />
</model><br />
<br />
</syntaxhighlight>Be aware that the direction follows the convention from ALS pointing_x, pointing_y and pointing_z and not the x, y and z tags as used by the Compositor. The Compositors directional vector is directly opposite to the one used here and by ALS.<br />
<br />
The power-source property replaces the select animation mentioned above.<br />
<br />
The power-source, direction and color components of the overlay section are shared between ALS and the Compositor. Other parameters are set to a sane default for normal navigation lights but can be adjusted individually.<br />
<br />
For example for a landing light you could add the following parameters to the overlay section<syntaxhighlight lang="xml"><br />
<!-- ALS settings --><br />
<dist-scale>10</dist-scale><br />
<inner_angle>0.1</inner_angle><br />
<outer_angle>0.4</outer_angle><br />
<zero_angle>0.5</zero_angle><br />
<outer_gain>0.0</outer_gain><br />
<br />
<!-- Compositor settings --><br />
<attenuation><br />
<c>0.375</c><br />
<l>0.01</l><br />
<q>0.0001</q><br />
</attenuation><br />
<spot-cutoff>60</spot-cutoff><br />
<spot-exponent>45</spot-exponent><br />
<range-m>1500</range-m><br />
</syntaxhighlight><br />
To tune the numbers of compositor lights, you may want to have a look at the Illuminator [[Addon|add-on]], which allows to modify ''existing'' lights at runtime. You need to add the light XML first to your aircraft with some guestimate numbers.[[File:NavlightsALS.jpg|thumb|PC-9M with ALS NAV lights|alt=|left]][[File:Navlights.jpg|thumb|PC-9M with pre-ALS sprite NAV lights|alt=]]<br />
[[Category:Nasal howto|Aircraft lights add]]</div>Jsbhttps://wiki.flightgear.org/w/index.php?title=Howto:Add_aircraft_lights&diff=137471Howto:Add aircraft lights2023-03-29T18:43:20Z<p>Jsb: Add hint to Illuminator add-on.</p>
<hr />
<div>In order to '''add''' (the right) '''lights to your aircraft''', you need to know what kind of lights [[Aircraft|planes]] are equipped with. Therefore it is suggested to read [[aircraft lighting]] first.<br />
<br />
== Anti-Collision Beacon lights ==<br />
=== Nasal ===<br />
<source lang="nasal"><br />
var beacon_switch = props.globals.getNode("controls/switches/beacon", 2);<br />
var beacon = aircraft.light.new( "/sim/model/lights/beacon", [0, 3], "/controls/lighting/beacon" );<br />
</source><br />
<br />
=== Model ===<br />
<source lang="xml"><br />
<animation><br />
<type>select</type><br />
<object-name>Light</object-name><br />
<condition><br />
<property>/sim/model/lights/beacon/state</property><br />
</condition><br />
</animation><br />
</source><br />
<br />
== Strobe lights ==<br />
=== Nasal ===<br />
<source lang="nasal"><br />
var strobe_switch = props.globals.getNode("controls/switches/strobe", 2);<br />
var strobe = aircraft.light.new( "/sim/model/lights/strobe", [0, 3], "/controls/lighting/strobe" );<br />
</source><br />
<br />
=== Model ===<br />
<source lang="xml"><br />
<animation><br />
<type>select</type><br />
<object-name>Light</object-name><br />
<condition><br />
<property>/sim/model/lights/strobe/state</property><br />
</condition><br />
</animation><br />
</source><br />
<br />
== Landing lights ==<br />
=== Model ===<br />
<source lang="xml"><br />
<animation><br />
<type>select</type><br />
<object-name>Light</object-name><br />
<condition><br />
<property>controls/lighting/landing-light</property><br />
</condition><br />
</animation><br />
</source><br />
<br />
If the lights are located on the nosegear, use the one below instead. It will only show the lights when the landinggear is out. Change the value to make the light switch on when gear is out (vary from plane to plane, you can use the [[property browser]] to find the right value). <br />
<source lang="xml"><br />
<animation><br />
<type>select</type><br />
<object-name>Light</object-name><br />
<condition><br />
<and><br />
<property>controls/lighting/landing-light</property><br />
<greater-than><br />
<property>gear/gear/position-norm</property><br />
<value>0.05</value><br />
</greater-than><br />
</and><br />
</condition><br />
</animation><br />
</source><br />
<br />
== Combined ALS procedural lights and Compositor lights ==<br />
Flightgear comes with three light effects which historically had to be configured separately. The original sprite lights, the [[ALS technical notes#ALS procedural lights|ALS procedural light]] which simulates the effect when looking at the light source, and the [[Compositor#Lights|Compositor lights]] which illuminate the surrounding.<br />
<br />
As of January 30th 2022 it is possible to configure all light effects simultaneously by using the XML overlay system and referencing spotlight.xml or pointlight.xml in the Aircraft/Generic directory.<syntaxhighlight lang="xml"><br />
<model><br />
<name>RedLight</name><br />
<path>Aircraft/Generic/spotlight.xml</path><br />
<offsets><br />
<x-m>-0.42</x-m><br />
<y-m>-5.28</y-m><br />
<z-m> 0.67</z-m><br />
</offsets><br />
<overlay><br />
<params><br />
<name>RedLight</name><br />
<power-source>/controls/lighting/nav-lights</power-source><br />
<direction><br />
<x>0.5</x><br />
<y>0.5</y><br />
<z>0</z><br />
</direction><br />
<ambient><br />
<r>0.5</r><br />
<g>0</g><br />
<b>0</b><br />
</ambient><br />
<specular><br />
<r>1.0</r><br />
<g>0.7</g><br />
<b>0.5</b><br />
</specular><br />
</params><br />
</overlay><br />
</model><br />
<br />
</syntaxhighlight>Be aware that the direction follows the convention from ALS pointing_x, pointing_y and pointing_z and not the x, y and z tags as used by the Compositor. The Compositors directional vector is directly opposite to the one used here and by ALS.<br />
<br />
The power-source property replaces the select animation mentioned above.<br />
<br />
The power-source, direction and color components of the overlay section are shared between ALS and the Compositor. Other parameters are set to a sane default for normal navigation lights but can be adjusted individually.<br />
<br />
For example for a landing light you could add the following parameters to the overlay section<syntaxhighlight lang="xml"><br />
<!-- ALS settings --><br />
<dist-scale>10</dist-scale><br />
<inner_angle>0.1</inner_angle><br />
<outer_angle>0.4</outer_angle><br />
<zero_angle>0.5</zero_angle><br />
<outer_gain>0.0</outer_gain><br />
<br />
<!-- Compositor settings --><br />
<attenuation><br />
<c>0.375</c><br />
<l>0.01</l><br />
<q>0.0001</q><br />
</attenuation><br />
<spot-cutoff>60</spot-cutoff><br />
<spot-exponent>45</spot-exponent><br />
<range-m>1500</range-m><br />
</syntaxhighlight><br />
To tune the numbers you may want to have a look at the Illuminator [[Addon|add-on]], which allows to modify ''existing'' lights at runtime. You need to add the light XML first to your aircraft with some guestimate numbers.[[File:NavlightsALS.jpg|thumb|PC-9M with ALS NAV lights|alt=|left]][[File:Navlights.jpg|thumb|PC-9M with pre-ALS sprite NAV lights|alt=]]<br />
[[Category:Nasal howto|Aircraft lights add]]</div>Jsbhttps://wiki.flightgear.org/w/index.php?title=Addon&diff=137470Addon2023-03-29T18:37:51Z<p>Jsb: Add illuminator add on to list.</p>
<hr />
<div>[[File:Fgaddonslogo202x89.png|right]] <br />
To make it easier to create '''addons''' there is since FlightGear 2017.3 a new way to create addons. In essence FlightGear will load an overlay XML into the property tree and start a well known Nasal function.<ref>{{cite web<br />
|url = https://forum.flightgear.org/viewtopic.php?p=314620#p314620 <br />
|title = <nowiki> Re: New Feature: Addon - "API" </nowiki> <br />
|author = <nowiki> Torsten </nowiki> <br />
|date = Jul 19th, 2017 <br />
|added = Jul 19th, 2017 <br />
|script_version = 0.40 <br />
}}</ref><br />
<br />
We now have a simple way to add addons to FlightGear without the need to mess around with <code>FGData/Nasal</code> or <code>FGHome/Nasal</code> directories.<ref name="Forum_announcement">{{cite web<br />
|url = https://forum.flightgear.org/viewtopic.php?p=314563#p314563 <br />
|title = <nowiki> New Feature: Addon - "API" </nowiki> <br />
|author = <nowiki> Torsten </nowiki> <br />
|date = Jul 18th, 2017 <br />
}}</ref><br />
<br />
{{TOC limit|3}}<br />
<br />
== Installing and using an addon ==<br />
Download and copy the addon to a directory on your computer.<br />
<br />
If you use the launcher, select the Add-ons page from the icon bar on the left, then find the section Add-on Module folders and click the Add(+) button. Select the folder where you put the addon. Once the addon is shown in the list, make sure its check box is selected.<br />
<br />
Use the command line switch <code>--addon=/path/to/some/addon</code>.<ref name="Forum_announcement"/><br />
<br />
== Creating an addon ==<br />
There is a very simple Skeleton addon available in FGAddon to be used as a template.<ref name="Forum_announcement"/> See {{fgaddon source|path=Addons/Skeleton}}.<br />
<br />
A leading slash (<code>/</code>) in this section indicates the base directory of the directory structure of the addon.<br />
<br />
=== Minimum configuration ===<br />
An addon may be installed in a directory anywhere on your hard disk and need at least two files:<br />
<br />
* <code>/addon-config.xml</code> - A standard [[PropertyList XML files|PropertyList XML file]] to be used to populate or modify the [[property tree]]. (Same as to be used in <code>--config=foo.xml</code>)<br />
* <code>/addon-main.nas</code> - The Nasal hook for the logic. This file needs a function called <code>main()</code> which will be called from the global addon initializer (<code>addons.nas</code>)<br />
<br />
=== Additional common files ===<br />
* <code>/addon-metadata.xml</code> - A PropertyList XML file with metadata about the addon it.<br />
* <code>/addon-menubar-items.xml</code> - A PropertyList XML file describing menus to be added to the FlightGear menu bar.<br />
* <code>/gui/dialogs/&lt;my-foobar-dialog&gt;.xml</code> - PropertyList XML files to create custom dialogs.<br />
<br />
=== Good to know ===<br />
The new addon mechanism lets you add a <code>addon-config.xml</code> to override the settings in <code>defaults.xml</code> and other files.<br />
<br />
That will allow an addon to<br />
* Override key bindings (as in the spoken ATC addon)<br />
* Add or override autopilots and property rules<br />
* Introduce XML state machines<br />
<br />
Unless your really want to add/change/remove those at runtime, this should cater for most use cases.<ref>{{cite web<br />
|url = https://forum.flightgear.org/viewtopic.php?p=314902#p314902 <br />
|title = <nowiki> Re: Spoken </nowiki> <br />
|author = <nowiki> Torsten </nowiki> <br />
|date = Jul 23rd, 2017 <br />
|added = Jul 23rd, 2017 <br />
|script_version = 0.40 <br />
}}</ref><br />
<br />
We have some instructions how to use SVN [[FGAddon|in our wiki]]. It covers mostly aircraft development but the workflow is pretty much the same for addons.<ref>{{cite web<br />
|url = https://forum.flightgear.org/viewtopic.php?p=314647#p314647 <br />
|title = <nowiki> Re: Spoken ATC </nowiki> <br />
|author = <nowiki> Torsten </nowiki> <br />
|date = Jul 19th, 2017 <br />
|added = Jul 19th, 2017 <br />
|script_version = 0.40 <br />
}}</ref><br />
<br />
=== Sound Support ===<br />
Sound support is available using fgcommand's.<br />
<br />
See [[Nasal FAQ]] "play-audio-sample"<br />
<br />
https://sourceforge.net/p/flightgear/flightgear/ci/5acf2e26d085b7553b2387b9753e9253e8b4bff4<br />
<br />
[[Hackathon Proposal:Addon specific Sound Queues]]<br />
<br />
== Addon initialization ==<br />
On initialization fgfs takes care of:<br />
* Through {{flightgear source|path=src/Main/options.cxx|text=<code>options.cxx</code>}}:<ref name="Forum_announcement"/><br />
** Creating a property under <code>/addons/addon[n]/path=/path/to/some/addon</code><br />
** Loading <code>/path/to/some/addon/addon-config.xml</code> into the property tree (same as <code>--config=/path/to/some/addon/addon-config.xml</code>)<br />
** Adding <code>/path/to/some/addon</code> to the list of allowed directories (same as <code>--fg-aircraft=/path/to/some/addon</code>)<br />
* Through {{fgdata source|path=Nasal/addons.nas|text=<code>addons.nas</code>}}:<br />
** Loading <code>/foo/bar/baz/addon-main.nas</code> into namespace <code>__addon[ADDON_ID]__</code><br />
** Calling <code>main(addonGhost)</code> from <code>/foo/bar/baz/addon-main.nas</code>.<br />
<br />
== APIs ==<br />
{{hatnote|For more details about these APIs, see the readme file, {{readme file|add-ons}}.}}<br />
=== C++ API ===<br />
There is a C++ API on FlightGear's side that handle some on the addon related tasks manly through the <code>AddonManager()</code>, <code>Addon()</code> and <code>AddonVersion()</code> classes.<br />
<br />
=== Nasal API ===<br />
The Nasal add-on API lives in the 'addons' namespace and can for example do queries to <code>AddonManager()</code> and read data from <code>addons.Addon</code> objects. It can for example compare addon versions if there is dependencies.<br />
<br />
== Background ==<br />
{{See also|Howto:Creating a simple modding framework}}<br />
<br />
ATC chatter was removed in 2015, see fgdata commit: [https://sourceforge.net/p/flightgear/fgdata/ci/81607f734e13add9be02816ddaec305d05bc4e47/ 81607f734e13add9be02816ddaec305d05bc4e47]<br />
<br />
And the devel list messages referenced in the commit log.<br />
the other relevant commit is this: b60736ba7add2a7cd39af3d8a974d5be3ea46e8b<br />
It would not be very difficult to restore this functionality, or even generalize/improve it significantly (which was the scope of the original discussion on the devel list)<ref>{{cite web<br />
|url = https://forum.flightgear.org/viewtopic.php?p=288388#p288388 <br />
|title = <nowiki> Re: Whatever happened to radom radio </nowiki> <br />
|author = <nowiki> Hooray </nowiki> <br />
|date = Jun 11th, 2016 <br />
|added = Jun 11th, 2016 <br />
|script_version = 0.40 <br />
}}</ref><br />
<br />
The restored functionality could be distributed as a tarball that is extracted into $FG_ROOT - alternatively, into $FG_HOME, because Nasal files there are treated as overlays, which basically means that you can install user-specific extensions there without having to tamper with $FG_ROOT, it would only take very minor changes to turn the chatter feature into a corresponding "addon" - not unlike fgcamera ...<ref>{{cite web<br />
|url = https://forum.flightgear.org/viewtopic.php?p=288392#p288392 <br />
|title = <nowiki> Re: Whatever happened to radom radio </nowiki> <br />
|author = <nowiki> Hooray </nowiki> <br />
|date = Jun 11th, 2016 <br />
|added = Jun 11th, 2016 <br />
|script_version = 0.40 <br />
}}</ref><br />
<br />
We should absolutely stop telling anyone to edit preferences.xml in FG_ROOT; any documentation or advice which says to should be changes ASAP. <ref>{{cite web<br />
|url = https://forum.flightgear.org/viewtopic.php?p=192632#p192632 <br />
|title = <nowiki> Re: NavCache:init failed:Sqlite error:Sqlite API abuse </nowiki> <br />
|author = <nowiki> zakalawe </nowiki> <br />
|date = Oct 26th, 2013 <br />
|added = Oct 26th, 2013 <br />
|script_version = 0.40 <br />
}}</ref><br />
<br />
As of 12/2017, the addon API is in the process of being significantly updated <ref>https://sourceforge.net/p/flightgear/mailman/message/36146017/</ref> <ref>https://sourceforge.net/p/flightgear/mailman/message/36150159/</ref> <ref>https://sourceforge.net/p/flightgear/mailman/message/36150444/</ref><br />
<br />
== List of Addons ==<br />
You can find the official repository at {{fgaddon source|path=Addons}}<br />
<br />
* [https://github.com/slawekmikula/flightgear-addon-hudheli Additional Heli HUD's] - ([https://github.com/slawekmikula/flightgear-addon-hudheli/blob/master/doc/manual.md manual]) - encapsulation of HeliHUD package as an addon<br />
* [https://github.com/PlayeRom/flightgear-addon-aerotow-everywhere Aerotow Everywhere] AI towing aircraft for gliders at every airport.<ref>{{cite web<br />
|url = https://forum.flightgear.org/viewtopic.php?t=40742<br />
|title = <nowiki> Re: Aerotow Everywhere </nowiki> <br />
|author = <nowiki> Roman Ludwicki (PlayeRom) </nowiki> <br />
|date = Aug 14th, 2022<br />
|added = Aug 14th, 2022<br />
|script_version = 0.40 <br />
}}</ref><br />
* [https://github.com/SP-NTX/AnotherGUI AnotherGUI] - An add-on that adds a new GUI style.<br />
* ATC Chatter (ported by Torsten) {{progressbar|100}}<br />
* [[Cargo Towing Addon]] <ref>{{cite web<br />
|url = https://forum.flightgear.org/viewtopic.php?t=36824<br />
|title = <nowiki> Re: Cargo Towing Addon </nowiki> <br />
|author = <nowiki> Wayne Bragg (wlbragg) </nowiki> <br />
|date = <br />
|added = <br />
|script_version = 0.40 <br />
}}</ref><br />
* cockpit-view (work in progress by wkitty42)<ref>{{cite web<br />
|url = https://forum.flightgear.org/viewtopic.php?p=316498#p316498 <br />
|title = <nowiki> Re: </nowiki> <br />
|author = <nowiki> wkitty42 </nowiki> <br />
|date = Aug 13th, 2017 <br />
|added = Aug 13th, 2017 <br />
|script_version = 0.40 <br />
}}</ref><br />
* [[Earthview#Customization]] - High resolution customization<br />
* [[FaceTrackNoIR]] (ported by HHS)<ref>{{cite web<br />
|url = https://sourceforge.net/p/flightgear/mailman/message/36454826/<br />
|title = <nowiki> Re: </nowiki> <br />
|author = <nowiki> Unknown, HHS</nowiki><br />
|date = Nov 1th, 2018 <br />
}}</ref> - An addon to interface this [[Head tracking|head tracker]] with FlightGear<br />
* [https://forum.flightgear.org/viewtopic.php?f=5&t=24792 Fencemaker] (Eases creating Fence-like scenery objects. Originally by VaLeo, converted to an addon by sfr) - ([https://www.mediafire.com/file/cf0la63v9g352md/fencemaker_addon.zip/file download])<ref>{{cite web<br />
|url = https://forum.flightgear.org/viewtopic.php?f=5&t=24792&start=45#p390066<br />
|title = <nowiki> Re: </nowiki><br />
|author = <nowiki> Stefan Frank </nowiki><br />
|maintainer = <nowiki> Stefan Frank </nowiki><br />
|date = Aug 8th, 2021<br />
}}</ref><br />
* [https://github.com/PlayeRom/flightgear-addon-fgcamera FGCamera] - ([https://github.com/PlayeRom/flightgear-addon-fgcamera/blob/master/Docs/manual.md manual]) - [[FGCamera | Wiki Page]]<br />
* [[FGPlot]]<br />
* [[Ground Services]] (ported by ThomasS)<ref>{{cite web<br />
|url = https://forum.flightgear.org/viewtopic.php?p=316400#p316400 <br />
|title = <nowiki> Re: </nowiki> <br />
|author = <nowiki> ThomasS </nowiki> <br />
|date = Aug 12th, 2017 <br />
|added = Aug 12th, 2017 <br />
|script_version = 0.40 <br />
}}</ref><br />
* {{fgaddon source|path=Addons/Headtracker/|text=Headtracker addon}} Helps integrate FaceTrackNoIR and opentrack with FlightGear.<br />
* [https://github.com/tdammers/fg-hoppie-acars Hoppie ACARS client] - connect to [http://www.hoppie.nl/acars Hoppie's ACARS], used on VATSIM and other networks.<br />
* [https://sourceforge.net/p/flightgear/fgaddon/HEAD/tree/trunk/Addons/Illuminator/ Illuminator] - configure lights attached to 3D models (e.g. taxi light, landing light or a light attached to a scenery model like a light pole) at runtime.<br />
* [https://github.com/slawekmikula/flightgear-addon-protocolkml KML Exporter (Google Earth)] - ([https://github.com/slawekmikula/flightgear-addon-protocolkml/blob/master/doc/manual.md manual])<br />
* [[Landing Rate addon]] [https://forum.flightgear.org/viewtopic.php?f=6&t=33101&p=327787#p327787]<br />
* [https://github.com/slawekmikula/flightgear-addon-linuxtrack LinuxTrack Head Tracker integration] - ([https://github.com/slawekmikula/flightgear-addon-linuxtrack/blob/master/doc/manual.md manual])<br />
* [https://github.com/slawekmikula/flightgear-addon-littlenavmap LittleNavMap integration] - ([https://github.com/slawekmikula/flightgear-addon-littlenavmap/blob/master/doc/manual.md manual])<br />
* [https://github.com/PlayeRom/flightgear-addon-logbook Logbook] all your flights to CSV file.<ref>{{cite web<br />
|url = https://forum.flightgear.org/viewtopic.php?t=41070<br />
|title = <nowiki> Re: Logbook Add-on </nowiki> <br />
|author = <nowiki> Roman Ludwicki (PlayeRom) </nowiki> <br />
|date = Dec 11th, 2022<br />
|added = Dec 11th, 2022<br />
|script_version = 0.40 <br />
}}</ref><br />
* [https://gitlab.com/mdanil/flightgear-hax mdanilov hax!]: landing evaluation and aircraft development tools, TerraSync toggler<br />
* [[Model Cockpit View]] <br />
* [https://github.com/SP-NTX/MPChatImprovments MPChatImprovments] Multi-key commands for Multiplayer, new features for chat,...<br />
* [[Oscilloscope addon]] - Allows displaying a property of Nasal function over time<br />
* [[PAR instrument]] - Precision Approach Radar and Ground Controlled Approach<br />
* [[Red Griffin ATC]] - Speaking Air Traffic Controller<ref>{{cite web<br />
|url = https://forum.flightgear.org/viewtopic.php?f=6&t=36755 <br />
|title = <nowiki> Re: </nowiki> <br />
|author = <nowiki> RedGriffin </nowiki> <br />
|date = Jan 6th, 2020 <br />
|added = Jan 6th, 2020 <br />
|script_version = 1.0.0 RC2 <br />
}}</ref><br />
* [https://github.com/tdammers/fg-simbrief-addon SimBrief import] - Import flightplans, weights, fuel, and winds alof, from SimBrief.<br />
* [[Spoken ATC]] (ported by Torsten)<ref>{{cite web<br />
|url = https://forum.flightgear.org/viewtopic.php?p=314095#p314095 <br />
|title = <nowiki> Re: </nowiki> <br />
|author = <nowiki> Torsten </nowiki> <br />
|date = Jul 10th, 2017 <br />
|added = Jul 10th, 2017 <br />
|script_version = 0.40 <br />
}}</ref><br />
* [[Spoken GCA]] - An offline ground controlled approach (GCA) addon<br />
* [https://gitlab.com/mdanil/flightgear-mickey Tiny HUD for mouse flying in FlightGear] - Visual feedback for mouse flying, to make up for mouse's lack of self-centering<br />
* [https://github.com/slawekmikula/flightgear-addon-vfrflight VFRFlight integration] - ([https://github.com/slawekmikula/flightgear-addon-vfrflight/blob/master/doc/manual.md manual])<br />
* [https://github.com/slawekmikula/flightgear-addon-vfrnavigator VFR Flying Helper] - ([https://github.com/slawekmikula/flightgear-addon-vfrnavigator/blob/master/doc/usage.md manual])<br />
* [[YASim Development Tools]] (by jsb)<br />
* [https://github.com/hbeni/fgfs-noGroundDamage noGroundDamage] - Addon to temporarily disable damage after landing and for ground operations for the c172/c182<br />
<br />
== Experimental Addons ==<br />
<br />
Addons which are in the development stage/unfinished but can be used as a quick view of addon functionality<br />
* [https://github.com/slawekmikula/flightgear-addon-missions FlightGear Missions addon] - Add-on for missions/adventures code<br />
<br />
== Ideas ==<br />
=== Hooking into features using legacy OpenGL code ===<br />
{{See also|Unifying the 2D rendering backend via canvas}}<br />
In 09/2018, James suggested that legacy features using raw OpenGL calls (e.g. HUD/2D panels) could be easily replaced via scripted Canvas/Nasal solutions at the mere cost of providing a mechanism to hook into the legacy code implementing these features (namely, the HUD/instrumentation subsystems) <ref>https://sourceforge.net/p/flightgear/mailman/message/36399261/</ref> <ref>https://sourceforge.net/p/flightgear/mailman/message/36399261/</ref><br />
<br />
=== Catalog & Package Manager support ===<br />
if this works with more complex, pre-existing addons such as [[Bombable]], where the file layout is a replica of the old FGData layout, these types of mature addons might be better as SourceForge FlightGear sub-projects rather than being copied into FGAddon. Maybe the config file and nasal script could be used to automate the installation process ?<ref>{{cite web<br />
|url = https://sourceforge.net/p/flightgear/mailman/message/35953179/ <br />
|title = <nowiki> Re: [Flightgear-devel] Simple API for creating FlightGear<br />
addons/plugins </nowiki> <br />
|author = <nowiki> Edward d'Auvergne </nowiki> <br />
|date = Jul 19th, 2017 <br />
|added = Jul 19th, 2017 <br />
|script_version = 0.40 <br />
}}</ref><br />
The [[Bombable]] addon is one of the most popular addons out there, and a large number of aircraft in FGAddon have bombable support, so it is worth not forgetting about. Especially if the addon system one day becomes automated through a [[Catalog metadata|catalog.xml type system]]<ref>{{cite web<br />
|url = https://sourceforge.net/p/flightgear/mailman/message/35953650/ <br />
|title = <nowiki> Re: [Flightgear-devel] Simple API for creating FlightGear<br />
addons/plugins </nowiki> <br />
|author = <nowiki> Edward d'Auvergne </nowiki> <br />
|date = Jul 19th, 2017 <br />
|added = Jul 19th, 2017 <br />
|script_version = 0.40 <br />
}}</ref><br />
<br />
== References ==<br />
{{Appendix}}<br />
<br />
== Related content ==<br />
=== Wiki articles ===<br />
* [[FG Add-on FAQ]]<br />
* [[FlightGear configuration via XML]]<br />
* [[FlightGear configuration via XML#preferences.xml]]<br />
* [[Nasal]]<br />
* [[Property tree]]<br />
* [[Properties persistent between sessions]]<br />
* [[PropertyList XML File]]<br />
<br />
=== Forum topics ===<br />
* {{forum link|t=32561|title=New Feature: Addon - "API"}}<br />
<br />
=== Readme files ===<br />
* {{readme file|add-ons}}<br />
* {{readme file|gui}} - Details on how to add menus and custom dialogs.<br />
<br />
=== Source code ===<br />
==== FGAddon ====<br />
* {{fgaddon source|path=Addons/Skeleton}} - Skeleton addon to be used as a template.<br />
<br />
==== FGData ====<br />
* {{fgdata source|path=Nasal/addons.nas}}<br />
<br />
==== FlightGear ====<br />
* {{flightgear source|path=src/Main/options.cxx}}<br />
* {{flightgear source|path=src/Add-ons/}}<br />
<br />
[[Category:FlightGear addons| ]]</div>Jsbhttps://wiki.flightgear.org/w/index.php?title=Illuminator_addon&diff=134272Illuminator addon2021-12-31T14:53:53Z<p>Jsb: add link to compositor page</p>
<hr />
<div>'''Illuminator''' is a configuration utility for [[Compositor#Lights|compositor lights]] in [[FlightGear]].<br />
<br />
You can use it to fine tune landing lights etc<br />
<br />
== Prerequisites ==<br />
To make use of this addon<br />
* Download the addon at https://sourceforge.net/p/flightgear/fgaddon/HEAD/tree/trunk/Addons/Illuminator/ and add it via the launcher or command line (see [[Addons]] for more information)<br />
* For the time being you need the development version aka "next".<br />
* Add XML light definition to your aircraft. This has to be done manually as the addon can only configure existing lights.<br />
<br />
=== Adding lights to an aircraft ===<br />
See [[Compositor#Lights]] for a sample XML and explanation. The UFO was modified to contain spot lights to illustrate the use of this addon.<br />
== Dialog window ==<br />
[[File:Fgfs-20210205170950 v1.jpg|thumb|Light configuration dialog.]]{{Warning|Addon works only for spot lights|padding=0|margin=left|width=50%}}<br />
You can select one or two lights to configure. If the checkbox for 2nd light is checked, configuration changes will be done simultaneously for both selected lights.<br />
{{Note|To find a light by name, click the props button (top right), go up one level in the property browser and ctrl-click on the "." entry to activate verbose mode.|margin=left|width=50%}}<br />
Use the direction sliders to can be selected inverted (multiplied by -1) for the 2nd light. This is useful if you have a left and a right light and you want to turn them to the center or outside.<br />
<br />
=== Direction ===<br />
'''It is assumed that the XML definition for the light uses the pitch, roll, heading elements for direction. Other variants have not been tested and may lead to unexpected results or even crash.'''<br />
<br />
=== Range and attenuation ===<br />
To make things easy, there are presets (taken from the website referenced on the [[Compositor]] page). Select a predefined range (in meters) by clicking the left or right button. <br />
<br />
The attenuation parameters are set accordingly, but can be modified again. <br />
<br />
== Known issues ==<br />
Configuration works only for spot lights.<br />
[[Category:FlightGear addons]]</div>Jsbhttps://wiki.flightgear.org/w/index.php?title=Compositor&diff=134234Compositor2021-12-26T18:22:22Z<p>Jsb: Add link to Illuminator add-on</p>
<hr />
<div>{{forum|47|Effects & Shaders}}<br />
<br />
{{infobox subsystem<br />
|image = Compositor logo.png<br />
|name = Compositor Framework<br />
|started = 01/2018 (Available since FlightGear 2020.4)<br />
|description = Dynamic rendering pipeline configured via the [[Property tree]] and [[PropertyList XML File|XML]]<br />
|status = Stable (merged and actively maintained)<br />
|developers = Fernando García Liñán <ref>https://sourceforge.net/u/fgarlin/profile/</ref><br />
|changelog = https://sourceforge.net/u/fgarlin/profile/feed.rss<br />
|folders = <br />
* {{simgear file|simgear/scene/viewer}}<br />
* {{flightgear file|src/Viewer}}<br />
* {{fgdata file|Compositor}}<br />
}}<br />
<br />
The '''Compositor''' aims to bring multi-pass rendering to FlightGear. It encapsulates a rendering pipeline and exposes its parameters to a [[Property Tree]] interface. At startup, FlightGear reads the pipeline definition file for each physical viewport defined on the [[Howto:Configure camera view windows|CameraGroup settings]]. If no Compositor file is specified for a physical camera, the one given by the <code>--compositor=</code> startup command will be used. If such startup option is not used either, FlightGear will look for a valid Compositor file in <tt>$FG_ROOT/Compositor/default.xml</tt>.<br />
<br />
The Compositor introduces a new dedicated fgdata directory for new/custom rendering pipelines: {{Fgdata file|Compositor}}.<br />
<br />
== Background ==<br />
<br />
First discussed in 03/2012 during the early [[Rembrandt]] days, Zan (Lauri Peltonen) came up with a set of patches demonstrating how to create an XML-configurable rendering pipeline. Back then, this work was considered to look pretty promising <ref>{{cite web<br />
|url = https://sourceforge.net/p/flightgear/mailman/message/28946515/ <br />
|title = <nowiki> Re: [Flightgear-devel] [Rembrandt] the plan </nowiki> <br />
|author = <nowiki> Mathias Fröhlich </nowiki> <br />
|date = Mar 7th, 2012 <br />
|added = Mar 7th, 2012 <br />
|script_version = 0.36 <br />
}}</ref> and at the time plans were discussed to unify this with the ongoing Rembrandt implementation (no longer maintained).<br />
<br />
Adopting Zan's approach would have meant that efforts like [[Rembrandt]] (deferred rendering) could have been implemented without requiring C++ space modifications, i.e. purely in [[Base package]] space. Rembrandt's developer (FredB) suggested to extend the format to avoid duplicating the stages when you have more than one viewport, i.e. specifying a pipeline as a template, with conditions like in effects, and have the current camera layout refer the pipeline that would be duplicated, resized and positioned for each declared viewport <ref>{{cite web<br />
|url = https://sourceforge.net/p/flightgear/mailman/message/28944773/ <br />
|title = <nowiki> Re: [Flightgear-devel] [Rembrandt] the plan </nowiki> <br />
|author = <nowiki> Frederic Bouvier </nowiki> <br />
|date = Mar 7th, 2012 <br />
|added = Mar 7th, 2012 <br />
|script_version = 0.36 <br />
}}</ref><br />
<br />
Zan's original patches can still be found in his newcameras branches which allow the user to define the rendering pipeline in preferences.xml: {{gitorious source|proj=fg|repo=zans-flightgear|branch=newcameras|text=FlightGear}}, {{gitorious source|proj=fg|repo=zans-simgear|branch=newcameras|text=SimGear}}. At that point, it didn't have everything Rembrandt's pipeline needs, but most likely could be easily enhanced to support those things. Basically, the original version added support for multiple camera passes, texture targets, texture formats, passing textures from one pass to another etc, while preserving the standard rendering line if user wants that. <ref>{{cite web<br />
|url = https://sourceforge.net/p/flightgear/mailman/message/28944733/ <br />
|title = <nowiki> [Flightgear-devel] [Rembrandt] the plan </nowiki> <br />
|author = <nowiki> Lauri Peltonen </nowiki> <br />
|date = Mar 7th, 2012 <br />
|added = Mar 7th, 2012 <br />
|script_version = 0.36 <br />
}}</ref><br />
<br />
Since the early days of Zan's groundwork, providing the (hooks) infrastructure to enable base package developers to prototype, test and develop distinct rendering pipelines without requiring C++ space modifications has been a long-standing idea, especially after the [[Canvas]] system became available in early 2012, which demonstrated how RTT-rendering buffers (FBOs) could be set up, created and manipulated procedurally (i.e. at run-time) using XML, the property tree and [[Nasal]] scripting. <ref>{{forum link|type=search|title=Zan's Rembrandt and Canvas work|keywords=zan+rembrandt+canvas}}</ref><br />
<br />
The new '''Compositor''' is an improved re-implementation of Zan's original work using not just XML, but also [[Property Tree|properties]] and a handful of [[Canvas]] concepts.<br />
<br />
== Features ==<br />
<br />
* Completely independent of other parts of the simulator, i.e. it's part of [[SimGear]] and can be used in a standalone fashion if needed, ala Canvas.<br />
* Although independent, its aim is to be fully compatible with the current rendering framework in FG. This includes the [[Effects]] system, [[Howto:Configure camera view windows|CameraGroup]], [[Rembrandt]], [[ALS]] and [[Canvas]].<br />
* Its functionality overlaps Rembrandt: what can be done with Rembrandt can be done with the Compositor, but not vice versa.<br />
* Fully configurable via an XML interface without compromising performance (ala Effects, using [[PropertyList XML File|PropertyList files]]).<br />
* Flexible, expandable and compatible with modern graphics.<br />
* It doesn't increase the hardware requirements, it expands the hardware range FG can run on. People with integrated GPUs (Intel HD etc) can run a Compositor with a single pass that renders directly to the screen like before, while people with more powerful cards can run a Compositor that implements deferred rendering, for example.<br />
* Static branching support. Every pipeline element can be enabled/disabled at startup via a [[Conditions|<condition> block]].<br />
* The entire rendering pipeline can be reloaded via an fgcommand (<tt>reload-compositor</tt>).<br />
<br />
== How to enable the Compositor ==<br />
<br />
The Compositor is now the default renderer framework in FlightGear since 2020/11/17. It will be included as part of version 2020.4 onwards.<br />
<br />
If you compile FlightGear from source, you can already try the Compositor. Make sure you are pulling the latest version of the 'next' branch. You can also try out the latest nightly build from the [[FlightGear build server]].<br />
<br />
== Notes for aircraft developers ==<br />
<br />
=== Lights ===<br />
<br />
The Compositor introduces a new way of defining lights that is renderer agnostic, so every rendering pipeline will be able to access the lights that have been implemented like this. The resulting light volumes can be visualized for debugging purposes by setting the property <tt>/sim/debug/show-light-volumes</tt> to true.<br />
<br />
[[Project Rembrandt]] light definitions are also read by the Compositor for backwards compatibility reasons. However, it is not recommended to use the old syntax for new/updated projects.<br />
<br />
{|cellpadding=10|<br />
|valign=top style="width: 20%;"|<br />
<syntaxhighlight lang="xml"><br />
<light><br />
<name>my-spotlight</name><br />
<type>spot</type><br />
<position><br />
<x-m>-7.7476</x-m><br />
<y-m>0</y-m><br />
<z-m>-1.7990</z-m><br />
</position><br />
<direction><br />
<x>-1.0</x><br />
<y>0</y><br />
<z>-0.013</z><br />
</direction><br />
<ambient><br />
<r>0.03</r><br />
<g>0.03</g><br />
<b>0.03</b><br />
<a>1</a><br />
</ambient><br />
<diffuse><br />
<r>0.95</r><br />
<g>0.9</g><br />
<b>0.9</b><br />
<a>1</a><br />
</diffuse><br />
<specular><br />
<r>0.95</r><br />
<g>0.9</g><br />
<b>0.9</b><br />
<a>1</a><br />
</specular><br />
<attenuation><br />
<c>1.0</c><br />
<l>0.09</l><br />
<q>0.032</q><br />
</attenuation><br />
<spot-exponent>5</spot-exponent><br />
<spot-cutoff>40</spot-cutoff><br />
<range-m>50</range-m><br />
<dim-factor><br />
<property>controls/lighting/instruments-norm</property><br />
</dim-factor><br />
</light><br />
</syntaxhighlight><br />
|valign=top style="width: 80%;"|<br />
* <tt>'''name'''</tt>. An {{tag|animation}} will be able to reference the light by this name. Most animations will work as expected (rotate, translate, spin etc). Material animations are not supported.<br />
* <tt>'''type'''</tt>. <tt>spot</tt> or <tt>point</tt>.<br />
* <tt>'''position'''</tt>. The position of the light source in model space and in meters.<br />
* <tt>'''direction'''</tt>. Only available in <tt>spot</tt> lights. It indicates the direction of the spotlight. This parameter can be specified in three different ways:<br />
{| class="wikitable" style="border: 1px solid darkgray;"<br />
! scope="col" style="width:33%;" |Direction vector<br />
! scope="col" style="width:33%;" |Look-at point<br />
! scope="col" style="width:33%;" |Rotation angles<br />
|-<br />
| style="padding: 10px" | A vector in model space that specifies the direction. Doesn't have to be normalized.<br />
<syntaxhighlight lang="xml"><br />
<x>-1.0</x><br />
<y>0</y><br />
<z>-0.013</z><br />
</syntaxhighlight><br />
| style="padding: 10px" | The spotlight will calculate its direction by looking at this position from the light position. The point is in model space and in meters.<br />
<syntaxhighlight lang="xml"><br />
<lookat-x-m>-8.031</lookat-x-m><br />
<lookat-y-m>0</lookat-y-m><br />
<lookat-z-m>-2</lookat-z-m><br />
</syntaxhighlight><br />
| style="padding: 10px" | A three angle rotation in degrees that rotates the spotlight around the three axes. A 0 degree angle in all axes makes the spotlight point downwards (negative Z).<br />
<syntaxhighlight lang="xml"><br />
<pitch-deg>90</pitch-deg><br />
<roll-deg>0</roll-deg><br />
<heading-deg>0</heading-deg><br />
</syntaxhighlight><br />
|}<br />
* <tt>'''ambient'''</tt>, <tt>'''diffuse'''</tt> and <tt>'''specular'''</tt>. Four-component vectors that specify the light color.<br />
* <tt>'''attenuation'''</tt>. Three-component vector where <code><c></code> specifies the constant factor, <code><l></code> specifies the linear factor and <code><nowiki><q></nowiki></code> specifies the quadratic factor. These factors are plugged into the OpenGL light attenuation formula [[File:Spotlight_attenuation.png]] where d is the distance of the fragment to the light source. See this [http://wiki.ogre3d.org/tiki-index.php?page=-Point+Light+Attenuation table] for a list of attenuation values based on the range of the light.<br />
* <tt>'''range-m'''</tt>. Maximum range from the light source position in meters. This value will be used by the renderers to determine if a fragment is illuminated by this source. Every fragment outside this range isn't guaranteed to be affected by the light, even if the attenuation factor isn't 0 in that particular fragment.<br />
* <tt>'''spot-cutoff'''</tt>. Only available in <tt>spot</tt> lights. It specifies the maximum spread angle of a light source. Only values in the range 0 90 are accepted. If the angle between the direction of the light and the direction from the light to the fragment being lighted is greater than the spot cutoff angle, it won't be lit.<br />
* <tt>'''spot-exponent'''</tt>. Only available in <tt>spot</tt> lights. Higher spot exponents result in a more focused light source, regardless of the spot cutoff angle.<br />
* <tt>'''dim-factor'''</tt> ('''Optional'''). Controls the dimming of the light source through an [[Expressions|expression]].<br />
* <tt>'''debug-color'''</tt> ('''Optional'''). Sets the color of the debug light volume. By default it's red.<br />
|}<br />
You can edit existing lights in FlightGear with the '''[[Illuminator addon|Illuminator]]''' but you have to define them first e.g. in your aircraft. The Illumintor add-on is ment for fine tuning the light parameters.<br />
<br />
=== Shadows ===<br />
<br />
The shadow mapping algorithm can be customized entirely by the rendering pipeline. This means that each pipeline will have its own requirements when it comes to shadows. Here are some general recommendations:<br />
<br />
* Use the [[Howto:Animate models#Noshadow|<tt>noshadow</tt> animation]] to disable shadows on objects that don't need them. An example would be billboarded lights (light sprites) or really small cockpit elements that don't need shadows and would cause degraded performance.<br />
If the casted aircraft shadow appears blocky then there probably are some objects which need the noshadow animation applied. Most notably are billboarded lights which include [[ALS_technical_notes#ALS procedural lights|ALS procedural lights]].<br />
* Try to mark as many cockpit objects as possible as <tt>interior</tt>.<br />
<syntaxhighlight lang="xml"><br />
<model><br />
<name>interior</name><br />
<usage>interior</usage><br />
<path>Aircraft/JA37/Models/ja37-interior.xml</path> <!-- All the objects that should only be seen when inside the cockpit are in this file --><br />
</model><br />
</syntaxhighlight><br />
* Unlike in Rembrandt, polygons facing the Sun are the ones used to generate the shadow map, so single sided surfaces and non-closed objects should be rendered correctly.<br />
<br />
== Porting and developing Effects/Shaders ==<br />
<br />
Effects can now have different implementations depending on the Compositor pipeline being used. For example, a grass Effect implemented for a high quality pipeline might have much more detail than one that targets low specification machines. Still, they both implement the "look" of grass, so they share the same Effect file (grass.eff).<br />
<br />
The Compositor chooses which implementation of an Effect to render based on the <tt><scheme></tt> of the techniques.<br />
<syntaxhighlight lang="xml"><br />
<technique n="15"><br />
<scheme>test-scheme</scheme><br />
[...]<br />
</technique><br />
</syntaxhighlight><br />
<br />
In this case the technique will be chosen if the Compositor pipeline <tt><scene></tt> pass uses the <tt>test-scheme</tt> Effect scheme. Consequently, porting an Effect to a pipeline will require knowing which Effect schemes it uses and writing a technique for each one. The only exception to this is the Classic pipeline, which uses techniques with no scheme.<br />
<br />
== Creating a custom rendering pipeline ==<br />
<br />
Since the Compositor is completely data-driven, new rendering pipelines can be created by writing a custom XML pipeline definition. This section tries to document most of the available parameters, but the best and most up-to-date resource is the Compositor parsing code in SimGear ({{simgear file|simgear/scene/viewer}}). See existing pipelines in {{fgdata file|Compositor}} for practical examples on how to use these parameters.<br />
<br />
Also keep in mind that every pipeline element can be enabled/disabled through a [[Conditions|<condition> block]]. This allows for dynamic rendering pipelines that have toggleable features. However, the pipeline can't be updated in real-time: the simulator has to be restarted or the Compositor has to be reloaded through the <tt>reload-compositor</tt> [[Fgcommands|fgcommand]].<br />
<br />
=== Buffers ===<br />
<br />
A buffer represents a texture or, more generically, a region of GPU memory.<br />
<br />
{| class="wikitable" style="text-align: center; font-size: 85%; width: auto; table-layout: fixed;<br />
! scope="col" | Parameter Name<br />
! scope="col" | Optional<br />
! scope="col" | Value<br />
! scope="col" | Default Value<br />
! scope="col" | Description<br />
|-<br />
! scope="row"| <tt>name</tt><br />
| {{No}}<br />
| string<br />
|<br />
| Passes will be able to address the buffer by this name<br />
|-<br />
! scope="row"| <tt>type</tt><br />
| {{No}}<br />
| <tt>1d, 2d, 2d-array, 2d-multisample, 3d, rect, cubemap</tt><br />
|<br />
| Any texture type allowed by OpenGL<br />
|-<br />
! scope="row"| <tt>width</tt><br />
| {{No}}<br />
| Any unsigned integer or <tt>screen</tt> to use the physical viewport width. The <code><property></code> tag can also be used to use a property value<br />
|<br />
| Texture width<br />
|-<br />
! scope="row"| <tt>screen-width-scale</tt><br />
| {{Yes}}<br />
| float<br />
| <tt>1.0</tt><br />
| If <tt>screen</tt> was used, this controls the width scaling factor<br />
|-<br />
! scope="row"| <tt>height</tt><br />
| {{No}}<br />
| Any unsigned integer or <tt>screen</tt> to use the physical viewport height. The <code><property></code> tag can also be used to use a property value<br />
|<br />
| Texture height<br />
|-<br />
! scope="row"| <tt>screen-height-scale</tt><br />
| {{Yes}}<br />
| float<br />
| <tt>1.0</tt><br />
| If <tt>screen</tt> was used, this controls the height scaling factor<br />
|-<br />
! scope="row"| <tt>depth</tt><br />
| {{No}}<br />
| Any unsigned integer. The <code><property></code> tag can also be used to use a property value<br />
|<br />
| Texture depth<br />
|-<br />
! scope="row"| <tt>format</tt><br />
| {{Yes}}<br />
| See {{simgear file|simgear/scene/viewer/CompositorBuffer.cxx}} for the latest available values<br />
| <tt>rgba8</tt><br />
| Specifies the texture format. It corresponds to the ''internalformat'', ''format'' and ''type'' arguments of the OpenGL function ''glTexImage2D''<br />
|-<br />
! scope="row"| <tt>min-filter, mag-filter</tt><br />
| {{Yes}}<br />
| <tt>linear, linear-mipmap-linear, linear-mipmap-nearest, nearest, nearest-mipmap-linear, nearest-mipmap-nearest</tt><br />
| <tt>linear</tt><br />
| Change the minification and magnification filtering respectively<br />
|-<br />
! scope="row"| <tt>wrap-s, wrap-t, wrap-r</tt><br />
| {{Yes}}<br />
| <tt>clamp, clamp-to-edge, clamp-to-border, repeat, mirror</tt><br />
| <tt>clamp-to-border</tt><br />
| They change the wrap mode for each coordinate<br />
|-<br />
! scope="row"| <tt>anisotropy</tt><br />
| {{Yes}}<br />
| float<br />
| <tt>1.0</tt><br />
|<br />
|-<br />
! scope="row"| <tt>border-color</tt><br />
| {{Yes}}<br />
| vec4<br />
| <tt>(0.0f, 0.0f, 0.0f, 0.0f)</tt><br />
|<br />
|-<br />
! scope="row"| <tt>shadow-comparison</tt><br />
| {{Yes}}<br />
| bool<br />
| <tt>true</tt><br />
|<br />
|-<br />
! scope="row"| <tt>shadow-texture-mode</tt><br />
| {{Yes}}<br />
| <tt>luminance, intensity, alpha</tt><br />
| <tt>luminance</tt><br />
|<br />
|-<br />
! scope="row"| <tt>shadow-compare-func</tt><br />
| {{Yes}}<br />
| <tt>never, less, equal, lequal, greater, notequal, gequal, always</tt><br />
| <tt>lequal</tt><br />
|<br />
|}<br />
<br />
=== Passes ===<br />
<br />
A pass wraps around an [http://public.vrac.iastate.edu/vancegroup/docs/OpenSceneGraphReferenceDocs-3.0/a00089.html osg::Camera]. Passes all have some common parameters:<br />
<br />
{| class="wikitable" style="text-align: center; font-size: 85%; width: auto; table-layout: fixed;<br />
! scope="col" | Parameter Name<br />
! scope="col" | Optional<br />
! scope="col" | Value<br />
! scope="col" | Default Value<br />
! scope="col" | Description<br />
|-<br />
! scope="row"| <tt>clear-color, clear-accum, clear-depth and clear-stencil</tt><br />
| {{Yes}}<br />
| vec4<br />
| black, black, <tt>1.0</tt>, <tt>0</tt> respectively<br />
| Pass clear colors<br />
|-<br />
! scope="row"| <tt>clear-mask</tt><br />
| {{Yes}}<br />
| <tt>color, stencil, depth, accum</tt><br />
| <tt>color depth</tt><br />
| Pass clear mask<br />
|-<br />
! scope="row"| <tt>effect-scheme</tt><br />
| {{Yes}}<br />
| Valid effect scheme name<br />
| None<br />
| The pass will try to use the specified effect scheme to draw every object.<br />
|}<br />
<br />
Passes can render to a buffer (Render to Texture), to several buffers (Multiple Render Targets) or directly to the framebuffer. This is accomplished by the <code><attachment></code> tag. Possible parameters of an attachment are:<br />
<br />
{| class="wikitable" style="text-align: center; font-size: 85%; width: auto; table-layout: fixed;<br />
! scope="col" | Parameter Name<br />
! scope="col" | Optional<br />
! scope="col" | Value<br />
! scope="col" | Default Value<br />
! scope="col" | Description<br />
|-<br />
! scope="row"| <tt>buffer</tt><br />
| {{No}}<br />
| Valid buffer name<br />
| <br />
| The name of the buffer to output to<br />
|-<br />
! scope="row"| <tt>component</tt><br />
| {{No}}<br />
| <tt>color, color0</tt> to <tt>color15, depth, stencil, depth-stencil</tt><br />
| <br />
| FBO attachment point<br />
|-<br />
! scope="row"| <tt>level</tt><br />
| {{Yes}}<br />
| int<br />
| <tt>0</tt><br />
| Mipmap level of the texture that is attached<br />
|-<br />
! scope="row"| <tt>face</tt><br />
| {{Yes}}<br />
| int<br />
| <tt>0</tt><br />
| Face of cube map texture or z-level of 3d texture<br />
|-<br />
! scope="row"| <tt>mipmap-generation</tt><br />
| {{Yes}}<br />
| bool<br />
| <tt>false</tt><br />
| Whether mipmap generation should be done for texture<br />
|-<br />
! scope="row"| <tt>multisample-samples</tt><br />
| {{Yes}}<br />
| int<br />
| <tt>0</tt><br />
| Multisample anti-aliasing (MSAA) samples<br />
|-<br />
! scope="row"| <tt>multisample-color-samples</tt><br />
| {{Yes}}<br />
| int<br />
| <tt>0</tt><br />
| Multisample anti-aliasing (MSAA) color samples<br />
|}<br />
<br />
Passes can also receive buffers as input and use them in their shaders. This is accomplished by the <code><binding></code> tag, which has the following parameters:<br />
<br />
{| class="wikitable" style="text-align: center; font-size: 85%; width: auto; table-layout: fixed;<br />
! scope="col" | Parameter Name<br />
! scope="col" | Optional<br />
! scope="col" | Value<br />
! scope="col" | Default Value<br />
! scope="col" | Description<br />
|-<br />
! scope="row"| <tt>buffer</tt><br />
| {{No}}<br />
| Valid buffer name<br />
| <br />
| The name of the buffer to bind<br />
|-<br />
! scope="row"| <tt>unit</tt><br />
| {{No}}<br />
| int<br />
| <br />
| The texture unit to place the texture on. Effects will be able to access the buffer on this texture unit<br />
|}<br />
<br />
There are specific pass types, each with their own set of custom parameters.<br />
<br />
==== scene ====<br />
Renders the scene from the point of view given by the CameraGroup.<br />
{| class="wikitable" style="text-align: center; font-size: 85%; width: auto; table-layout: fixed;<br />
! scope="col" | Parameter Name<br />
! scope="col" | Optional<br />
! scope="col" | Value<br />
! scope="col" | Default Value<br />
! scope="col" | Description<br />
|-<br />
! scope="row"| <tt>cull-mask</tt><br />
| {{Yes}}<br />
| A 32 bit number. See {{simgear file|simgear/scene/util/RenderConstants.hxx}} to know which bits enable what<br />
| <tt>0xffffffff</tt><br />
| Specifies the cull mask to be used in the underlying <tt>osg::Camera</tt><br />
|-<br />
! scope="row"| <tt>z-near, z-far</tt><br />
| {{Yes}}<br />
| float<br />
| 0.0 uses the value given by the CameraGroup<br />
| Sets a custom near and far values. Useful for implementing depth partition and limiting the depth range on cubemap passes<br />
|-<br />
! scope="row"| <tt>cubemap-face</tt><br />
| {{Yes}}<br />
| int<br />
| <tt>-1</tt> (don't use cubemap)<br />
| Ignores the given view and projection matrices and uses a custom one that renders the scene as if it was seen from inside a cubemap looking towards the specified face<br />
|-<br />
! scope="row"| <tt>use-shadow-pass</tt><br />
| {{Yes}}<br />
| string<br />
| Empty<br />
| Name of a shadow mapping pass. Exposes shadow mapping related uniforms to the shaders of the current pass<br />
|}<br />
<br />
Scene passes can also use the tag <code><clustered-shading></code> to enable clustered shading (lights). The following parameters are available:<br />
<br />
{| class="wikitable" style="text-align: center; font-size: 85%; width: auto; table-layout: fixed;<br />
! scope="col" | Parameter Name<br />
! scope="col" | Optional<br />
! scope="col" | Value<br />
! scope="col" | Default Value<br />
! scope="col" | Description<br />
|-<br />
! scope="row"| <tt>max-pointlights</tt><br />
| {{Yes}}<br />
| int<br />
| 1024<br />
| Maximum amount of point light sources allowed on the entire scene<br />
|-<br />
! scope="row"| <tt>max-spotlights</tt><br />
| {{Yes}}<br />
| int<br />
| 1024<br />
| Maximum amount of spot light sources allowed on the entire scene<br />
|-<br />
! scope="row"| <tt>max-light-indices</tt><br />
| {{Yes}}<br />
| int<br />
| 256<br />
| Size of the light indices texture. Keep in mind that it is a 2D texture, so the total size will be e.g. 256x256<br />
|-<br />
! scope="row"| <tt>tile-size</tt><br />
| {{Yes}}<br />
| int<br />
| 128<br />
| Size of each clustered shading tile<br />
|-<br />
! scope="row"| <tt>num-threads</tt><br />
| {{Yes}}<br />
| int<br />
| 1<br />
| Number of threads to use during the light culling process. Keep in mind that a high thread count when there aren't many lights will worsen performance due to the thread creation overhead<br />
|-<br />
! scope="row"| <tt>depth-slices</tt><br />
| {{Yes}}<br />
| int<br />
| 1<br />
| Number of slices to partition the view frustum in the Z axis. Higher numbers will cull lights more aggressively, increasing performance if there are many lights further out that don't contribute much to the overall scene's lighting<br />
|}<br />
<br />
==== quad ====<br />
Renders a fullscreen quad with an optional [[Effects|effect]] applied. Useful for screen space shaders (like SSAO, Screen Space Reflections or bloom) and deferred rendering.<br />
{| class="wikitable" style="text-align: center; font-size: 85%; width: auto; table-layout: fixed;<br />
! scope="col" | Parameter Name<br />
! scope="col" | Optional<br />
! scope="col" | Value<br />
! scope="col" | Default Value<br />
! scope="col" | Description<br />
|-<br />
! scope="row"| <tt>geometry</tt><br />
| {{Yes}}<br />
| float values for <code><x>, <y>, <width>, <height>, <scale></code><br />
| <tt>0.0, 0.0, 1.0, 1.0, 1.0</tt> respectively<br />
| Size of the fullscreen quad inside the viewport using normalized coordinates.<br />
|-<br />
! scope="row"| <tt>effect</tt><br />
| {{Yes}}<br />
| Valid Effect file<br />
| None<br />
| This Effect will be applied to the quad geometry<br />
|}<br />
<br />
==== csm ====<br />
Implements Cascaded Shadow Mapping by rendering the scene from a light's point of view (the Sun by default). For now it only supports directional light sources. There has to be one <tt>csm</tt> pass for each cascade.<br />
{| class="wikitable" style="text-align: center; font-size: 85%; width: auto; table-layout: fixed;<br />
! scope="col" | Parameter Name<br />
! scope="col" | Optional<br />
! scope="col" | Value<br />
! scope="col" | Default Value<br />
! scope="col" | Description<br />
|-<br />
! scope="row"| <tt>light-name</tt><br />
| {{Yes}}<br />
| Valid light name that exists in the scene graph<br />
| <tt>FGLightSource</tt> (Sun)<br />
| The name of the <tt>osg::LightSource</tt> to use for this shadow map<br />
|-<br />
! scope="row"| <tt>render-at-night</tt><br />
| {{Yes}}<br />
| bool<br />
| true<br />
| Whether to render the shadows at night or not<br />
|-<br />
! scope="row"| <tt>near-m, far-m</tt><br />
| {{No}}<br />
| float (meters)<br />
|<br />
| They specify the range of the shadow map<br />
|}<br />
<br />
== References ==<br />
{{Appendix}}<br />
<br />
== Related content ==<br />
=== Wiki articles ===<br />
* [[Canvas View Camera Element]]<br />
* [[CompositeViewer Support]]<br />
* [[Uniform Buffer Objects]]<br />
* [[FlightGear CIGI Support (Common Image Generator Interface)]]<br />
<br />
=== Forum topics ===<br />
* {{forum link|t=36269|text=The Compositor}}<br />
* {{forum link|t=35095|text=Clustered Forward Rendering}} (12/2018)<br />
* {{forum link|t=33045|text=Getting started with RTT}}<br />
<br />
[[Category:Compositor]]</div>Jsbhttps://wiki.flightgear.org/w/index.php?title=Nasal_library&diff=133054Nasal library2021-09-05T11:27:08Z<p>Jsb: /* isscalar() */</p>
<hr />
<div>{{Nasal Navigation|nocat=1}}<br />
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:<br />
* {{flightgear file|src/Scripting}}<br />
* {{simgear file|simgear/nasal}}<br />
<br />
All these functions and variables are in the global namespace, that is, they are directly accessible (e.g., one can call <syntaxhighlight lang="nasal" inline>magvar()</syntaxhighlight> instead of <syntaxhighlight lang="nasal" inline>namespace.magvar()</syntaxhighlight>). However, if a namespace must be used, <code>globals</code> is the correct namespace, but using it is not recommended. For a more complete explanation, see [[Nasal Namespaces in-depth]].<br />
<br />
{{tip|Copy & paste the examples into your [[Nasal Console]] and execute them to see what they do.|width=70%}}<br />
<br />
== Core library functions ==<br />
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:<br />
* http://plausible.org/nasal/lib.html ([http://web.archive.org/web/20101010094553/http://plausible.org/nasal/lib.html archive])<br />
* {{simgear file|simgear/nasal/lib.c}} ([http://sourceforge.net/p/flightgear/simgear/ci/next/log/?path=/simgear/nasal/lib.c history])<br />
<br />
=== append() ===<br />
{{Nasal doc<br />
|syntax = append(vector, element[, element[, ...]]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=42|t=Source}}<br />
|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.<br />
|param1 = vector<br />
|param1text = The vector to which the arguments will be appended.<br />
|param2 = element<br />
|param2text = An element to be added to the vector.<br />
|example1 = <br />
var vector = [1, 2, 3]; # Initialize the vector<br />
append(vector, 4); # Append the number 4 to the end of the vector<br />
debug.dump(vector); # Print the contents of the vector<br />
|example2 = <br />
var vector = [1, 2, 3]; # Initialize the vector<br />
append(vector, 4, 5, 6); # Append the numbers 4, 5, and 6 to the end of the vector<br />
debug.dump(vector); # Print the contents of the vector<br />
}}<br />
<br />
=== bind() ===<br />
{{Nasal doc<br />
|syntax = bind(function, locals[, outer_scope]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=502|t=Source}}<br />
|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.<br />
|param1 = function<br />
|param1text = Function to evaluate.<br />
|param2 = locals<br />
|param2text = Hash containing values that will become the namespace (first closure) for the function.<br />
|param3 = outer_scope<br />
|param3text = Optional function which is bound to the next closure. This can be bound to yet another, making a linked list.<br />
|example1 = # This is a namespace/hash with a single member, named "key," which is initialized to 12 <br />
var Namespace = {<br />
key: 12<br />
};<br />
<br />
# This is different namespace/hash containing a function<br />
# dividing a variable "key" (which is unavailable/nil in this namespace) by 2<br />
var AnotherNamespace = {<br />
ret: func {<br />
key /= 2;<br />
}<br />
};<br />
<br />
# To see that key is not available, try to call AnotherNamespace.ret() first<br />
call(AnotherNamespace.ret, [], nil, nil, var errors = []);<br />
if(size(errors)){<br />
print("Key could not be divided/resolved!");<br />
debug.printerror(errors);<br />
}<br />
<br />
# Associate the AnotherNamespace.ret() function with the first namespace<br />
# so that "key" is now available<br />
var function = bind(AnotherNamespace.ret, Namespace);<br />
<br />
# Invoke the new function<br />
function();<br />
<br />
# Print out the value of Namespace.key<br />
# It was changed to 12 from 6 by AnotherNamespace.ret()<br />
print(Namespace.key);<br />
}}<br />
<br />
=== call() ===<br />
{{Nasal doc<br />
|syntax = call(func[, args[, me[, locals[, error]]]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=247|t=Source}}<br />
|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. <br />
|param1 = func<br />
|param1text = Function to execute.<br />
|param2 = args<br />
|param2text = Vector containing arguments to give to the called function.<br />
|param3 = me<br />
|param3text = <code>'''me'''</code> reference for the function call (i.e., for method calls). If given, this will override any <code>'''me'''</code> value existing in the namespace (locals argument).<br />
|param4 = locals<br />
|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.<br />
|param5 = error<br />
|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}}.<br />
|example1 =<br />
# prints "Called from call()"<br />
call(func {<br />
print("Called from call()");<br />
});<br />
|example2 =<br />
# prints "a = 1 : b = 2<br />
call(func(a, b){<br />
print("a = ", a, " : b = ", b);<br />
},<br />
[1, 2]<br />
);<br />
|example3 =<br />
var Hash = {<br />
new: func {<br />
var m = { parents: [Hash] };<br />
<br />
m.el1 = "string1";<br />
m.el2 = "string2";<br />
<br />
return m;<br />
}<br />
};<br />
<br />
# prints "me.el1 = string1", then "me.el2 = string2" on the next line<br />
call(func(a, b){ <br />
print("me.el", a, " = ", me["el" ~ a]); <br />
print("me.el", b, " = ", me["el" ~ b]);<br />
},<br />
[1, 2],<br />
Hash.new()<br />
);<br />
|example4 =<br />
# prints the value of math.pi<br />
call(func {<br />
print(pi);<br />
}, nil, nil, <br />
math<br />
);<br />
|example5 =<br />
call(func {<br />
print(math.ip); # math.ip doesn't exist<br />
}, nil, nil, nil,<br />
var errs = []<br />
);<br />
debug.printerror(errs); # The error is caught and printed using debug.printerror()<br />
}}<br />
<br />
=== caller() ===<br />
{{Nasal doc<br />
|syntax = caller([level]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=404|t=Source}}<br />
|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.<br />
<br />
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. <br />
|param1 = level<br />
|param1text = Optional integer specifying the stack level to return a result from. Defaults to 1 (i.e. the caller of the currently executing function).<br />
|example1 =<br />
var myFunction = func(a, b){<br />
debug.dump(caller(0)[0]); # prints a hash of local variables, including arguments a and b<br />
return 2 * 2;<br />
};<br />
<br />
print("2 x 2 = ", myFunction(2, 2));<br />
|example2 =<br />
var get_arg_value = func(){<br />
print("Argument to myFunc = ", caller(1)[0]['a']); # print the value of myFunc's single argument, using caller()<br />
};<br />
<br />
var myFunc = func(a){<br />
get_arg_value();<br />
};<br />
<br />
myFunc(3);<br />
|example3text = This is a real example taken from {{fgdata file|Nasal/canvas/MapStructure.nas}}. Function <code>r()</code> (above the TODOs) returns a hash with the key/value pairs as per its arguments. For example, something like this is returned: <code>{ name: "<name>", vis: 1, zindex: nil }</code>.<br />
|example3 =<br />
var MapStructure_selfTest = func() {<br />
var temp = {};<br />
temp.dlg = canvas.Window.new([600,400],"dialog");<br />
temp.canvas = temp.dlg.createCanvas().setColorBackground(1,1,1,0.5);<br />
temp.root = temp.canvas.createGroup();<br />
var TestMap = temp.root.createChild("map");<br />
TestMap.setController("Aircraft position");<br />
TestMap.setRange(25); # TODO: implement zooming/panning via mouse/wheel here, for lack of buttons :-/<br />
TestMap.setTranslation(<br />
temp.canvas.get("view[0]")/2,<br />
temp.canvas.get("view[1]")/2<br />
);<br />
var r = func(name,vis=1,zindex=nil) return caller(0)[0];<br />
# TODO: we'll need some z-indexing here, right now it's just random<br />
# TODO: use foreach/keys to show all layers in this case by traversing SymbolLayer.registry direclty ?<br />
# maybe encode implicit z-indexing for each lcontroller ctor call ? - i.e. preferred above/below order ?<br />
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'), ] ) <br />
TestMap.addLayer(factory: canvas.SymbolLayer, type_arg: type.name,<br />
visible: type.vis, priority: type.zindex,<br />
);<br />
}; # MapStructure_selfTest<br />
}}<br />
<br />
=== chr() ===<br />
{{Nasal doc<br />
|syntax = chr(code);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=175|t=Source}}<br />
|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 (<code>"string"</code>) supports control chracters. Strings in single quotes (<code>'string'</code>) do not.}}<br />
{{{!}} class="wikitable"<br />
! Code !! Name !! Equivalent to<br />
{{!-}}<br />
{{!}} 10 {{!!}} {{Wikipedia|Newline}} {{!!}} <code>\n</code><br />
{{!-}}<br />
{{!}} 9 {{!!}} {{Wikipedia|Tab key#Tab characters|Horizontal tab}} {{!!}} <code>\t</code><br />
{{!-}}<br />
{{!}} 13 {{!!}} {{Wikipedia|Carriage return}} {{!!}} <code>\r</code><br />
{{!}}}<br />
|param1 = code<br />
|param1text = Integer character code for the desired glyph.<br />
|example1 = print("Code 65 = ", chr(65)); # prints "Code 65 = A"<br />
|example2text = This example displays all of the characters in a list, in the format <code>Code '''n''' = >'''char'''<</code>, '''n''' being the index, and '''char''' being the character.<br />
|example2 =<br />
for(var i = 0; i <= 255; i += 1){<br />
print("Code ", i, " = >", chr(i), "<");<br />
}<br />
}}<br />
<br />
=== closure() ===<br />
{{Nasal doc<br />
|syntax = closure(func[, level]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=421|t=Source}}<br />
|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'''. <br />
|param1 = func<br />
|param1text = Function to evaluate.<br />
|param2 = level<br />
|param2text = Optional integer specifying the scope level. Defaults to 0 (the namespace of '''func''').<br />
|example1 =<br />
var get_math_e = func {<br />
return e; # return the value of math.e<br />
}<br />
<br />
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<br />
debug.dump(closure(myFunction)); # print the namespace of get_math_e<br />
<br />
print(myFunction());<br />
}}<br />
<br />
=== cmp() ===<br />
{{Nasal doc<br />
|syntax = cmp(a, b);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=112|t=Source}}<br />
|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'''. <br />
|param1 = a<br />
|param1text = First string argument for comparison.<br />
|param2 = b<br />
|param2text = Second string argument for comparison.<br />
|example1 = print(cmp("1", "two")); # prints -1<br />
|example2 = print(cmp("string", "string")); # prints 0<br />
|example3 = print(cmp("one", "2")); # prints 1<br />
|example4 = print(cmp("string1", "string2")); # prints -1<br />
}}<br />
<br />
=== compile() ===<br />
{{Nasal doc<br />
|syntax = compile(code[, filename]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=220|t=Source}}<br />
|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'''.<br />
|param1 = code<br />
|param1text = String containing Nasal code to be compiled.<br />
|param2 = filename<br />
|param2text = Optional string used for error messages/logging. Defaults to <code><compile></code><br />
|example1 = <br />
var myCode = 'print("hello");';<br />
var helloFunc = compile(myCode, "myCode");<br />
helloFunc();<br />
|example2text = <code>compile</code> 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 <tt>''[[$FG_ROOT]]/gui/dialogs/test.xml''</tt>.<br />
<syntaxhighlight lang="xml"><br />
<?xml version="1.0"?><br />
<br />
<PropertyList><br />
<br />
<nasal><![CDATA[<br />
print("You have FlightGear v", getprop("/sim/version/flightgear"));<br />
]]></nasal><br />
<br />
</PropertyList><br />
</syntaxhighlight><br />
Now, start FlightGear and execute this code in the [[Nasal Console]].<br />
|example2 =<br />
# Build the path<br />
var FGRoot = getprop("/sim/fg-root");<br />
var filename = "/gui/dialogs/test.xml";<br />
var path = FGRoot ~ filename;<br />
<br />
var blob = io.read_properties(path);<br />
var script = blob.getValues().nasal; # Get the nasal string<br />
<br />
# Compile the script. We're passing the filename here for better runtime diagnostics <br />
var code = call(func {<br />
compile(script, filename);<br />
}, nil, nil, var compilation_errors = []);<br />
<br />
if(size(compilation_errors)){<br />
die("Error compiling code in: " ~ filename);<br />
}<br />
<br />
# Invoke the compiled script, equivalent to code(); <br />
# We're using call() here to detect errors:<br />
call(code, [], nil, nil, var runtime_errors = []);<br />
<br />
if(size(runtime_errors)){<br />
die("Error calling code compiled loaded from: " ~ filename);<br />
}<br />
}}<br />
<br />
=== contains() ===<br />
{{Nasal doc<br />
|syntax = contains(hash, key);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=184|t=Source}}<br />
|text = Returns 1 (True) if the hash contains the specified key, or 0 (False) if not.<br />
|param1 = hash<br />
|param1text = The hash to search in.<br />
|param2 = key<br />
|param2text = The scalar to be searched for, contained as a key in the hash.<br />
|example1 =<br />
# Initialize a hash<br />
var hash = {<br />
element: "value"<br />
};<br />
print(contains(hash, "element") ? "Yes" : "No"); # This will print "Yes"<br />
|example2 =<br />
# Initialize a hash<br />
var hash = {<br />
element: "value"<br />
};<br />
print(contains(hash, "element2") ? "Yes" : "No"); # This will print "No"<br />
}}<br />
<br />
=== delete() ===<br />
{{Nasal doc<br />
|syntax = delete(hash, key);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=83|t=Source}}<br />
|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 <code>'''nil'''</code> 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.<br />
|param1 = hash<br />
|param1text = The hash from which to delete the key.<br />
|param2 = key<br />
|param2text = The scalar to be deleted, contained as a key in the hash.<br />
|example1 =<br />
# Initialize the hash<br />
var hash = {<br />
element1: "value1",<br />
element2: "value2"<br />
};<br />
delete(hash, "element1"); # Delete element1<br />
debug.dump(hash); # prints the hash, which is now minus element1<br />
}}<br />
<br />
=== die() ===<br />
{{Nasal doc<br />
|syntax = die(error);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=288|t=Source}}<br />
|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()}}.<br />
|param1 = error<br />
|param1text = String describing the error.<br />
:{{inote|This parameter is technically optional, but it is highly recommended to use it.}}<br />
|example1 = <br />
print("Will print");<br />
die("Don't go any further!"); <br />
print("Won't print"); # Will not be printed because die() stops the process<br />
}}<br />
<br />
=== find() ===<br />
{{Nasal doc<br />
|syntax = find(needle, haystack);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=450|t=Source}}<br />
|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.<br />
|param1 = needle<br />
|param1text = String to search for.<br />
|param2 = haystack<br />
|param2text = String to search in.<br />
|example1 = print(find("c", "abcdef")); # prints 2<br />
|example2 = print(find("x", "abcdef")); # prints -1<br />
|example3 = print(find("cd", "abcdef")); # prints 2<br />
}}<br />
<br />
=== ghosttype() ===<br />
{{Nasal doc<br />
|syntax = ghosttype(ghost);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=207|t=Source}}<br />
|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++ <code>naGhostType</code> 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.<br />
|param1 = ghost<br />
|param1text = Ghost to return a description for.<br />
|example1 = print(ghosttype(airportinfo())); # prints "airport"<br />
}}<br />
<br />
=== id() ===<br />
{{Nasal doc<br />
|syntax = id(object);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=570|t=Source}}<br />
|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 <code>'''<type>''':'''<id>'''</code>, where '''<type>''' is the type of object, and '''<id>''' is the ID.<br />
|param1 = object<br />
|param1text = Can be either of a string, a vector, a hash, a code, a function, or a ghost.<br />
|example1 = print(id("A")); # prints "str:000000001624A590"<br />
}}<br />
<br />
=== int() ===<br />
{{Nasal doc<br />
|syntax = int(number);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=90|t=Source}}<br />
|text = Returns the integer part of the numeric value of the single argument, or <code>'''nil'''</code> if none exists.<br />
|param1 = number<br />
|param1text = Number or string with just a number in it to return an integer from.<br />
|example1 = print(int(23)); # prints "23"<br />
|example2 = print(int(23.123)); # prints "23"<br />
|example3 = debug.dump(int("string")); # prints "nil"<br />
}}<br />
<br />
=== keys() ===<br />
{{Nasal doc<br />
|syntax = keys(hash);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=33|t=Source}}<br />
|text = Returns a vector containing the list of keys found in the single hash argument. <br />
|param1 = hash<br />
|param1text = The hash to return the keys from.<br />
|example1 = <br />
# Initialize a hash<br />
var hash = {<br />
element1: "value",<br />
element2: "value"<br />
};<br />
debug.dump(keys(hash)); # print the vector<br />
}}<br />
<br />
=== left() ===<br />
{{Nasal doc<br />
|syntax = left(string, length);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=149|t=Source}}<br />
|version = 2.12<br />
|commit = {{simgear commit|bd7163|t=commit}}<br />
|text = Returns a substring of '''string''', starting from the left.<br />
|param1 = string<br />
|param1text = String to return part of.<br />
|param2 = length<br />
|param2text = Integer specifying the length of the substring to return.<br />
|example1 = print(left("string", 2)); # prints "st"<br />
}}<br />
<br />
=== num() ===<br />
{{Nasal doc<br />
|syntax = num(number);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=102|t=Source}}<br />
|text = Returns the numerical value of the single string argument, or <code>'''nil'''</code> if none exists. <br />
|param1 = number<br />
|param1text = String with just a number in it to return a number from.<br />
|example1 = print(num("23")); # prints "23"<br />
|example2 = print(num("23.123")); # prints "23.123"<br />
|example3 = debug.dump(num("string")); # prints "nil"<br />
}}<br />
<br />
=== pop() ===<br />
{{Nasal doc<br />
|syntax = pop(vector);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=50|t=Source}}<br />
|text = Removes and returns the last element of the single vector argument, or <code>'''nil'''</code> if the vector is empty. <br />
|param1 = vector<br />
|param1text = Vector to remove an element from.<br />
|example1 = <br />
var vector = [1, 2, 3];<br />
pop(vector);<br />
debug.dump(vector); # prints "[1, 2]"<br />
|example2 = <br />
var vector = [1, 2, 3];<br />
debug.dump(pop(vector)); # prints "3"<br />
|example3 = <br />
var vector = [];<br />
debug.dump(pop(vector)); # prints "nil"<br />
}}<br />
<br />
=== right() ===<br />
{{Nasal doc<br />
|syntax = right(string, length);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=161|t=Source}}<br />
|version = 2.12<br />
|commit = {{simgear commit|bd7163|t=commit}}<br />
|text = Returns a substring of '''string''', starting from the right.<br />
|param1 = string<br />
|param1text = String to return part of.<br />
|param2 = length<br />
|param2text = Integer specifying the length of the substring to return.<br />
|example1 = print(right("string", 2)); # prints "ng"<br />
}}<br />
<br />
=== setsize() ===<br />
{{Nasal doc<br />
|syntax = setsize(vector, size);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=56|t=Source}}<br />
|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 <code>'''nil'''</code> entries. Returns the vector operated upon. <br />
|param1 = vector<br />
|param1text = The vector to be operated on.<br />
|param2 = size<br />
|param2text = The desired size of the vector in number of entries.<br />
|example1 = <br />
var vector = [1, 2, 3]; # Initialize a vector<br />
setsize(vector, 4);<br />
debug.dump(vector); # print the vector<br />
|example2 = <br />
var vector = [1, 2, 3]; # Initialize a vector<br />
setsize(vector, 2);<br />
debug.dump(vector); # print the vector<br />
}}<br />
<br />
=== size() ===<br />
{{Nasal doc<br />
|syntax = size(object);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=23|t=Source}}<br />
|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 <code>'''nil'''</code> or a number, this error will be thrown: <code>object has no size()</code>.<br />
|param1 = object<br />
|param1text = Object to find the size of. Must be a string, a vector or a hash.<br />
|example1 = <br />
var string = "string";<br />
print(size(string)); # prints "6"<br />
|example2 =<br />
var vector = [1, 2, 3];<br />
print(size(vector)); # prints "3"<br />
|example3 =<br />
var hash = {<br />
element1: "value1",<br />
element2: "value2",<br />
element3: "value3"<br />
};<br />
print(size(hash)); # prints "3"<br />
}}<br />
<br />
=== sort() ===<br />
{{Nasal doc<br />
|syntax = sort(vector, function);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=542|t=Source}}<br />
|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/}}, <code>sort()</code> 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.<br />
|param1 = vector<br />
|param1text = Input vector to sort.<br />
|param2 = function<br />
|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.<br />
{{{!}} class="wikitable"<br />
! Return value !! Meaning<br />
{{!-}}<br />
{{!}} less than 0 {{!!}} first argument should go before second argument<br />
{{!-}}<br />
{{!}} 0 {{!!}} first argument equals second argument<br />
{{!-}}<br />
{{!}} greater than 0 {{!!}} first argument should go after second argument<br />
{{!}}}<br />
<br />
|example1text = This example sorts elements from smallest to greatest.<br />
|example1 = <br />
var sort_rules = func(a, b){<br />
if(a < b){<br />
return -1; # A should before b in the returned vector<br />
}elsif(a == b){<br />
return 0; # A is equivalent to b <br />
}else{<br />
return 1; # A should after b in the returned vector<br />
}<br />
}<br />
debug.dump(sort([3, 2, 5, 6, 4, 1], sort_rules)); # prints "[1, 2, 3, 4, 5, 6]"<br />
|example2text = This example sorts elements from greatest to smallest.<br />
|example2 = <br />
# Outputs the elements in reverse order (greatest to smallest)<br />
var sort_rules = func(a, b){<br />
if(a < b){<br />
return 1; # -1 in the above example<br />
}elsif(a == b){<br />
return 0;<br />
}else{<br />
return -1; # 1 in the above example<br />
}<br />
}<br />
debug.dump(sort([3, 2, 5, 6, 4, 1], sort_rules)); # prints "[6, 5, 4, 3, 2, 1]"<br />
|example3text = This example sorts a vector of strings (runways for example) from smallest to greatest.<br />
|example3 = <br />
var runways = ["09R","27R","26L","09L","15"];<br />
var rwy = sort(runways,func(a,b) cmp(a,b));<br />
debug.dump(rwy); # prints ['09L','09R','15','26L','27R']<br />
}}<br />
<br />
=== split() ===<br />
{{Nasal doc<br />
|syntax = split(delimiter, string);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=460|t=Source}}<br />
|text = Splits the input string into a vector of substrings bounded by occurrences of the delimiter substring.<br />
|param1 = delimiter<br />
|param1text = String that will split the substrings in the returned vector.<br />
|param2 = string<br />
|param2text = String to split up.<br />
|example1 = debug.dump(split("cd", "abcdef")); # prints "['ab', 'ef']"<br />
|example2 = debug.dump(split(".", "3.2.0")); # prints "[3, 2, 0]"<br />
|example3 = debug.dump(split("/", "path/to/file")); # prints "['path', 'to', 'file']"<br />
}}<br />
<br />
=== sprintf() ===<br />
{{Nasal doc<br />
|syntax = sprintf(format[, arg[, arg, [...]]]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=355|t=Source}}<br />
|text = Creates and returns a string formatted using ANSI C {{func link|vsnprintf()|link=http://en.cppreference.com/w/c/io/vfprintf}} <ref><br />
{{Cite web<br />
|url = http://sourceforge.net/p/flightgear/simgear/ci/next/tree/simgear/nasal/lib.c#l308<br />
|title = fgdata/simgear/simgear/nasal/lib.c, line 308<br />
|accessdate = October 2015<br />
}}<br />
</ref>. Below is a table of supported format specifiers.<br />
{{{!}} class="wikitable" width="75%"<br />
{{!}}+ %[flags][width][.precision]specifier<br />
! colspan="2" {{!}} Flags<br />
{{!-}}<br />
! Flag !! Output<br />
{{!-}}<br />
{{!}} <code>+</code> {{!!}} 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.<br />
{{!-}}<br />
{{!}} ''space'' {{!!}} Prefixes non-signed numbers with a space.<br />
{{!-}}<br />
{{!}} <code>-</code> {{!!}} Left-align the output of this placeholder (the default is to right-align the output) when the width option is specified.<br />
{{!-}}<br />
{{!}} <code>0</code> {{!!}} Use 0 instead of spaces to pad a field when the width option is specified.<br />
{{!-}}<br />
{{!}} <code>#</code> {{!!}} Used with <code>o</code>, <code>x</code> or <code>X</code> specifiers the value is preceded with <tt>0</tt>, <tt>0x</tt> or <tt>0X</tt> respectively for values different than zero. Used with <code>e</code>, <code>E</code> and <code>f</code>, 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 <code>g</code> or <code>G</code> the result is the same as with <code>e</code> or <code>E</code> but trailing zeros are not removed.<br />
{{!-}}<br />
! colspan="2" {{!}} Width<br />
{{!-}}<br />
{{!}} colspan="2" {{!}} Integer specifying the minimum number of characters to be returned. This includes the decimal point and decimal fraction as well as + or - signs.<br />
{{!-}}<br />
! colspan="2" {{!}} Precision<br />
{{!-}}<br />
{{!}} colspan="2" {{!}} Integer preceded by a dot specifying the number of decimal places to be written.<br />
{{!-}}<br />
! colspan="2" {{!}} Specifiers<br />
{{!-}}<br />
! Specifier !! Output<br />
{{!-}}<br />
{{!}} <code>d</code>, <code>i</code> {{!!}} Signed decimal number.<br />
{{!-}}<br />
{{!}} <code>s</code> {{!!}} A string<br />
{{!-}}<br />
{{!}} <code>%</code> {{!!}} Percent (%) character.<br />
{{!-}}<br />
{{!}} <code>c</code> {{!!}} 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.<br />
{{!-}}<br />
{{!}} <code>o</code> {{!!}} Unsigned integer as an octal number.<br />
{{!-}}<br />
{{!}} <code>u</code> {{!!}} Unsigned decimal integer.<br />
{{!-}}<br />
{{!}} <code>x</code>, <code>X</code> {{!!}} Unsigned integer as a hexadecimal number. If <code>x</code> is used, any letters in the number are lowercase, while <code>X</code> gives uppercase.<br />
{{!-}}<br />
{{!}} <code>e</code>, <code>E</code> {{!!}} Double value in scientific notation (i.e., ''[-]ddd.ddd'''e'''[+/-]ddd''), with an exponent being denoted by <tt>e</tt> or <tt>E</tt> depending on whether an upper or lowercase is used respectively.<br />
{{!-}}<br />
{{!}} <code>f</code> {{!!}} Floating-point number, in fixed decimal notation, by default with 6 decimal places.<br />
{{!-}}<br />
{{!}} <code>F</code> {{!!}} Appears to be available<ref><br />
{{Cite web<br />
|url = http://sourceforge.net/p/flightgear/simgear/ci/next/tree/simgear/nasal/lib.c#l389<br />
|title = fgdata/simgear/simgear/nasal/lib.c, line 389<br />
|accessdate = October 2015<br />
}}<br />
</ref>, but doesn't work.<br />
{{!-}}<br />
{{!}} <code>g</code>, <code>G</code> {{!!}} Double in either normal or exponential notation, whichever is more appropriate for its magnitude. <code>g</code> uses lower-case letters, <code>G</code> 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.<br />
{{!}}}<br />
<br />
|param1 = format<br />
|param1text = String specifying the format. Can be used with or without a format specifiers. See below for examples.<br />
|param2 = arg<br />
|param2text = Argument specifying a value to replace a format placeholder (such as <code>%d</code>) in the format string. Not required if there are no format specifiers.<br />
<br />
|example1 = print(sprintf("%i", 54)); # prints "54"<br />
|example2 = print(sprintf("Pi = %+.10f", math.pi)); # prints "Pi = +3.1415926536"<br />
|example3 = <br />
print(sprintf("%6d", 23)); # prints " 23"<br />
print(sprintf("%06d", 23)); # prints "000023"<br />
|example4 =<br />
var FGVer = getprop("/sim/version/flightgear");<br />
print(sprintf("You have FlightGear v%s", FGVer)); # prints "You have FlightGear v<your version>"<br />
|example5 = <br />
print(sprintf("Hexadecimal 100000 = %X", 100000)); # prints "Hexadecimal 100000 = 186A0"<br />
print(sprintf("Hexadecimal 100000 = %x", 100000)); # prints "Hexadecimal 100000 = 186a0"<br />
|example6 = print(sprintf("Code 65 is %c", 65)); # prints "Code 65 is A"<br />
|example7 = <br />
print(sprintf("%e", 54)); # prints "5.400000e+001"<br />
print(sprintf("%E", 54)); # prints "5.400000E+001"<br />
|example8 = print(sprintf("%o", 54)); # prints "66"<br />
|example9 = print(sprintf("50%% of 100 is %i", 100 / 2)); # prints "50% of 100 is 50"<br />
|example10 =<br />
print(sprintf("%.2f", 1.4)); #prints "1.40"<br />
print(sprintf("%.1f", 1.4)); #prints "1.4"<br />
print(sprintf("% 4.1f", 1.4)); #prints " 1.4"<br />
print(sprintf("%04.1f", 1.4)); #prints "01.4"<br />
print(sprintf("% 6.1f", 1.4)); #prints " 1.4"<br />
print(sprintf("%06.1f", 1.4)); #prints "0001.4"<br />
}}<br />
<br />
=== streq() ===<br />
{{Nasal doc<br />
|syntax = streq(a, b);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=129|t=Source}}<br />
|text = Tests the string values of the two arguments for equality. This function is needed because the <code>'''=='''</code> 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.}}<br />
|param1 = a<br />
|param1text = First argument for testing equality.<br />
|param2 = b<br />
|param2text = Second argument for testing equality.<br />
|example1 = print(streq("0", "0")); # prints "1" (True)<br />
|example2 = <br />
print(0 == 0.0); # prints "1" (True)<br />
print(streq("0", "0.0")); # prints "0" (False)<br />
}}<br />
<br />
=== substr() ===<br />
{{Nasal doc<br />
|syntax = substr(string, start [, length]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=129|t=Source}}<br />
|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).<br />
|param1 = string<br />
|param1text = String to return a substring from.<br />
|param2 = start<br />
|param2text = Integer specifying the start of a substring. Negative values specify a position from the end of the string.<br />
|param3 = length<br />
|param3text = Optional argument specifying the length of the substring. Defaults to the end of the string.<br />
|example1 = print(substr("abcde", 1, 3)); # prints "bcd"<br />
|example2 = print(substr("abcde", 1)); # prints "bcde"<br />
|example3 = print(substr("abcde", 2, 1)); # prints "c"<br />
|example4 = print(substr("abcde", -2)); # prints "de"<br />
|example5 = print(substr("abcde", -3, 2)); # prints "cd"<br />
}}<br />
<br />
=== subvec() ===<br />
{{Nasal doc<br />
|syntax = subvec(vector, start[, length]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=63|t=Source}}<br />
|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). <br />
|param1 = vector<br />
|param1text = The vector to take the sub-vector from.<br />
|param2 = start<br />
|param2text = The starting point of the sub-vector within the given vector.<br />
|param3 = length<br />
|param3text = Optional argument specifying the length of the sub-vector, from the starting point.<br />
'''Notes:'''<br />
* Omitting the ''vector'' and ''start'' arguments is not an error (possibly it should be) but the return value is ''nil''.<br />
* A negative ''start'' argument ''is'' an error. This seems wrong. Perhaps the language designer could comment.<br />
* A value of ''start'' greater than ''size(vector)'' causes an error. A value equal to ''size(vector)'' returns an empty vector.<br />
* 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.<br />
|example1 = <br />
var vector = [1, 2, 3];<br />
debug.dump(subvec(vector, 0)); # prints "[1, 2, 3]"<br />
|example2 = <br />
var vector = [1, 2, 3];<br />
debug.dump(subvec(vector, 1)); # prints "[2, 3]"<br />
|example3 = <br />
var vector = [1, 2, 3];<br />
debug.dump(subvec(vector, 1, 1)); # prints "[2]"<br />
}}<br />
<br />
=== typeof() ===<br />
{{Nasal doc<br />
|syntax = typeof(object);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=193|t=Source}}<br />
|text = Returns a string indicating the whether the object is <code>'''nil'''</code>, a scalar (number or string), a vector, a hash, a function, or a ghost.<br />
|param1 = object<br />
|param1text = Object to return the type of.<br />
|example1 = <br />
var object = nil;<br />
print(typeof(object)); # prints "nil"<br />
|example2 = <br />
var object = "Hello world!";<br />
print(typeof(object)); # prints "scalar"<br />
|example3 = <br />
var object = math.pi;<br />
print(typeof(object)); # prints "scalar"<br />
|example4 = <br />
var object = [1, 2, 3];<br />
print(typeof(object)); # prints "vector"<br />
|example5 = <br />
var object = {};<br />
print(typeof(object)); # prints "hash"<br />
|example6 = <br />
var object = func {};<br />
print(typeof(object)); # prints "func"<br />
|example7 =<br />
var object = airportinfo();<br />
print(typeof(object)); # prints "ghost"<br />
}}<br />
<br />
<!-- == Extension modules ==<br />
=== thread ===<br />
{{WIP}}<br />
<br />
{{Nasal doc<br />
|syntax = thread.newthread(func);<br />
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}<br />
|text = <br />
|example1text = start a new worker thread<br />
|example1 = thread.newthread( func() {} );<br />
}}<br />
<br />
{{Nasal doc<br />
|syntax = thread.newlock();<br />
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}<br />
|text = <br />
|example1text = create a new lock<br />
|example1 = var lock = thread.newlock()<br />
}}<br />
<br />
{{Nasal doc<br />
|syntax = thread.lock();<br />
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}<br />
|text = <br />
|example1text = lock a lock<br />
|example1 = var lock = thread.newlock()<br />
}}<br />
<br />
{{Nasal doc<br />
|syntax = thread.unlock();<br />
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}<br />
|text = <br />
|example1text = unlock a lock<br />
|example1 = var lock = thread.unlock()<br />
}}<br />
<br />
<br />
{{Nasal doc<br />
|syntax = thread.newsem();<br />
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}<br />
|text = <br />
|example1text = create a new {{Wikipedia|semaphore}}<br />
|example1 = var semaphore = thread.newsem()<br />
}}<br />
<br />
<br />
{{Nasal doc<br />
|syntax = thread.semdown();<br />
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}<br />
|text = <br />
|example1text = semaphore down<br />
|example1 = thread.semdown(semaphore)<br />
}}<br />
<br />
<br />
{{Nasal doc<br />
|syntax = thread.semup();<br />
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}<br />
|text = <br />
|example1text = semaphore up<br />
|example1 = thread.semup(semaphore)<br />
}} --><br />
<br />
== Extension functions ==<br />
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:<br />
* {{flightgear file|src/Scripting/NasalPositioned.cxx}}<br />
* {{flightgear file|src/Scripting/NasalSys.cxx}}<br />
* {{fgdata file|Nasal/globals.nas}}<br />
<br />
=== abort() ===<br />
{{Nasal doc<br />
|syntax = abort();<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=565|t=Source}}<br />
|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 "exit" [[Fgcommands|fgcommand]], which will exit FlightGear more gracefully (see example below).<br />
|example1text = This example will immediately stop FlightGear with an error, such as "FlightGear has stopped working."<br />
|example1 = abort();<br />
|example2text = For exiting FlightGear in a better way, please use the following code:<br />
|example2 = fgcommand("exit");<br />
}}<br />
<br />
=== abs() ===<br />
{{Nasal doc<br />
|syntax = abs(number);<br />
|source = {{fgdata file|Nasal/globals.nas|t=Source}}<br />
|text = This simple function returns the {{wikipedia|absolute value|noicon=1}} of the provided number.<br />
|param1 = number<br />
|param1text = This argument is required and should be a number.<br />
|example1 = print(abs(1)); # prints "1"<br />
|example2 = print(abs(-1)); # prints "1"<br />
}}<br />
<br />
=== aircraftToCart() ===<br />
This new function in FG 2017.2.1 takes coordinates in aircraft structural coordinate system, and translate them into geocentric coordinates.<br />
Example for (5,6,7):<br />
<syntaxhighlight lang="nasal"><br />
var pos = aircraftToCart({x: -5, y: 6, z: -7});<br />
var coord = geo.Coord.new();<br />
coord.set_xyz(pos.x, pos.y, pos.z);<br />
</syntaxhighlight><br />
Notice: x and z is inverted sign on purpose.<br />
if you want lat. lon, alt from that, just call: (degrees and meters)<br />
<br />
<syntaxhighlight lang="nasal"><br />
coord.lat()<br />
coord.lon()<br />
coord.alt()<br />
</syntaxhighlight><br />
<br />
=== addcommand() ===<br />
{{Nasal doc<br />
|syntax = addcommand(name, code);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=659|t=Source}}<br />
|version = 2.12<br />
|commit = {{flightgear commit|7b663c|t=commit}}<br />
|text = {{see also|Howto:Add new fgcommands to FlightGear}}<br />
<br />
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.<br />
|param1 = name<br />
|param1text = This will become the name of the new fgcommand. Must be a string.<br />
|param2 = code<br />
|param2text = The code that will be executed when the fgcommand is run. Must be a function.<br />
|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.<br />
|example1 = addcommand("myFGCmd", func(node) {<br />
print("fgcommand 'myFGCmd' has been run.");<br />
props.dump( node );<br />
});<br />
fgcommand("myFGCmd", props.Node.new({foo:1, bar:2}) );<br />
|example2text = This example demonstrates how parameters are defined in a new fgcommand.<br />
|example2 = addcommand("myFGCmd", func(node){<br />
print(node.getNode("number").getValue()); # prints the value of "number," which is 12<br />
});<br />
fgcommand("myFGCmd", props.Node.new({"number": 12}));<br />
}}<br />
<br />
=== airportinfo() ===<br />
{{Nasal doc<br />
|syntax = airportinfo();<br />
airportinfo(type);<br />
airportinfo(id);<br />
airportinfo(lat, lon[, type]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1024|t=Source}}<br />
|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:<br />
* '''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.<br />
* '''lon''': Longitude of the location.<br />
* '''lat''': Latitude of the location.<br />
* '''has_metar''': True or false depending whether the airport has a [[METAR]] code defined for it.<br />
* '''elevation''': Elevation of the location in metres.<br />
* '''id''': ICAO code of the airport (or ID of the seaplane base/heliport).<br />
* '''name''': Name of the airport/heliport/seaplane base.<br />
* '''runways'''<br />
** '''<runway name>'''<br />
*** '''id''': Name of runway.<br />
*** '''lat''': Latitude of the runway.<br />
*** '''lon''': Longitude of the runway.<br />
*** '''heading''': Heading of the runway.<br />
*** '''length''': Length of the runway in metres.<br />
*** '''width''': Width of the runway in metres.<br />
*** '''surface''': Runway surface type.<br />
*** '''threshold''': Length of the runway's {{wikipedia|displaced threshold}} in metres. Will return 0 if there is none.<br />
*** '''stopway''': Length of the runway's stopway (the area before the threshold) in metres. Will return 0 if there is none.<br />
*** '''reciprocal''': <code>runway</code> ghost of the reciprocal runway.<br />
*** '''ils_frequency_mhz''': ILS frequency in megahertz.<br />
*** '''ils''': <code>navaid</code> ghost of the ILS transmitter.<br />
* '''helipads'''<br />
** '''<helipad name>'''<br />
*** '''id''': Name of helipad.<br />
*** '''lat''': Latitude of the helipad.<br />
*** '''lon''': Longitude of the helipad.<br />
*** '''heading''': Heading of the helipad.<br />
*** '''length''': Length of the helipad in metres.<br />
*** '''width''': Width of the helipad in metres.<br />
*** '''surface''': Helipad surface type.<br />
* '''taxiways'''<br />
** '''<taxiway name>'''<br />
*** '''id''': Name of taxiway.<br />
*** '''lat''': Latitude of the taxiway.<br />
*** '''lon''': Longitude of the taxiway.<br />
*** '''heading''': Heading of the taxiway.<br />
*** '''length''': Length of the taxiway in metres.<br />
*** '''width''': Width of the taxiway in metres.<br />
*** '''surface''': Taxiway surface type.<br />
<br />
Information is extracted in the same way as accessing members of a Nasal hash. For example:<br />
<syntaxhighlight lang="nasal"><br />
# prints to lengths of the runways of the nearest airport in feet and metres<br />
var info = airportinfo();<br />
print("-- Lengths of the runways at ", info.name, " (", info.id, ") --");<br />
foreach(var rwy; keys(info.runways)){<br />
print(rwy, ": ", math.round(info.runways[rwy].length * M2FT), " ft (", info.runways[rwy].length, " m)");<br />
}<br />
</syntaxhighlight><br />
<br />
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.<br />
|param1 = id<br />
|param1text = The {{wikipedia|International Civil Aviation Organization airport code|ICAO code|noicon=1}} of an airport to retrieve information about.<br />
|param2 = type<br />
|param2text = When this argument is used, the function will return the closest airport of a certain type. Can be one of "heliport," "seaport," or "airport" (default).<br />
: {{inote|Running this function without any parameters is equivalent to this:<br />
: <syntaxhighlight lang="nasal"><br />
airportinfo("airport");<br />
</syntaxhighlight><br />
}}<br />
|param3 = lat ''and'' lon<br />
|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.<br />
|example1 = var info = airportinfo();<br />
print("Nearest airport: ", info.name, " (", info.id, ")"); # prints the name and ICAO code of the nearest airport<br />
|example2 = var info = airportinfo("heliport");<br />
print("Elevation of the nearest heliport: ", math.round(info.elevation * M2FT), " ft"); # prints the elevation and name of the nearest heliport<br />
|example3 = var info = airportinfo("KSQL");<br />
print("-- Runways of ", info.name, " (", info.id, "): --");<br />
foreach(var rwy; keys(info.runways)) {<br />
print(rwy); # prints the runways of KSQL<br />
}<br />
|example4 = var info = airportinfo(37.81909385, -122.4722484);<br />
print("Coordinates of the nearest airport: ", info.lat, ", ", info.lon); # print the name and ICAO of the nearest airport to the Golden Gate Bridge<br />
|example5 = var info = airportinfo(37.81909385, -122.4722484, "seaport");<br />
print("Nearest seaplane base: ", info.name, " (", info.id, ")"); # print the name and ID of the nearest seaplane base to the Golden Gate Bridge<br />
|example6text = This example prints the all information from an <code>airportinfo()</code> call.<br />
|example6 = var info = airportinfo("KSFO");<br />
print(info.name);<br />
print(info.id);<br />
print(info.lat);<br />
print(info.lon);<br />
print(info.has_metar);<br />
print(info.elevation);<br />
foreach(var rwy; keys(info.runways)){<br />
print("-- ", rwy, " --");<br />
print(info.runways[rwy].lat);<br />
print(info.runways[rwy].lon);<br />
print(info.runways[rwy].length);<br />
print(info.runways[rwy].width);<br />
print(info.runways[rwy].heading);<br />
print(info.runways[rwy].stopway);<br />
print(info.runways[rwy].threshold);<br />
}<br />
}}<br />
<br />
=== airwaysRoute() ===<br />
{{Nasal doc<br />
|syntax = airwaysRoute(start, end[, type]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1933|t=Source}}<br />
|text = {{see also|Nasal Flightplan}}<br />
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.<br />
|param1 = start<br />
|param1text = Start waypoint, in the form of a waypoint ghost, such as that provided by {{func link|flightplan()}}.<br />
|param2 = end<br />
|param2text = Same as above.<br />
|param3 = type<br />
|param3text = Instructs the function to compute a high level route (when set to "highlevel"), or a low level route (when set to "lowlevel"). Defaults to "highlevel."<br />
|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.<br />
|example1 = var fp = flightplan();<br />
var start = fp.getWP(0);<br />
var end = fp.getWP(fp.getPlanSize() - 1);<br />
var rt = airwaysRoute(start, end);<br />
foreach(var wp; rt){<br />
print(wp.wp_name); # print the waypoints in the computed route<br />
}<br />
|example2text = Exactly the same as above, but computes a low level path.<br />
|example2 = var fp = flightplan();<br />
var start = fp.getWP(0);<br />
var end = fp.getWP(fp.getPlanSize() - 1);<br />
var rt = airwaysRoute(start, end, "lowlevel");<br />
foreach(var wp; rt){<br />
print(wp.wp_name); # print the waypoints in the computed route<br />
}<br />
}}<br />
<br />
=== airway() ===<br />
<br />
{{Nasal doc<br />
|syntax = airway(ident [, pos]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2644|t=Source}}<br />
|text = {{see also|Nasal Flightplan}}<br />
This function returns a ghost containing an airway of a specified id.<br />
|param1 = ident<br />
|param1text = Identifier of airway<br />
|param2 = pos<br />
|param1text = a Positioned ghost (leg, navaid, airport) and so on, passed to the search function. <br />
}}<br />
<br />
=== assert() ===<br />
{{Nasal doc<br />
|syntax = assert(condition[, message]);<br />
|source = {{fgdata file|Nasal/globals.nas|t=Source}}<br />
|version = 3.2<br />
|commit = {{fgdata commit|8b16a7|t=commit}}<br />
|text = Returns either true if the condition evaluates as true, or aborts with a {{func link|die()}} call, which can be customised.<br />
|param1 = condition<br />
|param1text = Condition to evaluate.<br />
|param2 = message<br />
|param2text = Optional message that will be used in any {{func link|die()}} call. Defaults to "assertion failed!"<br />
|example1 = var a = 1;<br />
var b = 2;<br />
print(assert(a < b)); # prints "1" (true)<br />
|example2 = var a = 1;<br />
var b = 2;<br />
assert(a > b, 'a is not greater than b'); # aborts with a custom error message<br />
}}<br />
<br />
=== carttogeod() ===<br />
{{Nasal doc<br />
|syntax = carttogeod(x, y, z);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=945|t=Source}}<br />
|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).<ref>{{simgear file|simgear/math/sg_geodesy.hxx|l=43}}</ref><br />
|param1 = x<br />
|param1text = Mandatory x-axis value, in metres.<br />
|param2 = y<br />
|param2text = Mandatory y-axis value, in metres.<br />
|param3 = z<br />
|param3text = Mandatory z-axis value, in metres.<br />
|example1 = var (lat, lon, alt) = carttogeod(6378137, 0, 0); # point is the intersection of the prime meridian and equator.<br />
print("Latitude: ", lat); # prints lat, lon and alt, which are all zero, see above<br />
print("Longitude: ", lon);<br />
print("Altitude: ", alt);<br />
}}<br />
<br />
=== cmdarg() ===<br />
{{Nasal doc<br />
|private = _cmdarg()<br />
|syntax = cmdarg();<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=513|t=Part 1}} {{!}} {{fgdata file|Nasal/globals.nas|t=Part 2}}<br />
|text = <code>cmdarg()</code> 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. <br />
It is used by Nasal scripts embedded in XML files. It returns a <code>props.Node</code> object (see {{fgdata file|Nasal/props.nas}}), and you can use all of its methods on the returned value. <code>cmdarg()</code> should only be used in four types/places of XML files:<br />
* Bindings: This is needed so that the value of a joystick's axis can be accessed internally.<br />
* 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). <br />
* 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.<br />
* Animation XML files: If the animation XML file is used in an AI/MP model, <code>cmdarg()</code> will return the root of the AI model in the <code>/ai/models/</code> directory. Examples: <code>/ai/models/aircraft[3]/</code>, <code>/ai/models/multiplayer[1]/</code><br />
<br />
You should not use <code>cmdarg()</code> in places other than those stated above. Although it won't cause an error, it will return the value of the last legitimate <code>cmdarg()</code> call. <br />
<br />
Also, you should not delay <code>cmdarg()</code> using {{func link|maketimer()}}, {{func link|settimer()}} or {{func link|setlistener()}}, because it will return an unrelated property.<br />
|example1 = fgcommand("dialog-show", {"dialog-name": "cmdarg-demo"});<br />
|example1text = <br>This example demonstrates the usage of <code>cmdarg()</code> in a binding. Save the below XML snippet as <tt>[[$FG_ROOT]]/gui/dialogs/cmdarg-demo.xml</tt>. 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.<br />
<syntaxhighlight lang="xml"><br />
<?xml version="1.0" encoding="UTF-8"?><br />
<br />
<PropertyList><br />
<br />
<name>cmdarg-demo</name><br />
<layout>vbox</layout><br />
<br />
<text><br />
<label>Click "Close" to activate the demonstration (a message in the console).</label><br />
</text><br />
<br />
<button><br />
<legend>Close</legend><br />
<binding><br />
<command>nasal</command><br />
<script>print("Button binding root: '" ~ cmdarg().getPath() ~ "'");</script><br />
</binding><br />
<binding><br />
<command>dialog-close</command><br />
</binding><br />
</button><br />
<br />
</PropertyList><br />
</syntaxhighlight><br />
|example2text = This example demonstrates the usage of <code>cmdarg()</code> in Nasal code within dialogs. Open <tt>[[$FG_ROOT]]/gui/dialogs/cmdarg-demo.xml</tt> from the previous example, copy & 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 "I've been changed!"<br />
<syntaxhighlight lang="xml"><br />
<?xml version="1.0" encoding="UTF-8"?><br />
<br />
<PropertyList><br />
<br />
<name>cmdarg-demo</name><br />
<layout>vbox</layout><br />
<br />
<text><br />
<label>Click "Click me!" to activate the demonstration (the button's label will change).</label><br />
</text><br />
<br />
<button><br />
<legend>Click me!</legend><br />
<binding><br />
<command>nasal</command><br />
<script>change_label();</script><br />
</binding><br />
</button><br />
<br />
<button><br />
<legend>Close</legend><br />
<binding><br />
<command>dialog-close</command><br />
</binding><br />
</button><br />
<br />
<nasal><br />
<open><![CDATA[<br />
var dlg_root = cmdarg();<br />
var dlg_name = {"dialog-name": "cmdarg-demo"};<br />
var change_label = func {<br />
dlg_root.getNode("button[0]/legend").setValue("I've been changed!");<br />
fgcommand("dialog-close", dlg_name);<br />
fgcommand("dialog-show", dlg_name);<br />
}<br />
]]></open><br />
</nasal><br />
<br />
</PropertyList><br />
</syntaxhighlight><br />
|example3text = For an example of <code>cmdarg()</code> used with Canvas, please see [[Howto:Adding a canvas to a GUI dialog#FGPlot|Howto:Adding a canvas to a GUI dialog]].<br />
}}<br />
<br />
=== courseAndDistance() ===<br />
{{Nasal doc<br />
|syntax = courseAndDistance(to);<br />
courseAndDistance(from, to);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1668|t=Source}}<br />
|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:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost type<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A geo.Coord object<br />
|param1 = from<br />
|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.<br />
|param2 = to<br />
|param2text = Like the first parameter, but defines the second point.<br />
|example1text = This example demonstrates the usage of the function with the <code>airport</code> ghost type.<br />
|example1 = var from = airportinfo("KSFO");<br />
var to = airportinfo("KSQL");<br />
var (course, dist) = courseAndDistance(from, to);<br />
print(course); # prints course from KSFO to KSQL<br />
print(dist); # prints distance in nm from KSFO to KSQL<br />
|example2text = This example demonstrates the usage of the function with hashes containing ''lat'' and ''lon''.<br />
|example2 = var from = {lat: 0, lon: 0};<br />
var to = {lat: 1, lon: 1};<br />
var (course, dist) = courseAndDistance(from, to);<br />
print(course);<br />
print(dist);<br />
|example3text = This example demonstrates usage of a geo.Coord object.<br />
|example3 = var from = geo.Coord.new().set_latlon(0, 0);<br />
var to = geo.Coord.new().set_latlon(1, 1);<br />
var (course, dist) = courseAndDistance(from, to);<br />
print(course);<br />
print(dist);<br />
|example4text = This example demonstrates usage of differing parameter types.<br />
|example4 = var from = airportinfo("KSFO");<br />
var to = geo.Coord.new().set_latlon(0, 0);<br />
var (course, dist) = courseAndDistance(from, to);<br />
print(course);<br />
print(dist);<br />
|example5text = The same as above, but the other way round.<br />
|example5 = var to = {lat: 1, lon: 1};<br />
var (course, dist) = courseAndDistance(0, 0, to);<br />
print(course);<br />
print(dist);<br />
|example6text = Usage of just one parameter.<br />
|example6 = var dest = airportinfo("KSQL");<br />
var (course, dist) = courseAndDistance(dest);<br />
print("Turn to heading ", math.round(course), ". You have ", sprintf("%.2f", dist), " nm to go");<br />
}}<br />
<br />
=== createFlightplan() ===<br />
{{Nasal doc<br />
|syntax = createFlightplan(path);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2331|t=Source}}<br />
|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.<br />
|param1 = path<br />
|param1text = Optional parameter defining the file from which a flightplan will be populated.<br />
|example1 = <br />
var path = getprop("/sim/fg-home") ~ "/Export/test.fgfp";<br />
var flightplan = createFlightplan(path);<br />
debug.dump(flightplan);<br />
}}<br />
<br />
=== createDiscontinuity() ===<br />
{{Nasal doc<br />
|syntax = createDiscontinuity();<br />
|text = Returns a <code>waypoint</code> ghost object. A route discontinuity is inserted by an {{abbr|FMS|Flight Management System}} when it is unsure how to connect two waypoints.<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2045|t=Source}}<br />
|version = 2016.1<br />
|commit = {{flightgear commit|caead6|t=commit}}<br />
}}<br />
=== createViaTo() ===<br />
{{Nasal doc<br />
|syntax = createViaTo(airway, waypoint);<br />
|text = Returns a <code>waypoint</code> ghost object. It represents a route "via '''airway''' to '''waypoint'''".<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2009|t=Source}}<br />
|version = 2016.1<br />
|commit = {{flightgear commit|caead6|t=commit}}<br />
|param1 = airway<br />
|param1text = The name of an airway.<br />
|param2 = waypoint<br />
|param2text = Must be in the airway and one of:<br />
* The name of a waypoint.<br />
* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, or <code>fix</code> ghost object.<br />
}}<br />
=== createWP() ===<br />
{{Nasal doc<br />
|syntax = createWP(pos, name[, flag]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1964|t=Source}}<br />
|text = Creates a new waypoint ghost object.<br />
|param1 = pos<br />
|param1text = Dictates the position of the new waypoint. It can be one of the following:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost type<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A geo.Coord object<br />
:* Two numbers separated by a comma, as if the function is taking three arguments. See example 4 below.<br />
|param2 = name<br />
|param2text = String that will become the name of the new waypoint.<br />
|param3 = flag<br />
|param3text = Optional string that will tell FlightGear what type of waypoint it is. Must be one of "sid," "star," "approach," "missed," or "pseudo."<br />
|example1text = Creates a waypoint directly in front and 1 km away and appends it to the flight plan.<br />
|example1 = var pos = geo.aircraft_position().apply_course_distance(getprop("/orientation/heading-deg"), 1000);<br />
var wp = createWP(pos, "NEWWP");<br />
var fp = flightplan();<br />
fp.appendWP(wp);<br />
|example2 = var pos = geo.aircraft_position().apply_course_distance(getprop("/orientation/heading-deg"), 1000);<br />
var wp = createWP({lat: pos.lat(), lon: pos.lon()}, "NEWWP");<br />
var fp = flightplan();<br />
fp.appendWP(wp);<br />
|example3 = var apt = airportinfo();<br />
var wp = createWP(apt, "NEWWP");<br />
var fp = flightplan();<br />
fp.appendWP(wp);<br />
|example4 = var pos = geo.aircraft_position().apply_course_distance(getprop("/orientation/heading-deg"), 1000);<br />
var wp = createWP(pos.lat(), pos.lon(), "NEWWP");<br />
var fp = flightplan();<br />
fp.appendWP(wp);<br />
|example5text = Creates a new waypoint and adds it to the flight plan. Waypoints of the type "pseudo" are then removed from the flight plan, including the new waypoint. The {{func link|print()}} statements show this.<br />
|example5 = var pos = geo.aircraft_position().apply_course_distance(getprop("/orientation/heading-deg"), 1000);<br />
var wp = createWP(pos, "NEWWP", "pseudo");<br />
var fp = flightplan();<br />
fp.appendWP(wp);<br />
print(fp.getPlanSize());<br />
fp.clearWPType("pseudo");<br />
print(fp.getPlanSize());<br />
}}<br />
<br />
=== createWPFrom() ===<br />
{{Nasal doc<br />
|syntax = createWPFrom(object[, flag]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1989|t=Source}}<br />
|text = Creates a new waypoint object from another object.<br />
|param1 = object<br />
|param1text = A ghost object. Must be a ghost type that is one of "airport," "navaid," "runway," or "fix."<br />
|param2 = flag<br />
|param2text = Optional string that will tell FlightGear what type of waypoint it is. Must be one of "sid," "star," "approach," "missed," or "pseudo."<br />
|example1text = Creates a new waypoint and appends it to the flight plan.<br />
|example1 = var apt = airportinfo("KSFO");<br />
var wp = createWPFrom(apt);<br />
var fp = flightplan();<br />
fp.appendWP(wp);<br />
|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.<br />
|example2 = var apt = airportinfo("KSFO");<br />
var wp = createWPFrom(apt, "pseudo");<br />
print(wp.wp_name);<br />
var fp = flightplan();<br />
fp.appendWP(wp);<br />
print(fp.getPlanSize());<br />
fp.clearWPType("pseudo");<br />
print(fp.getPlanSize());<br />
}}<br />
<br />
=== defined() ===<br />
{{Nasal doc<br />
|syntax = defined(symbol);<br />
|source = {{fgdata file|Nasal/globals.nas|t=Source}}<br />
|text = Returns 1 (true) or 0 (false) depending on whether a variable exists.<br />
|param1 = symbol<br />
|param1text = A string that will be what the function searches for.<br />
|example1 = var number = 12;<br />
var check_exist = func {<br />
print("Variable 'number' ", defined("number") == 1 ? "exists" : "does not exist"); # 'number' does exist<br />
print("Variable 'number2' ", defined("number2") == 1 ? "exists" : "does not exist"); # 'number2' does not exist<br />
}<br />
check_exist();<br />
}}<br />
<br />
=== directory() ===<br />
{{Nasal doc<br />
|syntax = directory(path);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=572|t=Source}}<br />
|text = Returns a vector containing a list of the folders and files in a given file path or <code>'''nil'''</code> if the path doesn't exist. Hidden folders and files are not added to the vector.<br />
{{inote|The first two elements of the vector will be <code>'.'</code> and <code>'..'</code>. These are for navigating back up the file tree, but have no use in Nasal. They can be safely removed from the vector.}}<br />
|param1 = path<br />
|param1text = Absolute file path.<br />
|example1text = Gets the folders and files in [[$FG_ROOT]], and then removes the extra first two elements (see note above). <br />
|example1 = var dir = directory(getprop("/sim/fg-root")); # get directory<br />
dir = subvec(dir, 2); # strips off the first two elements<br />
debug.dump(dir); # dump the vector<br />
}}<br />
<br />
=== fgcommand() ===<br />
{{Nasal doc<br />
|syntax = fgcommand(cmd[, args]);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=456|t=Part 1}} {{!}} {{fgdata file|Nasal/globals.nas|t=Part 2}}<br />
|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.<br />
|param1 = cmd<br />
|param1text = String that is the name of the command that is to be run.<br />
|param2 = args<br />
|param2text = If the fgcommand takes arguments, they are inputted using this argument. Can either be a <code>props.Node</code> object, or a hash (see examples below).<br />
|example1 = fgcommand("null"); # does nothing<br />
|example2 = var args = props.Node.new({'script': 'print("Running fgcommand");'});<br />
if (fgcommand("nasal", args)) { # prints "Running fgcommand" and then one of these print statements<br />
print("Fgcommand succeeded");<br />
} else {<br />
print("Fgcommand encountered a problem");<br />
}<br />
|example3 = var args = { 'dialog-name': 'about' };<br />
fgcommand("dialog-show", args); # shows the 'about' dialog<br />
}}<br />
<br />
=== findAirportsByICAO() ===<br />
{{Nasal doc<br />
|syntax = findAirportsByICAO(search[, type]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1096|t=Source}}<br />
|text = Returns a vector containing <code>airport</code> ghost objects which are (by default) airports whose ICAO code matches the search string. The results are sorted by range from closest to furthest.<br />
|param1 = search<br />
|param1text = Search string for the function. Can either be a partial or a full ICAO code.<br />
:{{icaution|The more matches there are for the given code, the longer the function will take. Passing just one character (e.g., "K"), might make FlightGear hang for a certain amount of time.}}<br />
|param2 = type<br />
|param2text = This will narrow the search to airports of a certain type. By default, only airports are searched for. May be one of "airport," "heliport," or "seaport."<br />
|example1 = var apts = findAirportsByICAO("KSF"); # finds all airports matching "KSF"<br />
foreach(var apt; apts){<br />
print(apt.name, " (", apt.id, ")"); # prints them<br />
}<br />
|example2 = var apts = findAirportsByICAO("SP0", "seaport"); # finds all seaplane bases matching "SP0"<br />
foreach(var apt; apts){<br />
print(apt.name, " (", apt.id, ")"); # prints them<br />
}<br />
|example3 = var apt = findAirportsByICAO("XBET"); # one way to check if an airport does exist"<br />
if (size(apt) == 0) {<br />
print("Airport does not exist"); # this one will be printed<br />
} else {<br />
print("Airport does exist");<br />
}<br />
}}<br />
<br />
=== findAirportsWithinRange() ===<br />
{{Nasal doc<br />
|syntax = findAirportsWithinRange([pos, ]range[, type]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1066|t=Source}}<br />
|text = Returns a vector of <code>airport</code> 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.<br />
|param1 = pos<br />
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost type<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A geo.Coord object<br />
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: <code>findAirportsWithinRange(lat, lon, range, type);</code>.<br />
|param2 = range<br />
|param2text = Mandatory number giving the range in nautical miles within which to search for airports/heliports/seaplane bases.only airports are searched for.<br />
|param3 = type<br />
|param3text = This will narrow the search to airports of a certain type. By default, only airports are searched for. May be one of "airport," "heliport," or "seaport."<br />
|example1text = Searches for airports within 10 nm of [[KSFO]].<br />
|example1 = var pos = airportinfo("KSFO");<br />
var apts = findAirportsWithinRange(pos, 10);<br />
foreach(var apt; apts){<br />
print(apt.name, " (", apt.id, ")");<br />
}<br />
|example2text = Searches for seaplane bases within 15 nm of [[KSFO]].<br />
|example2 = var pos = airportinfo("KSFO");<br />
var apts = findAirportsWithinRange(pos, 15, "seaport");<br />
foreach(var apt; apts){<br />
print(apt.name, " (", apt.id, ")");<br />
}<br />
|example3text = Searches for airports within 10 nm of your current position.<br />
|example3 = var apts = findAirportsWithinRange(10);<br />
foreach(var apt; apts){<br />
print(apt.name, " (", apt.id, ")");<br />
}<br />
}}<br />
<br />
=== findFixesByID() ===<br />
{{Nasal doc<br />
|syntax = findFixesByID([pos, ]id);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1627|t=Source}}<br />
|text = Returns a vector containing <code>fix</code> ghost objects matching a given ID, sorted by range from a certain position.<br />
{{inote|Fixes are (usually) also known as waypoints.}}<br />
|param1 = pos<br />
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost type<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A geo.Coord object<br />
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: <code>findFixesByID(lat, lon, id);</code>.<br />
|param2 = id<br />
|param2text = Full or partial ID of the fix to search for.<br />
:{{inote|1=Inputting a partial ID does not work correctly (see [http://forum.flightgear.org/viewtopic.php?f=30&t=28129 here]). It is best to just input a full ID.}}<br />
|example1 = var fixes = findFixesByID("POGIC");<br />
foreach(var fix; fixes){<br />
print(fix.id, " - lat: ", fix.lat, " {{!}} lon: ", fix.lon); # prints information about POGIC<br />
}<br />
|example2 = var fix = findFixesByID("ZUNAP");<br />
fix = fix[0];<br />
var (course, dist) = courseAndDistance(fix);<br />
print("Turn to heading ", math.round(course), ". You have ", sprintf("%.2f", dist), " nm to go to reach ", fixes[0].id);<br />
}}<br />
<br />
=== findNavaidByFrequency() ===<br />
{{Nasal doc<br />
|syntax = findNavaidByFrequency([pos, ]freq[, type]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1547|t=Source}}<br />
|text = Returns a <code>navaid</code> ghost object for a navaid matching a given frequency. If there is more than one navaid with that frequency, the nearest station is returned.<br />
|param1 = pos<br />
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost type<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A geo.Coord object<br />
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: <code>findNavaidByFrequency(lat, lon, freq, type);</code>.<br />
|param2 = freq<br />
|param2text = Frequency, in megahertz, of the navaid to search for.<br />
|param3 = type<br />
|param3text = This will narrow the search to navaids of a certain type. Defaults to "all." For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.<br />
|example1 = var navaid = findNavaidByFrequency(11.17);<br />
print("ID: ", navaid.id); # prints info about the navaid<br />
print("Name: ", navaid.name);<br />
print("Latitude: ", navaid.lat);<br />
print("Longitude: ", navaid.lon);<br />
print("Elevation (AMSL): ", navaid.elevation, " m");<br />
print("Type: ", navaid.type);<br />
print("Frequency: ", sprintf("%.3f", navaid.frequency / 1000), " Mhz");<br />
print("Range: ", navaid.range_nm, " nm");<br />
if(navaid.course) print("Course: ", navaid.course);<br />
}}<br />
<br />
=== findNavaidsByFrequency() ===<br />
{{Nasal doc<br />
|syntax = findNavaidsByFrequency([pos, ]freq[, type]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1572|t=Source}}<br />
|text = Returns a vector conatining <code>navaid</code> ghost objects for navaids that match a given frequency, sorted from nearest to furthest.<br />
|param1 = pos<br />
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost type<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A geo.Coord object<br />
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: <code>findNavaidsByFrequency(lat, lon, freq, type);</code>.<br />
|param2 = freq<br />
|param2text = Frequency, in megahertz, of the navaid to search for.<br />
|param3 = type<br />
|param3text = This will narrow the search to navaids of a certain type. Defaults to "all." For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.<br />
|example1 = var navaids = findNavaidsByFrequency(10.955);<br />
foreach(var navaid; navaids){<br />
print("--");<br />
print("ID: ", navaid.id); # prints info about the navaid<br />
print("Name: ", navaid.name);<br />
print("Latitude: ", navaid.lat);<br />
print("Longitude: ", navaid.lon);<br />
print("Elevation (AMSL): ", navaid.elevation, " m");<br />
print("Type: ", navaid.type);<br />
print("Frequency: ", sprintf("%.3f", navaid.frequency / 1000), " Mhz");<br />
print("Range: ", navaid.range_nm, " nm");<br />
if(navaid.course) print("Course: ", navaid.course);<br />
print("--");<br />
}<br />
}}<br />
<br />
=== findNavaidsByID() ===<br />
{{Nasal doc<br />
|syntax = findNavaidsByID([pos, ]id[, type]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1600|t=Source}}<br />
|text = Returns a vector containing <code>navaid</code> ghost objects matching a given ID, sorted by range from a certain position.<br />
|param1 = pos<br />
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost type<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A geo.Coord object<br />
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: <code>findNavaidsByID(lat, lon, id, type);</code>.<br />
|param2 = id<br />
|param2text = Full or partial ID of the fix to search for.<br />
:{{inote|1=Inputting a partial ID does not work correctly (see [http://forum.flightgear.org/viewtopic.php?f=30&t=28129 here]). It is best to just input a full ID.}}<br />
|param3 = type<br />
|param3text = This will narrow the search to navaids of a certain type. Defaults to "all." For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.<br />
|example1 = var navaid = findNavaidsByID("MXW");<br />
navaid = navaid[0];<br />
print("ID: ", navaid.id); # prints info about 'MXW' (a VOR station)<br />
print("Name: ", navaid.name);<br />
print("Latitude: ", navaid.lat);<br />
print("Longitude: ", navaid.lon);<br />
print("Elevation (AMSL): ", navaid.elevation, " m");<br />
print("Type: ", navaid.type);<br />
print("Frequency: ", sprintf("%.3f", navaid.frequency / 1000), " Mhz");<br />
print("Range: ", navaid.range_nm, " nm");<br />
}}<br />
<br />
=== findNavaidsWithinRange() ===<br />
{{Nasal doc<br />
|syntax = findNavaidsWithinRange([pos, ]range[, type]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1518|t=Source}}<br />
|text = Returns a vector of <code>navaid</code> 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.<br />
|param1 = pos<br />
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost type<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A geo.Coord object<br />
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: <code>findNavaidsWithinRange(lat, lon, range, type);</code>.<br />
|param2 = range<br />
|param2text = Mandatory number giving the range in nautical miles within which to search for navaids. Defaults to "all." For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.<br />
|param3 = type<br />
|param3text = This will narrow the search to navaids of a certain type.<br />
|example1text = Searches for navaids within 10 nm of [[KSFO]].<br />
|example1 = var pos = airportinfo("KSFO");<br />
var navs = findNavaidsWithinRange(pos, 10);<br />
foreach(var nav; navs){<br />
print(nav.name, " (ID: ", nav.id, ")");<br />
}<br />
|example2text = Searches for navaids within 10 nm of your current position.<br />
|example2 = var navs = findNavaidsWithinRange(10);<br />
foreach(var nav; navs){<br />
print(nav.name, " (ID: ", nav.id, ")");<br />
}<br />
}}<br />
<br />
=== finddata() ===<br />
{{Nasal doc<br />
|syntax = finddata(path);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=603|t=Source}}<br />
|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]]. <br />
|param1 = path<br />
|param1text = A relative path as a string.<br />
|example1 = var path = finddata("Aircraft/Generic");<br />
print(path); # prints the absolute path to $FG_ROOT/Aircraft/Generic<br />
|example2 = var path = finddata("Airports");<br />
print(path); # prints the absolute path to <TerraSync dir>/Airports<br />
|example3 = var path = finddata("preferences.xml");<br />
print(path); # prints the absolute path to $FG_ROOT/preferences.xml<br />
}}<br />
<br />
=== flightplan() ===<br />
{{Nasal doc<br />
|syntax = flightplan([path]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1738|t=Source}}<br />
|text = {{see also|Nasal Flightplan}}<br />
Returns a flight plan object, either one for the current flight plan, or one loaded from a given path.<br />
|param1 = path<br />
|param1text = Optional path to flight plan XML file.<br />
|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.<br />
|example1 = var fp = flightplan();<br />
print(fp.getWP(fp.current).id);<br />
|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 <tt>''[[$FG_HOME]]/fp-demo.xml''</tt>.<br />
|example2 = var path = getprop('/sim/fg-home') ~ '/fp-demo.xml';<br />
var fp = flightplan(path);<br />
for(var i = 0; i < fp.getPlanSize(); i += 1){<br />
print(fp.getWP(i).id);<br />
}<br />
}}<br />
<br />
=== geodinfo() ===<br />
{{Nasal doc<br />
|syntax = geodinfo(lat, lon[, max_alt]);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=981|t=Source}}<br />
|text = Returns a vector containing two entries or <code>'''nil'''</code> 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 <tt>''[[$FG_ROOT]]/Materials''</tt>), or <code>'''nil'''</code> 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}}):<br />
* '''light_coverage:''' The coverage of a single point of light in m<sup>2</sup>.<br />
* '''bumpiness:''' Normalized bumpiness factor for the material.<br />
* '''load_resistance:''' The amount of pressure in N/m<sup>2</sup> the material can withstand without deformation.<br />
* '''solid:''' 1 (true) or false (0) depending on whether the material is solid or not.<br />
* '''names:''' Vector of scenery types (usually generated by [[TerraGear]]) that will use this material. <br />
* '''friction_factor:''' Normalized friction factor of the material.<br />
* '''rolling_friction:''' The rolling friction coefficient of the material.<br />
<br />
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.<br />
|param1 = lat<br />
|param1text = Latitude, inputted as a number.<br />
|param2 = lon<br />
|param2text = Longitude, inputted as a number.<br />
|param3 = max_alt<br />
|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, <code>'''nil'''</code> will be returned.<br />
|example1text = Dumps information about ground underneath the aircraft.<br />
|example1 = var pos = geo.aircraft_position();<br />
var info = geodinfo(pos.lat(), pos.lon());<br />
debug.dump(info);<br />
|example2text = Prints whether the ground underneath the aircraft is solid or is water.<br />
|example2 = var pos = geo.aircraft_position();<br />
var info = geodinfo(pos.lat(), pos.lon());<br />
if (info != nil and info[1] != nil) {<br />
print("The ground underneath the aircraft is ", info[1].solid == 1 ? "solid." : "water.");<br />
}<br />
}}<br />
<br />
=== geodtocart() ===<br />
{{Nasal doc<br />
|syntax = geodtocart(lat, lon, alt);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=962|t=Source}}<br />
|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.<br />
|param1 = lat<br />
|param1text = Latitude, in degrees.<br />
|param2 = lon<br />
|param2text = Longitude, in degrees.<br />
|param3 = alt<br />
|param3text = Altitude, in metres.<br />
|example1 = var (x, y, z) = geodtocart(0, 0, 0); # point is the intersection of the prime meridian and equator.<br />
print("x: ", x); # prints "x: 6378137"<br />
print("y: ", y); # prints "y: 0"<br />
print("z: ", z); # prints "y: 0"<br />
}}<br />
<br />
=== get_cart_ground_intersection() ===<br />
Introduced in 2017.2.1, see [[Terrain Detection]].<br />
<br />
=== getprop() ===<br />
{{Nasal doc<br />
|syntax = getprop(path[, path[, ...]]);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=345|t=Source}}<br />
|text = Returns the value of a node in the [[Property Tree]] or <code>'''nil'''</code> if the node does not exist or the value is not a number (NaN).<br />
|param1 = path<br />
|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.<br />
|example1 = print("You have FlightGear v", getprop("/sim/version/flightgear")); # prints FlightGear version<br />
|example2text = Note that the example below will only work in FlightGear 3.2 and above.<br />
|example2 = for(var i = 0; i < 8; i += 1){<br />
print("View #", i + 1, " is named ", getprop("/sim/view", i, "name"));<br />
}<br />
|example3text = Same as above, but is supported by all versions of FlightGear.<br />
|example3 = for(var i = 0; i < 8; i += 1){<br />
print("View #", i + 1, " is named ", getprop("/sim/view[" ~ i ~ "]/name"));<br />
}<br />
}}<br />
==== See also ====<br />
{{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: <br />
To get a Node rather than its value, use <code>props.globals.getNode()</code> - see [[Nasal_library/props]]. }}<br />
<br />
=== greatCircleMove() ===<br />
{{Nasal doc<br />
|syntax = greatCircleMove([pos, ]course, dist);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1681|t=Source}}<br />
|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).<br />
|param1 = pos<br />
|param1text = Optional position to calculate from. If not given, the aircraft's current position will be used. Can be one of:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost object.<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A <code>geo.Coord</code> object<br />
:* A lat/lon pair, that is, a pair of numbers (latitude followed by longitude) separated by a comma: <code>greatCircleMove(lat,lon, ...)</code>.<br />
|param2 = course<br />
|param2text = Course to new set of coordinates, in degrees (in the range 0–360).<br />
|param3 = dist<br />
|param3text = Distance in nautical miles to the new set of coordinates.<br />
|example1 = var pos = greatCircleMove(0,0, 0, 1);<br />
debug.dump(pos); # print hash with coordinates<br />
|example2 = var fix = findFixesByID("POGIC");<br />
fix = fix[0];<br />
var pos = greatCircleMove(fix, 45, 10);<br />
debug.dump(pos); # print hash with coordinates<br />
}}<br />
<br />
=== interpolate() ===<br />
{{Nasal doc<br />
|private = _interpolate()<br />
|syntax = interpolate(prop, value1, time1[, value2, time2[, ...]]);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=522|t=Part 1}} {{!}} {{fgdata file|Nasal/globals.nas|t=Part 2}}<br />
|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.<br />
|param1 = prop<br />
|param1text = String or <code>props.Node</code> object that indicates a node in the property tree to be used.<br />
|param2 = value''n''<br />
|param2text = Target value to change the property to in the set amount of time. This should be a number.<br />
|param3 = time''n''<br />
|param3text = Time in seconds, that will be taken for the interpolation.<br />
|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.<br />
|example1 = setprop("/test", 0); # (re-)set property<br />
interpolate("/test",<br />
50, 5, # interpolate to 50 in 5 seconds<br />
10, 2, # interpolate to 10 in 2 seconds<br />
0, 5); # interpolate to 0 in 5 seconds<br />
|example2 = # Apply the left brake at 20% per second<br />
var prop = "controls/gear/brake-left";<br />
var dist = 1 - getprop(prop);<br />
if (dist == 1) {<br />
interpolate(prop, 1, dist / 0.2);<br />
}<br />
}}<br />
<br />
=== isa() ===<br />
{{Nasal doc<br />
|syntax = isa(object, class);<br />
|source = {{fgdata file|Nasal/globals.nas|t=Source}}<br />
|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.<br />
|param1 = object<br />
|param1text = Object to check.<br />
|param2 = class<br />
|param2text = Class/object to check that '''object''' inherits from or is an instance of.<br />
|example1 = var coord = geo.Coord.new();<br />
if(isa(coord, geo.Coord)){<br />
print("Variable 'coord' is an instance of class 'geo.Coord'"); # this one will be printed<br />
} else {<br />
print("Variable 'coord' is not an instance of class 'geo.Coord'");<br />
}<br />
|example2 = var coord = geo.Coord.new();<br />
if(isa(coord, props.Node)){<br />
print("Variable 'coord' is an instance of class 'props.Node'");<br />
} else {<br />
print("Variable 'coord' is not an instance of class 'props.Node'"); # this one will be printed<br />
}<br />
|example3text = The example below demonstrates checking of inheritance.<br />
|example3 = var Const = {<br />
constant: 2,<br />
getConst: func {<br />
return me.constant;<br />
}<br />
};<br />
<br />
var Add = {<br />
new: func {<br />
return { parents: [Add, Const] };<br />
},<br />
<br />
addToConst: func(a){<br />
return a * me.getConst();<br />
}<br />
};<br />
<br />
var m = Add.new();<br />
print(m.addToConst(4));<br />
<br />
if(isa(m, Add)) print("Variable 'm' is an instance of class 'Add'"); # will be printed<br />
if(isa(m, Const)) print("Variable 'm' is an instance of class 'Const'"); # will also be printed<br />
}}<br />
<br />
=== logprint() ===<br />
{{Nasal doc<br />
|syntax = logprint(priority[, msg[, msg[, ...]]]);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=431|t=Source}}<br />
|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 <code>[[Commonly used debugging tools#fgfs.log|fgfs.log]]</code> file as coming from the Nasal file itself rather than from {{flightgear file|src/Scripting/NasalSys.cxx}}.<br />
|param1 = priority<br />
|param1text = Number specifying the priority level of the outputted message:<br />
:{{{!}} class="wikitable"<br />
! Number !! Debug type<br />
{{!-}}<br />
{{!}} 1 {{!!}} Bulk<br />
{{!-}}<br />
{{!}} 2 {{!!}} Debug<br />
{{!-}}<br />
{{!}} 3 {{!!}} Info<br />
{{!-}}<br />
{{!}} 4 {{!!}} Warn<br />
{{!-}}<br />
{{!}} 5 {{!!}} Alert<br />
{{!}}}<br />
|param2 = msg<br />
|param2text = The message. There is no limit to the arguments you give give. They will be concatenated together before logging.<br />
|example1 = # logs the value of pi to three decimal places with log level 3<br />
logprint(3, "pi = ", sprintf("%.3f", math.pi));<br />
|example2 = logprint(5, "Alert! This is an important message!");<br />
}}<br />
{{note| <br />
The following constants have been added to the development branch of FlightGear ("next") and will be releases with FG 2020.1 so you won't have to remember the numbers anymore:<br />
<br />
LOG_BULK, LOG_WARN, LOG_DEBUG, LOG_INFO, LOG_ALERT, DEV_WARN, DEV_ALERT }}<br />
<br />
=== magvar() ===<br />
{{Nasal doc<br />
|syntax = magvar([pos]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1642|t=Source}}<br />
|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.<br />
{{{!}} class="wikitable"<br />
! FlightGear versions !! Model !! Reference date<br />
{{!-}}<br />
{{!}} 3.6 and above {{!!}} {{wikipedia|World Magnetic Model}} (WMM) 2015 {{!!}} 1 January 2015<br />
{{!-}}<br />
{{!}} 0.9.11-pre1 to 3.4 {{!!}} WMM 2005 {{!!}} 1 January 2005<br />
{{!-}}<br />
{{!}} 0.7.3 to 0.9.10 {{!!}} WMM 2000 {{!!}} 1 January 2000<br />
{{!}}}<br />
|param1 = pos<br />
|param1text = Optional position to calculate from. If not given, the aircraft's current position will be used. Can be one of:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost object.<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A <code>geo.Coord</code> object<br />
:* A lat/lon pair, that is, a pair of numbers (latitude followed by longitude) separated by a comma: <code>magvar(lat,lon)</code>.<br />
|example1 = print(magvar(0, 0)); # prints the magnetic variation at 0, 0<br />
}}<br />
<br />
=== maketimer() ===<br />
{{Nasal doc<br />
|syntax = maketimer(interval[, self], function);<br />
|source = ''Implemented using the {{API Link|flightgear|class|TimerObj}} class.''<br>{{flightgear file|src/Scripting/NasalSys.cxx|l=90|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=533|t=Part 2}}<br />
|version = 2.12<br />
|commit = {{flightgear commit|ab939f|t=commit}}<br />
|text = Returns a timer object containing the following methods and members:<br />
* '''start()''': Starts the timer.<br />
* '''stop()''': Stops the timer.<br />
* '''restart(interval)''': Restarts the timer with the given interval.<br />
* '''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).<br />
* '''isRunning''': Read-only bool telling whether the timer is currently running.<br />
* '''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.<br />
Unlike {{func link|settimer()}}, which it replaces, <code>maketimer()</code> provides more control over the timer. In addition, it can help reduce memory usage.<br />
|param1 = interval<br />
|param1text = Interval in seconds for the timer.<br />
|param2 = self<br />
|param2text = Optional parameter specifying what any <code>'''me'''</code> references in the function being called will refer to.<br />
|param3 = function<br />
|param3text = Function to be called at the given interval.<br />
|example1 = var timer = maketimer(1, func(){<br />
print("Hello, World!"); # print "Hello, World!" once every second (call timer.stop() to stop it)<br />
});<br />
timer.start();<br />
|example2 = var timer = maketimer(1, math, func(){<br />
print(me.math); # 'me' reference is the 'math' namespace<br />
});<br />
timer.singleShot = 1; # timer will only be run once<br />
timer.start();<br />
|example3 = var timer = maketimer(1, func(){<br />
print("Hello, World!"); # print "Hello, World!" once every second (call timer.stop() to stop it)<br />
});<br />
timer.start();<br />
print(timer.isRunning); # prints 1<br />
|example4text = In the example below, "Hello, World!" will be printed after one second the first time, and after two seconds thereafter.<br />
|example4 = var update = func(){<br />
print("Hello, World!");<br />
timer.restart(2); # restarts the timer with a two second interval<br />
}<br />
<br />
var timer = maketimer(1, update);<br />
timer.singleShot = 1;<br />
timer.start();<br />
}}<br />
<br />
=== maketimestamp() ===<br />
{{Nasal doc<br />
|syntax = maketimestamp()<br />
|source = ''Implemented using the {{API Link|flightgear|class|TimeStampObj}} class.''<br>{{flightgear file|src/Scripting/NasalSys.cxx|l=214|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=589|t=Part 2}}<br />
|version = 2019.2<br />
|commit = {{flightgear commit|7db06300|t=commit}}<br />
|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:<br />
* '''stamp()''': Resets the timing operation. Call this first.<br />
* '''elapsedMSec()''': returns number of milliseconds elapsed since stamp() called. Resolution may vary depending on platform but is usually at least millisecond accuracy.<br />
* '''elapsedUSec()''': returns number of microseconds elapsed since stamp() called. Resolution may vary depending on platform but is usually at least millisecond accuracy.<br />
|<br />
|example1text = In the example below the number of milliseconds elapsed will be printed.<br />
|example1 = var timestamp = maketimestamp();<br />
timestamp.stamp();<br />
print(timestamp.elapsedMSec(), "ms elapsed");<br />
print(timestamp.elapsedMSec(), "ms elapsed");<br />
}}<br />
<br />
=== md5() ===<br />
{{Nasal doc<br />
|syntax = md5(string);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=758|t=Source}}<br />
|version = 3.2<br />
|commit = {{flightgear commit|cfbf9e|t=commit}}<br />
|text = Returns a the {{wikipedia|MD5}} hash (as a string) of the inputted string.<br />
|param1 = string<br />
|param1text = String the generate the hash of. Mandatory.<br />
|example1text = The below code should output <code>65a8e27d8879283831b664bd8b7f0ad4</code>.<br />
|example1 = print(md5("Hello, World!"));<br />
}}<br />
<br />
=== navinfo() ===<br />
{{Nasal doc<br />
|syntax = navinfo(lat, lon, type, id);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1453|t=Source}}<br />
|text = Returns vector <code>navaid</code> ghost objects matching the given '''type''' and '''id''' or <code>'''nil'''</code> on error.<br />
|param1 = lat ''and'' lon<br />
|param1text = If given, the returned navaids will be put into order of ascending distance from the location.<br />
|param2 = type<br />
|param2text = Narrows the search to the given type. Must be one of "any," "fix," "vor," "ndb," "ils," "dme," or "tacan." Defaults to the equivalent of "any."<br />
|param3 = id<br />
|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.<br />
|example1 = navinfo("vor"); # returns all VORs<br />
|example2 = navinfo("HAM"); # return all navaids whose names start with "HAM"<br />
|example3 = navinfo("vor", "HAM"); # return all VORs whose names start with "HAM"<br />
|example4 = navinfo(34,48,"vor","HAM"); # return all VORs whose names start with "HAM" and sorted by distance relative to 34°, 48°<br />
}}<br />
<br />
=== parse_markdown() ===<br />
{{Nasal doc<br />
|syntax = parse_markdown(markdown);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=745|t=Part 1}} {{!}} {{simgear file|simgear/misc/SimpleMarkdown.cxx|t=Part 2}} <br />
|version = 3.2<br />
|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:<br />
* It strips whitespace from the beginning of the string.<br />
* It supports [http://daringfireball.net/projects/markdown/syntax#p paragraphs and line breaks].<br />
* It collapses whitespace.<br />
* It converts unordered [http://daringfireball.net/projects/markdown/syntax#list lists] to use a bullet character (&bull;). Note that the bullet character is implemented in hexadecimal UTF-8 character bytes (<code>E2 80 A2</code>), as so may not work properly when the being displayed in an encoding other than UTF-8.<br />
|param1 = markdown<br />
|param1text = String containing Markdown to be parsed.<br />
|example1text = <br />
Save the below code as <tt>''[[$FG_ROOT]]/gui/dialogs/test.xml''</tt>, 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 (<tt>Debug > Reload GUI</tt>).<br />
<syntaxhighlight lang="xml" highlight="41-50"><br />
<?xml version="1.0" encoding="UTF-8"?><br />
<br />
<PropertyList><br />
<br />
<name>test</name><br />
<layout>vbox</layout><br />
<br />
<group><br />
<layout>hbox</layout><br />
<br />
<empty><br />
<stretch>true</stretch><br />
</empty><br />
<br />
<text><br />
<label>parse_markdown() test dialog</label><br />
</text><br />
<br />
<empty><br />
<stretch>true</stretch><br />
</empty><br />
<br />
<button><br />
<legend></legend><br />
<pref-width>16</pref-width><br />
<pref-height>16</pref-height><br />
<binding><br />
<command>dialog-close</command><br />
</binding><br />
</button><br />
<br />
</group><br />
<br />
<canvas><br />
<name>Canvas plot</name><br />
<stretch>true</stretch><br />
<pref-width>400</pref-width><br />
<pref-height>300</pref-height><br />
<nasal><br />
<load><![CDATA[<br />
var text = 'Items:<br />
* apples<br />
* oranges<br />
* pears<br />
<br />
Some text.<br />
Some more items:<br />
* apples<br />
* oranges<br />
* pears';<br />
<br />
var parsed = parse_markdown(text);<br />
<br />
var root_canvas = canvas.get(cmdarg());<br />
root_canvas.setColorBackground(255, 255, 255);<br />
var root = root_canvas.createGroup();<br />
<br />
var text_dis = root.createChild("text")<br />
.setText(parsed)<br />
.setTranslation(5, 5)<br />
.setFont("LiberationFonts\LiberationSans-Regular.ttf")<br />
.setFontSize(15)<br />
.setColor(0, 0, 0)<br />
.setDrawMode(canvas.Text.TEXT)<br />
.setAlignment("left-top");<br />
]]></load><br />
</nasal><br />
</canvas><br />
<br />
</PropertyList><br />
</syntaxhighlight><br />
|example1 = fgcommand("dialog-show", {"dialog-name": "test"});<br />
|example2text = The example below parses Markdown and outputs it in a HTML document. The parsed text is placed in <syntaxhighlight lang="xml" inline><pre></pre></syntaxhighlight> tags. To change the Markdown to be parsed, simply edit the variable <tt>markdown</tt> at the top of the code.<br />
|example2 = <nowiki>var markdown = 'Items:<br />
* apples<br />
* oranges<br />
* pears<br />
<br />
Some text.<br />
Some more items:<br />
* apples<br />
* oranges<br />
* pears';<br />
<br />
var parsed = parse_markdown(markdown);<br />
<br />
debug.dump(parsed);<br />
<br />
var path = string.normpath(getprop("/sim/fg-home") ~ '/Export/parse_markdown()-test.html');<br />
<br />
var file = io.open(path, "w");<br />
<br />
var html = "<!DOCTYPE html>\n\n<html>\n\n<head>\n\t<meta charset=\"UTF-8\">\n\t<title>parse_markdown() test generated by Nasal</title>\n</head>\n\n<body>\n\t<pre>" ~ parsed ~ "</pre>\n</body>\n\n</html>";<br />
<br />
io.write(file, html);<br />
io.close(file);<br />
print("Done, file ready for viewing (" ~ path ~ ")");</nowiki><br />
}}<br />
<br />
=== parsexml() ===<br />
{{Nasal doc<br />
|syntax = parsexml(path[, start[, end[, data[, pro_ins]]]]);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=710|t=Source}}<br />
|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 <code>'''nil'''</code> is returned on error.<br />
|param1 = path<br />
|param1text = Mandatory absolute path to the XML file to be parsed.<br />
|param2 = start<br />
|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.<br />
|param3 = end<br />
|param3text = Optional callback function that will be called for every ending tag. The function should take one argument: the tag name.<br />
|param4 = data<br />
|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.<br />
|param5 = pro_ins<br />
|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.<br />
|example1text = Save the below XML code in <tt>''[[$FG_HOME]]/Export/demo.xml''</tt>. Then, execute the Nasal code below in the Nasal Console. The XML will be parsed and each bit of the code will be printed.<br />
<syntaxhighlight lang="xml"><br />
<?xml version="1.0" encoding="UTF-8"?><br />
<br />
<?xml-stylesheet type="text/xsl" href="style.xsl"?><br />
<br />
<foo><br />
<blah type="string"><![CDATA[ <sender>John Smith</sender> ]]></blah><br />
<blah2 type="string">Orange &amp; lemons</blah2><br />
</foo><br />
</syntaxhighlight><br />
|example1 = var start = func(name, attr){<br />
print("Starting tag: '", name, "'");<br />
foreach(var a; keys(attr)){<br />
print("\twith attribute ", a, '="', attr[a], '"');<br />
}<br />
}<br />
<br />
var end = func(name){<br />
print("Ending tag: '", name, "'");<br />
}<br />
<br />
var data = func(data){<br />
print("Data = '", data, "'");<br />
}<br />
<br />
var pro_instr = func(target, data){<br />
print("Processing instruction: target = '", target, "', data = '", data, "'");<br />
}<br />
<br />
parsexml(getprop("/sim/fg-home") ~ '/Export/demo.xml', start, end, data, pro_instr);<br />
}}<br />
<br />
=== print() ===<br />
{{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.<br />
<br />
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.<ref>https://sourceforge.net/p/flightgear/mailman/message/37042224/</ref>}}<br />
<br />
{{Nasal doc<br />
|syntax = print(data[, data[, ...]]);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=415|t=Source}}<br />
|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.<br />
|param1 = data<br />
|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.<br />
|example1 = print("Just", " a ", "test"); # prints "Just a test"<br />
|example2 = print("pi = ", math.pi); # prints "pi = 3.141592..."<br />
}}<br />
<br />
=== printf() ===<br />
{{Nasal doc<br />
|syntax = printf(format[, arg[, arg, [...]]]);<br />
|source = {{fgdata file|Nasal/globals.nas|t=Source}}<br />
|text = Creates and prints a formatted string. For a description of its arguments, see {{func link|sprintf()}} (it is, in fact, implemented using <code>sprintf()</code>).<br />
|example1 = printf("In hexadecimal, 100000 = %X", 186A0); # prints "In hexadecimal, 100000 = 186A0"<br />
}}<br />
<br />
=== printlog() ===<br />
{{Nasal doc<br />
|syntax = printlog(level, data[, data[, ...]]);<br />
|source = {{fgdata file|Nasal/globals.nas|t=Source}}<br />
|text = Prints the given message with the given log level. If the log level is higher or equal to <code>/sim/logging/priority</code>, it is printed.<br />
|param1 = level<br />
|param1text = Mandatory log level as a string. Must be one of "none," "bulk," "debug," "info," "warn," or "alert." Note that "none" will mean that the message will never be printed.<br />
|param2 = data<br />
|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.<br />
|example1 = printlog("alert", "This is an alert"); # message will be printed<br />
|example2 = printlog("info", "Just informing you about something"); # message will be printed only if log level is set to "info" or less<br />
}}<br />
<br />
=== rand() ===<br />
{{Nasal doc<br />
|syntax = rand();<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=554|t=Source}}<br />
|text = Returns a random floating point number between 0 (inclusive) and 1 (exclusive). It takes no arguments.<br />
|example1 = print(rand()); # prints random number<br />
}}<br />
<br />
=== registerFlightPlanDelegate() ===<br />
{{Nasal doc<br />
|syntax = registerFlightPlanDelegate(init_func);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1879|t=Source}}<br />
|text = Registers a flight plan delegate. See <tt>''{{fgdata file|Nasal/route_manager.nas|t=$FG_ROOT/Nasal/route_manager.nas}}''</tt> for examples.<br />
|param1 = init_func<br />
|param1text = Initialization function which will be called during FlightGear's startup.<br />
}}<br />
=== removecommand() ===<br />
{{Nasal doc<br />
|syntax = removecommand(cmd);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=668|t=Source}}<br />
|text = Removes the given fgcommand. Returns <code>'''nil'''</code>.<br />
{{caution|This will remove '''any''' [[fgcommands|fgcommand]], even those implemented in C++, so use with caution!}}<br />
|param1 = cmd<br />
|param1text = String specifying the name of the command to remove.<br />
|example1 = addcommand("hello", func(){<br />
print("Hello");<br />
});<br />
fgcommand("hello"); # "Hello" will be printed<br />
removecommand("hello"); # removes it<br />
}}<br />
<br />
=== removelistener() ===<br />
{{Nasal doc<br />
|syntax = removelistener(id);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=1384|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=506|t=Part 2}}<br />
|text = Removes and deactivates the given listener and returns the number of listeners left or <code>'''nil'''</code> on error.<br />
{{note|It is good practice to remove listeners when they are not required anymore. This prevents the listeners reducing FlightGear's run performance.}}<br />
|param1 = id<br />
|param1text = ID of listener as returned by {{func link|setlistener()}}.<br />
|example1 = var ls = setlistener("/sim/test", func(){<br />
print("Property '/sim/test' has been changed");<br />
});<br />
setprop("/sim/test", "blah"); # trigger listener<br />
var rem = removelistener(ls); # remove listener<br />
print("There are ", rem, " listeners remaining");<br />
}}<br />
<br />
=== resolvepath() ===<br />
{{Nasal doc<br />
|syntax = resolvepath(path);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=604|t=Source}}<br />
|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.<br />
|param1 = path<br />
|param1text = Relative path to be completed.<br />
|example1 = print(resolvepath("Nasal/globals.nas")); # prints the equivalent of $FG_ROOT/Nasal/globals.nas<br />
|example2 = print(resolvepath("blah")); # prints nothing; could not be resolved<br />
|example3 = var file_path = resolvepath("Aircraft/SenecaII/some-file");<br />
if (file_path != ""){<br />
gui.popupTip("some-file found", 2);<br />
} else {<br />
gui.popupTip("some-file not found", 2);<br />
}<br />
}}<br />
<br />
=== setlistener() ===<br />
{{Nasal doc<br />
|syntax = setlistener(node, code[, init[, type]]);<br />
|private = _setlistener()<br />
|source = {{flightgear file|src/Scripting/Nasal|l=499|t=Part 1}} {{!}} {{flightgear file|src/Scripting/Nasal|l=1350|t=Part 2}}<br>''Listener implemented using the {{API Link|flightgear|class|FGNasalListener}} class.''<br />
|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()}}.<br />
{{note|Listeners are known to be a source of resource leaks. To avoid this, please take measures such as:<br />
* Using {{func link|removelistener()}} when they are not needed any more.<br />
* Using a single initialization listener.<br />
* Avoiding tying listeners to properties that are rapidly updated (e.g., many times per frame).<br />
}}<br />
|param1 = node<br />
|param1text = Mandatory string or <code>props.Node</code> object pointing to a property in the [[Property Tree]].<br />
|param2 = code<br />
|param2text = Mandatory callback function to execute when the listener is triggered. The function can take up to four arguments in the following order:<br />
* '''changed''': a <code>props.Node</code> object pointing to the changed node.<br />
* '''listen''': a <code>props.Node</code> object pointing to the listened-to node. Note that this argument maybe different depending on the '''type'''.<br />
* '''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.<br />
* '''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.<br />
|param3 = init<br />
|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).<br />
|param4 = type<br />
|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.<br />
|example1 = var prop = props.globals.initNode("/sim/test", "", "STRING"); # create property<br />
var id = setlistener("/sim/test", func(n){ # create listener<br />
print("Value: ", n.getValue());<br />
});<br />
setprop("/sim/test", "blah"); # trigger listener<br />
removelistener(id); # remove listener<br />
prop.remove(); # remove property<br />
}}<br />
<br />
=== setprop() ===<br />
{{Nasal doc<br />
|syntax = setprop(path[, path[, ...]], value);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=385|t=Source}}<br />
|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.<br />
{{note|If you want to remove a property, you will have to use one of the <code>props</code> helpers:<br />
<syntaxhighlight lang="nasal"><br />
props.globals.getNode("foo/bar").remove(); # take out the complete node<br />
props.globals.getNode("foo").removeChild("bar"); # take out a certain child node<br />
</syntaxhighlight><br />
}}<br />
|param1 = path<br />
|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.<br />
|param2 = value<br />
|param2text = Value to write to the given property. Must be either a string or a number.<br />
|example1 = setprop("/sim/demo", "This is a demo");<br />
|example2text = Note that the example below will only work in FlightGear 3.2 and above.<br />
|example2 = for(var i = 0; i < 3; i += 1){<br />
setprop("/sim/demo", i, "Demo #" ~ i));<br />
}<br />
|example3text = Same as above, but is supported by all versions of FlightGear.<br />
|example3 = for(var i = 0; i < 3; i += 1){<br />
setprop("/sim/demo[" ~ i ~ "]", "Demo #" ~ i));<br />
}<br />
}}<br />
==== See also ====<br />
{{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: <br />
To get a Node rather than its value, use <code>props.globals.getNode()</code> - see [[Nasal_library/props]]. }}<br />
<br />
=== settimer() ===<br />
{{Nasal doc<br />
|syntax = settimer(function, delta[, realtime]);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=499|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=1286|t=Part 2}}<br />
|text = Runs the given function a specified amount of seconds after the current time. Returns <code>'''nil'''</code>.<br />
{{note|Improper use of <code>settimer()</code> may cause resource leaks. It is also highly recommended that the newer {{func link|maketimer()}} should be used instead of this function.}} <br />
|param1 = function<br />
|param1text = Mandatory function that will be called. It may be necessary to enclose code in an anonymous function (see example).<br />
|param2 = delta<br />
|param2text = Mandatory amount of time in seconds after which the function will be called.<br />
|param3 = realtime<br />
|param3text = If 1 (true), "real time" will be used instead of "simulation time." Defaults to 0 (false). Note that if "simulation time" is used, the timer will not run while FlightGear is paused.<br />
|example1 = var myFunc = func(){<br />
print("Hello");<br />
}<br />
<br />
settimer(myFunc, 2); # runs myFunc after 2 seconds<br />
|example2 = var sqr = func(a){<br />
return a * a;<br />
}<br />
<br />
settimer(func(){<br />
print(sqr(2)); # will print 4 after 2 seconds<br />
}, 2);<br />
}}<br />
<br />
=== srand() ===<br />
{{Nasal doc<br />
|syntax = srand();<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=559|t=Source}}<br />
|text = Makes the pseudorandom number generator (see {{func link|rand()}}) generate a new {{wikipedia|random seed|noicon=1}} based on time. Returns 0.<br />
}}<br />
=== systime() ===<br />
{{Nasal doc<br />
|syntax = systime();<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=770|t=Source}}<br />
|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).<br />
{{note|1=High resolution timers under Windows can produce inaccurate or fixed sub-millisecond results.<ref>{{cite web|url=http://forum.flightgear.org/viewtopic.php?f=30&t=29259|title=Nasal: systime() ??!?|author=Necolatis|date=Apr 2nd, 2016}}</ref> 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]}}<br />
|example1 = print("Unix time: ", systime()); # prints Unix time<br />
|example2 = var myFunc = func(){<br />
for(var i = 0; i <= 10; i += 1){<br />
print("Interation #", i);<br />
}<br />
}<br />
var t = systime(); # record time<br />
myFunc(); # run function<br />
var t2 = systime(); # record new time<br />
print("myFunc() took ", t2 - t, " seconds"); # print result<br />
}}<br />
<br />
=== thisfunc() ===<br />
{{Nasal doc<br />
|syntax = thisfunc();<br />
|source = {{fgdata file|Nasal/globals.nas|t=Source}}<br />
|text = Returns the function from which this function is called. This allows a function to reliably and safely call itself from within a closure.<br />
|example1 = var stringify_vec = func(input){<br />
if (typeof(input) == "scalar"){<br />
return sprintf("%s", input);<br />
} elsif (typeof(input) == "vector") {<br />
if (size(input) == 0) return "[]";<br />
var this = thisfunc();<br />
var buffer = "[";<br />
for(var i = 0; i < size(input); i += 1){<br />
buffer ~= this(input[i]);<br />
if (i == size(input) - 1) {<br />
buffer ~= "]";<br />
} else {<br />
buffer ~= ", ";<br />
}<br />
}<br />
return buffer;<br />
} else {<br />
die("stringify_vec(): Error! Invalid input. Must be a vector or scalar");<br />
}<br />
}<br />
<br />
var test_vec = ["a", "b", "c", 1, 2, 3];<br />
debug.dump(stringify_vec(test_vec)); # prints "[a, b, c, 1, 2, 3]"<br />
test_vec = [];<br />
debug.dump(stringify_vec(test_vec)); # prints "[]"<br />
test_vec = {};<br />
debug.dump(stringify_vec(test_vec)); # will throw an error<br />
}}<br />
<br />
=== tileIndex() ===<br />
{{Nasal doc<br />
|syntax = tileIndex();<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1720|t=Source}}<br />
|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 <code>942050</code>, corresponding to <tt>''[[$FG_SCENERY]]/Terrain/w130n30/w123n3/942050.stg''</tt>.<br />
|example1 = print(tileIndex()); # print index<br />
}}<br />
<br />
=== tilePath() ===<br />
{{Nasal doc<br />
|syntax = tilePath();<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1712|t=Source}}<br />
|text = Returns the base path of the tile at the aircraft's current position as a string. For example, at KSFO, this would be <code>w130n30/w123n3</code>, corresponding to <tt>''[[$FG_SCENERY]]/Terrain/w130n30/w123n3''</tt>.<br />
|example1 = print(tilePath()); # print path<br />
}}<br />
<br />
=== values() ===<br />
{{Nasal doc<br />
|syntax = values(hash);<br />
|source = {{fgdata file|Nasal/globals.nas|t=Source}}<br />
|text = Returns a vector containing the values of the given hash.<br />
|param1 = hash<br />
|param1text = Mandatory hash to get the values of.<br />
|example1 = var hash = {<br />
"a": 1,<br />
"b": 2,<br />
"c": 3<br />
};<br />
<br />
foreach(var val; values(hash)){<br />
print(val);<br />
}<br />
|example2text = The below example does exactly the same thing as the above example, but does not use <code>values()</code>:<br />
|example2 = var hash = {<br />
"a": 1,<br />
"b": 2,<br />
"c": 3<br />
};<br />
<br />
foreach(var key; keys(hash)){<br />
print(hash[key]);<br />
}<br />
}}<br />
<br />
== Extension functions new in FG 2020.1 ==<br />
The following functions have been added to the nasal core library and will be released with FlightGear version 2020.1. <br />
Before the release they are available in the development branch "next".<br />
<br />
=== isfunc() ===<br />
Returns 1 if type or argument is a function, otherwise 0.<br />
<br />
=== isghost() ===<br />
Returns 1 if type or argument is a ghost, otherwise 0.<br />
<br />
=== ishash() ===<br />
Returns 1 if type or argument is a hash, otherwise 0.<br />
<br />
=== isint() ===<br />
{{Nasal doc<br />
|syntax = isint(x);<br />
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}<br />
|text = Returns 1 if argument has a numeric value and x == floor(x), e.g. integer.<br />
}}<br />
<br />
=== isnum() ===<br />
{{Nasal doc<br />
|syntax = isnum(x);<br />
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}<br />
|text = Returns 1 if typeof(x) is "scalar" and x has a numeric value otherwise 0. <br />
}}<br />
<br />
=== isscalar() ===<br />
{{Nasal doc<br />
|syntax = isscalar(x);<br />
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}<br />
|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 "~". <br />
|example1 = var a = "foo"; <br />
var b=42;<br />
if (isscalar(a) and isscalar(b)) print(a~b);<br />
if (isstr(a)) print("a is a string");<br />
if (isint(b)) print("b is an integer");<br />
# if (isscalar(a))... is equivalent to if (typeof(a) == "scalar")...<br />
}}<br />
<br />
=== isstr() ===<br />
Returns 1 if type or argument is a string, otherwise 0. <br />
<br />
=== isvec() ===<br />
Returns 1 if type or argument is a vector, otherwise 0.<br />
<br />
=== vecindex() ===<br />
{{Nasal doc<br />
|syntax = vecindex(vector, value);<br />
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}<br />
|text = Returns the index of value or nil, if value is not found in vector.<br />
|example1=<br />
var myvector = ["apple", "bananna", "coconut"];<br />
# to check if a vector contains a certain value compare vecindex to nil<br />
if (vecindex(myvector, "apple") != nil) {<br />
print("found apple");<br />
}<br />
# WARNING: this will not work as desired because index is 0<br />
if (vecindex(myvector, "apple")) {<br />
print("found apple");<br />
}<br />
<br />
}}<br />
<br />
{{note| There is a similar function function contains(hash, key) to check if a hash contains a key (not value!).<br />
It is to be discussed, if contains can/should be extended to support vectors in the above mentioned way. }}<br />
<br />
== Variables ==<br />
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}}.<br />
<br />
=== D2R ===<br />
{{Nasal doc<br />
|syntax = var radians = degrees * D2R;<br />
|text = Converts an angle from degrees to radians when multiplied by the angle in degrees. Equal to <code>π / 180</code>.<br />
}}<br />
=== FPS2KT ===<br />
{{Nasal doc<br />
|syntax = var knots = feet_per_second * FPS2KT;<br />
|text = Converts a velocity from feet per second to knots when multiplied by the velocity in feet per second. Approximately equal to 0.5925.<br />
}}<br />
=== FT2M ===<br />
{{Nasal doc<br />
|syntax = var metres = feet * FT2M;<br />
|text = Converts a length from feet to metres when multiplied by the length in feet. Equal to 0.3048.<br />
}}<br />
=== GAL2L ===<br />
{{Nasal doc<br />
|syntax = var litres = gallons * GAL2L;<br />
|text = Converts a volume from US liquid gallons to litres when multiplied by the volume in gallons. Approximately equal to 3.7854.<br />
}}<br />
=== IN2M ===<br />
{{Nasal doc<br />
|syntax = var metres = inches * IN2M;<br />
|text = Converts a length from inches to metres when multiplied by the length in inches. Equal to 0.0254.<br />
}}<br />
=== KG2LB ===<br />
{{Nasal doc<br />
|syntax = var pounds = kilograms * KG2LB;<br />
|text = Converts a mass from kilograms to pounds when multiplied by the mass in kilograms. Approximately equal to 2.2046.<br />
}}<br />
=== KT2FPS ===<br />
{{Nasal doc<br />
|syntax = var feet_per_second = knots * KT2FPS;<br />
|text = Converts a velocity from knots to feet per second when multiplied by the velocity in knots. Approximately equal to 1.6878.<br />
}}<br />
=== KT2MPS ===<br />
{{Nasal doc<br />
|syntax = var metres_per_second = knots * KT2MPS;<br />
|text = Converts a velocity from knots to metres per second when multiplied by the velocity in knots. Approximately equal to 0.5144.<br />
}}<br />
=== L2GAL ===<br />
{{Nasal doc<br />
|syntax = var gallons = litres * L2GAL;<br />
|text = Converts a volume from litres to US liquid gallons when multiplied by the volume in litres. Approximately equal to 0.2642.<br />
}}<br />
=== LB2KG ===<br />
{{Nasal doc<br />
|syntax = var kilograms = pounds * LB2KG;<br />
|text = Converts a mass from pounds to kilograms when multiplied by the mass in pounds. Approximately equal to 0.4536.<br />
}}<br />
=== M2FT ===<br />
{{Nasal doc<br />
|syntax = var feet = metres * M2FT;<br />
|text = Converts a length from metres to feet when multiplied by the length in metres. Approximately equal to 3.2808.<br />
}}<br />
=== M2IN ===<br />
{{Nasal doc<br />
|syntax = var inches = metres * M2IN;<br />
|text = Converts a length from metres to inches when multiplied by the length in metres. Approximately equal to 39.3701.<br />
}}<br />
=== M2NM ===<br />
{{Nasal doc<br />
|syntax = var nautical_miles = metres * M2NM;<br />
|text = Converts a length from metres to nautical miles when multiplied by the length in metres. Approximately equal to 0.00054.<br />
}}<br />
=== MPS2KT ===<br />
{{Nasal doc<br />
|syntax = var knots = metres_per_second * MPS2KT;<br />
|text = Converts a velocity from metres per second to knots when multiplied by the velocity in metres per second. Approximately equal to 1.9438.<br />
}}<br />
=== NM2M ===<br />
{{Nasal doc<br />
|syntax = var metres = nautical_miles * NM2M;<br />
|text = Converts a length from nautical miles to metres when multiplied by the length in nautical miles. Equal to 1,852.<br />
}}<br />
=== R2D ===<br />
{{Nasal doc<br />
|syntax = var degrees = radians * R2D;<br />
|text = Converts an angle from radians to degrees when multiplied by the angle in radians. Equal to <code>180 / π</code>.<br />
}}<br />
<br />
{{Appendix}}<br />
<br />
<br />
{{Nasal namespaces}}</div>Jsbhttps://wiki.flightgear.org/w/index.php?title=Illuminator_addon&diff=130337Illuminator addon2021-02-16T21:06:20Z<p>Jsb: </p>
<hr />
<div>'''Illuminator''' is a configuration utility for lights in [[FlightGear]].<br />
<br />
You can use it to fine tune landing lights etc<br />
<br />
== Prerequisites ==<br />
To make use of this addon<br />
* Download the addon at https://sourceforge.net/p/flightgear/fgaddon/HEAD/tree/trunk/Addons/Illuminator/ and add it via the launcher or command line (see [[Addons]] for more information)<br />
* For the time being you need the development version aka "next".<br />
* Add XML light definition to your aircraft. This has to be done manually as the addon can only configure existing lights.<br />
<br />
=== Adding lights to an aircraft ===<br />
See [[Compositor#Lights]] for a sample XML and explanation. The UFO was modified to contain spot lights to illustrate the use of this addon.<br />
== Dialog window ==<br />
[[File:Fgfs-20210205170950 v1.jpg|thumb|Light configuration dialog.]]{{Warning|Addon works only for spot lights|padding=0|margin=left|width=50%}}<br />
You can select one or two lights to configure. If the checkbox for 2nd light is checked, configuration changes will be done simultaneously for both selected lights.<br />
{{Note|To find a light by name, click the props button (top right), go up one level in the property browser and ctrl-click on the "." entry to activate verbose mode.|margin=left|width=50%}}<br />
Use the direction sliders to can be selected inverted (multiplied by -1) for the 2nd light. This is useful if you have a left and a right light and you want to turn them to the center or outside.<br />
<br />
=== Direction ===<br />
'''It is assumed that the XML definition for the light uses the pitch, roll, heading elements for direction. Other variants have not been tested and may lead to unexpected results or even crash.'''<br />
<br />
=== Range and attenuation ===<br />
To make things easy, there are presets (taken from the website referenced on the [[Compositor]] page). Select a predefined range (in meters) by clicking the left or right button. <br />
<br />
The attenuation parameters are set accordingly, but can be modified again. <br />
<br />
== Known issues ==<br />
Configuration works only for spot lights.<br />
[[Category:FlightGear addons]]</div>Jsbhttps://wiki.flightgear.org/w/index.php?title=Illuminator_addon&diff=130145Illuminator addon2021-02-07T21:46:28Z<p>Jsb: </p>
<hr />
<div>Illuminator is a configuration utility for lights in FlightGear.<br />
<br />
You can use it to fine tune landing lights etc<br />
<br />
== Prerequisites ==<br />
To make use of this addon<br />
* Download the Addon at https://sourceforge.net/p/flightgear/fgaddon/HEAD/tree/trunk/Addons/Illuminator/ and add it via the launcher or command line (see [[Addons]] for more information)<br />
* For the time being you need the development version aka "next" with simgear patched (merge request pending at the time of writing).<br />
* Add XML light definition to your aircraft. This has to be done manually as the addon can configure only existing lights.<br />
<br />
=== Adding lights to an aircraft ===<br />
See [[Compositor#Lights]] for a sample XML and explanation. The UFO was modified to contain lights to illustrate the use of this addon.<br />
== Dialog window ==<br />
You can select one or two lights to configure. If the checkbox for 2nd light, configuration changes will be done simultaneously for both selected lights.<br />
<br />
Use the direction sliders to can be selected inverted (multiplied by -1) for the 2nd light. This is useful if you have a left and a right light and you want to turn them to the center or outside.<br />
<br />
'''It is assumed that the XML definition for the light uses the pitch, roll, heading elements for direction. other variants have not been tested and may lead to unexpected results or even crash.'''<br />
<br />
=== Range and attenuation ===<br />
To make things easy, there are presets (taken from the website referenced on the [[Compositor]] page). Select a predefined range (in meters) by clicking the left or right button. <br />
<br />
The attenuation parameters are set accordingly. <br />
<br />
[[File:Fgfs-20210205170950 v1.jpg|thumb|light configuration dialog]]</div>Jsbhttps://wiki.flightgear.org/w/index.php?title=Illuminator_addon&diff=130095Illuminator addon2021-02-06T12:57:50Z<p>Jsb: </p>
<hr />
<div>Illuminator is a configuration utility for lights in FlightGear.<br />
<br />
You can use it to fine tune landing lights etc<br />
<br />
== Prerequisites ==<br />
To make use of this addon<br />
* Download the Addon at https://sourceforge.net/p/flightgear/fgaddon/HEAD/tree/trunk/Addons/Illuminator/ and add it via the launcher or command line (see [[Addons]] for more information)<br />
* For the time being you need the development version aka "next" with simgear patched (merge request pending at the time of writing).<br />
* Add XML light definition to your aircraft. This has to be done manually as the addon can configure only existing lights.<br />
<br />
=== Adding lights to an aircraft ===<br />
See [[Compositor#Lights]] for a sample XML and explanation. The UFO was modified to contain three spot lights to illustrate the use of this addon (merge request pending at the time of writing).<br />
<br />
<br />
== Dialog window ==<br />
You can select one or two lights to configure. If the checkbox for 2nd light, configuration changes will be done simultaneously for both selected lights.<br />
<br />
Use the direction sliders to can be selected inverted (multiplied by -1) for the 2nd light. This is useful if you have a left and a right light and you want to turn them to the center or outside.<br />
<br />
'''It is assumed that the XML definition for the light uses the pitch, roll, heading elements for direction. other variants have not been tested and may lead to unexpected results or even crash.'''<br />
<br />
=== Range and attenuation ===<br />
To make things easy, there are presets (taken from the website referenced on the [[Compositor]] page). Just select a range preset (number <br />
<br />
[[File:Fgfs-20210205170950 v1.jpg|thumb|light configuration dialog]]</div>Jsbhttps://wiki.flightgear.org/w/index.php?title=Addon&diff=130094Addon2021-02-06T12:33:18Z<p>Jsb: /* Installing and using an addon */</p>
<hr />
<div>[[File:Fgaddonslogo202x89.png|right]] <br />
To make it easier to create '''addons''' there is since FlightGear 2017.3 a new way to create addons. In essence FlightGear will load an overlay XML into the property tree and start a well known Nasal function.<ref>{{cite web<br />
|url = https://forum.flightgear.org/viewtopic.php?p=314620#p314620 <br />
|title = <nowiki> Re: New Feature: Addon - "API" </nowiki> <br />
|author = <nowiki> Torsten </nowiki> <br />
|date = Jul 19th, 2017 <br />
|added = Jul 19th, 2017 <br />
|script_version = 0.40 <br />
}}</ref><br />
<br />
We now have a simple way to add addons to FlightGear without the need to mess around with <code>FGData/Nasal</code> or <code>FGHome/Nasal</code> directories.<ref name="Forum_announcement">{{cite web<br />
|url = https://forum.flightgear.org/viewtopic.php?p=314563#p314563 <br />
|title = <nowiki> New Feature: Addon - "API" </nowiki> <br />
|author = <nowiki> Torsten </nowiki> <br />
|date = Jul 18th, 2017 <br />
}}</ref><br />
<br />
{{TOC limit|3}}<br />
<br />
== Installing and using an addon ==<br />
Download and copy the addon to a directory on your computer.<br />
<br />
If you use the launcher, select the Add-ons page from the icon bar on the left, then find the section Add-on Module folders and click the Add(+) button. Select the folder where you put the addon. Once the addon is shown in the list, make sure its check box is selected.<br />
<br />
Use the command line switch <code>--addon=/path/to/some/addon</code>.<ref name="Forum_announcement"/><br />
<br />
== Creating an addon ==<br />
There is a very simple Skeleton addon available in FGAddon to be used as a template.<ref name="Forum_announcement"/> See {{fgaddon source|path=Addons/Skeleton}}.<br />
<br />
A leading slash (<code>/</code>) in this section indicates the base directory of the directory structure of the addon.<br />
<br />
=== Minimum configuration ===<br />
An addon may be installed in a directory anywhere on your hard disk and need at least two files:<br />
<br />
* <code>/addon-config.xml</code> - A standard [[PropertyList XML files|PropertyList XML file]] to be used to populate or modify the [[property tree]]. (Same as to be used in <code>--config=foo.xml</code>)<br />
* <code>/addon-main.nas</code> - The Nasal hook for the logic. This file needs a function called <code>main()</code> which will be called from the global addon initializer (<code>addons.nas</code>)<br />
<br />
=== Additional common files ===<br />
* <code>/addon-metadata.xml</code> - A PropertyList XML file with metadata about the addon it.<br />
* <code>/addon-menubar-items.xml</code> - A PropertyList XML file describing menus to be added to the FlightGear menu bar.<br />
* <code>/gui/dialogs/&lt;my-foobar-dialog&gt;.xml</code> - PropertyList XML files to create custom dialogs.<br />
<br />
=== Good to know ===<br />
The new addon mechanism lets you add a <code>addon-config.xml</code> to override the settings in <code>defaults.xml</code> and other files.<br />
<br />
That will allow an addon to<br />
* Override key bindings (as in the spoken ATC addon)<br />
* Add or override autopilots and property rules<br />
* Introduce XML state machines<br />
<br />
Unless your really want to add/change/remove those at runtime, this should cater for most use cases.<ref>{{cite web<br />
|url = https://forum.flightgear.org/viewtopic.php?p=314902#p314902 <br />
|title = <nowiki> Re: Spoken </nowiki> <br />
|author = <nowiki> Torsten </nowiki> <br />
|date = Jul 23rd, 2017 <br />
|added = Jul 23rd, 2017 <br />
|script_version = 0.40 <br />
}}</ref><br />
<br />
We have some instructions how to use SVN [[FGAddon|in our wiki]]. It covers mostly aircraft development but the workflow is pretty much the same for addons.<ref>{{cite web<br />
|url = https://forum.flightgear.org/viewtopic.php?p=314647#p314647 <br />
|title = <nowiki> Re: Spoken ATC </nowiki> <br />
|author = <nowiki> Torsten </nowiki> <br />
|date = Jul 19th, 2017 <br />
|added = Jul 19th, 2017 <br />
|script_version = 0.40 <br />
}}</ref><br />
<br />
== Addon initialization ==<br />
On initialization fgfs takes care of:<br />
* Through {{flightgear source|path=src/Main/options.cxx|text=<code>options.cxx</code>}}:<ref name="Forum_announcement"/><br />
** Creating a property under <code>/addons/addon[n]/path=/path/to/some/addon</code><br />
** Loading <code>/path/to/some/addon/addon-config.xml</code> into the property tree (same as <code>--config=/path/to/some/addon/addon-config.xml</code>)<br />
** Adding <code>/path/to/some/addon</code> to the list of allowed directories (same as <code>--fg-aircraft=/path/to/some/addon</code>)<br />
* Through {{fgdata source|path=Nasal/addons.nas|text=<code>addons.nas</code>}}:<br />
** Loading <code>/foo/bar/baz/addon-main.nas</code> into namespace <code>__addon[ADDON_ID]__</code><br />
** Calling <code>main(addonGhost)</code> from <code>/foo/bar/baz/addon-main.nas</code>.<br />
<br />
== APIs ==<br />
{{hatnote|For more details about these APIs, see the readme file, {{readme file|add-ons}}.}}<br />
=== C++ API ===<br />
There is a C++ API on FlightGear's side that handle some on the addon related tasks manly through the <code>AddonManager()</code>, <code>Addon()</code> and <code>AddonVersion()</code> classes.<br />
<br />
=== Nasal API ===<br />
The Nasal add-on API lives in the 'addons' namespace and can for example do queries to <code>AddonManager()</code> and read data from <code>addons.Addon</code> objects. It can for example compare addon versions if there is dependencies.<br />
<br />
== Background ==<br />
{{See also|Howto:Creating a simple modding framework}}<br />
<br />
ATC chatter was removed in 2015, see fgdata commit: [https://sourceforge.net/p/flightgear/fgdata/ci/81607f734e13add9be02816ddaec305d05bc4e47/ 81607f734e13add9be02816ddaec305d05bc4e47]<br />
<br />
And the devel list messages referenced in the commit log.<br />
the other relevant commit is this: b60736ba7add2a7cd39af3d8a974d5be3ea46e8b<br />
It would not be very difficult to restore this functionality, or even generalize/improve it significantly (which was the scope of the original discussion on the devel list)<ref>{{cite web<br />
|url = https://forum.flightgear.org/viewtopic.php?p=288388#p288388 <br />
|title = <nowiki> Re: Whatever happened to radom radio </nowiki> <br />
|author = <nowiki> Hooray </nowiki> <br />
|date = Jun 11th, 2016 <br />
|added = Jun 11th, 2016 <br />
|script_version = 0.40 <br />
}}</ref><br />
<br />
The restored functionality could be distributed as a tarball that is extracted into $FG_ROOT - alternatively, into $FG_HOME, because Nasal files there are treated as overlays, which basically means that you can install user-specific extensions there without having to tamper with $FG_ROOT, it would only take very minor changes to turn the chatter feature into a corresponding "addon" - not unlike fgcamera ...<ref>{{cite web<br />
|url = https://forum.flightgear.org/viewtopic.php?p=288392#p288392 <br />
|title = <nowiki> Re: Whatever happened to radom radio </nowiki> <br />
|author = <nowiki> Hooray </nowiki> <br />
|date = Jun 11th, 2016 <br />
|added = Jun 11th, 2016 <br />
|script_version = 0.40 <br />
}}</ref><br />
<br />
We should absolutely stop telling anyone to edit preferences.xml in FG_ROOT; any documentation or advice which says to should be changes ASAP. <ref>{{cite web<br />
|url = https://forum.flightgear.org/viewtopic.php?p=192632#p192632 <br />
|title = <nowiki> Re: NavCache:init failed:Sqlite error:Sqlite API abuse </nowiki> <br />
|author = <nowiki> zakalawe </nowiki> <br />
|date = Oct 26th, 2013 <br />
|added = Oct 26th, 2013 <br />
|script_version = 0.40 <br />
}}</ref><br />
<br />
As of 12/2017, the addon API is in the process of being significantly updated <ref>https://sourceforge.net/p/flightgear/mailman/message/36146017/</ref> <ref>https://sourceforge.net/p/flightgear/mailman/message/36150159/</ref> <ref>https://sourceforge.net/p/flightgear/mailman/message/36150444/</ref><br />
<br />
== List of Addons ==<br />
You can find the official repository at {{fgaddon source|path=Addons}}<br />
<br />
* ATC Chatter (ported by Torsten) {{progressbar|100}}<br />
* [[Cargo Towing Addon]] <ref>{{cite web<br />
|url = https://forum.flightgear.org/viewtopic.php?t=36824<br />
|title = <nowiki> Re: Cargo Towing Addon </nowiki> <br />
|author = <nowiki> Wayne Bragg (wlbragg) </nowiki> <br />
|date = <br />
|added = <br />
|script_version = 0.40 <br />
}}</ref><br />
* cockpit-view (work in progress by wkitty42)<ref>{{cite web<br />
|url = https://forum.flightgear.org/viewtopic.php?p=316498#p316498 <br />
|title = <nowiki> Re: </nowiki> <br />
|author = <nowiki> wkitty42 </nowiki> <br />
|date = Aug 13th, 2017 <br />
|added = Aug 13th, 2017 <br />
|script_version = 0.40 <br />
}}</ref><br />
* [[Earthview#Customization]] - High resolution customization<br />
* [[FaceTrackNoIR]] (ported by HHS)<ref>{{cite web<br />
|url = https://sourceforge.net/p/flightgear/mailman/message/36454826/<br />
|title = <nowiki> Re: </nowiki> <br />
|author = <nowiki> Unknown, HHS</nowiki><br />
|date = Nov 1th, 2018 <br />
}}</ref> - An addon to interface this [[Head tracking|head tracker]] with FlightGear<br />
* [https://github.com/slawekmikula/flightgear-addon-fgcamera FGCamera] - ([https://github.com/slawekmikula/flightgear-addon-fgcamera/blob/master/Docs/manual.md manual]) - [[FGCamera | Wiki Page]]<br />
* [[FGPlot]]<br />
* [[Ground Services]] (ported by ThomasS)<ref>{{cite web<br />
|url = https://forum.flightgear.org/viewtopic.php?p=316400#p316400 <br />
|title = <nowiki> Re: </nowiki> <br />
|author = <nowiki> ThomasS </nowiki> <br />
|date = Aug 12th, 2017 <br />
|added = Aug 12th, 2017 <br />
|script_version = 0.40 <br />
}}</ref><br />
* [[Landing Rate addon]] [https://forum.flightgear.org/viewtopic.php?f=6&t=33101&p=327787#p327787]<br />
* [https://gitlab.com/mdanil/flightgear-hax mdanilov hax!]: landing evaluation and aircraft development tools, TerraSync toggler<br />
* [[Model Cockpit View]] <br />
* [[Oscilloscope addon]] - Allows displaying a property of Nasal function over time<br />
* [[PAR instrument]] - Precision Approach Radar and Ground Controlled Approach<br />
* [[Red Griffin ATC]] - Speaking Air Traffic Controller<ref>{{cite web<br />
|url = https://forum.flightgear.org/viewtopic.php?f=6&t=36755 <br />
|title = <nowiki> Re: </nowiki> <br />
|author = <nowiki> RedGriffin </nowiki> <br />
|date = Jan 6th, 2020 <br />
|added = Jan 6th, 2020 <br />
|script_version = 1.0.0 RC2 <br />
}}</ref><br />
* [[Spoken ATC]] (ported by Torsten)<ref>{{cite web<br />
|url = https://forum.flightgear.org/viewtopic.php?p=314095#p314095 <br />
|title = <nowiki> Re: </nowiki> <br />
|author = <nowiki> Torsten </nowiki> <br />
|date = Jul 10th, 2017 <br />
|added = Jul 10th, 2017 <br />
|script_version = 0.40 <br />
}}</ref><br />
* [[Spoken GCA]] - An offline ground controlled approach (GCA) addon<br />
* [[YASim Development Tools]] (by jsb)<br />
* [https://github.com/slawekmikula/flightgear-addon-vfrflight VFRFlight integration] - ([https://github.com/slawekmikula/flightgear-addon-vfrflight/blob/master/doc/manual.md manual])<br />
* [https://github.com/slawekmikula/flightgear-addon-protocolkml KML Exporter (Google Earth)] - ([https://github.com/slawekmikula/flightgear-addon-protocolkml/blob/master/doc/manual.md manual])<br />
* [https://github.com/slawekmikula/flightgear-addon-vfrnavigator VFR Flying Helper] - ([https://github.com/slawekmikula/flightgear-addon-vfrnavigator/blob/master/doc/usage.md manual])<br />
* [https://github.com/slawekmikula/flightgear-addon-linuxtrack LinuxTrack Head Tracker integration] - ([https://github.com/slawekmikula/flightgear-addon-linuxtrack/blob/master/doc/manual.md manual])<br />
* [https://github.com/slawekmikula/flightgear-addon-hudheli Additional Heli HUD's] - ([https://github.com/slawekmikula/flightgear-addon-hudheli/blob/master/doc/manual.md manual]) - encapsulation of HeliHUD package as an addon<br />
* [https://gitlab.com/mdanil/flightgear-mickey Tiny HUD for mouse flying in FlightGear] - Visual feedback for mouse flying, to make up for mouse's lack of self-centering<br />
* [https://github.com/slawekmikula/flightgear-addon-littlenavmap LittleNavMap integration] - ([https://github.com/slawekmikula/flightgear-addon-littlenavmap/blob/master/doc/manual.md manual])<br />
<br />
== Experimental Addons ==<br />
<br />
Addons which are in the development stage/unfinished but can be used as a quick view of addon functionality<br />
* [https://github.com/slawekmikula/flightgear-addon-missions FlightGear Missions addon] - Add-on for missions/adventures code<br />
<br />
== Ideas ==<br />
=== Hooking into features using legacy OpenGL code ===<br />
{{See also|Unifying the 2D rendering backend via canvas}}<br />
In 09/2018, James suggested that legacy features using raw OpenGL calls (e.g. HUD/2D panels) could be easily replaced via scripted Canvas/Nasal solutions at the mere cost of providing a mechanism to hook into the legacy code implementing these features (namely, the HUD/instrumentation subsystems) <ref>https://sourceforge.net/p/flightgear/mailman/message/36399261/</ref> <ref>https://sourceforge.net/p/flightgear/mailman/message/36399261/</ref><br />
<br />
=== Catalog & Package Manager support ===<br />
if this works with more complex, pre-existing addons such as [[Bombable]], where the file layout is a replica of the old FGData layout, these types of mature addons might be better as SourceForge FlightGear sub-projects rather than being copied into FGAddon. Maybe the config file and nasal script could be used to automate the installation process ?<ref>{{cite web<br />
|url = https://sourceforge.net/p/flightgear/mailman/message/35953179/ <br />
|title = <nowiki> Re: [Flightgear-devel] Simple API for creating FlightGear<br />
addons/plugins </nowiki> <br />
|author = <nowiki> Edward d'Auvergne </nowiki> <br />
|date = Jul 19th, 2017 <br />
|added = Jul 19th, 2017 <br />
|script_version = 0.40 <br />
}}</ref><br />
The [[Bombable]] addon is one of the most popular addons out there, and a large number of aircraft in FGAddon have bombable support, so it is worth not forgetting about. Especially if the addon system one day becomes automated through a [[Catalog metadata|catalog.xml type system]]<ref>{{cite web<br />
|url = https://sourceforge.net/p/flightgear/mailman/message/35953650/ <br />
|title = <nowiki> Re: [Flightgear-devel] Simple API for creating FlightGear<br />
addons/plugins </nowiki> <br />
|author = <nowiki> Edward d'Auvergne </nowiki> <br />
|date = Jul 19th, 2017 <br />
|added = Jul 19th, 2017 <br />
|script_version = 0.40 <br />
}}</ref><br />
<br />
== References ==<br />
{{Appendix}}<br />
<br />
== Related content ==<br />
=== Wiki articles ===<br />
* [[FlightGear configuration via XML]]<br />
* [[FlightGear configuration via XML#preferences.xml]]<br />
* [[Nasal]]<br />
* [[Property tree]]<br />
* [[Properties persistent between sessions]]<br />
* [[PropertyList XML File]]<br />
<br />
=== Forum topics ===<br />
* {{forum link|t=32561|title=New Feature: Addon - "API"}}<br />
<br />
=== Readme files ===<br />
* {{readme file|add-ons}}<br />
* {{readme file|gui}} - Details on how to add menus and custom dialogs.<br />
<br />
=== Source code ===<br />
==== FGAddon ====<br />
* {{fgaddon source|path=Addons/Skeleton}} - Skeleton addon to be used as a template.<br />
<br />
==== FGData ====<br />
* {{fgdata source|path=Nasal/addons.nas}}<br />
<br />
==== FlightGear ====<br />
* {{flightgear source|path=src/Main/options.cxx}}<br />
* {{flightgear source|path=src/Add-ons/}}<br />
<br />
[[Category:FlightGear addons| ]]</div>Jsbhttps://wiki.flightgear.org/w/index.php?title=Illuminator_addon&diff=130091Illuminator addon2021-02-05T22:26:05Z<p>Jsb: Created page with "Illuminator is a configuration utility for lights in FlightGear. You can use it to fine tune landing lights etc == Prerequisites == To make use of this addon * Download the..."</p>
<hr />
<div>Illuminator is a configuration utility for lights in FlightGear.<br />
<br />
You can use it to fine tune landing lights etc<br />
<br />
== Prerequisites ==<br />
To make use of this addon<br />
* Download the Addon at https://sourceforge.net/p/flightgear/fgaddon/HEAD/tree/trunk/Addons/Illuminator/<br />
* For the time being you need the development version aka "next" with simgear patched<br />
* Add XML light definition to your aircraft (see [[Compositor#Lights]] and UFO for an example). This has to be done manually as the addon can configure only existing lights.<br />
<br />
== Dialog window ==<br />
You can select one or two lights to configure. If the checkbox for 2nd light, configuration changes will be done simultaneously for both selected lights.<br />
The direction sliders can be selected inverted (multiplied by -1) for the 2nd light. This is useful if you have a left and a right light and you want to turn them to the center or outside.<br />
<br />
[[File:Fgfs-20210205170950 v1.jpg|thumb|light configuration dialog]]</div>Jsbhttps://wiki.flightgear.org/w/index.php?title=File:Fgfs-20210205170950_v1.jpg&diff=130088File:Fgfs-20210205170950 v1.jpg2021-02-05T17:15:10Z<p>Jsb: User created page with UploadWizard</p>
<hr />
<div>=={{int:filedesc}}==<br />
{{Information<br />
|description={{en|1=light configuration dialog}}<br />
|date=2021-02-05<br />
|source={{own}}<br />
|author=[[User:Jsb|Jsb]]<br />
|permission=<br />
|other versions=<br />
}}<br />
<br />
=={{int:license-header}}==<br />
{{self|cc-by-sa-4.0}}<br />
<br />
<br />
<br />
[[Category:Addons]]</div>Jsbhttps://wiki.flightgear.org/w/index.php?title=Using_listeners_and_signals_with_Nasal&diff=129998Using listeners and signals with Nasal2021-01-27T13:57:45Z<p>Jsb: /* setlistener() */</p>
<hr />
<div>{{Template:Nasal Navigation}}<br />
<br />
== Behind the Scenes of Listeners ==<br />
<br />
Repeatedly calling getprop() to update a property is called polling. Doing that at frame rate (each time a frame is updated) can become pretty expensive and often isn't necessary, especially for properties that are rarely updated.<br />
<br />
Polling is only really recommended for properties that change per frame or really often. Otherwise, you will probably want to use listeners or less-frequent polling using a custom timer.<br />
<br />
For example, a listener would be quite appropriate for an on/off switch or similar, an update loop for everything that needs to be updated each frame/update (i.e. engine rpm), and if something is changing every frame anyways or is a tied property, you might as well use the update loop for several reasons: besides the fact that it might be necessary, it linearizes your data flow and moves it closer to the use of the data, it makes use of the current known efficiency of getprop(), and I would venture that it seems most familiar to you. I really wouldn't worry about listener efficiency here<br />
<br />
The props.Node wrappers are slower than getprop/setprop because there's more Nasal-space overhead. Intuitively, the props.Node stuff should be faster, because of the cached reference - but the getprop()/setprop() code is native C code that uses a heavily optimized algorithm to look up properties (this may change once the props bindings start using the new cppbind framework).<br />
<br />
There will certainly be a break-even point, depending on how often properties change - and how many properties are involved. But usually, listeners should be superior to polling at frame rate for properties that do not change per frame.<br />
<br />
Keep in mind that listeners are not "background processes" at all - a listener will be triggered by the property tree once a node is accessed, which will invoke the Nasal callback. Timers and listeners are NOT background "processes". They are just invoked by different subsystems, i.e. the property tree (listeners) or the events subsystem (timers). There are other subsystems that can also invoke Nasal handlers, such as the GUI system or the AI code. This all takes place inside the FG main loop (=main thread), not some separate background/worker thread. Which is also the reason why all the Nasal APIs are safe to be used.<br />
<br />
It is important to understand that a "listener" is a passive thing, i.e. a "list" (array) of optional functions that are to be invoked whenever a property is modified/written to - thus, once you modify the property, the property tree code will check the size of the "callback list" that contains callbacks that are to be notified (called) when the property is written - and then calls each callback in a foreach() loop.<br />
<br />
Which is to say that those performance monitor stats are not necessarily representative when it comes to Nasal callbacks invoked as timers/listeners, which also applies to C++ code using these two APIs (timers & listeners).<br />
<br />
<br />
Listeners are not actively "listening" at all - there's no "listener watch" running - unlike timers, listeners are totally passive instead - it works basically like this:<br />
<br />
* register a listener for some property named "foo", to call some Nasal code<br />
* which just adds a callback to a property specific vector, NOTHING else.<br />
* once setprop("foo", value) is called<br />
* the property tree is updated<br />
* next the property tree checks if any listeners are registered for that branch<br />
* if there are listeners attached, they are all called (there's basically a vector of listeners)<br />
<br />
So listeners are not really "processes" at all - neither background nor foreground: Merely their *callbacks* become processes after being invoked. Otherwise, they're just a vector of Nasal callbacks - which are only ever called if the property is modified. In other words, there's basically zero cost. Listener overhead is mainly determined by the callback's payload - not by listening (which is just checking vector.size() != 0 and calling each element), unless the property is updated frequently (in terms of frame rate)<br />
<br />
Admittedly, having many callbacks/listeners attached, could also add up quickly.<br />
<br />
So for benchmarking purposes, you can just use a closure to wrap your callback and update your systime() object accordingly, you could provide a separate "timed_listener" function or just override setlistener().<br />
<br />
== Listeners and Signals ==<br />
The important thing to keep in mind is that custom listeners are generally not about loading or running Nasal files, most of the Nasal files are loaded and executed implicitly during startup. Only the Nasal sub modules (i.e. inside their own [[$FG_ROOT]]/Nasal/ directory) are dynamically loaded using a listener). So listeners are really just a simple way to talk to the property tree:<br />
'''Hey property tree, would you please be so kind to call this Nasal function whenever this property is modified?'''<br />
<br />
So, listeners are callback functions that are attached to property nodes. They are triggered whenever the node is written to, or, depending on the listener type, also when children are added or removed, and when children are written to. Unlike polling loops, listeners don't have the least effect on the frame rate when they aren't triggered, which makes them preferable to monitor properties that aren't written to frequently. <br />
<br />
To learn more about managing resources like timers and listeners, please see [[Developing and debugging Nasal code#Managing timers and listeners]].<br />
<br />
===setlistener() vs. _setlistener() ===<br />
You are requested '''not''' to use the raw _setlistener() function, except in files in [[$FG_ROOT]]/Nasal/ when they are<br />
needed immediately. Only then the raw function is required, as it doesn't rely on props.nas. Using setlistener() once props.nas is loaded allows using high-level objects to reference properties, instead of raw C-objects (called "ghosts").<br />
<br />
'''Note:''' Once [[Nasal/CppBind|cppbind]] is used to replace props.nas, _setlistener() will be deprecated because the builtin function will be effectively using the same mechanism as what the wrapper function (the current setlistener()) does right now.<br />
<br />
===<tt>When listeners don't work</tt>===<br />
Unfortunately, '''listeners don't work on so-called "tied" properties''' when the node value isn't set via property methods. Tied properties are a semi-deprecated API to allow C++ code to handle the value directly and control getting/setting directly, usually avoiding the property tree altogether. (You can spot such tied properties by Ctrl-clicking the "." entry in the property browser: they are marked with a "T".) The problem comes when the C++ value is written to outside of the property tree, which means that the property tree doesn't receive a notification, even though normal sets via the property tree would still fire the listeners. Most of the FDM properties are "tied", and a few in other subsystems.<br />
<br />
Examples of properties where setlistener ''won't'' work: <br />
<br />
* /position/elevation-ft<br />
* /ai/models/aircraft/orientation/heading-deg<br />
* /instrumentation/marker-beacon/[inner|middle|outer]<br />
* Any property node created as an alias<br />
* Lots of others<br />
<br />
Before working to create a listener, always check whether a listener will work with that property node by control-clicking the "." in property browser to put it into verbose mode, and then checking whether the property node for which you want to set up a listener is marked with a "T" or not.<br />
<br />
If you can't set a listener for a particular property, the alternative is to use settimer to set up a timer loop that checks the property value regularly. <br />
<br />
Listeners are most efficient for properties that change only occasionally. No code is called at all during frames where the listener function is not called. If the property value changes every frame, setting up a settimer loop with time=0 will execute every frame, just the same as setlistener would, and the settimer loop is more efficient than setting a listener. This is one reason the fact the setlistener doesn't work on certain tied and FDM properties is not a great loss. See the section on timer loops below.<br />
<br />
=== <tt>setlistener()</tt> ===<br />
<br />
Syntax:<br />
<br />
<syntaxhighlight lang="nasal"><br />
var listener_id = setlistener(<property>, <function> [, <startup=0> [, <runtime=1>]]);<br />
</syntaxhighlight><br />
<br />
'''<property>''' The first argument is a property node object (<tt>props.Node()</tt> hash) or a property path. Because the node hash depends on the props.nas module being loaded, <tt>setlistener()</tt> calls need to be deferred when used in an [[$FG_ROOT]]/Nasal/*.nas file, usually by calling them in a <tt>settimer(func {}, 0)</tt> construction. To avoid that, one can use the raw <tt>_setlistener()</tt> function directly, for which <tt>setlistener()</tt> is a wrapper. The raw function does only accept node paths (e.g. "/sim/menubar/visibility"), but not props.Node() objects.<br />
<br />
'''<function>''' The second argument is a function object (not a function call!). The function you pass here will be called with the target property node as its sole argument as soon as someone writes to the property.<br />
<br />
'''<startup=0>''' The third argument is optional. If it is non-zero, then it causes the listener to be called initially (but not if runtime is 1). This is useful to let the callback function pick up the node value at startup.<br />
<br />
'''<runtime=1>''' The fourth argument is optional, and defaults to 1. This means that the callback function will be executed whenever the property is written to, independent of the value. <br />
<br />
If the argument is set to 0, then the function will only get triggered if a value other than the current value is written to the node. This is important for cases where a property is written to once per frame, no matter if the value changed or not. YASim, for example, does that for /gear/gear/wow or /gear/launchbar/state.<br />
So, this should be used for properties that are written to in every frame, although the written value is mostly the same. If the argument is 2, then also write access to children will get reported, as well as the creation and removal of children nodes.<br />
<br />
For both optional flags 0 means less calls, and 1 means more calls. The first is for startup behavior, and the second for runtime behavior.<br />
<br />
Here's a real-life example:<br />
<br />
<syntaxhighlight lang="nasal"><br />
setlistener("/gear/launchbar/state", func {<br />
if (cmdarg().getValue() == "Engaged")<br />
setprop("/sim/messages/copilot", "Engaged!");<br />
}, 1, 0);<br />
</syntaxhighlight><br />
<br />
YASim writes once per frame the string "Disengaged" to property /gear/launchbar/state. When an aircraft on deck of the aircraft carrier locks into the catapult, this changes to "Engaged", which is then written again in every frame, until the aircraft leaves the catapult. Because the locking in is a bit difficult -- one has to target the sensitive area quite exactly --, it was desirable to get some quick feedback: a screen message that's also spoken by the Festival speech synthesis. With the args 1 and 0, this is done initially (for the unlikely case that we are locked in from the beginning), and then only when the node changes from an arbitrary value to "Engaged".<br />
<br />
<tt>setlistener()</tt> returns a unique listener id on success, and <tt>nil</tt> on error. The id is nothing else than a counter that is 0 for the first Nasal listener, 1 for the second etc. You need this id number to remove the listener. Most listeners are never removed, so that one doesn't assign the return value, but simply drop it.<br />
<br />
'''Listener callback functions''' can access up to four values via regular function arguments.<br />
<br />
The first two of which are property nodes in the form of a <tt>props.Node()</tt> object hash. <br />
<br />
The third is a indication of the operation: 0 for changing the value, -1 for removing a child node, and +1 for adding a child.<br />
<br />
The fourth indicates whether the event occurred on the node that was listened to and is always 0 if the previous argument is not 0.<br />
<br />
Here is the syntax supposing you have set a callback function named ''myCallbackFunc'' via <tt>setlistener</tt> (''setlistener(myNode, myCallbackFunc)''):<br />
<br />
<syntaxhighlight lang="nasal"><br />
myCallbackFunc([<changed_node> [, <listened_to_node> [, <operation> [, <is_child_event>]]]])<br />
</syntaxhighlight><br />
<br />
You cannot refer to OOP methods directly -- to do so, enclose the function call in a func() { } block as such:<br />
<syntaxhighlight lang="nasal"><br />
signal.listener = setlistener(node, func() { me.phaseFunc(); }, 0, 0);<br />
</syntaxhighlight><br />
<br />
=== <tt>removelistener()</tt> ===<br />
<br />
Syntax:<br />
<br />
<syntaxhighlight lang="nasal"><br />
var num_listeners = removelistener(<listener id>);<br />
</syntaxhighlight><br />
<br />
<tt>removelistener()</tt> takes one argument: the unique listener id that a <tt>setlistener()</tt> call returned. It returns the number of remaining active Nasal listeners on success, <tt>nil</tt> on error, or -1 if a listener function applies <tt>removelistener()</tt> to itself. The fact that a listener can remove itself, can be used to implement a one-shot listener function:<br />
<br />
<syntaxhighlight lang="nasal"><br />
var L = setlistener("/some/property", func {<br />
print("I can only be triggered once.");<br />
removelistener(L);<br />
});<br />
</syntaxhighlight><br />
<br />
{{FGCquote<br />
|1= For those not following all the cvs logs: I've added a new function to Nasal a few days ago: removelistener(). It takes one argument -- the unique id number of a listener as returned by setlistener(): var foo = setlistener("/sim/foo", die); ... removelistener(foo); This can be used to remove all listeners in an <unload> part that were set by the <load> part of a scenery object: <load> listener = []; append(listener, setlistener("/sim/foo", die)); append(listener, setlistener("/sim/bar", func {}); ... </load> <unload> foreach (l; listener) { removelistener(l) } </unload> screen.nas stores all relevant listener ids in a hash, so that other parts can, for example, remove the mapping of pilot messages to screen and voice): removelistener(screen.listener["pilot"]); The id is 0 for the first listener, 1 for the second etc. removelistener() returns the total number of remaining listeners, or nil on error (i.e. if there was no listener known with this id). This can be used for statistics: id = setlistener("/sim/signals/quit", func {}); # let's not count this one num = removelistener(id); print("there were ", id, " Nasal listeners attached since fgfs was started"); print("of which ", num, " are still active"); m.<br />
|2= {{cite web<br />
| url = http://sourceforge.net/p/flightgear/mailman/message/12102466/<br />
| title = <nowiki>[Flightgear-devel] Nasal: new command "removelistener()"</nowiki><br />
| author = <nowiki>Melchior FRANZ</nowiki><br />
| date = Mar 2nd, 2006<br />
| added = Mar 2nd, 2006<br />
| script_version = 0.23<br />
}}<br />
}}<br />
<br />
=== Listener Examples ===<br />
<br />
The following example attaches an anonymous callback function to a "signal". The function will be executed when FlightGear is closed.<br />
<br />
<syntaxhighlight lang="nasal"><br />
setlistener("/sim/signals/exit", func { print("bye!") });<br />
</syntaxhighlight><br />
<br />
Instead of an anonymous function, a named function can be used as well:<br />
<br />
<syntaxhighlight lang="nasal"><br />
var say_bye = func { print("bye") }<br />
setlistener("/sim/signals/exit", say_bye);<br />
</syntaxhighlight><br />
<br />
Callback functions can, optionally, access up to four parameters which are handed over via regular function arguments. Many times none of these parameters is used at all, as in the above example.<br />
<br />
Most often, only the first parameter is used--which gives the node of the changed value.<br />
<br />
The following code attaches the monitor_course() function to a gps property, using the argument ''course'' to get the node with the changed value.<br />
<br />
<syntaxhighlight lang="nasal"><br />
var monitor_course = func(course) {<br />
print("Monitored course set to ", course.getValue());<br />
}<br />
var i = setlistener("instrumentation/gps/wp/leg-course-deviation-deg", monitor_course);<br />
<br />
# here the listener is active<br />
<br />
removelistener(i); # remove that listener again<br />
</syntaxhighlight><br />
<br />
Here is code that accesses two arguments--the changed node and the listened-to node (these may be different when monitoring all children of a certain node)--and also shows how to monitor changes to a node including changes to children:<br />
<br />
<syntaxhighlight lang="nasal"><br />
var monitor_course = func(course, flightinfo) {<br />
print("One way to get the course setting: ", flightinfo.leg-course-deviation-deg.getValue());<br />
print("Another way to get the same setting ", course.getValue());<br />
}<br />
var i = setlistener("instrumentation/gps/wp", monitor_course, 0, 2);<br />
</syntaxhighlight><br />
<br />
The function object doesn't need to be a separate, external function -- it can also be an anonymous function made directly in the <tt>setlistener()</tt> call:<br />
<br />
<syntaxhighlight lang="nasal"><br />
setlistener("/sim/signals/exit", func { print("bye") }); # say "bye" on exit<br />
</syntaxhighlight><br />
<br />
Beware, however, that the contents of a function defined within the <tt>setlistener</tt> call are not evaluated until the call is actually made. If, for instance, local variables change before the setlistener call happens, the call will reflect the current value of those variables ''at the time the callback function is called'', not the value ''at the time the listener was set''. <br />
<br />
For example, with this loop, the function will always return the value 10--even if mynode[1], mynode[2], mynode[3] or any of the others is the one that changed. It is because the contents of the setlistener are evaluated after the loop has completed running and at that point, i=10:<br />
<br />
<syntaxhighlight lang="nasal"><br />
var output = func(number) {<br />
print("mynode", number, " has changed!"); #This won't work!<br />
}<br />
for(i=1; i <= 10; i = i+1) {<br />
var i = setlistener("mynode["~i~"]", func{ output (i); });<br />
}<br />
</syntaxhighlight><br />
<br />
You can also access the four available function properties (or just one, two, or three of them as you need) in your anonymous function. Here is an example that accesses the first value:<br />
<br />
<syntaxhighlight lang="nasal"><br />
for(i=1; i <= 10; i = i+1) {<br />
var i = setlistener("mynode["~i~"]", func (changedNode) { print (changedNode.getPath() ~ " : " ~ changedNode.getValue()); });<br />
}<br />
</syntaxhighlight><br />
<br />
Attaching a function to a node that is specified as <tt>props.Node()</tt> hash:<br />
<br />
<syntaxhighlight lang="nasal"><br />
var node = props.globals.getNode("/sim/signals/click", 1);<br />
setlistener(node, func { gui.popupTip("don't click here!") });<br />
</syntaxhighlight><br />
<br />
Sometimes it is desirable to call the listener function initially, so that it can pick up the node value. In the following example a listener watches the view number, and turns the HUD on in cockpit view, and off in all other views. It doesn't only do that on writing to "view-number", but also once when the listener gets attached, thanks to the third argument "1":<br />
<br />
<syntaxhighlight lang="nasal"><br />
setlistener("/sim/current-view/view-number", func(n) {<br />
setprop("/sim/hud/visibility[0]", n.getValue() == 0);<br />
}, 1);<br />
</syntaxhighlight><br />
<br />
There's no limit for listeners on a node. Several functions can get attached to one node, just as one function can get attached to several nodes. Listeners may write to the node they are listening to. This will not make the listener call itself causing an endless recursion.<br />
<br />
=== Signals ===<br />
<br />
In addition to "normal" nodes, there are "signal" nodes that were created solely for the purpose of having listeners attached:<br />
<br />
* <tt>/sim/signals/exit</tt> ... set to "true" on quitting FlightGear<br />
* <tt>/sim/signals/reinit</tt> ... set to "true" right before repositioning the aircraft, and to "false" when the teleport has been completed<br />
* <tt>/sim/signals/click</tt> ... set to "true" after a mouse click at the terrain. Hint that the geo coords for the click spot were updated and can be retrieved from /sim/input/click/{longitude-deg,latitude-deg,elevation-ft,elevation-m}<br />
* <tt>/sim/signals/screenshot</tt> ... set to "true" right before the screenshot is taken, and set to "false" after it. Can be used to hide and reveal dialogs etc.<br />
* <tt>/sim/signals/nasal-dir-initialized</tt> ... set to "true" after all Nasal "library" files in [[$FG_ROOT]]/Nasal/ were loaded and executed. It is only set once and can only be used to trigger listener functions that were defined in one of the Nasal files in that directory. After that signal was set Nasal starts loading and executing aircraft Nasal files, and only later are <tt>settimer()</tt> functions called and the next signal is set:<br />
* <tt>/sim/signals/fdm-initialized</tt> ... set to "true" when then FDM has just finished its initialization<br />
* <tt>/sim/signals/reinit-gui</tt> ... set to "true" when the GUI has just been reset (e.g. via Help menu). This is used by the gui.Dialog class to reload Nasal-loaded XML dialogs.<br />
* <tt>/sim/signals/frame</tt> ... triggered at the beginning of each iteration of the main loop (a.k.a. "frame"). This is meant for debugging purposes. Normally, one would just use a settimer() with interval 0 for the same effect. The difference is that the signal is guaranteed to be raised at a defined moment, while the timer call may change when subsystems are re-ordered.<br />
<br />
=== Nasal code coupled to the autopilot system ===<br />
{{Callback Disclaimer}}<br />
<br />
Some people have a need to run Nasal code at the same rate as the simulation/FDM. Currently, without modifying the source code for FlightGear, the only way to do this is to find a property updated at the right time in the simulation cycle and set a listener on it. From a code quality standpoint, this is less than ideal.<br />
<br />
Autopilot rules, FDM and important instruments run at fixed rate of 120Hz and are already _independant_ of frame rate (Note: this does not help those who try to implement APs manually using Nasal, since Nasal can only run at frame rate. But please do use the "autopilot property rule" system for the fast control part of the autopilot - and only do slow stuff in Nasal (such as switching between autopilot modes), which does not require a close coupling to the FDM/autopilot. The 777 is a good example showing this: dynamic part of AP is done by property rules; switching between AP modes, like "hold glideslope" => "flare" is done in Nasal). <br />
<br />
The FDM runs 120 times per second (if so configured), but it runs all iterations for a frame one after the other, then waits until the next frame. The FDM runs at 120 hertz and with a fixed time step.<br />
<br />
However, we play one small trick to make that happen. We take the time that has elapsed since the last frame, compute how many whole iterations of the<br />
FDM will fit in that time slice (at 1/120th of a second per iteration.) Then we invoke the FDM that many times with a time step of 1/120th of a<br />
second. Finally we save out the remainder and add that into the next time slice.<br />
<br />
This can produce a small amount of temporal jitter between the graphics and the fdm if the graphics frame rates are not a diviser of 120. In the best<br />
case scenario, you've locked your graphics frame rate to 60 hz so the FDM runs exactly 2 iterations every time it is invoked and there is no temporal<br />
jitter at all, ever.<br />
<br />
One thing to keep in mind is that handing a different size time slice to the FDM every frame (and sometimes that time slice could be 1 second or more?)<br />
can lead to instabilities in the math. So our approach is intended to avoid that potential problem. As far as the FDM is concerned, it *is* running<br />
asyncronously, at a fixed time step. But, we are playing a little trick on the FDM (it doesn't care) in order to handle the unfortunate possibility of<br />
non-fixed and highly variable frame rates on PC hardware running consumer grade operating systems.<br />
<br />
Some FDM stuff would like to be tied to the FDM update rate, and that's a desirable goal. What about a callback function then? The FDM subsystem would set /sim/signals/fdm-update, and you could attach a listener to that which does all the things that should interact with the FDM, such as AP, FCS, etc. The rest of Nasal would keep running with the frame rate.<br />
<br />
There's just one (minor) problem at the moment. There's no generic FDM update() function where one could put a sig.setDoubleValue(dt).This would have to be done in all FDMs.<br />
<br />
Another possibility is to extend the declarative expression logic, which is already supported by the autopilot components, to allow a Nasal expression. Then you mix the declarative components (which you're going to want for most autopilot laws) with some scripted ones.<br />
Since the expression evaluation would be driven by the autopilot subsystem, it would run at whatever frame-rate that itself runs at - which is currently in lock-step with the FDM [http://forum.flightgear.org/viewtopic.php?f=46&t=17069&hilit=nasal+fdm+autopilot&start=15#p165596].<br />
<br />
There are several other options, such as 1) Run a second events system and add an additional parameter to Nasal's settimer allowing you to use this new events system. 2) Add in a signal that is fired each simulation step, probably right before the Autopilot system is run:<br />
<br />
<syntaxhighlight lang="nasal"><br />
setlistener("/sim/signals/fdm-update", func(n) {<br />
var dt = n.getValue();<br />
# ... and whatever needs to be done at fdm rate<br />
});<br />
</syntaxhighlight><br />
<br />
Some core developers are fairly opposed to the whole idea, i.e. want to avoid *any* Nasal in the fast simulation loop of the FDM, because Nasal execution is slow and non-deterministic because of its GC issue. Running it in the fast simulation loop is the last thing they want[http://www.mail-archive.com/flightgear-devel@lists.sourceforge.net/msg38172.html].<br />
<br />
In addition, the developer and maintainer of the AP system is planning on adding Nasal bindings to the AP code to allow runtime instantiation of ap/property rules and has a Nasal binding for that in mind[http://forum.flightgear.org/viewtopic.php?f=66&t=15189&p=149376&hilit=#p149376]. <br />
<br />
You can then use Nasal for the high level stuff, and enable/disable/switch the individual controller elements (e.g. in order to automatically switch the autopilot mode when capturing the ILS). There are some nice examples with fgdata/Git aircraft. You could look at the 777.<br />
<br />
The big advantage of the property rules is that they don't produce garbage that a garbage collector has to clean up. But as nothing in life comes for free (except FlightGear, of course) XML tends to be much more verbose.<br />
<br />
Guideline<br />
* Computing properties from a well defined set of other properties once per frame: use a property rule.<br />
* If there is no other way to get it done: use Nasal.<br />
<br />
This is also how such things are done in the real world: controllers aren't implemented in imperative programming languages these days - especially not in scripting languages. People use model-based design and connect controller elements - using graphical tools like MATLAB/Simulink. Obviously, FG is missing a graphical interface to specify the controller rules - but the idea of specifying through XML is the same and specification is straight forward.<br />
<br />
Creating an autopilot (or any GNC or system model, for that matter) can be done very effectively with discrete objects such as summers, gains, controllers, filters, switches, etc., much as JSBSim has done with the system components. This is a standard approach in industry, as exemplified by Mathwork's $imulink product. <br />
<br />
Scilab/Scicos is similar in concept. Control system topologies are often diagrammed in a way that can lead to a one-to-one correspondence between a<br />
block and a control system object that can be referenced in an XML file, if the control system component library has been defined properly. This, again,<br />
is the way that JSBSim has approached the solution.<br />
<br />
Some benefits to such an approach include better testability, more predictability, and easier interface (someday) with a GUI tool, should one<br />
materialize. The downside is that XML can be verbose.<br />
<br />
All that being said, it is definitely possible to run Nasal in the FDM update loop, so to make up your own mind, you could try this:<br />
Alternatively, as a short-term solution that does not rely on editing C++ code, you could just trigger off an internal autopilot property.<br />
Which is a pretty clever workaround: trigger a boolean property for each iteration, so that the Nasal listener can pick it up, which will get invoked at FDM update rate that way.<br />
<br />
Perhaps we could set up a trigger that is fired by the Autopilot subsystem, immediately before the autopilot executes. However, someone more familiar with FG's code base than me can probably come up with a more elegant solution. I feel like adding a new events system and a parameter to settimer is cleaner than a signal -- and that if a signal is added, the Autopilot subsystem is probably not the place to add it.<br />
<br />
Also see:<br />
* http://forum.flightgear.org/viewtopic.php?f=66&t=15189&p=149376&#p149376<br />
* http://forum.flightgear.org/viewtopic.php?f=46&t=17069<br />
* http://www.mail-archive.com/flightgear-devel@lists.sourceforge.net/msg38170.html<br />
* http://www.mail-archive.com/flightgear-devel@lists.sourceforge.net/msg13022.html<br />
* https://code.google.com/p/flightgear-bugs/issues/detail?id=421</div>Jsbhttps://wiki.flightgear.org/w/index.php?title=Bombardier_CRJ700_series&diff=129984Bombardier CRJ700 series2021-01-23T20:27:22Z<p>Jsb: /* External links */</p>
<hr />
<div>{{:{{PAGENAME}}/info}}<br />
[[File:CRJ700-cockpit-night.jpg|The cockpit at night, demonstrating night lighting capabilities|thumb|350px|right]]<br />
[[File:CRJ700-cabin-night.jpg|The interior cabin, also at night|thumb|350px|right]]<br />
<br />
The '''Bombardier CRJ700 series''' is a family of 70- to 100- seat regional jet airliners manufactured by Bombardier Aerospace. Its primary competition is the Embraer E-jet series. The family consists of the Bombardier CRJ700, CRJ900, and CRJ1000. The CRJ700 series was developed from the [[Bombardier CRJ-200LR|Bombardier CRJ200]], and remains one of the most popular regional airliner series in the world.<br />
<br />
=Variants=<br />
<br />
* CRJ700 - the original CRJ700<br />
* CRJ700ER - extended range version<br />
* CRJ700LR - long-range version<br />
* CRJ900 - the first stretch of the airframe<br />
* CRJ900ER - extended range version<br />
* CRJ900LR - long-range version<br />
* CRJ1000 - the final stretch of the airframe<br />
* CRJ1000 EuroLite - low-range/low-cost version targeted at European operators<br />
* CRJ1000ER - extended range version<br />
<br />
=Aircraft help=<br />
<br />
HTML documentation is included in the Docs/ folder.<br />
<br />
==Key commands==<br />
<br />
{| class="keytable"<br />
! Key<br />
! Function<br />
|-<br />
! colspan="2" | Speedbrakes/spoilers<br />
|-<br />
| {{key press|Ctrl|B}}<br />
| Cycle speedbrake setting<br />
|-<br />
| {{key press|j}}<br />
| Decrease ground lift dump setting<br />
|-<br />
| {{key press|k}}<br />
| Increase ground lift dump setting<br />
|-<br />
! colspan="2"|Engines<br />
|-<br />
| {{key press|Ctrl|Del}}<br />
| Arm/disarm thrust reversers<br />
|-<br />
| {{key press|Del}}<br />
| Toggle thrust reversers<br />
|-<br />
! colspan="2" | Autopilot<br />
|-<br />
| {{key press|Ctrl|A}}<br />
| Toggle autopilot altitude mode<br />
|-<br />
| {{key press|Ctrl|F}}<br />
|Engage/disengage autopilot<br />
|-<br />
| {{key press|Ctrl|G}}<br />
| Toggle autopilot approach mode<br />
|-<br />
| {{key press|Ctrl|H}}<br />
| Toggle autopilot heading mode<br />
|-<br />
| {{key press|Ctrl|N}}<br />
| Toggle autopilot NAV mode<br />
|-<br />
| {{key press|Ctrl|P}}<br />
| Set autopilot basic pitch mode<br />
|-<br />
| {{key press|Ctrl|W}}<br />
| Set autopilot basic heading/roll mode<br />
|-<br />
| {{key press|F6}}<br />
| Cycle NAV source<br />
|-<br />
! colspan="2" | Tiller/Nose gear steering<br />
|-<br />
| {{key press|q}}<br />
| Steer tiller left<br />
|-<br />
| {{key press|Shift|Q}}<br />
| Set tiller to full left<br />
|-<br />
| {{key press|w}}<br />
| Center tiller<br />
|-<br />
| {{key press|Shift|W}}<br />
| Pop up tiller dialog<br />
|-<br />
| {{key press|e}}<br />
| Steer tiller right<br />
|-<br />
| {{key press|Shift|E}}<br />
| Set tiller to full right<br />
|-<br />
! colspan="2" | Walk view<br />
|-<br />
| {{key press|r}}<br />
| Walk forward toward view<br />
|-<br />
| {{key press|Shift|R}}<br />
| Run forward toward view<br />
|-<br />
| {{key press|f}}<br />
| Walk backwards from view<br />
|-<br />
| {{key press|y}}<br />
| View point down<br />
|-<br />
| {{key press|Y}}<br />
| View point up<br />
|}<br />
<br />
==Simplified procedures==<br />
===Startup===<br />
[[File:Start-123.png|thumb|right|CRJ700 Startup (1,2,3)]]<br />
#Battery switch ... on<br />
#APU start/stop switch ... on<br />
#Bleed air source ... automatic<br />
#Fuel Pumps ... enable<br />
#Engine 1 thrust lever ... idle<br />
#Engine 1 start sequence ... initiated<br />
#Engine 2 thrust lever ... idle<br />
#Engine 2 start sequence ... initiated<br />
#Engine 1 electric generator ... on<br />
#Engine 2 electric generator ... on<br />
#APU start/stop switch ... off<br />
<br />
===Takeoff===<br />
#Flaps/slats ... 8 or 20<br />
#Landing lights ... on<br />
#Engine thrust modes ... TO/GA<br />
<br />
===Climb and cruise===<br />
#Engine thrust modes ... climb<br />
Nominal cruise speed Mach 0.78 (0.80 for CRJ900)<br />
<br />
===Approach and landing===<br />
Remain below 250 knots below 10,000 feet at all times<br />
#Thrust reversers ... armed<br />
#Obey speed limits for flaps and gear extension<br />
#Flaps/slats on final ... 45<br />
<br />
===Shutdown===<br />
#Parking break ... set<br />
#External power ... select<br />
#Engine thrust levers ... shutoff<br />
#Engine 1 electric generator ... off<br />
#Engine 2 electric generator ... off<br />
<br />
=Downloading=<br />
<br />
The latest version is always available on the official FlightGear aircraft download page and will be committed regularly to [[FGAddon]].<br />
<br />
The latest development version at GitHub can also be downloaded [https://github.com/d-jsb/CRJ700-family/archive/master.zip here].<br />
<br />
=Aircraft of the Week/Month=<br />
<br />
The CRJ700 was reviewed by Thorsten R as part of the "Aircraft of the Week/Month" feature on the FlightGear Forums.<br />
<br />
====Model====<br />
<br />
The 3d cockpit of the CRJ-700 is very detailed and contains not only functionality on the main panel but also on the central console and an overhead panel. However, as often seen, the cockpit surfaces not covered by instrumentation are very simply textured by a monochromatic grey color - a more natural texture resembling a real material with some wear and tear could add a lot here.<br />
<br />
[http://www.phy.duke.edu/~trenk/pics/crj700-cockpit.jpg View image]<br />
<br />
There is a lot of functionality in the cockpit - a startup procedure using the APU is supported as well as a full set of external lights and no-smoking and seat belt signs for the passengers. A nice touch of the model is that it also contains an interior view of the cabin in which the signs can be observed. Switches and knobs are usually animated, and the clickspots are placed fairly intuitively. The MFD's all have various selectable functions.<br />
<br />
The exterior model of the aircraft is also very nicely done, contains good animations and shows all the light switch settings in the cockpit faithfully. An option to switch livery exists as well.<br />
<br />
[http://www.phy.duke.edu/~trenk/pics/crj700-model.jpg View image]<br />
<br />
====Flight characteristics====<br />
<br />
The FDM of the CRJ-700 is quite plausible for an airliner of its size. I haven't really tested the behaviour at the edge of the performance envelope, but during normal operations, the plane behaves well and reaches basic performance characteristics. All in all, the plane is an airliner (albeit a small one) - it turns slowly, it does not descent rapidly without picking up a lot of excess speed and all maneuvers need to be planned well in advance. Having said that, it's actually fun to fly.<br />
<br />
I found the AP nicely tuned and able to fly turns, climb or dive at all altitudes without oscillations or weird behaviour. Unfortunately, the support for AP modes involving navaids is not as good: While I could home in on a VOR station, the AP seems to be tracking the station rather than the chosen radial, i.e. the angle under which I approached the station kept changing. Also, I was unable to intercept an ILS glideslope automatically. However, the plane can be operated well in IFR conditions just using the instruments to display the navaid signals and the heading and altitude modes of the AP to control the plane. <br />
<br />
====My personal wishlist====<br />
<br />
Fixing the AP to a more consistent behaviour with navaids would be high on my priority list - there are currently very few airliners with a well-tuned AP capable of tracking navaids. Some more natural texturing in the cockpit would also be nice. I realize that the CRJ-700 is a fairly recent addition to the aircraft repository of Flightgear - but I like where the model is going very much.<br />
<br />
====Things to experience====<br />
<br />
Try flying at night - the CRJ-700 has one of the best light concepts I've ever seen. The instrument lights are very beautifully done and can be dimmed at need, the cabin lights likewise, and all lighting is visible both from internal and external views.<br />
<br />
<br />
<br />
''Editor's note: The autopilot VOR-LOC/ILS holds have been fixed as of CRJ700 v1.0.2.''<br />
<br />
=Development status/Issues/Todo=<br />
==Key features==<br />
* Startup and shutdown procedures<br />
* Mostly complete cockpit<br />
* Realistic autopilot<br />
* Functional flight management system (FMS), advisory-only vertical navigation system (VNAV), and control display unit (CDU)<br />
* Emergency procedures with ram air turbine (RAT) and auxiliary power unit (APU)<br />
* Small library of tutorials<br />
* Cockpit, cabin, and exterior model lightmaps<br />
* Reflection shader on the engines<br />
* Developed aircraft systems<br />
* Tiller steering system<br />
<br />
==Todo==<br />
For rating information, see [[Formalizing Aircraft Status]].<br />
===FDM (current rating 3)===<br />
* Make something a little more refined than my [[YASim]] solution (any [[JSBSim]] masters?)<br />
===Exterior (current rating 4)===<br />
* Add minor elements like antennas, ram air turbine, improve gear bay texturing and geometry<br />
* More liveries!<br />
===Flight deck (current rating 4)===<br />
* Improve texturing<br />
* Model circuit breakers<br />
===Systems (current rating 3)===<br />
* Add more pages to EICAS<br />
<br />
== Development (section added May 2015) ==<br />
D-JSB et al. work on the CRJ700 family.<br />
<br />
=== Status without any particular order ===<br />
==== done ====<br />
* Some bugfixes (e.g. MFD wind indicator, nav pointers, MFD symbols)<br />
* Removed 2D panel which depended on Boing 737-300<br />
* More views (cabin windows, gear)<br />
* OHP switches / switchlights (gimmick: implemented the light test button)<br />
* Details of auto pilot panel behaviour like push function of CRS and HDG<br />
* Modified slats/flaps indicator on EICAS<br />
* APU details on EICAS<br />
* EICAS pages for doors<br />
* Simulation of hydraulic system<br />
* EICAS pages for hydraulic system<br />
* Extend failures dialog (hydraulics)<br />
* Re-implement the electrical system (AC and DC). <br />
* Add cargo doors to model<br />
* Add APU door to model<br />
<br />
==== In progress / testing ====<br />
* Update of documentation<br />
<br />
==== Planed ====<br />
* Canvas implementation of glas cockpit<br />
<br />
== Canvas implementation (planed) ==<br />
[[CRJ 700 family canvas ]]<br />
<br />
=External links=<br />
* [http://en.wikipedia.org/wiki/Bombardier_CRJ700_series Bombardier CRJ700 series at Wikipedia]<br />
* [http://forum.flightgear.org/viewtopic.php?t=11863 Development thread at the FlightGear Forums]<br />
* [http://www.smartcockpit.com/data/...700.../HGS_00_Pilots_Guide_CRJ_00.pdf GHS Pilots Guide CRJ]<br />
* [http://aligero.us/files/FMS%204200.pdf FMS 4200 Intro]<br />
* [http://www.4shared.com/get/Q_rDeIsz/Collins_FMS-4200_Flight_Manage.html FMS-4200 Manual]<br />
* [https://360.schnurstracks.de/panoramafotografie/cockpit/cockpit-panorama-bombardier.html?initvars.internlink=1 360° cockpit photo]<br />
<br />
{{Template:Bombardier}}</div>Jsbhttps://wiki.flightgear.org/w/index.php?title=Controller-pilot_data_link_communication&diff=129906Controller-pilot data link communication2021-01-14T16:49:48Z<p>Jsb: update merge status</p>
<hr />
<div>{{WIP}}<br />
<br />
'''Controller-pilot data link communication''' (CPDLC) is a direct connection between ATC and aircraft through which pilots and controllers can communicate based on text, thereby relieving radio traffic. <br />
Its use is increasing everywhere in real life, and an extension to FlightGear is currently under development to support it in the multi-player environment.<br />
<br />
== Status in FlightGear multi-player ==<br />
[[File:CPDLC-generic-dialog.png|thumb|Screenshot of the generic CPDLC dialog add-on]]<br />
[[File:A320-CPDLC.png|thumb|Displays the A320 CPDLC cockpit instrumentation]]<br />
Recent efforts have already taken place towards implementing it in MP:<br />
* the [[Virtual FSWeekend Hackathon 2020]] attracted a team to work on the [[Hackathon Proposal: CPDLC|CPDLC proposal]];<br />
* CPDLC extension was merged to 'next' in January 2021;<br />
* a generic CPDLC dialog add-on is available (see screenshot); Nasal helpers in this addon should be moved to FGDATA once stable (needs feedback from aircraft developers)<br />
* work is currently in progress on at least one cockpit CPDLC panel;<br />
* ATC client [[ATC-pie]] is fully operational, ready to service its first logons to come.</div>Jsbhttps://wiki.flightgear.org/w/index.php?title=File:CPDLC-generic-dialog.png&diff=129905File:CPDLC-generic-dialog.png2021-01-14T16:45:11Z<p>Jsb: Jsb uploaded a new version of File:CPDLC-generic-dialog.png</p>
<hr />
<div>=={{int:filedesc}}==<br />
{{Information<br />
|description={{en|1=Screenshot of the generic CPDLC dialog add-on}}<br />
|date=2020-12-13<br />
|source={{own}}<br />
|author=[[User:Mickybadia|Mickybadia]]<br />
|permission=<br />
|other versions=<br />
}}<br />
<br />
=={{int:license-header}}==<br />
{{self|cc-by-sa-4.0}}<br />
<br />
<br />
<br />
[[Category:Screenshots]]</div>Jsbhttps://wiki.flightgear.org/w/index.php?title=CPDLC&diff=129023CPDLC2020-11-16T13:55:29Z<p>Jsb: </p>
<hr />
<div>{{WIP}}<br />
<br />
'''Controller-pilot data link communication''' (CPDLC) is a direct connection between ATC and aircraft through which controllers can communicate with pilots based on text, thereby relieving radio traffic. <br />
Its use is increasing everywhere in real life, while no aircraft is equipped in FlightGear yet.<br />
<br />
== Recent efforts ==<br />
Recent efforts have already taken place towards making it possible:<br />
* [[ATC-pie]] already integrates CPDLC: it is working for other session types (e.g. solo), and an IRC-based (simple text) infrastructure for FG sessions is already in place, including transfers between data authorities, etc. (all it needs now is to be contacted by aircraft!)---see [[#Available ATC-pie IRC scheme]] further down;<br />
* work is currently in progress on a cockpit CPDLC panel;<br />
* a CPDLC middleware is under dev, required at this point because FG models cannot open their own TCP connections.<br />
<br />
At the moment we would be interested in users to take up this project in the Hackathon, any assistance whatsoever would be welcome.<br />
<br />
== Issues/ideas ==<br />
The following could be done in the hackathon:<br />
* write the Nasal to exchange the right data with the outside system;<br />
* write the IRC hook to connect FlightGear to IRC (not least a generic capability, that other applications may use)<br />
* involving core dev's at least for the design choice, a way to get rid of the middleware eventually, e.g. allow Nasal connections to FG-approved servers (like irc.flightgear.org) or integrate specific accesses in the C++ code base (sort of the same history as FGCom, going from standalone executables to an option in the FG menu)?<br />
* can someone create a sketch of a block diagram of the elements that need to communicate and the connections between them? -- p callahan Done -- J Redpath<br />
<br />
[[File:CPDLC Block.png|thumb|Block diagram for CPDLC subsystem]]<br />
The block diagram describes how such a system might operate. The crucial elements are the IRC Hook and the FlightGear API. These are what the aircraft / ATC talk to directly, and therefore where input and output occur.<br />
<br />
The IRC hook will require elements such as:<br />
* Connection<br />
* Respond to PING <br />
* Disconnection<br />
* Transmit and receive PRIVMSG to a specified UID<br />
* Error handling - if UID does not exist, if server disconnects, if ...<br />
<br />
The FlightGear API will require elements such as:<br />
* Connection<br />
* Disconnection<br />
* Transmit + receive message element (including message history as a vector)<br />
<br />
One option is to have a <br />
/sim/network/cpcdlc/received/<br />
message<br />
sender<br />
status<br />
.. etc ….<br />
<br />
and then an additional<br />
<br />
/sim/network/cpcdlc/signals/message-received<br />
<br />
Then in C++ you fill in all the ‘received’ values from your socket callback, and fire value changed on the signal property. This will allow a Nasal listener to process the received data.<br />
<br />
For message history, you could either have multiple received nodes (e.g. received[0], [1], [2]) or else a Nasal hook that accesses a vector.<br />
<br />
For transmitted messages, a similar scheme would work; however, what would probably be better would be:<br />
<br />
<tt>fgcommand(‘cpcdlc-transmit’, props.Node.new({‘receient’:’blah’, ‘mesage’:’foobarzot’,<etc, etc>});</tt><br />
<br />
We’d use the same to establish the connection:<br />
<br />
<tt>fgcommand(‘cpcdlc-connect’, props.Node.new({<br />
’server’: “foo.bar.com’,<br />
‘port’ : 666,<br />
‘callsign’ : ‘wibble’,<br />
<etc, etc?<br />
‘recv-path’ : ‘/sim/network/cpcdlc/received’,<br />
‘recv-signal’ : /sim/network/cpcdlc/signals/received'<br />
});</tt><br />
<br />
== Available ATC-pie IRC scheme ==<br />
When connected to an IRC server, e.g. <code>irc.flightgear.org</code>, ATC-pie features an integrated text chat system for ATCs to coordinate off the (public) FG text chat. The choice of IRC and plain text lines was in part motivated by the possibility for users of other clients (e.g. OpenRadar) still to take part via their own IRC client.<br />
<br />
The connection is also used to embed special commands, in the form of escaped text lines, starting with <code>___ATC-pie___ </code>, e.g. for strip exchange (handovers) or "who-has" requests. Commands were later added to support CPDLC, which is ready for ACFT to connect. What follows is a description of those CPDLC-related commands.<br />
<br />
Note that the IRC nicknames are assumed to match the FGMS network callsigns, so the first line sent after connection is always:<br />
:<code>NICK <callsign>\r\n</code><br />
Then, from an ACFT point of view, every CPDLC command is sent to the current (or requested) data authority (ATC) via an escaped chat message line:<br />
:<code>PRIVMSG <atc> :___ATC-pie___ <command_line>\r\n</code><br />
<br />
Command lines that an aircraft can send to an ATC are:<br />
* <code>CPDLC_CONNECT</code>: attempt log-on<br />
* <code>CPDLC_MSG <msg_contents></code>: connected CPDLC dialogue message (see below)<br />
* <code>CPDLC_DISCONNECT</code>: connection shut down<br />
<br />
Command lines that an aircraft can receive from an ATC are:<br />
* <code>CPDLC_CONNECT</code>: accepted log-on, or new data authority (link has been transferred)<br />
* <code>CPDLC_MSG <msg_contents></code>: connected CPDLC dialogue msg (see below)<br />
* <code>CPDLC_DISCONNECT</code>: connection voluntarily dropped by ATC, or refused log-on<br />
<br />
The contents of connected dialogue messages (contained in CPDLC_MSG lines) can be discussed as part of the interface between aircraft and ATC-pie, but for the moment they are of either format below:<br />
* <code>REQUEST <display_text>(<sep_char><encoded_instruction>)*</code>: likely answered with an INSTR msg<br />
* <code>INSTR <display_text>(<sep_char><encoded_instruction>)*</code>: never sent by ACFT, only received<br />
* <code>ACK</code>: acknowledgement/"WILCO"<br />
* <code>REJECT <optional_reason></code><br />
* <code>TEXT <free_text></code><br />
<br />
== Hackathon 2020 results ==<br />
We have a first POC up and running :)<br />
[[File:Cpdlc-demo.png|thumb|CPDLC demo dialg]]<br />
<br />
* Basic IRC protocol has been implemented in C++<br />
* Some dummy CPDLC messages have been successfully passed around<br />
* CPDLC connect/disconnect works, handover to othe ATS unit works<br />
* Inbound CPDLC messages are processed and queued for later display in the cockpit<br />
* A simple PUI dialog interface is available in FGAddon<br />
* CPDLC protocol details still have to be worked out<br />
<br />
[[Category:Hackathon 2020 Ideas]]</div>Jsbhttps://wiki.flightgear.org/w/index.php?title=File:Cpdlc-demo.png&diff=129022File:Cpdlc-demo.png2020-11-16T13:46:08Z<p>Jsb: User created page with UploadWizard</p>
<hr />
<div>=={{int:filedesc}}==<br />
{{Information<br />
|description={{en|1=CPDLC demo dialg}}<br />
|date=2020-11-16<br />
|source={{own}}<br />
|author=[[User:Jsb|Jsb]]<br />
|permission=<br />
|other versions=<br />
}}<br />
<br />
=={{int:license-header}}==<br />
{{self|cc-by-sa-4.0}}<br />
<br />
<br />
<br />
[[Category:Hackathon 2020 Ideas]]</div>Jsbhttps://wiki.flightgear.org/w/index.php?title=SVGCanvas&diff=128538SVGCanvas2020-11-03T13:31:41Z<p>Jsb: /* Usage examples */</p>
<hr />
<div>SVGCanvas is a base class to populate a canvas from SVG and animate elements easily. <br />
The source file is $FGDATA/Nasal/canvas/api/svgcanvas.nas<br />
<br />
<br />
== Usage examples ==<br />
A simple usecase is a speed booklet as it can be found in the CRJ700 (versions since 2020). A SVG is used as a layout template, data is filled in by Nasal code from tables.<br />
[[File:Crj700-speedbooklet.png|thumb|screenshot of spreed booklet window]]<br />
<br />
A shortened code sample:<br />
<br />
<syntaxhighlight lang="nasal"><br />
var canvas_settings = {<br />
"size": [1024,1024],<br />
"view": [1024,1024],<br />
};<br />
var window_size = [512,512];<br />
var speedbooklet_svg = "speedbooklet.svg";<br />
<br />
# class Booklet extends SVGCanvas<br />
var Booklet =<br />
{<br />
bgcolor: [0.9, 0.9, 0.9, 1], #background color for canvas<br />
<br />
new: func(name, svgfile, speedtable) {<br />
var obj = {<br />
parents: [me, canvas.SVGCanvas.new(name, canvas_settings)],<br />
<br />
# put all element IDs you wish to animate in this vector (use a SVG editor like inkscape<br />
# to find/edit ID strings in SVG file, the strings used here must match the IDs in the SVG file)<br />
svg_keys: [<br />
"title", "tonns", "kilogram", "weight_unit", "overweight",<br />
"v1_8", "vr_8", "v2_8", "vt",<br />
"v1_20", "vr_20", "v2_20",<br />
"vref_0", "vref_1", "vref_8", "vref_20", "vref_30", "vref_45",<br />
"page", "left", "right",<br />
],<br />
<br />
_pageN: props.getNode("/sim/gui/speedbooklet/page",1),<br />
<br />
#the speedtable class is not described in detail to keep this short, it stores data for the pages of the booklet<br />
speedtable: speedtable, <br />
};<br />
<br />
obj.getCanvas().setColorBackground(me.bgcolor);<br />
obj.loadSVG(svgfile, obj.svg_keys);<br />
obj.init();<br />
return obj;<br />
},<br />
<br />
init: func() {<br />
# support mouse click for the page change buttons<br />
me["left"].addEventListener("click", func(e) { me.prevPage(); });<br />
me["right"].addEventListener("click", func(e) { me.nextPage(); });<br />
<br />
me._pageN.setIntValue(0);<br />
return ;<br />
},<br />
<br />
del: func() {<br />
if (me._L) removelistener(me._L);<br />
call(canvas.SVGCanvas.del, [], me, var err = []);<br />
return nil;<br />
},<br />
<br />
nextPage: func() {<br />
if (me._pageN.getValue() < me.speedtable.getNumberOfPages() - 1) {<br />
me._pageN.increment();<br />
me.update();<br />
}<br />
return me;<br />
},<br />
<br />
prevPage: func() {<br />
if (me._pageN.getValue() > 0) {<br />
me._pageN.decrement();<br />
me.update();<br />
}<br />
return me;<br />
},<br />
<br />
# update elements<br />
update: func() {<br />
var number = me._pageN.getValue();<br />
var speeds = me.speedtable.getPage(number);<br />
foreach (var key; keys(speeds)) {<br />
if (vecindex(me.svg_keys, key) != nil)<br />
me.updateTextElement(key, sprintf("%d", speeds[key]));<br />
}<br />
me["overweight"].setVisible(me.speedtable.isOverMLW(speeds.weight));<br />
me.updateTextElement("tonns", sprintf("%d", int(speeds.weight/1000)));<br />
me.updateTextElement("kilogram", sprintf("%03d", math.mod(speeds.weight, 1000)));<br />
me.updateTextElement("page", sprintf("%2d", number + 1));<br />
},<br />
};<br />
<br />
var window_title = "Speedbooklet "~aero;<br />
var book = Booklet.new(window_title, speedbooklet_svg, st);<br />
book.asWindow(window_size);<br />
<br />
</syntaxhighlight><br />
<br />
== Class functions ==<br />
<br />
=== new() ===<br />
{{Nasal doc<br />
|syntax = new(name, settings=nil);<br />
|text = Create a new SVGCanvas<br />
|param1 = name<br />
|param1text = Name of the canvas.<br />
|param2 = setting<br />
|param2text = Hash of canvas settings.<br />
|example1 = <br />
var myCanvas = SVGCanvas.new("mySVG");<br />
myCanvas.loadsvg("myfile.svg", ["foo", "bar"]);<br />
}}<br />
<br />
=== del() ===<br />
Destructor. Remove window (if any) and canvas.<br />
<br />
=== loadSVG() ===<br />
{{Nasal doc<br />
|syntax = loadSVG(file, svg_keys, options=nil);<br />
|text = loads SVG file and create canvas.element objects for given IDs<br />
|param1 = file<br />
|param1text = filename of SVG to load<br />
|param2 = svg_keys<br />
|param2text = Vector of id strings. For each id a member will create in the SVGCanvas object. If there is an SVG object named <id>_clip, it will be automatically setup as clip for <id><br />
|param3 = options<br />
|param3text = Optional hash with options passed to the SVG parser (canvas.parsesvg)<br />
|example1 = <br />
var myCanvas = SVGCanvas.new("mySVG");<br />
myCanvas.loadsvg("myfile.svg", ["foo", "bar"]);<br />
}}<br />
<br />
=== asWindow() ===<br />
{{Nasal doc<br />
|syntax = asWindow(window_size);<br />
|text = opens the canvas in a window <br />
|param1 = window_size<br />
|param1text = vector [size_x, size_y] passed to canvas.Window.new<br />
|example1 = <br />
var myCanvas = SVGCanvas.new("mySVG");<br />
myCanvas.loadsvg("myfile.svg", ["foo", "bar"]);<br />
}}<br />
<br />
=== getPath() ===<br />
wrapper for canvas.getPath()<br />
<br />
=== getCanvas() ===<br />
return the canvas object<br />
<br />
=== getRoot() ===<br />
return the top level canvas group element created by new()<br />
<br />
=== updateTextElement() ===<br />
{{Nasal doc<br />
|syntax = updateTextElement(svgkey, text, color=nil);<br />
|text = update text and color of a canvas text element<br />
|param1 = svgkey<br />
|param1text = Name (ID) of the text element.<br />
|param2 = text<br />
|param2text = New value for text element<br />
|param3 = color<br />
|param3text = Optional new color. Can be either a vector e.g. [r,g,b] or a color name from canvas.colors<br />
|example1 = <br />
<br />
}}<br />
<br />
== Semi-private class methods ==<br />
The following methods are ment for creating derived classes, not to be called directly. They all return listener functions ("handlers") to be used in a setlistener() call.<br />
The idea is to easily animate canvas elements based on properties that change "irregularly" and "not too often", e.g. not every frame.<br />
For properties that change regularly and/or very often, an update function called by a timer may be more efficient. If you plan for a rather complex display with regular updates, see [[Canvas EFIS Framework]] which extends SVGCanvas.<br />
<br />
=== _makeListener_showHide() ===<br />
{{Nasal doc<br />
|syntax = _makeListener_showHide(svgkeys, value=nil);<br />
|text = returns generic listener to show/hide element(s) based on property node value<br />
|param1 = svgkeys<br />
|param1text = string (single ID) or vector of stings (IDs). Hint: if possible, group elements in SVG file and animate group instead of individual elements.<br />
|param2 = value<br />
|param2text = optional value to trigger show(); otherwise node.value will be implicitly treated as bool<br />
<br />
}}<br />
<br />
=== _makeListener_rotate() ===<br />
{{Nasal doc<br />
|syntax = _makeListener_rotate(svgkeys, factors=nil);<br />
|text = returns listener to set rotation of element(s) based on property node value (optionally multiplied by a factor).<br />
|param1 = svgkeys<br />
|param1text = string (single ID) or vector of stings (IDs). Hint: if possible, group elements in SVG file and animate group instead of individual elements.<br />
|param2 = factors<br />
|param2text = optional, number (if svgkeys is a single key) or hash of numbers {"svgkey" : factor}, missing keys will be treated as 1, e.g. rotate by property value.<br />
|example1 = <br />
# this is from an EICAS display class in the CRJ700 (see CRJ700-family/Nasal/EFIS/*) extending EFISCanvas (see /Nasal/modules/canvas_efis/efis-canvas.nas) which in turn uses SVGCanvas<br />
# note: for the rudder trim a factor -1 is used to match the animation with the property<br />
setlistener("controls/flight/rudder-trim", me._makeListener_rotate("rudderTrim", -1), 1, 0);<br />
setlistener("controls/flight/aileron-trim", me._makeListener_rotate("ailTrim"), 1, 0);<br />
}}<br />
<br />
=== _makeListener_translate() ===<br />
{{Nasal doc<br />
|syntax = _makeListener_translate(svgkeys, fx, fy);<br />
|text = returns listener to set rotation of element(s) based on property node value (optionally multiplied by a factor).<br />
|param1 = svgkeys<br />
|param1text = string (single ID) or vector of stings (IDs). Hint: if possible, group elements in SVG file and animate group instead of individual elements.<br />
|param2 = fx<br />
|param2text = number or hash of numbers {"svgkey" : factor}, missing keys will be treated as 0 (=no op)<br />
|param3 = fy<br />
|param3text = number or hash of numbers {"svgkey" : factor}, missing keys will be treated as 0 (=no op)<br />
|example1 = <br />
# this is from an EICAS display class in the CRJ700 (see CRJ700-family/Nasal/EFIS/*) extending EFISCanvas (see /Nasal/modules/canvas_efis/efis-canvas.nas) which in turn uses SVGCanvas<br />
# two elements are animated by a single property, the translation in x direction is zero and in y direction the property is multiplied by -139.46 (which can probably be found using a SVG editor and check the size of the element)<br />
setlistener("/surface-positions/spoiler-ob-ground-pos-norm", me._makeListener_translate(["spoilerIndL3","spoilerIndR3"], 0, -139.46), 1, 0);<br />
}}<br />
<br />
=== _makeListener_setColor() ===<br />
{{Nasal doc<br />
|syntax = _makeListener_setColor(svgkeys, color_true, color_false);<br />
|text = returns generic listener to cange color of element(s) based on property node value<br />
|param1 = svgkeys<br />
|param1text = string (single ID) or vector of stings (IDs). Hint: if possible, group elements in SVG file and animate group instead of individual elements.<br />
|param2 = color_true<br />
|param2text = Color to be set if node evaluates to true, can be either a vector e.g. [r,g,b] or a color name from canvas.colors<br />
|param3 = color_false<br />
|param3text = Color to be set, if node evaluates to false, can be either a vector e.g. [r,g,b] or a color name from canvas.colors<br />
|example1 = <br />
# this is from an EICAS display class in the CRJ700 (see CRJ700-family/Nasal/EFIS/*) extending EFISCanvas (see /Nasal/modules/canvas_efis/efis-canvas.nas) which in turn uses SVGCanvas<br />
# change the color of pump symbol with the ID "xflowPump" to either yellow or white depending on the inop property<br />
setlistener("systems/fuel/xflow-pump/inop", me._makeListener_setColor("xflowPump", me.colors["amber"], me.colors["white"]), 1, 0);<br />
}}<br />
<br />
=== _makeListener_updateText() ===<br />
{{Nasal doc<br />
|syntax = _makeListener_updateText(svgkeys, format="%s", default="");<br />
|text = Returns a listener that calls updateTextElement(key, sprintf(format, n.getValue() or default));<br />
|param1 = svgkeys<br />
|param1text = string (single ID) or vector of stings (IDs).<br />
|param2 = format<br />
|param2text = optional format sting for sprintf<br />
|param3 = default<br />
|param3text = text to use if node.getValue() fails<br />
|example1 = <br />
# this is from an EICAS display class in the CRJ700 (see CRJ700-family/Nasal/EFIS/*) extending EFISCanvas (see /Nasal/modules/canvas_efis/efis-canvas.nas) which in turn uses SVGCanvas<br />
setlistener("/controls/autoflight/speed-select", me._makeListener_updateText("iasref.text", "%d", 0), 1, 0);<br />
}}</div>Jsbhttps://wiki.flightgear.org/w/index.php?title=SVGCanvas&diff=128537SVGCanvas2020-11-03T13:10:58Z<p>Jsb: /* Semi-private class methods */</p>
<hr />
<div>SVGCanvas is a base class to populate a canvas from SVG and animate elements easily. <br />
The source file is $FGDATA/Nasal/canvas/api/svgcanvas.nas<br />
<br />
<br />
== Usage examples ==<br />
A simple usecase is a speed booklet as it can be found in the CRJ700 (versions since 2020). A SVG is used as a layout template, data is filled in by Nasal code from tables.<br />
[[File:Crj700-speedbooklet.png|thumb|screenshot of spreed booklet window]]<br />
<br />
A shortened code sample:<br />
<br />
<syntaxhighlight lang="nasal"><br />
var canvas_settings = {<br />
"size": [1024,1024],<br />
"view": [1024,1024],<br />
};<br />
var window_size = [512,512];<br />
var speedbooklet_svg = "speedbooklet.svg";<br />
<br />
var Booklet =<br />
{<br />
bgcolor: [0.9, 0.9, 0.9, 1],<br />
new: func(name, svgfile, speedtable) {<br />
var obj = {<br />
parents: [me, canvas.SVGCanvas.new(name, canvas_settings)],<br />
svg_keys: [<br />
"title", "tonns", "kilogram", "weight_unit", "overweight",<br />
"v1_8", "vr_8", "v2_8", "vt",<br />
"v1_20", "vr_20", "v2_20",<br />
"vref_0", "vref_1", "vref_8", "vref_20", "vref_30", "vref_45",<br />
"page", "left", "right",<br />
],<br />
_pageN: props.getNode("/sim/gui/speedbooklet/page",1),<br />
speedtable: speedtable,<br />
};<br />
<br />
obj.getCanvas().setColorBackground(me.bgcolor);<br />
obj.loadSVG(svgfile, obj.svg_keys);<br />
obj.init();<br />
return obj;<br />
},<br />
<br />
init: func() {<br />
me["left"].addEventListener("click", func(e) { me.prevPage(); });<br />
me["right"].addEventListener("click", func(e) { me.nextPage(); });<br />
me._pageN.setIntValue(0);<br />
return ;<br />
},<br />
<br />
del: func() {<br />
if (me._L) removelistener(me._L);<br />
call(canvas.SVGCanvas.del, [], me, var err = []);<br />
return nil;<br />
},<br />
<br />
nextPage: func() {<br />
if (me._pageN.getValue() < me.speedtable.getNumberOfPages() - 1) {<br />
me._pageN.increment();<br />
me.update();<br />
}<br />
return me;<br />
},<br />
<br />
prevPage: func() {<br />
if (me._pageN.getValue() > 0) {<br />
me._pageN.decrement();<br />
me.update();<br />
}<br />
return me;<br />
},<br />
<br />
update: func() {<br />
var number = me._pageN.getValue();<br />
var speeds = me.speedtable.getPage(number);<br />
foreach (var key; keys(speeds)) {<br />
if (vecindex(me.svg_keys, key) != nil)<br />
me[key].setText(sprintf("%d", speeds[key]));<br />
}<br />
me["overweight"].setVisible(me.speedtable.isOverMLW(speeds.weight));<br />
me["tonns"].setText(sprintf("%d", int(speeds.weight/1000)));<br />
me["kilogram"].setText(sprintf("%03d", math.mod(speeds.weight, 1000)));<br />
me["page"].setText(sprintf("%2d", number + 1));<br />
},<br />
<br />
};<br />
</syntaxhighlight><br />
<br />
== Class functions ==<br />
<br />
=== new() ===<br />
{{Nasal doc<br />
|syntax = new(name, settings=nil);<br />
|text = Create a new SVGCanvas<br />
|param1 = name<br />
|param1text = Name of the canvas.<br />
|param2 = setting<br />
|param2text = Hash of canvas settings.<br />
|example1 = <br />
var myCanvas = SVGCanvas.new("mySVG");<br />
myCanvas.loadsvg("myfile.svg", ["foo", "bar"]);<br />
}}<br />
<br />
=== del() ===<br />
Destructor. Remove window (if any) and canvas.<br />
<br />
=== loadSVG() ===<br />
{{Nasal doc<br />
|syntax = loadSVG(file, svg_keys, options=nil);<br />
|text = loads SVG file and create canvas.element objects for given IDs<br />
|param1 = file<br />
|param1text = filename of SVG to load<br />
|param2 = svg_keys<br />
|param2text = Vector of id strings. For each id a member will create in the SVGCanvas object. If there is an SVG object named <id>_clip, it will be automatically setup as clip for <id><br />
|param3 = options<br />
|param3text = Optional hash with options passed to the SVG parser (canvas.parsesvg)<br />
|example1 = <br />
var myCanvas = SVGCanvas.new("mySVG");<br />
myCanvas.loadsvg("myfile.svg", ["foo", "bar"]);<br />
}}<br />
<br />
=== asWindow() ===<br />
{{Nasal doc<br />
|syntax = asWindow(window_size);<br />
|text = opens the canvas in a window <br />
|param1 = window_size<br />
|param1text = vector [size_x, size_y] passed to canvas.Window.new<br />
|example1 = <br />
var myCanvas = SVGCanvas.new("mySVG");<br />
myCanvas.loadsvg("myfile.svg", ["foo", "bar"]);<br />
}}<br />
<br />
=== getPath() ===<br />
wrapper for canvas.getPath()<br />
<br />
=== getCanvas() ===<br />
return the canvas object<br />
<br />
=== getRoot() ===<br />
return the top level canvas group element created by new()<br />
<br />
=== updateTextElement() ===<br />
{{Nasal doc<br />
|syntax = updateTextElement(svgkey, text, color=nil);<br />
|text = update text and color of a canvas text element<br />
|param1 = svgkey<br />
|param1text = Name (ID) of the text element.<br />
|param2 = text<br />
|param2text = New value for text element<br />
|param3 = color<br />
|param3text = Optional new color. Can be either a vector e.g. [r,g,b] or a color name from canvas.colors<br />
|example1 = <br />
<br />
}}<br />
<br />
== Semi-private class methods ==<br />
The following methods are ment for creating derived classes, not to be called directly. They all return listener functions ("handlers") to be used in a setlistener() call.<br />
The idea is to easily animate canvas elements based on properties that change "irregularly" and "not too often", e.g. not every frame.<br />
For properties that change regularly and/or very often, an update function called by a timer may be more efficient. If you plan for a rather complex display with regular updates, see [[Canvas EFIS Framework]] which extends SVGCanvas.<br />
<br />
=== _makeListener_showHide() ===<br />
{{Nasal doc<br />
|syntax = _makeListener_showHide(svgkeys, value=nil);<br />
|text = returns generic listener to show/hide element(s) based on property node value<br />
|param1 = svgkeys<br />
|param1text = string (single ID) or vector of stings (IDs). Hint: if possible, group elements in SVG file and animate group instead of individual elements.<br />
|param2 = value<br />
|param2text = optional value to trigger show(); otherwise node.value will be implicitly treated as bool<br />
<br />
}}<br />
<br />
=== _makeListener_rotate() ===<br />
{{Nasal doc<br />
|syntax = _makeListener_rotate(svgkeys, factors=nil);<br />
|text = returns listener to set rotation of element(s) based on property node value (optionally multiplied by a factor).<br />
|param1 = svgkeys<br />
|param1text = string (single ID) or vector of stings (IDs). Hint: if possible, group elements in SVG file and animate group instead of individual elements.<br />
|param2 = factors<br />
|param2text = optional, number (if svgkeys is a single key) or hash of numbers {"svgkey" : factor}, missing keys will be treated as 1, e.g. rotate by property value.<br />
|example1 = <br />
# this is from an EICAS display class in the CRJ700 (see CRJ700-family/Nasal/EFIS/*) extending EFISCanvas (see /Nasal/modules/canvas_efis/efis-canvas.nas) which in turn uses SVGCanvas<br />
# note: for the rudder trim a factor -1 is used to match the animation with the property<br />
setlistener("controls/flight/rudder-trim", me._makeListener_rotate("rudderTrim", -1), 1, 0);<br />
setlistener("controls/flight/aileron-trim", me._makeListener_rotate("ailTrim"), 1, 0);<br />
}}<br />
<br />
=== _makeListener_translate() ===<br />
{{Nasal doc<br />
|syntax = _makeListener_translate(svgkeys, fx, fy);<br />
|text = returns listener to set rotation of element(s) based on property node value (optionally multiplied by a factor).<br />
|param1 = svgkeys<br />
|param1text = string (single ID) or vector of stings (IDs). Hint: if possible, group elements in SVG file and animate group instead of individual elements.<br />
|param2 = fx<br />
|param2text = number or hash of numbers {"svgkey" : factor}, missing keys will be treated as 0 (=no op)<br />
|param3 = fy<br />
|param3text = number or hash of numbers {"svgkey" : factor}, missing keys will be treated as 0 (=no op)<br />
|example1 = <br />
# this is from an EICAS display class in the CRJ700 (see CRJ700-family/Nasal/EFIS/*) extending EFISCanvas (see /Nasal/modules/canvas_efis/efis-canvas.nas) which in turn uses SVGCanvas<br />
# two elements are animated by a single property, the translation in x direction is zero and in y direction the property is multiplied by -139.46 (which can probably be found using a SVG editor and check the size of the element)<br />
setlistener("/surface-positions/spoiler-ob-ground-pos-norm", me._makeListener_translate(["spoilerIndL3","spoilerIndR3"], 0, -139.46), 1, 0);<br />
}}<br />
<br />
=== _makeListener_setColor() ===<br />
{{Nasal doc<br />
|syntax = _makeListener_setColor(svgkeys, color_true, color_false);<br />
|text = returns generic listener to cange color of element(s) based on property node value<br />
|param1 = svgkeys<br />
|param1text = string (single ID) or vector of stings (IDs). Hint: if possible, group elements in SVG file and animate group instead of individual elements.<br />
|param2 = color_true<br />
|param2text = Color to be set if node evaluates to true, can be either a vector e.g. [r,g,b] or a color name from canvas.colors<br />
|param3 = color_false<br />
|param3text = Color to be set, if node evaluates to false, can be either a vector e.g. [r,g,b] or a color name from canvas.colors<br />
|example1 = <br />
# this is from an EICAS display class in the CRJ700 (see CRJ700-family/Nasal/EFIS/*) extending EFISCanvas (see /Nasal/modules/canvas_efis/efis-canvas.nas) which in turn uses SVGCanvas<br />
# change the color of pump symbol with the ID "xflowPump" to either yellow or white depending on the inop property<br />
setlistener("systems/fuel/xflow-pump/inop", me._makeListener_setColor("xflowPump", me.colors["amber"], me.colors["white"]), 1, 0);<br />
}}<br />
<br />
=== _makeListener_updateText() ===<br />
{{Nasal doc<br />
|syntax = _makeListener_updateText(svgkeys, format="%s", default="");<br />
|text = Returns a listener that calls updateTextElement(key, sprintf(format, n.getValue() or default));<br />
|param1 = svgkeys<br />
|param1text = string (single ID) or vector of stings (IDs).<br />
|param2 = format<br />
|param2text = optional format sting for sprintf<br />
|param3 = default<br />
|param3text = text to use if node.getValue() fails<br />
|example1 = <br />
# this is from an EICAS display class in the CRJ700 (see CRJ700-family/Nasal/EFIS/*) extending EFISCanvas (see /Nasal/modules/canvas_efis/efis-canvas.nas) which in turn uses SVGCanvas<br />
setlistener("/controls/autoflight/speed-select", me._makeListener_updateText("iasref.text", "%d", 0), 1, 0);<br />
}}</div>Jsbhttps://wiki.flightgear.org/w/index.php?title=SVGCanvas&diff=128480SVGCanvas2020-11-01T23:52:55Z<p>Jsb: /* Usage examples */</p>
<hr />
<div>SVGCanvas is a base class to populate a canvas from SVG and animate elements easily. <br />
The source file is $FGDATA/Nasal/canvas/api/svgcanvas.nas<br />
<br />
<br />
== Usage examples ==<br />
A simple usecase is a speed booklet as it can be found in the CRJ700 (versions since 2020). A SVG is used as a layout template, data is filled in by Nasal code from tables.<br />
[[File:Crj700-speedbooklet.png|thumb|screenshot of spreed booklet window]]<br />
<br />
A shortened code sample:<br />
<br />
<syntaxhighlight lang="nasal"><br />
var canvas_settings = {<br />
"size": [1024,1024],<br />
"view": [1024,1024],<br />
};<br />
var window_size = [512,512];<br />
var speedbooklet_svg = "speedbooklet.svg";<br />
<br />
var Booklet =<br />
{<br />
bgcolor: [0.9, 0.9, 0.9, 1],<br />
new: func(name, svgfile, speedtable) {<br />
var obj = {<br />
parents: [me, canvas.SVGCanvas.new(name, canvas_settings)],<br />
svg_keys: [<br />
"title", "tonns", "kilogram", "weight_unit", "overweight",<br />
"v1_8", "vr_8", "v2_8", "vt",<br />
"v1_20", "vr_20", "v2_20",<br />
"vref_0", "vref_1", "vref_8", "vref_20", "vref_30", "vref_45",<br />
"page", "left", "right",<br />
],<br />
_pageN: props.getNode("/sim/gui/speedbooklet/page",1),<br />
speedtable: speedtable,<br />
};<br />
<br />
obj.getCanvas().setColorBackground(me.bgcolor);<br />
obj.loadSVG(svgfile, obj.svg_keys);<br />
obj.init();<br />
return obj;<br />
},<br />
<br />
init: func() {<br />
me["left"].addEventListener("click", func(e) { me.prevPage(); });<br />
me["right"].addEventListener("click", func(e) { me.nextPage(); });<br />
me._pageN.setIntValue(0);<br />
return ;<br />
},<br />
<br />
del: func() {<br />
if (me._L) removelistener(me._L);<br />
call(canvas.SVGCanvas.del, [], me, var err = []);<br />
return nil;<br />
},<br />
<br />
nextPage: func() {<br />
if (me._pageN.getValue() < me.speedtable.getNumberOfPages() - 1) {<br />
me._pageN.increment();<br />
me.update();<br />
}<br />
return me;<br />
},<br />
<br />
prevPage: func() {<br />
if (me._pageN.getValue() > 0) {<br />
me._pageN.decrement();<br />
me.update();<br />
}<br />
return me;<br />
},<br />
<br />
update: func() {<br />
var number = me._pageN.getValue();<br />
var speeds = me.speedtable.getPage(number);<br />
foreach (var key; keys(speeds)) {<br />
if (vecindex(me.svg_keys, key) != nil)<br />
me[key].setText(sprintf("%d", speeds[key]));<br />
}<br />
me["overweight"].setVisible(me.speedtable.isOverMLW(speeds.weight));<br />
me["tonns"].setText(sprintf("%d", int(speeds.weight/1000)));<br />
me["kilogram"].setText(sprintf("%03d", math.mod(speeds.weight, 1000)));<br />
me["page"].setText(sprintf("%2d", number + 1));<br />
},<br />
<br />
};<br />
</syntaxhighlight><br />
<br />
== Class functions ==<br />
<br />
=== new() ===<br />
{{Nasal doc<br />
|syntax = new(name, settings=nil);<br />
|text = Create a new SVGCanvas<br />
|param1 = name<br />
|param1text = Name of the canvas.<br />
|param2 = setting<br />
|param2text = Hash of canvas settings.<br />
|example1 = <br />
var myCanvas = SVGCanvas.new("mySVG");<br />
myCanvas.loadsvg("myfile.svg", ["foo", "bar"]);<br />
}}<br />
<br />
=== del() ===<br />
Destructor. Remove window (if any) and canvas.<br />
<br />
=== loadSVG() ===<br />
{{Nasal doc<br />
|syntax = loadSVG(file, svg_keys, options=nil);<br />
|text = loads SVG file and create canvas.element objects for given IDs<br />
|param1 = file<br />
|param1text = filename of SVG to load<br />
|param2 = svg_keys<br />
|param2text = Vector of id strings. For each id a member will create in the SVGCanvas object. If there is an SVG object named <id>_clip, it will be automatically setup as clip for <id><br />
|param3 = options<br />
|param3text = Optional hash with options passed to the SVG parser (canvas.parsesvg)<br />
|example1 = <br />
var myCanvas = SVGCanvas.new("mySVG");<br />
myCanvas.loadsvg("myfile.svg", ["foo", "bar"]);<br />
}}<br />
<br />
=== asWindow() ===<br />
{{Nasal doc<br />
|syntax = asWindow(window_size);<br />
|text = opens the canvas in a window <br />
|param1 = window_size<br />
|param1text = vector [size_x, size_y] passed to canvas.Window.new<br />
|example1 = <br />
var myCanvas = SVGCanvas.new("mySVG");<br />
myCanvas.loadsvg("myfile.svg", ["foo", "bar"]);<br />
}}<br />
<br />
=== getPath() ===<br />
wrapper for canvas.getPath()<br />
<br />
=== getCanvas() ===<br />
return the canvas object<br />
<br />
=== getRoot() ===<br />
return the top level canvas group element created by new()<br />
<br />
=== updateTextElement() ===<br />
{{Nasal doc<br />
|syntax = updateTextElement(svgkey, text, color=nil);<br />
|text = update text and color of a canvas text element<br />
|param1 = svgkey<br />
|param1text = Name (ID) of the text element.<br />
|param2 = text<br />
|param2text = New value for text element<br />
|param3 = color<br />
|param3text = Optional new color. Can be either a vector e.g. [r,g,b] or a color name from canvas.colors<br />
|example1 = <br />
<br />
}}<br />
<br />
== Semi-private class methods ==<br />
The following methods are ment for creating derived classes, not to be called directly. The all return listener functions ("handlers") to be used in a setlistener() call.<br />
The idea is to easily animate canvas elements based on properties that change "irregularly" and "not too often", e.g. not every frame.<br />
For properties that change regularly and/or very often, an update function called by a timer may be more efficient. If you plan for a rather complex display with regular updates, see [[Canvas EFIS Framework]] which extends SVGCanvas.<br />
<br />
=== _makeListener_showHide() ===<br />
{{Nasal doc<br />
|syntax = _makeListener_showHide(svgkeys, value=nil);<br />
|text = returns generic listener to show/hide element(s) based on property node value<br />
|param1 = svgkeys<br />
|param1text = string (single ID) or vector of stings (IDs). Hint: if possible, group elements in SVG file and animate group instead of individual elements.<br />
|param2 = value<br />
|param2text = optional value to trigger show(); otherwise node.value will be implicitly treated as bool<br />
<br />
}}<br />
<br />
=== _makeListener_rotate() ===<br />
{{Nasal doc<br />
|syntax = _makeListener_rotate(svgkeys, factors=nil);<br />
|text = returns listener to set rotation of element(s) based on property node value (optionally multiplied by a factor).<br />
|param1 = svgkeys<br />
|param1text = string (single ID) or vector of stings (IDs). Hint: if possible, group elements in SVG file and animate group instead of individual elements.<br />
|param2 = factors<br />
|param2text = optional, number (if svgkeys is a single key) or hash of numbers {"svgkey" : factor}, missing keys will be treated as 1, e.g. rotate by property value.<br />
|example1 = <br />
# this is from an EICAS display class in the CRJ700 (see CRJ700-family/Nasal/EFIS/*) extending EFISCanvas (see /Nasal/modules/canvas_efis/efis-canvas.nas) which in turn uses SVGCanvas<br />
# note: for the rudder trim a factor -1 is used to match the animation with the property<br />
setlistener("controls/flight/rudder-trim", me._makeListener_rotate("rudderTrim", -1), 1, 0);<br />
setlistener("controls/flight/aileron-trim", me._makeListener_rotate("ailTrim"), 1, 0);<br />
}}<br />
<br />
=== _makeListener_translate() ===<br />
{{Nasal doc<br />
|syntax = _makeListener_translate(svgkeys, fx, fy);<br />
|text = returns listener to set rotation of element(s) based on property node value (optionally multiplied by a factor).<br />
|param1 = svgkeys<br />
|param1text = string (single ID) or vector of stings (IDs). Hint: if possible, group elements in SVG file and animate group instead of individual elements.<br />
|param2 = fx<br />
|param2text = number or hash of numbers {"svgkey" : factor}, missing keys will be treated as 0 (=no op)<br />
|param3 = fy<br />
|param3text = number or hash of numbers {"svgkey" : factor}, missing keys will be treated as 0 (=no op)<br />
|example1 = <br />
# this is from an EICAS display class in the CRJ700 (see CRJ700-family/Nasal/EFIS/*) extending EFISCanvas (see /Nasal/modules/canvas_efis/efis-canvas.nas) which in turn uses SVGCanvas<br />
# two elements are animated by a single property, the translation in x direction is zero and in y direction the property is multiplied by -139.46 (which can probably be found using a SVG editor and check the size of the element)<br />
setlistener("/surface-positions/spoiler-ob-ground-pos-norm", me._makeListener_translate(["spoilerIndL3","spoilerIndR3"], 0, -139.46), 1, 0);<br />
}}<br />
<br />
=== _makeListener_setColor() ===<br />
{{Nasal doc<br />
|syntax = _makeListener_setColor(svgkeys, color_true, color_false);<br />
|text = returns generic listener to cange color of element(s) based on property node value<br />
|param1 = svgkeys<br />
|param1text = string (single ID) or vector of stings (IDs). Hint: if possible, group elements in SVG file and animate group instead of individual elements.<br />
|param2 = color_true<br />
|param2text = Color to be set if node evaluates to true, can be either a vector e.g. [r,g,b] or a color name from canvas.colors<br />
|param3 = color_false<br />
|param3text = Color to be set, if node evaluates to false, can be either a vector e.g. [r,g,b] or a color name from canvas.colors<br />
|example1 = <br />
# this is from an EICAS display class in the CRJ700 (see CRJ700-family/Nasal/EFIS/*) extending EFISCanvas (see /Nasal/modules/canvas_efis/efis-canvas.nas) which in turn uses SVGCanvas<br />
# change the color of pump symbol with the ID "xflowPump" to either yellow or white depending on the inop property<br />
setlistener("systems/fuel/xflow-pump/inop", me._makeListener_setColor("xflowPump", me.colors["amber"], me.colors["white"]), 1, 0);<br />
}}<br />
<br />
=== _makeListener_updateText() ===<br />
{{Nasal doc<br />
|syntax = _makeListener_updateText(svgkeys, format="%s", default="");<br />
|text = Returns a listener that calls updateTextElement(key, sprintf(format, n.getValue() or default));<br />
|param1 = svgkeys<br />
|param1text = string (single ID) or vector of stings (IDs).<br />
|param2 = format<br />
|param2text = optional format sting for sprintf<br />
|param3 = default<br />
|param3text = text to use if node.getValue() fails<br />
|example1 = <br />
# this is from an EICAS display class in the CRJ700 (see CRJ700-family/Nasal/EFIS/*) extending EFISCanvas (see /Nasal/modules/canvas_efis/efis-canvas.nas) which in turn uses SVGCanvas<br />
setlistener("/controls/autoflight/speed-select", me._makeListener_updateText("iasref.text", "%d", 0), 1, 0);<br />
}}</div>Jsbhttps://wiki.flightgear.org/w/index.php?title=SVGCanvas&diff=128479SVGCanvas2020-11-01T23:44:54Z<p>Jsb: /* Usage examples */</p>
<hr />
<div>SVGCanvas is a base class to populate a canvas from SVG and animate elements easily. <br />
The source file is $FGDATA/Nasal/canvas/api/svgcanvas.nas<br />
<br />
<br />
== Usage examples ==<br />
A simple usecase is a speed booklet as it can be found in the CRJ700 (versions since 2020). A SVG is used as a layout template, data is filled in by Nasal code from tables.<br />
[[File:Crj700-speedbooklet.png|thumb|screenshot of spreed booklet window]]<br />
<br />
<add code sample><br />
<br />
== Class functions ==<br />
<br />
=== new() ===<br />
{{Nasal doc<br />
|syntax = new(name, settings=nil);<br />
|text = Create a new SVGCanvas<br />
|param1 = name<br />
|param1text = Name of the canvas.<br />
|param2 = setting<br />
|param2text = Hash of canvas settings.<br />
|example1 = <br />
var myCanvas = SVGCanvas.new("mySVG");<br />
myCanvas.loadsvg("myfile.svg", ["foo", "bar"]);<br />
}}<br />
<br />
=== del() ===<br />
Destructor. Remove window (if any) and canvas.<br />
<br />
=== loadSVG() ===<br />
{{Nasal doc<br />
|syntax = loadSVG(file, svg_keys, options=nil);<br />
|text = loads SVG file and create canvas.element objects for given IDs<br />
|param1 = file<br />
|param1text = filename of SVG to load<br />
|param2 = svg_keys<br />
|param2text = Vector of id strings. For each id a member will create in the SVGCanvas object. If there is an SVG object named <id>_clip, it will be automatically setup as clip for <id><br />
|param3 = options<br />
|param3text = Optional hash with options passed to the SVG parser (canvas.parsesvg)<br />
|example1 = <br />
var myCanvas = SVGCanvas.new("mySVG");<br />
myCanvas.loadsvg("myfile.svg", ["foo", "bar"]);<br />
}}<br />
<br />
=== asWindow() ===<br />
{{Nasal doc<br />
|syntax = asWindow(window_size);<br />
|text = opens the canvas in a window <br />
|param1 = window_size<br />
|param1text = vector [size_x, size_y] passed to canvas.Window.new<br />
|example1 = <br />
var myCanvas = SVGCanvas.new("mySVG");<br />
myCanvas.loadsvg("myfile.svg", ["foo", "bar"]);<br />
}}<br />
<br />
=== getPath() ===<br />
wrapper for canvas.getPath()<br />
<br />
=== getCanvas() ===<br />
return the canvas object<br />
<br />
=== getRoot() ===<br />
return the top level canvas group element created by new()<br />
<br />
=== updateTextElement() ===<br />
{{Nasal doc<br />
|syntax = updateTextElement(svgkey, text, color=nil);<br />
|text = update text and color of a canvas text element<br />
|param1 = svgkey<br />
|param1text = Name (ID) of the text element.<br />
|param2 = text<br />
|param2text = New value for text element<br />
|param3 = color<br />
|param3text = Optional new color. Can be either a vector e.g. [r,g,b] or a color name from canvas.colors<br />
|example1 = <br />
<br />
}}<br />
<br />
== Semi-private class methods ==<br />
The following methods are ment for creating derived classes, not to be called directly. The all return listener functions ("handlers") to be used in a setlistener() call.<br />
The idea is to easily animate canvas elements based on properties that change "irregularly" and "not too often", e.g. not every frame.<br />
For properties that change regularly and/or very often, an update function called by a timer may be more efficient. If you plan for a rather complex display with regular updates, see [[Canvas EFIS Framework]] which extends SVGCanvas.<br />
<br />
=== _makeListener_showHide() ===<br />
{{Nasal doc<br />
|syntax = _makeListener_showHide(svgkeys, value=nil);<br />
|text = returns generic listener to show/hide element(s) based on property node value<br />
|param1 = svgkeys<br />
|param1text = string (single ID) or vector of stings (IDs). Hint: if possible, group elements in SVG file and animate group instead of individual elements.<br />
|param2 = value<br />
|param2text = optional value to trigger show(); otherwise node.value will be implicitly treated as bool<br />
<br />
}}<br />
<br />
=== _makeListener_rotate() ===<br />
{{Nasal doc<br />
|syntax = _makeListener_rotate(svgkeys, factors=nil);<br />
|text = returns listener to set rotation of element(s) based on property node value (optionally multiplied by a factor).<br />
|param1 = svgkeys<br />
|param1text = string (single ID) or vector of stings (IDs). Hint: if possible, group elements in SVG file and animate group instead of individual elements.<br />
|param2 = factors<br />
|param2text = optional, number (if svgkeys is a single key) or hash of numbers {"svgkey" : factor}, missing keys will be treated as 1, e.g. rotate by property value.<br />
|example1 = <br />
# this is from an EICAS display class in the CRJ700 (see CRJ700-family/Nasal/EFIS/*) extending EFISCanvas (see /Nasal/modules/canvas_efis/efis-canvas.nas) which in turn uses SVGCanvas<br />
# note: for the rudder trim a factor -1 is used to match the animation with the property<br />
setlistener("controls/flight/rudder-trim", me._makeListener_rotate("rudderTrim", -1), 1, 0);<br />
setlistener("controls/flight/aileron-trim", me._makeListener_rotate("ailTrim"), 1, 0);<br />
}}<br />
<br />
=== _makeListener_translate() ===<br />
{{Nasal doc<br />
|syntax = _makeListener_translate(svgkeys, fx, fy);<br />
|text = returns listener to set rotation of element(s) based on property node value (optionally multiplied by a factor).<br />
|param1 = svgkeys<br />
|param1text = string (single ID) or vector of stings (IDs). Hint: if possible, group elements in SVG file and animate group instead of individual elements.<br />
|param2 = fx<br />
|param2text = number or hash of numbers {"svgkey" : factor}, missing keys will be treated as 0 (=no op)<br />
|param3 = fy<br />
|param3text = number or hash of numbers {"svgkey" : factor}, missing keys will be treated as 0 (=no op)<br />
|example1 = <br />
# this is from an EICAS display class in the CRJ700 (see CRJ700-family/Nasal/EFIS/*) extending EFISCanvas (see /Nasal/modules/canvas_efis/efis-canvas.nas) which in turn uses SVGCanvas<br />
# two elements are animated by a single property, the translation in x direction is zero and in y direction the property is multiplied by -139.46 (which can probably be found using a SVG editor and check the size of the element)<br />
setlistener("/surface-positions/spoiler-ob-ground-pos-norm", me._makeListener_translate(["spoilerIndL3","spoilerIndR3"], 0, -139.46), 1, 0);<br />
}}<br />
<br />
=== _makeListener_setColor() ===<br />
{{Nasal doc<br />
|syntax = _makeListener_setColor(svgkeys, color_true, color_false);<br />
|text = returns generic listener to cange color of element(s) based on property node value<br />
|param1 = svgkeys<br />
|param1text = string (single ID) or vector of stings (IDs). Hint: if possible, group elements in SVG file and animate group instead of individual elements.<br />
|param2 = color_true<br />
|param2text = Color to be set if node evaluates to true, can be either a vector e.g. [r,g,b] or a color name from canvas.colors<br />
|param3 = color_false<br />
|param3text = Color to be set, if node evaluates to false, can be either a vector e.g. [r,g,b] or a color name from canvas.colors<br />
|example1 = <br />
# this is from an EICAS display class in the CRJ700 (see CRJ700-family/Nasal/EFIS/*) extending EFISCanvas (see /Nasal/modules/canvas_efis/efis-canvas.nas) which in turn uses SVGCanvas<br />
# change the color of pump symbol with the ID "xflowPump" to either yellow or white depending on the inop property<br />
setlistener("systems/fuel/xflow-pump/inop", me._makeListener_setColor("xflowPump", me.colors["amber"], me.colors["white"]), 1, 0);<br />
}}<br />
<br />
=== _makeListener_updateText() ===<br />
{{Nasal doc<br />
|syntax = _makeListener_updateText(svgkeys, format="%s", default="");<br />
|text = Returns a listener that calls updateTextElement(key, sprintf(format, n.getValue() or default));<br />
|param1 = svgkeys<br />
|param1text = string (single ID) or vector of stings (IDs).<br />
|param2 = format<br />
|param2text = optional format sting for sprintf<br />
|param3 = default<br />
|param3text = text to use if node.getValue() fails<br />
|example1 = <br />
# this is from an EICAS display class in the CRJ700 (see CRJ700-family/Nasal/EFIS/*) extending EFISCanvas (see /Nasal/modules/canvas_efis/efis-canvas.nas) which in turn uses SVGCanvas<br />
setlistener("/controls/autoflight/speed-select", me._makeListener_updateText("iasref.text", "%d", 0), 1, 0);<br />
}}</div>Jsbhttps://wiki.flightgear.org/w/index.php?title=File:Crj700-speedbooklet.png&diff=128478File:Crj700-speedbooklet.png2020-11-01T23:44:00Z<p>Jsb: User created page with UploadWizard</p>
<hr />
<div>=={{int:filedesc}}==<br />
{{Information<br />
|description={{en|1=screenshot of spreed booklet window}}<br />
|date=2020-11-01<br />
|source={{own}}<br />
|author=[[User:Jsb|Jsb]]<br />
|permission=<br />
|other versions=<br />
}}<br />
<br />
=={{int:license-header}}==<br />
{{self|cc-zero}}<br />
<br />
<br />
<br />
[[Category:Canvas display screenshots]]</div>Jsbhttps://wiki.flightgear.org/w/index.php?title=SVGCanvas&diff=128477SVGCanvas2020-11-01T23:39:40Z<p>Jsb: Initial version</p>
<hr />
<div>SVGCanvas is a base class to populate a canvas from SVG and animate elements easily. <br />
The source file is $FGDATA/Nasal/canvas/api/svgcanvas.nas<br />
<br />
<br />
== Usage examples ==<br />
A simple usecase is a speed booklet as it can be found in the CRJ700 (versions since 2020). A SVG is used as a layout template, data is filled in by Nasal code from tables.<br />
<add screenshot><br />
<add code sample><br />
<br />
== Class functions ==<br />
<br />
=== new() ===<br />
{{Nasal doc<br />
|syntax = new(name, settings=nil);<br />
|text = Create a new SVGCanvas<br />
|param1 = name<br />
|param1text = Name of the canvas.<br />
|param2 = setting<br />
|param2text = Hash of canvas settings.<br />
|example1 = <br />
var myCanvas = SVGCanvas.new("mySVG");<br />
myCanvas.loadsvg("myfile.svg", ["foo", "bar"]);<br />
}}<br />
<br />
=== del() ===<br />
Destructor. Remove window (if any) and canvas.<br />
<br />
=== loadSVG() ===<br />
{{Nasal doc<br />
|syntax = loadSVG(file, svg_keys, options=nil);<br />
|text = loads SVG file and create canvas.element objects for given IDs<br />
|param1 = file<br />
|param1text = filename of SVG to load<br />
|param2 = svg_keys<br />
|param2text = Vector of id strings. For each id a member will create in the SVGCanvas object. If there is an SVG object named <id>_clip, it will be automatically setup as clip for <id><br />
|param3 = options<br />
|param3text = Optional hash with options passed to the SVG parser (canvas.parsesvg)<br />
|example1 = <br />
var myCanvas = SVGCanvas.new("mySVG");<br />
myCanvas.loadsvg("myfile.svg", ["foo", "bar"]);<br />
}}<br />
<br />
=== asWindow() ===<br />
{{Nasal doc<br />
|syntax = asWindow(window_size);<br />
|text = opens the canvas in a window <br />
|param1 = window_size<br />
|param1text = vector [size_x, size_y] passed to canvas.Window.new<br />
|example1 = <br />
var myCanvas = SVGCanvas.new("mySVG");<br />
myCanvas.loadsvg("myfile.svg", ["foo", "bar"]);<br />
}}<br />
<br />
=== getPath() ===<br />
wrapper for canvas.getPath()<br />
<br />
=== getCanvas() ===<br />
return the canvas object<br />
<br />
=== getRoot() ===<br />
return the top level canvas group element created by new()<br />
<br />
=== updateTextElement() ===<br />
{{Nasal doc<br />
|syntax = updateTextElement(svgkey, text, color=nil);<br />
|text = update text and color of a canvas text element<br />
|param1 = svgkey<br />
|param1text = Name (ID) of the text element.<br />
|param2 = text<br />
|param2text = New value for text element<br />
|param3 = color<br />
|param3text = Optional new color. Can be either a vector e.g. [r,g,b] or a color name from canvas.colors<br />
|example1 = <br />
<br />
}}<br />
<br />
== Semi-private class methods ==<br />
The following methods are ment for creating derived classes, not to be called directly. The all return listener functions ("handlers") to be used in a setlistener() call.<br />
The idea is to easily animate canvas elements based on properties that change "irregularly" and "not too often", e.g. not every frame.<br />
For properties that change regularly and/or very often, an update function called by a timer may be more efficient. If you plan for a rather complex display with regular updates, see [[Canvas EFIS Framework]] which extends SVGCanvas.<br />
<br />
=== _makeListener_showHide() ===<br />
{{Nasal doc<br />
|syntax = _makeListener_showHide(svgkeys, value=nil);<br />
|text = returns generic listener to show/hide element(s) based on property node value<br />
|param1 = svgkeys<br />
|param1text = string (single ID) or vector of stings (IDs). Hint: if possible, group elements in SVG file and animate group instead of individual elements.<br />
|param2 = value<br />
|param2text = optional value to trigger show(); otherwise node.value will be implicitly treated as bool<br />
<br />
}}<br />
<br />
=== _makeListener_rotate() ===<br />
{{Nasal doc<br />
|syntax = _makeListener_rotate(svgkeys, factors=nil);<br />
|text = returns listener to set rotation of element(s) based on property node value (optionally multiplied by a factor).<br />
|param1 = svgkeys<br />
|param1text = string (single ID) or vector of stings (IDs). Hint: if possible, group elements in SVG file and animate group instead of individual elements.<br />
|param2 = factors<br />
|param2text = optional, number (if svgkeys is a single key) or hash of numbers {"svgkey" : factor}, missing keys will be treated as 1, e.g. rotate by property value.<br />
|example1 = <br />
# this is from an EICAS display class in the CRJ700 (see CRJ700-family/Nasal/EFIS/*) extending EFISCanvas (see /Nasal/modules/canvas_efis/efis-canvas.nas) which in turn uses SVGCanvas<br />
# note: for the rudder trim a factor -1 is used to match the animation with the property<br />
setlistener("controls/flight/rudder-trim", me._makeListener_rotate("rudderTrim", -1), 1, 0);<br />
setlistener("controls/flight/aileron-trim", me._makeListener_rotate("ailTrim"), 1, 0);<br />
}}<br />
<br />
=== _makeListener_translate() ===<br />
{{Nasal doc<br />
|syntax = _makeListener_translate(svgkeys, fx, fy);<br />
|text = returns listener to set rotation of element(s) based on property node value (optionally multiplied by a factor).<br />
|param1 = svgkeys<br />
|param1text = string (single ID) or vector of stings (IDs). Hint: if possible, group elements in SVG file and animate group instead of individual elements.<br />
|param2 = fx<br />
|param2text = number or hash of numbers {"svgkey" : factor}, missing keys will be treated as 0 (=no op)<br />
|param3 = fy<br />
|param3text = number or hash of numbers {"svgkey" : factor}, missing keys will be treated as 0 (=no op)<br />
|example1 = <br />
# this is from an EICAS display class in the CRJ700 (see CRJ700-family/Nasal/EFIS/*) extending EFISCanvas (see /Nasal/modules/canvas_efis/efis-canvas.nas) which in turn uses SVGCanvas<br />
# two elements are animated by a single property, the translation in x direction is zero and in y direction the property is multiplied by -139.46 (which can probably be found using a SVG editor and check the size of the element)<br />
setlistener("/surface-positions/spoiler-ob-ground-pos-norm", me._makeListener_translate(["spoilerIndL3","spoilerIndR3"], 0, -139.46), 1, 0);<br />
}}<br />
<br />
=== _makeListener_setColor() ===<br />
{{Nasal doc<br />
|syntax = _makeListener_setColor(svgkeys, color_true, color_false);<br />
|text = returns generic listener to cange color of element(s) based on property node value<br />
|param1 = svgkeys<br />
|param1text = string (single ID) or vector of stings (IDs). Hint: if possible, group elements in SVG file and animate group instead of individual elements.<br />
|param2 = color_true<br />
|param2text = Color to be set if node evaluates to true, can be either a vector e.g. [r,g,b] or a color name from canvas.colors<br />
|param3 = color_false<br />
|param3text = Color to be set, if node evaluates to false, can be either a vector e.g. [r,g,b] or a color name from canvas.colors<br />
|example1 = <br />
# this is from an EICAS display class in the CRJ700 (see CRJ700-family/Nasal/EFIS/*) extending EFISCanvas (see /Nasal/modules/canvas_efis/efis-canvas.nas) which in turn uses SVGCanvas<br />
# change the color of pump symbol with the ID "xflowPump" to either yellow or white depending on the inop property<br />
setlistener("systems/fuel/xflow-pump/inop", me._makeListener_setColor("xflowPump", me.colors["amber"], me.colors["white"]), 1, 0);<br />
}}<br />
<br />
=== _makeListener_updateText() ===<br />
{{Nasal doc<br />
|syntax = _makeListener_updateText(svgkeys, format="%s", default="");<br />
|text = Returns a listener that calls updateTextElement(key, sprintf(format, n.getValue() or default));<br />
|param1 = svgkeys<br />
|param1text = string (single ID) or vector of stings (IDs).<br />
|param2 = format<br />
|param2text = optional format sting for sprintf<br />
|param3 = default<br />
|param3text = text to use if node.getValue() fails<br />
|example1 = <br />
# this is from an EICAS display class in the CRJ700 (see CRJ700-family/Nasal/EFIS/*) extending EFISCanvas (see /Nasal/modules/canvas_efis/efis-canvas.nas) which in turn uses SVGCanvas<br />
setlistener("/controls/autoflight/speed-select", me._makeListener_updateText("iasref.text", "%d", 0), 1, 0);<br />
}}</div>Jsbhttps://wiki.flightgear.org/w/index.php?title=Aircraft_tester_list&diff=125884Aircraft tester list2020-06-22T20:37:06Z<p>Jsb: /* Aircraft tester list */</p>
<hr />
<div>for test procedure see [[Aircraft testing checklist]]<br />
<br />
{| class="wikitable sortable"<br />
|-<br />
! Aircraft !! Tester(s) !! Remarks<br />
|-<br />
| A300 || Jonathan Redpath|| <br />
|-<br />
| A320-200 || Jonathan Redpath|| [NB - upstream github repo for issues]<br />
|-<br />
| A340-600 || Jonathan Redpath|| <br />
|-<br />
| A380 || Jonathan Redpath|| <br />
|-<br />
| Aerostar-700 || James || <br />
|-<br />
| B1900D || James || <br />
|-<br />
| 737NGs || James || <br />
|-<br />
| 757 || James || <br />
|-<br />
| F15 || Richard H. || presumably :)<br />
|-<br />
| J3Cub || Wayne Bragg|| <br />
|-<br />
| DaSH || Wayne Bragg|| <br />
|-<br />
| SUMPAC || Wayne Bragg|| <br />
|-<br />
| p51d || Stuart || <br />
|-<br />
| hunter || Stuart || <br />
|-<br />
| seahawk || Stuart || <br />
|-<br />
| CRJ700 family || Henning, Daniel || <br />
|}</div>Jsbhttps://wiki.flightgear.org/w/index.php?title=Aircraft_tester_list&diff=125883Aircraft tester list2020-06-22T20:36:40Z<p>Jsb: </p>
<hr />
<div>= Aircraft tester list =<br />
for test procedure see [[Aircraft testing checklist]]<br />
<br />
{| class="wikitable sortable"<br />
|-<br />
! Aircraft !! Tester(s) !! Remarks<br />
|-<br />
| A300 || Jonathan Redpath|| <br />
|-<br />
| A320-200 || Jonathan Redpath|| [NB - upstream github repo for issues]<br />
|-<br />
| A340-600 || Jonathan Redpath|| <br />
|-<br />
| A380 || Jonathan Redpath|| <br />
|-<br />
| Aerostar-700 || James || <br />
|-<br />
| B1900D || James || <br />
|-<br />
| 737NGs || James || <br />
|-<br />
| 757 || James || <br />
|-<br />
| F15 || Richard H. || presumably :)<br />
|-<br />
| J3Cub || Wayne Bragg|| <br />
|-<br />
| DaSH || Wayne Bragg|| <br />
|-<br />
| SUMPAC || Wayne Bragg|| <br />
|-<br />
| p51d || Stuart || <br />
|-<br />
| hunter || Stuart || <br />
|-<br />
| seahawk || Stuart || <br />
|-<br />
| CRJ700 family || Henning, Daniel || <br />
|}</div>Jsbhttps://wiki.flightgear.org/w/index.php?title=Aircraft_tester_list&diff=125882Aircraft tester list2020-06-22T20:35:44Z<p>Jsb: /* Aircraft tester list */</p>
<hr />
<div>= Aircraft tester list =<br />
<br />
{| class="wikitable sortable"<br />
|-<br />
! Aircraft !! Tester(s) !! Remarks<br />
|-<br />
| A300 || Jonathan Redpath|| <br />
|-<br />
| A320-200 || Jonathan Redpath|| [NB - upstream github repo for issues]<br />
|-<br />
| A340-600 || Jonathan Redpath|| <br />
|-<br />
| A380 || Jonathan Redpath|| <br />
|-<br />
| Aerostar-700 || James || <br />
|-<br />
| B1900D || James || <br />
|-<br />
| 737NGs || James || <br />
|-<br />
| 757 || James || <br />
|-<br />
| F15 || Richard H. || presumably :)<br />
|-<br />
| J3Cub || Wayne Bragg|| <br />
|-<br />
| DaSH || Wayne Bragg|| <br />
|-<br />
| SUMPAC || Wayne Bragg|| <br />
|-<br />
| p51d || Stuart || <br />
|-<br />
| hunter || Stuart || <br />
|-<br />
| seahawk || Stuart || <br />
|-<br />
| CRJ700 family || Henning, Daniel || <br />
|}</div>Jsbhttps://wiki.flightgear.org/w/index.php?title=Post_FlightGear_2020.2_LTS_changes&diff=125774Post FlightGear 2020.2 LTS changes2020-06-16T21:59:39Z<p>Jsb: </p>
<hr />
<div>== Code clean-ups and changes ==<br />
<br />
Collecting what's going to change : this will also be used to work out what manual or automatic migrations are required to keep aircraft working. It's expected that for the first few months of 'next', there will be higher than usual breakage, i.e regular might not be possible.<br />
<br />
As a general guideline, the minimum system to develop / build the code on will be Ubuntu 18.04 LTS (Bionic) : the versions below correspond to what is available in a stock Bionic install.<br />
<br />
# Make C++14 the minimum required version (will make it easier to continue [[Deboosting FlightGear|replacing Boost]] items with std:: ones) <ref>https://sourceforge.net/p/flightgear/mailman/message/36988831/</ref> <ref>https://sourceforge.net/p/flightgear/mailman/message/36984349/</ref><br />
# Make CMake 3.6 the minimum required version : this will allow simplifying a bunch of compatibility logic in the build files <ref>https://sourceforge.net/p/flightgear/mailman/message/37020794/</ref><br />
# Use CMake OBJECT_LIBRARY to improve how we collect sources together when building each sub-dir<br />
# Make Qt 5.9 the minimum for the launcher <br />
# Drop 32-bit windows support <ref>https://sourceforge.net/p/flightgear/mailman/message/34899704/</ref><br />
# Drop the pre-2017.x MultiPlayer message format - this will fix warnings from some aircraft about MP packet overflows<br />
# Drop the KLN-89 code, since it's unused and not maintained for a long time: Canvas and the regular GPS code can easily implement a working KLN-89 or similar equipment now <ref>https://sourceforge.net/p/flightgear/mailman/message/36327950/</ref><br />
# Switch to Compositor mode as the only rendering option <ref>https://sourceforge.net/p/flightgear/mailman/message/36606242/</ref><br />
# Drop Rembrandt support from C++ (really part of the above) <ref>https://sourceforge.net/p/flightgear/mailman/message/36340736/</ref><br />
# Drop the C++ NavDisplay : the Canvas version replaces it <ref>https://sourceforge.net/p/flightgear/mailman/message/36975265/</ref><br />
<br />
== Carriers and AI ==<br />
<br />
Richard has been working on carriers since April 2020; originally the intention was to release as part of the 2020.2 LTS however the changes have grown into a set of new features rather than bugfixes and are therefore more suited to a longer development and review process.<br />
<br />
* {{done}} Improved view support - so that the nearest tower will include carriers<br />
* {{done}} Changes to core code to support moving towers.<br />
* {{Progressbar|50}} Multiple (tower) views - permit selection of LSO, PLAT and Tower as part of the "Tower view"; selection mechanism TDB. Consider revisions to the existing view system to better support this.<br />
* {{done}} Extend XML to include definitions for FLOLS touchdown position, LSO view position, Tower View position, deck angle.<br />
* {{Progressbar|50}} Improve 3d models for IFLOLS to ensure that a ball call can be made.<br />
* {{Progressbar|80}} Improve logic for IFLOLS lights; not quite an LSO simulation more of an approximation of the definitive cases (e.g. waveoff)<br />
* {{pending}} Animate the lineup lights on the stern (of Nimitz class).<br />
* {{Progressbar|90}} Improved support for Precision Approach Landing System (PALS) (AN/SPN-46); to support lineup deviation as well as glideslope (ball) deviation. <br />
* {{Progressbar|70}} Change approach deviations to use new aircraft reference point rather than eyepoint; it is probably more correct to use the eyepoint when in the cockpit view - but for external views this can be slightly inaccurate when outside the aircraft to completely wrong when in tower/LSO view. The reference point is taken as the aircraft position with an optional model defined x,y,z offset. This allows ball tuning on a per aircraft basis.<br />
* {{Pending}} Improved support for AI logic to permit XML definition of e.g. LSO logic. Possibly a version of the autopilot / state machine that can be included as "logic blocks"<br />
* {{Pending}} Review 3d model usage for all Nimitz class and either add LOD selection (low, medium, high)<br />
* {{Pending}} Possible extra visual aids for Case 1 and Case 2 recovery training (e.g. a set of markers to fly through)<br />
* {{Pending}} Better integration of carriers with JSBSim to permit catapult hookup<br />
* {{Pending}} Simulate carrier operations Flight Deck Personnel, e.g. yellow shirts.<br />
* {{Pending}} Animation of arrestor wires<br />
* {{Pending}} Add some sort of trap grading (approach/touchdown plot)<br />
<br />
see https://www.cnatra.navy.mil/local/docs/pat-pubs/P-816.pdf for T-45 carrier operations<br />
<br />
== Possible additional items ==<br />
<br />
* Remove the 2D panel code in favour of Canvas (this requires completing some work to load 2D panel elements as Canvas, from XML) <ref>https://sourceforge.net/p/flightgear/mailman/message/36973988/</ref><br />
* Remove the C++ HUD in favour of Canvas-based version : this requires some kind of migration script or framework, so we have at least the default UFO HUD available<br />
<br />
== fgdata clean-ups ==<br />
=== /Nasal ===<br />
2do: list outdated / deprecated functions for removal (I remember one comment saying: remove after FG 3.0 or something)<br />
* Clean up the GPS code with respect to FG_210_COMPAT :<br />
<br />
== property tree clean-up ==<br />
remove /yasim/* "new" path is /fdm/yasim (since version 201x.?)<br />
<br />
== References ==<br />
{{Appendix}}<br />
<br />
[[Category:Core development]]</div>Jsbhttps://wiki.flightgear.org/w/index.php?title=Nasal_library/props&diff=125622Nasal library/props2020-06-13T09:05:47Z<p>Jsb: add toggleBoolValue()</p>
<hr />
<div>{{Nasal Navigation|nocat=1}}<br />
This page contains documentation for the '''<code>props</code> namespace''' in [[Nasal]]. This namespace provides APIs for working with property trees (including the main [[Property Tree]]) via {{API Link|simgear|class|SGPropertyNode}}. The <code>props</code> namespace is sourced from {{fgdata file|Nasal/props.nas}} and {{flightgear file|src/Scripting/nasal-props.cxx}}.<br />
<br />
== Class ==<br />
=== Node ===<br />
{{Nasal doc<br />
|mode = class<br />
|text = The main class, used widely for manipulating property trees.<br />
}}<br />
==== new() ====<br />
{{Nasal doc<br />
|syntax = props.Node.new([values]);<br />
|text = Constructor function. Returns a new <code>props.Node</code> instance.<br />
|param1 = values<br />
|param1text = An optional hash that will be the initial property structure.<br />
|example1 = var node = props.Node.new();<br />
props.dump(node);<br />
|example2 = var tree = {<br />
a: 1,<br />
b: ["a", "b", "c"],<br />
c: {<br />
d: 1 * 4<br />
}<br />
};<br />
<br />
var node = props.Node.new(tree);<br />
props.dump(node);<br />
}}<br />
<br />
==== addChild() ====<br />
{{Nasal doc<br />
|syntax = props.Node.addChild(name[, min_idx[, append]]);<br />
|version = 2.10<br />
|commit = {{fgdata commit|a15d58|t=commit}}<br />
|text = Add a new, blank child to the node. Returns the newly-created node.<br />
|param1 = name<br />
|param1text = The name of the node as a string.<br />
|param2 = min_idx<br />
|param2text = This specifies the minimum index to add the new one to. This takes precedence over '''append'''. Defaults to 0.<br />
|param3 = append<br />
|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).<br />
|example1 = var node = props.Node.new();<br />
node.addChild("a");<br />
props.dump(node);<br />
|example2 = var node = props.Node.new();<br />
node.addChild("a", 1);<br />
props.dump(node); # a[1]<br />
|example3 = var node = props.Node.new();<br />
node.addChild("a", 1);<br />
props.dump(node); # a[1]<br />
node.addChild("a", 0, 0);<br />
props.dump(node); # a[1] and a[0]<br />
|example4 = var node = props.Node.new();<br />
node.addChild("a", 1);<br />
props.dump(node); # a[1]<br />
node.addChild("a", 0, 1);<br />
props.dump(node); # a[1] and a[2]<br />
}}<br />
<br />
==== addChildren() ====<br />
{{Nasal doc<br />
|syntax = props.Node.addChildren(name, count[, min_idx[, append]]);<br />
|version = 2.10<br />
|commit = {{fgdata commit|7f1117|t=commit}}<br />
|text = Adds multiple children with the same name to this node. Returns <code>'''nil</code>.<br />
|param1 = name<br />
|param1text = The name of the nodes as a string.<br />
|param2 = count<br />
|param2text = Number of new children to add.<br />
|param3 = min_idx<br />
|param3text = Performs the same function as in <code>[[#addChild.28.29|Node.addChild()]]</code>.<br />
|param4 = append<br />
|param4text = Performs the same function as in <code>[[#addChild.28.29|Node.addChild()]]</code>.<br />
|example1 = var node = props.Node.new();<br />
node.addChildren("a", 2);<br />
props.dump(node); # a[0] and a[1]<br />
|example2 = var node = props.Node.new();<br />
node.addChildren("a", 2, 1);<br />
props.dump(node); # a[1] and a[2]<br />
|example3 = var node = props.Node.new();<br />
node.addChild("a", 2);<br />
props.dump(node); # a[2]<br />
node.addChildren("a", 2, 0, 0);<br />
props.dump(node); # a[2], a[0], and a[1]<br />
}}<br />
<br />
==== adjustValue() (since FG 2020.1) ====<br />
{{Nasal doc<br />
|syntax = props.Node.adjustValue(delta);<br />
|text = Adds delta (numeric) to current value respecting the node type. <br />
|param1 = delta<br />
|param1text = Numeric value (can be negative) to add to current node value.<br />
}}<br />
<br />
==== alias() ====<br />
{{Nasal doc<br />
|syntax = props.Node.alias(node);<br />
|text = Aliases this node to another one. Returns 1 on success and 0 on failure.<br />
|param1 = node<br />
|param1text = The node to alias to. Can be one of:<br />
* A path to a property in the [[Property Tree]] as a string.<br />
* A <code>prop</code> ghost.<br />
* A <code>props.Node</code> object.<br />
|example1 = var node = props.Node.new();<br />
node.alias("/position/altitude-ft");<br />
props.dump(node); # equals the current altitude<br />
|example2 = var node1 = props.Node.new();<br />
node1.setDoubleValue(2.34);<br />
var node2 = props.Node.new();<br />
node2.alias(node1);<br />
props.dump(node2); # equals 2.34<br />
}}<br />
<br />
==== clearValue() ====<br />
{{Nasal doc<br />
|syntax = props.Node.clearValue();<br />
|text = Clears the value and type of the node.<br />
|example1 = var node = props.Node.new();<br />
node.setDoubleValue(2.34);<br />
props.dump(node); # prints "{DOUBLE} = 2.35"<br />
node.clearValue();<br />
props.dump(node); # prints "{NONE} = nil"<br />
}}<br />
<br />
==== decrement() (since FG 2020.1) ====<br />
{{Nasal doc<br />
|syntax = props.Node.decrement(n = 1);<br />
|text = Decrements integer property by n (default: n = 1)<br />
|param1 = n<br />
|param1text = Value to subtract, will be converted to int, defaults to 1<br />
}}<br />
<br />
==== equals() ====<br />
{{Nasal doc<br />
|syntax = props.Node.equals(node);<br />
|version = 2.12<br />
|commit = {{fgdata commit|d80722|t=commit}}<br />
|text = Checks whether the node refers to the same one as another. Returns 1 (true) if it is, and 0 (false) if otherwise.<br />
|param1 = node<br />
|param1text = Node to check against. May be either a <code>prop</code> ghost or a <code>props.Node</code> object.<br />
|example1 = var n = props.Node.new();<br />
var a = n;<br />
print(a.equals(n)); # prints "1" (true)<br />
}}<br />
<br />
==== getAliasTarget() ====<br />
{{Nasal doc<br />
|syntax = props.Node.getAliasTarget();<br />
|text = Returns the alias target of a node as another <code>props.Node</code> instance.<br />
|example1 = setprop("/test", 2.35);<br />
var node = props.Node.new();<br />
node.alias("/test");<br />
var tgt = node.getAliasTarget();<br />
print(tgt.getPath()); # prints "/test"<br />
}}<br />
<br />
==== getAttribute() ====<br />
{{Nasal doc<br />
|syntax = props.Node.getAttribute([rel_path, ]name);<br />
props.Node.getAttribute();<br />
|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<br />
{{{!}} class="wikitable"<br />
! String !! Return value<br />
{{!-}}<br />
{{!}} last {{!!}} The highest used attribute code (should be 128). See for {{simgear file|simgear/props/props.hxx|l=767}} the codes.<br />
{{!-}}<br />
{{!}} children {{!!}} Number of child nodes.<br />
{{!-}}<br />
{{!}} listeners {{!!}} Number of listeners connected to this node.<br />
{{!-}}<br />
{{!}} references {{!!}} Number of times the node has previously been referenced.<br />
{{!-}}<br />
{{!}} tied {{!!}} Whether the node is tied.<br />
{{!-}}<br />
{{!}} alias {{!!}} Whether the node is aliased.<br />
{{!-}}<br />
{{!}} readable {{!!}} Whether the node can be read.<br />
{{!-}}<br />
{{!}} writable {{!!}} Whether the node can be written to.<br />
{{!-}}<br />
{{!}} archive {{!!}} Whether the node will be saved when the "save" [[fgcommands|fgcommand]] is triggered.<br />
{{!-}}<br />
{{!}} trace-read {{!!}} Whether the reading of the node will be logged when <code>--log-level=info</code>.<br />
{{!-}}<br />
{{!}} trace-write {{!!}} Whether the writing to the node will be logged when <code>--log-level=info</code>.<br />
{{!-}}<br />
{{!}} userarchive {{!!}} Whether the node will be saved to the [[FlightGear configuration via XML#autosave.xml|autosave file]] (only works for actual properties).<br />
{{!-}}<br />
{{!}} preserve {{!!}} Whether the value of node will be preserved during resets (only works for actual properties).<br />
{{!}}}<br />
|param1 = rel_path<br />
|param1text = Optional relative path as a string.<br />
|param2 = name<br />
|param2text = Attribute as a string. See the above table for a full list.<br />
|example1 = var node = props.Node.new();<br />
var child = node.addChild("a");<br />
print(node.getAttribute("children")); # prints "1"<br />
|example2text = Example using relative path<br />
|example2 = var node = props.Node.new();<br />
var child = node.addChild("a");<br />
print(node.getAttribute("a", "readable")); # prints "1" (node can be read from)<br />
|example3 = var node = props.Node.new();<br />
var node2 = props.Node.new();<br />
node2.alias(node);<br />
print(node2.getAttribute("alias")); # prints "1" (true)<br />
|example4 = print(props.globals.getNode("/sim/signals/fdm-initialized").getAttribute("listeners")); # prints the number of listeners<br />
|example5 = print(props.globals.getNode("/sim/time/elapsed-sec").getAttribute("tied")); # prints "1" (true), meaning it is tied<br />
|example6 = var node = props.Node.new();<br />
print(node.getAttribute("writable")); # prints "1" (true), meaning the node can be written to<br />
|example7text = Example using no arguments<br />
|example7 = var node = props.Node.new();<br />
print(node.getAttribute()); # prints "3" (true), meaning the node can be read from (1) and written to (2)<br />
}}<br />
<br />
==== getBoolValue() ====<br />
{{Nasal doc<br />
|syntax = props.Node.getBoolValue();<br />
|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 <code>"false"</code>, false will be returned. Otherwise, true will be returned. Remember that boolean values are represented in Nasal as 1 (true) and 0 (false).<br />
|example1 = var node = props.Node.new();<br />
node.setBoolValue(1);<br />
print(node.getBoolValue() ? "true" : "false"); # prints "true"<br />
node.setBoolValue(0);<br />
print(node.getBoolValue() ? "true" : "false"); # prints "false"<br />
|example2 = var node = props.Node.new();<br />
node.setDoubleValue(1.23);<br />
print(node.getBoolValue() ? "true" : "false"); # prints "true"<br />
node.setDoubleValue(-1.23);<br />
print(node.getBoolValue() ? "true" : "false"); # prints "true"<br />
node.setDoubleValue(0.0);<br />
print(node.getBoolValue() ? "true" : "false"); # prints "false"<br />
|example3 = var node = props.Node.new();<br />
node.setIntValue(2);<br />
print(node.getBoolValue() ? "true" : "false"); # prints "true"<br />
node.setIntValue(-2);<br />
print(node.getBoolValue() ? "true" : "false"); # prints "true"<br />
node.setIntValue(0);<br />
print(node.getBoolValue() ? "true" : "false"); # prints "false"<br />
|example4 = var node = props.Node.new();<br />
node.setValue("Hello, World!");<br />
print(node.getBoolValue() ? "true" : "false"); # prints "true"<br />
node.setValue("false");<br />
print(node.getBoolValue() ? "true" : "false"); # prints "false"<br />
}}<br />
<br />
==== getChild() ====<br />
{{Nasal doc<br />
|syntax = props.Node.getChild(rel_path[, idx[, create]]);<br />
|text = Returns a child of a node as another <code>props.Node</code> instance.<br />
|param1 = rel_path<br />
|param1text = Relative path to the child node as a string.<br />
|param2 = idx<br />
|param2text = Optional index for the child node as an integer.<br />
|param3 = create<br />
|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 <code>'''nil'''</code> if no child exists. Defaults to 0.<br />
|example1 = var node = props.Node.new();<br />
node.addChild("a");<br />
var c = node.getChild("a");<br />
|example2 = var node = props.Node.new();<br />
node.addChildren("a", 3);<br />
node.getNode("a[1]").setDoubleValue(2.35);<br />
var c = node.getChild("a", 1);<br />
print(c.getValue()); # prints "2.35"<br />
|example3 = var node = props.Node.new();<br />
var c = node.getChild("a", 1, 1);<br />
props.dump(node); # new child a[1] will have appeared<br />
}}<br />
<br />
==== getChildren() ====<br />
{{Nasal doc<br />
|syntax = props.Node.getChildren([name]);<br />
|text = Returns a vector of child nodes, optionally those with a certain name, as <code>props.Node</code> instances.<br />
|param1 = name<br />
|param1text = Optional name of the child nodes as a string. If not given, all children will be returned.<br />
|example1 = var node = props.Node.new();<br />
node.addChildren("a", 3);<br />
node.addChildren("b", 3);<br />
debug.dump(node.getChildren()); # all child nodes in the vector<br />
|example2 = var node = props.Node.new();<br />
node.addChildren("a", 3);<br />
node.addChildren("b", 3);<br />
debug.dump(node.getChildren("b")); # only children with the name "b" in the vector<br />
}}<br />
<br />
==== getIndex() ====<br />
{{Nasal doc<br />
|syntax = props.Node.getIndex();<br />
|text = Returns the index of a node as an integer.<br />
|example1 = var node = props.Node.new();<br />
node.addChildren("a", 3);<br />
print(node.getChild("a", 1).getIndex()); # prints "1"<br />
|example2 = var node = props.Node.new();<br />
node.addChild("b");<br />
print(node.getChild("b").getIndex()); # prints "0"<br />
}}<br />
<br />
==== getName() ====<br />
{{Nasal doc<br />
|syntax = props.Node.getName();<br />
|text = Returns the name of the node as a string.<br />
|example1 = var node = props.Node.new();<br />
var c = node.addChild("a");<br />
debug.dump(c.getName()); # prints "a"<br />
|example2 = var node = props.Node.new();<br />
node.addChildren("a", 3);<br />
debug.dump(node.getChild("a", 2).getName()); # prints "a"<br />
}}<br />
<br />
==== getNode() ====<br />
{{Nasal doc<br />
|syntax = props.Node.getNode(rel_path[, create]);<br />
|text = Returns a subnode as another <code>props.Node</code> instance.<br />
|param1 = rel_path<br />
|param1text = Relative path to the subnode as a string.<br />
|param2 = create<br />
|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 <code>'''nil'''</code>. Default to 0 (false).<br />
|example1 = var tree = {<br />
"a": 1,<br />
"b": "Hi",<br />
"c": {<br />
"d": "Hello, World!"<br />
}<br />
};<br />
var node = props.Node.new(tree);<br />
print(node.getNode("c/d").getValue()); # prints "Hello, World!"<br />
|example2 = var tree = {<br />
"a": 1,<br />
"b": "Hi",<br />
"c": {}<br />
};<br />
var node = props.Node.new(tree);<br />
node.getNode("c/d", 1).setDoubleValue(2.35);<br />
props.dump(node); # c/d now exists<br />
|example3 = var ac = props.globals.getNode("sim/aircraft");<br />
print("Current aircraft is: ", ac.getValue());<br />
}}<br />
<br />
==== getParent() ====<br />
{{Nasal doc<br />
|syntax = props.Node.getParent();<br />
|text = Returns the parent of a node, or <code>'''nil'''</code> if there is no parent.<br />
|example1 = var tree = {<br />
"a": 1,<br />
"b": "Hi",<br />
"c": {<br />
"d": "Hello, World!"<br />
}<br />
};<br />
var node = props.Node.new(tree);<br />
props.dump(node.getNode("c/d").getParent()); # dumps "c"<br />
|example2 = var node = props.Node.new();<br />
debug.dump(node.getParent()); # prints nil<br />
}}<br />
<br />
==== getPath() ====<br />
{{Nasal doc<br />
|syntax = props.Node.getPath();<br />
|text = Returns the path of the node as a string.<br />
|example1 = var tree = {<br />
"a": 1,<br />
"b": "Hi",<br />
"c": {<br />
"d": "Hello, World!"<br />
}<br />
};<br />
var node = props.Node.new(tree);<br />
print(node.getNode("c/d").getPath()); # prints "/c/d"<br />
}}<br />
<br />
==== getType() ====<br />
{{Nasal doc<br />
|syntax = props.Node.getType();<br />
|text = Returns node's type as a string. It should be one of "NONE", "ALIAS", "BOOL", "INT", "LONG" (long integer), "FLOAT", "DOUBLE", "STRING", "VEC3D", "VEC4D", "UNSPECIFIED".<br />
|example1 = var node = props.Node.new();<br />
print(node.getType()); # prints "NONE"<br />
|example2 = var node = props.Node.new();<br />
node.setIntValue(12);<br />
print(node.getType()); # prints "INT"<br />
|example3 = var node = props.Node.new();<br />
node.setValue([0.4, 0.2, 1]);<br />
debug.dump(node.getValue()); # prints "[0.4, 0.2, 1]"<br />
print(node.getType()); # prints "VEC3D"<br />
}}<br />
<br />
==== getValue() ====<br />
{{Nasal doc<br />
|syntax = props.Node.getValue([rel_path]);<br />
|text = {{hatnote|See also {{func link|getBoolValue()|page=this}}.}}<br />
<br />
Returns the value of the node.<br />
|param1 = rel_path<br />
|param1text = Optional relative path to a subnode as a string.<br />
|example1 = var node = props.Node.new();<br />
node.setDoubleValue(2.35);<br />
print(node.getValue()); # prints "2.35"<br />
|example2 = var node = props.Node.new();<br />
node.setValue("Hi");<br />
print(node.getValue()); # prints "Hi"<br />
|example3 = var node = props.Node.new();<br />
node.addChild("a").setValue("Hi");<br />
print(node.getValue("a")); # prints "Hi"<br />
|example4 = var node = props.Node.new();<br />
node.setValue([0, 0.5, 1]);<br />
debug.dump(node.getValue()); # prints "[0, 0.5, 1]"<br />
}}<br />
<br />
==== getValues() ====<br />
{{Nasal doc<br />
|syntax = props.Node.getValues();<br />
|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).<br />
|example1 = var tree = {<br />
"string": "Hi",<br />
"number": 1.2,<br />
"subnode": {<br />
"idx-node": [1, 2, 3]<br />
}<br />
};<br />
var node = props.Node.new(tree);<br />
node.addChild("bool").setBoolValue(1);<br />
props.dump(node); # dump to node tree<br />
debug.dump(node.getValues()); # dump the node converted to hash<br />
|example2 = var tree = {<br />
"a": [1, 2, 3]<br />
};<br />
var node = props.Node.new(tree);<br />
props.dump(node); # a[0] = 1, a[1] = 2, a[2] = 3<br />
debug.dump(node.getValues()); # a: [1, 2, 3]<br />
}}<br />
<br />
==== increment() (since FG 2020.1) ====<br />
{{Nasal doc<br />
|syntax = props.Node.increment(n = 1);<br />
|text = Increments integer property by n (default: n = 1)<br />
|param1 = n<br />
|param1text = Value to add, will be converted to int, defaults to 1<br />
}}<br />
<br />
==== initNode() ====<br />
{{Nasal doc<br />
|syntax = props.Node.initNode([path[, value[, type[, force]]]]);<br />
|text = Initializes a node if it doesn't exist and returns that node as a <code>props.Node</code> object.<br />
|param1 = path<br />
|param1text = Optional path to a subnode as a string. If not given, the node itself will be initialized.<br />
|param2 = value<br />
|param2text = Optional default value to initialize the node with.<br />
|param3 = type<br />
|param3text = Optional string that will set the type of the node. Must be one of <code>"DOUBLE"</code>, <code>"INT"</code>, <code>"BOOL"</code>, or string <code>"STRING"</code>.<br />
|param4 = force<br />
|param4text = If set to 1 (true), the node's type will be forced to change.<br />
|example1 = var node = props.Node.new();<br />
var a = node.initNode("a");<br />
props.dump(a);<br />
|example2 = var node = props.Node.new();<br />
var a = node.initNode("a", "Hi");<br />
props.dump(a);<br />
|example3 = var node = props.Node.new();<br />
var a = node.initNode("a", 1.25, "INT");<br />
props.dump(a); # a = 1<br />
|example4 = var node = props.Node.new();<br />
node.addChild("a").setBoolValue(0);<br />
props.dump(node.getChild("a")); # a = 0 (type: bool)<br />
var a = node.initNode("a", 1.25, "INT", 1);<br />
props.dump(a); # a = 0 (type: int)<br />
}}<br />
<br />
==== isInt() (since FG 2020.1) ====<br />
{{Nasal doc<br />
|syntax = props.Node.isInt();<br />
|text = Returns true (1) if node '''type''' is "INT" or "LONG" (long integer) otherwise false (0).<br />
}}<br />
<br />
==== isNumeric() (since FG 2020.1) ====<br />
{{Nasal doc<br />
|syntax = props.Node.isNumeric();<br />
|text = Returns true (1) if node '''type''' is "INT", "LONG", "FLOAT" or "DOUBLE" otherwise false (0).<br />
}}<br />
<br />
==== remove() ====<br />
{{Nasal doc<br />
|syntax = props.Node.remove();<br />
|text = Removes the node and returns the removed node.<br />
|example1 = var node = props.Node.new();<br />
node.addChild("a");<br />
props.dump(node);<br />
node.getChild("a").remove();<br />
props.dump(node); # child "a" does not exist anymore<br />
}}<br />
<br />
==== removeAllChildren() ====<br />
{{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]]}}<br />
{{Nasal doc<br />
|syntax = props.Node.removeAllChildren();<br />
|version = 3.2<br />
|commit = {{fgdata commit|4766ed|t=commit}}<br />
|text = Removes all child nodes and returns the node.<br />
|example1 = var tree = {<br />
"a": 1,<br />
"b": 2,<br />
"c": 3<br />
};<br />
var node = props.Node.new(tree);<br />
props.dump(node);<br />
node.removeAllChildren();<br />
props.dump(node); # all children have been removed<br />
}}<br />
<br />
==== removeChild() ====<br />
{{Nasal doc<br />
|syntax = props.Node.removeChild(rel_path, idx);<br />
|text = Removes a given child node child nodes and returns the node.<br />
|param1 = rel_path<br />
|param1text = Relative path to a subnode as a string.<br />
|param2 = idx<br />
|param2text = Index of the subnode to remove as an integer.<br />
|example1 = var node = props.Node.new();<br />
node.addChild("a");<br />
props.dump(node);<br />
node.removeChild("a", 0);<br />
props.dump(node); # child "a" has been removed<br />
|example2 = var node = props.Node.new();<br />
node.addChildren("a", 2);<br />
props.dump(node);<br />
node.removeChild("a", 0);<br />
props.dump(node); # just a[1] remains<br />
}}<br />
<br />
==== removeChildren() ====<br />
{{Nasal doc<br />
|syntax = props.Node.removeChildren([name]);<br />
|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}}).<br />
|param1 = name<br />
|param1text = Optional name of children to remove as a string.<br />
|example1 = var node = props.Node.new();<br />
node.addChildren("a", 2);<br />
node.addChildren("b", 2);<br />
props.dump(node);<br />
node.removeChildren("a");<br />
props.dump(node); # just children named "b" remain<br />
|example2 = var node = props.Node.new();<br />
node.addChildren("a", 2);<br />
node.addChildren("b", 2);<br />
props.dump(node);<br />
node.removeChildren();<br />
props.dump(node); # all children removed<br />
}}<br />
<br />
==== setAttribute() ====<br />
{{Nasal doc<br />
|syntax = props.Node.setAttribute([rel_path, ]attr, value);<br />
props.Node.setAttribute(attrs);<br />
|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.<br />
{{{!}} class="wikitable"<br />
! String !! Description<br />
{{!-}}<br />
{{!}} readable {{!!}} Whether the node can be read.<br />
{{!-}}<br />
{{!}} writable {{!!}} Whether the node can be written to.<br />
{{!-}}<br />
{{!}} archive {{!!}} Whether the node will be saved when the "save" [[fgcommands|fgcommand]] is triggered.<br />
{{!-}}<br />
{{!}} trace-read {{!!}} Whether the reading of the node will be logged when <code>--log-level=info</code>.<br />
{{!-}}<br />
{{!}} trace-write {{!!}} Whether the writing to the node will be logged when <code>--log-level=info</code>.<br />
{{!-}}<br />
{{!}} userarchive {{!!}} Whether the node will be saved to the [[FlightGear configuration via XML#autosave.xml|autosave file]] (only works for actual properties).<br />
{{!-}}<br />
{{!}} preserve {{!!}} Whether the value of node will be preserved during resets (only works for actual properties).<br />
{{!}}}<br />
|param1 = rel_path<br />
|param1text = Optional relative path as a string.<br />
|param2 = attr<br />
|param2text = Name of attribute to set as a string. See above.<br />
|param3 = value<br />
|param3text = Boolean value to set the property to.<br />
|param4 = attrs<br />
|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.<br />
|example1 = var node = props.Node.new();<br />
node.setAttribute("trace-write", 1);<br />
node.setIntValue(12); # will be traced<br />
|example2 = var node = props.Node.new();<br />
node.setAttribute("readable", 0);<br />
var val = node.getValue();<br />
debug.dump(val); # prints "nil"<br />
|example3 = var node = props.Node.new();<br />
node.addChild("a");<br />
node.setAttribute("a", "trace-write", 1);<br />
node.getChild("a").setIntValue(12); # will be traced<br />
|example4 = var node = props.Node.new();<br />
node.setAttribute(35); # read + write + trace-write<br />
node.setIntValue(12); # will be traced<br />
}}<br />
<br />
==== setBoolValue() ====<br />
{{Nasal doc<br />
|syntax = props.Node.setBoolValue([rel_path, ]value);<br />
|text = {{note|For setting the values of [[Property Tree]] nodes, it is recommended to use {{func link|setprop()}} if possible, due to performance differences.}}<br />
<br />
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 "true" or "false". Returns 1 (true) if the operation was successful.<br />
|param1 = rel_path<br />
|param1text = Optional relative path as a string.<br />
|param2 = value<br />
|param2text = Value to set the node to, will be interpreted into a boolean. If it is <code>'''nil'''</code>, 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.<br />
|example1 = var node = props.Node.new();<br />
node.setBoolValue(nil);<br />
props.dump(node); # node = 0 (false)<br />
|example2 = var node = props.Node.new();<br />
node.setBoolValue("Hi");<br />
props.dump(node); # node = 1 (true)<br />
|example3 = var node = props.Node.new();<br />
node.setBoolValue(0);<br />
props.dump(node); # node = 0 (false)<br />
node.setBoolValue(1.25);<br />
props.dump(node); # node = 1 (true)<br />
|example4 = var node = props.Node.new();<br />
node.setValue("String");<br />
props.dump(node); # node = "String" (type: string)<br />
node.setBoolValue(1);<br />
props.dump(node); # node = "true"<br />
|example5 = var node = props.Node.new();<br />
node.setDoubleValue(12.32);<br />
props.dump(node); # node = 12.32 (type: double)<br />
node.setBoolValue(1);<br />
props.dump(node); # node = 1<br />
|example6 = var node = props.Node.new();<br />
node.addChild("a");<br />
node.setBoolValue("a", 1);<br />
props.dump(node); # /a = 1<br />
}}<br />
<br />
==== setDoubleValue() ====<br />
{{Nasal doc<br />
|syntax = props.Node.setDoubleValue([rel_path, ]value);<br />
|text = {{note|For setting the values of [[Property Tree]] nodes, it is recommended to use {{func link|setprop()}} if possible, due to performance differences.}}<br />
<br />
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.<br />
|param1 = rel_path<br />
|param1text = Optional relative path as a string.<br />
|param2 = value<br />
|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.<br />
|example1 = var node = props.Node.new();<br />
node.setDoubleValue(1.1);<br />
props.dump(node); # node = 1.1 (type: double)<br />
|example2 = var node = props.Node.new();<br />
node.setDoubleValue("1.1");<br />
props.dump(node); # node = 1.1 (type: double)<br />
|example3 = var node = props.Node.new();<br />
node.setBoolValue(1);<br />
node.setDoubleValue("1.1");<br />
props.dump(node); # node = 1 (type: bool)<br />
node.setDoubleValue(0.0);<br />
props.dump(node); # node = 0 (type: bool)<br />
|example4 = var node = props.Node.new();<br />
node.setIntValue(1);<br />
node.setDoubleValue(1.1);<br />
props.dump(node); # node = 1 (type: int)<br />
node.setDoubleValue("-1.1");<br />
props.dump(node); # node = -1 (type: int)<br />
|example5 = var node = props.Node.new();<br />
node.addChild("a");<br />
node.setDoubleValue("a", 12.2);<br />
props.dump(node); # /a = 12.2<br />
}}<br />
<br />
==== setIntValue() ====<br />
{{Nasal doc<br />
|syntax = props.Node.setIntValue([rel_path, ]value);<br />
|text = {{note|For setting the values of [[Property Tree]] nodes, it is recommended to use {{func link|setprop()}} if possible, due to performance differences.}}<br />
<br />
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.<br />
|param1 = rel_path<br />
|param1text = Optional relative path as a string.<br />
|param2 = value<br />
|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.<br />
|example1 = var node = props.Node.new();<br />
node.setIntValue(12);<br />
props.dump(node); # node = 12<br />
node.setIntValue("6");<br />
props.dump(node); # node = 6<br />
|example2 = var node = props.Node.new();<br />
node.setIntValue(12.2);<br />
props.dump(node); # node = 12<br />
node.setIntValue(-12.2);<br />
props.dump(node); # node = 12<br />
|example3 = var node = props.Node.new();<br />
node.setBoolValue(1);<br />
node.setIntValue(12.5);<br />
props.dump(node); # node = 1 (type: bool)<br />
node.setIntValue(0);<br />
props.dump(node); # node = 0 (type: bool)<br />
|example4 = var node = props.Node.new();<br />
node.setValue("Hi");<br />
node.setIntValue(12);<br />
props.dump(node); # node = "12" (type: string)<br />
|example5 = var node = props.Node.new();<br />
node.addChild("a");<br />
node.setIntValue("a", 12);<br />
props.dump(node); # /a = 12<br />
}}<br />
<br />
==== setValue() ====<br />
{{Nasal doc<br />
|syntax = props.Node.setValue([rel_path, ]value);<br />
|text = {{hatnote|See also {{func link|setBoolValue()|page=this}}, {{func link|setDoubleValue()|page=this}}, and {{func link|setIntValue()|page=this}}.}}<br />
<br />
{{note|For setting the values of [[Property Tree]] nodes, it is recommended to use {{func link|setprop()}} if possible, due to performance differences.}}<br />
<br />
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.<br />
<br />
{{{!}} class="wikitable"<br />
{{!}} colspan="2" style="text-align:right" {{!}} '''value''' type → <br />
! rowspan="2" {{!}} Number<br />
! rowspan="2" {{!}} String <br />
! rowspan="2" {{!}} Vector<br />
{{!-}}<br />
{{!}} style="text-align:left" {{!}} Current node<br>type ↓<br />
{{!}} style="text-align:right" {{!}} Result ↘<br />
{{!-}}<br />
! colspan="2" {{!}} None/unspecified<br />
{{!}}<br />
* Type set to <code>double</code><br />
* Node set to '''value'''.<br />
{{!}}<br />
* Type set to <code>string</code><br />
* Node set to '''value'''.<br />
{{!}}<br />
* Type set to <code>vec*d</code><br />
* Node set to '''value'''.<br />
{{!-}}<br />
! colspan="2" {{!}} Bool<br />
{{!}}<br />
* If '''value''' != 0, node set to true.<br />
* If '''value''' == 0, node set to false.<br />
{{!}}<br />
* If '''value''' == "true", node set to true.<br />
* If '''value''' can be converted to an integer,<br>and != 0, node set to true.<br />
{{!}} Node set to true.<br />
{{!-}}<br />
! colspan="2" {{!}} Integer<br />
{{!}} Node set to truncated '''value'''<br />
{{!}} Node set to '''value ''' converted and truncated to an integer.<br />
{{!}} Node set to 1.<br />
{{!-}}<br />
! colspan="2" {{!}} Double<br />
{{!}} Node set to '''value'''.<br />
{{!}} Node set to '''value''' converted to number.<br />
{{!}} Throws an error (vector is not a number).<br />
{{!-}}<br />
! colspan="2" {{!}} String<br />
{{!}} Node set to '''value''' converted to string. {{!!}} Node set to '''value'''. {{!!}} Node not set.<br />
{{!}}}<br />
|param1 = rel_path<br />
|param1text = Optional relative path as a string.<br />
|param2 = value<br />
|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.<br />
|example1 = var node = props.Node.new();<br />
node.setValue("Hi");<br />
props.dump(node); # node = "Hi"<br />
|example2 = var node = props.Node.new();<br />
node.addChild("a");<br />
node.setValue("a", "Hi");<br />
props.dump(node); # \a = "Hi"<br />
|example3 = var node = props.Node.new();<br />
node.setValue([0.4, 0.2, 1]);<br />
debug.dump(node.getValue()); # prints "[0.4, 0.2, 1]"<br />
print(node.getType()); # prints "VEC3D"<br />
}}<br />
<br />
==== setValues() ====<br />
{{Nasal doc<br />
|syntax = props.Node.setValues(val);<br />
|text = {{hatnote|See also {{func link|getValues()|page=this}}.}}<br />
<br />
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).<br />
|param1 = val<br />
|param1text = A hash that will become the property tree.<br />
|example1 = var val = {<br />
"a": 100 # "a" will become the subnode's name, and 100 its value<br />
};<br />
var node = props.Node.new();<br />
node.setValues(val);<br />
props.dump(node); # dump tree<br />
|example2 = var val = {<br />
"a": 1,<br />
"b": "Hi",<br />
"c": {<br />
"d": [1, 2, 3]<br />
}<br />
};<br />
var node = props.Node.new();<br />
node.setValues(val);<br />
props.dump(node); # dump tree<br />
}}<br />
<br />
==== unalias() ====<br />
{{Nasal doc<br />
|syntax = props.Node.unalias();<br />
|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).<br />
|example2 = var node1 = props.Node.new();<br />
node1.setDoubleValue(2.35);<br />
var node2 = props.Node.new();<br />
node2.alias(node1);<br />
<br />
props.dump(node2); # equals 2.35<br />
node2.unalias();<br />
props.dump(node2); # no value or type<br />
}}<br />
<br />
==== toggleBoolValue() (since FG 2020.1) ====<br />
{{Nasal doc<br />
|syntax = toggleBoolValue();<br />
|text = Toggle a boolean property. You have to make sure the property is of type bool!<br />
|example1 = var b = props.Node.new().initNode("/_test/bool", 1, "BOOL");<br />
print("bool ", b.getValue());<br />
b.toggleBoolValue();<br />
print("after toggleBoolValue ", b.getValue());<br />
<br />
}}<br />
<br />
== Functions ==<br />
=== compileCondition() ===<br />
{{see also|Conditions}}<br />
<br />
{{Nasal doc<br />
|syntax = props.compileCondition(node);<br />
|version = 3.2<br />
|commit = {{fgdata commit|43f8ce|t=commit}}<br />
|text = Compiles a [[conditions|condition]] property branch and returns a <code>Condition</code> ghost object or <code>'''nil'''</code> on error. This ghost will contain a <code>test()</code> function that will return the result of the condition as either 1 (true) or 0 (false).<br />
|param1 = node<br />
|param1text = Either a props.Node containing the condition, or a string specifying a place in the [[Property Tree]] where there is a condition branch.<br />
|example1 = var tree = {<br />
"equals": {<br />
"property": '/test',<br />
"value": 12<br />
}<br />
};<br />
var node = props.Node.new(tree);<br />
setprop("/test", 12);<br />
<br />
var cond = props.compileCondition(node);<br />
print(cond.test()); # prints "1" (true)<br />
setprop("/test", 15);<br />
print(cond.test()); # prints "0" (false)<br />
|example2 = var tree = {<br />
"equals": {<br />
"property": '/test',<br />
"value": 12<br />
}<br />
};<br />
props.globals.getNode("test2/condition", 1).setValues(tree); # place it in the Property Tree<br />
setprop("/test", 12);<br />
<br />
var cond = props.compileCondition(node);<br />
print(cond.test()); # prints "1" (true)<br />
setprop("/test", 15);<br />
print(cond.test()); # prints "0" (false)<br />
}}<br />
<br />
=== condition() ===<br />
{{Nasal doc<br />
|syntax = props.condition(node);<br />
|text = Evaluates a [[conditions|condition]] property branch and returns the result as a boolean.<br />
|param1 = node<br />
|param1text = Either a props.Node containing the condition, or a string specifying a place in the [[Property Tree]] where there is a condition branch.<br />
|example1 = var tree = {<br />
"equals": {<br />
"property": '/test',<br />
"value": 12<br />
}<br />
};<br />
var node = props.Node.new(tree);<br />
setprop("/test", 12);<br />
<br />
print(props.condition(node)); # prints "1" (true)<br />
setprop("/test", 15);<br />
print(props.condition(node)); # prints "0" (false)<br />
|example2 = var tree = {<br />
"equals": {<br />
"property": '/test',<br />
"value": 12<br />
}<br />
};<br />
props.globals.getNode("test2/condition", 1).setValues(tree); # place it in the Property Tree<br />
setprop("/test", 12);<br />
<br />
print(props.condition(node)); # prints "1" (true)<br />
setprop("/test", 15);<br />
print(props.condition(node)); # prints "0" (false)<br />
}}<br />
<br />
=== copy() ===<br />
{{Nasal doc<br />
|syntax = props.copy(src, dest[, attr]);<br />
|text = Copies the property tree of the source into the destination node. Note that aliased properties will not be copied.<br />
|param1 = src<br />
|param1text = Source <code>props.Node object</code> to copy from.<br />
|param2 = dest<br />
|param2text = Destination <code>props.Node object</code> to copy to.<br />
|param3 = attr<br />
|param3text = If set to 1 (true), attributes will also be copied. Defaults to 0 (false).<br />
|example1 = var tree = {<br />
"a": 1.5,<br />
"b": "Hi",<br />
"c": {<br />
"d": [1, 2, 3]<br />
}<br />
};<br />
var src = props.Node.new(tree);<br />
var dest = props.Node.new();<br />
props.copy(src, dest);<br />
props.dump(dest);<br />
|example2 = var src = props.Node.new();<br />
var a = src.addChild("a");<br />
a.setAttribute("trace-write", 1);<br />
a.setIntValue(12);<br />
var dest = props.Node.new();<br />
props.copy(src, dest, 1);<br />
print(dest.getNode("a").getAttribute("trace-write")); # prints "1" (true)<br />
}}<br />
<br />
=== dump() ===<br />
{{Nasal doc<br />
|syntax = props.dump(node);<br />
|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.<br />
|param1 = node<br />
|param1text = Node to dump.<br />
|example1 = var node = var tree = {<br />
"a": 12,<br />
"b": "Hi",<br />
"c": {<br />
"d": [1, 2, 3]<br />
}<br />
};<br />
var node = props.Node.new(tree);<br />
props.dump(node); # dump into console<br />
|example2 = # Dump the entire Property Tree<br />
# Warning! This is an intensive operation!<br />
props.dump(props.globals);<br />
}}<br />
<br />
=== getNode() ===<br />
{{Nasal doc<br />
|syntax = props.getNode();<br />
|version = 3.2<br />
|commit = {{fgdata commit|807062|t=commit}}<br />
|text = Shortcut for <syntaxhighlight lang="nasal" inline>props.globals.getNode()</syntaxhighlight>. See {{func link|getNode()||Node|page=this}} for full documentation.<br />
|example1 = print("Current aircraft is: ", props.getNode("/sim/aircraft").getValue());<br />
}}<br />
<br />
=== nodeList() ===<br />
{{Nasal doc<br />
|syntax = props.nodeList(arg[, arg[, ...]]);<br />
|text = Converts its arguments into a vector of node objects if possible and returns that vector. <br />
|param1 = arg<br />
|param1text = Object to operate on. Must be a node object, string, vector, hash, function, or <code>prop</code> 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.<br />
|example1 = var node = props.Node.new();<br />
var f = func(){<br />
var n = props.Node.new();<br />
return n._g;<br />
}<br />
var list = props.nodeList(node,<br />
"/sim/aircraft",<br />
["/sim/fg-root"],<br />
{ "path": "/sim/fg-home" },<br />
f<br />
);<br />
debug.dump(list); # dump list<br />
|example2 = var root = "/sim/version/";<br />
var info = [<br />
root ~ "build-id",<br />
root ~ "build-number",<br />
root ~ "flightgear",<br />
root ~ "hla-support",<br />
root ~ "openscenegraph",<br />
root ~ "revision",<br />
root ~ "simgear"<br />
];<br />
info = props.nodeList(info); # turn into list of nodes<br />
foreach(var n; info){<br />
print(n.getValue()); # dump info<br />
}<br />
}}<br />
<br />
=== runBinding() ===<br />
{{Nasal doc<br />
|syntax = props.runBinding(node[, module]);<br />
|text = Runs a [[Bindings|binding]] element in a node object. Returns 1 (true) on success and 0 (false) on failure.<br />
|param1 = node<br />
|param1text = A {{tag|binding}} element as a node object.<br />
|param2 = module<br />
|param2text = Optional string specifying a module to run Nasal scripts in if the command is <code>nasal</code>. This argument will not override any {{tag|module}} element in the '''node'''<br />
|example1 = var tree = {<br />
"command": "dialog-show",<br />
"dialog-name": "map" # open map<br />
};<br />
var binding = props.Node.new(tree);<br />
props.runBinding(binding);<br />
|example2 = var tree = {<br />
"command": "nasal",<br />
"script": 'print(pi)' # prints value of math.pi<br />
};<br />
var binding = props.Node.new(tree);<br />
props.runBinding(binding, "math");<br />
|example3 = var tree = {<br />
"command": "nasal",<br />
"script": 'print(pi)', # prints value of math.pi<br />
"module": "math" # this is used<br />
};<br />
var binding = props.Node.new(tree);<br />
props.runBinding(binding, "debug");<br />
}}<br />
<br />
=== setAll() ===<br />
{{Nasal doc<br />
|syntax = props.setAll(base, child, value);<br />
|text = Sets indexed subnodes in the Property Tree with the same name to the same value.<br />
|param1 = base<br />
|param1text = Base path to the nodes.<br />
|param2 = child<br />
|param2text = Path to child nodes.<br />
|param3 = value<br />
|param3text = Value to set the subnodes to.<br />
|example1 = # apply 50% throttle to all engines<br />
props.setAll("/controls/engines/engine", "throttle", 0.5);<br />
|example2 = var nodes = props.globals.addChildren("/test", 3);<br />
foreach(var node; nodes){<br />
node.addChild("a");<br />
}<br />
props.setAll("/test", "a", "Hi"); # set all children (test[*]/a) to "Hi"<br />
}}<br />
<br />
=== wrap() ===<br />
{{Nasal doc<br />
|syntax = props.wrap(node);<br />
|text = Turns <code>prop</code> ghosts, either in a vector or single, into <code>props.Node</code> objects.<br />
|param1 = node<br />
|param1text = <code>prop</code> ghost or vector of such ghosts.<br />
|example1 = var ghost = canvas._newCanvasGhost();<br />
var node = props.wrap(ghost._node_ghost);<br />
props.dump(node);<br />
|example2 = var vector = [canvas._newCanvasGhost()._node_ghost, props.Node.new()._g];<br />
var nodes = props.wrap(vector);<br />
foreach(var node; nodes){<br />
props.dump(node);<br />
print("----");<br />
}<br />
}}<br />
<br />
=== wrapNode() ===<br />
{{Nasal doc<br />
|syntax = props.wrapNode(node);<br />
|text = Turns a <code>prop</code> ghost into a <code>props.Node</code> object.<br />
|param1 = node<br />
|param1text = <code>prop</code> ghost to convert.<br />
|example1 = var ghost = canvas._newCanvasGhost();<br />
var node = props.wrapNode(ghost._node_ghost);<br />
props.dump(node);<br />
}}<br />
<br />
== Variable ==<br />
=== globals ===<br />
{{Nasal doc<br />
|syntax = props.globals;<br />
|text = Exposes the [[Property Tree]] as a <code>props.Node</code> object.<br />
<br />
|example1 = print("Current aircraft: ", props.globals.getNode("/sim/aircraft").getValue());<br />
|example2text = Alternative using {{func link|getprop()}}.<br />
|example2 = print("Current aircraft: ", getprop("/sim/aircraft"));<br />
}}<br />
<br />
{{Nasal namespaces}}</div>Jsbhttps://wiki.flightgear.org/w/index.php?title=Nasal_Initialization&diff=125435Nasal Initialization2020-06-03T21:36:01Z<p>Jsb: </p>
<hr />
<div>{{Nasal Navigation}}<br />
some notes on how Nasal files from FGDATA/Nasal are processed when Flightgear is started.<br />
<br />
= Initialization of Nasal core-/default-modules =<br />
When Flightgear starts, it will start the Nasal parser and load Nasal (.nas) files from FGDATA/Nasal. <br />
<br />
== Core modules ==<br />
The nasal root folder (FGDATA/Nasal/) is scanned for .nas files and these files are loaded in alphabetical order.<br />
After all .nas files are loaded {{code|/sim/signals/nasal-dir-initialized}} is set, which triggers listeners in some of the files just loaded.<br />
This allows to use functions from other core files, which have just been defined, e.g. which were not yet available when parsing the file.<br />
{{note|for a list of modules see [[Nasal Modules]]|margin=10px|width=50%}}<br />
=== Nasal load priority (version 2020.1) ===<br />
Since version 2020.1 files that are listed in /Nasal/loadpriority.xml will be loaded first (in the order they appear in this XML).<br />
The remaining files will be loaded in alphabetical order.<br />
After some dependency checks, loadpriority.xml was filled with the relevant filenames. <br />
Most of the listeners (nasal-dir-initialized) have been removed accordingly. <br />
<br />
== Optional (load-once) modules ==<br />
After the files in the root directory have been processed, the first level of subdirectories is scanned for .nas files.<br />
Each subdirectory defines a module / namespace which becomes available only after the files in the subdirectory have been completly processed, <br />
e.g. a variable foo in module bar becomes available as bar.foo only after parsing of the respective folder.<br />
<br />
{{note| <br />
Sub-sub-directories will not be scanned for .nas files - at least not automatically on FG startup.<br />
Of course, files of sub-sub-directories can be included (sooner or later) by the Nasal code that ''is'' being processed, so optional code can be loaded on demand.<br />
|width=50%|margin=10px}}<br />
<br />
=== Enabling of load-once modules ===<br />
From the user (aircraft developer) point of view, enabling of this modules is done by setting the property {{code|/nasal/<moduleName>/enabled}} to {{code|true}}. <br />
<br />
If a module is needed by an aircraft, you can just add the following to your aircraft-set.xml file (replace ''module_name'' by the name of the desired module):<br />
<syntaxhighlight lang="xml"><br />
<PropertyList><br />
<nasal><br />
<module_name><br />
<enabled type="bool">true</enabled><br />
</module_name><br />
<nasal><br />
</PropertyList><br />
</syntaxhighlight><br />
Loading is done in the C++ code and is a little bit complicated (see FG sources /src/Scripting/NasalSys.cxx).<br />
''FGNasalSys::init()'' scans FGDATA/Nasal for subdirectories and calls ''FGNasalSys::addModule()'' for each subdirectory passing its name as module name and a list of all .nas files in it.<br />
<br />
''FGNasalSys::addModule()'' in turn creates property nodes for each file like ''/nasal/<moduleName>/file[i] = <filename>''<br />
{{caution| For each subdirectory there SHALL be an entry in FGDATA/defaults.xml defining the default state of the module. }}<br />
<br />
{{warning| At the time of writing addModule() creates ''/nasal/<moduleName>/enabled'' and sets it to ''true'' if it was not defined in defaults.xml. This is about to be corrected.}}<br />
<br />
init() continues, sets the property ''/sim/signal/nasal-dir-initialized'' to true which will trigger listeners in some of the previously loaded Nasal files.<br />
<br />
Next it calls ''FGNasalSys::loadPropertyScripts()'' which checks all the ''/nasal/<moduleName>/enabled'' nodes. If true, the files will be loaded by calling ''FGNasalSys::loadModule()'' which in turn calls ''FGNasalSys::createModule()''. <br />
<br />
Otherwise, if enabled is false, a listener is added that will load the module when enabled is set to true.<br />
{{note|The module name (which defaults to the name of the subdirectory) is also the name of the Nasal namespace in which functions and variables will be created.<br />
If the namespace already exists, the module will be added to it so you can extend existing modules. But be very careful when overloading existing modules, you can easily break things.}}<br />
<br />
== Re-loadable modules ==<br />
This type of module can be loaded and unloaded at runtime without exiting or restarting FlightGear.<br />
See [[Modules.nas]] for details.</div>Jsbhttps://wiki.flightgear.org/w/index.php?title=Emesary_notifications&diff=124828Emesary notifications2020-05-22T09:31:15Z<p>Jsb: reserve id 22 for EFISNotification</p>
<hr />
<div>{{Nasal Navigation}}<br />
<br />
This page defines the '''Emesary Notifications''' that are available in the base package; but also a reference of all currently active notification ID's that can be transmitted over MP.<br />
<br />
=== PropertySyncNotificationBase ===<br />
PropertySyncNotificationBase_Id = 16<br />
<br />
This is used to transmit a set of properties from one craft to a multiplayer instance of itself. The contents of the notification are variable and defined by the model. see $FGdata/Nasal/notifications.nas<br />
<br />
=== AircraftControlNotification ===<br />
AircraftControlNotification_Id = 17<br />
<br />
This is used to allow any number of elements to be controlled, such as cockpit switches, but theoretically anything on the model that needs to be controlled either as a single player ownship, or as a multiplayer dual control type of scenario. NOTE: Properly coded aircraft models using Emesary to implement bindings require no extra code to work in multiplayer, all that is required is to bridge the control notifications over MP using the Emesary MP bridge. see $FGdata/Nasal/notifications.nas<br />
<br />
=== GeoEventNotification ===<br />
GeoEventNotification_Id = 18<br />
<br />
This is a notification, usually from one craft to another, over MP, that something happened at a certain position (lat,lon,alt). This can be used to drop tanks, paratroopers, launch X-15's or anything that happens that one craft needs to tell another about. see $FGdata/Nasal/notifications.nas<br />
<br />
=== ArmamentNotification === <br />
ArmamentNotification_Id = 19<br />
<br />
Used to indicate when an some sort of armament has impacted another model. See f-14b/Nasal/ArmamentNotification.nas<br />
<br />
=== PFDEventNotification_Id ===<br />
PFDEventNotification_Id = 20<br />
<br />
Use to transmit PFD events for a particular device. see $FGdata/Nasal/notifications.nas<br />
<br />
=== ArmamentInFlightNotification ===<br />
ArmamentInFlightNotification_Id = 21<br />
<br />
This is a notification that a particular type of armament is in flight. see f-14b/Nasal/ArmamentNotification.nas<br />
<br />
=== EFISNotification_Id ===<br />
EFISNotification_Id = 22<br />
<br />
Use to transmit EFIS events. Work in progress, see $FGdata/Nasal/notifications.nas later<br />
<br />
<br />
== Related content ==<br />
=== Wiki articles ===<br />
* [[Emesary]]<br />
* [[Emesary MFD Bridge]]<br />
<br />
=== Forum topics ===<br />
* {{Search|mode=forum|keywords=emesary}}<br />
<!--<br />
=== Mailing list ===<br />
* {{Search|mode=list|keywords=emesary}} --><br />
<br />
== External documentation ==<br />
* http://chateau-logic.com/content/emesary-multiplayer-bridge-flightgear<br />
* http://chateau-logic.com/content/emesary-nasal-implementation-flightgear<br />
* http://emesary.codeplex.com/<br />
* https://emesary.codeplex.com/documentation<br />
* http://chateau-logic.com/content/emesary-efficient-inter-object-communication-using-interfaces-and-inheritance</div>Jsbhttps://wiki.flightgear.org/w/index.php?title=Modules.nas&diff=124738Modules.nas2020-05-17T17:25:16Z<p>Jsb: /* Methods of class Module */</p>
<hr />
<div>== Nasal runtime re-loadable modules ==<br />
=== Introduction and motivation ===<br />
The embedded scripting language of FlightGear, [[Nasal]] comes with a limited number of [[Nasal library|core functions]].<br />
<br />
The language has been [[Howto:Extend Nasal|extended by C++ functions]], as well as by libraries written in Nasal (for example [[Nasal library/props|props]], [[Nasal library/io|io]], [[Nasal library/math|math]], [[Nasal library/debug|debug]] etc). <br />
<br />
The latter are stored in the [[FGData]] repository under <code>/Nasal</code> and loaded automatically by FlightGear.<br />
<br />
Optional code, which is loaded only on demand, can be handled either by the legacy module system implemented in C++ or by <code>modules.nas</code> if run-time re-load support is required.<br />
<br />
For more information on how and when these files are loaded, see [[Nasal Initialization]].<br />
<br />
=== Differences between add-ons and modules ===<br />
While there are many similarities between add-ons and modules, there are also some differences: <br />
<br />
; Distribution<br />
: Modules are distributed with FlightGear as part of FGData or they are aircraft specific and delivered with the aircraft.<br />
: Add-ons have to be downloaded separately by a FlightGear user from wherever the author of the add-on publishes the add-on.<br />
<br />
; Loading<br />
: Modules can be loaded for example by an aircraft if the aircraft developer wants to make use of the module.<br />
: Add-ons are selected by the user before launching FlightGear, thus they may or '''may not be available''' at runtime.<br />
<br />
== HOWTOs and examples ==<br />
First some examples, the API documentation follows below.<br />
<br />
=== Nasal modules in an aircraft ===<br />
Nasal code for an aircraft (for example for development) is usually loaded by a XML declaration in the <code>[[Aircraft-set.xml]]</code> file like this:<br />
<syntaxhighlight lang="xml"><br />
<PropertyList><br />
<nasal><br />
<foo> <!-- Nasal namespace foo --><br />
<file>Nasal/foo.nas</file><br />
</foo><br />
<bar> <!-- Nasal namespace bar --><br />
<file>Aircraft/Generic/foo.nas</file><br />
</bar><br />
<myAircraft> <!-- Nasal namespace myAircraft--><br />
<file>Nasal/efis_module.nas</file><br />
</myAircraft><br />
</nasal><br />
</PropertyList><br />
</syntaxhighlight><br />
<br />
This is fine unless you are developing a Nasal subsystem for the aircraft and have to restart FlightGear every time you edit a few lines. <br />
For this case you could consider to use the Module class (defined in <code>FGDATA/Nasal/modules.nas</code>) and make your code re-loadable at runtime, <br />
which means you do not have to restart whole FlightGear just for a few lines of edited Nasal code.<br />
<br />
Example <code>Nasal/efis_module.nas</code>:<br />
<syntaxhighlight lang="nasal"><br />
#-- load EFIS as reloadable module<br />
var my_efis = modules.Module.new("myAircraft_EFIS"); # Module name<br />
my_efis.setDebug(1); # 0=(mostly) silent; 1=print setlistener and maketimer calls to console; 2=print also each listener hit, be very careful with this! <br />
my_efis.setFilePath(getprop("/sim/aircraft-dir")~"/Nasal/EFIS");<br />
my_efis.setMainFile("myAircraft-efis.nas");<br />
my_efis.load();<br />
</syntaxhighlight><br />
<br />
==== Menu item for reloading ====<br />
Now add a menu item for easy reloading like this:<br />
{{note|The module name passed to the fgcommand must match the name passed to <code>modules.Module.new()</code>}}<br />
<syntaxhighlight lang="xml"><br />
<menubar><br />
<default><br />
<menu n="100"><br />
<!-- normal aircraft menu --><br />
</menu><br />
<menu n="101"><br />
<label>Aircraft Development</label><br />
<item><br />
<label>Reload EFIS</label><br />
<binding><br />
<command>nasal-module-reload</command><br />
<module>myAircraft_EFIS</module><br />
</binding><br />
</item><br />
</menu><br />
</default><br />
</menubar><br />
</syntaxhighlight><br />
<br />
{{tip|You can also call <code>myAircraft.my_efis.reload();</code> to trigger the reload. <code>myAircraft</code> is the namespace given in the <code>Aircraft-set.xml</code> file, <code>my_efis</code> is the module object (see above).}}<br />
<br />
=== Nasal frameworks ===<br />
Another use case for <code>Modules.nas</code> is frameworks that need reload support.<br />
<br />
A framework usually provides more or less generic functionality that has to be adapted (configured) for a specific use case (for example a specific aircraft).<br />
<br />
Reload support may become very convenient, if the customizing based on such framework involves many iterations of edit-reload-test.<br />
<br />
First living example is the <code>canvas_efis</code> framework which can be included into an aircraft with a single line of code: <br />
<br />
var efis_module = modules.load("canvas_efis");<br />
<br />
(see also last chapter at the end of this article)<br />
<br />
== Structure of Nasal reloadable modules ==<br />
A module must contain at least the file <code>main.nas</code> (default, other filename is possible) and should contain the functions <code>main()</code> and <code>unload()</code>.<br />
<br />
==== main() function ====<br />
The <code>main.nas</code> file must contain a <code>main()</code> function that will be called on load with either the module object as parameter or the parameters passed to the <code>module.load()</code> function.<br />
<br />
==== unload() function ====<br />
If you want the module to be re-/un-loadable, you must make sure to track resources and remove them on onload.<br />
For this the <code>main.nas</code> shall contain a function <code>unload()</code> that removes any resources which were created by the module.<br />
<br />
'''Example:''' any canvas created by the module should have called its <code>del()</code> method here.<br />
<br />
{{note|Calls to <code>setlistener()<code> and <code>maketimer()<code> are automatically tracked by the <code>Module</code> class for you, timers will be stopped automatically, listeners will be removed automatically on <code>unload()<code>.}}<br />
<br />
==== Rules for Module names ====<br />
# Module names shall contain only letters (<code>a-z, A-Z</code>), numbers (<code>0-9</code>) and underscores (<code>_</code>).<br />
# The name must contain at least one letter so it cannot be confused with a number<br />
# The name shall not match any existing Nasal file (<code>.nas</code>) or directory in <code>FGDATA/Nasal</code> to avoid namespace clashes<br />
<br />
{{note|Reloadable modules shipped with FlightGear are stored in subdirectories of <code>FGDATA/Nasal/modules/</code> as these subdirectories will not be loaded automatically by FlightGear on start.<br />
<br />
However, <code>modules.nas</code> will scan this directory to create a list of available modules (list of subdirectories).<br />
<br />
Each module corresponds to one subdirectory and the directory name is also the module name.<br />
}}<br />
<br />
== modules.nas ==<br />
In <code>modules.nas</code> the class <code>Module</code> is defined.<br />
<br />
A module object holds information about the path and filename of the Nasal script and supports unloading and reloading the code at runtime<br />
(for example without restarting FlightGear as a whole) by tracking some critical resources like [[listeners]] and [[timers]].<br />
<br />
{{note|Parts of this functionality were added to the [[addons]] manager earlier and have now been extracted to avoid code duplication.}}<br />
<br />
{{caution|Within a module <code>setlistener()</code> is overloaded and the default value for the 4th argument (runtime) is changed to <code>0</code>, so the listener will run only if the property value has changed.}}<br />
<br />
=== library functions ===<br />
==== modules.isAvailable() ====<br />
{{Nasal doc<br />
|syntax = modules.isAvailable(module_name);<br />
|text = This function returns true, if there is a module (subdirectory) in <code>FGDATA/Nasal/modules/</code> with the given name.<br />
|param1 = module_name<br />
|param1text = The name of the module (in the subdirectory in <code>Nasal/modules</code>) to load.<br />
|example1 = <br />
if (modules.isAvailable("foo_bar")) {<br />
modules.load("foo_bar");<br />
} <br />
}}<br />
<br />
==== modules.setDebug() ====<br />
{{Nasal doc<br />
|syntax = modules.setDebug(module_name, [debug=1]);<br />
|text = This function enables debugging for a module. It must be called '''before''' <code>load()</code>!<br />
|param1 = module_name<br />
|param1text = The name of the module (in the subdirectory in <code>Nasal/modules</code>) to load.<br />
|param2 = debug<br />
|param2text = Defaults to <code>1</code> (true), use <code>0</code> to disable debug. If debug > <code>1</code>, each listener hit will be printed, be careful not to listen to rapidly changing props, do not set <code>runtime=1</code> in <code>setlistener()</code>.<br />
|example1 = <br />
var debug = 1;<br />
modules.setDebug("foo_bar", debug); <br />
}}<br />
<br />
==== modules.load() ====<br />
{{Nasal doc<br />
|syntax = modules.load(module_name, [namespace_name]);<br />
|text = This function attempts to load a module from <code>FGDATA/Nasal/modules/</code><br />
|param1 = module_name<br />
|param1text = The name of the module (in the subdirectory in <code>Nasal/modules</code>) to load.<br />
|param2 = namespace_name<br />
|param2text = Optional, load module to a different namespace.<br />
|example1 = <br />
modules.load("foo_bar"); <br />
}}<br />
<br />
=== Methods of class Module ===<br />
==== setFilePath() ====<br />
{{Nasal doc<br />
|syntax = mymod.setFilePath(path);<br />
|text = Configure where to look for the main file, for example in the aircraft (sub-)directory.<br />
|param1 = path<br />
|param1text = File path where the module is stored.<br />
}}<br />
<br />
==== setMainFile() ====<br />
{{Nasal doc<br />
|syntax = mymod.setMainFile(filename);<br />
|text = Configure the nasal file to load.<br />
|param1 = filename<br />
|param1text = File that will be loaded by <code>load()</code>.<br />
|example1 =<br />
# if you develop a new nasal system for your aircraft, it might be handy to implement it as module<br />
# so you can reload the file quickly without restarting FlightGear<br />
var my_foo_sys = modules.Module.new("my_aircraft_foo");<br />
my_foo_sys.setDebug(1);<br />
my_foo_sys.setFilePath(getprop("/sim/aircraft-dir")~"/Nasal");<br />
my_foo_sys.setMainFile("foo.nas");<br />
my_foo_sys.load();<br />
}}<br />
<br />
==== setNamespace() ====<br />
{{Nasal doc<br />
|syntax = mymod.setNamespace(namespace);<br />
|text = Configure the Nasal namespace to use. Be really careful when using existing namespaces! <code>unload()</code> or <code>reload()</code> will destroy them!<br />
|param1 = namespace<br />
|param1text = The Nasal namespace the module code will be loaded into.<br />
}}<br />
<br />
==== setlistenerRuntimeDefault() ====<br />
{{Nasal doc<br />
|syntax = mymod.setlistenerRuntimeDefault(i);<br />
|text = This changes the default setlistener behaviour for this module regarding 4th ('runtime') argument. FG default is 1 (=run listener every time the prop is written to), but you might want a default of 0 for your module (= run listener only, if value has changed). This is kind of a convenience function. It is better to explicitly specify the desired parameters when calling setlistener().<br />
|param1 = i<br />
|param1text = integer, 0..2 define the default value to setlister 4th parameter if not specified explicitly<br />
}}<br />
<br />
==== setDebug() ====<br />
{{Nasal doc<br />
|syntax = mymod.setDebug(debug=1);<br />
|text = Activate debugging for this module. '''Must be called before calling <code>load()</code>!'''<br />
|param1 = debug<br />
|param1text = <br />
0: no debugging; <br />
1 (default if no argument given): print calls to redirected <code>setlister()</code> and <code>maketimer()</code>; <br />
<br />
2: listeners print property path when hit (Use with caution! '''Do not call <code>setlistener()</code> with <code>runtime=1</code>'''.)<br />
}}<br />
<br />
==== load() ====<br />
{{Nasal doc<br />
|syntax = mymod.load([args]);<br />
|text = This function attempts to load the module into its namespace.<br />
|param1 = optional args<br />
|param1text = Arguments are passed to the <code>main()</code> function of the module. If empty, the module object will be passed to <code>main()</code>.<br />
}}<br />
<br />
==== unload() ====<br />
{{Nasal doc<br />
|syntax = mymod.unload();<br />
|text = This function attempts to remove tracked resources and remove the module by killing its namespace.<br />
}}<br />
<br />
==== reload() ====<br />
{{Nasal doc<br />
|syntax = mymod.reload();<br />
|text = Shorthand, calls <code>unload()</code> and <code>load()</code>.<br />
}}<br />
<br />
==== get() ====<br />
{{Nasal doc<br />
|syntax = mymod.get(var_name);<br />
|text = Returns a variable from the modules namespace.<br />
|param1 = var_name<br />
|param1text = The variable to get.<br />
|example1 =<br />
var foo = modules.load("foo");<br />
var bar = foo.get("bar"); # get variable "bar" defined in FGDATA/Nasal/modules/foo/main.nas (or a file included by this file)<br />
}}<br />
<br />
== Property tree interface for modules.nas ==<br />
In the property tree there is a subtree <code>/nasal</code> to control modules and get some statistics.<br />
<br />
The properties available depend on the type of module ("load-once" or "reloadable", see [[Nasal Initialization]] for more information on the differences).<br />
<br />
=== Reloadable modules / frameworks ===<br />
Modules handled by <code>modules.nas</code> will have their properties in <code><nowiki>/nasal/modules/<moduleName></nowiki></code> where <code><nowiki><moduleName></nowiki></code> is given by the developer when calling either <code><nowiki>Module.new("<moduleName>")</nowiki></code> or <code><nowiki>modules.load("<moduleName>")</nowiki></code>.<br />
<br />
In the latter case <code><nowiki><moduleName></nowiki></code> specifies the subdirectory <code><nowiki>FGDATA/Nasal/modules/<moduleName></nowiki></code> in which some framework is stored.<br />
<br />
{| class="wikitable"<br />
|-<br />
! property !! type !! content<br />
|-<br />
| <code>loaded</code> || bool || true if module was loaded without errors<br />
|-<br />
| <code>reload</code> || bool || to trigger reload from the property browser. For menues, XML bindings and scripts the fgcommand is the prefered way to trigger reload.<br />
|-<br />
| <code>listeners</code> || int || Number of tracked listeners<br />
|-<br />
| <code>listener-hits</code> || int || If debugging is enabled, this prop shows the total number of hits to all tracked listeners.<br />
|-<br />
| <code>timers</code> || int || Number of tracked timers (maketimer).<br />
|}<br />
<br />
=== Legacy load-once modules ===<br />
A legacy load-once module is a direct (1st level) subdirectory of <code>FGDATA/Nasal/</code> and its corresponding property tree is <code><nowiki>/nasal/<moduleName>/</nowiki></code> where <code><nowiki><moduleName></nowiki></code> equals the name of the subdirectory.<br />
<br />
It is handled by C++ code and must have a corresponding entry in <code>FGDATA/defaults.xml</code> which defines a property "enabled" and optionally a property "module".<br />
<br />
If enabled is set to false in <code>defaults.xml</code>, the C++ code will setup a listener and load the module as soon as enabled is set to true.<br />
<br />
The property name "module" is a bit misleading, it is used to define into which namespace the files shall be loaded.<br />
<br />
For each Nasal file in the subdirectory a <code>file[i]</code> property is created holding the full path+filename.<br />
<br />
The bool <code>loaded</code> property shows the status of the module.<br />
<br />
== Existing modules with reload support ==<br />
Stable Nasal frameworks which support reloading can be added to <code>FGDATA/Nasal/modules/<module_name></code>. <br />
This allows an aircraft developer to configure the framework for a specific aircraft and make use of the reload magic while developing the configuration.<br />
<br />
{| class="wikitable"<br />
|-<br />
! Module name !! Desctiption !! time added <br />
|-<br />
| [[Canvas EFIS Framework|<code>canvas_efis</code>]] || framework to manage canvas based EFIS screens || 02/2020<br />
|}<br />
<br />
[[Category:Nasal]]</div>Jsbhttps://wiki.flightgear.org/w/index.php?title=Nasal_library&diff=124512Nasal library2020-05-05T05:04:41Z<p>Jsb: vecindex() details</p>
<hr />
<div>{{Nasal Navigation|nocat=1}}<br />
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:<br />
* {{flightgear file|src/Scripting}}<br />
* {{simgear file|simgear/nasal}}<br />
<br />
All these functions and variables are in the global namespace, that is, they are directly accessible (e.g., one can call <syntaxhighlight lang="nasal" inline>magvar()</syntaxhighlight> instead of <syntaxhighlight lang="nasal" inline>namespace.magvar()</syntaxhighlight>). However, if a namespace must be used, <code>globals</code> is the correct namespace, but using it is not recommended. For a more complete explanation, see [[Nasal Namespaces in-depth]].<br />
<br />
{{tip|Copy & paste the examples into your [[Nasal Console]] and execute them to see what they do.|width=70%}}<br />
<br />
== Core library functions ==<br />
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:<br />
* http://plausible.org/nasal/lib.html ([http://web.archive.org/web/20101010094553/http://plausible.org/nasal/lib.html archive])<br />
* {{simgear file|simgear/nasal/lib.c}} ([http://sourceforge.net/p/flightgear/simgear/ci/next/log/?path=/simgear/nasal/lib.c history])<br />
<br />
=== append() ===<br />
{{Nasal doc<br />
|syntax = append(vector, element[, element[, ...]]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=42|t=Source}}<br />
|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.<br />
|param1 = vector<br />
|param1text = The vector to which the arguments will be appended.<br />
|param2 = element<br />
|param2text = An element to be added to the vector.<br />
|example1 = <br />
var vector = [1, 2, 3]; # Initialize the vector<br />
append(vector, 4); # Append the number 4 to the end of the vector<br />
debug.dump(vector); # Print the contents of the vector<br />
|example2 = <br />
var vector = [1, 2, 3]; # Initialize the vector<br />
append(vector, 4, 5, 6); # Append the numbers 4, 5, and 6 to the end of the vector<br />
debug.dump(vector); # Print the contents of the vector<br />
}}<br />
<br />
=== bind() ===<br />
{{Nasal doc<br />
|syntax = bind(function, locals[, outer_scope]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=502|t=Source}}<br />
|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.<br />
|param1 = function<br />
|param1text = Function to evaluate.<br />
|param2 = locals<br />
|param2text = Hash containing values that will become the namespace (first closure) for the function.<br />
|param3 = outer_scope<br />
|param3text = Optional function which is bound to the next closure. This can be bound to yet another, making a linked list.<br />
|example1 = # This is a namespace/hash with a single member, named "key," which is initialized to 12 <br />
var Namespace = {<br />
key: 12<br />
};<br />
<br />
# This is different namespace/hash containing a function<br />
# dividing a variable "key" (which is unavailable/nil in this namespace) by 2<br />
var AnotherNamespace = {<br />
ret: func {<br />
key /= 2;<br />
}<br />
};<br />
<br />
# To see that key is not available, try to call AnotherNamespace.ret() first<br />
call(AnotherNamespace.ret, [], nil, nil, var errors = []);<br />
if(size(errors)){<br />
print("Key could not be divided/resolved!");<br />
debug.printerror(errors);<br />
}<br />
<br />
# Associate the AnotherNamespace.ret() function with the first namespace<br />
# so that "key" is now available<br />
var function = bind(AnotherNamespace.ret, Namespace);<br />
<br />
# Invoke the new function<br />
function();<br />
<br />
# Print out the value of Namespace.key<br />
# It was changed to 12 from 6 by AnotherNamespace.ret()<br />
print(Namespace.key);<br />
}}<br />
<br />
=== call() ===<br />
{{Nasal doc<br />
|syntax = call(func[, args[, me[, locals[, error]]]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=247|t=Source}}<br />
|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. <br />
|param1 = func<br />
|param1text = Function to execute.<br />
|param2 = args<br />
|param2text = Vector containing arguments to give to the called function.<br />
|param3 = me<br />
|param3text = <code>'''me'''</code> reference for the function call (i.e., for method calls). If given, this will override any <code>'''me'''</code> value existing in the namespace (locals argument).<br />
|param4 = locals<br />
|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.<br />
|param5 = error<br />
|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}}.<br />
|example1 =<br />
# prints "Called from call()"<br />
call(func {<br />
print("Called from call()");<br />
});<br />
|example2 =<br />
# prints "a = 1 : b = 2<br />
call(func(a, b){<br />
print("a = ", a, " : b = ", b);<br />
},<br />
[1, 2]<br />
);<br />
|example3 =<br />
var Hash = {<br />
new: func {<br />
var m = { parents: [Hash] };<br />
<br />
m.el1 = "string1";<br />
m.el2 = "string2";<br />
<br />
return m;<br />
}<br />
};<br />
<br />
# prints "me.el1 = string1", then "me.el2 = string2" on the next line<br />
call(func(a, b){ <br />
print("me.el", a, " = ", me["el" ~ a]); <br />
print("me.el", b, " = ", me["el" ~ b]);<br />
},<br />
[1, 2],<br />
Hash.new()<br />
);<br />
|example4 =<br />
# prints the value of math.pi<br />
call(func {<br />
print(pi);<br />
}, nil, nil, <br />
math<br />
);<br />
|example5 =<br />
call(func {<br />
print(math.ip); # math.ip doesn't exist<br />
}, nil, nil, nil,<br />
var errs = []<br />
);<br />
debug.printerror(errs); # The error is caught and printed using debug.printerror()<br />
}}<br />
<br />
=== caller() ===<br />
{{Nasal doc<br />
|syntax = caller([level]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=404|t=Source}}<br />
|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.<br />
<br />
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. <br />
|param1 = level<br />
|param1text = Optional integer specifying the stack level to return a result from. Defaults to 1 (i.e. the caller of the currently executing function).<br />
|example1 =<br />
var myFunction = func(a, b){<br />
debug.dump(caller(0)[0]); # prints a hash of local variables, including arguments a and b<br />
return 2 * 2;<br />
};<br />
<br />
print("2 x 2 = ", myFunction(2, 2));<br />
|example2 =<br />
var get_arg_value = func(){<br />
print("Argument to myFunc = ", caller(1)[0]['a']); # print the value of myFunc's single argument, using caller()<br />
};<br />
<br />
var myFunc = func(a){<br />
get_arg_value();<br />
};<br />
<br />
myFunc(3);<br />
|example3text = This is a real example taken from {{fgdata file|Nasal/canvas/MapStructure.nas}}. Function <code>r()</code> (above the TODOs) returns a hash with the key/value pairs as per its arguments. For example, something like this is returned: <code>{ name: "<name>", vis: 1, zindex: nil }</code>.<br />
|example3 =<br />
var MapStructure_selfTest = func() {<br />
var temp = {};<br />
temp.dlg = canvas.Window.new([600,400],"dialog");<br />
temp.canvas = temp.dlg.createCanvas().setColorBackground(1,1,1,0.5);<br />
temp.root = temp.canvas.createGroup();<br />
var TestMap = temp.root.createChild("map");<br />
TestMap.setController("Aircraft position");<br />
TestMap.setRange(25); # TODO: implement zooming/panning via mouse/wheel here, for lack of buttons :-/<br />
TestMap.setTranslation(<br />
temp.canvas.get("view[0]")/2,<br />
temp.canvas.get("view[1]")/2<br />
);<br />
var r = func(name,vis=1,zindex=nil) return caller(0)[0];<br />
# TODO: we'll need some z-indexing here, right now it's just random<br />
# TODO: use foreach/keys to show all layers in this case by traversing SymbolLayer.registry direclty ?<br />
# maybe encode implicit z-indexing for each lcontroller ctor call ? - i.e. preferred above/below order ?<br />
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'), ] ) <br />
TestMap.addLayer(factory: canvas.SymbolLayer, type_arg: type.name,<br />
visible: type.vis, priority: type.zindex,<br />
);<br />
}; # MapStructure_selfTest<br />
}}<br />
<br />
=== chr() ===<br />
{{Nasal doc<br />
|syntax = chr(code);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=175|t=Source}}<br />
|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 (<code>"string"</code>) supports control chracters. Strings in single quotes (<code>'string'</code>) do not.}}<br />
{{{!}} class="wikitable"<br />
! Code !! Name !! Equivalent to<br />
{{!-}}<br />
{{!}} 10 {{!!}} {{Wikipedia|Newline}} {{!!}} <code>\n</code><br />
{{!-}}<br />
{{!}} 9 {{!!}} {{Wikipedia|Tab key#Tab characters|Horizontal tab}} {{!!}} <code>\t</code><br />
{{!-}}<br />
{{!}} 13 {{!!}} {{Wikipedia|Carriage return}} {{!!}} <code>\r</code><br />
{{!}}}<br />
|param1 = code<br />
|param1text = Integer character code for the desired glyph.<br />
|example1 = print("Code 65 = ", chr(65)); # prints "Code 65 = A"<br />
|example2text = This example displays all of the characters in a list, in the format <code>Code '''n''' = >'''char'''<</code>, '''n''' being the index, and '''char''' being the character.<br />
|example2 =<br />
for(var i = 0; i <= 255; i += 1){<br />
print("Code ", i, " = >", chr(i), "<");<br />
}<br />
}}<br />
<br />
=== closure() ===<br />
{{Nasal doc<br />
|syntax = closure(func[, level]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=421|t=Source}}<br />
|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'''. <br />
|param1 = func<br />
|param1text = Function to evaluate.<br />
|param2 = level<br />
|param2text = Optional integer specifying the scope level. Defaults to 0 (the namespace of '''func''').<br />
|example1 =<br />
var get_math_e = func {<br />
return e; # return the value of math.e<br />
}<br />
<br />
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<br />
debug.dump(closure(myFunction)); # print the namespace of get_math_e<br />
<br />
print(myFunction());<br />
}}<br />
<br />
=== cmp() ===<br />
{{Nasal doc<br />
|syntax = cmp(a, b);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=112|t=Source}}<br />
|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'''. <br />
|param1 = a<br />
|param1text = First string argument for comparison.<br />
|param2 = b<br />
|param2text = Second string argument for comparison.<br />
|example1 = print(cmp("1", "two")); # prints -1<br />
|example2 = print(cmp("string", "string")); # prints 0<br />
|example3 = print(cmp("one", "2")); # prints 1<br />
|example4 = print(cmp("string1", "string2")); # prints -1<br />
}}<br />
<br />
=== compile() ===<br />
{{Nasal doc<br />
|syntax = compile(code[, filename]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=220|t=Source}}<br />
|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'''.<br />
|param1 = code<br />
|param1text = String containing Nasal code to be compiled.<br />
|param2 = filename<br />
|param2text = Optional string used for error messages/logging. Defaults to <code><compile></code><br />
|example1 = <br />
var myCode = 'print("hello");';<br />
var helloFunc = compile(myCode, "myCode");<br />
helloFunc();<br />
|example2text = <code>compile</code> 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 <tt>''[[$FG_ROOT]]/gui/dialogs/test.xml''</tt>.<br />
<syntaxhighlight lang="xml"><br />
<?xml version="1.0"?><br />
<br />
<PropertyList><br />
<br />
<nasal><![CDATA[<br />
print("You have FlightGear v", getprop("/sim/version/flightgear"));<br />
]]></nasal><br />
<br />
</PropertyList><br />
</syntaxhighlight><br />
Now, start FlightGear and execute this code in the [[Nasal Console]].<br />
|example2 =<br />
# Build the path<br />
var FGRoot = getprop("/sim/fg-root");<br />
var filename = "/gui/dialogs/test.xml";<br />
var path = FGRoot ~ filename;<br />
<br />
var blob = io.read_properties(path);<br />
var script = blob.getValues().nasal; # Get the nasal string<br />
<br />
# Compile the script. We're passing the filename here for better runtime diagnostics <br />
var code = call(func {<br />
compile(script, filename);<br />
}, nil, nil, var compilation_errors = []);<br />
<br />
if(size(compilation_errors)){<br />
die("Error compiling code in: " ~ filename);<br />
}<br />
<br />
# Invoke the compiled script, equivalent to code(); <br />
# We're using call() here to detect errors:<br />
call(code, [], nil, nil, var runtime_errors = []);<br />
<br />
if(size(runtime_errors)){<br />
die("Error calling code compiled loaded from: " ~ filename);<br />
}<br />
}}<br />
<br />
=== contains() ===<br />
{{Nasal doc<br />
|syntax = contains(hash, key);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=184|t=Source}}<br />
|text = Returns 1 (True) if the hash contains the specified key, or 0 (False) if not.<br />
|param1 = hash<br />
|param1text = The hash to search in.<br />
|param2 = key<br />
|param2text = The scalar to be searched for, contained as a key in the hash.<br />
|example1 =<br />
# Initialize a hash<br />
var hash = {<br />
element: "value"<br />
};<br />
print(contains(hash, "element") ? "Yes" : "No"); # This will print "Yes"<br />
|example2 =<br />
# Initialize a hash<br />
var hash = {<br />
element: "value"<br />
};<br />
print(contains(hash, "element2") ? "Yes" : "No"); # This will print "No"<br />
}}<br />
<br />
=== delete() ===<br />
{{Nasal doc<br />
|syntax = delete(hash, key);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=83|t=Source}}<br />
|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 <code>'''nil'''</code> 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.<br />
|param1 = hash<br />
|param1text = The hash from which to delete the key.<br />
|param2 = key<br />
|param2text = The scalar to be deleted, contained as a key in the hash.<br />
|example1 =<br />
# Initialize the hash<br />
var hash = {<br />
element1: "value1",<br />
element2: "value2"<br />
};<br />
delete(hash, "element1"); # Delete element1<br />
debug.dump(hash); # prints the hash, which is now minus element1<br />
}}<br />
<br />
=== die() ===<br />
{{Nasal doc<br />
|syntax = die(error);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=288|t=Source}}<br />
|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()}}.<br />
|param1 = error<br />
|param1text = String describing the error.<br />
:{{inote|This parameter is technically optional, but it is highly recommended to use it.}}<br />
|example1 = <br />
print("Will print");<br />
die("Don't go any further!"); <br />
print("Won't print"); # Will not be printed because die() stops the process<br />
}}<br />
<br />
=== find() ===<br />
{{Nasal doc<br />
|syntax = find(needle, haystack);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=450|t=Source}}<br />
|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.<br />
|param1 = needle<br />
|param1text = String to search for.<br />
|param2 = haystack<br />
|param2text = String to search in.<br />
|example1 = print(find("c", "abcdef")); # prints 2<br />
|example2 = print(find("x", "abcdef")); # prints -1<br />
|example3 = print(find("cd", "abcdef")); # prints 2<br />
}}<br />
<br />
=== ghosttype() ===<br />
{{Nasal doc<br />
|syntax = ghosttype(ghost);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=207|t=Source}}<br />
|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++ <code>naGhostType</code> 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.<br />
|param1 = ghost<br />
|param1text = Ghost to return a description for.<br />
|example1 = print(ghosttype(airportinfo())); # prints "airport"<br />
}}<br />
<br />
=== id() ===<br />
{{Nasal doc<br />
|syntax = id(object);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=570|t=Source}}<br />
|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 <code>'''<type>''':'''<id>'''</code>, where '''<type>''' is the type of object, and '''<id>''' is the ID.<br />
|param1 = object<br />
|param1text = Can be either of a string, a vector, a hash, a code, a function, or a ghost.<br />
|example1 = print(id("A")); # prints "str:000000001624A590"<br />
}}<br />
<br />
=== int() ===<br />
{{Nasal doc<br />
|syntax = int(number);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=90|t=Source}}<br />
|text = Returns the integer part of the numeric value of the single argument, or <code>'''nil'''</code> if none exists.<br />
|param1 = number<br />
|param1text = Number or string with just a number in it to return an integer from.<br />
|example1 = print(int(23)); # prints "23"<br />
|example2 = print(int(23.123)); # prints "23"<br />
|example3 = debug.dump(int("string")); # prints "nil"<br />
}}<br />
<br />
=== keys() ===<br />
{{Nasal doc<br />
|syntax = keys(hash);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=33|t=Source}}<br />
|text = Returns a vector containing the list of keys found in the single hash argument. <br />
|param1 = hash<br />
|param1text = The hash to return the keys from.<br />
|example1 = <br />
# Initialize a hash<br />
var hash = {<br />
element1: "value",<br />
element2: "value"<br />
};<br />
debug.dump(keys(hash)); # print the vector<br />
}}<br />
<br />
=== left() ===<br />
{{Nasal doc<br />
|syntax = left(string, length);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=149|t=Source}}<br />
|version = 2.12<br />
|commit = {{simgear commit|bd7163|t=commit}}<br />
|text = Returns a substring of '''string''', starting from the left.<br />
|param1 = string<br />
|param1text = String to return part of.<br />
|param2 = length<br />
|param2text = Integer specifying the length of the substring to return.<br />
|example1 = print(left("string", 2)); # prints "st"<br />
}}<br />
<br />
=== num() ===<br />
{{Nasal doc<br />
|syntax = num(number);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=102|t=Source}}<br />
|text = Returns the numerical value of the single string argument, or <code>'''nil'''</code> if none exists. <br />
|param1 = number<br />
|param1text = String with just a number in it to return a number from.<br />
|example1 = print(num("23")); # prints "23"<br />
|example2 = print(num("23.123")); # prints "23.123"<br />
|example3 = debug.dump(num("string")); # prints "nil"<br />
}}<br />
<br />
=== pop() ===<br />
{{Nasal doc<br />
|syntax = pop(vector);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=50|t=Source}}<br />
|text = Removes and returns the last element of the single vector argument, or <code>'''nil'''</code> if the vector is empty. <br />
|param1 = vector<br />
|param1text = Vector to remove an element from.<br />
|example1 = <br />
var vector = [1, 2, 3];<br />
pop(vector);<br />
debug.dump(vector); # prints "[1, 2]"<br />
|example2 = <br />
var vector = [1, 2, 3];<br />
debug.dump(pop(vector)); # prints "3"<br />
|example3 = <br />
var vector = [];<br />
debug.dump(pop(vector)); # prints "nil"<br />
}}<br />
<br />
=== right() ===<br />
{{Nasal doc<br />
|syntax = right(string, length);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=161|t=Source}}<br />
|version = 2.12<br />
|commit = {{simgear commit|bd7163|t=commit}}<br />
|text = Returns a substring of '''string''', starting from the right.<br />
|param1 = string<br />
|param1text = String to return part of.<br />
|param2 = length<br />
|param2text = Integer specifying the length of the substring to return.<br />
|example1 = print(right("string", 2)); # prints "ng"<br />
}}<br />
<br />
=== setsize() ===<br />
{{Nasal doc<br />
|syntax = setsize(vector, size);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=56|t=Source}}<br />
|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 <code>'''nil'''</code> entries. Returns the vector operated upon. <br />
|param1 = vector<br />
|param1text = The vector to be operated on.<br />
|param2 = size<br />
|param2text = The desired size of the vector in number of entries.<br />
|example1 = <br />
var vector = [1, 2, 3]; # Initialize a vector<br />
setsize(vector, 4);<br />
debug.dump(vector); # print the vector<br />
|example2 = <br />
var vector = [1, 2, 3]; # Initialize a vector<br />
setsize(vector, 2);<br />
debug.dump(vector); # print the vector<br />
}}<br />
<br />
=== size() ===<br />
{{Nasal doc<br />
|syntax = size(object);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=23|t=Source}}<br />
|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 <code>'''nil'''</code> or a number, this error will be thrown: <code>object has no size()</code>.<br />
|param1 = object<br />
|param1text = Object to find the size of. Must be a string, a vector or a hash.<br />
|example1 = <br />
var string = "string";<br />
print(size(string)); # prints "6"<br />
|example2 =<br />
var vector = [1, 2, 3];<br />
print(size(vector)); # prints "3"<br />
|example3 =<br />
var hash = {<br />
element1: "value1",<br />
element2: "value2",<br />
element3: "value3"<br />
};<br />
print(size(hash)); # prints "3"<br />
}}<br />
<br />
=== sort() ===<br />
{{Nasal doc<br />
|syntax = sort(vector, function);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=542|t=Source}}<br />
|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/}}, <code>sort()</code> 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.<br />
|param1 = vector<br />
|param1text = Input vector to sort.<br />
|param2 = function<br />
|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.<br />
{{{!}} class="wikitable"<br />
! Return value !! Meaning<br />
{{!-}}<br />
{{!}} less than 0 {{!!}} first argument should go before second argument<br />
{{!-}}<br />
{{!}} 0 {{!!}} first argument equals second argument<br />
{{!-}}<br />
{{!}} greater than 0 {{!!}} first argument should go after second argument<br />
{{!}}}<br />
<br />
|example1text = This example sorts elements from smallest to greatest.<br />
|example1 = <br />
var sort_rules = func(a, b){<br />
if(a < b){<br />
return -1; # A should before b in the returned vector<br />
}elsif(a == b){<br />
return 0; # A is equivalent to b <br />
}else{<br />
return 1; # A should after b in the returned vector<br />
}<br />
}<br />
debug.dump(sort([3, 2, 5, 6, 4, 1], sort_rules)); # prints "[1, 2, 3, 4, 5, 6]"<br />
|example2text = This example sorts elements from greatest to smallest.<br />
|example2 = <br />
# Outputs the elements in reverse order (greatest to smallest)<br />
var sort_rules = func(a, b){<br />
if(a < b){<br />
return 1; # -1 in the above example<br />
}elsif(a == b){<br />
return 0;<br />
}else{<br />
return -1; # 1 in the above example<br />
}<br />
}<br />
debug.dump(sort([3, 2, 5, 6, 4, 1], sort_rules)); # prints "[6, 5, 4, 3, 2, 1]"<br />
|example3text = This example sorts a vector of strings (runways for example) from smallest to greatest.<br />
|example3 = <br />
var runways = ["09R","27R","26L","09L","15"];<br />
var rwy = sort(runways,func(a,b) cmp(a,b));<br />
debug.dump(rwy); # prints ['09L','09R','15','26L','27R']<br />
}}<br />
<br />
=== split() ===<br />
{{Nasal doc<br />
|syntax = split(delimiter, string);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=460|t=Source}}<br />
|text = Splits the input string into a vector of substrings bounded by occurrences of the delimiter substring.<br />
|param1 = delimiter<br />
|param1text = String that will split the substrings in the returned vector.<br />
|param2 = string<br />
|param2text = String to split up.<br />
|example1 = debug.dump(split("cd", "abcdef")); # prints "['ab', 'ef']"<br />
|example2 = debug.dump(split(".", "3.2.0")); # prints "[3, 2, 0]"<br />
|example3 = debug.dump(split("/", "path/to/file")); # prints "['path', 'to', 'file']"<br />
}}<br />
<br />
=== sprintf() ===<br />
{{Nasal doc<br />
|syntax = sprintf(format[, arg[, arg, [...]]]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=355|t=Source}}<br />
|text = Creates and returns a string formatted using ANSI C {{func link|vsnprintf()|link=http://en.cppreference.com/w/c/io/vfprintf}} <ref><br />
{{Cite web<br />
|url = http://sourceforge.net/p/flightgear/simgear/ci/next/tree/simgear/nasal/lib.c#l308<br />
|title = fgdata/simgear/simgear/nasal/lib.c, line 308<br />
|accessdate = October 2015<br />
}}<br />
</ref>. Below is a table of supported format specifiers.<br />
{{{!}} class="wikitable" width="75%"<br />
{{!}}+ %[flags][width][.precision]specifier<br />
! colspan="2" {{!}} Flags<br />
{{!-}}<br />
! Flag !! Output<br />
{{!-}}<br />
{{!}} <code>+</code> {{!!}} 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.<br />
{{!-}}<br />
{{!}} ''space'' {{!!}} Prefixes non-signed numbers with a space.<br />
{{!-}}<br />
{{!}} <code>-</code> {{!!}} Left-align the output of this placeholder (the default is to right-align the output) when the width option is specified.<br />
{{!-}}<br />
{{!}} <code>0</code> {{!!}} Use 0 instead of spaces to pad a field when the width option is specified.<br />
{{!-}}<br />
{{!}} <code>#</code> {{!!}} Used with <code>o</code>, <code>x</code> or <code>X</code> specifiers the value is preceded with <tt>0</tt>, <tt>0x</tt> or <tt>0X</tt> respectively for values different than zero. Used with <code>e</code>, <code>E</code> and <code>f</code>, 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 <code>g</code> or <code>G</code> the result is the same as with <code>e</code> or <code>E</code> but trailing zeros are not removed.<br />
{{!-}}<br />
! colspan="2" {{!}} Width<br />
{{!-}}<br />
{{!}} colspan="2" {{!}} Integer specifying the minimum number of characters to be returned. This includes the decimal point and decimal fraction.<br />
{{!-}}<br />
! colspan="2" {{!}} Precision<br />
{{!-}}<br />
{{!}} colspan="2" {{!}} Integer preceded by a dot specifying the number of decimal places to be written.<br />
{{!-}}<br />
! colspan="2" {{!}} Specifiers<br />
{{!-}}<br />
! Specifier !! Output<br />
{{!-}}<br />
{{!}} <code>d</code>, <code>i</code> {{!!}} Signed decimal number.<br />
{{!-}}<br />
{{!}} <code>s</code> {{!!}} A string<br />
{{!-}}<br />
{{!}} <code>%</code> {{!!}} Percent (%) character.<br />
{{!-}}<br />
{{!}} <code>c</code> {{!!}} 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.<br />
{{!-}}<br />
{{!}} <code>o</code> {{!!}} Unsigned integer as an octal number.<br />
{{!-}}<br />
{{!}} <code>u</code> {{!!}} Unsigned decimal integer.<br />
{{!-}}<br />
{{!}} <code>x</code>, <code>X</code> {{!!}} Unsigned integer as a hexadecimal number. If <code>x</code> is used, any letters in the number are lowercase, while <code>X</code> gives uppercase.<br />
{{!-}}<br />
{{!}} <code>e</code>, <code>E</code> {{!!}} Double value in scientific notation (i.e., ''[-]ddd.ddd'''e'''[+/-]ddd''), with an exponent being denoted by <tt>e</tt> or <tt>E</tt> depending on whether an upper or lowercase is used respectively.<br />
{{!-}}<br />
{{!}} <code>f</code> {{!!}} Floating-point number, in fixed decimal notation, by default with 6 decimal places.<br />
{{!-}}<br />
{{!}} <code>F</code> {{!!}} Appears to be available<ref><br />
{{Cite web<br />
|url = http://sourceforge.net/p/flightgear/simgear/ci/next/tree/simgear/nasal/lib.c#l389<br />
|title = fgdata/simgear/simgear/nasal/lib.c, line 389<br />
|accessdate = October 2015<br />
}}<br />
</ref>, but doesn't work.<br />
{{!-}}<br />
{{!}} <code>g</code>, <code>G</code> {{!!}} Double in either normal or exponential notation, whichever is more appropriate for its magnitude. <code>g</code> uses lower-case letters, <code>G</code> 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.<br />
{{!}}}<br />
<br />
|param1 = format<br />
|param1text = String specifying the format. Can be used with or without a format specifiers. See below for examples.<br />
|param2 = arg<br />
|param2text = Argument specifying a value to replace a format placeholder (such as <code>%d</code>) in the format string. Not required if there are no format specifiers.<br />
<br />
|example1 = print(sprintf("%i", 54)); # prints "54"<br />
|example2 = print(sprintf("Pi = %+.10f", math.pi)); # prints "Pi = +3.1415926536"<br />
|example3 = <br />
print(sprintf("%6d", 23)); # prints " 23"<br />
print(sprintf("%06d", 23)); # prints "000023"<br />
|example4 =<br />
var FGVer = getprop("/sim/version/flightgear");<br />
print(sprintf("You have FlightGear v%s", FGVer)); # prints "You have FlightGear v<your version>"<br />
|example5 = <br />
print(sprintf("Hexadecimal 100000 = %X", 100000)); # prints "Hexadecimal 100000 = 186A0"<br />
print(sprintf("Hexadecimal 100000 = %x", 100000)); # prints "Hexadecimal 100000 = 186a0"<br />
|example6 = print(sprintf("Code 65 is %c", 65)); # prints "Code 65 is A"<br />
|example7 = <br />
print(sprintf("%e", 54)); # prints "5.400000e+001"<br />
print(sprintf("%E", 54)); # prints "5.400000E+001"<br />
|example8 = print(sprintf("%o", 54)); # prints "66"<br />
|example9 = print(sprintf("50%% of 100 is %i", 100 / 2)); # prints "50% of 100 is 50"<br />
|example10 =<br />
print(sprintf("%.2f", 1.4)); #prints "1.40"<br />
print(sprintf("%.1f", 1.4)); #prints "1.4"<br />
print(sprintf("% 4.1f", 1.4)); #prints " 1.4"<br />
print(sprintf("%04.1f", 1.4)); #prints "01.4"<br />
print(sprintf("% 6.1f", 1.4)); #prints " 1.4"<br />
print(sprintf("%06.1f", 1.4)); #prints "0001.4"<br />
}}<br />
<br />
=== streq() ===<br />
{{Nasal doc<br />
|syntax = streq(a, b);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=129|t=Source}}<br />
|text = Tests the string values of the two arguments for equality. This function is needed because the <code>'''=='''</code> 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.}}<br />
|param1 = a<br />
|param1text = First argument for testing equality.<br />
|param2 = b<br />
|param2text = Second argument for testing equality.<br />
|example1 = print(streq("0", "0")); # prints "1" (True)<br />
|example2 = <br />
print(0 == 0.0); # prints "1" (True)<br />
print(streq("0", "0.0")); # prints "0" (False)<br />
}}<br />
<br />
=== substr() ===<br />
{{Nasal doc<br />
|syntax = substr(string, start [, length]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=129|t=Source}}<br />
|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).<br />
|param1 = string<br />
|param1text = String to return a substring from.<br />
|param2 = start<br />
|param2text = Integer specifying the start of a substring. Negative values specify a position from the end of the string.<br />
|param3 = length<br />
|param3text = Optional argument specifying the length of the substring. Defaults to the end of the string.<br />
|example1 = print(substr("abcde", 1, 3)); # prints "bcd"<br />
|example2 = print(substr("abcde", 1)); # prints "bcde"<br />
|example3 = print(substr("abcde", 2, 1)); # prints "c"<br />
|example4 = print(substr("abcde", -2)); # prints "de"<br />
|example5 = print(substr("abcde", -3, 2)); # prints "cd"<br />
}}<br />
<br />
=== subvec() ===<br />
{{Nasal doc<br />
|syntax = subvec(vector, start[, length]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=63|t=Source}}<br />
|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). <br />
|param1 = vector<br />
|param1text = The vector to take the sub-vector from.<br />
|param2 = start<br />
|param2text = The starting point of the sub-vector within the given vector.<br />
|param3 = length<br />
|param3text = Optional argument specifying the length of the sub-vector, from the starting point.<br />
'''Notes:'''<br />
* Omitting the ''vector'' and ''start'' arguments is not an error (possibly it should be) but the return value is ''nil''.<br />
* A negative ''start'' argument ''is'' an error. This seems wrong. Perhaps the language designer could comment.<br />
* A value of ''start'' greater than ''size(vector)'' causes an error. A value equal to ''size(vector)'' returns an empty vector.<br />
* 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.<br />
|example1 = <br />
var vector = [1, 2, 3];<br />
debug.dump(subvec(vector, 0)); # prints "[1, 2, 3]"<br />
|example2 = <br />
var vector = [1, 2, 3];<br />
debug.dump(subvec(vector, 1)); # prints "[2, 3]"<br />
|example3 = <br />
var vector = [1, 2, 3];<br />
debug.dump(subvec(vector, 1, 1)); # prints "[2]"<br />
}}<br />
<br />
=== typeof() ===<br />
{{Nasal doc<br />
|syntax = typeof(object);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=193|t=Source}}<br />
|text = Returns a string indicating the whether the object is <code>'''nil'''</code>, a scalar (number or string), a vector, a hash, a function, or a ghost.<br />
|param1 = object<br />
|param1text = Object to return the type of.<br />
|example1 = <br />
var object = nil;<br />
print(typeof(object)); # prints "nil"<br />
|example2 = <br />
var object = "Hello world!";<br />
print(typeof(object)); # prints "scalar"<br />
|example3 = <br />
var object = math.pi;<br />
print(typeof(object)); # prints "scalar"<br />
|example4 = <br />
var object = [1, 2, 3];<br />
print(typeof(object)); # prints "vector"<br />
|example5 = <br />
var object = {};<br />
print(typeof(object)); # prints "hash"<br />
|example6 = <br />
var object = func {};<br />
print(typeof(object)); # prints "func"<br />
|example7 =<br />
var object = airportinfo();<br />
print(typeof(object)); # prints "ghost"<br />
}}<br />
<br />
<!-- == Extension modules ==<br />
=== thread ===<br />
{{WIP}}<br />
<br />
{{Nasal doc<br />
|syntax = thread.newthread(func);<br />
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}<br />
|text = <br />
|example1text = start a new worker thread<br />
|example1 = thread.newthread( func() {} );<br />
}}<br />
<br />
{{Nasal doc<br />
|syntax = thread.newlock();<br />
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}<br />
|text = <br />
|example1text = create a new lock<br />
|example1 = var lock = thread.newlock()<br />
}}<br />
<br />
{{Nasal doc<br />
|syntax = thread.lock();<br />
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}<br />
|text = <br />
|example1text = lock a lock<br />
|example1 = var lock = thread.newlock()<br />
}}<br />
<br />
{{Nasal doc<br />
|syntax = thread.unlock();<br />
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}<br />
|text = <br />
|example1text = unlock a lock<br />
|example1 = var lock = thread.unlock()<br />
}}<br />
<br />
<br />
{{Nasal doc<br />
|syntax = thread.newsem();<br />
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}<br />
|text = <br />
|example1text = create a new {{Wikipedia|semaphore}}<br />
|example1 = var semaphore = thread.newsem()<br />
}}<br />
<br />
<br />
{{Nasal doc<br />
|syntax = thread.semdown();<br />
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}<br />
|text = <br />
|example1text = semaphore down<br />
|example1 = thread.semdown(semaphore)<br />
}}<br />
<br />
<br />
{{Nasal doc<br />
|syntax = thread.semup();<br />
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}<br />
|text = <br />
|example1text = semaphore up<br />
|example1 = thread.semup(semaphore)<br />
}} --><br />
<br />
== Extension functions ==<br />
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:<br />
* {{flightgear file|src/Scripting/NasalPositioned.cxx}}<br />
* {{flightgear file|src/Scripting/NasalSys.cxx}}<br />
* {{fgdata file|Nasal/globals.nas}}<br />
<br />
=== abort() ===<br />
{{Nasal doc<br />
|syntax = abort();<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=565|t=Source}}<br />
|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 "exit" [[Fgcommands|fgcommand]], which will exit FlightGear more gracefully (see example below).<br />
|example1text = This example will immediately stop FlightGear with an error, such as "FlightGear has stopped working."<br />
|example1 = abort();<br />
|example2text = For exiting FlightGear in a better way, please use the following code:<br />
|example2 = fgcommand("exit");<br />
}}<br />
<br />
=== abs() ===<br />
{{Nasal doc<br />
|syntax = abs(number);<br />
|source = {{fgdata file|Nasal/globals.nas|t=Source}}<br />
|text = This simple function returns the {{wikipedia|absolute value|noicon=1}} of the provided number.<br />
|param1 = number<br />
|param1text = This argument is required and should be a number.<br />
|example1 = print(abs(1)); # prints "1"<br />
|example2 = print(abs(-1)); # prints "1"<br />
}}<br />
<br />
=== aircraftToCart() ===<br />
This new function in FG 2017.2.1 takes coordinates in aircraft structural coordinate system, and translate them into geocentric coordinates.<br />
Example for (5,6,7):<br />
<syntaxhighlight lang="nasal"><br />
var pos = aircraftToCart({x: -5, y: 6, z: -7});<br />
var coord = geo.Coord.new();<br />
coord.set_xyz(pos.x, pos.y, pos.z);<br />
</syntaxhighlight><br />
Notice: x and z is inverted sign on purpose.<br />
if you want lat. lon, alt from that, just call: (degrees and meters)<br />
<br />
<syntaxhighlight lang="nasal"><br />
coord.lat()<br />
coord.lon()<br />
coord.alt()<br />
</syntaxhighlight><br />
<br />
=== addcommand() ===<br />
{{Nasal doc<br />
|syntax = addcommand(name, code);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=659|t=Source}}<br />
|version = 2.12<br />
|commit = {{flightgear commit|7b663c|t=commit}}<br />
|text = {{see also|Howto:Add new fgcommands to FlightGear}}<br />
<br />
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.<br />
|param1 = name<br />
|param1text = This will become the name of the new fgcommand. Must be a string.<br />
|param2 = code<br />
|param2text = The code that will be executed when the fgcommand is run. Must be a function.<br />
|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.<br />
|example1 = addcommand("myFGCmd", func(node) {<br />
print("fgcommand 'myFGCmd' has been run.");<br />
props.dump( node );<br />
});<br />
fgcommand("myFGCmd", props.Node.new({foo:1, bar:2}) );<br />
|example2text = This example demonstrates how parameters are defined in a new fgcommand.<br />
|example2 = addcommand("myFGCmd", func(node){<br />
print(node.getNode("number").getValue()); # prints the value of "number," which is 12<br />
});<br />
fgcommand("myFGCmd", props.Node.new({"number": 12}));<br />
}}<br />
<br />
=== airportinfo() ===<br />
{{Nasal doc<br />
|syntax = airportinfo();<br />
airportinfo(type);<br />
airportinfo(id);<br />
airportinfo(lat, lon[, type]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1024|t=Source}}<br />
|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:<br />
* '''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.<br />
* '''lon''': Longitude of the location.<br />
* '''lat''': Latitude of the location.<br />
* '''has_metar''': True or false depending whether the airport has a [[METAR]] code defined for it.<br />
* '''elevation''': Elevation of the location in metres.<br />
* '''id''': ICAO code of the airport (or ID of the seaplane base/heliport).<br />
* '''name''': Name of the airport/heliport/seaplane base.<br />
* '''runways'''<br />
** '''<runway name>'''<br />
*** '''id''': Name of runway.<br />
*** '''lat''': Latitude of the runway.<br />
*** '''lon''': Longitude of the runway.<br />
*** '''heading''': Heading of the runway.<br />
*** '''length''': Length of the runway in metres.<br />
*** '''width''': Width of the runway in metres.<br />
*** '''surface''': Runway surface type.<br />
*** '''threshold''': Length of the runway's {{wikipedia|displaced threshold}} in metres. Will return 0 if there is none.<br />
*** '''stopway''': Length of the runway's stopway (the area before the threshold) in metres. Will return 0 if there is none.<br />
*** '''reciprocal''': <code>runway</code> ghost of the reciprocal runway.<br />
*** '''ils_frequency_mhz''': ILS frequency in megahertz.<br />
*** '''ils''': <code>navaid</code> ghost of the ILS transmitter.<br />
* '''helipads'''<br />
** '''<helipad name>'''<br />
*** '''id''': Name of helipad.<br />
*** '''lat''': Latitude of the helipad.<br />
*** '''lon''': Longitude of the helipad.<br />
*** '''heading''': Heading of the helipad.<br />
*** '''length''': Length of the helipad in metres.<br />
*** '''width''': Width of the helipad in metres.<br />
*** '''surface''': Helipad surface type.<br />
* '''taxiways'''<br />
** '''<taxiway name>'''<br />
*** '''id''': Name of taxiway.<br />
*** '''lat''': Latitude of the taxiway.<br />
*** '''lon''': Longitude of the taxiway.<br />
*** '''heading''': Heading of the taxiway.<br />
*** '''length''': Length of the taxiway in metres.<br />
*** '''width''': Width of the taxiway in metres.<br />
*** '''surface''': Taxiway surface type.<br />
<br />
Information is extracted in the same way as accessing members of a Nasal hash. For example:<br />
<syntaxhighlight lang="nasal"><br />
# prints to lengths of the runways of the nearest airport in feet and metres<br />
var info = airportinfo();<br />
print("-- Lengths of the runways at ", info.name, " (", info.id, ") --");<br />
foreach(var rwy; keys(info.runways)){<br />
print(rwy, ": ", math.round(info.runways[rwy].length * M2FT), " ft (", info.runways[rwy].length, " m)");<br />
}<br />
</syntaxhighlight><br />
<br />
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.<br />
|param1 = id<br />
|param1text = The {{wikipedia|International Civil Aviation Organization airport code|ICAO code|noicon=1}} of an airport to retrieve information about.<br />
|param2 = type<br />
|param2text = When this argument is used, the function will return the closest airport of a certain type. Can be one of "heliport," "seaport," or "airport" (default).<br />
: {{inote|Running this function without any parameters is equivalent to this:<br />
: <syntaxhighlight lang="nasal"><br />
airportinfo("airport");<br />
</syntaxhighlight><br />
}}<br />
|param3 = lat ''and'' lon<br />
|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.<br />
|example1 = var info = airportinfo();<br />
print("Nearest airport: ", info.name, " (", info.id, ")"); # prints the name and ICAO code of the nearest airport<br />
|example2 = var info = airportinfo("heliport");<br />
print("Elevation of the nearest heliport: ", math.round(info.elevation * M2FT), " ft"); # prints the elevation and name of the nearest heliport<br />
|example3 = var info = airportinfo("KSQL");<br />
print("-- Runways of ", info.name, " (", info.id, "): --");<br />
foreach(var rwy; keys(info.runways)) {<br />
print(rwy); # prints the runways of KSQL<br />
}<br />
|example4 = var info = airportinfo(37.81909385, -122.4722484);<br />
print("Coordinates of the nearest airport: ", info.lat, ", ", info.lon); # print the name and ICAO of the nearest airport to the Golden Gate Bridge<br />
|example5 = var info = airportinfo(37.81909385, -122.4722484, "seaport");<br />
print("Nearest seaplane base: ", info.name, " (", info.id, ")"); # print the name and ID of the nearest seaplane base to the Golden Gate Bridge<br />
|example6text = This example prints the all information from an <code>airportinfo()</code> call.<br />
|example6 = var info = airportinfo("KSFO");<br />
print(info.name);<br />
print(info.id);<br />
print(info.lat);<br />
print(info.lon);<br />
print(info.has_metar);<br />
print(info.elevation);<br />
foreach(var rwy; keys(info.runways)){<br />
print("-- ", rwy, " --");<br />
print(info.runways[rwy].lat);<br />
print(info.runways[rwy].lon);<br />
print(info.runways[rwy].length);<br />
print(info.runways[rwy].width);<br />
print(info.runways[rwy].heading);<br />
print(info.runways[rwy].stopway);<br />
print(info.runways[rwy].threshold);<br />
}<br />
}}<br />
<br />
=== airwaysRoute() ===<br />
{{Nasal doc<br />
|syntax = airwaysRoute(start, end[, type]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1933|t=Source}}<br />
|text = {{see also|Nasal Flightplan}}<br />
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.<br />
|param1 = start<br />
|param1text = Start waypoint, in the form of a waypoint ghost, such as that provided by {{func link|flightplan()}}.<br />
|param2 = end<br />
|param2text = Same as above.<br />
|param3 = type<br />
|param3text = Instructs the function to compute a high level route (when set to "highlevel"), or a low level route (when set to "lowlevel"). Defaults to "highlevel."<br />
|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.<br />
|example1 = var fp = flightplan();<br />
var start = fp.getWP(0);<br />
var end = fp.getWP(fp.getPlanSize() - 1);<br />
var rt = airwaysRoute(start, end);<br />
foreach(var wp; rt){<br />
print(wp.wp_name); # print the waypoints in the computed route<br />
}<br />
|example2text = Exactly the same as above, but computes a low level path.<br />
|example2 = var fp = flightplan();<br />
var start = fp.getWP(0);<br />
var end = fp.getWP(fp.getPlanSize() - 1);<br />
var rt = airwaysRoute(start, end, "lowlevel");<br />
foreach(var wp; rt){<br />
print(wp.wp_name); # print the waypoints in the computed route<br />
}<br />
}}<br />
<br />
=== airway() ===<br />
<br />
{{Nasal doc<br />
|syntax = airway(ident [, pos]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2644|t=Source}}<br />
|text = {{see also|Nasal Flightplan}}<br />
This function returns a ghost containing an airway of a specified id.<br />
|param1 = ident<br />
|param1text = Identifier of airway<br />
|param2 = pos<br />
|param1text = a Positioned ghost (leg, navaid, airport) and so on, passed to the search function. <br />
}}<br />
<br />
=== assert() ===<br />
{{Nasal doc<br />
|syntax = assert(condition[, message]);<br />
|source = {{fgdata file|Nasal/globals.nas|t=Source}}<br />
|version = 3.2<br />
|commit = {{fgdata commit|8b16a7|t=commit}}<br />
|text = Returns either true if the condition evaluates as true, or aborts with a {{func link|die()}} call, which can be customised.<br />
|param1 = condition<br />
|param1text = Condition to evaluate.<br />
|param2 = message<br />
|param2text = Optional message that will be used in any {{func link|die()}} call. Defaults to "assertion failed!"<br />
|example1 = var a = 1;<br />
var b = 2;<br />
print(assert(a < b)); # prints "1" (true)<br />
|example2 = var a = 1;<br />
var b = 2;<br />
assert(a > b, 'a is not greater than b'); # aborts with a custom error message<br />
}}<br />
<br />
=== carttogeod() ===<br />
{{Nasal doc<br />
|syntax = carttogeod(x, y, z);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=945|t=Source}}<br />
|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).<ref>{{simgear file|simgear/math/sg_geodesy.hxx|l=43}}</ref><br />
|param1 = x<br />
|param1text = Mandatory x-axis value, in metres.<br />
|param2 = y<br />
|param2text = Mandatory y-axis value, in metres.<br />
|param3 = z<br />
|param3text = Mandatory z-axis value, in metres.<br />
|example1 = var (lat, lon, alt) = carttogeod(6378137, 0, 0); # point is the intersection of the prime meridian and equator.<br />
print("Latitude: ", lat); # prints lat, lon and alt, which are all zero, see above<br />
print("Longitude: ", lon);<br />
print("Altitude: ", alt);<br />
}}<br />
<br />
=== cmdarg() ===<br />
{{Nasal doc<br />
|private = _cmdarg()<br />
|syntax = cmdarg();<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=513|t=Part 1}} {{!}} {{fgdata file|Nasal/globals.nas|t=Part 2}}<br />
|text = <code>cmdarg()</code> 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. <br />
It is used by Nasal scripts embedded in XML files. It returns a <code>props.Node</code> object (see {{fgdata file|Nasal/props.nas}}), and you can use all of its methods on the returned value. <code>cmdarg()</code> should only be used in four types/places of XML files:<br />
* Bindings: This is needed so that the value of a joystick's axis can be accessed internally.<br />
* 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). <br />
* 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.<br />
* Animation XML files: If the animation XML file is used in an AI/MP model, <code>cmdarg()</code> will return the root of the AI model in the <code>/ai/models/</code> directory. Examples: <code>/ai/models/aircraft[3]/</code>, <code>/ai/models/multiplayer[1]/</code><br />
<br />
You should not use <code>cmdarg()</code> in places other than those stated above. Although it won't cause an error, it will return the value of the last legitimate <code>cmdarg()</code> call. <br />
<br />
Also, you should not delay <code>cmdarg()</code> using {{func link|maketimer()}}, {{func link|settimer()}} or {{func link|setlistener()}}, because it will return an unrelated property.<br />
|example1 = fgcommand("dialog-show", {"dialog-name": "cmdarg-demo"});<br />
|example1text = <br>This example demonstrates the usage of <code>cmdarg()</code> in a binding. Save the below XML snippet as <tt>[[$FG_ROOT]]/gui/dialogs/cmdarg-demo.xml</tt>. 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.<br />
<syntaxhighlight lang="xml"><br />
<?xml version="1.0" encoding="UTF-8"?><br />
<br />
<PropertyList><br />
<br />
<name>cmdarg-demo</name><br />
<layout>vbox</layout><br />
<br />
<text><br />
<label>Click "Close" to activate the demonstration (a message in the console).</label><br />
</text><br />
<br />
<button><br />
<legend>Close</legend><br />
<binding><br />
<command>nasal</command><br />
<script>print("Button binding root: '" ~ cmdarg().getPath() ~ "'");</script><br />
</binding><br />
<binding><br />
<command>dialog-close</command><br />
</binding><br />
</button><br />
<br />
</PropertyList><br />
</syntaxhighlight><br />
|example2text = This example demonstrates the usage of <code>cmdarg()</code> in Nasal code within dialogs. Open <tt>[[$FG_ROOT]]/gui/dialogs/cmdarg-demo.xml</tt> from the previous example, copy & 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 "I've been changed!"<br />
<syntaxhighlight lang="xml"><br />
<?xml version="1.0" encoding="UTF-8"?><br />
<br />
<PropertyList><br />
<br />
<name>cmdarg-demo</name><br />
<layout>vbox</layout><br />
<br />
<text><br />
<label>Click "Click me!" to activate the demonstration (the button's label will change).</label><br />
</text><br />
<br />
<button><br />
<legend>Click me!</legend><br />
<binding><br />
<command>nasal</command><br />
<script>change_label();</script><br />
</binding><br />
</button><br />
<br />
<button><br />
<legend>Close</legend><br />
<binding><br />
<command>dialog-close</command><br />
</binding><br />
</button><br />
<br />
<nasal><br />
<open><![CDATA[<br />
var dlg_root = cmdarg();<br />
var dlg_name = {"dialog-name": "cmdarg-demo"};<br />
var change_label = func {<br />
dlg_root.getNode("button[0]/legend").setValue("I've been changed!");<br />
fgcommand("dialog-close", dlg_name);<br />
fgcommand("dialog-show", dlg_name);<br />
}<br />
]]></open><br />
</nasal><br />
<br />
</PropertyList><br />
</syntaxhighlight><br />
|example3text = For an example of <code>cmdarg()</code> used with Canvas, please see [[Howto:Adding a canvas to a GUI dialog#FGPlot|Howto:Adding a canvas to a GUI dialog]].<br />
}}<br />
<br />
=== courseAndDistance() ===<br />
{{Nasal doc<br />
|syntax = courseAndDistance(to);<br />
courseAndDistance(from, to);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1668|t=Source}}<br />
|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:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost type<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A geo.Coord object<br />
|param1 = from<br />
|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.<br />
|param2 = to<br />
|param2text = Like the first parameter, but defines the second point.<br />
|example1text = This example demonstrates the usage of the function with the <code>airport</code> ghost type.<br />
|example1 = var from = airportinfo("KSFO");<br />
var to = airportinfo("KSQL");<br />
var (course, dist) = courseAndDistance(from, to);<br />
print(course); # prints course from KSFO to KSQL<br />
print(dist); # prints distance in nm from KSFO to KSQL<br />
|example2text = This example demonstrates the usage of the function with hashes containing ''lat'' and ''lon''.<br />
|example2 = var from = {lat: 0, lon: 0};<br />
var to = {lat: 1, lon: 1};<br />
var (course, dist) = courseAndDistance(from, to);<br />
print(course);<br />
print(dist);<br />
|example3text = This example demonstrates usage of a geo.Coord object.<br />
|example3 = var from = geo.Coord.new().set_latlon(0, 0);<br />
var to = geo.Coord.new().set_latlon(1, 1);<br />
var (course, dist) = courseAndDistance(from, to);<br />
print(course);<br />
print(dist);<br />
|example4text = This example demonstrates usage of differing parameter types.<br />
|example4 = var from = airportinfo("KSFO");<br />
var to = geo.Coord.new().set_latlon(0, 0);<br />
var (course, dist) = courseAndDistance(from, to);<br />
print(course);<br />
print(dist);<br />
|example5text = The same as above, but the other way round.<br />
|example5 = var to = {lat: 1, lon: 1};<br />
var (course, dist) = courseAndDistance(0, 0, to);<br />
print(course);<br />
print(dist);<br />
|example6text = Usage of just one parameter.<br />
|example6 = var dest = airportinfo("KSQL");<br />
var (course, dist) = courseAndDistance(dest);<br />
print("Turn to heading ", math.round(course), ". You have ", sprintf("%.2f", dist), " nm to go");<br />
}}<br />
<br />
=== createFlightplan() ===<br />
{{Nasal doc<br />
|syntax = createFlightplan(path);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2331|t=Source}}<br />
|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.<br />
|param1 = path<br />
|param1text = Optional parameter defining the file from which a flightplan will be populated.<br />
|example1 = <br />
var path = getprop("/sim/fg-home") ~ "/Export/test.fgfp";<br />
var flightplan = createFlightplan(path);<br />
debug.dump(flightplan);<br />
}}<br />
<br />
=== createDiscontinuity() ===<br />
{{Nasal doc<br />
|syntax = createDiscontinuity();<br />
|text = Returns a <code>waypoint</code> ghost object. A route discontinuity is inserted by an {{abbr|FMS|Flight Management System}} when it is unsure how to connect two waypoints.<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2045|t=Source}}<br />
|version = 2016.1<br />
|commit = {{flightgear commit|caead6|t=commit}}<br />
}}<br />
=== createViaTo() ===<br />
{{Nasal doc<br />
|syntax = createViaTo(airway, waypoint);<br />
|text = Returns a <code>waypoint</code> ghost object. It represents a route "via '''airway''' to '''waypoint'''".<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2009|t=Source}}<br />
|version = 2016.1<br />
|commit = {{flightgear commit|caead6|t=commit}}<br />
|param1 = airway<br />
|param1text = The name of an airway.<br />
|param2 = waypoint<br />
|param2text = Must be in the airway and one of:<br />
* The name of a waypoint.<br />
* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, or <code>fix</code> ghost object.<br />
}}<br />
=== createWP() ===<br />
{{Nasal doc<br />
|syntax = createWP(pos, name[, flag]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1964|t=Source}}<br />
|text = Creates a new waypoint ghost object.<br />
|param1 = pos<br />
|param1text = Dictates the position of the new waypoint. It can be one of the following:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost type<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A geo.Coord object<br />
:* Two numbers separated by a comma, as if the function is taking three arguments. See example 4 below.<br />
|param2 = name<br />
|param2text = String that will become the name of the new waypoint.<br />
|param3 = flag<br />
|param3text = Optional string that will tell FlightGear what type of waypoint it is. Must be one of "sid," "star," "approach," "missed," or "pseudo."<br />
|example1text = Creates a waypoint directly in front and 1 km away and appends it to the flight plan.<br />
|example1 = var pos = geo.aircraft_position().apply_course_distance(getprop("/orientation/heading-deg"), 1000);<br />
var wp = createWP(pos, "NEWWP");<br />
var fp = flightplan();<br />
fp.appendWP(wp);<br />
|example2 = var pos = geo.aircraft_position().apply_course_distance(getprop("/orientation/heading-deg"), 1000);<br />
var wp = createWP({lat: pos.lat(), lon: pos.lon()}, "NEWWP");<br />
var fp = flightplan();<br />
fp.appendWP(wp);<br />
|example3 = var apt = airportinfo();<br />
var wp = createWP(apt, "NEWWP");<br />
var fp = flightplan();<br />
fp.appendWP(wp);<br />
|example4 = var pos = geo.aircraft_position().apply_course_distance(getprop("/orientation/heading-deg"), 1000);<br />
var wp = createWP(pos.lat(), pos.lon(), "NEWWP");<br />
var fp = flightplan();<br />
fp.appendWP(wp);<br />
|example5text = Creates a new waypoint and adds it to the flight plan. Waypoints of the type "pseudo" are then removed from the flight plan, including the new waypoint. The {{func link|print()}} statements show this.<br />
|example5 = var pos = geo.aircraft_position().apply_course_distance(getprop("/orientation/heading-deg"), 1000);<br />
var wp = createWP(pos, "NEWWP", "pseudo");<br />
var fp = flightplan();<br />
fp.appendWP(wp);<br />
print(fp.getPlanSize());<br />
fp.clearWPType("pseudo");<br />
print(fp.getPlanSize());<br />
}}<br />
<br />
=== createWPFrom() ===<br />
{{Nasal doc<br />
|syntax = createWPFrom(object[, flag]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1989|t=Source}}<br />
|text = Creates a new waypoint object from another object.<br />
|param1 = object<br />
|param1text = A ghost object. Must be a ghost type that is one of "airport," "navaid," "runway," or "fix."<br />
|param2 = flag<br />
|param2text = Optional string that will tell FlightGear what type of waypoint it is. Must be one of "sid," "star," "approach," "missed," or "pseudo."<br />
|example1text = Creates a new waypoint and appends it to the flight plan.<br />
|example1 = var apt = airportinfo("KSFO");<br />
var wp = createWPFrom(apt);<br />
var fp = flightplan();<br />
fp.appendWP(wp);<br />
|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.<br />
|example2 = var apt = airportinfo("KSFO");<br />
var wp = createWPFrom(apt, "pseudo");<br />
print(wp.wp_name);<br />
var fp = flightplan();<br />
fp.appendWP(wp);<br />
print(fp.getPlanSize());<br />
fp.clearWPType("pseudo");<br />
print(fp.getPlanSize());<br />
}}<br />
<br />
=== defined() ===<br />
{{Nasal doc<br />
|syntax = defined(symbol);<br />
|source = {{fgdata file|Nasal/globals.nas|t=Source}}<br />
|text = Returns 1 (true) or 0 (false) depending on whether a variable exists.<br />
|param1 = symbol<br />
|param1text = A string that will be what the function searches for.<br />
|example1 = var number = 12;<br />
var check_exist = func {<br />
print("Variable 'number' ", defined("number") == 1 ? "exists" : "does not exist"); # 'number' does exist<br />
print("Variable 'number2' ", defined("number2") == 1 ? "exists" : "does not exist"); # 'number2' does not exist<br />
}<br />
check_exist();<br />
}}<br />
<br />
=== directory() ===<br />
{{Nasal doc<br />
|syntax = directory(path);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=572|t=Source}}<br />
|text = Returns a vector containing a list of the folders and files in a given file path or <code>'''nil'''</code> if the path doesn't exist. Hidden folders and files are not added to the vector.<br />
{{inote|The first two elements of the vector will be <code>'.'</code> and <code>'..'</code>. These are for navigating back up the file tree, but have no use in Nasal. They can be safely removed from the vector.}}<br />
|param1 = path<br />
|param1text = Absolute file path.<br />
|example1text = Gets the folders and files in [[$FG_ROOT]], and then removes the extra first two elements (see note above). <br />
|example1 = var dir = directory(getprop("/sim/fg-root")); # get directory<br />
dir = subvec(dir, 2); # strips off the first two elements<br />
debug.dump(dir); # dump the vector<br />
}}<br />
<br />
=== fgcommand() ===<br />
{{Nasal doc<br />
|syntax = fgcommand(cmd[, args]);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=456|t=Part 1}} {{!}} {{fgdata file|Nasal/globals.nas|t=Part 2}}<br />
|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.<br />
|param1 = cmd<br />
|param1text = String that is the name of the command that is to be run.<br />
|param2 = args<br />
|param2text = If the fgcommand takes arguments, they are inputted using this argument. Can either be a <code>props.Node</code> object, or a hash (see examples below).<br />
|example1 = fgcommand("null"); # does nothing<br />
|example2 = var args = props.Node.new({'script': 'print("Running fgcommand");'});<br />
if (fgcommand("nasal", args)) { # prints "Running fgcommand" and then one of these print statements<br />
print("Fgcommand succeeded");<br />
} else {<br />
print("Fgcommand encountered a problem");<br />
}<br />
|example3 = var args = { 'dialog-name': 'about' };<br />
fgcommand("dialog-show", args); # shows the 'about' dialog<br />
}}<br />
<br />
=== findAirportsByICAO() ===<br />
{{Nasal doc<br />
|syntax = findAirportsByICAO(search[, type]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1096|t=Source}}<br />
|text = Returns a vector containing <code>airport</code> ghost objects which are (by default) airports whose ICAO code matches the search string. The results are sorted by range from closest to furthest.<br />
|param1 = search<br />
|param1text = Search string for the function. Can either be a partial or a full ICAO code.<br />
:{{icaution|The more matches there are for the given code, the longer the function will take. Passing just one character (e.g., "K"), might make FlightGear hang for a certain amount of time.}}<br />
|param2 = type<br />
|param2text = This will narrow the search to airports of a certain type. By default, only airports are searched for. May be one of "airport," "heliport," or "seaport."<br />
|example1 = var apts = findAirportsByICAO("KSF"); # finds all airports matching "KSF"<br />
foreach(var apt; apts){<br />
print(apt.name, " (", apt.id, ")"); # prints them<br />
}<br />
|example2 = var apts = findAirportsByICAO("SP0", "seaport"); # finds all seaplane bases matching "SP0"<br />
foreach(var apt; apts){<br />
print(apt.name, " (", apt.id, ")"); # prints them<br />
}<br />
|example3 = var apt = findAirportsByICAO("XBET"); # one way to check if an airport does exist"<br />
if (size(apt) == 0) {<br />
print("Airport does not exist"); # this one will be printed<br />
} else {<br />
print("Airport does exist");<br />
}<br />
}}<br />
<br />
=== findAirportsWithinRange() ===<br />
{{Nasal doc<br />
|syntax = findAirportsWithinRange([pos, ]range[, type]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1066|t=Source}}<br />
|text = Returns a vector of <code>airport</code> 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.<br />
|param1 = pos<br />
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost type<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A geo.Coord object<br />
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: <code>findAirportsWithinRange(lat, lon, range, type);</code>.<br />
|param2 = range<br />
|param2text = Mandatory number giving the range in nautical miles within which to search for airports/heliports/seaplane bases.only airports are searched for.<br />
|param3 = type<br />
|param3text = This will narrow the search to airports of a certain type. By default, only airports are searched for. May be one of "airport," "heliport," or "seaport."<br />
|example1text = Searches for airports within 10 nm of [[KSFO]].<br />
|example1 = var pos = airportinfo("KSFO");<br />
var apts = findAirportsWithinRange(pos, 10);<br />
foreach(var apt; apts){<br />
print(apt.name, " (", apt.id, ")");<br />
}<br />
|example2text = Searches for seaplane bases within 15 nm of [[KSFO]].<br />
|example2 = var pos = airportinfo("KSFO");<br />
var apts = findAirportsWithinRange(pos, 15, "seaport");<br />
foreach(var apt; apts){<br />
print(apt.name, " (", apt.id, ")");<br />
}<br />
|example3text = Searches for airports within 10 nm of your current position.<br />
|example3 = var apts = findAirportsWithinRange(10);<br />
foreach(var apt; apts){<br />
print(apt.name, " (", apt.id, ")");<br />
}<br />
}}<br />
<br />
=== findFixesByID() ===<br />
{{Nasal doc<br />
|syntax = findFixesByID([pos, ]id);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1627|t=Source}}<br />
|text = Returns a vector containing <code>fix</code> ghost objects matching a given ID, sorted by range from a certain position.<br />
{{inote|Fixes are (usually) also known as waypoints.}}<br />
|param1 = pos<br />
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost type<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A geo.Coord object<br />
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: <code>findFixesByID(lat, lon, id);</code>.<br />
|param2 = id<br />
|param2text = Full or partial ID of the fix to search for.<br />
:{{inote|1=Inputting a partial ID does not work correctly (see [http://forum.flightgear.org/viewtopic.php?f=30&t=28129 here]). It is best to just input a full ID.}}<br />
|example1 = var fixes = findFixesByID("POGIC");<br />
foreach(var fix; fixes){<br />
print(fix.id, " - lat: ", fix.lat, " {{!}} lon: ", fix.lon); # prints information about POGIC<br />
}<br />
|example2 = var fix = findFixesByID("ZUNAP");<br />
fix = fix[0];<br />
var (course, dist) = courseAndDistance(fix);<br />
print("Turn to heading ", math.round(course), ". You have ", sprintf("%.2f", dist), " nm to go to reach ", fixes[0].id);<br />
}}<br />
<br />
=== findNavaidByFrequency() ===<br />
{{Nasal doc<br />
|syntax = findNavaidByFrequency([pos, ]freq[, type]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1547|t=Source}}<br />
|text = Returns a <code>navaid</code> ghost object for a navaid matching a given frequency. If there is more than one navaid with that frequency, the nearest station is returned.<br />
|param1 = pos<br />
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost type<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A geo.Coord object<br />
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: <code>findNavaidByFrequency(lat, lon, freq, type);</code>.<br />
|param2 = freq<br />
|param2text = Frequency, in megahertz, of the navaid to search for.<br />
|param3 = type<br />
|param3text = This will narrow the search to navaids of a certain type. Defaults to "all." For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.<br />
|example1 = var navaid = findNavaidByFrequency(11.17);<br />
print("ID: ", navaid.id); # prints info about the navaid<br />
print("Name: ", navaid.name);<br />
print("Latitude: ", navaid.lat);<br />
print("Longitude: ", navaid.lon);<br />
print("Elevation (AMSL): ", navaid.elevation, " m");<br />
print("Type: ", navaid.type);<br />
print("Frequency: ", sprintf("%.3f", navaid.frequency / 1000), " Mhz");<br />
print("Range: ", navaid.range_nm, " nm");<br />
if(navaid.course) print("Course: ", navaid.course);<br />
}}<br />
<br />
=== findNavaidsByFrequency() ===<br />
{{Nasal doc<br />
|syntax = findNavaidsByFrequency([pos, ]freq[, type]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1572|t=Source}}<br />
|text = Returns a vector conatining <code>navaid</code> ghost objects for navaids that match a given frequency, sorted from nearest to furthest.<br />
|param1 = pos<br />
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost type<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A geo.Coord object<br />
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: <code>findNavaidsByFrequency(lat, lon, freq, type);</code>.<br />
|param2 = freq<br />
|param2text = Frequency, in megahertz, of the navaid to search for.<br />
|param3 = type<br />
|param3text = This will narrow the search to navaids of a certain type. Defaults to "all." For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.<br />
|example1 = var navaids = findNavaidsByFrequency(10.955);<br />
foreach(var navaid; navaids){<br />
print("--");<br />
print("ID: ", navaid.id); # prints info about the navaid<br />
print("Name: ", navaid.name);<br />
print("Latitude: ", navaid.lat);<br />
print("Longitude: ", navaid.lon);<br />
print("Elevation (AMSL): ", navaid.elevation, " m");<br />
print("Type: ", navaid.type);<br />
print("Frequency: ", sprintf("%.3f", navaid.frequency / 1000), " Mhz");<br />
print("Range: ", navaid.range_nm, " nm");<br />
if(navaid.course) print("Course: ", navaid.course);<br />
print("--");<br />
}<br />
}}<br />
<br />
=== findNavaidsByID() ===<br />
{{Nasal doc<br />
|syntax = findNavaidsByID([pos, ]id[, type]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1600|t=Source}}<br />
|text = Returns a vector containing <code>navaid</code> ghost objects matching a given ID, sorted by range from a certain position.<br />
|param1 = pos<br />
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost type<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A geo.Coord object<br />
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: <code>findNavaidsByID(lat, lon, id, type);</code>.<br />
|param2 = id<br />
|param2text = Full or partial ID of the fix to search for.<br />
:{{inote|1=Inputting a partial ID does not work correctly (see [http://forum.flightgear.org/viewtopic.php?f=30&t=28129 here]). It is best to just input a full ID.}}<br />
|param3 = type<br />
|param3text = This will narrow the search to navaids of a certain type. Defaults to "all." For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.<br />
|example1 = var navaid = findNavaidsByID("MXW");<br />
navaid = navaid[0];<br />
print("ID: ", navaid.id); # prints info about 'MXW' (a VOR station)<br />
print("Name: ", navaid.name);<br />
print("Latitude: ", navaid.lat);<br />
print("Longitude: ", navaid.lon);<br />
print("Elevation (AMSL): ", navaid.elevation, " m");<br />
print("Type: ", navaid.type);<br />
print("Frequency: ", sprintf("%.3f", navaid.frequency / 1000), " Mhz");<br />
print("Range: ", navaid.range_nm, " nm");<br />
}}<br />
<br />
=== findNavaidsWithinRange() ===<br />
{{Nasal doc<br />
|syntax = findNavaidsWithinRange([pos, ]range[, type]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1518|t=Source}}<br />
|text = Returns a vector of <code>navaid</code> 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.<br />
|param1 = pos<br />
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost type<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A geo.Coord object<br />
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: <code>findNavaidsWithinRange(lat, lon, range, type);</code>.<br />
|param2 = range<br />
|param2text = Mandatory number giving the range in nautical miles within which to search for navaids. Defaults to "all." For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.<br />
|param3 = type<br />
|param3text = This will narrow the search to navaids of a certain type.<br />
|example1text = Searches for navaids within 10 nm of [[KSFO]].<br />
|example1 = var pos = airportinfo("KSFO");<br />
var navs = findNavaidsWithinRange(pos, 10);<br />
foreach(var nav; navs){<br />
print(nav.name, " (ID: ", nav.id, ")");<br />
}<br />
|example2text = Searches for navaids within 10 nm of your current position.<br />
|example2 = var navs = findNavaidsWithinRange(10);<br />
foreach(var nav; navs){<br />
print(nav.name, " (ID: ", nav.id, ")");<br />
}<br />
}}<br />
<br />
=== finddata() ===<br />
{{Nasal doc<br />
|syntax = finddata(path);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=603|t=Source}}<br />
|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]]. <br />
|param1 = path<br />
|param1text = A relative path as a string.<br />
|example1 = var path = finddata("Aircraft/Generic");<br />
print(path); # prints the absolute path to $FG_ROOT/Aircraft/Generic<br />
|example2 = var path = finddata("Airports");<br />
print(path); # prints the absolute path to <TerraSync dir>/Airports<br />
|example3 = var path = finddata("preferences.xml");<br />
print(path); # prints the absolute path to $FG_ROOT/preferences.xml<br />
}}<br />
<br />
=== flightplan() ===<br />
{{Nasal doc<br />
|syntax = flightplan([path]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1738|t=Source}}<br />
|text = {{see also|Nasal Flightplan}}<br />
Returns a flight plan object, either one for the current flight plan, or one loaded from a given path.<br />
|param1 = path<br />
|param1text = Optional path to flight plan XML file.<br />
|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.<br />
|example1 = var fp = flightplan();<br />
print(fp.getWP(fp.current).id);<br />
|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 <tt>''[[$FG_HOME]]/fp-demo.xml''</tt>.<br />
|example2 = var path = getprop('/sim/fg-home') ~ '/fp-demo.xml';<br />
var fp = flightplan(path);<br />
for(var i = 0; i < fp.getPlanSize(); i += 1){<br />
print(fp.getWP(i).id);<br />
}<br />
}}<br />
<br />
=== geodinfo() ===<br />
{{Nasal doc<br />
|syntax = geodinfo(lat, lon[, max_alt]);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=981|t=Source}}<br />
|text = Returns a vector containing two entries or <code>'''nil'''</code> 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 <tt>''[[$FG_ROOT]]/Materials''</tt>), or <code>'''nil'''</code> 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}}):<br />
* '''light_coverage:''' The coverage of a single point of light in m<sup>2</sup>.<br />
* '''bumpiness:''' Normalized bumpiness factor for the material.<br />
* '''load_resistance:''' The amount of pressure in N/m<sup>2</sup> the material can withstand without deformation.<br />
* '''solid:''' 1 (true) or false (0) depending on whether the material is solid or not.<br />
* '''names:''' Vector of scenery types (usually generated by [[TerraGear]]) that will use this material. <br />
* '''friction_factor:''' Normalized friction factor of the material.<br />
* '''rolling_friction:''' The rolling friction coefficient of the material.<br />
<br />
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.<br />
|param1 = lat<br />
|param1text = Latitude, inputted as a number.<br />
|param2 = lon<br />
|param2text = Longitude, inputted as a number.<br />
|param3 = max_alt<br />
|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, <code>'''nil'''</code> will be returned.<br />
|example1text = Dumps information about ground underneath the aircraft.<br />
|example1 = var pos = geo.aircraft_position();<br />
var info = geodinfo(pos.lat(), pos.lon());<br />
debug.dump(info);<br />
|example2text = Prints whether the ground underneath the aircraft is solid or is water.<br />
|example2 = var pos = geo.aircraft_position();<br />
var info = geodinfo(pos.lat(), pos.lon());<br />
if (info != nil and info[1] != nil) {<br />
print("The ground underneath the aircraft is ", info[1].solid == 1 ? "solid." : "water.");<br />
}<br />
}}<br />
<br />
=== geodtocart() ===<br />
{{Nasal doc<br />
|syntax = geodtocart(lat, lon, alt);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=962|t=Source}}<br />
|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.<br />
|param1 = lat<br />
|param1text = Latitude, in degrees.<br />
|param2 = lon<br />
|param2text = Longitude, in degrees.<br />
|param3 = alt<br />
|param3text = Altitude, in metres.<br />
|example1 = var (x, y, z) = geodtocart(0, 0, 0); # point is the intersection of the prime meridian and equator.<br />
print("x: ", x); # prints "x: 6378137"<br />
print("y: ", y); # prints "y: 0"<br />
print("z: ", z); # prints "y: 0"<br />
}}<br />
<br />
=== get_cart_ground_intersection() ===<br />
Introduced in 2017.2.1, see [[Terrain Detection]].<br />
<br />
=== getprop() ===<br />
{{Nasal doc<br />
|syntax = getprop(path[, path[, ...]]);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=345|t=Source}}<br />
|text = Returns the value of a node in the [[Property Tree]] or <code>'''nil'''</code> if the node does not exist or the value is not a number (NaN).<br />
|param1 = path<br />
|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.<br />
|example1 = print("You have FlightGear v", getprop("/sim/version/flightgear")); # prints FlightGear version<br />
|example2text = Note that the example below will only work in FlightGear 3.2 and above.<br />
|example2 = for(var i = 0; i < 8; i += 1){<br />
print("View #", i + 1, " is named ", getprop("/sim/view", i, "name"));<br />
}<br />
|example3text = Same as above, but is supported by all versions of FlightGear.<br />
|example3 = for(var i = 0; i < 8; i += 1){<br />
print("View #", i + 1, " is named ", getprop("/sim/view[" ~ i ~ "]/name"));<br />
}<br />
}}<br />
==== See also ====<br />
{{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: <br />
To get a Node rather than its value, use <code>props.globals.getNode()</code> - see [[Nasal_library/props]]. }}<br />
<br />
=== greatCircleMove() ===<br />
{{Nasal doc<br />
|syntax = greatCircleMove([pos, ]course, dist);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1681|t=Source}}<br />
|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).<br />
|param1 = pos<br />
|param1text = Optional position to calculate from. If not given, the aircraft's current position will be used. Can be one of:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost object.<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A <code>geo.Coord</code> object<br />
:* A lat/lon pair, that is, a pair of numbers (latitude followed by longitude) separated by a comma: <code>greatCircleMove(lat,lon, ...)</code>.<br />
|param2 = course<br />
|param2text = Course to new set of coordinates, in degrees (in the range 0–360).<br />
|param3 = dist<br />
|param3text = Distance in nautical miles to the new set of coordinates.<br />
|example1 = var pos = greatCircleMove(0,0, 0, 1);<br />
debug.dump(pos); # print hash with coordinates<br />
|example2 = var fix = findFixesByID("POGIC");<br />
fix = fix[0];<br />
var pos = greatCircleMove(fix, 45, 10);<br />
debug.dump(pos); # print hash with coordinates<br />
}}<br />
<br />
=== interpolate() ===<br />
{{Nasal doc<br />
|private = _interpolate()<br />
|syntax = interpolate(prop, value1, time1[, value2, time2[, ...]]);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=522|t=Part 1}} {{!}} {{fgdata file|Nasal/globals.nas|t=Part 2}}<br />
|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.<br />
|param1 = prop<br />
|param1text = String or <code>props.Node</code> object that indicates a node in the property tree to be used.<br />
|param2 = value''n''<br />
|param2text = Target value to change the property to in the set amount of time. This should be a number.<br />
|param3 = time''n''<br />
|param3text = Time in seconds, that will be taken for the interpolation.<br />
|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.<br />
|example1 = setprop("/test", 0); # (re-)set property<br />
interpolate("/test",<br />
50, 5, # interpolate to 50 in 5 seconds<br />
10, 2, # interpolate to 10 in 2 seconds<br />
0, 5); # interpolate to 0 in 5 seconds<br />
|example2 = # Apply the left brake at 20% per second<br />
var prop = "controls/gear/brake-left";<br />
var dist = 1 - getprop(prop);<br />
if (dist == 1) {<br />
interpolate(prop, 1, dist / 0.2);<br />
}<br />
}}<br />
<br />
=== isa() ===<br />
{{Nasal doc<br />
|syntax = isa(object, class);<br />
|source = {{fgdata file|Nasal/globals.nas|t=Source}}<br />
|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.<br />
|param1 = object<br />
|param1text = Object to check.<br />
|param2 = class<br />
|param2text = Class/object to check that '''object''' inherits from or is an instance of.<br />
|example1 = var coord = geo.Coord.new();<br />
if(isa(coord, geo.Coord)){<br />
print("Variable 'coord' is an instance of class 'geo.Coord'"); # this one will be printed<br />
} else {<br />
print("Variable 'coord' is not an instance of class 'geo.Coord'");<br />
}<br />
|example2 = var coord = geo.Coord.new();<br />
if(isa(coord, props.Node)){<br />
print("Variable 'coord' is an instance of class 'props.Node'");<br />
} else {<br />
print("Variable 'coord' is not an instance of class 'props.Node'"); # this one will be printed<br />
}<br />
|example3text = The example below demonstrates checking of inheritance.<br />
|example3 = var Const = {<br />
constant: 2,<br />
getConst: func {<br />
return me.constant;<br />
}<br />
};<br />
<br />
var Add = {<br />
new: func {<br />
return { parents: [Add, Const] };<br />
},<br />
<br />
addToConst: func(a){<br />
return a * me.getConst();<br />
}<br />
};<br />
<br />
var m = Add.new();<br />
print(m.addToConst(4));<br />
<br />
if(isa(m, Add)) print("Variable 'm' is an instance of class 'Add'"); # will be printed<br />
if(isa(m, Const)) print("Variable 'm' is an instance of class 'Const'"); # will also be printed<br />
}}<br />
<br />
=== logprint() ===<br />
{{Nasal doc<br />
|syntax = logprint(priority[, msg[, msg[, ...]]]);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=431|t=Source}}<br />
|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 <code>[[Commonly used debugging tools#fgfs.log|fgfs.log]]</code> file as coming from the Nasal file itself rather than from {{flightgear file|src/Scripting/NasalSys.cxx}}.<br />
|param1 = priority<br />
|param1text = Number specifying the priority level of the outputted message:<br />
:{{{!}} class="wikitable"<br />
! Number !! Debug type<br />
{{!-}}<br />
{{!}} 1 {{!!}} Bulk<br />
{{!-}}<br />
{{!}} 2 {{!!}} Debug<br />
{{!-}}<br />
{{!}} 3 {{!!}} Info<br />
{{!-}}<br />
{{!}} 4 {{!!}} Warn<br />
{{!-}}<br />
{{!}} 5 {{!!}} Alert<br />
{{!}}}<br />
|param2 = msg<br />
|param2text = The message. There is no limit to the arguments you give give. They will be concatenated together before logging.<br />
|example1 = # logs the value of pi to three decimal places with log level 3<br />
logprint(3, "pi = ", sprintf("%.3f", math.pi));<br />
|example2 = logprint(5, "Alert! This is an important message!");<br />
}}<br />
{{note| <br />
The following constants have been added to the development branch of FlightGear ("next") and will be releases with FG 2020.1 so you won't have to remember the numbers anymore:<br />
<br />
LOG_BULK, LOG_WARN, LOG_DEBUG, LOG_INFO, LOG_ALERT, DEV_WARN, DEV_ALERT }}<br />
<br />
=== magvar() ===<br />
{{Nasal doc<br />
|syntax = magvar([pos]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1642|t=Source}}<br />
|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.<br />
{{{!}} class="wikitable"<br />
! FlightGear versions !! Model !! Reference date<br />
{{!-}}<br />
{{!}} 3.6 and above {{!!}} {{wikipedia|World Magnetic Model}} (WMM) 2015 {{!!}} 1 January 2015<br />
{{!-}}<br />
{{!}} 0.9.11-pre1 to 3.4 {{!!}} WMM 2005 {{!!}} 1 January 2005<br />
{{!-}}<br />
{{!}} 0.7.3 to 0.9.10 {{!!}} WMM 2000 {{!!}} 1 January 2000<br />
{{!}}}<br />
|param1 = pos<br />
|param1text = Optional position to calculate from. If not given, the aircraft's current position will be used. Can be one of:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost object.<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A <code>geo.Coord</code> object<br />
:* A lat/lon pair, that is, a pair of numbers (latitude followed by longitude) separated by a comma: <code>magvar(lat,lon)</code>.<br />
|example1 = print(magvar(0, 0)); # prints the magnetic variation at 0, 0<br />
}}<br />
<br />
=== maketimer() ===<br />
{{Nasal doc<br />
|syntax = maketimer(interval[, self], function);<br />
|source = ''Implemented using the {{API Link|flightgear|class|TimerObj}} class.''<br>{{flightgear file|src/Scripting/NasalSys.cxx|l=90|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=533|t=Part 2}}<br />
|version = 2.12<br />
|commit = {{flightgear commit|ab939f|t=commit}}<br />
|text = Returns a timer object containing the following methods and members:<br />
* '''start()''': Starts the timer.<br />
* '''stop()''': Stops the timer.<br />
* '''restart(interval)''': Restarts the timer with the given interval.<br />
* '''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).<br />
* '''isRunning''': Read-only bool telling whether the timer is currently running.<br />
* '''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.<br />
Unlike {{func link|settimer()}}, which it replaces, <code>maketimer()</code> provides more control over the timer. In addition, it can help reduce memory usage.<br />
|param1 = interval<br />
|param1text = Interval in seconds for the timer.<br />
|param2 = self<br />
|param2text = Optional parameter specifying what any <code>'''me'''</code> references in the function being called will refer to.<br />
|param3 = function<br />
|param3text = Function to be called at the given interval.<br />
|example1 = var timer = maketimer(1, func(){<br />
print("Hello, World!"); # print "Hello, World!" once every second (call timer.stop() to stop it)<br />
});<br />
timer.start();<br />
|example2 = var timer = maketimer(1, math, func(){<br />
print(me.math); # 'me' reference is the 'math' namespace<br />
});<br />
timer.singleShot = 1; # timer will only be run once<br />
timer.start();<br />
|example3 = var timer = maketimer(1, func(){<br />
print("Hello, World!"); # print "Hello, World!" once every second (call timer.stop() to stop it)<br />
});<br />
timer.start();<br />
print(timer.isRunning); # prints 1<br />
|example4text = In the example below, "Hello, World!" will be printed after one second the first time, and after two seconds thereafter.<br />
|example4 = var update = func(){<br />
print("Hello, World!");<br />
timer.restart(2); # restarts the timer with a two second interval<br />
}<br />
<br />
var timer = maketimer(1, update);<br />
timer.singleShot = 1;<br />
timer.start();<br />
}}<br />
<br />
=== maketimestamp() ===<br />
{{Nasal doc<br />
|syntax = maketimestamp()<br />
|source = ''Implemented using the {{API Link|flightgear|class|TimeStampObj}} class.''<br>{{flightgear file|src/Scripting/NasalSys.cxx|l=214|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=589|t=Part 2}}<br />
|version = 2019.2<br />
|commit = {{flightgear commit|7db06300|t=commit}}<br />
|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:<br />
* '''stamp()''': Resets the timing operation. Call this first.<br />
* '''elapsedMSec()''': returns number of milliseconds elapsed since stamp() called. Resolution may vary depending on platform but is usually at least millisecond accuracy.<br />
* '''elapsedUSec()''': returns number of microseconds elapsed since stamp() called. Resolution may vary depending on platform but is usually at least millisecond accuracy.<br />
|<br />
|example1text = In the example below the number of milliseconds elapsed will be printed.<br />
|example1 = var timestamp = maketimestamp();<br />
timestamp.stamp();<br />
print(timestamp.elapsedMSec(), "ms elapsed");<br />
print(timestamp.elapsedMSec(), "ms elapsed");<br />
}}<br />
<br />
=== md5() ===<br />
{{Nasal doc<br />
|syntax = md5(string);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=758|t=Source}}<br />
|version = 3.2<br />
|commit = {{flightgear commit|cfbf9e|t=commit}}<br />
|text = Returns a the {{wikipedia|MD5}} hash (as a string) of the inputted string.<br />
|param1 = string<br />
|param1text = String the generate the hash of. Mandatory.<br />
|example1text = The below code should output <code>65a8e27d8879283831b664bd8b7f0ad4</code>.<br />
|example1 = print(md5("Hello, World!"));<br />
}}<br />
<br />
=== navinfo() ===<br />
{{Nasal doc<br />
|syntax = navinfo(lat, lon, type, id);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1453|t=Source}}<br />
|text = Returns vector <code>navaid</code> ghost objects matching the given '''type''' and '''id''' or <code>'''nil'''</code> on error.<br />
|param1 = lat ''and'' lon<br />
|param1text = If given, the returned navaids will be put into order of ascending distance from the location.<br />
|param2 = type<br />
|param2text = Narrows the search to the given type. Must be one of "any," "fix," "vor," "ndb," "ils," "dme," or "tacan." Defaults to the equivalent of "any."<br />
|param3 = id<br />
|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.<br />
|example1 = navinfo("vor"); # returns all VORs<br />
|example2 = navinfo("HAM"); # return all navaids whose names start with "HAM"<br />
|example3 = navinfo("vor", "HAM"); # return all VORs whose names start with "HAM"<br />
|example4 = navinfo(34,48,"vor","HAM"); # return all VORs whose names start with "HAM" and sorted by distance relative to 34°, 48°<br />
}}<br />
<br />
=== parse_markdown() ===<br />
{{Nasal doc<br />
|syntax = parse_markdown(markdown);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=745|t=Part 1}} {{!}} {{simgear file|simgear/misc/SimpleMarkdown.cxx|t=Part 2}} <br />
|version = 3.2<br />
|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:<br />
* It strips whitespace from the beginning of the string.<br />
* It supports [http://daringfireball.net/projects/markdown/syntax#p paragraphs and line breaks].<br />
* It collapses whitespace.<br />
* It converts unordered [http://daringfireball.net/projects/markdown/syntax#list lists] to use a bullet character (&bull;). Note that the bullet character is implemented in hexadecimal UTF-8 character bytes (<code>E2 80 A2</code>), as so may not work properly when the being displayed in an encoding other than UTF-8.<br />
|param1 = markdown<br />
|param1text = String containing Markdown to be parsed.<br />
|example1text = <br />
Save the below code as <tt>''[[$FG_ROOT]]/gui/dialogs/test.xml''</tt>, 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 (<tt>Debug > Reload GUI</tt>).<br />
<syntaxhighlight lang="xml" highlight="41-50"><br />
<?xml version="1.0" encoding="UTF-8"?><br />
<br />
<PropertyList><br />
<br />
<name>test</name><br />
<layout>vbox</layout><br />
<br />
<group><br />
<layout>hbox</layout><br />
<br />
<empty><br />
<stretch>true</stretch><br />
</empty><br />
<br />
<text><br />
<label>parse_markdown() test dialog</label><br />
</text><br />
<br />
<empty><br />
<stretch>true</stretch><br />
</empty><br />
<br />
<button><br />
<legend></legend><br />
<pref-width>16</pref-width><br />
<pref-height>16</pref-height><br />
<binding><br />
<command>dialog-close</command><br />
</binding><br />
</button><br />
<br />
</group><br />
<br />
<canvas><br />
<name>Canvas plot</name><br />
<stretch>true</stretch><br />
<pref-width>400</pref-width><br />
<pref-height>300</pref-height><br />
<nasal><br />
<load><![CDATA[<br />
var text = 'Items:<br />
* apples<br />
* oranges<br />
* pears<br />
<br />
Some text.<br />
Some more items:<br />
* apples<br />
* oranges<br />
* pears';<br />
<br />
var parsed = parse_markdown(text);<br />
<br />
var root_canvas = canvas.get(cmdarg());<br />
root_canvas.setColorBackground(255, 255, 255);<br />
var root = root_canvas.createGroup();<br />
<br />
var text_dis = root.createChild("text")<br />
.setText(parsed)<br />
.setTranslation(5, 5)<br />
.setFont("LiberationFonts\LiberationSans-Regular.ttf")<br />
.setFontSize(15)<br />
.setColor(0, 0, 0)<br />
.setDrawMode(canvas.Text.TEXT)<br />
.setAlignment("left-top");<br />
]]></load><br />
</nasal><br />
</canvas><br />
<br />
</PropertyList><br />
</syntaxhighlight><br />
|example1 = fgcommand("dialog-show", {"dialog-name": "test"});<br />
|example2text = The example below parses Markdown and outputs it in a HTML document. The parsed text is placed in <syntaxhighlight lang="xml" inline><pre></pre></syntaxhighlight> tags. To change the Markdown to be parsed, simply edit the variable <tt>markdown</tt> at the top of the code.<br />
|example2 = <nowiki>var markdown = 'Items:<br />
* apples<br />
* oranges<br />
* pears<br />
<br />
Some text.<br />
Some more items:<br />
* apples<br />
* oranges<br />
* pears';<br />
<br />
var parsed = parse_markdown(markdown);<br />
<br />
debug.dump(parsed);<br />
<br />
var path = string.normpath(getprop("/sim/fg-home") ~ '/Export/parse_markdown()-test.html');<br />
<br />
var file = io.open(path, "w");<br />
<br />
var html = "<!DOCTYPE html>\n\n<html>\n\n<head>\n\t<meta charset=\"UTF-8\">\n\t<title>parse_markdown() test generated by Nasal</title>\n</head>\n\n<body>\n\t<pre>" ~ parsed ~ "</pre>\n</body>\n\n</html>";<br />
<br />
io.write(file, html);<br />
io.close(file);<br />
print("Done, file ready for viewing (" ~ path ~ ")");</nowiki><br />
}}<br />
<br />
=== parsexml() ===<br />
{{Nasal doc<br />
|syntax = parsexml(path[, start[, end[, data[, pro_ins]]]]);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=710|t=Source}}<br />
|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 <code>'''nil'''</code> is returned on error.<br />
|param1 = path<br />
|param1text = Mandatory absolute path to the XML file to be parsed.<br />
|param2 = start<br />
|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.<br />
|param3 = end<br />
|param3text = Optional callback function that will be called for every ending tag. The function should take one argument: the tag name.<br />
|param4 = data<br />
|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.<br />
|param5 = pro_ins<br />
|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.<br />
|example1text = Save the below XML code in <tt>''[[$FG_HOME]]/Export/demo.xml''</tt>. Then, execute the Nasal code below in the Nasal Console. The XML will be parsed and each bit of the code will be printed.<br />
<syntaxhighlight lang="xml"><br />
<?xml version="1.0" encoding="UTF-8"?><br />
<br />
<?xml-stylesheet type="text/xsl" href="style.xsl"?><br />
<br />
<foo><br />
<blah type="string"><![CDATA[ <sender>John Smith</sender> ]]></blah><br />
<blah2 type="string">Orange &amp; lemons</blah2><br />
</foo><br />
</syntaxhighlight><br />
|example1 = var start = func(name, attr){<br />
print("Starting tag: '", name, "'");<br />
foreach(var a; keys(attr)){<br />
print("\twith attribute ", a, '="', attr[a], '"');<br />
}<br />
}<br />
<br />
var end = func(name){<br />
print("Ending tag: '", name, "'");<br />
}<br />
<br />
var data = func(data){<br />
print("Data = '", data, "'");<br />
}<br />
<br />
var pro_instr = func(target, data){<br />
print("Processing instruction: target = '", target, "', data = '", data, "'");<br />
}<br />
<br />
parsexml(getprop("/sim/fg-home") ~ '/Export/demo.xml', start, end, data, pro_instr);<br />
}}<br />
<br />
=== print() ===<br />
{{Nasal doc<br />
|syntax = print(data[, data[, ...]]);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=415|t=Source}}<br />
|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.<br />
|param1 = data<br />
|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.<br />
|example1 = print("Just", " a ", "test"); # prints "Just a test"<br />
|example2 = print("pi = ", math.pi); # prints "pi = 3.141592..."<br />
}}<br />
<br />
=== printf() ===<br />
{{Nasal doc<br />
|syntax = printf(format[, arg[, arg, [...]]]);<br />
|source = {{fgdata file|Nasal/globals.nas|t=Source}}<br />
|text = Creates and prints a formatted string. For a description of its arguments, see {{func link|sprintf()}} (it is, in fact, implemented using <code>sprintf()</code>).<br />
|example1 = printf("In hexadecimal, 100000 = %X", 186A0); # prints "In hexadecimal, 100000 = 186A0"<br />
}}<br />
<br />
=== printlog() ===<br />
{{Nasal doc<br />
|syntax = printlog(level, data[, data[, ...]]);<br />
|source = {{fgdata file|Nasal/globals.nas|t=Source}}<br />
|text = Prints the given message with the given log level. If the log level is higher or equal to <code>/sim/logging/priority</code>, it is printed.<br />
|param1 = level<br />
|param1text = Mandatory log level as a string. Must be one of "none," "bulk," "debug," "info," "warn," or "alert." Note that "none" will mean that the message will never be printed.<br />
|param2 = data<br />
|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.<br />
|example1 = printlog("alert", "This is an alert"); # message will be printed<br />
|example2 = printlog("info", "Just informing you about something"); # message will be printed only if log level is set to "info" or less<br />
}}<br />
<br />
=== rand() ===<br />
{{Nasal doc<br />
|syntax = rand();<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=554|t=Source}}<br />
|text = Returns a random floating point number between 0 (inclusive) and 1 (exclusive). It takes no arguments.<br />
|example1 = print(rand()); # prints random number<br />
}}<br />
<br />
=== registerFlightPlanDelegate() ===<br />
{{Nasal doc<br />
|syntax = registerFlightPlanDelegate(init_func);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1879|t=Source}}<br />
|text = Registers a flight plan delegate. See <tt>''{{fgdata file|Nasal/route_manager.nas|t=$FG_ROOT/Nasal/route_manager.nas}}''</tt> for examples.<br />
|param1 = init_func<br />
|param1text = Initialization function which will be called during FlightGear's startup.<br />
}}<br />
=== removecommand() ===<br />
{{Nasal doc<br />
|syntax = removecommand(cmd);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=668|t=Source}}<br />
|text = Removes the given fgcommand. Returns <code>'''nil'''</code>.<br />
{{caution|This will remove '''any''' [[fgcommands|fgcommand]], even those implemented in C++, so use with caution!}}<br />
|param1 = cmd<br />
|param1text = String specifying the name of the command to remove.<br />
|example1 = addcommand("hello", func(){<br />
print("Hello");<br />
});<br />
fgcommand("hello"); # "Hello" will be printed<br />
removecommand("hello"); # removes it<br />
}}<br />
<br />
=== removelistener() ===<br />
{{Nasal doc<br />
|syntax = removelistener(id);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=1384|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=506|t=Part 2}}<br />
|text = Removes and deactivates the given listener and returns the number of listeners left or <code>'''nil'''</code> on error.<br />
{{note|It is good practice to remove listeners when they are not required anymore. This prevents the listeners reducing FlightGear's run performance.}}<br />
|param1 = id<br />
|param1text = ID of listener as returned by {{func link|setlistener()}}.<br />
|example1 = var ls = setlistener("/sim/test", func(){<br />
print("Property '/sim/test' has been changed");<br />
});<br />
setprop("/sim/test", "blah"); # trigger listener<br />
var rem = removelistener(ls); # remove listener<br />
print("There are ", rem, " listeners remaining");<br />
}}<br />
<br />
=== resolvepath() ===<br />
{{Nasal doc<br />
|syntax = resolvepath(path);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=604|t=Source}}<br />
|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.<br />
|param1 = path<br />
|param1text = Relative path to be completed.<br />
|example1 = print(resolvepath("Nasal/globals.nas")); # prints the equivalent of $FG_ROOT/Nasal/globals.nas<br />
|example2 = print(resolvepath("blah")); # prints nothing; could not be resolved<br />
|example3 = var file_path = resolvepath("Aircraft/SenecaII/some-file");<br />
if (file_path != ""){<br />
gui.popupTip("some-file found", 2);<br />
} else {<br />
gui.popupTip("some-file not found", 2);<br />
}<br />
}}<br />
<br />
=== setlistener() ===<br />
{{Nasal doc<br />
|syntax = setlistener(node, code[, init[, type]]);<br />
|private = _setlistener()<br />
|source = {{flightgear file|src/Scripting/Nasal|l=499|t=Part 1}} {{!}} {{flightgear file|src/Scripting/Nasal|l=1350|t=Part 2}}<br>''Listener implemented using the {{API Link|flightgear|class|FGNasalListener}} class.''<br />
|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()}}.<br />
{{note|Listeners are known to be a source of resource leaks. To avoid this, please take measures such as:<br />
* Using {{func link|removelistener()}} when they are not needed any more.<br />
* Using a single initialization listener.<br />
* Avoiding tying listeners to properties that are rapidly updated (e.g., many times per frame).<br />
}}<br />
|param1 = node<br />
|param1text = Mandatory string or <code>props.Node</code> object pointing to a property in the [[Property Tree]].<br />
|param2 = code<br />
|param2text = Mandatory callback function to execute when the listener is triggered. The function can take up to four arguments in the following order:<br />
* '''changed''': a <code>props.Node</code> object pointing to the changed node.<br />
* '''listen''': a <code>props.Node</code> object pointing to the listened-to node. Note that this argument maybe different depending on the '''type'''.<br />
* '''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.<br />
* '''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.<br />
|param3 = init<br />
|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).<br />
|param4 = type<br />
|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.<br />
|example1 = var prop = props.globals.initNode("/sim/test", "", "STRING"); # create property<br />
var id = setlistener("/sim/test", func(n){ # create listener<br />
print("Value: ", n.getValue());<br />
});<br />
setprop("/sim/test", "blah"); # trigger listener<br />
removelistener(id); # remove listener<br />
prop.remove(); # remove property<br />
}}<br />
<br />
=== setprop() ===<br />
{{Nasal doc<br />
|syntax = setprop(path[, path[, ...]], value);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=385|t=Source}}<br />
|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.<br />
{{note|If you want to remove a property, you will have to use one of the <code>props</code> helpers:<br />
<syntaxhighlight lang="nasal"><br />
props.globals.getNode("foo/bar").remove(); # take out the complete node<br />
props.globals.getNode("foo").removeChild("bar"); # take out a certain child node<br />
</syntaxhighlight><br />
}}<br />
|param1 = path<br />
|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.<br />
|param2 = value<br />
|param2text = Value to write to the given property. Must be either a string or a number.<br />
|example1 = setprop("/sim/demo", "This is a demo");<br />
|example2text = Note that the example below will only work in FlightGear 3.2 and above.<br />
|example2 = for(var i = 0; i < 3; i += 1){<br />
setprop("/sim/demo", i, "Demo #" ~ i));<br />
}<br />
|example3text = Same as above, but is supported by all versions of FlightGear.<br />
|example3 = for(var i = 0; i < 3; i += 1){<br />
setprop("/sim/demo[" ~ i ~ "]", "Demo #" ~ i));<br />
}<br />
}}<br />
==== See also ====<br />
{{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: <br />
To get a Node rather than its value, use <code>props.globals.getNode()</code> - see [[Nasal_library/props]]. }}<br />
<br />
=== settimer() ===<br />
{{Nasal doc<br />
|syntax = settimer(function, delta[, realtime]);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=499|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=1286|t=Part 2}}<br />
|text = Runs the given function a specified amount of seconds after the current time. Returns <code>'''nil'''</code>.<br />
{{note|Improper use of <code>settimer()</code> may cause resource leaks. It is also highly recommended that the newer {{func link|maketimer()}} should be used instead of this function.}} <br />
|param1 = function<br />
|param1text = Mandatory function that will be called. It may be necessary to enclose code in an anonymous function (see example).<br />
|param2 = delta<br />
|param2text = Mandatory amount of time in seconds after which the function will be called.<br />
|param3 = realtime<br />
|param3text = If 1 (true), "real time" will be used instead of "simulation time." Defaults to 0 (false). Note that if "simulation time" is used, the timer will not run while FlightGear is paused.<br />
|example1 = var myFunc = func(){<br />
print("Hello");<br />
}<br />
<br />
settimer(myFunc, 2); # runs myFunc after 2 seconds<br />
|example2 = var sqr = func(a){<br />
return a * a;<br />
}<br />
<br />
settimer(func(){<br />
print(sqr(2)); # will print 4 after 2 seconds<br />
}, 2);<br />
}}<br />
<br />
=== srand() ===<br />
{{Nasal doc<br />
|syntax = srand();<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=559|t=Source}}<br />
|text = Makes the pseudorandom number generator (see {{func link|rand()}}) generate a new {{wikipedia|random seed|noicon=1}} based on time. Returns 0.<br />
}}<br />
=== systime() ===<br />
{{Nasal doc<br />
|syntax = systime();<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=770|t=Source}}<br />
|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).<br />
{{note|1=High resolution timers under Windows can produce inaccurate or fixed sub-millisecond results.<ref>{{cite web|url=http://forum.flightgear.org/viewtopic.php?f=30&t=29259|title=Nasal: systime() ??!?|author=Necolatis|date=Apr 2nd, 2016}}</ref> 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]}}<br />
|example1 = print("Unix time: ", systime()); # prints Unix time<br />
|example2 = var myFunc = func(){<br />
for(var i = 0; i <= 10; i += 1){<br />
print("Interation #", i);<br />
}<br />
}<br />
var t = systime(); # record time<br />
myFunc(); # run function<br />
var t2 = systime(); # record new time<br />
print("myFunc() took ", t2 - t, " seconds"); # print result<br />
}}<br />
<br />
=== thisfunc() ===<br />
{{Nasal doc<br />
|syntax = thisfunc();<br />
|source = {{fgdata file|Nasal/globals.nas|t=Source}}<br />
|text = Returns the function from which this function is called. This allows a function to reliably and safely call itself from within a closure.<br />
|example1 = var stringify_vec = func(input){<br />
if (typeof(input) == "scalar"){<br />
return sprintf("%s", input);<br />
} elsif (typeof(input) == "vector") {<br />
if (size(input) == 0) return "[]";<br />
var this = thisfunc();<br />
var buffer = "[";<br />
for(var i = 0; i < size(input); i += 1){<br />
buffer ~= this(input[i]);<br />
if (i == size(input) - 1) {<br />
buffer ~= "]";<br />
} else {<br />
buffer ~= ", ";<br />
}<br />
}<br />
return buffer;<br />
} else {<br />
die("stringify_vec(): Error! Invalid input. Must be a vector or scalar");<br />
}<br />
}<br />
<br />
var test_vec = ["a", "b", "c", 1, 2, 3];<br />
debug.dump(stringify_vec(test_vec)); # prints "[a, b, c, 1, 2, 3]"<br />
test_vec = [];<br />
debug.dump(stringify_vec(test_vec)); # prints "[]"<br />
test_vec = {};<br />
debug.dump(stringify_vec(test_vec)); # will throw an error<br />
}}<br />
<br />
=== tileIndex() ===<br />
{{Nasal doc<br />
|syntax = tileIndex();<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1720|t=Source}}<br />
|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 <code>942050</code>, corresponding to <tt>''[[$FG_SCENERY]]/Terrain/w130n30/w123n3/942050.stg''</tt>.<br />
|example1 = print(tileIndex()); # print index<br />
}}<br />
<br />
=== tilePath() ===<br />
{{Nasal doc<br />
|syntax = tilePath();<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1712|t=Source}}<br />
|text = Returns the base path of the tile at the aircraft's current position as a string. For example, at KSFO, this would be <code>w130n30/w123n3</code>, corresponding to <tt>''[[$FG_SCENERY]]/Terrain/w130n30/w123n3''</tt>.<br />
|example1 = print(tilePath()); # print path<br />
}}<br />
<br />
=== values() ===<br />
{{Nasal doc<br />
|syntax = values(hash);<br />
|source = {{fgdata file|Nasal/globals.nas|t=Source}}<br />
|text = Returns a vector containing the values of the given hash.<br />
|param1 = hash<br />
|param1text = Mandatory hash to get the values of.<br />
|example1 = var hash = {<br />
"a": 1,<br />
"b": 2,<br />
"c": 3<br />
};<br />
<br />
foreach(var val; values(hash)){<br />
print(val);<br />
}<br />
|example2text = The below example does exactly the same thing as the above example, but does not use <code>values()</code>:<br />
|example2 = var hash = {<br />
"a": 1,<br />
"b": 2,<br />
"c": 3<br />
};<br />
<br />
foreach(var key; keys(hash)){<br />
print(hash[key]);<br />
}<br />
}}<br />
<br />
== Extension functions new in FG 2020.1 ==<br />
The following functions have been added to the nasal core library and will be released with FlightGear version 2020.1. <br />
Before the release they are available in the development branch "next".<br />
<br />
=== isfunc() ===<br />
Returns 1 if type or argument is a function, otherwise 0.<br />
<br />
=== isghost() ===<br />
Returns 1 if type or argument is a ghost, otherwise 0.<br />
<br />
=== ishash() ===<br />
Returns 1 if type or argument is a hash, otherwise 0.<br />
<br />
=== isint() ===<br />
{{Nasal doc<br />
|syntax = isint(x);<br />
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}<br />
|text = Returns 1 if argument has a numeric value and x == floor(x), e.g. integer.<br />
}}<br />
<br />
=== isnum() ===<br />
{{Nasal doc<br />
|syntax = isnum(x);<br />
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}<br />
|text = Returns 1 if typeof(x) is "scalar" and x has a numeric value otherwise 0. <br />
}}<br />
<br />
=== isscalar() ===<br />
{{Nasal doc<br />
|syntax = isnum(x);<br />
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}<br />
|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 "~". <br />
|example1 = var a = "foo"; <br />
var b=42;<br />
if (isscalar(a) and isscalar(b)) print(a~b);<br />
if (isstr(a)) print("a is a string");<br />
if (isint(b)) print("b is an integer");<br />
# if (isscalar(a))... is equivalent to if (typeof(a) == "scalar")...<br />
}}<br />
<br />
=== isstr() ===<br />
Returns 1 if type or argument is a string, otherwise 0. <br />
<br />
=== isvec() ===<br />
Returns 1 if type or argument is a vector, otherwise 0.<br />
<br />
=== vecindex() ===<br />
{{Nasal doc<br />
|syntax = vecindex(vector, value);<br />
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}<br />
|text = Returns the index of value or nil, if value is not found in vector.<br />
|example1=<br />
var myvector = ["apple", "bananna", "coconut"];<br />
# to check if a vector contains a certain value compare vecindex to nil<br />
if (vecindex(myvector, "apple") != nil) {<br />
print("found apple");<br />
}<br />
# WARNING: this will not work as desired because index is 0<br />
if (vecindex(myvector, "apple")) {<br />
print("found apple");<br />
}<br />
<br />
}}<br />
<br />
{{note| There is a similar function function contains(hash, key) to check if a hash contains a key (not value!).<br />
It is to be discussed, if contains can/should be extended to support vectors in the above mentioned way. }}<br />
<br />
== Variables ==<br />
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}}.<br />
<br />
=== D2R ===<br />
{{Nasal doc<br />
|syntax = var radians = degrees * D2R;<br />
|text = Converts an angle from degrees to radians when multiplied by the angle in degrees. Equal to <code>π / 180</code>.<br />
}}<br />
=== FPS2KT ===<br />
{{Nasal doc<br />
|syntax = var knots = feet_per_second * FPS2KT;<br />
|text = Converts a velocity from feet per second to knots when multiplied by the velocity in feet per second. Approximately equal to 0.5925.<br />
}}<br />
=== FT2M ===<br />
{{Nasal doc<br />
|syntax = var metres = feet * FT2M;<br />
|text = Converts a length from feet to metres when multiplied by the length in feet. Equal to 0.3048.<br />
}}<br />
=== GAL2L ===<br />
{{Nasal doc<br />
|syntax = var litres = gallons * GAL2L;<br />
|text = Converts a volume from US liquid gallons to litres when multiplied by the volume in gallons. Approximately equal to 3.7854.<br />
}}<br />
=== IN2M ===<br />
{{Nasal doc<br />
|syntax = var metres = inches * IN2M;<br />
|text = Converts a length from inches to metres when multiplied by the length in inches. Equal to 0.0254.<br />
}}<br />
=== KG2LB ===<br />
{{Nasal doc<br />
|syntax = var pounds = kilograms * KG2LB;<br />
|text = Converts a mass from kilograms to pounds when multiplied by the mass in kilograms. Approximately equal to 2.2046.<br />
}}<br />
=== KT2FPS ===<br />
{{Nasal doc<br />
|syntax = var feet_per_second = knots * KT2FPS;<br />
|text = Converts a velocity from knots to feet per second when multiplied by the velocity in knots. Approximately equal to 1.6878.<br />
}}<br />
=== KT2MPS ===<br />
{{Nasal doc<br />
|syntax = var metres_per_second = knots * KT2MPS;<br />
|text = Converts a velocity from knots to metres per second when multiplied by the velocity in knots. Approximately equal to 0.5144.<br />
}}<br />
=== L2GAL ===<br />
{{Nasal doc<br />
|syntax = var gallons = litres * L2GAL;<br />
|text = Converts a volume from litres to US liquid gallons when multiplied by the volume in litres. Approximately equal to 0.2642.<br />
}}<br />
=== LB2KG ===<br />
{{Nasal doc<br />
|syntax = var kilograms = pounds * LB2KG;<br />
|text = Converts a mass from pounds to kilograms when multiplied by the mass in pounds. Approximately equal to 0.4536.<br />
}}<br />
=== M2FT ===<br />
{{Nasal doc<br />
|syntax = var feet = metres * M2FT;<br />
|text = Converts a length from metres to feet when multiplied by the length in metres. Approximately equal to 3.2808.<br />
}}<br />
=== M2IN ===<br />
{{Nasal doc<br />
|syntax = var inches = metres * M2IN;<br />
|text = Converts a length from metres to inches when multiplied by the length in metres. Approximately equal to 39.3701.<br />
}}<br />
=== M2NM ===<br />
{{Nasal doc<br />
|syntax = var nautical_miles = metres * M2NM;<br />
|text = Converts a length from metres to nautical miles when multiplied by the length in metres. Approximately equal to 0.00054.<br />
}}<br />
=== MPS2KT ===<br />
{{Nasal doc<br />
|syntax = var knots = metres_per_second * MPS2KT;<br />
|text = Converts a velocity from metres per second to knots when multiplied by the velocity in metres per second. Approximately equal to 1.9438.<br />
}}<br />
=== NM2M ===<br />
{{Nasal doc<br />
|syntax = var metres = nautical_miles * NM2M;<br />
|text = Converts a length from nautical miles to metres when multiplied by the length in nautical miles. Equal to 1,852.<br />
}}<br />
=== R2D ===<br />
{{Nasal doc<br />
|syntax = var degrees = radians * R2D;<br />
|text = Converts an angle from radians to degrees when multiplied by the angle in radians. Equal to <code>180 / π</code>.<br />
}}<br />
<br />
{{Appendix}}<br />
<br />
<br />
{{Nasal namespaces}}</div>Jsbhttps://wiki.flightgear.org/w/index.php?title=Modules.nas&diff=124431Modules.nas2020-05-01T08:50:05Z<p>Jsb: /* Property tree interface for modules.nas */</p>
<hr />
<div>== Nasal runtime re-loadable modules ==<br />
=== Introduction and motivation ===<br />
The embedded scripting language of FlightGear, [[Nasal]] comes with a limited number of [[Nasal library|core functions]].<br />
<br />
The language has been [[Howto:Extend Nasal|extended by C++ functions]], as well as by libraries written in Nasal (for example [[Nasal library/props|props]], [[Nasal library/io|io]], [[Nasal library/math|math]], [[Nasal library/debug|debug]] etc). <br />
<br />
The latter are stored in the [[FGData]] repository under <code>/Nasal</code> and loaded automatically by FlightGear.<br />
<br />
Optional code, which is loaded only on demand, can be handled either by the legacy module system implemented in C++ or by <code>modules.nas</code> if run-time re-load support is required.<br />
<br />
For more information on how and when these files are loaded, see [[Nasal Initialization]].<br />
<br />
=== Differences between add-ons and modules ===<br />
While there are many similarities between add-ons and modules, there are also some differences: <br />
<br />
; Distribution<br />
: Modules are distributed with FlightGear as part of FGData or they are aircraft specific and delivered with the aircraft.<br />
: Add-ons have to be downloaded separately by a FlightGear user from wherever the author of the add-on publishes the add-on.<br />
<br />
; Loading<br />
: Modules can be loaded for example by an aircraft if the aircraft developer wants to make use of the module.<br />
: Add-ons are selected by the user before launching FlightGear, thus they may or '''may not be available''' at runtime.<br />
<br />
== HOWTOs and examples ==<br />
First some examples, the API documentation follows below.<br />
<br />
=== Nasal modules in an aircraft ===<br />
Nasal code for an aircraft (for example for development) is usually loaded by a XML declaration in the <code>[[Aircraft-set.xml]]</code> file like this:<br />
<syntaxhighlight lang="xml"><br />
<PropertyList><br />
<nasal><br />
<foo> <!-- Nasal namespace foo --><br />
<file>Nasal/foo.nas</file><br />
</foo><br />
<bar> <!-- Nasal namespace bar --><br />
<file>Aircraft/Generic/foo.nas</file><br />
</bar><br />
<myAircraft> <!-- Nasal namespace myAircraft--><br />
<file>Nasal/efis_module.nas</file><br />
</myAircraft><br />
</nasal><br />
</PropertyList><br />
</syntaxhighlight><br />
<br />
This is fine unless you are developing a Nasal subsystem for the aircraft and have to restart FlightGear every time you edit a few lines. <br />
For this case you could consider to use the Module class (defined in <code>FGDATA/Nasal/modules.nas</code>) and make your code re-loadable at runtime, <br />
which means you do not have to restart whole FlightGear just for a few lines of edited Nasal code.<br />
<br />
Example <code>Nasal/efis_module.nas</code>:<br />
<syntaxhighlight lang="nasal"><br />
#-- load EFIS as reloadable module<br />
var my_efis = modules.Module.new("myAircraft_EFIS"); # Module name<br />
my_efis.setDebug(1); # 0=(mostly) silent; 1=print setlistener and maketimer calls to console; 2=print also each listener hit, be very careful with this! <br />
my_efis.setFilePath(getprop("/sim/aircraft-dir")~"/Nasal/EFIS");<br />
my_efis.setMainFile("myAircraft-efis.nas");<br />
my_efis.load();<br />
</syntaxhighlight><br />
<br />
==== Menu item for reloading ====<br />
Now add a menu item for easy reloading like this:<br />
{{note|The module name passed to the fgcommand must match the name passed to <code>modules.Module.new()</code>}}<br />
<syntaxhighlight lang="xml"><br />
<menubar><br />
<default><br />
<menu n="100"><br />
<!-- normal aircraft menu --><br />
</menu><br />
<menu n="101"><br />
<label>Aircraft Development</label><br />
<item><br />
<label>Reload EFIS</label><br />
<binding><br />
<command>nasal-module-reload</command><br />
<module>myAircraft_EFIS</module><br />
</binding><br />
</item><br />
</menu><br />
</default><br />
</menubar><br />
</syntaxhighlight><br />
<br />
{{tip|You can also call <code>myAircraft.my_efis.reload();</code> to trigger the reload. <code>myAircraft</code> is the namespace given in the <code>Aircraft-set.xml</code> file, <code>my_efis</code> is the module object (see above).}}<br />
<br />
=== Nasal frameworks ===<br />
Another use case for <code>Modules.nas</code> is frameworks that need reload support.<br />
<br />
A framework usually provides more or less generic functionality that has to be adapted (configured) for a specific use case (for example a specific aircraft).<br />
<br />
Reload support may become very convenient, if the customizing based on such framework involves many iterations of edit-reload-test.<br />
<br />
First living example is the <code>canvas_efis</code> framework which can be included into an aircraft with a single line of code: <br />
<br />
var efis_module = modules.load("canvas_efis");<br />
<br />
(see also last chapter at the end of this article)<br />
<br />
== Structure of Nasal reloadable modules ==<br />
A module must contain at least the file <code>main.nas</code> (default, other filename is possible) and should contain the functions <code>main()</code> and <code>unload()</code>.<br />
<br />
==== main() function ====<br />
The <code>main.nas</code> file must contain a <code>main()</code> function that will be called on load with either the module object as parameter or the parameters passed to the <code>module.load()</code> function.<br />
<br />
==== unload() function ====<br />
If you want the module to be re-/un-loadable, you must make sure to track resources and remove them on onload.<br />
For this the <code>main.nas</code> shall contain a function <code>unload()</code> that removes any resources which were created by the module.<br />
<br />
'''Example:''' any canvas created by the module should have called its <code>del()</code> method here.<br />
<br />
{{note|Calls to <code>setlistener()<code> and <code>maketimer()<code> are automatically tracked by the <code>Module</code> class for you, timers will be stopped automatically, listeners will be removed automatically on <code>unload()<code>.}}<br />
<br />
==== Rules for Module names ====<br />
# Module names shall contain only letters (<code>a-z, A-Z</code>), numbers (<code>0-9</code>) and underscores (<code>_</code>).<br />
# The name must contain at least one letter so it cannot be confused with a number<br />
# The name shall not match any existing Nasal file (<code>.nas</code>) or directory in <code>FGDATA/Nasal</code> to avoid namespace clashes<br />
<br />
{{note|Reloadable modules shipped with FlightGear are stored in subdirectories of <code>FGDATA/Nasal/modules/</code> as these subdirectories will not be loaded automatically by FlightGear on start.<br />
<br />
However, <code>modules.nas</code> will scan this directory to create a list of available modules (list of subdirectories).<br />
<br />
Each module corresponds to one subdirectory and the directory name is also the module name.<br />
}}<br />
<br />
== modules.nas ==<br />
In <code>modules.nas</code> the class <code>Module</code> is defined.<br />
<br />
A module object holds information about the path and filename of the Nasal script and supports unloading and reloading the code at runtime<br />
(for example without restarting FlightGear as a whole) by tracking some critical resources like [[listeners]] and [[timers]].<br />
<br />
{{note|Parts of this functionality were added to the [[addons]] manager earlier and have now been extracted to avoid code duplication.}}<br />
<br />
{{caution|Within a module <code>setlistener()</code> is overloaded and the default value for the 4th argument (runtime) is changed to <code>0</code>, so the listener will run only if the property value has changed.}}<br />
<br />
=== library functions ===<br />
==== modules.isAvailable() ====<br />
{{Nasal doc<br />
|syntax = modules.isAvailable(module_name);<br />
|text = This function returns true, if there is a module (subdirectory) in <code>FGDATA/Nasal/modules/</code> with the given name.<br />
|param1 = module_name<br />
|param1text = The name of the module (in the subdirectory in <code>Nasal/modules</code>) to load.<br />
|example1 = <br />
if (modules.isAvailable("foo_bar")) {<br />
modules.load("foo_bar");<br />
} <br />
}}<br />
<br />
==== modules.setDebug() ====<br />
{{Nasal doc<br />
|syntax = modules.setDebug(module_name, [debug=1]);<br />
|text = This function enables debugging for a module. It must be called '''before''' <code>load()</code>!<br />
|param1 = module_name<br />
|param1text = The name of the module (in the subdirectory in <code>Nasal/modules</code>) to load.<br />
|param2 = debug<br />
|param2text = Defaults to <code>1</code> (true), use <code>0</code> to disable debug. If debug > <code>1</code>, each listener hit will be printed, be careful not to listen to rapidly changing props, do not set <code>runtime=1</code> in <code>setlistener()</code>.<br />
|example1 = <br />
var debug = 1;<br />
modules.setDebug("foo_bar", debug); <br />
}}<br />
<br />
==== modules.load() ====<br />
{{Nasal doc<br />
|syntax = modules.load(module_name, [namespace_name]);<br />
|text = This function attempts to load a module from <code>FGDATA/Nasal/modules/</code><br />
|param1 = module_name<br />
|param1text = The name of the module (in the subdirectory in <code>Nasal/modules</code>) to load.<br />
|param2 = namespace_name<br />
|param2text = Optional, load module to a different namespace.<br />
|example1 = <br />
modules.load("foo_bar"); <br />
}}<br />
<br />
=== Methods of class Module ===<br />
==== setFilePath() ====<br />
{{Nasal doc<br />
|syntax = mymod.setFilePath(path);<br />
|text = Configure where to look for the main file, for example in the aircraft (sub-)directory.<br />
|param1 = path<br />
|param1text = File path where the module is stored.<br />
}}<br />
<br />
==== setMainFile() ====<br />
{{Nasal doc<br />
|syntax = mymod.setMainFile(filename);<br />
|text = Configure the nasal file to load.<br />
|param1 = filename<br />
|param1text = File that will be loaded by <code>load()</code>.<br />
|example1 =<br />
# if you develop a new nasal system for your aircraft, it might be handy to implement it as module<br />
# so you can reload the file quickly without restarting FlightGear<br />
var my_foo_sys = modules.Module.new("my_aircraft_foo");<br />
my_foo_sys.setDebug(1);<br />
my_foo_sys.setFilePath(getprop("/sim/aircraft-dir")~"/Nasal");<br />
my_foo_sys.setMainFile("foo.nas");<br />
my_foo_sys.load();<br />
}}<br />
<br />
==== setNamespace() ====<br />
{{Nasal doc<br />
|syntax = mymod.setNamespace(namespace);<br />
|text = Configure the Nasal namespace to use. Be really careful when using existing namespaces! <code>unload()</code> or <code>reload()</code> will destroy them!<br />
|param1 = namespace<br />
|param1text = The Nasal namespace the module code will be loaded into.<br />
}}<br />
<br />
==== setDebug() ====<br />
{{Nasal doc<br />
|syntax = mymod.setDebug(debug=1);<br />
|text = Activate debugging for this module. '''Must be called before calling <code>load()</code>!'''<br />
|param1 = debug<br />
|param1text = <br />
0: no debugging; <br />
1 (default if no argument given): print calls to redirected <code>setlister()</code> and <code>maketimer()</code>; <br />
<br />
2: listeners print property path when hit (Use with caution! '''Do not call <code>setlistener()</code> with <code>runtime=1</code>'''.)<br />
}}<br />
<br />
==== load() ====<br />
{{Nasal doc<br />
|syntax = mymod.load([args]);<br />
|text = This function attempts to load the module into its namespace.<br />
|param1 = optional args<br />
|param1text = Arguments are passed to the <code>main()</code> function of the module. If empty, the module object will be passed to <code>main()</code>.<br />
}}<br />
<br />
==== unload() ====<br />
{{Nasal doc<br />
|syntax = mymod.unload();<br />
|text = This function attempts to remove tracked resources and remove the module by killing its namespace.<br />
}}<br />
<br />
==== reload() ====<br />
{{Nasal doc<br />
|syntax = mymod.reload();<br />
|text = Shorthand, calls <code>unload()</code> and <code>load()</code>.<br />
}}<br />
<br />
==== get() ====<br />
{{Nasal doc<br />
|syntax = mymod.get(var_name);<br />
|text = Returns a variable from the modules namespace.<br />
|param1 = var_name<br />
|param1text = The variable to get.<br />
|example1 =<br />
var foo = modules.load("foo");<br />
var bar = foo.get("bar"); # get variable "bar" defined in FGDATA/Nasal/modules/foo/main.nas (or a file included by this file)<br />
}}<br />
<br />
== Property tree interface for modules.nas ==<br />
In the property tree there is a subtree <code>/nasal</code> to control modules and get some statistics.<br />
<br />
The properties available depend on the type of module ("load-once" or "reloadable", see [[Nasal Initialization]] for more information on the differences).<br />
<br />
=== Reloadable modules / frameworks ===<br />
Modules handled by <code>modules.nas</code> will have their properties in <code><nowiki>/nasal/modules/<moduleName></nowiki></code> where <code><nowiki><moduleName></nowiki></code> is given by the developer when calling either <code><nowiki>Module.new("<moduleName>")</nowiki></code> or <code><nowiki>modules.load("<moduleName>")</nowiki></code>.<br />
<br />
In the latter case <code><nowiki><moduleName></nowiki></code> specifies the subdirectory <code><nowiki>FGDATA/Nasal/modules/<moduleName></nowiki></code> in which some framework is stored.<br />
<br />
{| class="wikitable"<br />
|-<br />
! property !! type !! content<br />
|-<br />
| <code>loaded</code> || bool || true if module was loaded without errors<br />
|-<br />
| <code>reload</code> || bool || to trigger reload from the property browser. For menues, XML bindings and scripts the fgcommand is the prefered way to trigger reload.<br />
|-<br />
| <code>listeners</code> || int || Number of tracked listeners<br />
|-<br />
| <code>listener-hits</code> || int || If debugging is enabled, this prop shows the total number of hits to all tracked listeners.<br />
|-<br />
| <code>timers</code> || int || Number of tracked timers (maketimer).<br />
|}<br />
<br />
=== Legacy load-once modules ===<br />
A legacy load-once module is a direct (1st level) subdirectory of <code>FGDATA/Nasal/</code> and its corresponding property tree is <code><nowiki>/nasal/<moduleName>/</nowiki></code> where <code><nowiki><moduleName></nowiki></code> equals the name of the subdirectory.<br />
<br />
It is handled by C++ code and must have a corresponding entry in <code>FGDATA/defaults.xml</code> which defines a property "enabled" and optionally a property "module".<br />
<br />
If enabled is set to false in <code>defaults.xml</code>, the C++ code will setup a listener and load the module as soon as enabled is set to true.<br />
<br />
The property name "module" is a bit misleading, it is used to define into which namespace the files shall be loaded.<br />
<br />
For each Nasal file in the subdirectory a <code>file[i]</code> property is created holding the full path+filename.<br />
<br />
The bool <code>loaded</code> property shows the status of the module.<br />
<br />
== Existing modules with reload support ==<br />
Stable Nasal frameworks which support reloading can be added to <code>FGDATA/Nasal/modules/<module_name></code>. <br />
This allows an aircraft developer to configure the framework for a specific aircraft and make use of the reload magic while developing the configuration.<br />
<br />
{| class="wikitable"<br />
|-<br />
! Module name !! Desctiption !! time added <br />
|-<br />
| [[Canvas EFIS Framework|<code>canvas_efis</code>]] || framework to manage canvas based EFIS screens || 02/2020<br />
|}<br />
<br />
[[Category:Nasal]]</div>Jsbhttps://wiki.flightgear.org/w/index.php?title=Modules.nas&diff=124430Modules.nas2020-05-01T08:43:59Z<p>Jsb: /* Menu item for reloading */</p>
<hr />
<div>== Nasal runtime re-loadable modules ==<br />
=== Introduction and motivation ===<br />
The embedded scripting language of FlightGear, [[Nasal]] comes with a limited number of [[Nasal library|core functions]].<br />
<br />
The language has been [[Howto:Extend Nasal|extended by C++ functions]], as well as by libraries written in Nasal (for example [[Nasal library/props|props]], [[Nasal library/io|io]], [[Nasal library/math|math]], [[Nasal library/debug|debug]] etc). <br />
<br />
The latter are stored in the [[FGData]] repository under <code>/Nasal</code> and loaded automatically by FlightGear.<br />
<br />
Optional code, which is loaded only on demand, can be handled either by the legacy module system implemented in C++ or by <code>modules.nas</code> if run-time re-load support is required.<br />
<br />
For more information on how and when these files are loaded, see [[Nasal Initialization]].<br />
<br />
=== Differences between add-ons and modules ===<br />
While there are many similarities between add-ons and modules, there are also some differences: <br />
<br />
; Distribution<br />
: Modules are distributed with FlightGear as part of FGData or they are aircraft specific and delivered with the aircraft.<br />
: Add-ons have to be downloaded separately by a FlightGear user from wherever the author of the add-on publishes the add-on.<br />
<br />
; Loading<br />
: Modules can be loaded for example by an aircraft if the aircraft developer wants to make use of the module.<br />
: Add-ons are selected by the user before launching FlightGear, thus they may or '''may not be available''' at runtime.<br />
<br />
== HOWTOs and examples ==<br />
First some examples, the API documentation follows below.<br />
<br />
=== Nasal modules in an aircraft ===<br />
Nasal code for an aircraft (for example for development) is usually loaded by a XML declaration in the <code>[[Aircraft-set.xml]]</code> file like this:<br />
<syntaxhighlight lang="xml"><br />
<PropertyList><br />
<nasal><br />
<foo> <!-- Nasal namespace foo --><br />
<file>Nasal/foo.nas</file><br />
</foo><br />
<bar> <!-- Nasal namespace bar --><br />
<file>Aircraft/Generic/foo.nas</file><br />
</bar><br />
<myAircraft> <!-- Nasal namespace myAircraft--><br />
<file>Nasal/efis_module.nas</file><br />
</myAircraft><br />
</nasal><br />
</PropertyList><br />
</syntaxhighlight><br />
<br />
This is fine unless you are developing a Nasal subsystem for the aircraft and have to restart FlightGear every time you edit a few lines. <br />
For this case you could consider to use the Module class (defined in <code>FGDATA/Nasal/modules.nas</code>) and make your code re-loadable at runtime, <br />
which means you do not have to restart whole FlightGear just for a few lines of edited Nasal code.<br />
<br />
Example <code>Nasal/efis_module.nas</code>:<br />
<syntaxhighlight lang="nasal"><br />
#-- load EFIS as reloadable module<br />
var my_efis = modules.Module.new("myAircraft_EFIS"); # Module name<br />
my_efis.setDebug(1); # 0=(mostly) silent; 1=print setlistener and maketimer calls to console; 2=print also each listener hit, be very careful with this! <br />
my_efis.setFilePath(getprop("/sim/aircraft-dir")~"/Nasal/EFIS");<br />
my_efis.setMainFile("myAircraft-efis.nas");<br />
my_efis.load();<br />
</syntaxhighlight><br />
<br />
==== Menu item for reloading ====<br />
Now add a menu item for easy reloading like this:<br />
{{note|The module name passed to the fgcommand must match the name passed to <code>modules.Module.new()</code>}}<br />
<syntaxhighlight lang="xml"><br />
<menubar><br />
<default><br />
<menu n="100"><br />
<!-- normal aircraft menu --><br />
</menu><br />
<menu n="101"><br />
<label>Aircraft Development</label><br />
<item><br />
<label>Reload EFIS</label><br />
<binding><br />
<command>nasal-module-reload</command><br />
<module>myAircraft_EFIS</module><br />
</binding><br />
</item><br />
</menu><br />
</default><br />
</menubar><br />
</syntaxhighlight><br />
<br />
{{tip|You can also call <code>myAircraft.my_efis.reload();</code> to trigger the reload. <code>myAircraft</code> is the namespace given in the <code>Aircraft-set.xml</code> file, <code>my_efis</code> is the module object (see above).}}<br />
<br />
=== Nasal frameworks ===<br />
Another use case for <code>Modules.nas</code> is frameworks that need reload support.<br />
<br />
A framework usually provides more or less generic functionality that has to be adapted (configured) for a specific use case (for example a specific aircraft).<br />
<br />
Reload support may become very convenient, if the customizing based on such framework involves many iterations of edit-reload-test.<br />
<br />
First living example is the <code>canvas_efis</code> framework which can be included into an aircraft with a single line of code: <br />
<br />
var efis_module = modules.load("canvas_efis");<br />
<br />
(see also last chapter at the end of this article)<br />
<br />
== Structure of Nasal reloadable modules ==<br />
A module must contain at least the file <code>main.nas</code> (default, other filename is possible) and should contain the functions <code>main()</code> and <code>unload()</code>.<br />
<br />
==== main() function ====<br />
The <code>main.nas</code> file must contain a <code>main()</code> function that will be called on load with either the module object as parameter or the parameters passed to the <code>module.load()</code> function.<br />
<br />
==== unload() function ====<br />
If you want the module to be re-/un-loadable, you must make sure to track resources and remove them on onload.<br />
For this the <code>main.nas</code> shall contain a function <code>unload()</code> that removes any resources which were created by the module.<br />
<br />
'''Example:''' any canvas created by the module should have called its <code>del()</code> method here.<br />
<br />
{{note|Calls to <code>setlistener()<code> and <code>maketimer()<code> are automatically tracked by the <code>Module</code> class for you, timers will be stopped automatically, listeners will be removed automatically on <code>unload()<code>.}}<br />
<br />
==== Rules for Module names ====<br />
# Module names shall contain only letters (<code>a-z, A-Z</code>), numbers (<code>0-9</code>) and underscores (<code>_</code>).<br />
# The name must contain at least one letter so it cannot be confused with a number<br />
# The name shall not match any existing Nasal file (<code>.nas</code>) or directory in <code>FGDATA/Nasal</code> to avoid namespace clashes<br />
<br />
{{note|Reloadable modules shipped with FlightGear are stored in subdirectories of <code>FGDATA/Nasal/modules/</code> as these subdirectories will not be loaded automatically by FlightGear on start.<br />
<br />
However, <code>modules.nas</code> will scan this directory to create a list of available modules (list of subdirectories).<br />
<br />
Each module corresponds to one subdirectory and the directory name is also the module name.<br />
}}<br />
<br />
== modules.nas ==<br />
In <code>modules.nas</code> the class <code>Module</code> is defined.<br />
<br />
A module object holds information about the path and filename of the Nasal script and supports unloading and reloading the code at runtime<br />
(for example without restarting FlightGear as a whole) by tracking some critical resources like [[listeners]] and [[timers]].<br />
<br />
{{note|Parts of this functionality were added to the [[addons]] manager earlier and have now been extracted to avoid code duplication.}}<br />
<br />
{{caution|Within a module <code>setlistener()</code> is overloaded and the default value for the 4th argument (runtime) is changed to <code>0</code>, so the listener will run only if the property value has changed.}}<br />
<br />
=== library functions ===<br />
==== modules.isAvailable() ====<br />
{{Nasal doc<br />
|syntax = modules.isAvailable(module_name);<br />
|text = This function returns true, if there is a module (subdirectory) in <code>FGDATA/Nasal/modules/</code> with the given name.<br />
|param1 = module_name<br />
|param1text = The name of the module (in the subdirectory in <code>Nasal/modules</code>) to load.<br />
|example1 = <br />
if (modules.isAvailable("foo_bar")) {<br />
modules.load("foo_bar");<br />
} <br />
}}<br />
<br />
==== modules.setDebug() ====<br />
{{Nasal doc<br />
|syntax = modules.setDebug(module_name, [debug=1]);<br />
|text = This function enables debugging for a module. It must be called '''before''' <code>load()</code>!<br />
|param1 = module_name<br />
|param1text = The name of the module (in the subdirectory in <code>Nasal/modules</code>) to load.<br />
|param2 = debug<br />
|param2text = Defaults to <code>1</code> (true), use <code>0</code> to disable debug. If debug > <code>1</code>, each listener hit will be printed, be careful not to listen to rapidly changing props, do not set <code>runtime=1</code> in <code>setlistener()</code>.<br />
|example1 = <br />
var debug = 1;<br />
modules.setDebug("foo_bar", debug); <br />
}}<br />
<br />
==== modules.load() ====<br />
{{Nasal doc<br />
|syntax = modules.load(module_name, [namespace_name]);<br />
|text = This function attempts to load a module from <code>FGDATA/Nasal/modules/</code><br />
|param1 = module_name<br />
|param1text = The name of the module (in the subdirectory in <code>Nasal/modules</code>) to load.<br />
|param2 = namespace_name<br />
|param2text = Optional, load module to a different namespace.<br />
|example1 = <br />
modules.load("foo_bar"); <br />
}}<br />
<br />
=== Methods of class Module ===<br />
==== setFilePath() ====<br />
{{Nasal doc<br />
|syntax = mymod.setFilePath(path);<br />
|text = Configure where to look for the main file, for example in the aircraft (sub-)directory.<br />
|param1 = path<br />
|param1text = File path where the module is stored.<br />
}}<br />
<br />
==== setMainFile() ====<br />
{{Nasal doc<br />
|syntax = mymod.setMainFile(filename);<br />
|text = Configure the nasal file to load.<br />
|param1 = filename<br />
|param1text = File that will be loaded by <code>load()</code>.<br />
|example1 =<br />
# if you develop a new nasal system for your aircraft, it might be handy to implement it as module<br />
# so you can reload the file quickly without restarting FlightGear<br />
var my_foo_sys = modules.Module.new("my_aircraft_foo");<br />
my_foo_sys.setDebug(1);<br />
my_foo_sys.setFilePath(getprop("/sim/aircraft-dir")~"/Nasal");<br />
my_foo_sys.setMainFile("foo.nas");<br />
my_foo_sys.load();<br />
}}<br />
<br />
==== setNamespace() ====<br />
{{Nasal doc<br />
|syntax = mymod.setNamespace(namespace);<br />
|text = Configure the Nasal namespace to use. Be really careful when using existing namespaces! <code>unload()</code> or <code>reload()</code> will destroy them!<br />
|param1 = namespace<br />
|param1text = The Nasal namespace the module code will be loaded into.<br />
}}<br />
<br />
==== setDebug() ====<br />
{{Nasal doc<br />
|syntax = mymod.setDebug(debug=1);<br />
|text = Activate debugging for this module. '''Must be called before calling <code>load()</code>!'''<br />
|param1 = debug<br />
|param1text = <br />
0: no debugging; <br />
1 (default if no argument given): print calls to redirected <code>setlister()</code> and <code>maketimer()</code>; <br />
<br />
2: listeners print property path when hit (Use with caution! '''Do not call <code>setlistener()</code> with <code>runtime=1</code>'''.)<br />
}}<br />
<br />
==== load() ====<br />
{{Nasal doc<br />
|syntax = mymod.load([args]);<br />
|text = This function attempts to load the module into its namespace.<br />
|param1 = optional args<br />
|param1text = Arguments are passed to the <code>main()</code> function of the module. If empty, the module object will be passed to <code>main()</code>.<br />
}}<br />
<br />
==== unload() ====<br />
{{Nasal doc<br />
|syntax = mymod.unload();<br />
|text = This function attempts to remove tracked resources and remove the module by killing its namespace.<br />
}}<br />
<br />
==== reload() ====<br />
{{Nasal doc<br />
|syntax = mymod.reload();<br />
|text = Shorthand, calls <code>unload()</code> and <code>load()</code>.<br />
}}<br />
<br />
==== get() ====<br />
{{Nasal doc<br />
|syntax = mymod.get(var_name);<br />
|text = Returns a variable from the modules namespace.<br />
|param1 = var_name<br />
|param1text = The variable to get.<br />
|example1 =<br />
var foo = modules.load("foo");<br />
var bar = foo.get("bar"); # get variable "bar" defined in FGDATA/Nasal/modules/foo/main.nas (or a file included by this file)<br />
}}<br />
<br />
== Property tree interface for modules.nas ==<br />
In the property tree there is a subtree <code>/nasal</code> to control modules and get some statistics.<br />
<br />
The properties available depend on the type of module ("load-once" or "reloadable", see [[Nasal Initialization]] for more information on the differences).<br />
<br />
=== Reloadable modules / frameworks ===<br />
Modules handled by <code>modules.nas</code> will have their properties in <code><nowiki>/nasal/modules/<moduleName></nowiki></code> where <code><nowiki><moduleName></nowiki></code> is given by the developer when calling either <code><nowiki>Module.new("<moduleName>")</nowiki></code> or <code><nowiki>modules.load("<moduleName>")</nowiki></code>.<br />
<br />
In the latter case <code><nowiki><moduleName></nowiki></code> specifies the subdirectory <code><nowiki>FGDATA/Nasal/modules/<moduleName></nowiki></code> in which some framework is stored.<br />
<br />
{| class="wikitable"<br />
|-<br />
! property !! type !! content<br />
|-<br />
| <code>loaded</code> || bool || true if module was loaded without errors<br />
|-<br />
| <code>listeners</code> || int || Number of tracked listeners<br />
|-<br />
| <code>listener-hits</code> || int || If debugging is enabled, this prop shows the total number of hits to all tracked listeners.<br />
|-<br />
| <code>timers</code> || int || Number of tracked timers (maketimer).<br />
|}<br />
<br />
=== Legacy load-once modules ===<br />
A legacy load-once module is a direct (1st level) subdirectory of <code>FGDATA/Nasal/</code> and its corresponding property tree is <code><nowiki>/nasal/<moduleName>/</nowiki></code> where <code><nowiki><moduleName></nowiki></code> equals the name of the subdirectory.<br />
<br />
It is handled by C++ code and must have a corresponding entry in <code>FGDATA/defaults.xml</code> which defines a property "enabled" and optionally a property "module".<br />
<br />
If enabled is set to false in <code>defaults.xml</code>, the C++ code will setup a listener and load the module as soon as enabled is set to true.<br />
<br />
The property name "module" is a bit misleading, it is used to define into which namespace the files shall be loaded.<br />
<br />
For each Nasal file in the subdirectory a <code>file[i]</code> property is created holding the full path+filename.<br />
<br />
The bool <code>loaded</code> property shows the status of the module.<br />
<br />
== Existing modules with reload support ==<br />
Stable Nasal frameworks which support reloading can be added to <code>FGDATA/Nasal/modules/<module_name></code>. <br />
This allows an aircraft developer to configure the framework for a specific aircraft and make use of the reload magic while developing the configuration.<br />
<br />
{| class="wikitable"<br />
|-<br />
! Module name !! Desctiption !! time added <br />
|-<br />
| [[Canvas EFIS Framework|<code>canvas_efis</code>]] || framework to manage canvas based EFIS screens || 02/2020<br />
|}<br />
<br />
[[Category:Nasal]]</div>Jsbhttps://wiki.flightgear.org/w/index.php?title=Post_FlightGear_2020.2_LTS_changes&diff=124421Post FlightGear 2020.2 LTS changes2020-04-30T21:16:19Z<p>Jsb: </p>
<hr />
<div>== Code clean-ups and changes ==<br />
<br />
Collecting what's going to change : this will also be used to work out what manual or automatic migrations are required to keep aircraft working. It's expected that for the first few months of 'next', there will be higher than usual breakage, i.e regular might not be possible.<br />
<br />
As a general guideline, the minimum system to develop / build the code on will be Ubuntu 18.04 LTS (Bionic) : the versions below correspond to what is available in a stock Bionic install.<br />
<br />
# Make C++14 the minimum required version (will make it easier to continue replacing Boost items with std:: ones)<br />
# Make CMake 3.6 the minimum required version : this will allow simplifying a bunch of compatibility logic in the build files<br />
# Make Qt 5.9 the minimum for the launcher <br />
# Drop 32-bit windows support<br />
# Drop the pre-2017.x MultiPlayer message format - this will fix warnings from some aircraft about MP packet overflows<br />
# Drop the KLN-89 code, since it's unused and not maintained for a long time: Canvas and the regular GPS code can easily implement a working KLN-89 or similar equipment now<br />
# Switch to Compositor mode as the only rendering option<br />
# Drop Rembrandt support from C++ (really part of the above)<br />
# Drop the non-ALS shaders / pipeline : this will reduce the number of options to test in the compositor effects<br />
# Drop the C++ NavDisplay : the Canvas version replaces it<br />
<br />
== Possible additional items ==<br />
<br />
* Remove the 2D panel code in favour of Canvas (this requires completing some work to load 2D panel elements as Canvas, from XML)<br />
* Remove the C++ HUD in favour of Canvas-based version : this requires some kind of migration script or framework, so we have at least the default UFO HUD available<br />
<br />
== fgdata clean-ups ==<br />
=== /Nasal ===<br />
2do: list outdated / depricated functions for removal (I remember one comment saying: remove after FG 3.0 or something)<br />
<br />
[[Category:Core development]]</div>Jsbhttps://wiki.flightgear.org/w/index.php?title=Howto:Nasal_in_scenery_object_XML_files&diff=124100Howto:Nasal in scenery object XML files2020-04-24T14:51:27Z<p>Jsb: </p>
<hr />
<div>{{Nasal Navigation}}<br />
[[FlightGear]] supports '''embedding [[Nasal]] scripts into scenery object XML files'''. This can be used for "intelligent" objects, such as special lighting features, specific lighthouse signals, and animated hangar doors. In other words, things that couldn't be done with [[Howto:Animate models|animations]] alone. <br />
<br />
In the example below, the code in the <syntaxhighlight lang="xml" inline><load></syntaxhighlight> tag is run as soon as the tile loader decides to load the model. Using a {{func link|maketimer()}}, it prints a message every 5 seconds. When the object is removed from the memory, the code within the <syntaxhighlight lang="xml" inline><unload></syntaxhighlight> tag is executed. The timer is stopped and a message is printed. The code in the <syntaxhighlight lang="xml" inline><unload></syntaxhighlight> tag may also do things like [[Nasal library#removelistener.28.29|removing listeners]].<br />
<br />
{{warning| In dialogs you have to use <open> and <close> instead of <load> and <unload>}}<br />
<br />
<syntaxhighlight lang="xml"><br />
<?xml version="1.0" encoding="UTF-8"?><br />
<br />
<PropertyList><br />
<br />
<path>helipad.ac</path><br />
<nasal><br />
<load><![CDATA[<br />
print("Hello, I'm the helipad!");<br />
var timer = maketimer(5, func(){<br />
print("I'm still here!");<br />
});<br />
timer.start();<br />
]]></load><br />
<br />
<unload><![CDATA[<br />
timer.stop();<br />
print("Bye, bye!")<br />
]]></unload><br />
</nasal><br />
<br />
</PropertyList><br />
</syntaxhighlight><br />
<br />
{{note|The object has to be present at sim startup. If you place an object with the UFO, the XML code will be executed, but not the Nasal code.}}<br />
<br />
{{note|It is best practice to put Nasal code in a [[XML#Data|CDATA section]]. This prevents problems with the Nasal code containing {{wikipedia|List of XML and HTML character entity references#Predefined entities in XML|predefined XML entities}}.<br />
<syntaxhighlight lang="xml"><br />
<nasal><br />
<load><![CDATA[<br />
# ...<br />
# your nasal code here<br />
# ...<br />
]]></load><br />
<nasal><br />
</syntaxhighlight><br />
}}<br />
<br />
[[Category:Nasal howto]]<br />
[[Category:Scenery enhancement]]</div>Jsbhttps://wiki.flightgear.org/w/index.php?title=Addon&diff=124096Addon2020-04-24T07:30:20Z<p>Jsb: /* Implementation */</p>
<hr />
<div>{{Stub}}<br />
[[File:Fgaddonslogo202x89.png|thumb]] <br />
{{forum|10000|FlightGear Addons}}<br />
== Background ==<br />
{{See also|Howto:Creating a simple modding framework}}<br />
<br />
ATC chatter was removed in 2015, see fgdata commit: [https://sourceforge.net/p/flightgear/fgdata/ci/81607f734e13add9be02816ddaec305d05bc4e47/ 81607f734e13add9be02816ddaec305d05bc4e47]<br />
<br />
and the devel list messages referenced in the commit log.<br />
the other relevant commit is this: b60736ba7add2a7cd39af3d8a974d5be3ea46e8b<br />
It would not be very difficult to restore this functionality, or even generalize/improve it significantly (which was the scope of the original discussion on the devel list)<ref>{{cite web<br />
|url = https://forum.flightgear.org/viewtopic.php?p=288388#p288388 <br />
|title = <nowiki> Re: Whatever happened to radom radio </nowiki> <br />
|author = <nowiki> Hooray </nowiki> <br />
|date = Jun 11th, 2016 <br />
|added = Jun 11th, 2016 <br />
|script_version = 0.40 <br />
}}</ref><br />
<br />
The restored functionality could be distributed as a tarball that is extracted into $FG_ROOT - alternatively, into $FG_HOME, because Nasal files there are treated as overlays, which basically means that you can install user-specific extensions there without having to tamper with $FG_ROOT, it would only take very minor changes to turn the chatter feature into a corresponding "addon" - not unlike fgcamera ...<ref>{{cite web<br />
|url = https://forum.flightgear.org/viewtopic.php?p=288392#p288392 <br />
|title = <nowiki> Re: Whatever happened to radom radio </nowiki> <br />
|author = <nowiki> Hooray </nowiki> <br />
|date = Jun 11th, 2016 <br />
|added = Jun 11th, 2016 <br />
|script_version = 0.40 <br />
}}</ref><br />
<br />
we should absolutely stop telling anyone to edit preferences.xml in FG_ROOT; any documentation or advice which says to should be changes ASAP. <ref>{{cite web<br />
|url = https://forum.flightgear.org/viewtopic.php?p=192632#p192632 <br />
|title = <nowiki> Re: NavCache:init failed:Sqlite error:Sqlite API abuse </nowiki> <br />
|author = <nowiki> zakalawe </nowiki> <br />
|date = Oct 26th, 2013 <br />
|added = Oct 26th, 2013 <br />
|script_version = 0.40 <br />
}}</ref><br />
<br />
As of 12/2017, the addon API is in the process of being significantly updated <ref>https://sourceforge.net/p/flightgear/mailman/message/36146017/</ref> <ref>https://sourceforge.net/p/flightgear/mailman/message/36150159/</ref> <ref>https://sourceforge.net/p/flightgear/mailman/message/36150444/</ref><br />
<br />
== Good to know ==<br />
this API does nothing more than loading a overlay XML into the property tree and starts a well known Nasal function<ref>{{cite web<br />
|url = https://forum.flightgear.org/viewtopic.php?p=314620#p314620 <br />
|title = <nowiki> Re: New Feature: Addon - "API" </nowiki> <br />
|author = <nowiki> Torsten </nowiki> <br />
|date = Jul 19th, 2017 <br />
|added = Jul 19th, 2017 <br />
|script_version = 0.40 <br />
}}</ref><br />
<br />
<br />
The new addon "API" lets you add a addon-config.xml to override the settings in defaults.xml.<br />
<br />
That easily allows to<br />
* Override key bindings (as in the spoken atc addon)<br />
* Add or override autopilots and property rules<br />
* And of course introduce XML-statemachines<br />
<br />
Unless your really want to add/change/remove those at runtime, this should cater for most use cases.<ref>{{cite web<br />
|url = https://forum.flightgear.org/viewtopic.php?p=314902#p314902 <br />
|title = <nowiki> Re: Spoken </nowiki> <br />
|author = <nowiki> Torsten </nowiki> <br />
|date = Jul 23rd, 2017 <br />
|added = Jul 23rd, 2017 <br />
|script_version = 0.40 <br />
}}</ref><br />
<br />
We have some instructions how to use SVN [[FGAddon|in our wiki]]. It covers mostly aircraft development but the workflow is pretty much the same for addons.<ref>{{cite web<br />
|url = https://forum.flightgear.org/viewtopic.php?p=314647#p314647 <br />
|title = <nowiki> Re: Spoken ATC </nowiki> <br />
|author = <nowiki> Torsten </nowiki> <br />
|date = Jul 19th, 2017 <br />
|added = Jul 19th, 2017 <br />
|script_version = 0.40 <br />
}}</ref><br />
<br />
<br />
* [[Property tree]]<br />
* [[PropertyList XML File]]<br />
* [[FlightGear configuration via XML]]<br />
* [[Properties persistent between sessions]]<br />
* [[FlightGear_configuration_via_XML#preferences.xml]]<br />
* [[Nasal]]<br />
<br />
== Overlays ==<br />
<br />
Torsten ended up using a fairly clever method to come up with this idea for implementing addon support via PropertyList-XML overlays loaded via the --config argument - however, that also brings with it all the power for conflicting with stuff you are loading using the same mechanism, and/or any defaults you have customied. So just be aware of how this works - and again, kudos to Torsten for coming up with such a fancy approach to make this work, despite people arguing for the better part of a decade that FlightGear would not have "proper addon/modding" support" ... ;-)<br />
With that being said, it would probably be a good idea to formalize the whole method and stop using tons of embedded Nasal, and instead document/favor the use of io.load_nasal() and/or io.include() to load Nasal code from the addon directory.<br />
Equally, such code should ideally wrap setlistener and settimer/maketimer to honor the /sim/signal "events" - but apart from that, it's a really clever idea<ref>{{cite web<br />
|url = https://forum.flightgear.org/viewtopic.php?p=295617#p295617 <br />
|title = <nowiki> Re: Whatever happened to radom radio chatter?? </nowiki> <br />
|author = <nowiki> Hooray </nowiki> <br />
|date = Sep 27th, 2016 <br />
|added = Sep 27th, 2016 <br />
|script_version = 0.40 <br />
}}</ref><br />
<br />
== API ==<br />
We now have a simple API to add addons to FlightGear without the need to mess around with FGData/Nasal or FGHome/Nasal directories. FlightGear now accepts the command line switch --addon=/path/to/some/addon (note: command line switch is just that: a command line switch - not an option to be entered into the launcher). fgfs (through options.cxx) takes care of <br />
* creating a property subtree under /addons for each addon<br />
* adding /path/to/some/addon/addon-config.xml as a config file (same as --config=/path/to/some/addon/addon-config.xml) <br />
* adding /path/to/some/addon to the list of allowed directories (same as --fg-aircraft=/path/to/some/addon) <br />
<br />
The addon may be installed anywhere on your hard disk and it needs at least two files: <br />
* addon-config.xml - a standard PropertyList to be used to populate or modify the property tree. (Same as to be used in --config=foo.xml) <br />
* addon-main.nas - the Nasal hook for the logic. This file needs a function called main() which will be called from the global addon initialier (FGData/addons.nas) <br />
<br />
Additional files: <br />
* addon-menubar-items.xml to add a menu for the addon (see chapter 6 in [https://sourceforge.net/p/flightgear/fgdata/ci/next/tree/Docs/README.add-ons README.add-ons])<br />
<br />
It is pretty simple but does it's job nicely. There is a /very/ simple Skeleton addon available to be used as a boilerplate here: https://sourceforge.net/p/flightgear/fgaddon/HEAD/tree/trunk/Addons/Skeleton/ As always: feedback is much appreciated. <ref>{{cite web<br />
|url = https://sourceforge.net/p/flightgear/mailman/message/35951307/ <br />
|title = <nowiki> [Flightgear-devel] Simple API for creating FlightGear addons/plugins </nowiki> <br />
|author = <nowiki> Torsten Dreyer </nowiki> <br />
|date = Jul 18th, 2017 <br />
|added = Jul 18th, 2017 <br />
|script_version = 0.40 <br />
}}</ref> <ref> {{cite web<br />
|url = https://forum.flightgear.org/viewtopic.php?p=314563#p314563 <br />
|title = <nowiki> New Feature: Addon - "API" </nowiki> <br />
|author = <nowiki> Torsten </nowiki> <br />
|date = Jul 18th, 2017 <br />
|added = Jul 18th, 2017 <br />
|script_version = 0.40 <br />
}}</ref><br />
<br />
Triggered by this addon, Torsten has overhauled the addon "API" to make it a little easier to use. <br />
<br />
Along with this, he added the spoken ATC feature as an addon to fgaddon: https://sourceforge.net/p/flightgear/fgaddon/HEAD/tree/trunk/Addons/SpokenATC/<br />
It is now very easy to use (given you have FlightGear compiled from git HEAD or use FlightGear 2017.3.x or later.<br />
<br />
you need at least these commits: <br />
<br />
FlightGear: https://sourceforge.net/p/flightgear/flightgear/ci/f6698a0b1f9e8c0791314aa09cbe1625927ef3ff/<br />
<br />
FGData: https://sourceforge.net/p/flightgear/fgdata/ci/5c1f4a69f131a55521050f4631b8fda42f050dd2/<br />
* Get the addon from https://sourceforge.net/p/flightgear/fgaddon/HEAD/tree/trunk/Addons/SpokenATC/<br />
* start flightgear with --addon=/your/full/path/to/SpokenATC<br />
<br />
done.<ref>{{cite web<br />
|url = https://forum.flightgear.org/viewtopic.php?p=314562#p314562 <br />
|title = <nowiki> Re: Spoken ATC </nowiki> <br />
|author = <nowiki> Torsten </nowiki> <br />
|date = Jul 18th, 2017 <br />
|added = Jul 18th, 2017 <br />
|script_version = 0.40 <br />
}}</ref><br />
<br />
<br />
== Implementation ==<br />
{{caution| This section needs an update}}<br />
{{note| see also [[Modules.nas]]}}<br />
For the time being, the addons.nas module will:<br />
* initialize addons configured with --addon=foobar command line switch<br />
* get the list of registered add-ons<br />
* load the addon-main.nas file of each add-on into namespace __addon[ADDON_ID]__<br />
* call function main() from every such addon-main.nas with the add-on ghost as argument.<br />
<br />
It depend on a change to options.cxx to accept an optional --addon argument <ref>https://sourceforge.net/p/flightgear/flightgear/ci/f6698a0b1f9e8c0791314aa09cbe1625927ef3ff/</ref><br />
<br />
== List of Addons ==<br />
You can find the official repository [https://sourceforge.net/p/flightgear/fgaddon/HEAD/tree/trunk/Addons/ on sourceforge.net (fgaddon/Addons)]<br />
* [[Landing Rate addon]] [https://forum.flightgear.org/viewtopic.php?f=6&t=33101&p=327787#p327787]<br />
* [[YASim Development Tools]] (by jsb)<br />
* [[Model Cockpit View]] <br />
* [[FGPlot]]<br />
* ATC Chatter (ported by Torsten) {{progressbar|100}}<br />
* [[Spoken ATC]] (ported by Torsten <ref>{{cite web<br />
|url = https://forum.flightgear.org/viewtopic.php?p=314095#p314095 <br />
|title = <nowiki> Re: </nowiki> <br />
|author = <nowiki> Torsten </nowiki> <br />
|date = Jul 10th, 2017 <br />
|added = Jul 10th, 2017 <br />
|script_version = 0.40 <br />
}}</ref>)<br />
* [[Oscilloscope_addon]] <br />
* [[PAR_instrument]] (Precision Approach Radar and Ground Controlled Approach)<br />
* [[Red Griffin ATC]] (Speaking Air Traffic Controller <ref>{{cite web<br />
|url = https://forum.flightgear.org/viewtopic.php?f=6&t=36755 <br />
|title = <nowiki> Re: </nowiki> <br />
|author = <nowiki> RedGriffin </nowiki> <br />
|date = Jan 6th, 2020 <br />
|added = Jan 6th, 2020 <br />
|script_version = 1.0.0 RC2 <br />
}}</ref>)<br />
* [[FGCamera]] (work in progress by Torsten <ref>{{cite web<br />
|url = https://forum.flightgear.org/viewtopic.php?p=314650#p314650 <br />
|title = <nowiki> Re: Alternative camera control </nowiki> <br />
|author = <nowiki> Torsten </nowiki> <br />
|date = Jul 19th, 2017 <br />
|added = Jul 19th, 2017 <br />
|script_version = 0.40 <br />
}}</ref> <ref>{{cite web<br />
|url = https://forum.flightgear.org/viewtopic.php?p=319804#p319804 <br />
|title = <nowiki> Re: New Feature: Addon - "API" </nowiki> <br />
|author = <nowiki> slawekmikula </nowiki> <br />
|date = Oct 2nd, 2017 <br />
|added = Oct 2nd, 2017 <br />
|script_version = 0.40 <br />
}}</ref>) <br />
* [[Ground Services]] (ported by ThomasS<ref>{{cite web<br />
|url = https://forum.flightgear.org/viewtopic.php?p=316400#p316400 <br />
|title = <nowiki> Re: </nowiki> <br />
|author = <nowiki> ThomasS </nowiki> <br />
|date = Aug 12th, 2017 <br />
|added = Aug 12th, 2017 <br />
|script_version = 0.40 <br />
}}</ref>)<br />
* cockpit-view (work in progress by wkitty42 <ref>{{cite web<br />
|url = https://forum.flightgear.org/viewtopic.php?p=316498#p316498 <br />
|title = <nowiki> Re: </nowiki> <br />
|author = <nowiki> wkitty42 </nowiki> <br />
|date = Aug 13th, 2017 <br />
|added = Aug 13th, 2017 <br />
|script_version = 0.40 <br />
}}</ref>)<br />
* [[A Failure Management Framework for FlightGear]]<br />
* [https://gitlab.com/mdanil/flightgear-hax mdanilov hax!]: landing evaluation and aircraft development tools, TerraSync toggler<br />
* [[FaceTrackNoIR]] (ported by HHS<ref>{{cite web<br />
|url = https://sourceforge.net/p/flightgear/mailman/message/36454826/<br />
|title = <nowiki> Re: </nowiki> <br />
|author = <nowiki> Unknown, HHS</nowiki><br />
|date = Nov 1th, 2018 <br />
}}</ref>)<br />
<br />
== Ideas ==<br />
<br />
=== Hooking into features using legacy OpenGL code ===<br />
{{See also|Unifying the 2D rendering backend via canvas}}<br />
In 09/2018, James suggested that legacy features using raw OpenGL calls (e.g. HUD/2D panels) could be easily replaced via scripted Canvas/Nasal solutions at the mere cost of providing a mechanism to hook into the legacy code implementing these features (namely, the HUD/instrumentation subsystems) <ref>https://sourceforge.net/p/flightgear/mailman/message/36399261/</ref> <ref>https://sourceforge.net/p/flightgear/mailman/message/36399261/</ref><br />
<br />
=== Catalog & Package Manager support ===<br />
<br />
if this works with more complex, pre-existing addons such as [[Bombable]], where the file layout is a replica of the old FGData layout, these types of mature addons might be better as SourceForge FlightGear sub-projects rather than being copied into FGAddon. Maybe the config file and nasal script could be used to automate the installation process ?<ref>{{cite web<br />
|url = https://sourceforge.net/p/flightgear/mailman/message/35953179/ <br />
|title = <nowiki> Re: [Flightgear-devel] Simple API for creating FlightGear<br />
addons/plugins </nowiki> <br />
|author = <nowiki> Edward d'Auvergne </nowiki> <br />
|date = Jul 19th, 2017 <br />
|added = Jul 19th, 2017 <br />
|script_version = 0.40 <br />
}}</ref><br />
The [[Bombable]] addon is one of the most popular addons out there, and a large number of aircraft in FGAddon have bombable support, so it is worth not forgetting about. Especially if the addon system one day becomes automated through a [[Catalog metadata|catalog.xml type system]]<ref>{{cite web<br />
|url = https://sourceforge.net/p/flightgear/mailman/message/35953650/ <br />
|title = <nowiki> Re: [Flightgear-devel] Simple API for creating FlightGear<br />
addons/plugins </nowiki> <br />
|author = <nowiki> Edward d'Auvergne </nowiki> <br />
|date = Jul 19th, 2017 <br />
|added = Jul 19th, 2017 <br />
|script_version = 0.40 <br />
}}</ref><br />
<br />
== References ==<br />
{{Appendix}}</div>Jsbhttps://wiki.flightgear.org/w/index.php?title=Nasal_library/debug&diff=124087Nasal library/debug2020-04-23T09:01:00Z<p>Jsb: /* Class Breakpoint (available since 2019.1) */</p>
<hr />
<div>{{Nasal Navigation|nocat=1}}<br />
This page contains documentation for the '''<code>debug</code> namespace''' in [[Nasal]]. This namespace provides various useful APIs for debugging Nasal code. The <code>debug</code> namespace is sourced from {{fgdata file|Nasal/debug.nas}}.<br />
<br />
== Functions ==<br />
=== attributes() ===<br />
{{Nasal doc<br />
|syntax = debug.attributes(p[, verbose[, color]]);<br />
|text = Returns a string showing the attributes of the node, in the form <code>('''type'''[, '''attr'''[, L'''num'''[, #'''refs''']])</code>. See the table below for explanation.<br />
<br />
{{{!}} class="wikitable"<br />
! Data !! Meaning<br />
{{!-}}<br />
{{!}} type {{!!}} Type of node as returned by {{func link|getType()|props|Node}}.<br />
{{!-}}<br />
{{!}} attr {{!!}} Various attribute flags, see {{func link|getAttribute()|props|Node}}. "r" = read protected, "w" = write protected, "R" = trace read, "W" = trace write, "A" = archive, "U" = userarchive, "P" = preserve, and "T" = tied.<br />
{{!-}}<br />
{{!}} num {{!!}} Number of listeners, if any.<br />
{{!-}}<br />
{{!}} refs {{!!}} This argument will be shown if '''verbose''' is true. Tells the number of references to the node. Note that all nodes have two references by default, but this tells the number of extra references.<br />
{{!}}}<br />
|param1 = p<br />
|param1text = Mandatory <code>props.Node</code> object.<br />
|param2 = verbose<br />
|param2text = Optional bool specifying whether to show the number of times the node is referenced. Defaults to 1 (true).<br />
|param3 = color<br />
|param3text = Optional bool specifying whether to output the string with an {{wikipedia|ANSI escape code#Colors|ANSI color code}}. Defaults to <code>'''nil'''</code>.<br />
|example1 = var node = props.globals.getNode("/sim/time/gmt");<br />
print(debug.attributes(node)); # prints "(STRING, AT, #1)" - string, archive, tied, 1 extra ref<br />
|example2 = var node = props.Node.new();<br />
node.setIntValue(12);<br />
node.setAttribute(17);<br />
print(debug.attributes(node)); # prints "(INT, wR)" - integer, write protected, trace read<br />
|example3 = var node = props.globals.getNode("/sim/signals/fdm-initialized");<br />
print(debug.attributes(node)); # prints "(BOOL, L6, #16)" - bool, 6 listeners, 16 extra refs<br />
}}<br />
<br />
=== backtrace() ===<br />
{{Nasal doc<br />
|syntax = debug.backtrace([desc]);<br />
|text = When called, this function prints the {{wikipedia|backtrace}}, also printing the local variables at each level.<br />
|param1 = desc<br />
|param1text = Optional extra description to add.<br />
|example1 = var myFunc = func(a){<br />
multiply(a, 2);<br />
}<br />
var multiply = func(x, y){<br />
debug.backtrace();<br />
}<br />
myFunc(2);<br />
|example2 = var myFunc = func(a){<br />
multiply(a, 2);<br />
}<br />
var multiply = func(x, y){<br />
debug.backtrace("multiply() function");<br />
}<br />
myFunc(2);<br />
}}<br />
<br />
=== bt() ===<br />
{{Nasal doc<br />
|syntax = debug.bt([desc]);<br />
|text = Shortcut for {{func link|backtrace()|page=this}}. See doc there<br />
}}<br />
<br />
=== benchmark() ===<br />
{{Nasal doc<br />
|syntax = debug.benchmark(label, fn[, repeat[, output]]);<br />
|text = Analyses the amount of time a function takes to run. If '''repeat''' is given and '''output''' is not, the time taken to repeat the function the given amount off times will be printed. If both are given, the return value of each repeat of the function will be returned in a vector '''output'''.<br />
|param1 = label<br />
|param1text = Label to add to the output.<br />
|param2 = fn<br />
|param2text = Function to call.<br />
|param3 = repeat<br />
|param3text = Optional integer specifying how many times the function is to be run. If not given, the function will be run once.<br />
|param4 = output<br />
|param4text = Optional vector that will be returned with the return value of each function.<br />
|example1 = var test = func(){<br />
var children = props.globals.getNode("sim").getChildren();<br />
var vec = [];<br />
foreach(var child; children){<br />
append(vec, child.getChildren());<br />
}<br />
return vec;<br />
}<br />
var result = debug.benchmark("test()", test);<br />
debug.dump(result);<br />
|example2 = var test = func(){<br />
var children = props.globals.getNode("sim").getChildren();<br />
var vec = [];<br />
foreach(var child; children){<br />
append(vec, child.getChildren());<br />
}<br />
return vec;<br />
}<br />
debug.benchmark("test()", test, 10);<br />
|example3 = var test = func(){<br />
var children = props.globals.getNode("sim").getChildren();<br />
var vec = [];<br />
foreach(var child; children){<br />
append(vec, child.getChildren());<br />
}<br />
return vec;<br />
}<br />
var result = debug.benchmark("test()", test, 10, []); # this example may cause a small pause<br />
debug.dump(result);<br />
}}<br />
<br />
=== benchmark_time() ===<br />
{{Nasal doc<br />
|syntax = debug.benchmark_time(fn[, repeat[, output]]);<br />
|text = Behaves in exactly the same way as {{func link|benchmark()|page=this}}, but returns the amount of time taken, rather than printing it out.<br />
|param1 = fn<br />
|param1text = Function to call.<br />
|param2 = repeat<br />
|param2text = Optional integer specifying how many times the function is to be run. If not given, the function will be run once.<br />
|param3 = output<br />
|param3text = Optional vector that will be returned with the return value of each function.<br />
|example1 = var test = func(){<br />
var children = props.globals.getNode("sim").getChildren();<br />
var vec = [];<br />
foreach(var child; children){<br />
append(vec, child.getChildren());<br />
}<br />
return vec;<br />
}<br />
print(debug.benchmark_time(test));<br />
|example2 = var test = func(){<br />
var children = props.globals.getNode("sim").getChildren();<br />
var vec = [];<br />
foreach(var child; children){<br />
append(vec, child.getChildren());<br />
}<br />
return vec;<br />
}<br />
print(debug.benchmark_time(test, 10));<br />
|example3 = var test = func(){<br />
return getprop("/sim/time/gmt");<br />
}<br />
print(debug.benchmark_time(test, 1000, var res = []));<br />
debug.dump(res);<br />
}}<br />
<br />
=== dump() ===<br />
{{Nasal doc<br />
|syntax = debug.dump([arg[, arg[, ...]]]);<br />
|text = Dumps the given arguments in the [[console]]. If no arguments of given, the local namespace will be dumped.<br />
|param1 = arg<br />
|param1text = Anything. Literally, anything. There may be as may arguments as you like. If more than one argument is given, each will be dumped separately with an index.<br />
|example1 = var x = nil;<br />
debug.dump(); # prints "{ x: nil, arg: [] }"<br />
|example2 = debug.dump("Hello, World!"); # 'Hello, World!'<br />
debug.dump(1234); # 1234<br />
debug.dump(nil); # nil<br />
debug.dump(["a", "b", "c"]); # ['a', 'b', 'c']<br />
debug.dump({ a: 1, b: 2, c: 3 }); # { a: 1, b: 2, c: 3 }<br />
debug.dump(props.Node.new()); # < = nil (NONE)><br />
debug.dump(airportinfo()); # <airport> <br />
debug.dump(func(a){ print(a); }); # <func><br />
|example3 = debug.dump( # dump, showing the index of each argument<br />
"Hello, World!",<br />
1234,<br />
nil,<br />
["a", "b", "c"],<br />
{ a: 1, b: 2, c: 3 },<br />
props.Node.new(),<br />
airportinfo(),<br />
func(a){ print(a); },<br />
);<br />
}}<br />
<br />
=== isnan() ===<br />
{{Nasal doc<br />
|syntax = debug.isnan(num);<br />
|text = Checks whether a number actually is a valid number and returns 0 (false) if it is and 1 (true) if it is not.<br />
|param1 = num<br />
|param1text = Number to check.<br />
|example1 = print(debug.isnan(1.234)); # prints "0" (1.234 is valid)<br />
|example2 = print(debug.isnan(1/0)); # prints "1" (division by 0 is undefined)<br />
}}<br />
<br />
=== local() ===<br />
{{Nasal doc<br />
|syntax = debug.local([frame]);<br />
|text = Prints and also returns a hash containing all the local variables.<br />
|param1 = frame<br />
|param1text = Optional integer specifying the frame. Corresponds to the argument given to {{func link|caller()}}.<br />
|example1 = var n = 12;<br />
debug.local(); # prints "{ n: 12, arg: [] }"<br />
|example2 = var sqr = func(a){<br />
debug.local(); # prints "{ a: 16 }"<br />
debug.local(1); # prints "{ sqr: <func>, arg: [] }"<br />
return a * a;<br />
}<br />
print(sqr(16));<br />
}}<br />
<br />
=== print_rank() ===<br />
{{Nasal doc<br />
|syntax = debug.print_rank(label, list, names);<br />
|text = Formats and prints results from {{func link|rank()|page=this}}. It will show them in order of fastest to slowest, the time taken in milliseconds, and what percentage of time each took relative to the slowest.<br />
|param1 = label<br />
|param1text = Label to add to the results header.<br />
|param2 = list<br />
|param2text = Results vector from {{func link|rank()|page=this}}.<br />
|param3 = names<br />
|param3text = Either a vector or hash that will supply the names of the functions. If a hash, it must have the structure of <code>{ name: func, ... }</code>. If a vector, its structure can be either <code><nowiki>[[name, func], ...]</nowiki></code> or <code><nowiki>[[func, name], ...]</nowiki></code><br />
|example1 = var getElev = func(){<br />
var pos = geo.aircraft_position();<br />
return geo.elevation(pos.lat(), pos.lon());<br />
}<br />
var getTime = func(){<br />
return getprop("/sim/time/gmt");<br />
}<br />
var list = [getElev, getTime];<br />
var result = debug.rank(list, 1000);<br />
var names = [["getElev", getElev], ["getTime", getTime]];<br />
#var names = [[getElev, "getElev"], [getTime, "getTime"]]; # other option<br />
#var names = { # third option<br />
# "getElev": getElev,<br />
# "getTime": getTime<br />
#};<br />
debug.print_rank("getElev() and getTime()", result, names);<br />
}}<br />
<br />
=== printerror() ===<br />
{{Nasal doc<br />
|syntax = debug.printerror(err);<br />
|text = Prints out errors from {{func link|call()}}.<br />
|param1 = err<br />
|param1text = Error vector from {{func link|call()}}.<br />
|example1 = call(func {<br />
print(math.ip); # should be math.pi, not math.ip<br />
}, [], nil, nil, var err = []);<br />
debug.printerror(err); # prints "no such member" error<br />
print("Hello again"); # this will still print<br />
}}<br />
<br />
=== propify() ===<br />
{{Nasal doc<br />
|syntax = debug.propify(p[, create]);<br />
|text = Attempts to turn its argument into a {{func link|Node|props}} object and return it. If it can't, it will return <code>'''nil</code>.<br />
|param1 = p<br />
|param1text = Thing to turn into a node. May be a <code>props.Node</code> object, a <code>prop</code> ghost, or a string pointing to a property.<br />
|param2 = create<br />
|param2text = If the above argument is a string, this specifies whether to create the property. Defaults to 0 (false).<br />
|example1 = var p = canvas._newCanvasGhost()._node_ghost;<br />
debug.dump(debug.propify(p));<br />
|example2 = var p = "/sim/time/gmt";<br />
debug.dump(debug.propify(p));<br />
|example3 = var p = "/demo";<br />
debug.dump(debug.propify(p, 1));<br />
|example4 = var p = func(){ print("Hello, World!"); }<br />
debug.dump(debug.propify(p)); # prints "nil"<br />
}}<br />
<br />
=== proptrace() ===<br />
{{Nasal doc<br />
|syntax = debug.proptrace([root[, frames]]);<br />
|text = Traces changes to a given part of the [[property tree]]. It flags up when a child node is added, the value of a node is set, and a node is deleted.<br />
|param1 = root<br />
|param1text = Optional <code>props.Node</code> object, a <code>prop</code> ghost, or a string, all pointing to a property. Defaults to the root of the property tree.<br />
|param2 = frames<br />
|param2text = Optional number of frames to trace for. Defaults to 2.<br />
|example1 = var n = props.globals.getNode("/demo", 1);<br />
debug.proptrace("/demo", 10);<br />
var c = n.addChild("foo")<br />
c.setIntValue(12)<br />
c.remove();<br />
}}<br />
<br />
=== rank() ===<br />
{{Nasal doc<br />
|syntax = debug.rank(list[, repeat]);<br />
|text = Benchmarks a given list of functions and returns them in a vector with them sorted from fastest to slowest. The result vector will be in the form of <code><nowiki>[[func, time], ...]</nowiki></code>. This result vector can be processed by {{func link|print_rank()|page=this}}.<br />
|param1 = list <br />
|param1text = Vector containing the functions to {{func link|benchmark()|page=this}}.<br />
|param2 = repeat<br />
|param2text = Number of times to repeat each function.<br />
|example1 = var getElev = func(){<br />
var pos = geo.aircraft_position();<br />
return geo.elevation(pos.lat(), pos.lon());<br />
}<br />
var getTime = func(){<br />
return getprop("/sim/time/gmt");<br />
}<br />
var list = [getElev, getTime];<br />
var result = debug.rank(list, 1000); # repeat 1,000 times<br />
debug.dump(result); # getTime() will be first<br />
}}<br />
<br />
=== string() ===<br />
{{Nasal doc<br />
|syntax = debug.string(o[, color]);<br />
|text = Converts its argument to a string and returns it.<br />
|param1 = o<br />
|param1text = Thing to return as a string.<br />
|param2 = color<br />
|param2text = Optional bool specifying whether to output the string with an {{wikipedia|ANSI escape code#Colors|ANSI color code}}. Defaults to <code>'''nil'''</code>.<br />
|example1 = print(debug.string(nil)); # prints "nil"<br />
print(debug.string(1.25)); # prints "1.25"<br />
print(debug.string("Hello, World!")); # prints 'Hello, World!'<br />
print(debug.string(["a", "b", "c"])); # prints "['a', 'b', 'c']"<br />
print(debug.string({ "a": 1, "b": 2, "c": 3 })); # prints "{ a: 1, c: 3, b: 2 }"<br />
print(debug.string(props.globals.getNode("/sim/time/gmt"))); # prints "</sim/time/gmt = '2016-12-04T18:58:18' (STRING, AT, #1)>" (value may be different)<br />
print(debug.string(airportinfo())); # prints "<airport>"<br />
print(debug.string(func(){})); # prints "<func>"<br />
}}<br />
<br />
=== tree() ===<br />
{{Nasal doc<br />
|syntax = debug.tree([n[, graph]);<br />
|text = Dumps all the nodes under a given property node into the console.<br />
|param1 = n<br />
|param1text = Optional node to dump from. May be a <code>props</code> ghost, string, or <code>props.Node</code> object. If not given, the default is the entire [[Property Tree]].<br />
|param2 = graph<br />
|param2text = Optional boolean specifying whether to graph the properties in "flat" mode or using spaces. Defaults to 1 (use spaces).<br />
|example1 = debug.tree("/position");<br />
|example2 = debug.tree("/position", 0);<br />
}}<br />
<br />
=== warn() ===<br />
{{Nasal doc<br />
|syntax = debug.warn(msg[, level]);<br />
|text = Similar to {{func link|die()}}, but the execution of the code does not stop.<br />
|param1 = msg<br />
|param1text = Message to print<br />
|param2 = level<br />
|param2text = Optional number of calling levels to omit<br />
|example1 = var check = func(pressure){<br />
if (pressure > 120){<br />
debug.warn("Pressure above 120 may cause system failure");<br />
}<br />
}<br />
check(130);<br />
|example2 = var A = func(a){<br />
B(a);<br />
}<br />
var B = func(b){<br />
if (b > 120){<br />
debug.warn(b ~ " is not within limits", 2);<br />
}<br />
}<br />
A(130);<br />
}}<br />
<br />
== Class Probe (available since 2019.1, upgraded 2020.1) ==<br />
The probe class can help to finde hot spots in Nasal code by collecting statistics. <br />
It is controlled via the property tree, so you can use it via the property browser.<br />
Data can be viewed / modified in the prop tree /_debug/nas/probe-<myLabel>/*<br />
<br />
{{note|New in 2020.1: <br />
Tracing support allows you to see the backtrace in the property tree after a hit. You can see the filenames and line numbers an how often the were hit.}}<br />
<br />
=== new() ===<br />
{{Nasal doc<br />
|syntax = debug.Probe.new(label, [class = "probe"]);<br />
|text = create a debug.Probe object<br />
|param1 = label<br />
|param1text = A string to name the probe, used in property path and in output.<br />
|param2 = class<br />
|param2text = Used to identify derived classes (see Breakpoint below), used in prop. path<br />
|example1 = var myProbe = debug.Probe.new("myLabel");<br />
}}<br />
<br />
=== enable() ===<br />
{{Nasal doc<br />
|syntax = myProbe.enable();<br />
|text = enable counting (and record start time<br />
}}<br />
=== disable() ===<br />
{{Nasal doc<br />
|syntax = myProbe.disable();<br />
|text = Disable counting, record stop time and generate stats.<br />
}}<br />
=== getHits() ===<br />
{{Nasal doc<br />
|syntax = myProbe.getHits();<br />
|text = Get total number of hits.<br />
}}<br />
<br />
=== addCounter() ===<br />
{{Nasal doc<br />
|syntax = var id = myProbe.addCounter();<br />
|text = Creates another counter, selectable as hit(id)<br />
}}<br />
<br />
=== hit() ===<br />
{{Nasal doc<br />
|syntax = myProbe.hit([counter_id=0][,callback = nil]);<br />
|text = Put this at the place in your code where you want to do the trace.<br />
|param1 = counter_id<br />
|param1text = Number of counter as returned by addCounter(), defaults to 0<br />
|param2 = callback<br />
|param2text = Optional: on hit call callback(hit_count);<br />
}}<br />
<br />
=== reset() ===<br />
{{Nasal doc<br />
|syntax = myProbe.reset();<br />
|text = Reset counter to zero and start time current time.<br />
}}<br />
<br />
=== setTimeout(seconds) ===<br />
{{Nasal doc<br />
|syntax = myProbe.setTimeout(seconds);<br />
|text = Set timeout. Next hit() after timeout will disable()<br />
}}<br />
<br />
=== enableTracing() ===<br />
{{Nasal doc<br />
|syntax = myProbe.enableTracing();<br />
|text = Enable tracing.<br />
}}<br />
=== disableTracing() ===<br />
{{Nasal doc<br />
|syntax = myProbe.disableTracing();<br />
|text = Disable tracing.<br />
}}<br />
== Class Breakpoint (available since 2019.1, upgraded 2020.1) ==<br />
The breakpoint class is for selective backtracing. It is derived from the debug.Probe class (see above) and thus supports the same methods unless overridden (see below).<br />
Putting a debug.backtrace() into some nasal code could flood the console / log especially if you do not (yet) know where that code is used.<br />
Using a breakpoint gives you better control when to do backtraces and how often via the property browser at runtime. <br />
You have to give "tokens" to you breakpoint which are consumed on each hit of the breakpoint, one per trace. No more tokens, no more traces done until you give more tokens.<br />
<br />
For example, if you wish to trace some API call, you can insert a breakpoint into the API code at the point of interest and monitor the number of hits in the property browser.<br />
<br />
After FG has started, you can open a property browser and go to /_debug/nas/bp-myLabel<br />
<br />
Set tokens to a positive integer to start tracing. Each hit will consume one token and perform one backtrace. <br />
To start tracing again, just set tokens again to the number of traces you wish to perform.<br />
<br />
{{note| Tracing support was added in version 2020.1, see Probe class above }}<br />
<br />
=== new() ===<br />
{{Nasal doc<br />
|syntax = Breakpoint.new(label [,dump_locals = 1][,skip_level=0]);<br />
|text = create a breakpoint object<br />
|param1 = label<br />
|param1text = A string to name the breakpoint, used in property path and in output.<br />
|param2 = dump_locals<br />
|param2text = 0 or 1, passed to backtrace to control printing of namespaces (variables). Warning: dumping big namespaces can cause a stack overflow and crash the nasal script.<br />
|param3 = skip_level<br />
|param3text = passed to debug.backtrace to skip the first n levels in the bt.<br />
|example1 = var myBP = debug.Breakpoint.new("myLabel", 0);<br />
}}<br />
<br />
=== enable() ===<br />
{{Nasal doc<br />
|syntax = myBP.enable([tokens = 1]);<br />
|text = Start tracing <br />
|param1 = tokens<br />
|param1text = Number of traces allowed; each hit will consume one token, tokens < 1 means no more traces. Tokens can be also set via the property browser at runtime.<br />
}}<br />
<br />
=== hit() ===<br />
{{Nasal doc<br />
|syntax = myBP.hit([callback]);<br />
|text = Put this at the place in your code where you want to do the trace.<br />
|param1 = callback<br />
|param1text = Optional: function to call instread of debug.backtrace. It will be called as callback(number_of_hits, remaining_tokens);<br />
<br />
}}<br />
<br />
{{Nasal namespaces}}</div>Jsbhttps://wiki.flightgear.org/w/index.php?title=Nasal_library&diff=123943Nasal library2020-04-18T22:12:30Z<p>Jsb: /* Extension functions new in FG 2020.1 */</p>
<hr />
<div>{{Nasal Navigation|nocat=1}}<br />
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:<br />
* {{flightgear file|src/Scripting}}<br />
* {{simgear file|simgear/nasal}}<br />
<br />
All these functions and variables are in the global namespace, that is, they are directly accessible (e.g., one can call <syntaxhighlight lang="nasal" inline>magvar()</syntaxhighlight> instead of <syntaxhighlight lang="nasal" inline>namespace.magvar()</syntaxhighlight>). However, if a namespace must be used, <code>globals</code> is the correct namespace, but using it is not recommended. For a more complete explanation, see [[Nasal Namespaces in-depth]].<br />
<br />
{{tip|Copy & paste the examples into your [[Nasal Console]] and execute them to see what they do.|width=70%}}<br />
<br />
== Core library functions ==<br />
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:<br />
* http://plausible.org/nasal/lib.html ([http://web.archive.org/web/20101010094553/http://plausible.org/nasal/lib.html archive])<br />
* {{simgear file|simgear/nasal/lib.c}} ([http://sourceforge.net/p/flightgear/simgear/ci/next/log/?path=/simgear/nasal/lib.c history])<br />
<br />
=== append() ===<br />
{{Nasal doc<br />
|syntax = append(vector, element[, element[, ...]]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=42|t=Source}}<br />
|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.<br />
|param1 = vector<br />
|param1text = The vector to which the arguments will be appended.<br />
|param2 = element<br />
|param2text = An element to be added to the vector.<br />
|example1 = <br />
var vector = [1, 2, 3]; # Initialize the vector<br />
append(vector, 4); # Append the number 4 to the end of the vector<br />
debug.dump(vector); # Print the contents of the vector<br />
|example2 = <br />
var vector = [1, 2, 3]; # Initialize the vector<br />
append(vector, 4, 5, 6); # Append the numbers 4, 5, and 6 to the end of the vector<br />
debug.dump(vector); # Print the contents of the vector<br />
}}<br />
<br />
=== bind() ===<br />
{{Nasal doc<br />
|syntax = bind(function, locals[, outer_scope]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=502|t=Source}}<br />
|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.<br />
|param1 = function<br />
|param1text = Function to evaluate.<br />
|param2 = locals<br />
|param2text = Hash containing values that will become the namespace (first closure) for the function.<br />
|param3 = outer_scope<br />
|param3text = Optional function which is bound to the next closure. This can be bound to yet another, making a linked list.<br />
|example1 = # This is a namespace/hash with a single member, named "key," which is initialized to 12 <br />
var Namespace = {<br />
key: 12<br />
};<br />
<br />
# This is different namespace/hash containing a function<br />
# dividing a variable "key" (which is unavailable/nil in this namespace) by 2<br />
var AnotherNamespace = {<br />
ret: func {<br />
key /= 2;<br />
}<br />
};<br />
<br />
# To see that key is not available, try to call AnotherNamespace.ret() first<br />
call(AnotherNamespace.ret, [], nil, nil, var errors = []);<br />
if(size(errors)){<br />
print("Key could not be divided/resolved!");<br />
debug.printerror(errors);<br />
}<br />
<br />
# Associate the AnotherNamespace.ret() function with the first namespace<br />
# so that "key" is now available<br />
var function = bind(AnotherNamespace.ret, Namespace);<br />
<br />
# Invoke the new function<br />
function();<br />
<br />
# Print out the value of Namespace.key<br />
# It was changed to 12 from 6 by AnotherNamespace.ret()<br />
print(Namespace.key);<br />
}}<br />
<br />
=== call() ===<br />
{{Nasal doc<br />
|syntax = call(func[, args[, me[, locals[, error]]]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=247|t=Source}}<br />
|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. <br />
|param1 = func<br />
|param1text = Function to execute.<br />
|param2 = args<br />
|param2text = Vector containing arguments to give to the called function.<br />
|param3 = me<br />
|param3text = <code>'''me'''</code> reference for the function call (i.e., for method calls). If given, this will override any <code>'''me'''</code> value existing in the namespace (locals argument).<br />
|param4 = locals<br />
|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.<br />
|param5 = error<br />
|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}}.<br />
|example1 =<br />
# prints "Called from call()"<br />
call(func {<br />
print("Called from call()");<br />
});<br />
|example2 =<br />
# prints "a = 1 : b = 2<br />
call(func(a, b){<br />
print("a = ", a, " : b = ", b);<br />
},<br />
[1, 2]<br />
);<br />
|example3 =<br />
var Hash = {<br />
new: func {<br />
var m = { parents: [Hash] };<br />
<br />
m.el1 = "string1";<br />
m.el2 = "string2";<br />
<br />
return m;<br />
}<br />
};<br />
<br />
# prints "me.el1 = string1", then "me.el2 = string2" on the next line<br />
call(func(a, b){ <br />
print("me.el", a, " = ", me["el" ~ a]); <br />
print("me.el", b, " = ", me["el" ~ b]);<br />
},<br />
[1, 2],<br />
Hash.new()<br />
);<br />
|example4 =<br />
# prints the value of math.pi<br />
call(func {<br />
print(pi);<br />
}, nil, nil, <br />
math<br />
);<br />
|example5 =<br />
call(func {<br />
print(math.ip); # math.ip doesn't exist<br />
}, nil, nil, nil,<br />
var errs = []<br />
);<br />
debug.printerror(errs); # The error is caught and printed using debug.printerror()<br />
}}<br />
<br />
=== caller() ===<br />
{{Nasal doc<br />
|syntax = caller([level]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=404|t=Source}}<br />
|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.<br />
<br />
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. <br />
|param1 = level<br />
|param1text = Optional integer specifying the stack level to return a result from. Defaults to 1 (i.e. the caller of the currently executing function).<br />
|example1 =<br />
var myFunction = func(a, b){<br />
debug.dump(caller(0)[0]); # prints a hash of local variables, including arguments a and b<br />
return 2 * 2;<br />
};<br />
<br />
print("2 x 2 = ", myFunction(2, 2));<br />
|example2 =<br />
var get_arg_value = func(){<br />
print("Argument to myFunc = ", caller(1)[0]['a']); # print the value of myFunc's single argument, using caller()<br />
};<br />
<br />
var myFunc = func(a){<br />
get_arg_value();<br />
};<br />
<br />
myFunc(3);<br />
|example3text = This is a real example taken from {{fgdata file|Nasal/canvas/MapStructure.nas}}. Function <code>r()</code> (above the TODOs) returns a hash with the key/value pairs as per its arguments. For example, something like this is returned: <code>{ name: "<name>", vis: 1, zindex: nil }</code>.<br />
|example3 =<br />
var MapStructure_selfTest = func() {<br />
var temp = {};<br />
temp.dlg = canvas.Window.new([600,400],"dialog");<br />
temp.canvas = temp.dlg.createCanvas().setColorBackground(1,1,1,0.5);<br />
temp.root = temp.canvas.createGroup();<br />
var TestMap = temp.root.createChild("map");<br />
TestMap.setController("Aircraft position");<br />
TestMap.setRange(25); # TODO: implement zooming/panning via mouse/wheel here, for lack of buttons :-/<br />
TestMap.setTranslation(<br />
temp.canvas.get("view[0]")/2,<br />
temp.canvas.get("view[1]")/2<br />
);<br />
var r = func(name,vis=1,zindex=nil) return caller(0)[0];<br />
# TODO: we'll need some z-indexing here, right now it's just random<br />
# TODO: use foreach/keys to show all layers in this case by traversing SymbolLayer.registry direclty ?<br />
# maybe encode implicit z-indexing for each lcontroller ctor call ? - i.e. preferred above/below order ?<br />
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'), ] ) <br />
TestMap.addLayer(factory: canvas.SymbolLayer, type_arg: type.name,<br />
visible: type.vis, priority: type.zindex,<br />
);<br />
}; # MapStructure_selfTest<br />
}}<br />
<br />
=== chr() ===<br />
{{Nasal doc<br />
|syntax = chr(code);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=175|t=Source}}<br />
|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 (<code>"string"</code>) supports control chracters. Strings in single quotes (<code>'string'</code>) do not.}}<br />
{{{!}} class="wikitable"<br />
! Code !! Name !! Equivalent to<br />
{{!-}}<br />
{{!}} 10 {{!!}} {{Wikipedia|Newline}} {{!!}} <code>\n</code><br />
{{!-}}<br />
{{!}} 9 {{!!}} {{Wikipedia|Tab key#Tab characters|Horizontal tab}} {{!!}} <code>\t</code><br />
{{!-}}<br />
{{!}} 13 {{!!}} {{Wikipedia|Carriage return}} {{!!}} <code>\r</code><br />
{{!}}}<br />
|param1 = code<br />
|param1text = Integer character code for the desired glyph.<br />
|example1 = print("Code 65 = ", chr(65)); # prints "Code 65 = A"<br />
|example2text = This example displays all of the characters in a list, in the format <code>Code '''n''' = >'''char'''<</code>, '''n''' being the index, and '''char''' being the character.<br />
|example2 =<br />
for(var i = 0; i <= 255; i += 1){<br />
print("Code ", i, " = >", chr(i), "<");<br />
}<br />
}}<br />
<br />
=== closure() ===<br />
{{Nasal doc<br />
|syntax = closure(func[, level]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=421|t=Source}}<br />
|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'''. <br />
|param1 = func<br />
|param1text = Function to evaluate.<br />
|param2 = level<br />
|param2text = Optional integer specifying the scope level. Defaults to 0 (the namespace of '''func''').<br />
|example1 =<br />
var get_math_e = func {<br />
return e; # return the value of math.e<br />
}<br />
<br />
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<br />
debug.dump(closure(myFunction)); # print the namespace of get_math_e<br />
<br />
print(myFunction());<br />
}}<br />
<br />
=== cmp() ===<br />
{{Nasal doc<br />
|syntax = cmp(a, b);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=112|t=Source}}<br />
|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'''. <br />
|param1 = a<br />
|param1text = First string argument for comparison.<br />
|param2 = b<br />
|param2text = Second string argument for comparison.<br />
|example1 = print(cmp("1", "two")); # prints -1<br />
|example2 = print(cmp("string", "string")); # prints 0<br />
|example3 = print(cmp("one", "2")); # prints 1<br />
|example4 = print(cmp("string1", "string2")); # prints -1<br />
}}<br />
<br />
=== compile() ===<br />
{{Nasal doc<br />
|syntax = compile(code[, filename]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=220|t=Source}}<br />
|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'''.<br />
|param1 = code<br />
|param1text = String containing Nasal code to be compiled.<br />
|param2 = filename<br />
|param2text = Optional string used for error messages/logging. Defaults to <code><compile></code><br />
|example1 = <br />
var myCode = 'print("hello");';<br />
var helloFunc = compile(myCode, "myCode");<br />
helloFunc();<br />
|example2text = <code>compile</code> 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 <tt>''[[$FG_ROOT]]/gui/dialogs/test.xml''</tt>.<br />
<syntaxhighlight lang="xml"><br />
<?xml version="1.0"?><br />
<br />
<PropertyList><br />
<br />
<nasal><![CDATA[<br />
print("You have FlightGear v", getprop("/sim/version/flightgear"));<br />
]]></nasal><br />
<br />
</PropertyList><br />
</syntaxhighlight><br />
Now, start FlightGear and execute this code in the [[Nasal Console]].<br />
|example2 =<br />
# Build the path<br />
var FGRoot = getprop("/sim/fg-root");<br />
var filename = "/gui/dialogs/test.xml";<br />
var path = FGRoot ~ filename;<br />
<br />
var blob = io.read_properties(path);<br />
var script = blob.getValues().nasal; # Get the nasal string<br />
<br />
# Compile the script. We're passing the filename here for better runtime diagnostics <br />
var code = call(func {<br />
compile(script, filename);<br />
}, nil, nil, var compilation_errors = []);<br />
<br />
if(size(compilation_errors)){<br />
die("Error compiling code in: " ~ filename);<br />
}<br />
<br />
# Invoke the compiled script, equivalent to code(); <br />
# We're using call() here to detect errors:<br />
call(code, [], nil, nil, var runtime_errors = []);<br />
<br />
if(size(runtime_errors)){<br />
die("Error calling code compiled loaded from: " ~ filename);<br />
}<br />
}}<br />
<br />
=== contains() ===<br />
{{Nasal doc<br />
|syntax = contains(hash, key);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=184|t=Source}}<br />
|text = Returns 1 (True) if the hash contains the specified key, or 0 (False) if not.<br />
|param1 = hash<br />
|param1text = The hash to search in.<br />
|param2 = key<br />
|param2text = The scalar to be searched for, contained as a key in the hash.<br />
|example1 =<br />
# Initialize a hash<br />
var hash = {<br />
element: "value"<br />
};<br />
print(contains(hash, "element") ? "Yes" : "No"); # This will print "Yes"<br />
|example2 =<br />
# Initialize a hash<br />
var hash = {<br />
element: "value"<br />
};<br />
print(contains(hash, "element2") ? "Yes" : "No"); # This will print "No"<br />
}}<br />
<br />
=== delete() ===<br />
{{Nasal doc<br />
|syntax = delete(hash, key);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=83|t=Source}}<br />
|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 <code>'''nil'''</code> 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.<br />
|param1 = hash<br />
|param1text = The hash from which to delete the key.<br />
|param2 = key<br />
|param2text = The scalar to be deleted, contained as a key in the hash.<br />
|example1 =<br />
# Initialize the hash<br />
var hash = {<br />
element1: "value1",<br />
element2: "value2"<br />
};<br />
delete(hash, "element1"); # Delete element1<br />
debug.dump(hash); # prints the hash, which is now minus element1<br />
}}<br />
<br />
=== die() ===<br />
{{Nasal doc<br />
|syntax = die(error);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=288|t=Source}}<br />
|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()}}.<br />
|param1 = error<br />
|param1text = String describing the error.<br />
:{{inote|This parameter is technically optional, but it is highly recommended to use it.}}<br />
|example1 = <br />
print("Will print");<br />
die("Don't go any further!"); <br />
print("Won't print"); # Will not be printed because die() stops the process<br />
}}<br />
<br />
=== find() ===<br />
{{Nasal doc<br />
|syntax = find(needle, haystack);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=450|t=Source}}<br />
|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.<br />
|param1 = needle<br />
|param1text = String to search for.<br />
|param2 = haystack<br />
|param2text = String to search in.<br />
|example1 = print(find("c", "abcdef")); # prints 2<br />
|example2 = print(find("x", "abcdef")); # prints -1<br />
|example3 = print(find("cd", "abcdef")); # prints 2<br />
}}<br />
<br />
=== ghosttype() ===<br />
{{Nasal doc<br />
|syntax = ghosttype(ghost);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=207|t=Source}}<br />
|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++ <code>naGhostType</code> 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.<br />
|param1 = ghost<br />
|param1text = Ghost to return a description for.<br />
|example1 = print(ghosttype(airportinfo())); # prints "airport"<br />
}}<br />
<br />
=== id() ===<br />
{{Nasal doc<br />
|syntax = id(object);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=570|t=Source}}<br />
|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 <code>'''<type>''':'''<id>'''</code>, where '''<type>''' is the type of object, and '''<id>''' is the ID.<br />
|param1 = object<br />
|param1text = Can be either of a string, a vector, a hash, a code, a function, or a ghost.<br />
|example1 = print(id("A")); # prints "str:000000001624A590"<br />
}}<br />
<br />
=== int() ===<br />
{{Nasal doc<br />
|syntax = int(number);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=90|t=Source}}<br />
|text = Returns the integer part of the numeric value of the single argument, or <code>'''nil'''</code> if none exists.<br />
|param1 = number<br />
|param1text = Number or string with just a number in it to return an integer from.<br />
|example1 = print(int(23)); # prints "23"<br />
|example2 = print(int(23.123)); # prints "23"<br />
|example3 = debug.dump(int("string")); # prints "nil"<br />
}}<br />
<br />
=== keys() ===<br />
{{Nasal doc<br />
|syntax = keys(hash);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=33|t=Source}}<br />
|text = Returns a vector containing the list of keys found in the single hash argument. <br />
|param1 = hash<br />
|param1text = The hash to return the keys from.<br />
|example1 = <br />
# Initialize a hash<br />
var hash = {<br />
element1: "value",<br />
element2: "value"<br />
};<br />
debug.dump(keys(hash)); # print the vector<br />
}}<br />
<br />
=== left() ===<br />
{{Nasal doc<br />
|syntax = left(string, length);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=149|t=Source}}<br />
|version = 2.12<br />
|commit = {{simgear commit|bd7163|t=commit}}<br />
|text = Returns a substring of '''string''', starting from the left.<br />
|param1 = string<br />
|param1text = String to return part of.<br />
|param2 = length<br />
|param2text = Integer specifying the length of the substring to return.<br />
|example1 = print(left("string", 2)); # prints "st"<br />
}}<br />
<br />
=== num() ===<br />
{{Nasal doc<br />
|syntax = num(number);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=102|t=Source}}<br />
|text = Returns the numerical value of the single string argument, or <code>'''nil'''</code> if none exists. <br />
|param1 = number<br />
|param1text = String with just a number in it to return a number from.<br />
|example1 = print(num("23")); # prints "23"<br />
|example2 = print(num("23.123")); # prints "23.123"<br />
|example3 = debug.dump(num("string")); # prints "nil"<br />
}}<br />
<br />
=== pop() ===<br />
{{Nasal doc<br />
|syntax = pop(vector);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=50|t=Source}}<br />
|text = Removes and returns the last element of the single vector argument, or <code>'''nil'''</code> if the vector is empty. <br />
|param1 = vector<br />
|param1text = Vector to remove an element from.<br />
|example1 = <br />
var vector = [1, 2, 3];<br />
pop(vector);<br />
debug.dump(vector); # prints "[1, 2]"<br />
|example2 = <br />
var vector = [1, 2, 3];<br />
debug.dump(pop(vector)); # prints "3"<br />
|example3 = <br />
var vector = [];<br />
debug.dump(pop(vector)); # prints "nil"<br />
}}<br />
<br />
=== right() ===<br />
{{Nasal doc<br />
|syntax = right(string, length);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=161|t=Source}}<br />
|version = 2.12<br />
|commit = {{simgear commit|bd7163|t=commit}}<br />
|text = Returns a substring of '''string''', starting from the right.<br />
|param1 = string<br />
|param1text = String to return part of.<br />
|param2 = length<br />
|param2text = Integer specifying the length of the substring to return.<br />
|example1 = print(right("string", 2)); # prints "ng"<br />
}}<br />
<br />
=== setsize() ===<br />
{{Nasal doc<br />
|syntax = setsize(vector, size);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=56|t=Source}}<br />
|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 <code>'''nil'''</code> entries. Returns the vector operated upon. <br />
|param1 = vector<br />
|param1text = The vector to be operated on.<br />
|param2 = size<br />
|param2text = The desired size of the vector in number of entries.<br />
|example1 = <br />
var vector = [1, 2, 3]; # Initialize a vector<br />
setsize(vector, 4);<br />
debug.dump(vector); # print the vector<br />
|example2 = <br />
var vector = [1, 2, 3]; # Initialize a vector<br />
setsize(vector, 2);<br />
debug.dump(vector); # print the vector<br />
}}<br />
<br />
=== size() ===<br />
{{Nasal doc<br />
|syntax = size(object);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=23|t=Source}}<br />
|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 <code>'''nil'''</code> or a number, this error will be thrown: <code>object has no size()</code>.<br />
|param1 = object<br />
|param1text = Object to find the size of. Must be a string, a vector or a hash.<br />
|example1 = <br />
var string = "string";<br />
print(size(string)); # prints "6"<br />
|example2 =<br />
var vector = [1, 2, 3];<br />
print(size(vector)); # prints "3"<br />
|example3 =<br />
var hash = {<br />
element1: "value1",<br />
element2: "value2",<br />
element3: "value3"<br />
};<br />
print(size(hash)); # prints "3"<br />
}}<br />
<br />
=== sort() ===<br />
{{Nasal doc<br />
|syntax = sort(vector, function);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=542|t=Source}}<br />
|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/}}, <code>sort()</code> 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.<br />
|param1 = vector<br />
|param1text = Input vector to sort.<br />
|param2 = function<br />
|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.<br />
{{{!}} class="wikitable"<br />
! Return value !! Meaning<br />
{{!-}}<br />
{{!}} less than 0 {{!!}} first argument should go before second argument<br />
{{!-}}<br />
{{!}} 0 {{!!}} first argument equals second argument<br />
{{!-}}<br />
{{!}} greater than 0 {{!!}} first argument should go after second argument<br />
{{!}}}<br />
<br />
|example1text = This example sorts elements from smallest to greatest.<br />
|example1 = <br />
var sort_rules = func(a, b){<br />
if(a < b){<br />
return -1; # A should before b in the returned vector<br />
}elsif(a == b){<br />
return 0; # A is equivalent to b <br />
}else{<br />
return 1; # A should after b in the returned vector<br />
}<br />
}<br />
debug.dump(sort([3, 2, 5, 6, 4, 1], sort_rules)); # prints "[1, 2, 3, 4, 5, 6]"<br />
|example2text = This example sorts elements from greatest to smallest.<br />
|example2 = <br />
# Outputs the elements in reverse order (greatest to smallest)<br />
var sort_rules = func(a, b){<br />
if(a < b){<br />
return 1; # -1 in the above example<br />
}elsif(a == b){<br />
return 0;<br />
}else{<br />
return -1; # 1 in the above example<br />
}<br />
}<br />
debug.dump(sort([3, 2, 5, 6, 4, 1], sort_rules)); # prints "[6, 5, 4, 3, 2, 1]"<br />
|example3text = This example sorts a vector of strings (runways for example) from smallest to greatest.<br />
|example3 = <br />
var runways = ["09R","27R","26L","09L","15"];<br />
var rwy = sort(runways,func(a,b) cmp(a,b));<br />
debug.dump(rwy); # prints ['09L','09R','15','26L','27R']<br />
}}<br />
<br />
=== split() ===<br />
{{Nasal doc<br />
|syntax = split(delimiter, string);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=460|t=Source}}<br />
|text = Splits the input string into a vector of substrings bounded by occurrences of the delimiter substring.<br />
|param1 = delimiter<br />
|param1text = String that will split the substrings in the returned vector.<br />
|param2 = string<br />
|param2text = String to split up.<br />
|example1 = debug.dump(split("cd", "abcdef")); # prints "['ab', 'ef']"<br />
|example2 = debug.dump(split(".", "3.2.0")); # prints "[3, 2, 0]"<br />
|example3 = debug.dump(split("/", "path/to/file")); # prints "['path', 'to', 'file']"<br />
}}<br />
<br />
=== sprintf() ===<br />
{{Nasal doc<br />
|syntax = sprintf(format[, arg[, arg, [...]]]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=355|t=Source}}<br />
|text = Creates and returns a string formatted using ANSI C {{func link|vsnprintf()|link=http://en.cppreference.com/w/c/io/vfprintf}} <ref><br />
{{Cite web<br />
|url = http://sourceforge.net/p/flightgear/simgear/ci/next/tree/simgear/nasal/lib.c#l308<br />
|title = fgdata/simgear/simgear/nasal/lib.c, line 308<br />
|accessdate = October 2015<br />
}}<br />
</ref>. Below is a table of supported format specifiers.<br />
{{{!}} class="wikitable" width="75%"<br />
{{!}}+ %[flags][width][.precision]specifier<br />
! colspan="2" {{!}} Flags<br />
{{!-}}<br />
! Flag !! Output<br />
{{!-}}<br />
{{!}} <code>+</code> {{!!}} 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.<br />
{{!-}}<br />
{{!}} ''space'' {{!!}} Prefixes non-signed numbers with a space.<br />
{{!-}}<br />
{{!}} <code>-</code> {{!!}} Left-align the output of this placeholder (the default is to right-align the output) when the width option is specified.<br />
{{!-}}<br />
{{!}} <code>0</code> {{!!}} Use 0 instead of spaces to pad a field when the width option is specified.<br />
{{!-}}<br />
{{!}} <code>#</code> {{!!}} Used with <code>o</code>, <code>x</code> or <code>X</code> specifiers the value is preceded with <tt>0</tt>, <tt>0x</tt> or <tt>0X</tt> respectively for values different than zero. Used with <code>e</code>, <code>E</code> and <code>f</code>, 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 <code>g</code> or <code>G</code> the result is the same as with <code>e</code> or <code>E</code> but trailing zeros are not removed.<br />
{{!-}}<br />
! colspan="2" {{!}} Width<br />
{{!-}}<br />
{{!}} colspan="2" {{!}} Integer specifying the minimum number of characters to be returned. This includes the decimal point and decimal fraction.<br />
{{!-}}<br />
! colspan="2" {{!}} Precision<br />
{{!-}}<br />
{{!}} colspan="2" {{!}} Integer preceded by a dot specifying the number of decimal places to be written.<br />
{{!-}}<br />
! colspan="2" {{!}} Specifiers<br />
{{!-}}<br />
! Specifier !! Output<br />
{{!-}}<br />
{{!}} <code>d</code>, <code>i</code> {{!!}} Signed decimal number.<br />
{{!-}}<br />
{{!}} <code>s</code> {{!!}} A string<br />
{{!-}}<br />
{{!}} <code>%</code> {{!!}} Percent (%) character.<br />
{{!-}}<br />
{{!}} <code>c</code> {{!!}} 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.<br />
{{!-}}<br />
{{!}} <code>o</code> {{!!}} Unsigned integer as an octal number.<br />
{{!-}}<br />
{{!}} <code>u</code> {{!!}} Unsigned decimal integer.<br />
{{!-}}<br />
{{!}} <code>x</code>, <code>X</code> {{!!}} Unsigned integer as a hexadecimal number. If <code>x</code> is used, any letters in the number are lowercase, while <code>X</code> gives uppercase.<br />
{{!-}}<br />
{{!}} <code>e</code>, <code>E</code> {{!!}} Double value in scientific notation (i.e., ''[-]ddd.ddd'''e'''[+/-]ddd''), with an exponent being denoted by <tt>e</tt> or <tt>E</tt> depending on whether an upper or lowercase is used respectively.<br />
{{!-}}<br />
{{!}} <code>f</code> {{!!}} Floating-point number, in fixed decimal notation, by default with 6 decimal places.<br />
{{!-}}<br />
{{!}} <code>F</code> {{!!}} Appears to be available<ref><br />
{{Cite web<br />
|url = http://sourceforge.net/p/flightgear/simgear/ci/next/tree/simgear/nasal/lib.c#l389<br />
|title = fgdata/simgear/simgear/nasal/lib.c, line 389<br />
|accessdate = October 2015<br />
}}<br />
</ref>, but doesn't work.<br />
{{!-}}<br />
{{!}} <code>g</code>, <code>G</code> {{!!}} Double in either normal or exponential notation, whichever is more appropriate for its magnitude. <code>g</code> uses lower-case letters, <code>G</code> 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.<br />
{{!}}}<br />
<br />
|param1 = format<br />
|param1text = String specifying the format. Can be used with or without a format specifiers. See below for examples.<br />
|param2 = arg<br />
|param2text = Argument specifying a value to replace a format placeholder (such as <code>%d</code>) in the format string. Not required if there are no format specifiers.<br />
<br />
|example1 = print(sprintf("%i", 54)); # prints "54"<br />
|example2 = print(sprintf("Pi = %+.10f", math.pi)); # prints "Pi = +3.1415926536"<br />
|example3 = <br />
print(sprintf("%6d", 23)); # prints " 23"<br />
print(sprintf("%06d", 23)); # prints "000023"<br />
|example4 =<br />
var FGVer = getprop("/sim/version/flightgear");<br />
print(sprintf("You have FlightGear v%s", FGVer)); # prints "You have FlightGear v<your version>"<br />
|example5 = <br />
print(sprintf("Hexadecimal 100000 = %X", 100000)); # prints "Hexadecimal 100000 = 186A0"<br />
print(sprintf("Hexadecimal 100000 = %x", 100000)); # prints "Hexadecimal 100000 = 186a0"<br />
|example6 = print(sprintf("Code 65 is %c", 65)); # prints "Code 65 is A"<br />
|example7 = <br />
print(sprintf("%e", 54)); # prints "5.400000e+001"<br />
print(sprintf("%E", 54)); # prints "5.400000E+001"<br />
|example8 = print(sprintf("%o", 54)); # prints "66"<br />
|example9 = print(sprintf("50%% of 100 is %i", 100 / 2)); # prints "50% of 100 is 50"<br />
|example10 =<br />
print(sprintf("%.2f", 1.4)); #prints "1.40"<br />
print(sprintf("%.1f", 1.4)); #prints "1.4"<br />
print(sprintf("% 4.1f", 1.4)); #prints " 1.4"<br />
print(sprintf("%04.1f", 1.4)); #prints "01.4"<br />
print(sprintf("% 6.1f", 1.4)); #prints " 1.4"<br />
print(sprintf("%06.1f", 1.4)); #prints "0001.4"<br />
}}<br />
<br />
=== streq() ===<br />
{{Nasal doc<br />
|syntax = streq(a, b);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=129|t=Source}}<br />
|text = Tests the string values of the two arguments for equality. This function is needed because the <code>'''=='''</code> 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.}}<br />
|param1 = a<br />
|param1text = First argument for testing equality.<br />
|param2 = b<br />
|param2text = Second argument for testing equality.<br />
|example1 = print(streq("0", "0")); # prints "1" (True)<br />
|example2 = <br />
print(0 == 0.0); # prints "1" (True)<br />
print(streq("0", "0.0")); # prints "0" (False)<br />
}}<br />
<br />
=== substr() ===<br />
{{Nasal doc<br />
|syntax = substr(string, start [, length]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=129|t=Source}}<br />
|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).<br />
|param1 = string<br />
|param1text = String to return a substring from.<br />
|param2 = start<br />
|param2text = Integer specifying the start of a substring. Negative values specify a position from the end of the string.<br />
|param3 = length<br />
|param3text = Optional argument specifying the length of the substring. Defaults to the end of the string.<br />
|example1 = print(substr("abcde", 1, 3)); # prints "bcd"<br />
|example2 = print(substr("abcde", 1)); # prints "bcde"<br />
|example3 = print(substr("abcde", 2, 1)); # prints "c"<br />
|example4 = print(substr("abcde", -2)); # prints "de"<br />
|example5 = print(substr("abcde", -3, 2)); # prints "cd"<br />
}}<br />
<br />
=== subvec() ===<br />
{{Nasal doc<br />
|syntax = subvec(vector, start[, length]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=63|t=Source}}<br />
|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). <br />
|param1 = vector<br />
|param1text = The vector to take the sub-vector from.<br />
|param2 = start<br />
|param2text = The starting point of the sub-vector within the given vector.<br />
|param3 = length<br />
|param3text = Optional argument specifying the length of the sub-vector, from the starting point.<br />
'''Notes:'''<br />
* Omitting the ''vector'' and ''start'' arguments is not an error (possibly it should be) but the return value is ''nil''.<br />
* A negative ''start'' argument ''is'' an error. This seems wrong. Perhaps the language designer could comment.<br />
* A value of ''start'' greater than ''size(vector)'' causes an error. A value equal to ''size(vector)'' returns an empty vector.<br />
* 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.<br />
|example1 = <br />
var vector = [1, 2, 3];<br />
debug.dump(subvec(vector, 0)); # prints "[1, 2, 3]"<br />
|example2 = <br />
var vector = [1, 2, 3];<br />
debug.dump(subvec(vector, 1)); # prints "[2, 3]"<br />
|example3 = <br />
var vector = [1, 2, 3];<br />
debug.dump(subvec(vector, 1, 1)); # prints "[2]"<br />
}}<br />
<br />
=== typeof() ===<br />
{{Nasal doc<br />
|syntax = typeof(object);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=193|t=Source}}<br />
|text = Returns a string indicating the whether the object is <code>'''nil'''</code>, a scalar (number or string), a vector, a hash, a function, or a ghost.<br />
|param1 = object<br />
|param1text = Object to return the type of.<br />
|example1 = <br />
var object = nil;<br />
print(typeof(object)); # prints "nil"<br />
|example2 = <br />
var object = "Hello world!";<br />
print(typeof(object)); # prints "scalar"<br />
|example3 = <br />
var object = math.pi;<br />
print(typeof(object)); # prints "scalar"<br />
|example4 = <br />
var object = [1, 2, 3];<br />
print(typeof(object)); # prints "vector"<br />
|example5 = <br />
var object = {};<br />
print(typeof(object)); # prints "hash"<br />
|example6 = <br />
var object = func {};<br />
print(typeof(object)); # prints "func"<br />
|example7 =<br />
var object = airportinfo();<br />
print(typeof(object)); # prints "ghost"<br />
}}<br />
<br />
<!-- == Extension modules ==<br />
=== thread ===<br />
{{WIP}}<br />
<br />
{{Nasal doc<br />
|syntax = thread.newthread(func);<br />
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}<br />
|text = <br />
|example1text = start a new worker thread<br />
|example1 = thread.newthread( func() {} );<br />
}}<br />
<br />
{{Nasal doc<br />
|syntax = thread.newlock();<br />
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}<br />
|text = <br />
|example1text = create a new lock<br />
|example1 = var lock = thread.newlock()<br />
}}<br />
<br />
{{Nasal doc<br />
|syntax = thread.lock();<br />
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}<br />
|text = <br />
|example1text = lock a lock<br />
|example1 = var lock = thread.newlock()<br />
}}<br />
<br />
{{Nasal doc<br />
|syntax = thread.unlock();<br />
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}<br />
|text = <br />
|example1text = unlock a lock<br />
|example1 = var lock = thread.unlock()<br />
}}<br />
<br />
<br />
{{Nasal doc<br />
|syntax = thread.newsem();<br />
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}<br />
|text = <br />
|example1text = create a new {{Wikipedia|semaphore}}<br />
|example1 = var semaphore = thread.newsem()<br />
}}<br />
<br />
<br />
{{Nasal doc<br />
|syntax = thread.semdown();<br />
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}<br />
|text = <br />
|example1text = semaphore down<br />
|example1 = thread.semdown(semaphore)<br />
}}<br />
<br />
<br />
{{Nasal doc<br />
|syntax = thread.semup();<br />
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}<br />
|text = <br />
|example1text = semaphore up<br />
|example1 = thread.semup(semaphore)<br />
}} --><br />
<br />
== Extension functions ==<br />
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:<br />
* {{flightgear file|src/Scripting/NasalPositioned.cxx}}<br />
* {{flightgear file|src/Scripting/NasalSys.cxx}}<br />
* {{fgdata file|Nasal/globals.nas}}<br />
<br />
=== abort() ===<br />
{{Nasal doc<br />
|syntax = abort();<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=565|t=Source}}<br />
|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 "exit" [[Fgcommands|fgcommand]], which will exit FlightGear more gracefully (see example below).<br />
|example1text = This example will immediately stop FlightGear with an error, such as "FlightGear has stopped working."<br />
|example1 = abort();<br />
|example2text = For exiting FlightGear in a better way, please use the following code:<br />
|example2 = fgcommand("exit");<br />
}}<br />
<br />
=== abs() ===<br />
{{Nasal doc<br />
|syntax = abs(number);<br />
|source = {{fgdata file|Nasal/globals.nas|t=Source}}<br />
|text = This simple function returns the {{wikipedia|absolute value|noicon=1}} of the provided number.<br />
|param1 = number<br />
|param1text = This argument is required and should be a number.<br />
|example1 = print(abs(1)); # prints "1"<br />
|example2 = print(abs(-1)); # prints "1"<br />
}}<br />
<br />
=== aircraftToCart() ===<br />
This new function in FG 2017.2.1 takes coordinates in aircraft structural coordinate system, and translate them into geocentric coordinates.<br />
Example for (5,6,7):<br />
<syntaxhighlight lang="nasal"><br />
var pos = aircraftToCart({x: -5, y: 6, z: -7});<br />
var coord = geo.Coord.new();<br />
coord.set_xyz(pos.x, pos.y, pos.z);<br />
</syntaxhighlight><br />
Notice: x and z is inverted sign on purpose.<br />
if you want lat. lon, alt from that, just call: (degrees and meters)<br />
<br />
<syntaxhighlight lang="nasal"><br />
coord.lat()<br />
coord.lon()<br />
coord.alt()<br />
</syntaxhighlight><br />
<br />
=== addcommand() ===<br />
{{Nasal doc<br />
|syntax = addcommand(name, code);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=659|t=Source}}<br />
|version = 2.12<br />
|commit = {{flightgear commit|7b663c|t=commit}}<br />
|text = {{see also|Howto:Add new fgcommands to FlightGear}}<br />
<br />
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.<br />
|param1 = name<br />
|param1text = This will become the name of the new fgcommand. Must be a string.<br />
|param2 = code<br />
|param2text = The code that will be executed when the fgcommand is run. Must be a function.<br />
|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.<br />
|example1 = addcommand("myFGCmd", func(node) {<br />
print("fgcommand 'myFGCmd' has been run.");<br />
props.dump( node );<br />
});<br />
fgcommand("myFGCmd", props.Node.new({foo:1, bar:2}) );<br />
|example2text = This example demonstrates how parameters are defined in a new fgcommand.<br />
|example2 = addcommand("myFGCmd", func(node){<br />
print(node.getNode("number").getValue()); # prints the value of "number," which is 12<br />
});<br />
fgcommand("myFGCmd", props.Node.new({"number": 12}));<br />
}}<br />
<br />
=== airportinfo() ===<br />
{{Nasal doc<br />
|syntax = airportinfo();<br />
airportinfo(type);<br />
airportinfo(id);<br />
airportinfo(lat, lon[, type]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1024|t=Source}}<br />
|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:<br />
* '''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.<br />
* '''lon''': Longitude of the location.<br />
* '''lat''': Latitude of the location.<br />
* '''has_metar''': True or false depending whether the airport has a [[METAR]] code defined for it.<br />
* '''elevation''': Elevation of the location in metres.<br />
* '''id''': ICAO code of the airport (or ID of the seaplane base/heliport).<br />
* '''name''': Name of the airport/heliport/seaplane base.<br />
* '''runways'''<br />
** '''<runway name>'''<br />
*** '''id''': Name of runway.<br />
*** '''lat''': Latitude of the runway.<br />
*** '''lon''': Longitude of the runway.<br />
*** '''heading''': Heading of the runway.<br />
*** '''length''': Length of the runway in metres.<br />
*** '''width''': Width of the runway in metres.<br />
*** '''surface''': Runway surface type.<br />
*** '''threshold''': Length of the runway's {{wikipedia|displaced threshold}} in metres. Will return 0 if there is none.<br />
*** '''stopway''': Length of the runway's stopway (the area before the threshold) in metres. Will return 0 if there is none.<br />
*** '''reciprocal''': <code>runway</code> ghost of the reciprocal runway.<br />
*** '''ils_frequency_mhz''': ILS frequency in megahertz.<br />
*** '''ils''': <code>navaid</code> ghost of the ILS transmitter.<br />
* '''helipads'''<br />
** '''<helipad name>'''<br />
*** '''id''': Name of helipad.<br />
*** '''lat''': Latitude of the helipad.<br />
*** '''lon''': Longitude of the helipad.<br />
*** '''heading''': Heading of the helipad.<br />
*** '''length''': Length of the helipad in metres.<br />
*** '''width''': Width of the helipad in metres.<br />
*** '''surface''': Helipad surface type.<br />
* '''taxiways'''<br />
** '''<taxiway name>'''<br />
*** '''id''': Name of taxiway.<br />
*** '''lat''': Latitude of the taxiway.<br />
*** '''lon''': Longitude of the taxiway.<br />
*** '''heading''': Heading of the taxiway.<br />
*** '''length''': Length of the taxiway in metres.<br />
*** '''width''': Width of the taxiway in metres.<br />
*** '''surface''': Taxiway surface type.<br />
<br />
Information is extracted in the same way as accessing members of a Nasal hash. For example:<br />
<syntaxhighlight lang="nasal"><br />
# prints to lengths of the runways of the nearest airport in feet and metres<br />
var info = airportinfo();<br />
print("-- Lengths of the runways at ", info.name, " (", info.id, ") --");<br />
foreach(var rwy; keys(info.runways)){<br />
print(rwy, ": ", math.round(info.runways[rwy].length * M2FT), " ft (", info.runways[rwy].length, " m)");<br />
}<br />
</syntaxhighlight><br />
<br />
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.<br />
|param1 = id<br />
|param1text = The {{wikipedia|International Civil Aviation Organization airport code|ICAO code|noicon=1}} of an airport to retrieve information about.<br />
|param2 = type<br />
|param2text = When this argument is used, the function will return the closest airport of a certain type. Can be one of "heliport," "seaport," or "airport" (default).<br />
: {{inote|Running this function without any parameters is equivalent to this:<br />
: <syntaxhighlight lang="nasal"><br />
airportinfo("airport");<br />
</syntaxhighlight><br />
}}<br />
|param3 = lat ''and'' lon<br />
|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.<br />
|example1 = var info = airportinfo();<br />
print("Nearest airport: ", info.name, " (", info.id, ")"); # prints the name and ICAO code of the nearest airport<br />
|example2 = var info = airportinfo("heliport");<br />
print("Elevation of the nearest heliport: ", math.round(info.elevation * M2FT), " ft"); # prints the elevation and name of the nearest heliport<br />
|example3 = var info = airportinfo("KSQL");<br />
print("-- Runways of ", info.name, " (", info.id, "): --");<br />
foreach(var rwy; keys(info.runways)) {<br />
print(rwy); # prints the runways of KSQL<br />
}<br />
|example4 = var info = airportinfo(37.81909385, -122.4722484);<br />
print("Coordinates of the nearest airport: ", info.lat, ", ", info.lon); # print the name and ICAO of the nearest airport to the Golden Gate Bridge<br />
|example5 = var info = airportinfo(37.81909385, -122.4722484, "seaport");<br />
print("Nearest seaplane base: ", info.name, " (", info.id, ")"); # print the name and ID of the nearest seaplane base to the Golden Gate Bridge<br />
|example6text = This example prints the all information from an <code>airportinfo()</code> call.<br />
|example6 = var info = airportinfo("KSFO");<br />
print(info.name);<br />
print(info.id);<br />
print(info.lat);<br />
print(info.lon);<br />
print(info.has_metar);<br />
print(info.elevation);<br />
foreach(var rwy; keys(info.runways)){<br />
print("-- ", rwy, " --");<br />
print(info.runways[rwy].lat);<br />
print(info.runways[rwy].lon);<br />
print(info.runways[rwy].length);<br />
print(info.runways[rwy].width);<br />
print(info.runways[rwy].heading);<br />
print(info.runways[rwy].stopway);<br />
print(info.runways[rwy].threshold);<br />
}<br />
}}<br />
<br />
=== airwaysRoute() ===<br />
{{Nasal doc<br />
|syntax = airwaysRoute(start, end[, type]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1933|t=Source}}<br />
|text = {{see also|Nasal Flightplan}}<br />
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.<br />
|param1 = start<br />
|param1text = Start waypoint, in the form of a waypoint ghost, such as that provided by {{func link|flightplan()}}.<br />
|param2 = end<br />
|param2text = Same as above.<br />
|param3 = type<br />
|param3text = Instructs the function to compute a high level route (when set to "highlevel"), or a low level route (when set to "lowlevel"). Defaults to "highlevel."<br />
|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.<br />
|example1 = var fp = flightplan();<br />
var start = fp.getWP(0);<br />
var end = fp.getWP(fp.getPlanSize() - 1);<br />
var rt = airwaysRoute(start, end);<br />
foreach(var wp; rt){<br />
print(wp.wp_name); # print the waypoints in the computed route<br />
}<br />
|example2text = Exactly the same as above, but computes a low level path.<br />
|example2 = var fp = flightplan();<br />
var start = fp.getWP(0);<br />
var end = fp.getWP(fp.getPlanSize() - 1);<br />
var rt = airwaysRoute(start, end, "lowlevel");<br />
foreach(var wp; rt){<br />
print(wp.wp_name); # print the waypoints in the computed route<br />
}<br />
}}<br />
<br />
=== assert() ===<br />
{{Nasal doc<br />
|syntax = assert(condition[, message]);<br />
|source = {{fgdata file|Nasal/globals.nas|t=Source}}<br />
|version = 3.2<br />
|commit = {{fgdata commit|8b16a7|t=commit}}<br />
|text = Returns either true if the condition evaluates as true, or aborts with a {{func link|die()}} call, which can be customised.<br />
|param1 = condition<br />
|param1text = Condition to evaluate.<br />
|param2 = message<br />
|param2text = Optional message that will be used in any {{func link|die()}} call. Defaults to "assertion failed!"<br />
|example1 = var a = 1;<br />
var b = 2;<br />
print(assert(a < b)); # prints "1" (true)<br />
|example2 = var a = 1;<br />
var b = 2;<br />
assert(a > b, 'a is not greater than b'); # aborts with a custom error message<br />
}}<br />
<br />
=== carttogeod() ===<br />
{{Nasal doc<br />
|syntax = carttogeod(x, y, z);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=945|t=Source}}<br />
|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).<ref>{{simgear file|simgear/math/sg_geodesy.hxx|l=43}}</ref><br />
|param1 = x<br />
|param1text = Mandatory x-axis value, in metres.<br />
|param2 = y<br />
|param2text = Mandatory y-axis value, in metres.<br />
|param3 = z<br />
|param3text = Mandatory z-axis value, in metres.<br />
|example1 = var (lat, lon, alt) = carttogeod(6378137, 0, 0); # point is the intersection of the prime meridian and equator.<br />
print("Latitude: ", lat); # prints lat, lon and alt, which are all zero, see above<br />
print("Longitude: ", lon);<br />
print("Altitude: ", alt);<br />
}}<br />
<br />
=== cmdarg() ===<br />
{{Nasal doc<br />
|private = _cmdarg()<br />
|syntax = cmdarg();<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=513|t=Part 1}} {{!}} {{fgdata file|Nasal/globals.nas|t=Part 2}}<br />
|text = <code>cmdarg()</code> 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. <br />
It is used by Nasal scripts embedded in XML files. It returns a <code>props.Node</code> object (see {{fgdata file|Nasal/props.nas}}), and you can use all of its methods on the returned value. <code>cmdarg()</code> should only be used in four types/places of XML files:<br />
* Bindings: This is needed so that the value of a joystick's axis can be accessed internally.<br />
* 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). <br />
* 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.<br />
* Animation XML files: If the animation XML file is used in an AI/MP model, <code>cmdarg()</code> will return the root of the AI model in the <code>/ai/models/</code> directory. Examples: <code>/ai/models/aircraft[3]/</code>, <code>/ai/models/multiplayer[1]/</code><br />
<br />
You should not use <code>cmdarg()</code> in places other than those stated above. Although it won't cause an error, it will return the value of the last legitimate <code>cmdarg()</code> call. <br />
<br />
Also, you should not delay <code>cmdarg()</code> using {{func link|maketimer()}}, {{func link|settimer()}} or {{func link|setlistener()}}, because it will return an unrelated property.<br />
|example1 = fgcommand("dialog-show", {"dialog-name": "cmdarg-demo"});<br />
|example1text = <br>This example demonstrates the usage of <code>cmdarg()</code> in a binding. Save the below XML snippet as <tt>[[$FG_ROOT]]/gui/dialogs/cmdarg-demo.xml</tt>. 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.<br />
<syntaxhighlight lang="xml"><br />
<?xml version="1.0" encoding="UTF-8"?><br />
<br />
<PropertyList><br />
<br />
<name>cmdarg-demo</name><br />
<layout>vbox</layout><br />
<br />
<text><br />
<label>Click "Close" to activate the demonstration (a message in the console).</label><br />
</text><br />
<br />
<button><br />
<legend>Close</legend><br />
<binding><br />
<command>nasal</command><br />
<script>print("Button binding root: '" ~ cmdarg().getPath() ~ "'");</script><br />
</binding><br />
<binding><br />
<command>dialog-close</command><br />
</binding><br />
</button><br />
<br />
</PropertyList><br />
</syntaxhighlight><br />
|example2text = This example demonstrates the usage of <code>cmdarg()</code> in Nasal code within dialogs. Open <tt>[[$FG_ROOT]]/gui/dialogs/cmdarg-demo.xml</tt> from the previous example, copy & 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 "I've been changed!"<br />
<syntaxhighlight lang="xml"><br />
<?xml version="1.0" encoding="UTF-8"?><br />
<br />
<PropertyList><br />
<br />
<name>cmdarg-demo</name><br />
<layout>vbox</layout><br />
<br />
<text><br />
<label>Click "Click me!" to activate the demonstration (the button's label will change).</label><br />
</text><br />
<br />
<button><br />
<legend>Click me!</legend><br />
<binding><br />
<command>nasal</command><br />
<script>change_label();</script><br />
</binding><br />
</button><br />
<br />
<button><br />
<legend>Close</legend><br />
<binding><br />
<command>dialog-close</command><br />
</binding><br />
</button><br />
<br />
<nasal><br />
<open><![CDATA[<br />
var dlg_root = cmdarg();<br />
var dlg_name = {"dialog-name": "cmdarg-demo"};<br />
var change_label = func {<br />
dlg_root.getNode("button[0]/legend").setValue("I've been changed!");<br />
fgcommand("dialog-close", dlg_name);<br />
fgcommand("dialog-show", dlg_name);<br />
}<br />
]]></open><br />
</nasal><br />
<br />
</PropertyList><br />
</syntaxhighlight><br />
|example3text = For an example of <code>cmdarg()</code> used with Canvas, please see [[Howto:Adding a canvas to a GUI dialog#FGPlot|Howto:Adding a canvas to a GUI dialog]].<br />
}}<br />
<br />
=== courseAndDistance() ===<br />
{{Nasal doc<br />
|syntax = courseAndDistance(to);<br />
courseAndDistance(from, to);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1668|t=Source}}<br />
|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:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost type<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A geo.Coord object<br />
|param1 = from<br />
|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.<br />
|param2 = to<br />
|param2text = Like the first parameter, but defines the second point.<br />
|example1text = This example demonstrates the usage of the function with the <code>airport</code> ghost type.<br />
|example1 = var from = airportinfo("KSFO");<br />
var to = airportinfo("KSQL");<br />
var (course, dist) = courseAndDistance(from, to);<br />
print(course); # prints course from KSFO to KSQL<br />
print(dist); # prints distance in nm from KSFO to KSQL<br />
|example2text = This example demonstrates the usage of the function with hashes containing ''lat'' and ''lon''.<br />
|example2 = var from = {lat: 0, lon: 0};<br />
var to = {lat: 1, lon: 1};<br />
var (course, dist) = courseAndDistance(from, to);<br />
print(course);<br />
print(dist);<br />
|example3text = This example demonstrates usage of a geo.Coord object.<br />
|example3 = var from = geo.Coord.new().set_latlon(0, 0);<br />
var to = geo.Coord.new().set_latlon(1, 1);<br />
var (course, dist) = courseAndDistance(from, to);<br />
print(course);<br />
print(dist);<br />
|example4text = This example demonstrates usage of differing parameter types.<br />
|example4 = var from = airportinfo("KSFO");<br />
var to = geo.Coord.new().set_latlon(0, 0);<br />
var (course, dist) = courseAndDistance(from, to);<br />
print(course);<br />
print(dist);<br />
|example5text = The same as above, but the other way round.<br />
|example5 = var to = {lat: 1, lon: 1};<br />
var (course, dist) = courseAndDistance(0, 0, to);<br />
print(course);<br />
print(dist);<br />
|example6text = Usage of just one parameter.<br />
|example6 = var dest = airportinfo("KSQL");<br />
var (course, dist) = courseAndDistance(dest);<br />
print("Turn to heading ", math.round(course), ". You have ", sprintf("%.2f", dist), " nm to go");<br />
}}<br />
<br />
=== createDiscontinuity() ===<br />
{{Nasal doc<br />
|syntax = createDiscontinuity();<br />
|text = Returns a <code>waypoint</code> ghost object. A route discontinuity is inserted by an {{abbr|FMS|Flight Management System}} when it is unsure how to connect two waypoints.<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2045|t=Source}}<br />
|version = 2016.1<br />
|commit = {{flightgear commit|caead6|t=commit}}<br />
}}<br />
=== createViaTo() ===<br />
{{Nasal doc<br />
|syntax = createViaTo(airway, waypoint);<br />
|text = Returns a <code>waypoint</code> ghost object. It represents a route "via '''airway''' to '''waypoint'''".<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2009|t=Source}}<br />
|version = 2016.1<br />
|commit = {{flightgear commit|caead6|t=commit}}<br />
|param1 = airway<br />
|param1text = The name of an airway.<br />
|param2 = waypoint<br />
|param2text = Must be in the airway and one of:<br />
* The name of a waypoint.<br />
* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, or <code>fix</code> ghost object.<br />
}}<br />
=== createWP() ===<br />
{{Nasal doc<br />
|syntax = createWP(pos, name[, flag]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1964|t=Source}}<br />
|text = Creates a new waypoint ghost object.<br />
|param1 = pos<br />
|param1text = Dictates the position of the new waypoint. It can be one of the following:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost type<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A geo.Coord object<br />
:* Two numbers separated by a comma, as if the function is taking three arguments. See example 4 below.<br />
|param2 = name<br />
|param2text = String that will become the name of the new waypoint.<br />
|param3 = flag<br />
|param3text = Optional string that will tell FlightGear what type of waypoint it is. Must be one of "sid," "star," "approach," "missed," or "pseudo."<br />
|example1text = Creates a waypoint directly in front and 1 km away and appends it to the flight plan.<br />
|example1 = var pos = geo.aircraft_position().apply_course_distance(getprop("/orientation/heading-deg"), 1000);<br />
var wp = createWP(pos, "NEWWP");<br />
var fp = flightplan();<br />
fp.appendWP(wp);<br />
|example2 = var pos = geo.aircraft_position().apply_course_distance(getprop("/orientation/heading-deg"), 1000);<br />
var wp = createWP({lat: pos.lat(), lon: pos.lon()}, "NEWWP");<br />
var fp = flightplan();<br />
fp.appendWP(wp);<br />
|example3 = var apt = airportinfo();<br />
var wp = createWP(apt, "NEWWP");<br />
var fp = flightplan();<br />
fp.appendWP(wp);<br />
|example4 = var pos = geo.aircraft_position().apply_course_distance(getprop("/orientation/heading-deg"), 1000);<br />
var wp = createWP(pos.lat(), pos.lon(), "NEWWP");<br />
var fp = flightplan();<br />
fp.appendWP(wp);<br />
|example5text = Creates a new waypoint and adds it to the flight plan. Waypoints of the type "pseudo" are then removed from the flight plan, including the new waypoint. The {{func link|print()}} statements show this.<br />
|example5 = var pos = geo.aircraft_position().apply_course_distance(getprop("/orientation/heading-deg"), 1000);<br />
var wp = createWP(pos, "NEWWP", "pseudo");<br />
var fp = flightplan();<br />
fp.appendWP(wp);<br />
print(fp.getPlanSize());<br />
fp.clearWPType("pseudo");<br />
print(fp.getPlanSize());<br />
}}<br />
<br />
=== createWPFrom() ===<br />
{{Nasal doc<br />
|syntax = createWPFrom(object[, flag]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1989|t=Source}}<br />
|text = Creates a new waypoint object from another object.<br />
|param1 = object<br />
|param1text = A ghost object. Must be a ghost type that is one of "airport," "navaid," "runway," or "fix."<br />
|param2 = flag<br />
|param2text = Optional string that will tell FlightGear what type of waypoint it is. Must be one of "sid," "star," "approach," "missed," or "pseudo."<br />
|example1text = Creates a new waypoint and appends it to the flight plan.<br />
|example1 = var apt = airportinfo("KSFO");<br />
var wp = createWPFrom(apt);<br />
var fp = flightplan();<br />
fp.appendWP(wp);<br />
|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.<br />
|example2 = var apt = airportinfo("KSFO");<br />
var wp = createWPFrom(apt, "pseudo");<br />
print(wp.wp_name);<br />
var fp = flightplan();<br />
fp.appendWP(wp);<br />
print(fp.getPlanSize());<br />
fp.clearWPType("pseudo");<br />
print(fp.getPlanSize());<br />
}}<br />
<br />
=== defined() ===<br />
{{Nasal doc<br />
|syntax = defined(symbol);<br />
|source = {{fgdata file|Nasal/globals.nas|t=Source}}<br />
|text = Returns 1 (true) or 0 (false) depending on whether a variable exists.<br />
|param1 = symbol<br />
|param1text = A string that will be what the function searches for.<br />
|example1 = var number = 12;<br />
var check_exist = func {<br />
print("Variable 'number' ", defined("number") == 1 ? "exists" : "does not exist"); # 'number' does exist<br />
print("Variable 'number2' ", defined("number2") == 1 ? "exists" : "does not exist"); # 'number2' does not exist<br />
}<br />
check_exist();<br />
}}<br />
<br />
=== directory() ===<br />
{{Nasal doc<br />
|syntax = directory(path);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=572|t=Source}}<br />
|text = Returns a vector containing a list of the folders and files in a given file path or <code>'''nil'''</code> if the path doesn't exist. Hidden folders and files are not added to the vector.<br />
{{inote|The first two elements of the vector will be <code>'.'</code> and <code>'..'</code>. These are for navigating back up the file tree, but have no use in Nasal. They can be safely removed from the vector.}}<br />
|param1 = path<br />
|param1text = Absolute file path.<br />
|example1text = Gets the folders and files in [[$FG_ROOT]], and then removes the extra first two elements (see note above). <br />
|example1 = var dir = directory(getprop("/sim/fg-root")); # get directory<br />
dir = subvec(dir, 2); # strips off the first two elements<br />
debug.dump(dir); # dump the vector<br />
}}<br />
<br />
=== fgcommand() ===<br />
{{Nasal doc<br />
|syntax = fgcommand(cmd[, args]);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=456|t=Part 1}} {{!}} {{fgdata file|Nasal/globals.nas|t=Part 2}}<br />
|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.<br />
|param1 = cmd<br />
|param1text = String that is the name of the command that is to be run.<br />
|param2 = args<br />
|param2text = If the fgcommand takes arguments, they are inputted using this argument. Can either be a <code>props.Node</code> object, or a hash (see examples below).<br />
|example1 = fgcommand("null"); # does nothing<br />
|example2 = var args = props.Node.new({'script': 'print("Running fgcommand");'});<br />
if (fgcommand("nasal", args)) { # prints "Running fgcommand" and then one of these print statements<br />
print("Fgcommand succeeded");<br />
} else {<br />
print("Fgcommand encountered a problem");<br />
}<br />
|example3 = var args = { 'dialog-name': 'about' };<br />
fgcommand("dialog-show", args); # shows the 'about' dialog<br />
}}<br />
<br />
=== findAirportsByICAO() ===<br />
{{Nasal doc<br />
|syntax = findAirportsByICAO(search[, type]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1096|t=Source}}<br />
|text = Returns a vector containing <code>airport</code> ghost objects which are (by default) airports whose ICAO code matches the search string. The results are sorted by range from closest to furthest.<br />
|param1 = search<br />
|param1text = Search string for the function. Can either be a partial or a full ICAO code.<br />
:{{icaution|The more matches there are for the given code, the longer the function will take. Passing just one character (e.g., "K"), might make FlightGear hang for a certain amount of time.}}<br />
|param2 = type<br />
|param2text = This will narrow the search to airports of a certain type. By default, only airports are searched for. May be one of "airport," "heliport," or "seaport."<br />
|example1 = var apts = findAirportsByICAO("KSF"); # finds all airports matching "KSF"<br />
foreach(var apt; apts){<br />
print(apt.name, " (", apt.id, ")"); # prints them<br />
}<br />
|example2 = var apts = findAirportsByICAO("SP0", "seaport"); # finds all seaplane bases matching "SP0"<br />
foreach(var apt; apts){<br />
print(apt.name, " (", apt.id, ")"); # prints them<br />
}<br />
|example3 = var apt = findAirportsByICAO("XBET"); # one way to check if an airport does exist"<br />
if (size(apt) == 0) {<br />
print("Airport does not exist"); # this one will be printed<br />
} else {<br />
print("Airport does exist");<br />
}<br />
}}<br />
<br />
=== findAirportsWithinRange() ===<br />
{{Nasal doc<br />
|syntax = findAirportsWithinRange([pos, ]range[, type]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1066|t=Source}}<br />
|text = Returns a vector of <code>airport</code> 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.<br />
|param1 = pos<br />
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost type<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A geo.Coord object<br />
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: <code>findAirportsWithinRange(lat, lon, range, type);</code>.<br />
|param2 = range<br />
|param2text = Mandatory number giving the range in nautical miles within which to search for airports/heliports/seaplane bases.only airports are searched for.<br />
|param3 = type<br />
|param3text = This will narrow the search to airports of a certain type. By default, only airports are searched for. May be one of "airport," "heliport," or "seaport."<br />
|example1text = Searches for airports within 10 nm of [[KSFO]].<br />
|example1 = var pos = airportinfo("KSFO");<br />
var apts = findAirportsWithinRange(pos, 10);<br />
foreach(var apt; apts){<br />
print(apt.name, " (", apt.id, ")");<br />
}<br />
|example2text = Searches for seaplane bases within 15 nm of [[KSFO]].<br />
|example2 = var pos = airportinfo("KSFO");<br />
var apts = findAirportsWithinRange(pos, 15, "seaport");<br />
foreach(var apt; apts){<br />
print(apt.name, " (", apt.id, ")");<br />
}<br />
|example3text = Searches for airports within 10 nm of your current position.<br />
|example3 = var apts = findAirportsWithinRange(10);<br />
foreach(var apt; apts){<br />
print(apt.name, " (", apt.id, ")");<br />
}<br />
}}<br />
<br />
=== findFixesByID() ===<br />
{{Nasal doc<br />
|syntax = findFixesByID([pos, ]id);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1627|t=Source}}<br />
|text = Returns a vector containing <code>fix</code> ghost objects matching a given ID, sorted by range from a certain position.<br />
{{inote|Fixes are (usually) also known as waypoints.}}<br />
|param1 = pos<br />
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost type<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A geo.Coord object<br />
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: <code>findFixesByID(lat, lon, id);</code>.<br />
|param2 = id<br />
|param2text = Full or partial ID of the fix to search for.<br />
:{{inote|1=Inputting a partial ID does not work correctly (see [http://forum.flightgear.org/viewtopic.php?f=30&t=28129 here]). It is best to just input a full ID.}}<br />
|example1 = var fixes = findFixesByID("POGIC");<br />
foreach(var fix; fixes){<br />
print(fix.id, " - lat: ", fix.lat, " {{!}} lon: ", fix.lon); # prints information about POGIC<br />
}<br />
|example2 = var fix = findFixesByID("ZUNAP");<br />
fix = fix[0];<br />
var (course, dist) = courseAndDistance(fix);<br />
print("Turn to heading ", math.round(course), ". You have ", sprintf("%.2f", dist), " nm to go to reach ", fixes[0].id);<br />
}}<br />
<br />
=== findNavaidByFrequency() ===<br />
{{Nasal doc<br />
|syntax = findNavaidByFrequency([pos, ]freq[, type]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1547|t=Source}}<br />
|text = Returns a <code>navaid</code> ghost object for a navaid matching a given frequency. If there is more than one navaid with that frequency, the nearest station is returned.<br />
|param1 = pos<br />
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost type<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A geo.Coord object<br />
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: <code>findNavaidByFrequency(lat, lon, freq, type);</code>.<br />
|param2 = freq<br />
|param2text = Frequency, in megahertz, of the navaid to search for.<br />
|param3 = type<br />
|param3text = This will narrow the search to navaids of a certain type. Defaults to "all." For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.<br />
|example1 = var navaid = findNavaidByFrequency(11.17);<br />
print("ID: ", navaid.id); # prints info about the navaid<br />
print("Name: ", navaid.name);<br />
print("Latitude: ", navaid.lat);<br />
print("Longitude: ", navaid.lon);<br />
print("Elevation (AMSL): ", navaid.elevation, " m");<br />
print("Type: ", navaid.type);<br />
print("Frequency: ", sprintf("%.3f", navaid.frequency / 1000), " Mhz");<br />
print("Range: ", navaid.range_nm, " nm");<br />
if(navaid.course) print("Course: ", navaid.course);<br />
}}<br />
<br />
=== findNavaidsByFrequency() ===<br />
{{Nasal doc<br />
|syntax = findNavaidsByFrequency([pos, ]freq[, type]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1572|t=Source}}<br />
|text = Returns a vector conatining <code>navaid</code> ghost objects for navaids that match a given frequency, sorted from nearest to furthest.<br />
|param1 = pos<br />
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost type<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A geo.Coord object<br />
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: <code>findNavaidsByFrequency(lat, lon, freq, type);</code>.<br />
|param2 = freq<br />
|param2text = Frequency, in megahertz, of the navaid to search for.<br />
|param3 = type<br />
|param3text = This will narrow the search to navaids of a certain type. Defaults to "all." For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.<br />
|example1 = var navaids = findNavaidsByFrequency(10.955);<br />
foreach(var navaid; navaids){<br />
print("--");<br />
print("ID: ", navaid.id); # prints info about the navaid<br />
print("Name: ", navaid.name);<br />
print("Latitude: ", navaid.lat);<br />
print("Longitude: ", navaid.lon);<br />
print("Elevation (AMSL): ", navaid.elevation, " m");<br />
print("Type: ", navaid.type);<br />
print("Frequency: ", sprintf("%.3f", navaid.frequency / 1000), " Mhz");<br />
print("Range: ", navaid.range_nm, " nm");<br />
if(navaid.course) print("Course: ", navaid.course);<br />
print("--");<br />
}<br />
}}<br />
<br />
=== findNavaidsByID() ===<br />
{{Nasal doc<br />
|syntax = findNavaidsByID([pos, ]id[, type]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1600|t=Source}}<br />
|text = Returns a vector containing <code>navaid</code> ghost objects matching a given ID, sorted by range from a certain position.<br />
|param1 = pos<br />
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost type<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A geo.Coord object<br />
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: <code>findNavaidsByID(lat, lon, id, type);</code>.<br />
|param2 = id<br />
|param2text = Full or partial ID of the fix to search for.<br />
:{{inote|1=Inputting a partial ID does not work correctly (see [http://forum.flightgear.org/viewtopic.php?f=30&t=28129 here]). It is best to just input a full ID.}}<br />
|param3 = type<br />
|param3text = This will narrow the search to navaids of a certain type. Defaults to "all." For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.<br />
|example1 = var navaid = findNavaidsByID("MXW");<br />
navaid = navaid[0];<br />
print("ID: ", navaid.id); # prints info about 'MXW' (a VOR station)<br />
print("Name: ", navaid.name);<br />
print("Latitude: ", navaid.lat);<br />
print("Longitude: ", navaid.lon);<br />
print("Elevation (AMSL): ", navaid.elevation, " m");<br />
print("Type: ", navaid.type);<br />
print("Frequency: ", sprintf("%.3f", navaid.frequency / 1000), " Mhz");<br />
print("Range: ", navaid.range_nm, " nm");<br />
}}<br />
<br />
=== findNavaidsWithinRange() ===<br />
{{Nasal doc<br />
|syntax = findNavaidsWithinRange([pos, ]range[, type]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1518|t=Source}}<br />
|text = Returns a vector of <code>navaid</code> 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.<br />
|param1 = pos<br />
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost type<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A geo.Coord object<br />
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: <code>findNavaidsWithinRange(lat, lon, range, type);</code>.<br />
|param2 = range<br />
|param2text = Mandatory number giving the range in nautical miles within which to search for navaids. Defaults to "all." For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.<br />
|param3 = type<br />
|param3text = This will narrow the search to navaids of a certain type.<br />
|example1text = Searches for navaids within 10 nm of [[KSFO]].<br />
|example1 = var pos = airportinfo("KSFO");<br />
var navs = findNavaidsWithinRange(pos, 10);<br />
foreach(var nav; navs){<br />
print(nav.name, " (ID: ", nav.id, ")");<br />
}<br />
|example2text = Searches for navaids within 10 nm of your current position.<br />
|example2 = var navs = findNavaidsWithinRange(10);<br />
foreach(var nav; navs){<br />
print(nav.name, " (ID: ", nav.id, ")");<br />
}<br />
}}<br />
<br />
=== finddata() ===<br />
{{Nasal doc<br />
|syntax = finddata(path);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=603|t=Source}}<br />
|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]]. <br />
|param1 = path<br />
|param1text = A relative path as a string.<br />
|example1 = var path = finddata("Aircraft/Generic");<br />
print(path); # prints the absolute path to $FG_ROOT/Aircraft/Generic<br />
|example2 = var path = finddata("Airports");<br />
print(path); # prints the absolute path to <TerraSync dir>/Airports<br />
|example3 = var path = finddata("preferences.xml");<br />
print(path); # prints the absolute path to $FG_ROOT/preferences.xml<br />
}}<br />
<br />
=== flightplan() ===<br />
{{Nasal doc<br />
|syntax = flightplan([path]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1738|t=Source}}<br />
|text = {{see also|Nasal Flightplan}}<br />
Returns a flight plan object, either one for the current flight plan, or one loaded from a given path.<br />
|param1 = path<br />
|param1text = Optional path to flight plan XML file.<br />
|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.<br />
|example1 = var fp = flightplan();<br />
print(fp.getWP(fp.current).id);<br />
|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 <tt>''[[$FG_HOME]]/fp-demo.xml''</tt>.<br />
|example2 = var path = getprop('/sim/fg-home') ~ '/fp-demo.xml';<br />
var fp = flightplan(path);<br />
for(var i = 0; i < fp.getPlanSize(); i += 1){<br />
print(fp.getWP(i).id);<br />
}<br />
}}<br />
<br />
=== geodinfo() ===<br />
{{Nasal doc<br />
|syntax = geodinfo(lat, lon[, max_alt]);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=981|t=Source}}<br />
|text = Returns a vector containing two entries or <code>'''nil'''</code> 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 <tt>''[[$FG_ROOT]]/Materials''</tt>), or <code>'''nil'''</code> 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}}):<br />
* '''light_coverage:''' The coverage of a single point of light in m<sup>2</sup>.<br />
* '''bumpiness:''' Normalized bumpiness factor for the material.<br />
* '''load_resistance:''' The amount of pressure in N/m<sup>2</sup> the material can withstand without deformation.<br />
* '''solid:''' 1 (true) or false (0) depending on whether the material is solid or not.<br />
* '''names:''' Vector of scenery types (usually generated by [[TerraGear]]) that will use this material. <br />
* '''friction_factor:''' Normalized friction factor of the material.<br />
* '''rolling_friction:''' The rolling friction coefficient of the material.<br />
<br />
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.<br />
|param1 = lat<br />
|param1text = Latitude, inputted as a number.<br />
|param2 = lon<br />
|param2text = Longitude, inputted as a number.<br />
|param3 = max_alt<br />
|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, <code>'''nil'''</code> will be returned.<br />
|example1text = Dumps information about ground underneath the aircraft.<br />
|example1 = var pos = geo.aircraft_position();<br />
var info = geodinfo(pos.lat(), pos.lon());<br />
debug.dump(info);<br />
|example2text = Prints whether the ground underneath the aircraft is solid or is water.<br />
|example2 = var pos = geo.aircraft_position();<br />
var info = geodinfo(pos.lat(), pos.lon());<br />
if (info != nil and info[1] != nil) {<br />
print("The ground underneath the aircraft is ", info[1].solid == 1 ? "solid." : "water.");<br />
}<br />
}}<br />
<br />
=== geodtocart() ===<br />
{{Nasal doc<br />
|syntax = geodtocart(lat, lon, alt);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=962|t=Source}}<br />
|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.<br />
|param1 = lat<br />
|param1text = Latitude, in degrees.<br />
|param2 = lon<br />
|param2text = Longitude, in degrees.<br />
|param3 = alt<br />
|param3text = Altitude, in metres.<br />
|example1 = var (x, y, z) = geodtocart(0, 0, 0); # point is the intersection of the prime meridian and equator.<br />
print("x: ", x); # prints "x: 6378137"<br />
print("y: ", y); # prints "y: 0"<br />
print("z: ", z); # prints "y: 0"<br />
}}<br />
<br />
=== get_cart_ground_intersection() ===<br />
Introduced in 2017.2.1, see [[Terrain Detection]].<br />
<br />
=== getprop() ===<br />
{{Nasal doc<br />
|syntax = getprop(path[, path[, ...]]);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=345|t=Source}}<br />
|text = Returns the value of a node in the [[Property Tree]] or <code>'''nil'''</code> if the node does not exist or the value is not a number (NaN).<br />
|param1 = path<br />
|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.<br />
|example1 = print("You have FlightGear v", getprop("/sim/version/flightgear")); # prints FlightGear version<br />
|example2text = Note that the example below will only work in FlightGear 3.2 and above.<br />
|example2 = for(var i = 0; i < 8; i += 1){<br />
print("View #", i + 1, " is named ", getprop("/sim/view", i, "name"));<br />
}<br />
|example3text = Same as above, but is supported by all versions of FlightGear.<br />
|example3 = for(var i = 0; i < 8; i += 1){<br />
print("View #", i + 1, " is named ", getprop("/sim/view[" ~ i ~ "]/name"));<br />
}<br />
}}<br />
==== See also ====<br />
{{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: <br />
To get a Node rather than its value, use <code>props.globals.getNode()</code> - see [[Nasal_library/props]]. }}<br />
<br />
=== greatCircleMove() ===<br />
{{Nasal doc<br />
|syntax = greatCircleMove([pos, ]course, dist);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1681|t=Source}}<br />
|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).<br />
|param1 = pos<br />
|param1text = Optional position to calculate from. If not given, the aircraft's current position will be used. Can be one of:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost object.<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A <code>geo.Coord</code> object<br />
:* A lat/lon pair, that is, a pair of numbers (latitude followed by longitude) separated by a comma: <code>greatCircleMove(lat,lon, ...)</code>.<br />
|param2 = course<br />
|param2text = Course to new set of coordinates, in degrees (in the range 0–360).<br />
|param3 = dist<br />
|param3text = Distance in nautical miles to the new set of coordinates.<br />
|example1 = var pos = greatCircleMove(0,0, 0, 1);<br />
debug.dump(pos); # print hash with coordinates<br />
|example2 = var fix = findFixesByID("POGIC");<br />
fix = fix[0];<br />
var pos = greatCircleMove(fix, 45, 10);<br />
debug.dump(pos); # print hash with coordinates<br />
}}<br />
<br />
=== interpolate() ===<br />
{{Nasal doc<br />
|private = _interpolate()<br />
|syntax = interpolate(prop, value1, time1[, value2, time2[, ...]]);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=522|t=Part 1}} {{!}} {{fgdata file|Nasal/globals.nas|t=Part 2}}<br />
|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.<br />
|param1 = prop<br />
|param1text = String or <code>props.Node</code> object that indicates a node in the property tree to be used.<br />
|param2 = value''n''<br />
|param2text = Target value to change the property to in the set amount of time. This should be a number.<br />
|param3 = time''n''<br />
|param3text = Time in seconds, that will be taken for the interpolation.<br />
|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.<br />
|example1 = setprop("/test", 0); # (re-)set property<br />
interpolate("/test",<br />
50, 5, # interpolate to 50 in 5 seconds<br />
10, 2, # interpolate to 10 in 2 seconds<br />
0, 5); # interpolate to 0 in 5 seconds<br />
|example2 = # Apply the left brake at 20% per second<br />
var prop = "controls/gear/brake-left";<br />
var dist = 1 - getprop(prop);<br />
if (dist == 1) {<br />
interpolate(prop, 1, dist / 0.2);<br />
}<br />
}}<br />
<br />
=== isa() ===<br />
{{Nasal doc<br />
|syntax = isa(object, class);<br />
|source = {{fgdata file|Nasal/globals.nas|t=Source}}<br />
|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.<br />
|param1 = object<br />
|param1text = Object to check.<br />
|param2 = class<br />
|param2text = Class/object to check that '''object''' inherits from or is an instance of.<br />
|example1 = var coord = geo.Coord.new();<br />
if(isa(coord, geo.Coord)){<br />
print("Variable 'coord' is an instance of class 'geo.Coord'"); # this one will be printed<br />
} else {<br />
print("Variable 'coord' is not an instance of class 'geo.Coord'");<br />
}<br />
|example2 = var coord = geo.Coord.new();<br />
if(isa(coord, props.Node)){<br />
print("Variable 'coord' is an instance of class 'props.Node'");<br />
} else {<br />
print("Variable 'coord' is not an instance of class 'props.Node'"); # this one will be printed<br />
}<br />
|example3text = The example below demonstrates checking of inheritance.<br />
|example3 = var Const = {<br />
constant: 2,<br />
getConst: func {<br />
return me.constant;<br />
}<br />
};<br />
<br />
var Add = {<br />
new: func {<br />
return { parents: [Add, Const] };<br />
},<br />
<br />
addToConst: func(a){<br />
return a * me.getConst();<br />
}<br />
};<br />
<br />
var m = Add.new();<br />
print(m.addToConst(4));<br />
<br />
if(isa(m, Add)) print("Variable 'm' is an instance of class 'Add'"); # will be printed<br />
if(isa(m, Const)) print("Variable 'm' is an instance of class 'Const'"); # will also be printed<br />
}}<br />
<br />
=== logprint() ===<br />
{{Nasal doc<br />
|syntax = logprint(priority[, msg[, msg[, ...]]]);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=431|t=Source}}<br />
|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 <code>[[Commonly used debugging tools#fgfs.log|fgfs.log]]</code> file as coming from the Nasal file itself rather than from {{flightgear file|src/Scripting/NasalSys.cxx}}.<br />
|param1 = priority<br />
|param1text = Number specifying the priority level of the outputted message:<br />
:{{{!}} class="wikitable"<br />
! Number !! Debug type<br />
{{!-}}<br />
{{!}} 1 {{!!}} Bulk<br />
{{!-}}<br />
{{!}} 2 {{!!}} Debug<br />
{{!-}}<br />
{{!}} 3 {{!!}} Info<br />
{{!-}}<br />
{{!}} 4 {{!!}} Warn<br />
{{!-}}<br />
{{!}} 5 {{!!}} Alert<br />
{{!}}}<br />
|param2 = msg<br />
|param2text = The message. There is no limit to the arguments you give give. They will be concatenated together before logging.<br />
|example1 = # logs the value of pi to three decimal places with log level 3<br />
logprint(3, "pi = ", sprintf("%.3f", math.pi));<br />
|example2 = logprint(5, "Alert! This is an important message!");<br />
}}<br />
{{note| <br />
The following constants have been added to the development branch of FlightGear ("next") and will be releases with FG 2020.1 so you won't have to remember the numbers anymore:<br />
<br />
LOG_BULK, LOG_WARN, LOG_DEBUG, LOG_INFO, LOG_ALERT, DEV_WARN, DEV_ALERT }}<br />
<br />
=== magvar() ===<br />
{{Nasal doc<br />
|syntax = magvar([pos]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1642|t=Source}}<br />
|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.<br />
{{{!}} class="wikitable"<br />
! FlightGear versions !! Model !! Reference date<br />
{{!-}}<br />
{{!}} 3.6 and above {{!!}} {{wikipedia|World Magnetic Model}} (WMM) 2015 {{!!}} 1 January 2015<br />
{{!-}}<br />
{{!}} 0.9.11-pre1 to 3.4 {{!!}} WMM 2005 {{!!}} 1 January 2005<br />
{{!-}}<br />
{{!}} 0.7.3 to 0.9.10 {{!!}} WMM 2000 {{!!}} 1 January 2000<br />
{{!}}}<br />
|param1 = pos<br />
|param1text = Optional position to calculate from. If not given, the aircraft's current position will be used. Can be one of:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost object.<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A <code>geo.Coord</code> object<br />
:* A lat/lon pair, that is, a pair of numbers (latitude followed by longitude) separated by a comma: <code>magvar(lat,lon)</code>.<br />
|example1 = print(magvar(0, 0)); # prints the magnetic variation at 0, 0<br />
}}<br />
<br />
=== maketimer() ===<br />
{{Nasal doc<br />
|syntax = maketimer(interval[, self], function);<br />
|source = ''Implemented using the {{API Link|flightgear|class|TimerObj}} class.''<br>{{flightgear file|src/Scripting/NasalSys.cxx|l=90|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=533|t=Part 2}}<br />
|version = 2.12<br />
|commit = {{flightgear commit|ab939f|t=commit}}<br />
|text = Returns a timer object containing the following methods and members:<br />
* '''start()''': Starts the timer.<br />
* '''stop()''': Stops the timer.<br />
* '''restart(interval)''': Restarts the timer with the given interval.<br />
* '''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).<br />
* '''isRunning''': Read-only bool telling whether the timer is currently running.<br />
* '''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.<br />
Unlike {{func link|settimer()}}, which it replaces, <code>maketimer()</code> provides more control over the timer. In addition, it can help reduce memory usage.<br />
|param1 = interval<br />
|param1text = Interval in seconds for the timer.<br />
|param2 = self<br />
|param2text = Optional parameter specifying what any <code>'''me'''</code> references in the function being called will refer to.<br />
|param3 = function<br />
|param3text = Function to be called at the given interval.<br />
|example1 = var timer = maketimer(1, func(){<br />
print("Hello, World!"); # print "Hello, World!" once every second (call timer.stop() to stop it)<br />
});<br />
timer.start();<br />
|example2 = var timer = maketimer(1, math, func(){<br />
print(me.math); # 'me' reference is the 'math' namespace<br />
});<br />
timer.singleShot = 1; # timer will only be run once<br />
timer.start();<br />
|example3 = var timer = maketimer(1, func(){<br />
print("Hello, World!"); # print "Hello, World!" once every second (call timer.stop() to stop it)<br />
});<br />
timer.start();<br />
print(timer.isRunning); # prints 1<br />
|example4text = In the example below, "Hello, World!" will be printed after one second the first time, and after two seconds thereafter.<br />
|example4 = var update = func(){<br />
print("Hello, World!");<br />
timer.restart(2); # restarts the timer with a two second interval<br />
}<br />
<br />
var timer = maketimer(1, update);<br />
timer.singleShot = 1;<br />
timer.start();<br />
}}<br />
<br />
=== maketimestamp() ===<br />
{{Nasal doc<br />
|syntax = maketimestamp()<br />
|source = ''Implemented using the {{API Link|flightgear|class|TimeStampObj}} class.''<br>{{flightgear file|src/Scripting/NasalSys.cxx|l=214|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=589|t=Part 2}}<br />
|version = 2019.2<br />
|commit = {{flightgear commit|7db06300|t=commit}}<br />
|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:<br />
* '''stamp()''': Resets the timing operation. Call this first.<br />
* '''elapsedMSec()''': returns number of milliseconds elapsed since stamp() called. Resolution may vary depending on platform but is usually at least millisecond accuracy.<br />
* '''elapsedUSec()''': returns number of microseconds elapsed since stamp() called. Resolution may vary depending on platform but is usually at least millisecond accuracy.<br />
|<br />
|example1text = In the example below the number of milliseconds elapsed will be printed.<br />
|example1 = var timestamp = maketimestamp();<br />
timestamp.stamp();<br />
print(timestamp.elapsedMSec(), "ms elapsed");<br />
print(timestamp.elapsedMSec(), "ms elapsed");<br />
}}<br />
<br />
=== md5() ===<br />
{{Nasal doc<br />
|syntax = md5(string);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=758|t=Source}}<br />
|version = 3.2<br />
|commit = {{flightgear commit|cfbf9e|t=commit}}<br />
|text = Returns a the {{wikipedia|MD5}} hash (as a string) of the inputted string.<br />
|param1 = string<br />
|param1text = String the generate the hash of. Mandatory.<br />
|example1text = The below code should output <code>65a8e27d8879283831b664bd8b7f0ad4</code>.<br />
|example1 = print(md5("Hello, World!"));<br />
}}<br />
<br />
=== navinfo() ===<br />
{{Nasal doc<br />
|syntax = navinfo(lat, lon, type, id);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1453|t=Source}}<br />
|text = Returns vector <code>navaid</code> ghost objects matching the given '''type''' and '''id''' or <code>'''nil'''</code> on error.<br />
|param1 = lat ''and'' lon<br />
|param1text = If given, the returned navaids will be put into order of ascending distance from the location.<br />
|param2 = type<br />
|param2text = Narrows the search to the given type. Must be one of "any," "fix," "vor," "ndb," "ils," "dme," or "tacan." Defaults to the equivalent of "any."<br />
|param3 = id<br />
|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.<br />
|example1 = navinfo("vor"); # returns all VORs<br />
|example2 = navinfo("HAM"); # return all navaids whose names start with "HAM"<br />
|example3 = navinfo("vor", "HAM"); # return all VORs whose names start with "HAM"<br />
|example4 = navinfo(34,48,"vor","HAM"); # return all VORs whose names start with "HAM" and sorted by distance relative to 34°, 48°<br />
}}<br />
<br />
=== parse_markdown() ===<br />
{{Nasal doc<br />
|syntax = parse_markdown(markdown);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=745|t=Part 1}} {{!}} {{simgear file|simgear/misc/SimpleMarkdown.cxx|t=Part 2}} <br />
|version = 3.2<br />
|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:<br />
* It strips whitespace from the beginning of the string.<br />
* It supports [http://daringfireball.net/projects/markdown/syntax#p paragraphs and line breaks].<br />
* It collapses whitespace.<br />
* It converts unordered [http://daringfireball.net/projects/markdown/syntax#list lists] to use a bullet character (&bull;). Note that the bullet character is implemented in hexadecimal UTF-8 character bytes (<code>E2 80 A2</code>), as so may not work properly when the being displayed in an encoding other than UTF-8.<br />
|param1 = markdown<br />
|param1text = String containing Markdown to be parsed.<br />
|example1text = <br />
Save the below code as <tt>''[[$FG_ROOT]]/gui/dialogs/test.xml''</tt>, 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 (<tt>Debug > Reload GUI</tt>).<br />
<syntaxhighlight lang="xml" highlight="41-50"><br />
<?xml version="1.0" encoding="UTF-8"?><br />
<br />
<PropertyList><br />
<br />
<name>test</name><br />
<layout>vbox</layout><br />
<br />
<group><br />
<layout>hbox</layout><br />
<br />
<empty><br />
<stretch>true</stretch><br />
</empty><br />
<br />
<text><br />
<label>parse_markdown() test dialog</label><br />
</text><br />
<br />
<empty><br />
<stretch>true</stretch><br />
</empty><br />
<br />
<button><br />
<legend></legend><br />
<pref-width>16</pref-width><br />
<pref-height>16</pref-height><br />
<binding><br />
<command>dialog-close</command><br />
</binding><br />
</button><br />
<br />
</group><br />
<br />
<canvas><br />
<name>Canvas plot</name><br />
<stretch>true</stretch><br />
<pref-width>400</pref-width><br />
<pref-height>300</pref-height><br />
<nasal><br />
<load><![CDATA[<br />
var text = 'Items:<br />
* apples<br />
* oranges<br />
* pears<br />
<br />
Some text.<br />
Some more items:<br />
* apples<br />
* oranges<br />
* pears';<br />
<br />
var parsed = parse_markdown(text);<br />
<br />
var root_canvas = canvas.get(cmdarg());<br />
root_canvas.setColorBackground(255, 255, 255);<br />
var root = root_canvas.createGroup();<br />
<br />
var text_dis = root.createChild("text")<br />
.setText(parsed)<br />
.setTranslation(5, 5)<br />
.setFont("LiberationFonts\LiberationSans-Regular.ttf")<br />
.setFontSize(15)<br />
.setColor(0, 0, 0)<br />
.setDrawMode(canvas.Text.TEXT)<br />
.setAlignment("left-top");<br />
]]></load><br />
</nasal><br />
</canvas><br />
<br />
</PropertyList><br />
</syntaxhighlight><br />
|example1 = fgcommand("dialog-show", {"dialog-name": "test"});<br />
|example2text = The example below parses Markdown and outputs it in a HTML document. The parsed text is placed in <syntaxhighlight lang="xml" inline><pre></pre></syntaxhighlight> tags. To change the Markdown to be parsed, simply edit the variable <tt>markdown</tt> at the top of the code.<br />
|example2 = <nowiki>var markdown = 'Items:<br />
* apples<br />
* oranges<br />
* pears<br />
<br />
Some text.<br />
Some more items:<br />
* apples<br />
* oranges<br />
* pears';<br />
<br />
var parsed = parse_markdown(markdown);<br />
<br />
debug.dump(parsed);<br />
<br />
var path = string.normpath(getprop("/sim/fg-home") ~ '/Export/parse_markdown()-test.html');<br />
<br />
var file = io.open(path, "w");<br />
<br />
var html = "<!DOCTYPE html>\n\n<html>\n\n<head>\n\t<meta charset=\"UTF-8\">\n\t<title>parse_markdown() test generated by Nasal</title>\n</head>\n\n<body>\n\t<pre>" ~ parsed ~ "</pre>\n</body>\n\n</html>";<br />
<br />
io.write(file, html);<br />
io.close(file);<br />
print("Done, file ready for viewing (" ~ path ~ ")");</nowiki><br />
}}<br />
<br />
=== parsexml() ===<br />
{{Nasal doc<br />
|syntax = parsexml(path[, start[, end[, data[, pro_ins]]]]);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=710|t=Source}}<br />
|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 <code>'''nil'''</code> is returned on error.<br />
|param1 = path<br />
|param1text = Mandatory absolute path to the XML file to be parsed.<br />
|param2 = start<br />
|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.<br />
|param3 = end<br />
|param3text = Optional callback function that will be called for every ending tag. The function should take one argument: the tag name.<br />
|param4 = data<br />
|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.<br />
|param5 = pro_ins<br />
|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.<br />
|example1text = Save the below XML code in <tt>''[[$FG_HOME]]/Export/demo.xml''</tt>. Then, execute the Nasal code below in the Nasal Console. The XML will be parsed and each bit of the code will be printed.<br />
<syntaxhighlight lang="xml"><br />
<?xml version="1.0" encoding="UTF-8"?><br />
<br />
<?xml-stylesheet type="text/xsl" href="style.xsl"?><br />
<br />
<foo><br />
<blah type="string"><![CDATA[ <sender>John Smith</sender> ]]></blah><br />
<blah2 type="string">Orange &amp; lemons</blah2><br />
</foo><br />
</syntaxhighlight><br />
|example1 = var start = func(name, attr){<br />
print("Starting tag: '", name, "'");<br />
foreach(var a; keys(attr)){<br />
print("\twith attribute ", a, '="', attr[a], '"');<br />
}<br />
}<br />
<br />
var end = func(name){<br />
print("Ending tag: '", name, "'");<br />
}<br />
<br />
var data = func(data){<br />
print("Data = '", data, "'");<br />
}<br />
<br />
var pro_instr = func(target, data){<br />
print("Processing instruction: target = '", target, "', data = '", data, "'");<br />
}<br />
<br />
parsexml(getprop("/sim/fg-home") ~ '/Export/demo.xml', start, end, data, pro_instr);<br />
}}<br />
<br />
=== print() ===<br />
{{Nasal doc<br />
|syntax = print(data[, data[, ...]]);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=415|t=Source}}<br />
|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.<br />
|param1 = data<br />
|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.<br />
|example1 = print("Just", " a ", "test"); # prints "Just a test"<br />
|example2 = print("pi = ", math.pi); # prints "pi = 3.141592..."<br />
}}<br />
<br />
=== printf() ===<br />
{{Nasal doc<br />
|syntax = printf(format[, arg[, arg, [...]]]);<br />
|source = {{fgdata file|Nasal/globals.nas|t=Source}}<br />
|text = Creates and prints a formatted string. For a description of its arguments, see {{func link|sprintf()}} (it is, in fact, implemented using <code>sprintf()</code>).<br />
|example1 = printf("In hexadecimal, 100000 = %X", 186A0); # prints "In hexadecimal, 100000 = 186A0"<br />
}}<br />
<br />
=== printlog() ===<br />
{{Nasal doc<br />
|syntax = printlog(level, data[, data[, ...]]);<br />
|source = {{fgdata file|Nasal/globals.nas|t=Source}}<br />
|text = Prints the given message with the given log level. If the log level is higher or equal to <code>/sim/logging/priority</code>, it is printed.<br />
|param1 = level<br />
|param1text = Mandatory log level as a string. Must be one of "none," "bulk," "debug," "info," "warn," or "alert." Note that "none" will mean that the message will never be printed.<br />
|param2 = data<br />
|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.<br />
|example1 = printlog("alert", "This is an alert"); # message will be printed<br />
|example2 = printlog("info", "Just informing you about something"); # message will be printed only if log level is set to "info" or less<br />
}}<br />
<br />
=== rand() ===<br />
{{Nasal doc<br />
|syntax = rand();<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=554|t=Source}}<br />
|text = Returns a random floating point number between 0 (inclusive) and 1 (exclusive). It takes no arguments.<br />
|example1 = print(rand()); # prints random number<br />
}}<br />
<br />
=== registerFlightPlanDelegate() ===<br />
{{Nasal doc<br />
|syntax = registerFlightPlanDelegate(init_func);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1879|t=Source}}<br />
|text = Registers a flight plan delegate. See <tt>''{{fgdata file|Nasal/route_manager.nas|t=$FG_ROOT/Nasal/route_manager.nas}}''</tt> for examples.<br />
|param1 = init_func<br />
|param1text = Initialization function which will be called during FlightGear's startup.<br />
}}<br />
=== removecommand() ===<br />
{{Nasal doc<br />
|syntax = removecommand(cmd);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=668|t=Source}}<br />
|text = Removes the given fgcommand. Returns <code>'''nil'''</code>.<br />
{{caution|This will remove '''any''' [[fgcommands|fgcommand]], even those implemented in C++, so use with caution!}}<br />
|param1 = cmd<br />
|param1text = String specifying the name of the command to remove.<br />
|example1 = addcommand("hello", func(){<br />
print("Hello");<br />
});<br />
fgcommand("hello"); # "Hello" will be printed<br />
removecommand("hello"); # removes it<br />
}}<br />
<br />
=== removelistener() ===<br />
{{Nasal doc<br />
|syntax = removelistener(id);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=1384|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=506|t=Part 2}}<br />
|text = Removes and deactivates the given listener and returns the number of listeners left or <code>'''nil'''</code> on error.<br />
{{note|It is good practice to remove listeners when they are not required anymore. This prevents the listeners reducing FlightGear's run performance.}}<br />
|param1 = id<br />
|param1text = ID of listener as returned by {{func link|setlistener()}}.<br />
|example1 = var ls = setlistener("/sim/test", func(){<br />
print("Property '/sim/test' has been changed");<br />
});<br />
setprop("/sim/test", "blah"); # trigger listener<br />
var rem = removelistener(ls); # remove listener<br />
print("There are ", rem, " listeners remaining");<br />
}}<br />
<br />
=== resolvepath() ===<br />
{{Nasal doc<br />
|syntax = resolvepath(path);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=604|t=Source}}<br />
|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.<br />
|param1 = path<br />
|param1text = Relative path to be completed.<br />
|example1 = print(resolvepath("Nasal/globals.nas")); # prints the equivalent of $FG_ROOT/Nasal/globals.nas<br />
|example2 = print(resolvepath("blah")); # prints nothing; could not be resolved<br />
|example3 = var file_path = resolvepath("Aircraft/SenecaII/some-file");<br />
if (file_path != ""){<br />
gui.popupTip("some-file found", 2);<br />
} else {<br />
gui.popupTip("some-file not found", 2);<br />
}<br />
}}<br />
<br />
=== setlistener() ===<br />
{{Nasal doc<br />
|syntax = setlistener(node, code[, init[, type]]);<br />
|private = _setlistener()<br />
|source = {{flightgear file|src/Scripting/Nasal|l=499|t=Part 1}} {{!}} {{flightgear file|src/Scripting/Nasal|l=1350|t=Part 2}}<br>''Listener implemented using the {{API Link|flightgear|class|FGNasalListener}} class.''<br />
|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()}}.<br />
{{note|Listeners are known to be a source of resource leaks. To avoid this, please take measures such as:<br />
* Using {{func link|removelistener()}} when they are not needed any more.<br />
* Using a single initialization listener.<br />
* Avoiding tying listeners to properties that are rapidly updated (e.g., many times per frame).<br />
}}<br />
|param1 = node<br />
|param1text = Mandatory string or <code>props.Node</code> object pointing to a property in the [[Property Tree]].<br />
|param2 = code<br />
|param2text = Mandatory callback function to execute when the listener is triggered. The function can take up to four arguments in the following order:<br />
* '''changed''': a <code>props.Node</code> object pointing to the changed node.<br />
* '''listen''': a <code>props.Node</code> object pointing to the listened-to node. Note that this argument maybe different depending on the '''type'''.<br />
* '''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.<br />
* '''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.<br />
|param3 = init<br />
|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).<br />
|param4 = type<br />
|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.<br />
|example1 = var prop = props.globals.initNode("/sim/test", "", "STRING"); # create property<br />
var id = setlistener("/sim/test", func(n){ # create listener<br />
print("Value: ", n.getValue());<br />
});<br />
setprop("/sim/test", "blah"); # trigger listener<br />
removelistener(id); # remove listener<br />
prop.remove(); # remove property<br />
}}<br />
<br />
=== setprop() ===<br />
{{Nasal doc<br />
|syntax = setprop(path[, path[, ...]], value);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=385|t=Source}}<br />
|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.<br />
{{note|If you want to remove a property, you will have to use one of the <code>props</code> helpers:<br />
<syntaxhighlight lang="nasal"><br />
props.globals.getNode("foo/bar").remove(); # take out the complete node<br />
props.globals.getNode("foo").removeChild("bar"); # take out a certain child node<br />
</syntaxhighlight><br />
}}<br />
|param1 = path<br />
|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.<br />
|param2 = value<br />
|param2text = Value to write to the given property. Must be either a string or a number.<br />
|example1 = setprop("/sim/demo", "This is a demo");<br />
|example2text = Note that the example below will only work in FlightGear 3.2 and above.<br />
|example2 = for(var i = 0; i < 3; i += 1){<br />
setprop("/sim/demo", i, "Demo #" ~ i));<br />
}<br />
|example3text = Same as above, but is supported by all versions of FlightGear.<br />
|example3 = for(var i = 0; i < 3; i += 1){<br />
setprop("/sim/demo[" ~ i ~ "]", "Demo #" ~ i));<br />
}<br />
}}<br />
==== See also ====<br />
{{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: <br />
To get a Node rather than its value, use <code>props.globals.getNode()</code> - see [[Nasal_library/props]]. }}<br />
<br />
=== settimer() ===<br />
{{Nasal doc<br />
|syntax = settimer(function, delta[, realtime]);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=499|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=1286|t=Part 2}}<br />
|text = Runs the given function a specified amount of seconds after the current time. Returns <code>'''nil'''</code>.<br />
{{note|Improper use of <code>settimer()</code> may cause resource leaks. It is also highly recommended that the newer {{func link|maketimer()}} should be used instead of this function.}} <br />
|param1 = function<br />
|param1text = Mandatory function that will be called. It may be necessary to enclose code in an anonymous function (see example).<br />
|param2 = delta<br />
|param2text = Mandatory amount of time in seconds after which the function will be called.<br />
|param3 = realtime<br />
|param3text = If 1 (true), "real time" will be used instead of "simulation time." Defaults to 0 (false). Note that if "simulation time" is used, the timer will not run while FlightGear is paused.<br />
|example1 = var myFunc = func(){<br />
print("Hello");<br />
}<br />
<br />
settimer(myFunc, 2); # runs myFunc after 2 seconds<br />
|example2 = var sqr = func(a){<br />
return a * a;<br />
}<br />
<br />
settimer(func(){<br />
print(sqr(2)); # will print 4 after 2 seconds<br />
}, 2);<br />
}}<br />
<br />
=== srand() ===<br />
{{Nasal doc<br />
|syntax = srand();<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=559|t=Source}}<br />
|text = Makes the pseudorandom number generator (see {{func link|rand()}}) generate a new {{wikipedia|random seed|noicon=1}} based on time. Returns 0.<br />
}}<br />
=== systime() ===<br />
{{Nasal doc<br />
|syntax = systime();<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=770|t=Source}}<br />
|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).<br />
{{note|1=High resolution timers under Windows can produce inaccurate or fixed sub-millisecond results.<ref>{{cite web|url=http://forum.flightgear.org/viewtopic.php?f=30&t=29259|title=Nasal: systime() ??!?|author=Necolatis|date=Apr 2nd, 2016}}</ref> 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]}}<br />
|example1 = print("Unix time: ", systime()); # prints Unix time<br />
|example2 = var myFunc = func(){<br />
for(var i = 0; i <= 10; i += 1){<br />
print("Interation #", i);<br />
}<br />
}<br />
var t = systime(); # record time<br />
myFunc(); # run function<br />
var t2 = systime(); # record new time<br />
print("myFunc() took ", t2 - t, " seconds"); # print result<br />
}}<br />
<br />
=== thisfunc() ===<br />
{{Nasal doc<br />
|syntax = thisfunc();<br />
|source = {{fgdata file|Nasal/globals.nas|t=Source}}<br />
|text = Returns the function from which this function is called. This allows a function to reliably and safely call itself from within a closure.<br />
|example1 = var stringify_vec = func(input){<br />
if (typeof(input) == "scalar"){<br />
return sprintf("%s", input);<br />
} elsif (typeof(input) == "vector") {<br />
if (size(input) == 0) return "[]";<br />
var this = thisfunc();<br />
var buffer = "[";<br />
for(var i = 0; i < size(input); i += 1){<br />
buffer ~= this(input[i]);<br />
if (i == size(input) - 1) {<br />
buffer ~= "]";<br />
} else {<br />
buffer ~= ", ";<br />
}<br />
}<br />
return buffer;<br />
} else {<br />
die("stringify_vec(): Error! Invalid input. Must be a vector or scalar");<br />
}<br />
}<br />
<br />
var test_vec = ["a", "b", "c", 1, 2, 3];<br />
debug.dump(stringify_vec(test_vec)); # prints "[a, b, c, 1, 2, 3]"<br />
test_vec = [];<br />
debug.dump(stringify_vec(test_vec)); # prints "[]"<br />
test_vec = {};<br />
debug.dump(stringify_vec(test_vec)); # will throw an error<br />
}}<br />
<br />
=== tileIndex() ===<br />
{{Nasal doc<br />
|syntax = tileIndex();<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1720|t=Source}}<br />
|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 <code>942050</code>, corresponding to <tt>''[[$FG_SCENERY]]/Terrain/w130n30/w123n3/942050.stg''</tt>.<br />
|example1 = print(tileIndex()); # print index<br />
}}<br />
<br />
=== tilePath() ===<br />
{{Nasal doc<br />
|syntax = tilePath();<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1712|t=Source}}<br />
|text = Returns the base path of the tile at the aircraft's current position as a string. For example, at KSFO, this would be <code>w130n30/w123n3</code>, corresponding to <tt>''[[$FG_SCENERY]]/Terrain/w130n30/w123n3''</tt>.<br />
|example1 = print(tilePath()); # print path<br />
}}<br />
<br />
=== values() ===<br />
{{Nasal doc<br />
|syntax = values(hash);<br />
|source = {{fgdata file|Nasal/globals.nas|t=Source}}<br />
|text = Returns a vector containing the values of the given hash.<br />
|param1 = hash<br />
|param1text = Mandatory hash to get the values of.<br />
|example1 = var hash = {<br />
"a": 1,<br />
"b": 2,<br />
"c": 3<br />
};<br />
<br />
foreach(var val; values(hash)){<br />
print(val);<br />
}<br />
|example2text = The below example does exactly the same thing as the above example, but does not use <code>values()</code>:<br />
|example2 = var hash = {<br />
"a": 1,<br />
"b": 2,<br />
"c": 3<br />
};<br />
<br />
foreach(var key; keys(hash)){<br />
print(hash[key]);<br />
}<br />
}}<br />
<br />
== Extension functions new in FG 2020.1 ==<br />
The following functions have been added to the nasal core library and will be released with FlightGear version 2020.1. <br />
Before the release they are available in the development branch "next".<br />
<br />
=== isfunc() ===<br />
Returns 1 if type or argument is a function, otherwise 0.<br />
<br />
=== isghost() ===<br />
Returns 1 if type or argument is a ghost, otherwise 0.<br />
<br />
=== ishash() ===<br />
Returns 1 if type or argument is a hash, otherwise 0.<br />
<br />
=== isint() ===<br />
{{Nasal doc<br />
|syntax = isint(x);<br />
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}<br />
|text = Returns 1 if argument has a numeric value and x == floor(x), e.g. integer.<br />
}}<br />
<br />
=== isnum() ===<br />
{{Nasal doc<br />
|syntax = isnum(x);<br />
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}<br />
|text = Returns 1 if typeof(x) is "scalar" and x has a numeric value otherwise 0. <br />
}}<br />
<br />
=== isscalar() ===<br />
{{Nasal doc<br />
|syntax = isnum(x);<br />
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}<br />
|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 "~". <br />
|example1 = var a = "foo"; <br />
var b=42;<br />
if (isscalar(a) and isscalar(b)) print(a~b);<br />
if (isstr(a)) print("a is a string");<br />
if (isint(b)) print("b is an integer");<br />
# if (isscalar(a))... is equivalent to if (typeof(a) == "scalar")...<br />
}}<br />
<br />
=== isstr() ===<br />
Returns 1 if type or argument is a string, otherwise 0. <br />
<br />
=== isvec() ===<br />
Returns 1 if type or argument is a vector, otherwise 0.<br />
<br />
=== vecindex() ===<br />
{{Nasal doc<br />
|syntax = vecindex(vector, value);<br />
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}<br />
|text = Returns the index of value or nil, if value is not found in vector.<br />
}}<br />
<br />
== Variables ==<br />
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}}.<br />
<br />
=== D2R ===<br />
{{Nasal doc<br />
|syntax = var radians = degrees * D2R;<br />
|text = Converts an angle from degrees to radians when multiplied by the angle in degrees. Equal to <code>π / 180</code>.<br />
}}<br />
=== FPS2KT ===<br />
{{Nasal doc<br />
|syntax = var knots = feet_per_second * FPS2KT;<br />
|text = Converts a velocity from feet per second to knots when multiplied by the velocity in feet per second. Approximately equal to 0.5925.<br />
}}<br />
=== FT2M ===<br />
{{Nasal doc<br />
|syntax = var metres = feet * FT2M;<br />
|text = Converts a length from feet to metres when multiplied by the length in feet. Equal to 0.3048.<br />
}}<br />
=== GAL2L ===<br />
{{Nasal doc<br />
|syntax = var litres = gallons * GAL2L;<br />
|text = Converts a volume from US liquid gallons to litres when multiplied by the volume in gallons. Approximately equal to 3.7854.<br />
}}<br />
=== IN2M ===<br />
{{Nasal doc<br />
|syntax = var metres = inches * IN2M;<br />
|text = Converts a length from inches to metres when multiplied by the length in inches. Equal to 0.0254.<br />
}}<br />
=== KG2LB ===<br />
{{Nasal doc<br />
|syntax = var pounds = kilograms * KG2LB;<br />
|text = Converts a mass from kilograms to pounds when multiplied by the mass in kilograms. Approximately equal to 2.2046.<br />
}}<br />
=== KT2FPS ===<br />
{{Nasal doc<br />
|syntax = var feet_per_second = knots * KT2FPS;<br />
|text = Converts a velocity from knots to feet per second when multiplied by the velocity in knots. Approximately equal to 1.6878.<br />
}}<br />
=== KT2MPS ===<br />
{{Nasal doc<br />
|syntax = var metres_per_second = knots * KT2MPS;<br />
|text = Converts a velocity from knots to metres per second when multiplied by the velocity in knots. Approximately equal to 0.5144.<br />
}}<br />
=== L2GAL ===<br />
{{Nasal doc<br />
|syntax = var gallons = litres * L2GAL;<br />
|text = Converts a volume from litres to US liquid gallons when multiplied by the volume in litres. Approximately equal to 0.2642.<br />
}}<br />
=== LB2KG ===<br />
{{Nasal doc<br />
|syntax = var kilograms = pounds * LB2KG;<br />
|text = Converts a mass from pounds to kilograms when multiplied by the mass in pounds. Approximately equal to 0.4536.<br />
}}<br />
=== M2FT ===<br />
{{Nasal doc<br />
|syntax = var feet = metres * M2FT;<br />
|text = Converts a length from metres to feet when multiplied by the length in metres. Approximately equal to 3.2808.<br />
}}<br />
=== M2IN ===<br />
{{Nasal doc<br />
|syntax = var inches = metres * M2IN;<br />
|text = Converts a length from metres to inches when multiplied by the length in metres. Approximately equal to 39.3701.<br />
}}<br />
=== M2NM ===<br />
{{Nasal doc<br />
|syntax = var nautical_miles = metres * M2NM;<br />
|text = Converts a length from metres to nautical miles when multiplied by the length in metres. Approximately equal to 0.00054.<br />
}}<br />
=== MPS2KT ===<br />
{{Nasal doc<br />
|syntax = var knots = metres_per_second * MPS2KT;<br />
|text = Converts a velocity from metres per second to knots when multiplied by the velocity in metres per second. Approximately equal to 1.9438.<br />
}}<br />
=== NM2M ===<br />
{{Nasal doc<br />
|syntax = var metres = nautical_miles * NM2M;<br />
|text = Converts a length from nautical miles to metres when multiplied by the length in nautical miles. Equal to 1,852.<br />
}}<br />
=== R2D ===<br />
{{Nasal doc<br />
|syntax = var degrees = radians * R2D;<br />
|text = Converts an angle from radians to degrees when multiplied by the angle in radians. Equal to <code>180 / π</code>.<br />
}}<br />
<br />
{{Appendix}}<br />
<br />
<br />
{{Nasal namespaces}}</div>Jsbhttps://wiki.flightgear.org/w/index.php?title=Nasal_library&diff=123791Nasal library2020-04-17T16:04:41Z<p>Jsb: /* streq() */</p>
<hr />
<div>{{Nasal Navigation|nocat=1}}<br />
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:<br />
* {{flightgear file|src/Scripting}}<br />
* {{simgear file|simgear/nasal}}<br />
<br />
All these functions and variables are in the global namespace, that is, they are directly accessible (e.g., one can call <syntaxhighlight lang="nasal" inline>magvar()</syntaxhighlight> instead of <syntaxhighlight lang="nasal" inline>namespace.magvar()</syntaxhighlight>). However, if a namespace must be used, <code>globals</code> is the correct namespace, but using it is not recommended. For a more complete explanation, see [[Nasal Namespaces in-depth]].<br />
<br />
{{tip|Copy & paste the examples into your [[Nasal Console]] and execute them to see what they do.|width=70%}}<br />
<br />
== Core library functions ==<br />
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:<br />
* http://plausible.org/nasal/lib.html ([http://web.archive.org/web/20101010094553/http://plausible.org/nasal/lib.html archive])<br />
* {{simgear file|simgear/nasal/lib.c}} ([http://sourceforge.net/p/flightgear/simgear/ci/next/log/?path=/simgear/nasal/lib.c history])<br />
<br />
=== append() ===<br />
{{Nasal doc<br />
|syntax = append(vector, element[, element[, ...]]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=42|t=Source}}<br />
|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.<br />
|param1 = vector<br />
|param1text = The vector to which the arguments will be appended.<br />
|param2 = element<br />
|param2text = An element to be added to the vector.<br />
|example1 = <br />
var vector = [1, 2, 3]; # Initialize the vector<br />
append(vector, 4); # Append the number 4 to the end of the vector<br />
debug.dump(vector); # Print the contents of the vector<br />
|example2 = <br />
var vector = [1, 2, 3]; # Initialize the vector<br />
append(vector, 4, 5, 6); # Append the numbers 4, 5, and 6 to the end of the vector<br />
debug.dump(vector); # Print the contents of the vector<br />
}}<br />
<br />
=== bind() ===<br />
{{Nasal doc<br />
|syntax = bind(function, locals[, outer_scope]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=502|t=Source}}<br />
|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.<br />
|param1 = function<br />
|param1text = Function to evaluate.<br />
|param2 = locals<br />
|param2text = Hash containing values that will become the namespace (first closure) for the function.<br />
|param3 = outer_scope<br />
|param3text = Optional function which is bound to the next closure. This can be bound to yet another, making a linked list.<br />
|example1 = # This is a namespace/hash with a single member, named "key," which is initialized to 12 <br />
var Namespace = {<br />
key: 12<br />
};<br />
<br />
# This is different namespace/hash containing a function<br />
# dividing a variable "key" (which is unavailable/nil in this namespace) by 2<br />
var AnotherNamespace = {<br />
ret: func {<br />
key /= 2;<br />
}<br />
};<br />
<br />
# To see that key is not available, try to call AnotherNamespace.ret() first<br />
call(AnotherNamespace.ret, [], nil, nil, var errors = []);<br />
if(size(errors)){<br />
print("Key could not be divided/resolved!");<br />
debug.printerror(errors);<br />
}<br />
<br />
# Associate the AnotherNamespace.ret() function with the first namespace<br />
# so that "key" is now available<br />
var function = bind(AnotherNamespace.ret, Namespace);<br />
<br />
# Invoke the new function<br />
function();<br />
<br />
# Print out the value of Namespace.key<br />
# It was changed to 12 from 6 by AnotherNamespace.ret()<br />
print(Namespace.key);<br />
}}<br />
<br />
=== call() ===<br />
{{Nasal doc<br />
|syntax = call(func[, args[, me[, locals[, error]]]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=247|t=Source}}<br />
|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. <br />
|param1 = func<br />
|param1text = Function to execute.<br />
|param2 = args<br />
|param2text = Vector containing arguments to give to the called function.<br />
|param3 = me<br />
|param3text = <code>'''me'''</code> reference for the function call (i.e., for method calls). If given, this will override any <code>'''me'''</code> value existing in the namespace (locals argument).<br />
|param4 = locals<br />
|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.<br />
|param5 = error<br />
|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}}.<br />
|example1 =<br />
# prints "Called from call()"<br />
call(func {<br />
print("Called from call()");<br />
});<br />
|example2 =<br />
# prints "a = 1 : b = 2<br />
call(func(a, b){<br />
print("a = ", a, " : b = ", b);<br />
},<br />
[1, 2]<br />
);<br />
|example3 =<br />
var Hash = {<br />
new: func {<br />
var m = { parents: [Hash] };<br />
<br />
m.el1 = "string1";<br />
m.el2 = "string2";<br />
<br />
return m;<br />
}<br />
};<br />
<br />
# prints "me.el1 = string1", then "me.el2 = string2" on the next line<br />
call(func(a, b){ <br />
print("me.el", a, " = ", me["el" ~ a]); <br />
print("me.el", b, " = ", me["el" ~ b]);<br />
},<br />
[1, 2],<br />
Hash.new()<br />
);<br />
|example4 =<br />
# prints the value of math.pi<br />
call(func {<br />
print(pi);<br />
}, nil, nil, <br />
math<br />
);<br />
|example5 =<br />
call(func {<br />
print(math.ip); # math.ip doesn't exist<br />
}, nil, nil, nil,<br />
var errs = []<br />
);<br />
debug.printerror(errs); # The error is caught and printed using debug.printerror()<br />
}}<br />
<br />
=== caller() ===<br />
{{Nasal doc<br />
|syntax = caller([level]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=404|t=Source}}<br />
|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.<br />
<br />
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. <br />
|param1 = level<br />
|param1text = Optional integer specifying the stack level to return a result from. Defaults to 1 (i.e. the caller of the currently executing function).<br />
|example1 =<br />
var myFunction = func(a, b){<br />
debug.dump(caller(0)[0]); # prints a hash of local variables, including arguments a and b<br />
return 2 * 2;<br />
};<br />
<br />
print("2 x 2 = ", myFunction(2, 2));<br />
|example2 =<br />
var get_arg_value = func(){<br />
print("Argument to myFunc = ", caller(1)[0]['a']); # print the value of myFunc's single argument, using caller()<br />
};<br />
<br />
var myFunc = func(a){<br />
get_arg_value();<br />
};<br />
<br />
myFunc(3);<br />
|example3text = This is a real example taken from {{fgdata file|Nasal/canvas/MapStructure.nas}}. Function <code>r()</code> (above the TODOs) returns a hash with the key/value pairs as per its arguments. For example, something like this is returned: <code>{ name: "<name>", vis: 1, zindex: nil }</code>.<br />
|example3 =<br />
var MapStructure_selfTest = func() {<br />
var temp = {};<br />
temp.dlg = canvas.Window.new([600,400],"dialog");<br />
temp.canvas = temp.dlg.createCanvas().setColorBackground(1,1,1,0.5);<br />
temp.root = temp.canvas.createGroup();<br />
var TestMap = temp.root.createChild("map");<br />
TestMap.setController("Aircraft position");<br />
TestMap.setRange(25); # TODO: implement zooming/panning via mouse/wheel here, for lack of buttons :-/<br />
TestMap.setTranslation(<br />
temp.canvas.get("view[0]")/2,<br />
temp.canvas.get("view[1]")/2<br />
);<br />
var r = func(name,vis=1,zindex=nil) return caller(0)[0];<br />
# TODO: we'll need some z-indexing here, right now it's just random<br />
# TODO: use foreach/keys to show all layers in this case by traversing SymbolLayer.registry direclty ?<br />
# maybe encode implicit z-indexing for each lcontroller ctor call ? - i.e. preferred above/below order ?<br />
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'), ] ) <br />
TestMap.addLayer(factory: canvas.SymbolLayer, type_arg: type.name,<br />
visible: type.vis, priority: type.zindex,<br />
);<br />
}; # MapStructure_selfTest<br />
}}<br />
<br />
=== chr() ===<br />
{{Nasal doc<br />
|syntax = chr(code);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=175|t=Source}}<br />
|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 (<code>"string"</code>) supports control chracters. Strings in single quotes (<code>'string'</code>) do not.}}<br />
{{{!}} class="wikitable"<br />
! Code !! Name !! Equivalent to<br />
{{!-}}<br />
{{!}} 10 {{!!}} {{Wikipedia|Newline}} {{!!}} <code>\n</code><br />
{{!-}}<br />
{{!}} 9 {{!!}} {{Wikipedia|Tab key#Tab characters|Horizontal tab}} {{!!}} <code>\t</code><br />
{{!-}}<br />
{{!}} 13 {{!!}} {{Wikipedia|Carriage return}} {{!!}} <code>\r</code><br />
{{!}}}<br />
|param1 = code<br />
|param1text = Integer character code for the desired glyph.<br />
|example1 = print("Code 65 = ", chr(65)); # prints "Code 65 = A"<br />
|example2text = This example displays all of the characters in a list, in the format <code>Code '''n''' = >'''char'''<</code>, '''n''' being the index, and '''char''' being the character.<br />
|example2 =<br />
for(var i = 0; i <= 255; i += 1){<br />
print("Code ", i, " = >", chr(i), "<");<br />
}<br />
}}<br />
<br />
=== closure() ===<br />
{{Nasal doc<br />
|syntax = closure(func[, level]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=421|t=Source}}<br />
|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'''. <br />
|param1 = func<br />
|param1text = Function to evaluate.<br />
|param2 = level<br />
|param2text = Optional integer specifying the scope level. Defaults to 0 (the namespace of '''func''').<br />
|example1 =<br />
var get_math_e = func {<br />
return e; # return the value of math.e<br />
}<br />
<br />
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<br />
debug.dump(closure(myFunction)); # print the namespace of get_math_e<br />
<br />
print(myFunction());<br />
}}<br />
<br />
=== cmp() ===<br />
{{Nasal doc<br />
|syntax = cmp(a, b);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=112|t=Source}}<br />
|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'''. <br />
|param1 = a<br />
|param1text = First string argument for comparison.<br />
|param2 = b<br />
|param2text = Second string argument for comparison.<br />
|example1 = print(cmp("1", "two")); # prints -1<br />
|example2 = print(cmp("string", "string")); # prints 0<br />
|example3 = print(cmp("one", "2")); # prints 1<br />
|example4 = print(cmp("string1", "string2")); # prints -1<br />
}}<br />
<br />
=== compile() ===<br />
{{Nasal doc<br />
|syntax = compile(code[, filename]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=220|t=Source}}<br />
|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'''.<br />
|param1 = code<br />
|param1text = String containing Nasal code to be compiled.<br />
|param2 = filename<br />
|param2text = Optional string used for error messages/logging. Defaults to <code><compile></code><br />
|example1 = <br />
var myCode = 'print("hello");';<br />
var helloFunc = compile(myCode, "myCode");<br />
helloFunc();<br />
|example2text = <code>compile</code> 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 <tt>''[[$FG_ROOT]]/gui/dialogs/test.xml''</tt>.<br />
<syntaxhighlight lang="xml"><br />
<?xml version="1.0"?><br />
<br />
<PropertyList><br />
<br />
<nasal><![CDATA[<br />
print("You have FlightGear v", getprop("/sim/version/flightgear"));<br />
]]></nasal><br />
<br />
</PropertyList><br />
</syntaxhighlight><br />
Now, start FlightGear and execute this code in the [[Nasal Console]].<br />
|example2 =<br />
# Build the path<br />
var FGRoot = getprop("/sim/fg-root");<br />
var filename = "/gui/dialogs/test.xml";<br />
var path = FGRoot ~ filename;<br />
<br />
var blob = io.read_properties(path);<br />
var script = blob.getValues().nasal; # Get the nasal string<br />
<br />
# Compile the script. We're passing the filename here for better runtime diagnostics <br />
var code = call(func {<br />
compile(script, filename);<br />
}, nil, nil, var compilation_errors = []);<br />
<br />
if(size(compilation_errors)){<br />
die("Error compiling code in: " ~ filename);<br />
}<br />
<br />
# Invoke the compiled script, equivalent to code(); <br />
# We're using call() here to detect errors:<br />
call(code, [], nil, nil, var runtime_errors = []);<br />
<br />
if(size(runtime_errors)){<br />
die("Error calling code compiled loaded from: " ~ filename);<br />
}<br />
}}<br />
<br />
=== contains() ===<br />
{{Nasal doc<br />
|syntax = contains(hash, key);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=184|t=Source}}<br />
|text = Returns 1 (True) if the hash contains the specified key, or 0 (False) if not.<br />
|param1 = hash<br />
|param1text = The hash to search in.<br />
|param2 = key<br />
|param2text = The scalar to be searched for, contained as a key in the hash.<br />
|example1 =<br />
# Initialize a hash<br />
var hash = {<br />
element: "value"<br />
};<br />
print(contains(hash, "element") ? "Yes" : "No"); # This will print "Yes"<br />
|example2 =<br />
# Initialize a hash<br />
var hash = {<br />
element: "value"<br />
};<br />
print(contains(hash, "element2") ? "Yes" : "No"); # This will print "No"<br />
}}<br />
<br />
=== delete() ===<br />
{{Nasal doc<br />
|syntax = delete(hash, key);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=83|t=Source}}<br />
|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 <code>'''nil'''</code> 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.<br />
|param1 = hash<br />
|param1text = The hash from which to delete the key.<br />
|param2 = key<br />
|param2text = The scalar to be deleted, contained as a key in the hash.<br />
|example1 =<br />
# Initialize the hash<br />
var hash = {<br />
element1: "value1",<br />
element2: "value2"<br />
};<br />
delete(hash, "element1"); # Delete element1<br />
debug.dump(hash); # prints the hash, which is now minus element1<br />
}}<br />
<br />
=== die() ===<br />
{{Nasal doc<br />
|syntax = die(error);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=288|t=Source}}<br />
|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()}}.<br />
|param1 = error<br />
|param1text = String describing the error.<br />
:{{inote|This parameter is technically optional, but it is highly recommended to use it.}}<br />
|example1 = <br />
print("Will print");<br />
die("Don't go any further!"); <br />
print("Won't print"); # Will not be printed because die() stops the process<br />
}}<br />
<br />
=== find() ===<br />
{{Nasal doc<br />
|syntax = find(needle, haystack);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=450|t=Source}}<br />
|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.<br />
|param1 = needle<br />
|param1text = String to search for.<br />
|param2 = haystack<br />
|param2text = String to search in.<br />
|example1 = print(find("c", "abcdef")); # prints 2<br />
|example2 = print(find("x", "abcdef")); # prints -1<br />
|example3 = print(find("cd", "abcdef")); # prints 2<br />
}}<br />
<br />
=== ghosttype() ===<br />
{{Nasal doc<br />
|syntax = ghosttype(ghost);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=207|t=Source}}<br />
|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++ <code>naGhostType</code> 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.<br />
|param1 = ghost<br />
|param1text = Ghost to return a description for.<br />
|example1 = print(ghosttype(airportinfo())); # prints "airport"<br />
}}<br />
<br />
=== id() ===<br />
{{Nasal doc<br />
|syntax = id(object);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=570|t=Source}}<br />
|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 <code>'''<type>''':'''<id>'''</code>, where '''<type>''' is the type of object, and '''<id>''' is the ID.<br />
|param1 = object<br />
|param1text = Can be either of a string, a vector, a hash, a code, a function, or a ghost.<br />
|example1 = print(id("A")); # prints "str:000000001624A590"<br />
}}<br />
<br />
=== int() ===<br />
{{Nasal doc<br />
|syntax = int(number);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=90|t=Source}}<br />
|text = Returns the integer part of the numeric value of the single argument, or <code>'''nil'''</code> if none exists.<br />
|param1 = number<br />
|param1text = Number or string with just a number in it to return an integer from.<br />
|example1 = print(int(23)); # prints "23"<br />
|example2 = print(int(23.123)); # prints "23"<br />
|example3 = debug.dump(int("string")); # prints "nil"<br />
}}<br />
<br />
=== keys() ===<br />
{{Nasal doc<br />
|syntax = keys(hash);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=33|t=Source}}<br />
|text = Returns a vector containing the list of keys found in the single hash argument. <br />
|param1 = hash<br />
|param1text = The hash to return the keys from.<br />
|example1 = <br />
# Initialize a hash<br />
var hash = {<br />
element1: "value",<br />
element2: "value"<br />
};<br />
debug.dump(keys(hash)); # print the vector<br />
}}<br />
<br />
=== left() ===<br />
{{Nasal doc<br />
|syntax = left(string, length);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=149|t=Source}}<br />
|version = 2.12<br />
|commit = {{simgear commit|bd7163|t=commit}}<br />
|text = Returns a substring of '''string''', starting from the left.<br />
|param1 = string<br />
|param1text = String to return part of.<br />
|param2 = length<br />
|param2text = Integer specifying the length of the substring to return.<br />
|example1 = print(left("string", 2)); # prints "st"<br />
}}<br />
<br />
=== num() ===<br />
{{Nasal doc<br />
|syntax = num(number);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=102|t=Source}}<br />
|text = Returns the numerical value of the single string argument, or <code>'''nil'''</code> if none exists. <br />
|param1 = number<br />
|param1text = String with just a number in it to return a number from.<br />
|example1 = print(num("23")); # prints "23"<br />
|example2 = print(num("23.123")); # prints "23.123"<br />
|example3 = debug.dump(num("string")); # prints "nil"<br />
}}<br />
<br />
=== pop() ===<br />
{{Nasal doc<br />
|syntax = pop(vector);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=50|t=Source}}<br />
|text = Removes and returns the last element of the single vector argument, or <code>'''nil'''</code> if the vector is empty. <br />
|param1 = vector<br />
|param1text = Vector to remove an element from.<br />
|example1 = <br />
var vector = [1, 2, 3];<br />
pop(vector);<br />
debug.dump(vector); # prints "[1, 2]"<br />
|example2 = <br />
var vector = [1, 2, 3];<br />
debug.dump(pop(vector)); # prints "3"<br />
|example3 = <br />
var vector = [];<br />
debug.dump(pop(vector)); # prints "nil"<br />
}}<br />
<br />
=== right() ===<br />
{{Nasal doc<br />
|syntax = right(string, length);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=161|t=Source}}<br />
|version = 2.12<br />
|commit = {{simgear commit|bd7163|t=commit}}<br />
|text = Returns a substring of '''string''', starting from the right.<br />
|param1 = string<br />
|param1text = String to return part of.<br />
|param2 = length<br />
|param2text = Integer specifying the length of the substring to return.<br />
|example1 = print(right("string", 2)); # prints "ng"<br />
}}<br />
<br />
=== setsize() ===<br />
{{Nasal doc<br />
|syntax = setsize(vector, size);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=56|t=Source}}<br />
|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 <code>'''nil'''</code> entries. Returns the vector operated upon. <br />
|param1 = vector<br />
|param1text = The vector to be operated on.<br />
|param2 = size<br />
|param2text = The desired size of the vector in number of entries.<br />
|example1 = <br />
var vector = [1, 2, 3]; # Initialize a vector<br />
setsize(vector, 4);<br />
debug.dump(vector); # print the vector<br />
|example2 = <br />
var vector = [1, 2, 3]; # Initialize a vector<br />
setsize(vector, 2);<br />
debug.dump(vector); # print the vector<br />
}}<br />
<br />
=== size() ===<br />
{{Nasal doc<br />
|syntax = size(object);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=23|t=Source}}<br />
|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 <code>'''nil'''</code> or a number, this error will be thrown: <code>object has no size()</code>.<br />
|param1 = object<br />
|param1text = Object to find the size of. Must be a string, a vector or a hash.<br />
|example1 = <br />
var string = "string";<br />
print(size(string)); # prints "6"<br />
|example2 =<br />
var vector = [1, 2, 3];<br />
print(size(vector)); # prints "3"<br />
|example3 =<br />
var hash = {<br />
element1: "value1",<br />
element2: "value2",<br />
element3: "value3"<br />
};<br />
print(size(hash)); # prints "3"<br />
}}<br />
<br />
=== sort() ===<br />
{{Nasal doc<br />
|syntax = sort(vector, function);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=542|t=Source}}<br />
|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/}}, <code>sort()</code> 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.<br />
|param1 = vector<br />
|param1text = Input vector to sort.<br />
|param2 = function<br />
|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.<br />
{{{!}} class="wikitable"<br />
! Return value !! Meaning<br />
{{!-}}<br />
{{!}} less than 0 {{!!}} first argument should go before second argument<br />
{{!-}}<br />
{{!}} 0 {{!!}} first argument equals second argument<br />
{{!-}}<br />
{{!}} greater than 0 {{!!}} first argument should go after second argument<br />
{{!}}}<br />
<br />
|example1text = This example sorts elements from smallest to greatest.<br />
|example1 = <br />
var sort_rules = func(a, b){<br />
if(a < b){<br />
return -1; # A should before b in the returned vector<br />
}elsif(a == b){<br />
return 0; # A is equivalent to b <br />
}else{<br />
return 1; # A should after b in the returned vector<br />
}<br />
}<br />
debug.dump(sort([3, 2, 5, 6, 4, 1], sort_rules)); # prints "[1, 2, 3, 4, 5, 6]"<br />
|example2text = This example sorts elements from greatest to smallest.<br />
|example2 = <br />
# Outputs the elements in reverse order (greatest to smallest)<br />
var sort_rules = func(a, b){<br />
if(a < b){<br />
return 1; # -1 in the above example<br />
}elsif(a == b){<br />
return 0;<br />
}else{<br />
return -1; # 1 in the above example<br />
}<br />
}<br />
debug.dump(sort([3, 2, 5, 6, 4, 1], sort_rules)); # prints "[6, 5, 4, 3, 2, 1]"<br />
|example3text = This example sorts a vector of strings (runways for example) from smallest to greatest.<br />
|example3 = <br />
var runways = ["09R","27R","26L","09L","15"];<br />
var rwy = sort(runways,func(a,b) cmp(a,b));<br />
debug.dump(rwy); # prints ['09L','09R','15','26L','27R']<br />
}}<br />
<br />
=== split() ===<br />
{{Nasal doc<br />
|syntax = split(delimiter, string);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=460|t=Source}}<br />
|text = Splits the input string into a vector of substrings bounded by occurrences of the delimiter substring.<br />
|param1 = delimiter<br />
|param1text = String that will split the substrings in the returned vector.<br />
|param2 = string<br />
|param2text = String to split up.<br />
|example1 = debug.dump(split("cd", "abcdef")); # prints "['ab', 'ef']"<br />
|example2 = debug.dump(split(".", "3.2.0")); # prints "[3, 2, 0]"<br />
|example3 = debug.dump(split("/", "path/to/file")); # prints "['path', 'to', 'file']"<br />
}}<br />
<br />
=== sprintf() ===<br />
{{Nasal doc<br />
|syntax = sprintf(format[, arg[, arg, [...]]]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=355|t=Source}}<br />
|text = Creates and returns a string formatted using ANSI C {{func link|vsnprintf()|link=http://en.cppreference.com/w/c/io/vfprintf}} <ref><br />
{{Cite web<br />
|url = http://sourceforge.net/p/flightgear/simgear/ci/next/tree/simgear/nasal/lib.c#l308<br />
|title = fgdata/simgear/simgear/nasal/lib.c, line 308<br />
|accessdate = October 2015<br />
}}<br />
</ref>. Below is a table of supported format specifiers.<br />
{{{!}} class="wikitable" width="75%"<br />
{{!}}+ %[flags][width][.precision]specifier<br />
! colspan="2" {{!}} Flags<br />
{{!-}}<br />
! Flag !! Output<br />
{{!-}}<br />
{{!}} <code>+</code> {{!!}} 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.<br />
{{!-}}<br />
{{!}} ''space'' {{!!}} Prefixes non-signed numbers with a space.<br />
{{!-}}<br />
{{!}} <code>-</code> {{!!}} Left-align the output of this placeholder (the default is to right-align the output) when the width option is specified.<br />
{{!-}}<br />
{{!}} <code>0</code> {{!!}} Use 0 instead of spaces to pad a field when the width option is specified.<br />
{{!-}}<br />
{{!}} <code>#</code> {{!!}} Used with <code>o</code>, <code>x</code> or <code>X</code> specifiers the value is preceded with <tt>0</tt>, <tt>0x</tt> or <tt>0X</tt> respectively for values different than zero. Used with <code>e</code>, <code>E</code> and <code>f</code>, 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 <code>g</code> or <code>G</code> the result is the same as with <code>e</code> or <code>E</code> but trailing zeros are not removed.<br />
{{!-}}<br />
! colspan="2" {{!}} Width<br />
{{!-}}<br />
{{!}} colspan="2" {{!}} Integer specifying the minimum number of characters to be returned. This includes the decimal point and decimal fraction.<br />
{{!-}}<br />
! colspan="2" {{!}} Precision<br />
{{!-}}<br />
{{!}} colspan="2" {{!}} Integer preceded by a dot specifying the number of decimal places to be written.<br />
{{!-}}<br />
! colspan="2" {{!}} Specifiers<br />
{{!-}}<br />
! Specifier !! Output<br />
{{!-}}<br />
{{!}} <code>d</code>, <code>i</code> {{!!}} Signed decimal number.<br />
{{!-}}<br />
{{!}} <code>s</code> {{!!}} A string<br />
{{!-}}<br />
{{!}} <code>%</code> {{!!}} Percent (%) character.<br />
{{!-}}<br />
{{!}} <code>c</code> {{!!}} 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.<br />
{{!-}}<br />
{{!}} <code>o</code> {{!!}} Unsigned integer as an octal number.<br />
{{!-}}<br />
{{!}} <code>u</code> {{!!}} Unsigned decimal integer.<br />
{{!-}}<br />
{{!}} <code>x</code>, <code>X</code> {{!!}} Unsigned integer as a hexadecimal number. If <code>x</code> is used, any letters in the number are lowercase, while <code>X</code> gives uppercase.<br />
{{!-}}<br />
{{!}} <code>e</code>, <code>E</code> {{!!}} Double value in scientific notation (i.e., ''[-]ddd.ddd'''e'''[+/-]ddd''), with an exponent being denoted by <tt>e</tt> or <tt>E</tt> depending on whether an upper or lowercase is used respectively.<br />
{{!-}}<br />
{{!}} <code>f</code> {{!!}} Floating-point number, in fixed decimal notation, by default with 6 decimal places.<br />
{{!-}}<br />
{{!}} <code>F</code> {{!!}} Appears to be available<ref><br />
{{Cite web<br />
|url = http://sourceforge.net/p/flightgear/simgear/ci/next/tree/simgear/nasal/lib.c#l389<br />
|title = fgdata/simgear/simgear/nasal/lib.c, line 389<br />
|accessdate = October 2015<br />
}}<br />
</ref>, but doesn't work.<br />
{{!-}}<br />
{{!}} <code>g</code>, <code>G</code> {{!!}} Double in either normal or exponential notation, whichever is more appropriate for its magnitude. <code>g</code> uses lower-case letters, <code>G</code> 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.<br />
{{!}}}<br />
<br />
|param1 = format<br />
|param1text = String specifying the format. Can be used with or without a format specifiers. See below for examples.<br />
|param2 = arg<br />
|param2text = Argument specifying a value to replace a format placeholder (such as <code>%d</code>) in the format string. Not required if there are no format specifiers.<br />
<br />
|example1 = print(sprintf("%i", 54)); # prints "54"<br />
|example2 = print(sprintf("Pi = %+.10f", math.pi)); # prints "Pi = +3.1415926536"<br />
|example3 = <br />
print(sprintf("%6d", 23)); # prints " 23"<br />
print(sprintf("%06d", 23)); # prints "000023"<br />
|example4 =<br />
var FGVer = getprop("/sim/version/flightgear");<br />
print(sprintf("You have FlightGear v%s", FGVer)); # prints "You have FlightGear v<your version>"<br />
|example5 = <br />
print(sprintf("Hexadecimal 100000 = %X", 100000)); # prints "Hexadecimal 100000 = 186A0"<br />
print(sprintf("Hexadecimal 100000 = %x", 100000)); # prints "Hexadecimal 100000 = 186a0"<br />
|example6 = print(sprintf("Code 65 is %c", 65)); # prints "Code 65 is A"<br />
|example7 = <br />
print(sprintf("%e", 54)); # prints "5.400000e+001"<br />
print(sprintf("%E", 54)); # prints "5.400000E+001"<br />
|example8 = print(sprintf("%o", 54)); # prints "66"<br />
|example9 = print(sprintf("50%% of 100 is %i", 100 / 2)); # prints "50% of 100 is 50"<br />
|example10 =<br />
print(sprintf("%.2f", 1.4)); #prints "1.40"<br />
print(sprintf("%.1f", 1.4)); #prints "1.4"<br />
print(sprintf("% 4.1f", 1.4)); #prints " 1.4"<br />
print(sprintf("%04.1f", 1.4)); #prints "01.4"<br />
print(sprintf("% 6.1f", 1.4)); #prints " 1.4"<br />
print(sprintf("%06.1f", 1.4)); #prints "0001.4"<br />
}}<br />
<br />
=== streq() ===<br />
{{Nasal doc<br />
|syntax = streq(a, b);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=129|t=Source}}<br />
|text = Tests the string values of the two arguments for equality. This function is needed because the <code>'''=='''</code> 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.}}<br />
|param1 = a<br />
|param1text = First argument for testing equality.<br />
|param2 = b<br />
|param2text = Second argument for testing equality.<br />
|example1 = print(streq("0", "0")); # prints "1" (True)<br />
|example2 = <br />
print(0 == 0.0); # prints "1" (True)<br />
print(streq("0", "0.0")); # prints "0" (False)<br />
}}<br />
<br />
=== substr() ===<br />
{{Nasal doc<br />
|syntax = substr(string, start [, length]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=129|t=Source}}<br />
|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).<br />
|param1 = string<br />
|param1text = String to return a substring from.<br />
|param2 = start<br />
|param2text = Integer specifying the start of a substring. Negative values specify a position from the end of the string.<br />
|param3 = length<br />
|param3text = Optional argument specifying the length of the substring. Defaults to the end of the string.<br />
|example1 = print(substr("abcde", 1, 3)); # prints "bcd"<br />
|example2 = print(substr("abcde", 1)); # prints "bcde"<br />
|example3 = print(substr("abcde", 2, 1)); # prints "c"<br />
|example4 = print(substr("abcde", -2)); # prints "de"<br />
|example5 = print(substr("abcde", -3, 2)); # prints "cd"<br />
}}<br />
<br />
=== subvec() ===<br />
{{Nasal doc<br />
|syntax = subvec(vector, start[, length]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=63|t=Source}}<br />
|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). <br />
|param1 = vector<br />
|param1text = The vector to take the sub-vector from.<br />
|param2 = start<br />
|param2text = The starting point of the sub-vector within the given vector.<br />
|param3 = length<br />
|param3text = Optional argument specifying the length of the sub-vector, from the starting point.<br />
'''Notes:'''<br />
* Omitting the ''vector'' and ''start'' arguments is not an error (possibly it should be) but the return value is ''nil''.<br />
* A negative ''start'' argument ''is'' an error. This seems wrong. Perhaps the language designer could comment.<br />
* A value of ''start'' greater than ''size(vector)'' causes an error. A value equal to ''size(vector)'' returns an empty vector.<br />
* 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.<br />
|example1 = <br />
var vector = [1, 2, 3];<br />
debug.dump(subvec(vector, 0)); # prints "[1, 2, 3]"<br />
|example2 = <br />
var vector = [1, 2, 3];<br />
debug.dump(subvec(vector, 1)); # prints "[2, 3]"<br />
|example3 = <br />
var vector = [1, 2, 3];<br />
debug.dump(subvec(vector, 1, 1)); # prints "[2]"<br />
}}<br />
<br />
=== typeof() ===<br />
{{Nasal doc<br />
|syntax = typeof(object);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=193|t=Source}}<br />
|text = Returns a string indicating the whether the object is <code>'''nil'''</code>, a scalar (number or string), a vector, a hash, a function, or a ghost.<br />
|param1 = object<br />
|param1text = Object to return the type of.<br />
|example1 = <br />
var object = nil;<br />
print(typeof(object)); # prints "nil"<br />
|example2 = <br />
var object = "Hello world!";<br />
print(typeof(object)); # prints "scalar"<br />
|example3 = <br />
var object = math.pi;<br />
print(typeof(object)); # prints "scalar"<br />
|example4 = <br />
var object = [1, 2, 3];<br />
print(typeof(object)); # prints "vector"<br />
|example5 = <br />
var object = {};<br />
print(typeof(object)); # prints "hash"<br />
|example6 = <br />
var object = func {};<br />
print(typeof(object)); # prints "func"<br />
|example7 =<br />
var object = airportinfo();<br />
print(typeof(object)); # prints "ghost"<br />
}}<br />
<br />
<!-- == Extension modules ==<br />
=== thread ===<br />
{{WIP}}<br />
<br />
{{Nasal doc<br />
|syntax = thread.newthread(func);<br />
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}<br />
|text = <br />
|example1text = start a new worker thread<br />
|example1 = thread.newthread( func() {} );<br />
}}<br />
<br />
{{Nasal doc<br />
|syntax = thread.newlock();<br />
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}<br />
|text = <br />
|example1text = create a new lock<br />
|example1 = var lock = thread.newlock()<br />
}}<br />
<br />
{{Nasal doc<br />
|syntax = thread.lock();<br />
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}<br />
|text = <br />
|example1text = lock a lock<br />
|example1 = var lock = thread.newlock()<br />
}}<br />
<br />
{{Nasal doc<br />
|syntax = thread.unlock();<br />
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}<br />
|text = <br />
|example1text = unlock a lock<br />
|example1 = var lock = thread.unlock()<br />
}}<br />
<br />
<br />
{{Nasal doc<br />
|syntax = thread.newsem();<br />
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}<br />
|text = <br />
|example1text = create a new {{Wikipedia|semaphore}}<br />
|example1 = var semaphore = thread.newsem()<br />
}}<br />
<br />
<br />
{{Nasal doc<br />
|syntax = thread.semdown();<br />
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}<br />
|text = <br />
|example1text = semaphore down<br />
|example1 = thread.semdown(semaphore)<br />
}}<br />
<br />
<br />
{{Nasal doc<br />
|syntax = thread.semup();<br />
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}<br />
|text = <br />
|example1text = semaphore up<br />
|example1 = thread.semup(semaphore)<br />
}} --><br />
<br />
== Extension functions ==<br />
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:<br />
* {{flightgear file|src/Scripting/NasalPositioned.cxx}}<br />
* {{flightgear file|src/Scripting/NasalSys.cxx}}<br />
* {{fgdata file|Nasal/globals.nas}}<br />
<br />
=== abort() ===<br />
{{Nasal doc<br />
|syntax = abort();<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=565|t=Source}}<br />
|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 "exit" [[Fgcommands|fgcommand]], which will exit FlightGear more gracefully (see example below).<br />
|example1text = This example will immediately stop FlightGear with an error, such as "FlightGear has stopped working."<br />
|example1 = abort();<br />
|example2text = For exiting FlightGear in a better way, please use the following code:<br />
|example2 = fgcommand("exit");<br />
}}<br />
<br />
=== abs() ===<br />
{{Nasal doc<br />
|syntax = abs(number);<br />
|source = {{fgdata file|Nasal/globals.nas|t=Source}}<br />
|text = This simple function returns the {{wikipedia|absolute value|noicon=1}} of the provided number.<br />
|param1 = number<br />
|param1text = This argument is required and should be a number.<br />
|example1 = print(abs(1)); # prints "1"<br />
|example2 = print(abs(-1)); # prints "1"<br />
}}<br />
<br />
=== aircraftToCart() ===<br />
This new function in FG 2017.2.1 takes coordinates in aircraft structural coordinate system, and translate them into geocentric coordinates.<br />
Example for (5,6,7):<br />
<syntaxhighlight lang="nasal"><br />
var pos = aircraftToCart({x: -5, y: 6, z: -7});<br />
var coord = geo.Coord.new();<br />
coord.set_xyz(pos.x, pos.y, pos.z);<br />
</syntaxhighlight><br />
Notice: x and z is inverted sign on purpose.<br />
if you want lat. lon, alt from that, just call: (degrees and meters)<br />
<br />
<syntaxhighlight lang="nasal"><br />
coord.lat()<br />
coord.lon()<br />
coord.alt()<br />
</syntaxhighlight><br />
<br />
=== addcommand() ===<br />
{{Nasal doc<br />
|syntax = addcommand(name, code);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=659|t=Source}}<br />
|version = 2.12<br />
|commit = {{flightgear commit|7b663c|t=commit}}<br />
|text = {{see also|Howto:Add new fgcommands to FlightGear}}<br />
<br />
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.<br />
|param1 = name<br />
|param1text = This will become the name of the new fgcommand. Must be a string.<br />
|param2 = code<br />
|param2text = The code that will be executed when the fgcommand is run. Must be a function.<br />
|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.<br />
|example1 = addcommand("myFGCmd", func(node) {<br />
print("fgcommand 'myFGCmd' has been run.");<br />
props.dump( node );<br />
});<br />
fgcommand("myFGCmd", props.Node.new({foo:1, bar:2}) );<br />
|example2text = This example demonstrates how parameters are defined in a new fgcommand.<br />
|example2 = addcommand("myFGCmd", func(node){<br />
print(node.getNode("number").getValue()); # prints the value of "number," which is 12<br />
});<br />
fgcommand("myFGCmd", props.Node.new({"number": 12}));<br />
}}<br />
<br />
=== airportinfo() ===<br />
{{Nasal doc<br />
|syntax = airportinfo();<br />
airportinfo(type);<br />
airportinfo(id);<br />
airportinfo(lat, lon[, type]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1024|t=Source}}<br />
|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:<br />
* '''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.<br />
* '''lon''': Longitude of the location.<br />
* '''lat''': Latitude of the location.<br />
* '''has_metar''': True or false depending whether the airport has a [[METAR]] code defined for it.<br />
* '''elevation''': Elevation of the location in metres.<br />
* '''id''': ICAO code of the airport (or ID of the seaplane base/heliport).<br />
* '''name''': Name of the airport/heliport/seaplane base.<br />
* '''runways'''<br />
** '''<runway name>'''<br />
*** '''id''': Name of runway.<br />
*** '''lat''': Latitude of the runway.<br />
*** '''lon''': Longitude of the runway.<br />
*** '''heading''': Heading of the runway.<br />
*** '''length''': Length of the runway in metres.<br />
*** '''width''': Width of the runway in metres.<br />
*** '''surface''': Runway surface type.<br />
*** '''threshold''': Length of the runway's {{wikipedia|displaced threshold}} in metres. Will return 0 if there is none.<br />
*** '''stopway''': Length of the runway's stopway (the area before the threshold) in metres. Will return 0 if there is none.<br />
*** '''reciprocal''': <code>runway</code> ghost of the reciprocal runway.<br />
*** '''ils_frequency_mhz''': ILS frequency in megahertz.<br />
*** '''ils''': <code>navaid</code> ghost of the ILS transmitter.<br />
* '''helipads'''<br />
** '''<helipad name>'''<br />
*** '''id''': Name of helipad.<br />
*** '''lat''': Latitude of the helipad.<br />
*** '''lon''': Longitude of the helipad.<br />
*** '''heading''': Heading of the helipad.<br />
*** '''length''': Length of the helipad in metres.<br />
*** '''width''': Width of the helipad in metres.<br />
*** '''surface''': Helipad surface type.<br />
* '''taxiways'''<br />
** '''<taxiway name>'''<br />
*** '''id''': Name of taxiway.<br />
*** '''lat''': Latitude of the taxiway.<br />
*** '''lon''': Longitude of the taxiway.<br />
*** '''heading''': Heading of the taxiway.<br />
*** '''length''': Length of the taxiway in metres.<br />
*** '''width''': Width of the taxiway in metres.<br />
*** '''surface''': Taxiway surface type.<br />
<br />
Information is extracted in the same way as accessing members of a Nasal hash. For example:<br />
<syntaxhighlight lang="nasal"><br />
# prints to lengths of the runways of the nearest airport in feet and metres<br />
var info = airportinfo();<br />
print("-- Lengths of the runways at ", info.name, " (", info.id, ") --");<br />
foreach(var rwy; keys(info.runways)){<br />
print(rwy, ": ", math.round(info.runways[rwy].length * M2FT), " ft (", info.runways[rwy].length, " m)");<br />
}<br />
</syntaxhighlight><br />
<br />
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.<br />
|param1 = id<br />
|param1text = The {{wikipedia|International Civil Aviation Organization airport code|ICAO code|noicon=1}} of an airport to retrieve information about.<br />
|param2 = type<br />
|param2text = When this argument is used, the function will return the closest airport of a certain type. Can be one of "heliport," "seaport," or "airport" (default).<br />
: {{inote|Running this function without any parameters is equivalent to this:<br />
: <syntaxhighlight lang="nasal"><br />
airportinfo("airport");<br />
</syntaxhighlight><br />
}}<br />
|param3 = lat ''and'' lon<br />
|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.<br />
|example1 = var info = airportinfo();<br />
print("Nearest airport: ", info.name, " (", info.id, ")"); # prints the name and ICAO code of the nearest airport<br />
|example2 = var info = airportinfo("heliport");<br />
print("Elevation of the nearest heliport: ", math.round(info.elevation * M2FT), " ft"); # prints the elevation and name of the nearest heliport<br />
|example3 = var info = airportinfo("KSQL");<br />
print("-- Runways of ", info.name, " (", info.id, "): --");<br />
foreach(var rwy; keys(info.runways)) {<br />
print(rwy); # prints the runways of KSQL<br />
}<br />
|example4 = var info = airportinfo(37.81909385, -122.4722484);<br />
print("Coordinates of the nearest airport: ", info.lat, ", ", info.lon); # print the name and ICAO of the nearest airport to the Golden Gate Bridge<br />
|example5 = var info = airportinfo(37.81909385, -122.4722484, "seaport");<br />
print("Nearest seaplane base: ", info.name, " (", info.id, ")"); # print the name and ID of the nearest seaplane base to the Golden Gate Bridge<br />
|example6text = This example prints the all information from an <code>airportinfo()</code> call.<br />
|example6 = var info = airportinfo("KSFO");<br />
print(info.name);<br />
print(info.id);<br />
print(info.lat);<br />
print(info.lon);<br />
print(info.has_metar);<br />
print(info.elevation);<br />
foreach(var rwy; keys(info.runways)){<br />
print("-- ", rwy, " --");<br />
print(info.runways[rwy].lat);<br />
print(info.runways[rwy].lon);<br />
print(info.runways[rwy].length);<br />
print(info.runways[rwy].width);<br />
print(info.runways[rwy].heading);<br />
print(info.runways[rwy].stopway);<br />
print(info.runways[rwy].threshold);<br />
}<br />
}}<br />
<br />
=== airwaysRoute() ===<br />
{{Nasal doc<br />
|syntax = airwaysRoute(start, end[, type]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1933|t=Source}}<br />
|text = {{see also|Nasal Flightplan}}<br />
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.<br />
|param1 = start<br />
|param1text = Start waypoint, in the form of a waypoint ghost, such as that provided by {{func link|flightplan()}}.<br />
|param2 = end<br />
|param2text = Same as above.<br />
|param3 = type<br />
|param3text = Instructs the function to compute a high level route (when set to "highlevel"), or a low level route (when set to "lowlevel"). Defaults to "highlevel."<br />
|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.<br />
|example1 = var fp = flightplan();<br />
var start = fp.getWP(0);<br />
var end = fp.getWP(fp.getPlanSize() - 1);<br />
var rt = airwaysRoute(start, end);<br />
foreach(var wp; rt){<br />
print(wp.wp_name); # print the waypoints in the computed route<br />
}<br />
|example2text = Exactly the same as above, but computes a low level path.<br />
|example2 = var fp = flightplan();<br />
var start = fp.getWP(0);<br />
var end = fp.getWP(fp.getPlanSize() - 1);<br />
var rt = airwaysRoute(start, end, "lowlevel");<br />
foreach(var wp; rt){<br />
print(wp.wp_name); # print the waypoints in the computed route<br />
}<br />
}}<br />
<br />
=== assert() ===<br />
{{Nasal doc<br />
|syntax = assert(condition[, message]);<br />
|source = {{fgdata file|Nasal/globals.nas|t=Source}}<br />
|version = 3.2<br />
|commit = {{fgdata commit|8b16a7|t=commit}}<br />
|text = Returns either true if the condition evaluates as true, or aborts with a {{func link|die()}} call, which can be customised.<br />
|param1 = condition<br />
|param1text = Condition to evaluate.<br />
|param2 = message<br />
|param2text = Optional message that will be used in any {{func link|die()}} call. Defaults to "assertion failed!"<br />
|example1 = var a = 1;<br />
var b = 2;<br />
print(assert(a < b)); # prints "1" (true)<br />
|example2 = var a = 1;<br />
var b = 2;<br />
assert(a > b, 'a is not greater than b'); # aborts with a custom error message<br />
}}<br />
<br />
=== carttogeod() ===<br />
{{Nasal doc<br />
|syntax = carttogeod(x, y, z);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=945|t=Source}}<br />
|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).<ref>{{simgear file|simgear/math/sg_geodesy.hxx|l=43}}</ref><br />
|param1 = x<br />
|param1text = Mandatory x-axis value, in metres.<br />
|param2 = y<br />
|param2text = Mandatory y-axis value, in metres.<br />
|param3 = z<br />
|param3text = Mandatory z-axis value, in metres.<br />
|example1 = var (lat, lon, alt) = carttogeod(6378137, 0, 0); # point is the intersection of the prime meridian and equator.<br />
print("Latitude: ", lat); # prints lat, lon and alt, which are all zero, see above<br />
print("Longitude: ", lon);<br />
print("Altitude: ", alt);<br />
}}<br />
<br />
=== cmdarg() ===<br />
{{Nasal doc<br />
|private = _cmdarg()<br />
|syntax = cmdarg();<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=513|t=Part 1}} {{!}} {{fgdata file|Nasal/globals.nas|t=Part 2}}<br />
|text = <code>cmdarg()</code> 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. <br />
It is used by Nasal scripts embedded in XML files. It returns a <code>props.Node</code> object (see {{fgdata file|Nasal/props.nas}}), and you can use all of its methods on the returned value. <code>cmdarg()</code> should only be used in four types/places of XML files:<br />
* Bindings: This is needed so that the value of a joystick's axis can be accessed internally.<br />
* 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). <br />
* 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.<br />
* Animation XML files: If the animation XML file is used in an AI/MP model, <code>cmdarg()</code> will return the root of the AI model in the <code>/ai/models/</code> directory. Examples: <code>/ai/models/aircraft[3]/</code>, <code>/ai/models/multiplayer[1]/</code><br />
<br />
You should not use <code>cmdarg()</code> in places other than those stated above. Although it won't cause an error, it will return the value of the last legitimate <code>cmdarg()</code> call. <br />
<br />
Also, you should not delay <code>cmdarg()</code> using {{func link|maketimer()}}, {{func link|settimer()}} or {{func link|setlistener()}}, because it will return an unrelated property.<br />
|example1 = fgcommand("dialog-show", {"dialog-name": "cmdarg-demo"});<br />
|example1text = <br>This example demonstrates the usage of <code>cmdarg()</code> in a binding. Save the below XML snippet as <tt>[[$FG_ROOT]]/gui/dialogs/cmdarg-demo.xml</tt>. 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.<br />
<syntaxhighlight lang="xml"><br />
<?xml version="1.0" encoding="UTF-8"?><br />
<br />
<PropertyList><br />
<br />
<name>cmdarg-demo</name><br />
<layout>vbox</layout><br />
<br />
<text><br />
<label>Click "Close" to activate the demonstration (a message in the console).</label><br />
</text><br />
<br />
<button><br />
<legend>Close</legend><br />
<binding><br />
<command>nasal</command><br />
<script>print("Button binding root: '" ~ cmdarg().getPath() ~ "'");</script><br />
</binding><br />
<binding><br />
<command>dialog-close</command><br />
</binding><br />
</button><br />
<br />
</PropertyList><br />
</syntaxhighlight><br />
|example2text = This example demonstrates the usage of <code>cmdarg()</code> in Nasal code within dialogs. Open <tt>[[$FG_ROOT]]/gui/dialogs/cmdarg-demo.xml</tt> from the previous example, copy & 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 "I've been changed!"<br />
<syntaxhighlight lang="xml"><br />
<?xml version="1.0" encoding="UTF-8"?><br />
<br />
<PropertyList><br />
<br />
<name>cmdarg-demo</name><br />
<layout>vbox</layout><br />
<br />
<text><br />
<label>Click "Click me!" to activate the demonstration (the button's label will change).</label><br />
</text><br />
<br />
<button><br />
<legend>Click me!</legend><br />
<binding><br />
<command>nasal</command><br />
<script>change_label();</script><br />
</binding><br />
</button><br />
<br />
<button><br />
<legend>Close</legend><br />
<binding><br />
<command>dialog-close</command><br />
</binding><br />
</button><br />
<br />
<nasal><br />
<open><![CDATA[<br />
var dlg_root = cmdarg();<br />
var dlg_name = {"dialog-name": "cmdarg-demo"};<br />
var change_label = func {<br />
dlg_root.getNode("button[0]/legend").setValue("I've been changed!");<br />
fgcommand("dialog-close", dlg_name);<br />
fgcommand("dialog-show", dlg_name);<br />
}<br />
]]></open><br />
</nasal><br />
<br />
</PropertyList><br />
</syntaxhighlight><br />
|example3text = For an example of <code>cmdarg()</code> used with Canvas, please see [[Howto:Adding a canvas to a GUI dialog#FGPlot|Howto:Adding a canvas to a GUI dialog]].<br />
}}<br />
<br />
=== courseAndDistance() ===<br />
{{Nasal doc<br />
|syntax = courseAndDistance(to);<br />
courseAndDistance(from, to);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1668|t=Source}}<br />
|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:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost type<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A geo.Coord object<br />
|param1 = from<br />
|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.<br />
|param2 = to<br />
|param2text = Like the first parameter, but defines the second point.<br />
|example1text = This example demonstrates the usage of the function with the <code>airport</code> ghost type.<br />
|example1 = var from = airportinfo("KSFO");<br />
var to = airportinfo("KSQL");<br />
var (course, dist) = courseAndDistance(from, to);<br />
print(course); # prints course from KSFO to KSQL<br />
print(dist); # prints distance in nm from KSFO to KSQL<br />
|example2text = This example demonstrates the usage of the function with hashes containing ''lat'' and ''lon''.<br />
|example2 = var from = {lat: 0, lon: 0};<br />
var to = {lat: 1, lon: 1};<br />
var (course, dist) = courseAndDistance(from, to);<br />
print(course);<br />
print(dist);<br />
|example3text = This example demonstrates usage of a geo.Coord object.<br />
|example3 = var from = geo.Coord.new().set_latlon(0, 0);<br />
var to = geo.Coord.new().set_latlon(1, 1);<br />
var (course, dist) = courseAndDistance(from, to);<br />
print(course);<br />
print(dist);<br />
|example4text = This example demonstrates usage of differing parameter types.<br />
|example4 = var from = airportinfo("KSFO");<br />
var to = geo.Coord.new().set_latlon(0, 0);<br />
var (course, dist) = courseAndDistance(from, to);<br />
print(course);<br />
print(dist);<br />
|example5text = The same as above, but the other way round.<br />
|example5 = var to = {lat: 1, lon: 1};<br />
var (course, dist) = courseAndDistance(0, 0, to);<br />
print(course);<br />
print(dist);<br />
|example6text = Usage of just one parameter.<br />
|example6 = var dest = airportinfo("KSQL");<br />
var (course, dist) = courseAndDistance(dest);<br />
print("Turn to heading ", math.round(course), ". You have ", sprintf("%.2f", dist), " nm to go");<br />
}}<br />
<br />
=== createDiscontinuity() ===<br />
{{Nasal doc<br />
|syntax = createDiscontinuity();<br />
|text = Returns a <code>waypoint</code> ghost object. A route discontinuity is inserted by an {{abbr|FMS|Flight Management System}} when it is unsure how to connect two waypoints.<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2045|t=Source}}<br />
|version = 2016.1<br />
|commit = {{flightgear commit|caead6|t=commit}}<br />
}}<br />
=== createViaTo() ===<br />
{{Nasal doc<br />
|syntax = createViaTo(airway, waypoint);<br />
|text = Returns a <code>waypoint</code> ghost object. It represents a route "via '''airway''' to '''waypoint'''".<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2009|t=Source}}<br />
|version = 2016.1<br />
|commit = {{flightgear commit|caead6|t=commit}}<br />
|param1 = airway<br />
|param1text = The name of an airway.<br />
|param2 = waypoint<br />
|param2text = Must be in the airway and one of:<br />
* The name of a waypoint.<br />
* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, or <code>fix</code> ghost object.<br />
}}<br />
=== createWP() ===<br />
{{Nasal doc<br />
|syntax = createWP(pos, name[, flag]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1964|t=Source}}<br />
|text = Creates a new waypoint ghost object.<br />
|param1 = pos<br />
|param1text = Dictates the position of the new waypoint. It can be one of the following:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost type<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A geo.Coord object<br />
:* Two numbers separated by a comma, as if the function is taking three arguments. See example 4 below.<br />
|param2 = name<br />
|param2text = String that will become the name of the new waypoint.<br />
|param3 = flag<br />
|param3text = Optional string that will tell FlightGear what type of waypoint it is. Must be one of "sid," "star," "approach," "missed," or "pseudo."<br />
|example1text = Creates a waypoint directly in front and 1 km away and appends it to the flight plan.<br />
|example1 = var pos = geo.aircraft_position().apply_course_distance(getprop("/orientation/heading-deg"), 1000);<br />
var wp = createWP(pos, "NEWWP");<br />
var fp = flightplan();<br />
fp.appendWP(wp);<br />
|example2 = var pos = geo.aircraft_position().apply_course_distance(getprop("/orientation/heading-deg"), 1000);<br />
var wp = createWP({lat: pos.lat(), lon: pos.lon()}, "NEWWP");<br />
var fp = flightplan();<br />
fp.appendWP(wp);<br />
|example3 = var apt = airportinfo();<br />
var wp = createWP(apt, "NEWWP");<br />
var fp = flightplan();<br />
fp.appendWP(wp);<br />
|example4 = var pos = geo.aircraft_position().apply_course_distance(getprop("/orientation/heading-deg"), 1000);<br />
var wp = createWP(pos.lat(), pos.lon(), "NEWWP");<br />
var fp = flightplan();<br />
fp.appendWP(wp);<br />
|example5text = Creates a new waypoint and adds it to the flight plan. Waypoints of the type "pseudo" are then removed from the flight plan, including the new waypoint. The {{func link|print()}} statements show this.<br />
|example5 = var pos = geo.aircraft_position().apply_course_distance(getprop("/orientation/heading-deg"), 1000);<br />
var wp = createWP(pos, "NEWWP", "pseudo");<br />
var fp = flightplan();<br />
fp.appendWP(wp);<br />
print(fp.getPlanSize());<br />
fp.clearWPType("pseudo");<br />
print(fp.getPlanSize());<br />
}}<br />
<br />
=== createWPFrom() ===<br />
{{Nasal doc<br />
|syntax = createWPFrom(object[, flag]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1989|t=Source}}<br />
|text = Creates a new waypoint object from another object.<br />
|param1 = object<br />
|param1text = A ghost object. Must be a ghost type that is one of "airport," "navaid," "runway," or "fix."<br />
|param2 = flag<br />
|param2text = Optional string that will tell FlightGear what type of waypoint it is. Must be one of "sid," "star," "approach," "missed," or "pseudo."<br />
|example1text = Creates a new waypoint and appends it to the flight plan.<br />
|example1 = var apt = airportinfo("KSFO");<br />
var wp = createWPFrom(apt);<br />
var fp = flightplan();<br />
fp.appendWP(wp);<br />
|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.<br />
|example2 = var apt = airportinfo("KSFO");<br />
var wp = createWPFrom(apt, "pseudo");<br />
print(wp.wp_name);<br />
var fp = flightplan();<br />
fp.appendWP(wp);<br />
print(fp.getPlanSize());<br />
fp.clearWPType("pseudo");<br />
print(fp.getPlanSize());<br />
}}<br />
<br />
=== defined() ===<br />
{{Nasal doc<br />
|syntax = defined(symbol);<br />
|source = {{fgdata file|Nasal/globals.nas|t=Source}}<br />
|text = Returns 1 (true) or 0 (false) depending on whether a variable exists.<br />
|param1 = symbol<br />
|param1text = A string that will be what the function searches for.<br />
|example1 = var number = 12;<br />
var check_exist = func {<br />
print("Variable 'number' ", defined("number") == 1 ? "exists" : "does not exist"); # 'number' does exist<br />
print("Variable 'number2' ", defined("number2") == 1 ? "exists" : "does not exist"); # 'number2' does not exist<br />
}<br />
check_exist();<br />
}}<br />
<br />
=== directory() ===<br />
{{Nasal doc<br />
|syntax = directory(path);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=572|t=Source}}<br />
|text = Returns a vector containing a list of the folders and files in a given file path or <code>'''nil'''</code> if the path doesn't exist. Hidden folders and files are not added to the vector.<br />
{{inote|The first two elements of the vector will be <code>'.'</code> and <code>'..'</code>. These are for navigating back up the file tree, but have no use in Nasal. They can be safely removed from the vector.}}<br />
|param1 = path<br />
|param1text = Absolute file path.<br />
|example1text = Gets the folders and files in [[$FG_ROOT]], and then removes the extra first two elements (see note above). <br />
|example1 = var dir = directory(getprop("/sim/fg-root")); # get directory<br />
dir = subvec(dir, 2); # strips off the first two elements<br />
debug.dump(dir); # dump the vector<br />
}}<br />
<br />
=== fgcommand() ===<br />
{{Nasal doc<br />
|syntax = fgcommand(cmd[, args]);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=456|t=Part 1}} {{!}} {{fgdata file|Nasal/globals.nas|t=Part 2}}<br />
|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.<br />
|param1 = cmd<br />
|param1text = String that is the name of the command that is to be run.<br />
|param2 = args<br />
|param2text = If the fgcommand takes arguments, they are inputted using this argument. Can either be a <code>props.Node</code> object, or a hash (see examples below).<br />
|example1 = fgcommand("null"); # does nothing<br />
|example2 = var args = props.Node.new({'script': 'print("Running fgcommand");'});<br />
if (fgcommand("nasal", args)) { # prints "Running fgcommand" and then one of these print statements<br />
print("Fgcommand succeeded");<br />
} else {<br />
print("Fgcommand encountered a problem");<br />
}<br />
|example3 = var args = { 'dialog-name': 'about' };<br />
fgcommand("dialog-show", args); # shows the 'about' dialog<br />
}}<br />
<br />
=== findAirportsByICAO() ===<br />
{{Nasal doc<br />
|syntax = findAirportsByICAO(search[, type]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1096|t=Source}}<br />
|text = Returns a vector containing <code>airport</code> ghost objects which are (by default) airports whose ICAO code matches the search string. The results are sorted by range from closest to furthest.<br />
|param1 = search<br />
|param1text = Search string for the function. Can either be a partial or a full ICAO code.<br />
:{{icaution|The more matches there are for the given code, the longer the function will take. Passing just one character (e.g., "K"), might make FlightGear hang for a certain amount of time.}}<br />
|param2 = type<br />
|param2text = This will narrow the search to airports of a certain type. By default, only airports are searched for. May be one of "airport," "heliport," or "seaport."<br />
|example1 = var apts = findAirportsByICAO("KSF"); # finds all airports matching "KSF"<br />
foreach(var apt; apts){<br />
print(apt.name, " (", apt.id, ")"); # prints them<br />
}<br />
|example2 = var apts = findAirportsByICAO("SP0", "seaport"); # finds all seaplane bases matching "SP0"<br />
foreach(var apt; apts){<br />
print(apt.name, " (", apt.id, ")"); # prints them<br />
}<br />
|example3 = var apt = findAirportsByICAO("XBET"); # one way to check if an airport does exist"<br />
if (size(apt) == 0) {<br />
print("Airport does not exist"); # this one will be printed<br />
} else {<br />
print("Airport does exist");<br />
}<br />
}}<br />
<br />
=== findAirportsWithinRange() ===<br />
{{Nasal doc<br />
|syntax = findAirportsWithinRange([pos, ]range[, type]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1066|t=Source}}<br />
|text = Returns a vector of <code>airport</code> 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.<br />
|param1 = pos<br />
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost type<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A geo.Coord object<br />
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: <code>findAirportsWithinRange(lat, lon, range, type);</code>.<br />
|param2 = range<br />
|param2text = Mandatory number giving the range in nautical miles within which to search for airports/heliports/seaplane bases.only airports are searched for.<br />
|param3 = type<br />
|param3text = This will narrow the search to airports of a certain type. By default, only airports are searched for. May be one of "airport," "heliport," or "seaport."<br />
|example1text = Searches for airports within 10 nm of [[KSFO]].<br />
|example1 = var pos = airportinfo("KSFO");<br />
var apts = findAirportsWithinRange(pos, 10);<br />
foreach(var apt; apts){<br />
print(apt.name, " (", apt.id, ")");<br />
}<br />
|example2text = Searches for seaplane bases within 15 nm of [[KSFO]].<br />
|example2 = var pos = airportinfo("KSFO");<br />
var apts = findAirportsWithinRange(pos, 15, "seaport");<br />
foreach(var apt; apts){<br />
print(apt.name, " (", apt.id, ")");<br />
}<br />
|example3text = Searches for airports within 10 nm of your current position.<br />
|example3 = var apts = findAirportsWithinRange(10);<br />
foreach(var apt; apts){<br />
print(apt.name, " (", apt.id, ")");<br />
}<br />
}}<br />
<br />
=== findFixesByID() ===<br />
{{Nasal doc<br />
|syntax = findFixesByID([pos, ]id);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1627|t=Source}}<br />
|text = Returns a vector containing <code>fix</code> ghost objects matching a given ID, sorted by range from a certain position.<br />
{{inote|Fixes are (usually) also known as waypoints.}}<br />
|param1 = pos<br />
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost type<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A geo.Coord object<br />
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: <code>findFixesByID(lat, lon, id);</code>.<br />
|param2 = id<br />
|param2text = Full or partial ID of the fix to search for.<br />
:{{inote|1=Inputting a partial ID does not work correctly (see [http://forum.flightgear.org/viewtopic.php?f=30&t=28129 here]). It is best to just input a full ID.}}<br />
|example1 = var fixes = findFixesByID("POGIC");<br />
foreach(var fix; fixes){<br />
print(fix.id, " - lat: ", fix.lat, " {{!}} lon: ", fix.lon); # prints information about POGIC<br />
}<br />
|example2 = var fix = findFixesByID("ZUNAP");<br />
fix = fix[0];<br />
var (course, dist) = courseAndDistance(fix);<br />
print("Turn to heading ", math.round(course), ". You have ", sprintf("%.2f", dist), " nm to go to reach ", fixes[0].id);<br />
}}<br />
<br />
=== findNavaidByFrequency() ===<br />
{{Nasal doc<br />
|syntax = findNavaidByFrequency([pos, ]freq[, type]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1547|t=Source}}<br />
|text = Returns a <code>navaid</code> ghost object for a navaid matching a given frequency. If there is more than one navaid with that frequency, the nearest station is returned.<br />
|param1 = pos<br />
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost type<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A geo.Coord object<br />
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: <code>findNavaidByFrequency(lat, lon, freq, type);</code>.<br />
|param2 = freq<br />
|param2text = Frequency, in megahertz, of the navaid to search for.<br />
|param3 = type<br />
|param3text = This will narrow the search to navaids of a certain type. Defaults to "all." For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.<br />
|example1 = var navaid = findNavaidByFrequency(11.17);<br />
print("ID: ", navaid.id); # prints info about the navaid<br />
print("Name: ", navaid.name);<br />
print("Latitude: ", navaid.lat);<br />
print("Longitude: ", navaid.lon);<br />
print("Elevation (AMSL): ", navaid.elevation, " m");<br />
print("Type: ", navaid.type);<br />
print("Frequency: ", sprintf("%.3f", navaid.frequency / 1000), " Mhz");<br />
print("Range: ", navaid.range_nm, " nm");<br />
if(navaid.course) print("Course: ", navaid.course);<br />
}}<br />
<br />
=== findNavaidsByFrequency() ===<br />
{{Nasal doc<br />
|syntax = findNavaidsByFrequency([pos, ]freq[, type]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1572|t=Source}}<br />
|text = Returns a vector conatining <code>navaid</code> ghost objects for navaids that match a given frequency, sorted from nearest to furthest.<br />
|param1 = pos<br />
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost type<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A geo.Coord object<br />
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: <code>findNavaidsByFrequency(lat, lon, freq, type);</code>.<br />
|param2 = freq<br />
|param2text = Frequency, in megahertz, of the navaid to search for.<br />
|param3 = type<br />
|param3text = This will narrow the search to navaids of a certain type. Defaults to "all." For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.<br />
|example1 = var navaids = findNavaidsByFrequency(10.955);<br />
foreach(var navaid; navaids){<br />
print("--");<br />
print("ID: ", navaid.id); # prints info about the navaid<br />
print("Name: ", navaid.name);<br />
print("Latitude: ", navaid.lat);<br />
print("Longitude: ", navaid.lon);<br />
print("Elevation (AMSL): ", navaid.elevation, " m");<br />
print("Type: ", navaid.type);<br />
print("Frequency: ", sprintf("%.3f", navaid.frequency / 1000), " Mhz");<br />
print("Range: ", navaid.range_nm, " nm");<br />
if(navaid.course) print("Course: ", navaid.course);<br />
print("--");<br />
}<br />
}}<br />
<br />
=== findNavaidsByID() ===<br />
{{Nasal doc<br />
|syntax = findNavaidsByID([pos, ]id[, type]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1600|t=Source}}<br />
|text = Returns a vector containing <code>navaid</code> ghost objects matching a given ID, sorted by range from a certain position.<br />
|param1 = pos<br />
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost type<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A geo.Coord object<br />
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: <code>findNavaidsByID(lat, lon, id, type);</code>.<br />
|param2 = id<br />
|param2text = Full or partial ID of the fix to search for.<br />
:{{inote|1=Inputting a partial ID does not work correctly (see [http://forum.flightgear.org/viewtopic.php?f=30&t=28129 here]). It is best to just input a full ID.}}<br />
|param3 = type<br />
|param3text = This will narrow the search to navaids of a certain type. Defaults to "all." For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.<br />
|example1 = var navaid = findNavaidsByID("MXW");<br />
navaid = navaid[0];<br />
print("ID: ", navaid.id); # prints info about 'MXW' (a VOR station)<br />
print("Name: ", navaid.name);<br />
print("Latitude: ", navaid.lat);<br />
print("Longitude: ", navaid.lon);<br />
print("Elevation (AMSL): ", navaid.elevation, " m");<br />
print("Type: ", navaid.type);<br />
print("Frequency: ", sprintf("%.3f", navaid.frequency / 1000), " Mhz");<br />
print("Range: ", navaid.range_nm, " nm");<br />
}}<br />
<br />
=== findNavaidsWithinRange() ===<br />
{{Nasal doc<br />
|syntax = findNavaidsWithinRange([pos, ]range[, type]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1518|t=Source}}<br />
|text = Returns a vector of <code>navaid</code> 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.<br />
|param1 = pos<br />
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost type<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A geo.Coord object<br />
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: <code>findNavaidsWithinRange(lat, lon, range, type);</code>.<br />
|param2 = range<br />
|param2text = Mandatory number giving the range in nautical miles within which to search for navaids. Defaults to "all." For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.<br />
|param3 = type<br />
|param3text = This will narrow the search to navaids of a certain type.<br />
|example1text = Searches for navaids within 10 nm of [[KSFO]].<br />
|example1 = var pos = airportinfo("KSFO");<br />
var navs = findNavaidsWithinRange(pos, 10);<br />
foreach(var nav; navs){<br />
print(nav.name, " (ID: ", nav.id, ")");<br />
}<br />
|example2text = Searches for navaids within 10 nm of your current position.<br />
|example2 = var navs = findNavaidsWithinRange(10);<br />
foreach(var nav; navs){<br />
print(nav.name, " (ID: ", nav.id, ")");<br />
}<br />
}}<br />
<br />
=== finddata() ===<br />
{{Nasal doc<br />
|syntax = finddata(path);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=603|t=Source}}<br />
|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]]. <br />
|param1 = path<br />
|param1text = A relative path as a string.<br />
|example1 = var path = finddata("Aircraft/Generic");<br />
print(path); # prints the absolute path to $FG_ROOT/Aircraft/Generic<br />
|example2 = var path = finddata("Airports");<br />
print(path); # prints the absolute path to <TerraSync dir>/Airports<br />
|example3 = var path = finddata("preferences.xml");<br />
print(path); # prints the absolute path to $FG_ROOT/preferences.xml<br />
}}<br />
<br />
=== flightplan() ===<br />
{{Nasal doc<br />
|syntax = flightplan([path]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1738|t=Source}}<br />
|text = {{see also|Nasal Flightplan}}<br />
Returns a flight plan object, either one for the current flight plan, or one loaded from a given path.<br />
|param1 = path<br />
|param1text = Optional path to flight plan XML file.<br />
|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.<br />
|example1 = var fp = flightplan();<br />
print(fp.getWP(fp.current).id);<br />
|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 <tt>''[[$FG_HOME]]/fp-demo.xml''</tt>.<br />
|example2 = var path = getprop('/sim/fg-home') ~ '/fp-demo.xml';<br />
var fp = flightplan(path);<br />
for(var i = 0; i < fp.getPlanSize(); i += 1){<br />
print(fp.getWP(i).id);<br />
}<br />
}}<br />
<br />
=== geodinfo() ===<br />
{{Nasal doc<br />
|syntax = geodinfo(lat, lon[, max_alt]);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=981|t=Source}}<br />
|text = Returns a vector containing two entries or <code>'''nil'''</code> 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 <tt>''[[$FG_ROOT]]/Materials''</tt>), or <code>'''nil'''</code> 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}}):<br />
* '''light_coverage:''' The coverage of a single point of light in m<sup>2</sup>.<br />
* '''bumpiness:''' Normalized bumpiness factor for the material.<br />
* '''load_resistance:''' The amount of pressure in N/m<sup>2</sup> the material can withstand without deformation.<br />
* '''solid:''' 1 (true) or false (0) depending on whether the material is solid or not.<br />
* '''names:''' Vector of scenery types (usually generated by [[TerraGear]]) that will use this material. <br />
* '''friction_factor:''' Normalized friction factor of the material.<br />
* '''rolling_friction:''' The rolling friction coefficient of the material.<br />
<br />
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.<br />
|param1 = lat<br />
|param1text = Latitude, inputted as a number.<br />
|param2 = lon<br />
|param2text = Longitude, inputted as a number.<br />
|param3 = max_alt<br />
|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, <code>'''nil'''</code> will be returned.<br />
|example1text = Dumps information about ground underneath the aircraft.<br />
|example1 = var pos = geo.aircraft_position();<br />
var info = geodinfo(pos.lat(), pos.lon());<br />
debug.dump(info);<br />
|example2text = Prints whether the ground underneath the aircraft is solid or is water.<br />
|example2 = var pos = geo.aircraft_position();<br />
var info = geodinfo(pos.lat(), pos.lon());<br />
if (info != nil and info[1] != nil) {<br />
print("The ground underneath the aircraft is ", info[1].solid == 1 ? "solid." : "water.");<br />
}<br />
}}<br />
<br />
=== geodtocart() ===<br />
{{Nasal doc<br />
|syntax = geodtocart(lat, lon, alt);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=962|t=Source}}<br />
|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.<br />
|param1 = lat<br />
|param1text = Latitude, in degrees.<br />
|param2 = lon<br />
|param2text = Longitude, in degrees.<br />
|param3 = alt<br />
|param3text = Altitude, in metres.<br />
|example1 = var (x, y, z) = geodtocart(0, 0, 0); # point is the intersection of the prime meridian and equator.<br />
print("x: ", x); # prints "x: 6378137"<br />
print("y: ", y); # prints "y: 0"<br />
print("z: ", z); # prints "y: 0"<br />
}}<br />
<br />
=== get_cart_ground_intersection() ===<br />
Introduced in 2017.2.1, see [[Terrain Detection]].<br />
<br />
=== getprop() ===<br />
{{Nasal doc<br />
|syntax = getprop(path[, path[, ...]]);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=345|t=Source}}<br />
|text = Returns the value of a node in the [[Property Tree]] or <code>'''nil'''</code> if the node does not exist or the value is not a number (NaN).<br />
|param1 = path<br />
|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.<br />
|example1 = print("You have FlightGear v", getprop("/sim/version/flightgear")); # prints FlightGear version<br />
|example2text = Note that the example below will only work in FlightGear 3.2 and above.<br />
|example2 = for(var i = 0; i < 8; i += 1){<br />
print("View #", i + 1, " is named ", getprop("/sim/view", i, "name"));<br />
}<br />
|example3text = Same as above, but is supported by all versions of FlightGear.<br />
|example3 = for(var i = 0; i < 8; i += 1){<br />
print("View #", i + 1, " is named ", getprop("/sim/view[" ~ i ~ "]/name"));<br />
}<br />
}}<br />
==== See also ====<br />
{{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: <br />
To get a Node rather than its value, use <code>props.globals.getNode()</code> - see [[Nasal_library/props]]. }}<br />
<br />
=== greatCircleMove() ===<br />
{{Nasal doc<br />
|syntax = greatCircleMove([pos, ]course, dist);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1681|t=Source}}<br />
|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).<br />
|param1 = pos<br />
|param1text = Optional position to calculate from. If not given, the aircraft's current position will be used. Can be one of:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost object.<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A <code>geo.Coord</code> object<br />
:* A lat/lon pair, that is, a pair of numbers (latitude followed by longitude) separated by a comma: <code>greatCircleMove(lat,lon, ...)</code>.<br />
|param2 = course<br />
|param2text = Course to new set of coordinates, in degrees (in the range 0–360).<br />
|param3 = dist<br />
|param3text = Distance in nautical miles to the new set of coordinates.<br />
|example1 = var pos = greatCircleMove(0,0, 0, 1);<br />
debug.dump(pos); # print hash with coordinates<br />
|example2 = var fix = findFixesByID("POGIC");<br />
fix = fix[0];<br />
var pos = greatCircleMove(fix, 45, 10);<br />
debug.dump(pos); # print hash with coordinates<br />
}}<br />
<br />
=== interpolate() ===<br />
{{Nasal doc<br />
|private = _interpolate()<br />
|syntax = interpolate(prop, value1, time1[, value2, time2[, ...]]);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=522|t=Part 1}} {{!}} {{fgdata file|Nasal/globals.nas|t=Part 2}}<br />
|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.<br />
|param1 = prop<br />
|param1text = String or <code>props.Node</code> object that indicates a node in the property tree to be used.<br />
|param2 = value''n''<br />
|param2text = Target value to change the property to in the set amount of time. This should be a number.<br />
|param3 = time''n''<br />
|param3text = Time in seconds, that will be taken for the interpolation.<br />
|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.<br />
|example1 = setprop("/test", 0); # (re-)set property<br />
interpolate("/test",<br />
50, 5, # interpolate to 50 in 5 seconds<br />
10, 2, # interpolate to 10 in 2 seconds<br />
0, 5); # interpolate to 0 in 5 seconds<br />
|example2 = # Apply the left brake at 20% per second<br />
var prop = "controls/gear/brake-left";<br />
var dist = 1 - getprop(prop);<br />
if (dist == 1) {<br />
interpolate(prop, 1, dist / 0.2);<br />
}<br />
}}<br />
<br />
=== isa() ===<br />
{{Nasal doc<br />
|syntax = isa(object, class);<br />
|source = {{fgdata file|Nasal/globals.nas|t=Source}}<br />
|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.<br />
|param1 = object<br />
|param1text = Object to check.<br />
|param2 = class<br />
|param2text = Class/object to check that '''object''' inherits from or is an instance of.<br />
|example1 = var coord = geo.Coord.new();<br />
if(isa(coord, geo.Coord)){<br />
print("Variable 'coord' is an instance of class 'geo.Coord'"); # this one will be printed<br />
} else {<br />
print("Variable 'coord' is not an instance of class 'geo.Coord'");<br />
}<br />
|example2 = var coord = geo.Coord.new();<br />
if(isa(coord, props.Node)){<br />
print("Variable 'coord' is an instance of class 'props.Node'");<br />
} else {<br />
print("Variable 'coord' is not an instance of class 'props.Node'"); # this one will be printed<br />
}<br />
|example3text = The example below demonstrates checking of inheritance.<br />
|example3 = var Const = {<br />
constant: 2,<br />
getConst: func {<br />
return me.constant;<br />
}<br />
};<br />
<br />
var Add = {<br />
new: func {<br />
return { parents: [Add, Const] };<br />
},<br />
<br />
addToConst: func(a){<br />
return a * me.getConst();<br />
}<br />
};<br />
<br />
var m = Add.new();<br />
print(m.addToConst(4));<br />
<br />
if(isa(m, Add)) print("Variable 'm' is an instance of class 'Add'"); # will be printed<br />
if(isa(m, Const)) print("Variable 'm' is an instance of class 'Const'"); # will also be printed<br />
}}<br />
<br />
=== logprint() ===<br />
{{Nasal doc<br />
|syntax = logprint(priority[, msg[, msg[, ...]]]);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=431|t=Source}}<br />
|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 <code>[[Commonly used debugging tools#fgfs.log|fgfs.log]]</code> file as coming from the Nasal file itself rather than from {{flightgear file|src/Scripting/NasalSys.cxx}}.<br />
|param1 = priority<br />
|param1text = Number specifying the priority level of the outputted message:<br />
:{{{!}} class="wikitable"<br />
! Number !! Debug type<br />
{{!-}}<br />
{{!}} 1 {{!!}} Bulk<br />
{{!-}}<br />
{{!}} 2 {{!!}} Debug<br />
{{!-}}<br />
{{!}} 3 {{!!}} Info<br />
{{!-}}<br />
{{!}} 4 {{!!}} Warn<br />
{{!-}}<br />
{{!}} 5 {{!!}} Alert<br />
{{!}}}<br />
|param2 = msg<br />
|param2text = The message. There is no limit to the arguments you give give. They will be concatenated together before logging.<br />
|example1 = # logs the value of pi to three decimal places with log level 3<br />
logprint(3, "pi = ", sprintf("%.3f", math.pi));<br />
|example2 = logprint(5, "Alert! This is an important message!");<br />
}}<br />
{{note| <br />
The following constants have been added to the development branch of FlightGear ("next") and will be releases with FG 2020.1 so you won't have to remember the numbers anymore:<br />
<br />
LOG_BULK, LOG_WARN, LOG_DEBUG, LOG_INFO, LOG_ALERT, DEV_WARN, DEV_ALERT }}<br />
<br />
=== magvar() ===<br />
{{Nasal doc<br />
|syntax = magvar([pos]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1642|t=Source}}<br />
|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.<br />
{{{!}} class="wikitable"<br />
! FlightGear versions !! Model !! Reference date<br />
{{!-}}<br />
{{!}} 3.6 and above {{!!}} {{wikipedia|World Magnetic Model}} (WMM) 2015 {{!!}} 1 January 2015<br />
{{!-}}<br />
{{!}} 0.9.11-pre1 to 3.4 {{!!}} WMM 2005 {{!!}} 1 January 2005<br />
{{!-}}<br />
{{!}} 0.7.3 to 0.9.10 {{!!}} WMM 2000 {{!!}} 1 January 2000<br />
{{!}}}<br />
|param1 = pos<br />
|param1text = Optional position to calculate from. If not given, the aircraft's current position will be used. Can be one of:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost object.<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A <code>geo.Coord</code> object<br />
:* A lat/lon pair, that is, a pair of numbers (latitude followed by longitude) separated by a comma: <code>magvar(lat,lon)</code>.<br />
|example1 = print(magvar(0, 0)); # prints the magnetic variation at 0, 0<br />
}}<br />
<br />
=== maketimer() ===<br />
{{Nasal doc<br />
|syntax = maketimer(interval[, self], function);<br />
|source = ''Implemented using the {{API Link|flightgear|class|TimerObj}} class.''<br>{{flightgear file|src/Scripting/NasalSys.cxx|l=90|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=533|t=Part 2}}<br />
|version = 2.12<br />
|commit = {{flightgear commit|ab939f|t=commit}}<br />
|text = Returns a timer object containing the following methods and members:<br />
* '''start()''': Starts the timer.<br />
* '''stop()''': Stops the timer.<br />
* '''restart(interval)''': Restarts the timer with the given interval.<br />
* '''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).<br />
* '''isRunning''': Read-only bool telling whether the timer is currently running.<br />
* '''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.<br />
Unlike {{func link|settimer()}}, which it replaces, <code>maketimer()</code> provides more control over the timer. In addition, it can help reduce memory usage.<br />
|param1 = interval<br />
|param1text = Interval in seconds for the timer.<br />
|param2 = self<br />
|param2text = Optional parameter specifying what any <code>'''me'''</code> references in the function being called will refer to.<br />
|param3 = function<br />
|param3text = Function to be called at the given interval.<br />
|example1 = var timer = maketimer(1, func(){<br />
print("Hello, World!"); # print "Hello, World!" once every second (call timer.stop() to stop it)<br />
});<br />
timer.start();<br />
|example2 = var timer = maketimer(1, math, func(){<br />
print(me.math); # 'me' reference is the 'math' namespace<br />
});<br />
timer.singleShot = 1; # timer will only be run once<br />
timer.start();<br />
|example3 = var timer = maketimer(1, func(){<br />
print("Hello, World!"); # print "Hello, World!" once every second (call timer.stop() to stop it)<br />
});<br />
timer.start();<br />
print(timer.isRunning); # prints 1<br />
|example4text = In the example below, "Hello, World!" will be printed after one second the first time, and after two seconds thereafter.<br />
|example4 = var update = func(){<br />
print("Hello, World!");<br />
timer.restart(2); # restarts the timer with a two second interval<br />
}<br />
<br />
var timer = maketimer(1, update);<br />
timer.singleShot = 1;<br />
timer.start();<br />
}}<br />
<br />
=== maketimestamp() ===<br />
{{Nasal doc<br />
|syntax = maketimestamp()<br />
|source = ''Implemented using the {{API Link|flightgear|class|TimeStampObj}} class.''<br>{{flightgear file|src/Scripting/NasalSys.cxx|l=214|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=589|t=Part 2}}<br />
|version = 2019.2<br />
|commit = {{flightgear commit|7db06300|t=commit}}<br />
|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:<br />
* '''stamp()''': Resets the timing operation. Call this first.<br />
* '''elapsedMSec()''': returns number of milliseconds elapsed since stamp() called. Resolution may vary depending on platform but is usually at least millisecond accuracy.<br />
* '''elapsedUSec()''': returns number of microseconds elapsed since stamp() called. Resolution may vary depending on platform but is usually at least millisecond accuracy.<br />
|<br />
|example1text = In the example below the number of milliseconds elapsed will be printed.<br />
|example1 = var timestamp = maketimestamp();<br />
timestamp.stamp();<br />
print(timestamp.elapsedMSec(), "ms elapsed");<br />
print(timestamp.elapsedMSec(), "ms elapsed");<br />
}}<br />
<br />
=== md5() ===<br />
{{Nasal doc<br />
|syntax = md5(string);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=758|t=Source}}<br />
|version = 3.2<br />
|commit = {{flightgear commit|cfbf9e|t=commit}}<br />
|text = Returns a the {{wikipedia|MD5}} hash (as a string) of the inputted string.<br />
|param1 = string<br />
|param1text = String the generate the hash of. Mandatory.<br />
|example1text = The below code should output <code>65a8e27d8879283831b664bd8b7f0ad4</code>.<br />
|example1 = print(md5("Hello, World!"));<br />
}}<br />
<br />
=== navinfo() ===<br />
{{Nasal doc<br />
|syntax = navinfo(lat, lon, type, id);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1453|t=Source}}<br />
|text = Returns vector <code>navaid</code> ghost objects matching the given '''type''' and '''id''' or <code>'''nil'''</code> on error.<br />
|param1 = lat ''and'' lon<br />
|param1text = If given, the returned navaids will be put into order of ascending distance from the location.<br />
|param2 = type<br />
|param2text = Narrows the search to the given type. Must be one of "any," "fix," "vor," "ndb," "ils," "dme," or "tacan." Defaults to the equivalent of "any."<br />
|param3 = id<br />
|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.<br />
|example1 = navinfo("vor"); # returns all VORs<br />
|example2 = navinfo("HAM"); # return all navaids whose names start with "HAM"<br />
|example3 = navinfo("vor", "HAM"); # return all VORs whose names start with "HAM"<br />
|example4 = navinfo(34,48,"vor","HAM"); # return all VORs whose names start with "HAM" and sorted by distance relative to 34°, 48°<br />
}}<br />
<br />
=== parse_markdown() ===<br />
{{Nasal doc<br />
|syntax = parse_markdown(markdown);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=745|t=Part 1}} {{!}} {{simgear file|simgear/misc/SimpleMarkdown.cxx|t=Part 2}} <br />
|version = 3.2<br />
|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:<br />
* It strips whitespace from the beginning of the string.<br />
* It supports [http://daringfireball.net/projects/markdown/syntax#p paragraphs and line breaks].<br />
* It collapses whitespace.<br />
* It converts unordered [http://daringfireball.net/projects/markdown/syntax#list lists] to use a bullet character (&bull;). Note that the bullet character is implemented in hexadecimal UTF-8 character bytes (<code>E2 80 A2</code>), as so may not work properly when the being displayed in an encoding other than UTF-8.<br />
|param1 = markdown<br />
|param1text = String containing Markdown to be parsed.<br />
|example1text = <br />
Save the below code as <tt>''[[$FG_ROOT]]/gui/dialogs/test.xml''</tt>, 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 (<tt>Debug > Reload GUI</tt>).<br />
<syntaxhighlight lang="xml" highlight="41-50"><br />
<?xml version="1.0" encoding="UTF-8"?><br />
<br />
<PropertyList><br />
<br />
<name>test</name><br />
<layout>vbox</layout><br />
<br />
<group><br />
<layout>hbox</layout><br />
<br />
<empty><br />
<stretch>true</stretch><br />
</empty><br />
<br />
<text><br />
<label>parse_markdown() test dialog</label><br />
</text><br />
<br />
<empty><br />
<stretch>true</stretch><br />
</empty><br />
<br />
<button><br />
<legend></legend><br />
<pref-width>16</pref-width><br />
<pref-height>16</pref-height><br />
<binding><br />
<command>dialog-close</command><br />
</binding><br />
</button><br />
<br />
</group><br />
<br />
<canvas><br />
<name>Canvas plot</name><br />
<stretch>true</stretch><br />
<pref-width>400</pref-width><br />
<pref-height>300</pref-height><br />
<nasal><br />
<load><![CDATA[<br />
var text = 'Items:<br />
* apples<br />
* oranges<br />
* pears<br />
<br />
Some text.<br />
Some more items:<br />
* apples<br />
* oranges<br />
* pears';<br />
<br />
var parsed = parse_markdown(text);<br />
<br />
var root_canvas = canvas.get(cmdarg());<br />
root_canvas.setColorBackground(255, 255, 255);<br />
var root = root_canvas.createGroup();<br />
<br />
var text_dis = root.createChild("text")<br />
.setText(parsed)<br />
.setTranslation(5, 5)<br />
.setFont("LiberationFonts\LiberationSans-Regular.ttf")<br />
.setFontSize(15)<br />
.setColor(0, 0, 0)<br />
.setDrawMode(canvas.Text.TEXT)<br />
.setAlignment("left-top");<br />
]]></load><br />
</nasal><br />
</canvas><br />
<br />
</PropertyList><br />
</syntaxhighlight><br />
|example1 = fgcommand("dialog-show", {"dialog-name": "test"});<br />
|example2text = The example below parses Markdown and outputs it in a HTML document. The parsed text is placed in <syntaxhighlight lang="xml" inline><pre></pre></syntaxhighlight> tags. To change the Markdown to be parsed, simply edit the variable <tt>markdown</tt> at the top of the code.<br />
|example2 = <nowiki>var markdown = 'Items:<br />
* apples<br />
* oranges<br />
* pears<br />
<br />
Some text.<br />
Some more items:<br />
* apples<br />
* oranges<br />
* pears';<br />
<br />
var parsed = parse_markdown(markdown);<br />
<br />
debug.dump(parsed);<br />
<br />
var path = string.normpath(getprop("/sim/fg-home") ~ '/Export/parse_markdown()-test.html');<br />
<br />
var file = io.open(path, "w");<br />
<br />
var html = "<!DOCTYPE html>\n\n<html>\n\n<head>\n\t<meta charset=\"UTF-8\">\n\t<title>parse_markdown() test generated by Nasal</title>\n</head>\n\n<body>\n\t<pre>" ~ parsed ~ "</pre>\n</body>\n\n</html>";<br />
<br />
io.write(file, html);<br />
io.close(file);<br />
print("Done, file ready for viewing (" ~ path ~ ")");</nowiki><br />
}}<br />
<br />
=== parsexml() ===<br />
{{Nasal doc<br />
|syntax = parsexml(path[, start[, end[, data[, pro_ins]]]]);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=710|t=Source}}<br />
|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 <code>'''nil'''</code> is returned on error.<br />
|param1 = path<br />
|param1text = Mandatory absolute path to the XML file to be parsed.<br />
|param2 = start<br />
|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.<br />
|param3 = end<br />
|param3text = Optional callback function that will be called for every ending tag. The function should take one argument: the tag name.<br />
|param4 = data<br />
|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.<br />
|param5 = pro_ins<br />
|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.<br />
|example1text = Save the below XML code in <tt>''[[$FG_HOME]]/Export/demo.xml''</tt>. Then, execute the Nasal code below in the Nasal Console. The XML will be parsed and each bit of the code will be printed.<br />
<syntaxhighlight lang="xml"><br />
<?xml version="1.0" encoding="UTF-8"?><br />
<br />
<?xml-stylesheet type="text/xsl" href="style.xsl"?><br />
<br />
<foo><br />
<blah type="string"><![CDATA[ <sender>John Smith</sender> ]]></blah><br />
<blah2 type="string">Orange &amp; lemons</blah2><br />
</foo><br />
</syntaxhighlight><br />
|example1 = var start = func(name, attr){<br />
print("Starting tag: '", name, "'");<br />
foreach(var a; keys(attr)){<br />
print("\twith attribute ", a, '="', attr[a], '"');<br />
}<br />
}<br />
<br />
var end = func(name){<br />
print("Ending tag: '", name, "'");<br />
}<br />
<br />
var data = func(data){<br />
print("Data = '", data, "'");<br />
}<br />
<br />
var pro_instr = func(target, data){<br />
print("Processing instruction: target = '", target, "', data = '", data, "'");<br />
}<br />
<br />
parsexml(getprop("/sim/fg-home") ~ '/Export/demo.xml', start, end, data, pro_instr);<br />
}}<br />
<br />
=== print() ===<br />
{{Nasal doc<br />
|syntax = print(data[, data[, ...]]);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=415|t=Source}}<br />
|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.<br />
|param1 = data<br />
|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.<br />
|example1 = print("Just", " a ", "test"); # prints "Just a test"<br />
|example2 = print("pi = ", math.pi); # prints "pi = 3.141592..."<br />
}}<br />
<br />
=== printf() ===<br />
{{Nasal doc<br />
|syntax = printf(format[, arg[, arg, [...]]]);<br />
|source = {{fgdata file|Nasal/globals.nas|t=Source}}<br />
|text = Creates and prints a formatted string. For a description of its arguments, see {{func link|sprintf()}} (it is, in fact, implemented using <code>sprintf()</code>).<br />
|example1 = printf("In hexadecimal, 100000 = %X", 186A0); # prints "In hexadecimal, 100000 = 186A0"<br />
}}<br />
<br />
=== printlog() ===<br />
{{Nasal doc<br />
|syntax = printlog(level, data[, data[, ...]]);<br />
|source = {{fgdata file|Nasal/globals.nas|t=Source}}<br />
|text = Prints the given message with the given log level. If the log level is higher or equal to <code>/sim/logging/priority</code>, it is printed.<br />
|param1 = level<br />
|param1text = Mandatory log level as a string. Must be one of "none," "bulk," "debug," "info," "warn," or "alert." Note that "none" will mean that the message will never be printed.<br />
|param2 = data<br />
|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.<br />
|example1 = printlog("alert", "This is an alert"); # message will be printed<br />
|example2 = printlog("info", "Just informing you about something"); # message will be printed only if log level is set to "info" or less<br />
}}<br />
<br />
=== rand() ===<br />
{{Nasal doc<br />
|syntax = rand();<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=554|t=Source}}<br />
|text = Returns a random floating point number between 0 (inclusive) and 1 (exclusive). It takes no arguments.<br />
|example1 = print(rand()); # prints random number<br />
}}<br />
<br />
=== registerFlightPlanDelegate() ===<br />
{{Nasal doc<br />
|syntax = registerFlightPlanDelegate(init_func);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1879|t=Source}}<br />
|text = Registers a flight plan delegate. See <tt>''{{fgdata file|Nasal/route_manager.nas|t=$FG_ROOT/Nasal/route_manager.nas}}''</tt> for examples.<br />
|param1 = init_func<br />
|param1text = Initialization function which will be called during FlightGear's startup.<br />
}}<br />
=== removecommand() ===<br />
{{Nasal doc<br />
|syntax = removecommand(cmd);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=668|t=Source}}<br />
|text = Removes the given fgcommand. Returns <code>'''nil'''</code>.<br />
{{caution|This will remove '''any''' [[fgcommands|fgcommand]], even those implemented in C++, so use with caution!}}<br />
|param1 = cmd<br />
|param1text = String specifying the name of the command to remove.<br />
|example1 = addcommand("hello", func(){<br />
print("Hello");<br />
});<br />
fgcommand("hello"); # "Hello" will be printed<br />
removecommand("hello"); # removes it<br />
}}<br />
<br />
=== removelistener() ===<br />
{{Nasal doc<br />
|syntax = removelistener(id);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=1384|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=506|t=Part 2}}<br />
|text = Removes and deactivates the given listener and returns the number of listeners left or <code>'''nil'''</code> on error.<br />
{{note|It is good practice to remove listeners when they are not required anymore. This prevents the listeners reducing FlightGear's run performance.}}<br />
|param1 = id<br />
|param1text = ID of listener as returned by {{func link|setlistener()}}.<br />
|example1 = var ls = setlistener("/sim/test", func(){<br />
print("Property '/sim/test' has been changed");<br />
});<br />
setprop("/sim/test", "blah"); # trigger listener<br />
var rem = removelistener(ls); # remove listener<br />
print("There are ", rem, " listeners remaining");<br />
}}<br />
<br />
=== resolvepath() ===<br />
{{Nasal doc<br />
|syntax = resolvepath(path);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=604|t=Source}}<br />
|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.<br />
|param1 = path<br />
|param1text = Relative path to be completed.<br />
|example1 = print(resolvepath("Nasal/globals.nas")); # prints the equivalent of $FG_ROOT/Nasal/globals.nas<br />
|example2 = print(resolvepath("blah")); # prints nothing; could not be resolved<br />
|example3 = var file_path = resolvepath("Aircraft/SenecaII/some-file");<br />
if (file_path != ""){<br />
gui.popupTip("some-file found", 2);<br />
} else {<br />
gui.popupTip("some-file not found", 2);<br />
}<br />
}}<br />
<br />
=== setlistener() ===<br />
{{Nasal doc<br />
|syntax = setlistener(node, code[, init[, type]]);<br />
|private = _setlistener()<br />
|source = {{flightgear file|src/Scripting/Nasal|l=499|t=Part 1}} {{!}} {{flightgear file|src/Scripting/Nasal|l=1350|t=Part 2}}<br>''Listener implemented using the {{API Link|flightgear|class|FGNasalListener}} class.''<br />
|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()}}.<br />
{{note|Listeners are known to be a source of resource leaks. To avoid this, please take measures such as:<br />
* Using {{func link|removelistener()}} when they are not needed any more.<br />
* Using a single initialization listener.<br />
* Avoiding tying listeners to properties that are rapidly updated (e.g., many times per frame).<br />
}}<br />
|param1 = node<br />
|param1text = Mandatory string or <code>props.Node</code> object pointing to a property in the [[Property Tree]].<br />
|param2 = code<br />
|param2text = Mandatory callback function to execute when the listener is triggered. The function can take up to four arguments in the following order:<br />
* '''changed''': a <code>props.Node</code> object pointing to the changed node.<br />
* '''listen''': a <code>props.Node</code> object pointing to the listened-to node. Note that this argument maybe different depending on the '''type'''.<br />
* '''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.<br />
* '''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.<br />
|param3 = init<br />
|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).<br />
|param4 = type<br />
|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.<br />
|example1 = var prop = props.globals.initNode("/sim/test", "", "STRING"); # create property<br />
var id = setlistener("/sim/test", func(n){ # create listener<br />
print("Value: ", n.getValue());<br />
});<br />
setprop("/sim/test", "blah"); # trigger listener<br />
removelistener(id); # remove listener<br />
prop.remove(); # remove property<br />
}}<br />
<br />
=== setprop() ===<br />
{{Nasal doc<br />
|syntax = setprop(path[, path[, ...]], value);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=385|t=Source}}<br />
|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.<br />
{{note|If you want to remove a property, you will have to use one of the <code>props</code> helpers:<br />
<syntaxhighlight lang="nasal"><br />
props.globals.getNode("foo/bar").remove(); # take out the complete node<br />
props.globals.getNode("foo").removeChild("bar"); # take out a certain child node<br />
</syntaxhighlight><br />
}}<br />
|param1 = path<br />
|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.<br />
|param2 = value<br />
|param2text = Value to write to the given property. Must be either a string or a number.<br />
|example1 = setprop("/sim/demo", "This is a demo");<br />
|example2text = Note that the example below will only work in FlightGear 3.2 and above.<br />
|example2 = for(var i = 0; i < 3; i += 1){<br />
setprop("/sim/demo", i, "Demo #" ~ i));<br />
}<br />
|example3text = Same as above, but is supported by all versions of FlightGear.<br />
|example3 = for(var i = 0; i < 3; i += 1){<br />
setprop("/sim/demo[" ~ i ~ "]", "Demo #" ~ i));<br />
}<br />
}}<br />
==== See also ====<br />
{{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: <br />
To get a Node rather than its value, use <code>props.globals.getNode()</code> - see [[Nasal_library/props]]. }}<br />
<br />
=== settimer() ===<br />
{{Nasal doc<br />
|syntax = settimer(function, delta[, realtime]);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=499|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=1286|t=Part 2}}<br />
|text = Runs the given function a specified amount of seconds after the current time. Returns <code>'''nil'''</code>.<br />
{{note|Improper use of <code>settimer()</code> may cause resource leaks. It is also highly recommended that the newer {{func link|maketimer()}} should be used instead of this function.}} <br />
|param1 = function<br />
|param1text = Mandatory function that will be called. It may be necessary to enclose code in an anonymous function (see example).<br />
|param2 = delta<br />
|param2text = Mandatory amount of time in seconds after which the function will be called.<br />
|param3 = realtime<br />
|param3text = If 1 (true), "real time" will be used instead of "simulation time." Defaults to 0 (false). Note that if "simulation time" is used, the timer will not run while FlightGear is paused.<br />
|example1 = var myFunc = func(){<br />
print("Hello");<br />
}<br />
<br />
settimer(myFunc, 2); # runs myFunc after 2 seconds<br />
|example2 = var sqr = func(a){<br />
return a * a;<br />
}<br />
<br />
settimer(func(){<br />
print(sqr(2)); # will print 4 after 2 seconds<br />
}, 2);<br />
}}<br />
<br />
=== srand() ===<br />
{{Nasal doc<br />
|syntax = srand();<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=559|t=Source}}<br />
|text = Makes the pseudorandom number generator (see {{func link|rand()}}) generate a new {{wikipedia|random seed|noicon=1}} based on time. Returns 0.<br />
}}<br />
=== systime() ===<br />
{{Nasal doc<br />
|syntax = systime();<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=770|t=Source}}<br />
|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).<br />
{{note|1=High resolution timers under Windows can produce inaccurate or fixed sub-millisecond results.<ref>{{cite web|url=http://forum.flightgear.org/viewtopic.php?f=30&t=29259|title=Nasal: systime() ??!?|author=Necolatis|date=Apr 2nd, 2016}}</ref> 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]}}<br />
|example1 = print("Unix time: ", systime()); # prints Unix time<br />
|example2 = var myFunc = func(){<br />
for(var i = 0; i <= 10; i += 1){<br />
print("Interation #", i);<br />
}<br />
}<br />
var t = systime(); # record time<br />
myFunc(); # run function<br />
var t2 = systime(); # record new time<br />
print("myFunc() took ", t2 - t, " seconds"); # print result<br />
}}<br />
<br />
=== thisfunc() ===<br />
{{Nasal doc<br />
|syntax = thisfunc();<br />
|source = {{fgdata file|Nasal/globals.nas|t=Source}}<br />
|text = Returns the function from which this function is called. This allows a function to reliably and safely call itself from within a closure.<br />
|example1 = var stringify_vec = func(input){<br />
if (typeof(input) == "scalar"){<br />
return sprintf("%s", input);<br />
} elsif (typeof(input) == "vector") {<br />
if (size(input) == 0) return "[]";<br />
var this = thisfunc();<br />
var buffer = "[";<br />
for(var i = 0; i < size(input); i += 1){<br />
buffer ~= this(input[i]);<br />
if (i == size(input) - 1) {<br />
buffer ~= "]";<br />
} else {<br />
buffer ~= ", ";<br />
}<br />
}<br />
return buffer;<br />
} else {<br />
die("stringify_vec(): Error! Invalid input. Must be a vector or scalar");<br />
}<br />
}<br />
<br />
var test_vec = ["a", "b", "c", 1, 2, 3];<br />
debug.dump(stringify_vec(test_vec)); # prints "[a, b, c, 1, 2, 3]"<br />
test_vec = [];<br />
debug.dump(stringify_vec(test_vec)); # prints "[]"<br />
test_vec = {};<br />
debug.dump(stringify_vec(test_vec)); # will throw an error<br />
}}<br />
<br />
=== tileIndex() ===<br />
{{Nasal doc<br />
|syntax = tileIndex();<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1720|t=Source}}<br />
|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 <code>942050</code>, corresponding to <tt>''[[$FG_SCENERY]]/Terrain/w130n30/w123n3/942050.stg''</tt>.<br />
|example1 = print(tileIndex()); # print index<br />
}}<br />
<br />
=== tilePath() ===<br />
{{Nasal doc<br />
|syntax = tilePath();<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1712|t=Source}}<br />
|text = Returns the base path of the tile at the aircraft's current position as a string. For example, at KSFO, this would be <code>w130n30/w123n3</code>, corresponding to <tt>''[[$FG_SCENERY]]/Terrain/w130n30/w123n3''</tt>.<br />
|example1 = print(tilePath()); # print path<br />
}}<br />
<br />
=== values() ===<br />
{{Nasal doc<br />
|syntax = values(hash);<br />
|source = {{fgdata file|Nasal/globals.nas|t=Source}}<br />
|text = Returns a vector containing the values of the given hash.<br />
|param1 = hash<br />
|param1text = Mandatory hash to get the values of.<br />
|example1 = var hash = {<br />
"a": 1,<br />
"b": 2,<br />
"c": 3<br />
};<br />
<br />
foreach(var val; values(hash)){<br />
print(val);<br />
}<br />
|example2text = The below example does exactly the same thing as the above example, but does not use <code>values()</code>:<br />
|example2 = var hash = {<br />
"a": 1,<br />
"b": 2,<br />
"c": 3<br />
};<br />
<br />
foreach(var key; keys(hash)){<br />
print(hash[key]);<br />
}<br />
}}<br />
<br />
== Extension functions new in FG 2020.1 ==<br />
The following functions have been added to the nasal core library and will be released with FlightGear version 2020.1. <br />
Befor the release they are available in the development branch "next".<br />
<br />
=== isfunc() ===<br />
Returns 1 if type or argument is a function, otherwise 0.<br />
<br />
=== isghost() ===<br />
Returns 1 if type or argument is a ghost, otherwise 0.<br />
<br />
=== ishash() ===<br />
Returns 1 if type or argument is a hash, otherwise 0.<br />
<br />
=== isint() ===<br />
Returns 1 if argument has a numeric value and x == floor(x), e.g. integer<br />
<br />
=== isnum() ===<br />
{{Nasal doc<br />
|syntax = isnum(x);<br />
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}<br />
|text = Returns 1 if x has a numeric value otherwise 0. <br />
}}<br />
<br />
=== isstr() ===<br />
Returns 1 if type or argument is a string, otherwise 0.<br />
<br />
=== isvec() ===<br />
Returns 1 if type or argument is a vector, otherwise 0.<br />
<br />
=== vecindex() ===<br />
{{Nasal doc<br />
|syntax = vecindex(vector, value);<br />
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}<br />
|text = Returns the index of value or nil, if value is not found in vector.<br />
}}<br />
<br />
== Variables ==<br />
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}}.<br />
<br />
=== D2R ===<br />
{{Nasal doc<br />
|syntax = var radians = degrees * D2R;<br />
|text = Converts an angle from degrees to radians when multiplied by the angle in degrees. Equal to <code>π / 180</code>.<br />
}}<br />
=== FPS2KT ===<br />
{{Nasal doc<br />
|syntax = var knots = feet_per_second * FPS2KT;<br />
|text = Converts a velocity from feet per second to knots when multiplied by the velocity in feet per second. Approximately equal to 0.5925.<br />
}}<br />
=== FT2M ===<br />
{{Nasal doc<br />
|syntax = var metres = feet * FT2M;<br />
|text = Converts a length from feet to metres when multiplied by the length in feet. Equal to 0.3048.<br />
}}<br />
=== GAL2L ===<br />
{{Nasal doc<br />
|syntax = var litres = gallons * GAL2L;<br />
|text = Converts a volume from US liquid gallons to litres when multiplied by the volume in gallons. Approximately equal to 3.7854.<br />
}}<br />
=== IN2M ===<br />
{{Nasal doc<br />
|syntax = var metres = inches * IN2M;<br />
|text = Converts a length from inches to metres when multiplied by the length in inches. Equal to 0.0254.<br />
}}<br />
=== KG2LB ===<br />
{{Nasal doc<br />
|syntax = var pounds = kilograms * KG2LB;<br />
|text = Converts a mass from kilograms to pounds when multiplied by the mass in kilograms. Approximately equal to 2.2046.<br />
}}<br />
=== KT2FPS ===<br />
{{Nasal doc<br />
|syntax = var feet_per_second = knots * KT2FPS;<br />
|text = Converts a velocity from knots to feet per second when multiplied by the velocity in knots. Approximately equal to 1.6878.<br />
}}<br />
=== KT2MPS ===<br />
{{Nasal doc<br />
|syntax = var metres_per_second = knots * KT2MPS;<br />
|text = Converts a velocity from knots to metres per second when multiplied by the velocity in knots. Approximately equal to 0.5144.<br />
}}<br />
=== L2GAL ===<br />
{{Nasal doc<br />
|syntax = var gallons = litres * L2GAL;<br />
|text = Converts a volume from litres to US liquid gallons when multiplied by the volume in litres. Approximately equal to 0.2642.<br />
}}<br />
=== LB2KG ===<br />
{{Nasal doc<br />
|syntax = var kilograms = pounds * LB2KG;<br />
|text = Converts a mass from pounds to kilograms when multiplied by the mass in pounds. Approximately equal to 0.4536.<br />
}}<br />
=== M2FT ===<br />
{{Nasal doc<br />
|syntax = var feet = metres * M2FT;<br />
|text = Converts a length from metres to feet when multiplied by the length in metres. Approximately equal to 3.2808.<br />
}}<br />
=== M2IN ===<br />
{{Nasal doc<br />
|syntax = var inches = metres * M2IN;<br />
|text = Converts a length from metres to inches when multiplied by the length in metres. Approximately equal to 39.3701.<br />
}}<br />
=== M2NM ===<br />
{{Nasal doc<br />
|syntax = var nautical_miles = metres * M2NM;<br />
|text = Converts a length from metres to nautical miles when multiplied by the length in metres. Approximately equal to 0.00054.<br />
}}<br />
=== MPS2KT ===<br />
{{Nasal doc<br />
|syntax = var knots = metres_per_second * MPS2KT;<br />
|text = Converts a velocity from metres per second to knots when multiplied by the velocity in metres per second. Approximately equal to 1.9438.<br />
}}<br />
=== NM2M ===<br />
{{Nasal doc<br />
|syntax = var metres = nautical_miles * NM2M;<br />
|text = Converts a length from nautical miles to metres when multiplied by the length in nautical miles. Equal to 1,852.<br />
}}<br />
=== R2D ===<br />
{{Nasal doc<br />
|syntax = var degrees = radians * R2D;<br />
|text = Converts an angle from radians to degrees when multiplied by the angle in radians. Equal to <code>180 / π</code>.<br />
}}<br />
<br />
{{Appendix}}<br />
<br />
<br />
{{Nasal namespaces}}</div>Jsbhttps://wiki.flightgear.org/w/index.php?title=Nasal_library&diff=123777Nasal library2020-04-17T15:34:36Z<p>Jsb: /* Extension functions new in FG 2020.1 */</p>
<hr />
<div>{{Nasal Navigation|nocat=1}}<br />
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:<br />
* {{flightgear file|src/Scripting}}<br />
* {{simgear file|simgear/nasal}}<br />
<br />
All these functions and variables are in the global namespace, that is, they are directly accessible (e.g., one can call <syntaxhighlight lang="nasal" inline>magvar()</syntaxhighlight> instead of <syntaxhighlight lang="nasal" inline>namespace.magvar()</syntaxhighlight>). However, if a namespace must be used, <code>globals</code> is the correct namespace, but using it is not recommended. For a more complete explanation, see [[Nasal Namespaces in-depth]].<br />
<br />
{{tip|Copy & paste the examples into your [[Nasal Console]] and execute them to see what they do.|width=70%}}<br />
<br />
== Core library functions ==<br />
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:<br />
* http://plausible.org/nasal/lib.html ([http://web.archive.org/web/20101010094553/http://plausible.org/nasal/lib.html archive])<br />
* {{simgear file|simgear/nasal/lib.c}} ([http://sourceforge.net/p/flightgear/simgear/ci/next/log/?path=/simgear/nasal/lib.c history])<br />
<br />
=== append() ===<br />
{{Nasal doc<br />
|syntax = append(vector, element[, element[, ...]]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=42|t=Source}}<br />
|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.<br />
|param1 = vector<br />
|param1text = The vector to which the arguments will be appended.<br />
|param2 = element<br />
|param2text = An element to be added to the vector.<br />
|example1 = <br />
var vector = [1, 2, 3]; # Initialize the vector<br />
append(vector, 4); # Append the number 4 to the end of the vector<br />
debug.dump(vector); # Print the contents of the vector<br />
|example2 = <br />
var vector = [1, 2, 3]; # Initialize the vector<br />
append(vector, 4, 5, 6); # Append the numbers 4, 5, and 6 to the end of the vector<br />
debug.dump(vector); # Print the contents of the vector<br />
}}<br />
<br />
=== bind() ===<br />
{{Nasal doc<br />
|syntax = bind(function, locals[, outer_scope]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=502|t=Source}}<br />
|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.<br />
|param1 = function<br />
|param1text = Function to evaluate.<br />
|param2 = locals<br />
|param2text = Hash containing values that will become the namespace (first closure) for the function.<br />
|param3 = outer_scope<br />
|param3text = Optional function which is bound to the next closure. This can be bound to yet another, making a linked list.<br />
|example1 = # This is a namespace/hash with a single member, named "key," which is initialized to 12 <br />
var Namespace = {<br />
key: 12<br />
};<br />
<br />
# This is different namespace/hash containing a function<br />
# dividing a variable "key" (which is unavailable/nil in this namespace) by 2<br />
var AnotherNamespace = {<br />
ret: func {<br />
key /= 2;<br />
}<br />
};<br />
<br />
# To see that key is not available, try to call AnotherNamespace.ret() first<br />
call(AnotherNamespace.ret, [], nil, nil, var errors = []);<br />
if(size(errors)){<br />
print("Key could not be divided/resolved!");<br />
debug.printerror(errors);<br />
}<br />
<br />
# Associate the AnotherNamespace.ret() function with the first namespace<br />
# so that "key" is now available<br />
var function = bind(AnotherNamespace.ret, Namespace);<br />
<br />
# Invoke the new function<br />
function();<br />
<br />
# Print out the value of Namespace.key<br />
# It was changed to 12 from 6 by AnotherNamespace.ret()<br />
print(Namespace.key);<br />
}}<br />
<br />
=== call() ===<br />
{{Nasal doc<br />
|syntax = call(func[, args[, me[, locals[, error]]]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=247|t=Source}}<br />
|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. <br />
|param1 = func<br />
|param1text = Function to execute.<br />
|param2 = args<br />
|param2text = Vector containing arguments to give to the called function.<br />
|param3 = me<br />
|param3text = <code>'''me'''</code> reference for the function call (i.e., for method calls). If given, this will override any <code>'''me'''</code> value existing in the namespace (locals argument).<br />
|param4 = locals<br />
|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.<br />
|param5 = error<br />
|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}}.<br />
|example1 =<br />
# prints "Called from call()"<br />
call(func {<br />
print("Called from call()");<br />
});<br />
|example2 =<br />
# prints "a = 1 : b = 2<br />
call(func(a, b){<br />
print("a = ", a, " : b = ", b);<br />
},<br />
[1, 2]<br />
);<br />
|example3 =<br />
var Hash = {<br />
new: func {<br />
var m = { parents: [Hash] };<br />
<br />
m.el1 = "string1";<br />
m.el2 = "string2";<br />
<br />
return m;<br />
}<br />
};<br />
<br />
# prints "me.el1 = string1", then "me.el2 = string2" on the next line<br />
call(func(a, b){ <br />
print("me.el", a, " = ", me["el" ~ a]); <br />
print("me.el", b, " = ", me["el" ~ b]);<br />
},<br />
[1, 2],<br />
Hash.new()<br />
);<br />
|example4 =<br />
# prints the value of math.pi<br />
call(func {<br />
print(pi);<br />
}, nil, nil, <br />
math<br />
);<br />
|example5 =<br />
call(func {<br />
print(math.ip); # math.ip doesn't exist<br />
}, nil, nil, nil,<br />
var errs = []<br />
);<br />
debug.printerror(errs); # The error is caught and printed using debug.printerror()<br />
}}<br />
<br />
=== caller() ===<br />
{{Nasal doc<br />
|syntax = caller([level]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=404|t=Source}}<br />
|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.<br />
<br />
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. <br />
|param1 = level<br />
|param1text = Optional integer specifying the stack level to return a result from. Defaults to 1 (i.e. the caller of the currently executing function).<br />
|example1 =<br />
var myFunction = func(a, b){<br />
debug.dump(caller(0)[0]); # prints a hash of local variables, including arguments a and b<br />
return 2 * 2;<br />
};<br />
<br />
print("2 x 2 = ", myFunction(2, 2));<br />
|example2 =<br />
var get_arg_value = func(){<br />
print("Argument to myFunc = ", caller(1)[0]['a']); # print the value of myFunc's single argument, using caller()<br />
};<br />
<br />
var myFunc = func(a){<br />
get_arg_value();<br />
};<br />
<br />
myFunc(3);<br />
|example3text = This is a real example taken from {{fgdata file|Nasal/canvas/MapStructure.nas}}. Function <code>r()</code> (above the TODOs) returns a hash with the key/value pairs as per its arguments. For example, something like this is returned: <code>{ name: "<name>", vis: 1, zindex: nil }</code>.<br />
|example3 =<br />
var MapStructure_selfTest = func() {<br />
var temp = {};<br />
temp.dlg = canvas.Window.new([600,400],"dialog");<br />
temp.canvas = temp.dlg.createCanvas().setColorBackground(1,1,1,0.5);<br />
temp.root = temp.canvas.createGroup();<br />
var TestMap = temp.root.createChild("map");<br />
TestMap.setController("Aircraft position");<br />
TestMap.setRange(25); # TODO: implement zooming/panning via mouse/wheel here, for lack of buttons :-/<br />
TestMap.setTranslation(<br />
temp.canvas.get("view[0]")/2,<br />
temp.canvas.get("view[1]")/2<br />
);<br />
var r = func(name,vis=1,zindex=nil) return caller(0)[0];<br />
# TODO: we'll need some z-indexing here, right now it's just random<br />
# TODO: use foreach/keys to show all layers in this case by traversing SymbolLayer.registry direclty ?<br />
# maybe encode implicit z-indexing for each lcontroller ctor call ? - i.e. preferred above/below order ?<br />
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'), ] ) <br />
TestMap.addLayer(factory: canvas.SymbolLayer, type_arg: type.name,<br />
visible: type.vis, priority: type.zindex,<br />
);<br />
}; # MapStructure_selfTest<br />
}}<br />
<br />
=== chr() ===<br />
{{Nasal doc<br />
|syntax = chr(code);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=175|t=Source}}<br />
|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 (<code>"string"</code>) supports control chracters. Strings in single quotes (<code>'string'</code>) do not.}}<br />
{{{!}} class="wikitable"<br />
! Code !! Name !! Equivalent to<br />
{{!-}}<br />
{{!}} 10 {{!!}} {{Wikipedia|Newline}} {{!!}} <code>\n</code><br />
{{!-}}<br />
{{!}} 9 {{!!}} {{Wikipedia|Tab key#Tab characters|Horizontal tab}} {{!!}} <code>\t</code><br />
{{!-}}<br />
{{!}} 13 {{!!}} {{Wikipedia|Carriage return}} {{!!}} <code>\r</code><br />
{{!}}}<br />
|param1 = code<br />
|param1text = Integer character code for the desired glyph.<br />
|example1 = print("Code 65 = ", chr(65)); # prints "Code 65 = A"<br />
|example2text = This example displays all of the characters in a list, in the format <code>Code '''n''' = >'''char'''<</code>, '''n''' being the index, and '''char''' being the character.<br />
|example2 =<br />
for(var i = 0; i <= 255; i += 1){<br />
print("Code ", i, " = >", chr(i), "<");<br />
}<br />
}}<br />
<br />
=== closure() ===<br />
{{Nasal doc<br />
|syntax = closure(func[, level]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=421|t=Source}}<br />
|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'''. <br />
|param1 = func<br />
|param1text = Function to evaluate.<br />
|param2 = level<br />
|param2text = Optional integer specifying the scope level. Defaults to 0 (the namespace of '''func''').<br />
|example1 =<br />
var get_math_e = func {<br />
return e; # return the value of math.e<br />
}<br />
<br />
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<br />
debug.dump(closure(myFunction)); # print the namespace of get_math_e<br />
<br />
print(myFunction());<br />
}}<br />
<br />
=== cmp() ===<br />
{{Nasal doc<br />
|syntax = cmp(a, b);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=112|t=Source}}<br />
|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'''. <br />
|param1 = a<br />
|param1text = First string argument for comparison.<br />
|param2 = b<br />
|param2text = Second string argument for comparison.<br />
|example1 = print(cmp("1", "two")); # prints -1<br />
|example2 = print(cmp("string", "string")); # prints 0<br />
|example3 = print(cmp("one", "2")); # prints 1<br />
|example4 = print(cmp("string1", "string2")); # prints -1<br />
}}<br />
<br />
=== compile() ===<br />
{{Nasal doc<br />
|syntax = compile(code[, filename]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=220|t=Source}}<br />
|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'''.<br />
|param1 = code<br />
|param1text = String containing Nasal code to be compiled.<br />
|param2 = filename<br />
|param2text = Optional string used for error messages/logging. Defaults to <code><compile></code><br />
|example1 = <br />
var myCode = 'print("hello");';<br />
var helloFunc = compile(myCode, "myCode");<br />
helloFunc();<br />
|example2text = <code>compile</code> 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 <tt>''[[$FG_ROOT]]/gui/dialogs/test.xml''</tt>.<br />
<syntaxhighlight lang="xml"><br />
<?xml version="1.0"?><br />
<br />
<PropertyList><br />
<br />
<nasal><![CDATA[<br />
print("You have FlightGear v", getprop("/sim/version/flightgear"));<br />
]]></nasal><br />
<br />
</PropertyList><br />
</syntaxhighlight><br />
Now, start FlightGear and execute this code in the [[Nasal Console]].<br />
|example2 =<br />
# Build the path<br />
var FGRoot = getprop("/sim/fg-root");<br />
var filename = "/gui/dialogs/test.xml";<br />
var path = FGRoot ~ filename;<br />
<br />
var blob = io.read_properties(path);<br />
var script = blob.getValues().nasal; # Get the nasal string<br />
<br />
# Compile the script. We're passing the filename here for better runtime diagnostics <br />
var code = call(func {<br />
compile(script, filename);<br />
}, nil, nil, var compilation_errors = []);<br />
<br />
if(size(compilation_errors)){<br />
die("Error compiling code in: " ~ filename);<br />
}<br />
<br />
# Invoke the compiled script, equivalent to code(); <br />
# We're using call() here to detect errors:<br />
call(code, [], nil, nil, var runtime_errors = []);<br />
<br />
if(size(runtime_errors)){<br />
die("Error calling code compiled loaded from: " ~ filename);<br />
}<br />
}}<br />
<br />
=== contains() ===<br />
{{Nasal doc<br />
|syntax = contains(hash, key);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=184|t=Source}}<br />
|text = Returns 1 (True) if the hash contains the specified key, or 0 (False) if not.<br />
|param1 = hash<br />
|param1text = The hash to search in.<br />
|param2 = key<br />
|param2text = The scalar to be searched for, contained as a key in the hash.<br />
|example1 =<br />
# Initialize a hash<br />
var hash = {<br />
element: "value"<br />
};<br />
print(contains(hash, "element") ? "Yes" : "No"); # This will print "Yes"<br />
|example2 =<br />
# Initialize a hash<br />
var hash = {<br />
element: "value"<br />
};<br />
print(contains(hash, "element2") ? "Yes" : "No"); # This will print "No"<br />
}}<br />
<br />
=== delete() ===<br />
{{Nasal doc<br />
|syntax = delete(hash, key);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=83|t=Source}}<br />
|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 <code>'''nil'''</code> 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.<br />
|param1 = hash<br />
|param1text = The hash from which to delete the key.<br />
|param2 = key<br />
|param2text = The scalar to be deleted, contained as a key in the hash.<br />
|example1 =<br />
# Initialize the hash<br />
var hash = {<br />
element1: "value1",<br />
element2: "value2"<br />
};<br />
delete(hash, "element1"); # Delete element1<br />
debug.dump(hash); # prints the hash, which is now minus element1<br />
}}<br />
<br />
=== die() ===<br />
{{Nasal doc<br />
|syntax = die(error);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=288|t=Source}}<br />
|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()}}.<br />
|param1 = error<br />
|param1text = String describing the error.<br />
:{{inote|This parameter is technically optional, but it is highly recommended to use it.}}<br />
|example1 = <br />
print("Will print");<br />
die("Don't go any further!"); <br />
print("Won't print"); # Will not be printed because die() stops the process<br />
}}<br />
<br />
=== find() ===<br />
{{Nasal doc<br />
|syntax = find(needle, haystack);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=450|t=Source}}<br />
|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.<br />
|param1 = needle<br />
|param1text = String to search for.<br />
|param2 = haystack<br />
|param2text = String to search in.<br />
|example1 = print(find("c", "abcdef")); # prints 2<br />
|example2 = print(find("x", "abcdef")); # prints -1<br />
|example3 = print(find("cd", "abcdef")); # prints 2<br />
}}<br />
<br />
=== ghosttype() ===<br />
{{Nasal doc<br />
|syntax = ghosttype(ghost);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=207|t=Source}}<br />
|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++ <code>naGhostType</code> 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.<br />
|param1 = ghost<br />
|param1text = Ghost to return a description for.<br />
|example1 = print(ghosttype(airportinfo())); # prints "airport"<br />
}}<br />
<br />
=== id() ===<br />
{{Nasal doc<br />
|syntax = id(object);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=570|t=Source}}<br />
|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 <code>'''<type>''':'''<id>'''</code>, where '''<type>''' is the type of object, and '''<id>''' is the ID.<br />
|param1 = object<br />
|param1text = Can be either of a string, a vector, a hash, a code, a function, or a ghost.<br />
|example1 = print(id("A")); # prints "str:000000001624A590"<br />
}}<br />
<br />
=== int() ===<br />
{{Nasal doc<br />
|syntax = int(number);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=90|t=Source}}<br />
|text = Returns the integer part of the numeric value of the single argument, or <code>'''nil'''</code> if none exists.<br />
|param1 = number<br />
|param1text = Number or string with just a number in it to return an integer from.<br />
|example1 = print(int(23)); # prints "23"<br />
|example2 = print(int(23.123)); # prints "23"<br />
|example3 = debug.dump(int("string")); # prints "nil"<br />
}}<br />
<br />
=== keys() ===<br />
{{Nasal doc<br />
|syntax = keys(hash);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=33|t=Source}}<br />
|text = Returns a vector containing the list of keys found in the single hash argument. <br />
|param1 = hash<br />
|param1text = The hash to return the keys from.<br />
|example1 = <br />
# Initialize a hash<br />
var hash = {<br />
element1: "value",<br />
element2: "value"<br />
};<br />
debug.dump(keys(hash)); # print the vector<br />
}}<br />
<br />
=== left() ===<br />
{{Nasal doc<br />
|syntax = left(string, length);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=149|t=Source}}<br />
|version = 2.12<br />
|commit = {{simgear commit|bd7163|t=commit}}<br />
|text = Returns a substring of '''string''', starting from the left.<br />
|param1 = string<br />
|param1text = String to return part of.<br />
|param2 = length<br />
|param2text = Integer specifying the length of the substring to return.<br />
|example1 = print(left("string", 2)); # prints "st"<br />
}}<br />
<br />
=== num() ===<br />
{{Nasal doc<br />
|syntax = num(number);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=102|t=Source}}<br />
|text = Returns the numerical value of the single string argument, or <code>'''nil'''</code> if none exists. <br />
|param1 = number<br />
|param1text = String with just a number in it to return a number from.<br />
|example1 = print(num("23")); # prints "23"<br />
|example2 = print(num("23.123")); # prints "23.123"<br />
|example3 = debug.dump(num("string")); # prints "nil"<br />
}}<br />
<br />
=== pop() ===<br />
{{Nasal doc<br />
|syntax = pop(vector);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=50|t=Source}}<br />
|text = Removes and returns the last element of the single vector argument, or <code>'''nil'''</code> if the vector is empty. <br />
|param1 = vector<br />
|param1text = Vector to remove an element from.<br />
|example1 = <br />
var vector = [1, 2, 3];<br />
pop(vector);<br />
debug.dump(vector); # prints "[1, 2]"<br />
|example2 = <br />
var vector = [1, 2, 3];<br />
debug.dump(pop(vector)); # prints "3"<br />
|example3 = <br />
var vector = [];<br />
debug.dump(pop(vector)); # prints "nil"<br />
}}<br />
<br />
=== right() ===<br />
{{Nasal doc<br />
|syntax = right(string, length);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=161|t=Source}}<br />
|version = 2.12<br />
|commit = {{simgear commit|bd7163|t=commit}}<br />
|text = Returns a substring of '''string''', starting from the right.<br />
|param1 = string<br />
|param1text = String to return part of.<br />
|param2 = length<br />
|param2text = Integer specifying the length of the substring to return.<br />
|example1 = print(right("string", 2)); # prints "ng"<br />
}}<br />
<br />
=== setsize() ===<br />
{{Nasal doc<br />
|syntax = setsize(vector, size);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=56|t=Source}}<br />
|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 <code>'''nil'''</code> entries. Returns the vector operated upon. <br />
|param1 = vector<br />
|param1text = The vector to be operated on.<br />
|param2 = size<br />
|param2text = The desired size of the vector in number of entries.<br />
|example1 = <br />
var vector = [1, 2, 3]; # Initialize a vector<br />
setsize(vector, 4);<br />
debug.dump(vector); # print the vector<br />
|example2 = <br />
var vector = [1, 2, 3]; # Initialize a vector<br />
setsize(vector, 2);<br />
debug.dump(vector); # print the vector<br />
}}<br />
<br />
=== size() ===<br />
{{Nasal doc<br />
|syntax = size(object);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=23|t=Source}}<br />
|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 <code>'''nil'''</code> or a number, this error will be thrown: <code>object has no size()</code>.<br />
|param1 = object<br />
|param1text = Object to find the size of. Must be a string, a vector or a hash.<br />
|example1 = <br />
var string = "string";<br />
print(size(string)); # prints "6"<br />
|example2 =<br />
var vector = [1, 2, 3];<br />
print(size(vector)); # prints "3"<br />
|example3 =<br />
var hash = {<br />
element1: "value1",<br />
element2: "value2",<br />
element3: "value3"<br />
};<br />
print(size(hash)); # prints "3"<br />
}}<br />
<br />
=== sort() ===<br />
{{Nasal doc<br />
|syntax = sort(vector, function);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=542|t=Source}}<br />
|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/}}, <code>sort()</code> 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.<br />
|param1 = vector<br />
|param1text = Input vector to sort.<br />
|param2 = function<br />
|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.<br />
{{{!}} class="wikitable"<br />
! Return value !! Meaning<br />
{{!-}}<br />
{{!}} less than 0 {{!!}} first argument should go before second argument<br />
{{!-}}<br />
{{!}} 0 {{!!}} first argument equals second argument<br />
{{!-}}<br />
{{!}} greater than 0 {{!!}} first argument should go after second argument<br />
{{!}}}<br />
<br />
|example1text = This example sorts elements from smallest to greatest.<br />
|example1 = <br />
var sort_rules = func(a, b){<br />
if(a < b){<br />
return -1; # A should before b in the returned vector<br />
}elsif(a == b){<br />
return 0; # A is equivalent to b <br />
}else{<br />
return 1; # A should after b in the returned vector<br />
}<br />
}<br />
debug.dump(sort([3, 2, 5, 6, 4, 1], sort_rules)); # prints "[1, 2, 3, 4, 5, 6]"<br />
|example2text = This example sorts elements from greatest to smallest.<br />
|example2 = <br />
# Outputs the elements in reverse order (greatest to smallest)<br />
var sort_rules = func(a, b){<br />
if(a < b){<br />
return 1; # -1 in the above example<br />
}elsif(a == b){<br />
return 0;<br />
}else{<br />
return -1; # 1 in the above example<br />
}<br />
}<br />
debug.dump(sort([3, 2, 5, 6, 4, 1], sort_rules)); # prints "[6, 5, 4, 3, 2, 1]"<br />
|example3text = This example sorts a vector of strings (runways for example) from smallest to greatest.<br />
|example3 = <br />
var runways = ["09R","27R","26L","09L","15"];<br />
var rwy = sort(runways,func(a,b) cmp(a,b));<br />
debug.dump(rwy); # prints ['09L','09R','15','26L','27R']<br />
}}<br />
<br />
=== split() ===<br />
{{Nasal doc<br />
|syntax = split(delimiter, string);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=460|t=Source}}<br />
|text = Splits the input string into a vector of substrings bounded by occurrences of the delimiter substring.<br />
|param1 = delimiter<br />
|param1text = String that will split the substrings in the returned vector.<br />
|param2 = string<br />
|param2text = String to split up.<br />
|example1 = debug.dump(split("cd", "abcdef")); # prints "['ab', 'ef']"<br />
|example2 = debug.dump(split(".", "3.2.0")); # prints "[3, 2, 0]"<br />
|example3 = debug.dump(split("/", "path/to/file")); # prints "['path', 'to', 'file']"<br />
}}<br />
<br />
=== sprintf() ===<br />
{{Nasal doc<br />
|syntax = sprintf(format[, arg[, arg, [...]]]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=355|t=Source}}<br />
|text = Creates and returns a string formatted using ANSI C {{func link|vsnprintf()|link=http://en.cppreference.com/w/c/io/vfprintf}} <ref><br />
{{Cite web<br />
|url = http://sourceforge.net/p/flightgear/simgear/ci/next/tree/simgear/nasal/lib.c#l308<br />
|title = fgdata/simgear/simgear/nasal/lib.c, line 308<br />
|accessdate = October 2015<br />
}}<br />
</ref>. Below is a table of supported format specifiers.<br />
{{{!}} class="wikitable" width="75%"<br />
{{!}}+ %[flags][width][.precision]specifier<br />
! colspan="2" {{!}} Flags<br />
{{!-}}<br />
! Flag !! Output<br />
{{!-}}<br />
{{!}} <code>+</code> {{!!}} 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.<br />
{{!-}}<br />
{{!}} ''space'' {{!!}} Prefixes non-signed numbers with a space.<br />
{{!-}}<br />
{{!}} <code>-</code> {{!!}} Left-align the output of this placeholder (the default is to right-align the output) when the width option is specified.<br />
{{!-}}<br />
{{!}} <code>0</code> {{!!}} Use 0 instead of spaces to pad a field when the width option is specified.<br />
{{!-}}<br />
{{!}} <code>#</code> {{!!}} Used with <code>o</code>, <code>x</code> or <code>X</code> specifiers the value is preceded with <tt>0</tt>, <tt>0x</tt> or <tt>0X</tt> respectively for values different than zero. Used with <code>e</code>, <code>E</code> and <code>f</code>, 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 <code>g</code> or <code>G</code> the result is the same as with <code>e</code> or <code>E</code> but trailing zeros are not removed.<br />
{{!-}}<br />
! colspan="2" {{!}} Width<br />
{{!-}}<br />
{{!}} colspan="2" {{!}} Integer specifying the minimum number of characters to be returned. This includes the decimal point and decimal fraction.<br />
{{!-}}<br />
! colspan="2" {{!}} Precision<br />
{{!-}}<br />
{{!}} colspan="2" {{!}} Integer preceded by a dot specifying the number of decimal places to be written.<br />
{{!-}}<br />
! colspan="2" {{!}} Specifiers<br />
{{!-}}<br />
! Specifier !! Output<br />
{{!-}}<br />
{{!}} <code>d</code>, <code>i</code> {{!!}} Signed decimal number.<br />
{{!-}}<br />
{{!}} <code>s</code> {{!!}} A string<br />
{{!-}}<br />
{{!}} <code>%</code> {{!!}} Percent (%) character.<br />
{{!-}}<br />
{{!}} <code>c</code> {{!!}} 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.<br />
{{!-}}<br />
{{!}} <code>o</code> {{!!}} Unsigned integer as an octal number.<br />
{{!-}}<br />
{{!}} <code>u</code> {{!!}} Unsigned decimal integer.<br />
{{!-}}<br />
{{!}} <code>x</code>, <code>X</code> {{!!}} Unsigned integer as a hexadecimal number. If <code>x</code> is used, any letters in the number are lowercase, while <code>X</code> gives uppercase.<br />
{{!-}}<br />
{{!}} <code>e</code>, <code>E</code> {{!!}} Double value in scientific notation (i.e., ''[-]ddd.ddd'''e'''[+/-]ddd''), with an exponent being denoted by <tt>e</tt> or <tt>E</tt> depending on whether an upper or lowercase is used respectively.<br />
{{!-}}<br />
{{!}} <code>f</code> {{!!}} Floating-point number, in fixed decimal notation, by default with 6 decimal places.<br />
{{!-}}<br />
{{!}} <code>F</code> {{!!}} Appears to be available<ref><br />
{{Cite web<br />
|url = http://sourceforge.net/p/flightgear/simgear/ci/next/tree/simgear/nasal/lib.c#l389<br />
|title = fgdata/simgear/simgear/nasal/lib.c, line 389<br />
|accessdate = October 2015<br />
}}<br />
</ref>, but doesn't work.<br />
{{!-}}<br />
{{!}} <code>g</code>, <code>G</code> {{!!}} Double in either normal or exponential notation, whichever is more appropriate for its magnitude. <code>g</code> uses lower-case letters, <code>G</code> 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.<br />
{{!}}}<br />
<br />
|param1 = format<br />
|param1text = String specifying the format. Can be used with or without a format specifiers. See below for examples.<br />
|param2 = arg<br />
|param2text = Argument specifying a value to replace a format placeholder (such as <code>%d</code>) in the format string. Not required if there are no format specifiers.<br />
<br />
|example1 = print(sprintf("%i", 54)); # prints "54"<br />
|example2 = print(sprintf("Pi = %+.10f", math.pi)); # prints "Pi = +3.1415926536"<br />
|example3 = <br />
print(sprintf("%6d", 23)); # prints " 23"<br />
print(sprintf("%06d", 23)); # prints "000023"<br />
|example4 =<br />
var FGVer = getprop("/sim/version/flightgear");<br />
print(sprintf("You have FlightGear v%s", FGVer)); # prints "You have FlightGear v<your version>"<br />
|example5 = <br />
print(sprintf("Hexadecimal 100000 = %X", 100000)); # prints "Hexadecimal 100000 = 186A0"<br />
print(sprintf("Hexadecimal 100000 = %x", 100000)); # prints "Hexadecimal 100000 = 186a0"<br />
|example6 = print(sprintf("Code 65 is %c", 65)); # prints "Code 65 is A"<br />
|example7 = <br />
print(sprintf("%e", 54)); # prints "5.400000e+001"<br />
print(sprintf("%E", 54)); # prints "5.400000E+001"<br />
|example8 = print(sprintf("%o", 54)); # prints "66"<br />
|example9 = print(sprintf("50%% of 100 is %i", 100 / 2)); # prints "50% of 100 is 50"<br />
|example10 =<br />
print(sprintf("%.2f", 1.4)); #prints "1.40"<br />
print(sprintf("%.1f", 1.4)); #prints "1.4"<br />
print(sprintf("% 4.1f", 1.4)); #prints " 1.4"<br />
print(sprintf("%04.1f", 1.4)); #prints "01.4"<br />
print(sprintf("% 6.1f", 1.4)); #prints " 1.4"<br />
print(sprintf("%06.1f", 1.4)); #prints "0001.4"<br />
}}<br />
<br />
=== streq() ===<br />
{{Nasal doc<br />
|syntax = streq(a, b);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=107|t=Source}}<br />
|text = Tests the string values of the two arguments for equality. This function is needed because the <code>'''=='''</code> 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.}}<br />
|param1 = a<br />
|param1text = First argument for testing equality.<br />
|param2 = b<br />
|param2text = Second argument for testing equality.<br />
|example1 = print(streq("0", "0")); # prints "1" (True)<br />
|example2 = <br />
print(0 == 0.0); # prints "1" (True)<br />
print(streq("0", "0.0")); # prints "0" (False)<br />
}}<br />
<br />
=== substr() ===<br />
{{Nasal doc<br />
|syntax = substr(string, start [, length]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=129|t=Source}}<br />
|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).<br />
|param1 = string<br />
|param1text = String to return a substring from.<br />
|param2 = start<br />
|param2text = Integer specifying the start of a substring. Negative values specify a position from the end of the string.<br />
|param3 = length<br />
|param3text = Optional argument specifying the length of the substring. Defaults to the end of the string.<br />
|example1 = print(substr("abcde", 1, 3)); # prints "bcd"<br />
|example2 = print(substr("abcde", 1)); # prints "bcde"<br />
|example3 = print(substr("abcde", 2, 1)); # prints "c"<br />
|example4 = print(substr("abcde", -2)); # prints "de"<br />
|example5 = print(substr("abcde", -3, 2)); # prints "cd"<br />
}}<br />
<br />
=== subvec() ===<br />
{{Nasal doc<br />
|syntax = subvec(vector, start[, length]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=63|t=Source}}<br />
|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). <br />
|param1 = vector<br />
|param1text = The vector to take the sub-vector from.<br />
|param2 = start<br />
|param2text = The starting point of the sub-vector within the given vector.<br />
|param3 = length<br />
|param3text = Optional argument specifying the length of the sub-vector, from the starting point.<br />
'''Notes:'''<br />
* Omitting the ''vector'' and ''start'' arguments is not an error (possibly it should be) but the return value is ''nil''.<br />
* A negative ''start'' argument ''is'' an error. This seems wrong. Perhaps the language designer could comment.<br />
* A value of ''start'' greater than ''size(vector)'' causes an error. A value equal to ''size(vector)'' returns an empty vector.<br />
* 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.<br />
|example1 = <br />
var vector = [1, 2, 3];<br />
debug.dump(subvec(vector, 0)); # prints "[1, 2, 3]"<br />
|example2 = <br />
var vector = [1, 2, 3];<br />
debug.dump(subvec(vector, 1)); # prints "[2, 3]"<br />
|example3 = <br />
var vector = [1, 2, 3];<br />
debug.dump(subvec(vector, 1, 1)); # prints "[2]"<br />
}}<br />
<br />
=== typeof() ===<br />
{{Nasal doc<br />
|syntax = typeof(object);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=193|t=Source}}<br />
|text = Returns a string indicating the whether the object is <code>'''nil'''</code>, a scalar (number or string), a vector, a hash, a function, or a ghost.<br />
|param1 = object<br />
|param1text = Object to return the type of.<br />
|example1 = <br />
var object = nil;<br />
print(typeof(object)); # prints "nil"<br />
|example2 = <br />
var object = "Hello world!";<br />
print(typeof(object)); # prints "scalar"<br />
|example3 = <br />
var object = math.pi;<br />
print(typeof(object)); # prints "scalar"<br />
|example4 = <br />
var object = [1, 2, 3];<br />
print(typeof(object)); # prints "vector"<br />
|example5 = <br />
var object = {};<br />
print(typeof(object)); # prints "hash"<br />
|example6 = <br />
var object = func {};<br />
print(typeof(object)); # prints "func"<br />
|example7 =<br />
var object = airportinfo();<br />
print(typeof(object)); # prints "ghost"<br />
}}<br />
<br />
<!-- == Extension modules ==<br />
=== thread ===<br />
{{WIP}}<br />
<br />
{{Nasal doc<br />
|syntax = thread.newthread(func);<br />
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}<br />
|text = <br />
|example1text = start a new worker thread<br />
|example1 = thread.newthread( func() {} );<br />
}}<br />
<br />
{{Nasal doc<br />
|syntax = thread.newlock();<br />
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}<br />
|text = <br />
|example1text = create a new lock<br />
|example1 = var lock = thread.newlock()<br />
}}<br />
<br />
{{Nasal doc<br />
|syntax = thread.lock();<br />
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}<br />
|text = <br />
|example1text = lock a lock<br />
|example1 = var lock = thread.newlock()<br />
}}<br />
<br />
{{Nasal doc<br />
|syntax = thread.unlock();<br />
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}<br />
|text = <br />
|example1text = unlock a lock<br />
|example1 = var lock = thread.unlock()<br />
}}<br />
<br />
<br />
{{Nasal doc<br />
|syntax = thread.newsem();<br />
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}<br />
|text = <br />
|example1text = create a new {{Wikipedia|semaphore}}<br />
|example1 = var semaphore = thread.newsem()<br />
}}<br />
<br />
<br />
{{Nasal doc<br />
|syntax = thread.semdown();<br />
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}<br />
|text = <br />
|example1text = semaphore down<br />
|example1 = thread.semdown(semaphore)<br />
}}<br />
<br />
<br />
{{Nasal doc<br />
|syntax = thread.semup();<br />
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}<br />
|text = <br />
|example1text = semaphore up<br />
|example1 = thread.semup(semaphore)<br />
}} --><br />
<br />
== Extension functions ==<br />
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:<br />
* {{flightgear file|src/Scripting/NasalPositioned.cxx}}<br />
* {{flightgear file|src/Scripting/NasalSys.cxx}}<br />
* {{fgdata file|Nasal/globals.nas}}<br />
<br />
=== abort() ===<br />
{{Nasal doc<br />
|syntax = abort();<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=565|t=Source}}<br />
|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 "exit" [[Fgcommands|fgcommand]], which will exit FlightGear more gracefully (see example below).<br />
|example1text = This example will immediately stop FlightGear with an error, such as "FlightGear has stopped working."<br />
|example1 = abort();<br />
|example2text = For exiting FlightGear in a better way, please use the following code:<br />
|example2 = fgcommand("exit");<br />
}}<br />
<br />
=== abs() ===<br />
{{Nasal doc<br />
|syntax = abs(number);<br />
|source = {{fgdata file|Nasal/globals.nas|t=Source}}<br />
|text = This simple function returns the {{wikipedia|absolute value|noicon=1}} of the provided number.<br />
|param1 = number<br />
|param1text = This argument is required and should be a number.<br />
|example1 = print(abs(1)); # prints "1"<br />
|example2 = print(abs(-1)); # prints "1"<br />
}}<br />
<br />
=== aircraftToCart() ===<br />
This new function in FG 2017.2.1 takes coordinates in aircraft structural coordinate system, and translate them into geocentric coordinates.<br />
Example for (5,6,7):<br />
<syntaxhighlight lang="nasal"><br />
var pos = aircraftToCart({x: -5, y: 6, z: -7});<br />
var coord = geo.Coord.new();<br />
coord.set_xyz(pos.x, pos.y, pos.z);<br />
</syntaxhighlight><br />
Notice: x and z is inverted sign on purpose.<br />
if you want lat. lon, alt from that, just call: (degrees and meters)<br />
<br />
<syntaxhighlight lang="nasal"><br />
coord.lat()<br />
coord.lon()<br />
coord.alt()<br />
</syntaxhighlight><br />
<br />
=== addcommand() ===<br />
{{Nasal doc<br />
|syntax = addcommand(name, code);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=659|t=Source}}<br />
|version = 2.12<br />
|commit = {{flightgear commit|7b663c|t=commit}}<br />
|text = {{see also|Howto:Add new fgcommands to FlightGear}}<br />
<br />
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.<br />
|param1 = name<br />
|param1text = This will become the name of the new fgcommand. Must be a string.<br />
|param2 = code<br />
|param2text = The code that will be executed when the fgcommand is run. Must be a function.<br />
|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.<br />
|example1 = addcommand("myFGCmd", func(node) {<br />
print("fgcommand 'myFGCmd' has been run.");<br />
props.dump( node );<br />
});<br />
fgcommand("myFGCmd", props.Node.new({foo:1, bar:2}) );<br />
|example2text = This example demonstrates how parameters are defined in a new fgcommand.<br />
|example2 = addcommand("myFGCmd", func(node){<br />
print(node.getNode("number").getValue()); # prints the value of "number," which is 12<br />
});<br />
fgcommand("myFGCmd", props.Node.new({"number": 12}));<br />
}}<br />
<br />
=== airportinfo() ===<br />
{{Nasal doc<br />
|syntax = airportinfo();<br />
airportinfo(type);<br />
airportinfo(id);<br />
airportinfo(lat, lon[, type]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1024|t=Source}}<br />
|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:<br />
* '''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.<br />
* '''lon''': Longitude of the location.<br />
* '''lat''': Latitude of the location.<br />
* '''has_metar''': True or false depending whether the airport has a [[METAR]] code defined for it.<br />
* '''elevation''': Elevation of the location in metres.<br />
* '''id''': ICAO code of the airport (or ID of the seaplane base/heliport).<br />
* '''name''': Name of the airport/heliport/seaplane base.<br />
* '''runways'''<br />
** '''<runway name>'''<br />
*** '''id''': Name of runway.<br />
*** '''lat''': Latitude of the runway.<br />
*** '''lon''': Longitude of the runway.<br />
*** '''heading''': Heading of the runway.<br />
*** '''length''': Length of the runway in metres.<br />
*** '''width''': Width of the runway in metres.<br />
*** '''surface''': Runway surface type.<br />
*** '''threshold''': Length of the runway's {{wikipedia|displaced threshold}} in metres. Will return 0 if there is none.<br />
*** '''stopway''': Length of the runway's stopway (the area before the threshold) in metres. Will return 0 if there is none.<br />
*** '''reciprocal''': <code>runway</code> ghost of the reciprocal runway.<br />
*** '''ils_frequency_mhz''': ILS frequency in megahertz.<br />
*** '''ils''': <code>navaid</code> ghost of the ILS transmitter.<br />
* '''helipads'''<br />
** '''<helipad name>'''<br />
*** '''id''': Name of helipad.<br />
*** '''lat''': Latitude of the helipad.<br />
*** '''lon''': Longitude of the helipad.<br />
*** '''heading''': Heading of the helipad.<br />
*** '''length''': Length of the helipad in metres.<br />
*** '''width''': Width of the helipad in metres.<br />
*** '''surface''': Helipad surface type.<br />
* '''taxiways'''<br />
** '''<taxiway name>'''<br />
*** '''id''': Name of taxiway.<br />
*** '''lat''': Latitude of the taxiway.<br />
*** '''lon''': Longitude of the taxiway.<br />
*** '''heading''': Heading of the taxiway.<br />
*** '''length''': Length of the taxiway in metres.<br />
*** '''width''': Width of the taxiway in metres.<br />
*** '''surface''': Taxiway surface type.<br />
<br />
Information is extracted in the same way as accessing members of a Nasal hash. For example:<br />
<syntaxhighlight lang="nasal"><br />
# prints to lengths of the runways of the nearest airport in feet and metres<br />
var info = airportinfo();<br />
print("-- Lengths of the runways at ", info.name, " (", info.id, ") --");<br />
foreach(var rwy; keys(info.runways)){<br />
print(rwy, ": ", math.round(info.runways[rwy].length * M2FT), " ft (", info.runways[rwy].length, " m)");<br />
}<br />
</syntaxhighlight><br />
<br />
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.<br />
|param1 = id<br />
|param1text = The {{wikipedia|International Civil Aviation Organization airport code|ICAO code|noicon=1}} of an airport to retrieve information about.<br />
|param2 = type<br />
|param2text = When this argument is used, the function will return the closest airport of a certain type. Can be one of "heliport," "seaport," or "airport" (default).<br />
: {{inote|Running this function without any parameters is equivalent to this:<br />
: <syntaxhighlight lang="nasal"><br />
airportinfo("airport");<br />
</syntaxhighlight><br />
}}<br />
|param3 = lat ''and'' lon<br />
|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.<br />
|example1 = var info = airportinfo();<br />
print("Nearest airport: ", info.name, " (", info.id, ")"); # prints the name and ICAO code of the nearest airport<br />
|example2 = var info = airportinfo("heliport");<br />
print("Elevation of the nearest heliport: ", math.round(info.elevation * M2FT), " ft"); # prints the elevation and name of the nearest heliport<br />
|example3 = var info = airportinfo("KSQL");<br />
print("-- Runways of ", info.name, " (", info.id, "): --");<br />
foreach(var rwy; keys(info.runways)) {<br />
print(rwy); # prints the runways of KSQL<br />
}<br />
|example4 = var info = airportinfo(37.81909385, -122.4722484);<br />
print("Coordinates of the nearest airport: ", info.lat, ", ", info.lon); # print the name and ICAO of the nearest airport to the Golden Gate Bridge<br />
|example5 = var info = airportinfo(37.81909385, -122.4722484, "seaport");<br />
print("Nearest seaplane base: ", info.name, " (", info.id, ")"); # print the name and ID of the nearest seaplane base to the Golden Gate Bridge<br />
|example6text = This example prints the all information from an <code>airportinfo()</code> call.<br />
|example6 = var info = airportinfo("KSFO");<br />
print(info.name);<br />
print(info.id);<br />
print(info.lat);<br />
print(info.lon);<br />
print(info.has_metar);<br />
print(info.elevation);<br />
foreach(var rwy; keys(info.runways)){<br />
print("-- ", rwy, " --");<br />
print(info.runways[rwy].lat);<br />
print(info.runways[rwy].lon);<br />
print(info.runways[rwy].length);<br />
print(info.runways[rwy].width);<br />
print(info.runways[rwy].heading);<br />
print(info.runways[rwy].stopway);<br />
print(info.runways[rwy].threshold);<br />
}<br />
}}<br />
<br />
=== airwaysRoute() ===<br />
{{Nasal doc<br />
|syntax = airwaysRoute(start, end[, type]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1933|t=Source}}<br />
|text = {{see also|Nasal Flightplan}}<br />
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.<br />
|param1 = start<br />
|param1text = Start waypoint, in the form of a waypoint ghost, such as that provided by {{func link|flightplan()}}.<br />
|param2 = end<br />
|param2text = Same as above.<br />
|param3 = type<br />
|param3text = Instructs the function to compute a high level route (when set to "highlevel"), or a low level route (when set to "lowlevel"). Defaults to "highlevel."<br />
|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.<br />
|example1 = var fp = flightplan();<br />
var start = fp.getWP(0);<br />
var end = fp.getWP(fp.getPlanSize() - 1);<br />
var rt = airwaysRoute(start, end);<br />
foreach(var wp; rt){<br />
print(wp.wp_name); # print the waypoints in the computed route<br />
}<br />
|example2text = Exactly the same as above, but computes a low level path.<br />
|example2 = var fp = flightplan();<br />
var start = fp.getWP(0);<br />
var end = fp.getWP(fp.getPlanSize() - 1);<br />
var rt = airwaysRoute(start, end, "lowlevel");<br />
foreach(var wp; rt){<br />
print(wp.wp_name); # print the waypoints in the computed route<br />
}<br />
}}<br />
<br />
=== assert() ===<br />
{{Nasal doc<br />
|syntax = assert(condition[, message]);<br />
|source = {{fgdata file|Nasal/globals.nas|t=Source}}<br />
|version = 3.2<br />
|commit = {{fgdata commit|8b16a7|t=commit}}<br />
|text = Returns either true if the condition evaluates as true, or aborts with a {{func link|die()}} call, which can be customised.<br />
|param1 = condition<br />
|param1text = Condition to evaluate.<br />
|param2 = message<br />
|param2text = Optional message that will be used in any {{func link|die()}} call. Defaults to "assertion failed!"<br />
|example1 = var a = 1;<br />
var b = 2;<br />
print(assert(a < b)); # prints "1" (true)<br />
|example2 = var a = 1;<br />
var b = 2;<br />
assert(a > b, 'a is not greater than b'); # aborts with a custom error message<br />
}}<br />
<br />
=== carttogeod() ===<br />
{{Nasal doc<br />
|syntax = carttogeod(x, y, z);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=945|t=Source}}<br />
|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).<ref>{{simgear file|simgear/math/sg_geodesy.hxx|l=43}}</ref><br />
|param1 = x<br />
|param1text = Mandatory x-axis value, in metres.<br />
|param2 = y<br />
|param2text = Mandatory y-axis value, in metres.<br />
|param3 = z<br />
|param3text = Mandatory z-axis value, in metres.<br />
|example1 = var (lat, lon, alt) = carttogeod(6378137, 0, 0); # point is the intersection of the prime meridian and equator.<br />
print("Latitude: ", lat); # prints lat, lon and alt, which are all zero, see above<br />
print("Longitude: ", lon);<br />
print("Altitude: ", alt);<br />
}}<br />
<br />
=== cmdarg() ===<br />
{{Nasal doc<br />
|private = _cmdarg()<br />
|syntax = cmdarg();<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=513|t=Part 1}} {{!}} {{fgdata file|Nasal/globals.nas|t=Part 2}}<br />
|text = <code>cmdarg()</code> 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. <br />
It is used by Nasal scripts embedded in XML files. It returns a <code>props.Node</code> object (see {{fgdata file|Nasal/props.nas}}), and you can use all of its methods on the returned value. <code>cmdarg()</code> should only be used in four types/places of XML files:<br />
* Bindings: This is needed so that the value of a joystick's axis can be accessed internally.<br />
* 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). <br />
* 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.<br />
* Animation XML files: If the animation XML file is used in an AI/MP model, <code>cmdarg()</code> will return the root of the AI model in the <code>/ai/models/</code> directory. Examples: <code>/ai/models/aircraft[3]/</code>, <code>/ai/models/multiplayer[1]/</code><br />
<br />
You should not use <code>cmdarg()</code> in places other than those stated above. Although it won't cause an error, it will return the value of the last legitimate <code>cmdarg()</code> call. <br />
<br />
Also, you should not delay <code>cmdarg()</code> using {{func link|maketimer()}}, {{func link|settimer()}} or {{func link|setlistener()}}, because it will return an unrelated property.<br />
|example1 = fgcommand("dialog-show", {"dialog-name": "cmdarg-demo"});<br />
|example1text = <br>This example demonstrates the usage of <code>cmdarg()</code> in a binding. Save the below XML snippet as <tt>[[$FG_ROOT]]/gui/dialogs/cmdarg-demo.xml</tt>. 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.<br />
<syntaxhighlight lang="xml"><br />
<?xml version="1.0" encoding="UTF-8"?><br />
<br />
<PropertyList><br />
<br />
<name>cmdarg-demo</name><br />
<layout>vbox</layout><br />
<br />
<text><br />
<label>Click "Close" to activate the demonstration (a message in the console).</label><br />
</text><br />
<br />
<button><br />
<legend>Close</legend><br />
<binding><br />
<command>nasal</command><br />
<script>print("Button binding root: '" ~ cmdarg().getPath() ~ "'");</script><br />
</binding><br />
<binding><br />
<command>dialog-close</command><br />
</binding><br />
</button><br />
<br />
</PropertyList><br />
</syntaxhighlight><br />
|example2text = This example demonstrates the usage of <code>cmdarg()</code> in Nasal code within dialogs. Open <tt>[[$FG_ROOT]]/gui/dialogs/cmdarg-demo.xml</tt> from the previous example, copy & 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 "I've been changed!"<br />
<syntaxhighlight lang="xml"><br />
<?xml version="1.0" encoding="UTF-8"?><br />
<br />
<PropertyList><br />
<br />
<name>cmdarg-demo</name><br />
<layout>vbox</layout><br />
<br />
<text><br />
<label>Click "Click me!" to activate the demonstration (the button's label will change).</label><br />
</text><br />
<br />
<button><br />
<legend>Click me!</legend><br />
<binding><br />
<command>nasal</command><br />
<script>change_label();</script><br />
</binding><br />
</button><br />
<br />
<button><br />
<legend>Close</legend><br />
<binding><br />
<command>dialog-close</command><br />
</binding><br />
</button><br />
<br />
<nasal><br />
<open><![CDATA[<br />
var dlg_root = cmdarg();<br />
var dlg_name = {"dialog-name": "cmdarg-demo"};<br />
var change_label = func {<br />
dlg_root.getNode("button[0]/legend").setValue("I've been changed!");<br />
fgcommand("dialog-close", dlg_name);<br />
fgcommand("dialog-show", dlg_name);<br />
}<br />
]]></open><br />
</nasal><br />
<br />
</PropertyList><br />
</syntaxhighlight><br />
|example3text = For an example of <code>cmdarg()</code> used with Canvas, please see [[Howto:Adding a canvas to a GUI dialog#FGPlot|Howto:Adding a canvas to a GUI dialog]].<br />
}}<br />
<br />
=== courseAndDistance() ===<br />
{{Nasal doc<br />
|syntax = courseAndDistance(to);<br />
courseAndDistance(from, to);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1668|t=Source}}<br />
|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:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost type<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A geo.Coord object<br />
|param1 = from<br />
|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.<br />
|param2 = to<br />
|param2text = Like the first parameter, but defines the second point.<br />
|example1text = This example demonstrates the usage of the function with the <code>airport</code> ghost type.<br />
|example1 = var from = airportinfo("KSFO");<br />
var to = airportinfo("KSQL");<br />
var (course, dist) = courseAndDistance(from, to);<br />
print(course); # prints course from KSFO to KSQL<br />
print(dist); # prints distance in nm from KSFO to KSQL<br />
|example2text = This example demonstrates the usage of the function with hashes containing ''lat'' and ''lon''.<br />
|example2 = var from = {lat: 0, lon: 0};<br />
var to = {lat: 1, lon: 1};<br />
var (course, dist) = courseAndDistance(from, to);<br />
print(course);<br />
print(dist);<br />
|example3text = This example demonstrates usage of a geo.Coord object.<br />
|example3 = var from = geo.Coord.new().set_latlon(0, 0);<br />
var to = geo.Coord.new().set_latlon(1, 1);<br />
var (course, dist) = courseAndDistance(from, to);<br />
print(course);<br />
print(dist);<br />
|example4text = This example demonstrates usage of differing parameter types.<br />
|example4 = var from = airportinfo("KSFO");<br />
var to = geo.Coord.new().set_latlon(0, 0);<br />
var (course, dist) = courseAndDistance(from, to);<br />
print(course);<br />
print(dist);<br />
|example5text = The same as above, but the other way round.<br />
|example5 = var to = {lat: 1, lon: 1};<br />
var (course, dist) = courseAndDistance(0, 0, to);<br />
print(course);<br />
print(dist);<br />
|example6text = Usage of just one parameter.<br />
|example6 = var dest = airportinfo("KSQL");<br />
var (course, dist) = courseAndDistance(dest);<br />
print("Turn to heading ", math.round(course), ". You have ", sprintf("%.2f", dist), " nm to go");<br />
}}<br />
<br />
=== createDiscontinuity() ===<br />
{{Nasal doc<br />
|syntax = createDiscontinuity();<br />
|text = Returns a <code>waypoint</code> ghost object. A route discontinuity is inserted by an {{abbr|FMS|Flight Management System}} when it is unsure how to connect two waypoints.<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2045|t=Source}}<br />
|version = 2016.1<br />
|commit = {{flightgear commit|caead6|t=commit}}<br />
}}<br />
=== createViaTo() ===<br />
{{Nasal doc<br />
|syntax = createViaTo(airway, waypoint);<br />
|text = Returns a <code>waypoint</code> ghost object. It represents a route "via '''airway''' to '''waypoint'''".<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2009|t=Source}}<br />
|version = 2016.1<br />
|commit = {{flightgear commit|caead6|t=commit}}<br />
|param1 = airway<br />
|param1text = The name of an airway.<br />
|param2 = waypoint<br />
|param2text = Must be in the airway and one of:<br />
* The name of a waypoint.<br />
* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, or <code>fix</code> ghost object.<br />
}}<br />
=== createWP() ===<br />
{{Nasal doc<br />
|syntax = createWP(pos, name[, flag]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1964|t=Source}}<br />
|text = Creates a new waypoint ghost object.<br />
|param1 = pos<br />
|param1text = Dictates the position of the new waypoint. It can be one of the following:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost type<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A geo.Coord object<br />
:* Two numbers separated by a comma, as if the function is taking three arguments. See example 4 below.<br />
|param2 = name<br />
|param2text = String that will become the name of the new waypoint.<br />
|param3 = flag<br />
|param3text = Optional string that will tell FlightGear what type of waypoint it is. Must be one of "sid," "star," "approach," "missed," or "pseudo."<br />
|example1text = Creates a waypoint directly in front and 1 km away and appends it to the flight plan.<br />
|example1 = var pos = geo.aircraft_position().apply_course_distance(getprop("/orientation/heading-deg"), 1000);<br />
var wp = createWP(pos, "NEWWP");<br />
var fp = flightplan();<br />
fp.appendWP(wp);<br />
|example2 = var pos = geo.aircraft_position().apply_course_distance(getprop("/orientation/heading-deg"), 1000);<br />
var wp = createWP({lat: pos.lat(), lon: pos.lon()}, "NEWWP");<br />
var fp = flightplan();<br />
fp.appendWP(wp);<br />
|example3 = var apt = airportinfo();<br />
var wp = createWP(apt, "NEWWP");<br />
var fp = flightplan();<br />
fp.appendWP(wp);<br />
|example4 = var pos = geo.aircraft_position().apply_course_distance(getprop("/orientation/heading-deg"), 1000);<br />
var wp = createWP(pos.lat(), pos.lon(), "NEWWP");<br />
var fp = flightplan();<br />
fp.appendWP(wp);<br />
|example5text = Creates a new waypoint and adds it to the flight plan. Waypoints of the type "pseudo" are then removed from the flight plan, including the new waypoint. The {{func link|print()}} statements show this.<br />
|example5 = var pos = geo.aircraft_position().apply_course_distance(getprop("/orientation/heading-deg"), 1000);<br />
var wp = createWP(pos, "NEWWP", "pseudo");<br />
var fp = flightplan();<br />
fp.appendWP(wp);<br />
print(fp.getPlanSize());<br />
fp.clearWPType("pseudo");<br />
print(fp.getPlanSize());<br />
}}<br />
<br />
=== createWPFrom() ===<br />
{{Nasal doc<br />
|syntax = createWPFrom(object[, flag]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1989|t=Source}}<br />
|text = Creates a new waypoint object from another object.<br />
|param1 = object<br />
|param1text = A ghost object. Must be a ghost type that is one of "airport," "navaid," "runway," or "fix."<br />
|param2 = flag<br />
|param2text = Optional string that will tell FlightGear what type of waypoint it is. Must be one of "sid," "star," "approach," "missed," or "pseudo."<br />
|example1text = Creates a new waypoint and appends it to the flight plan.<br />
|example1 = var apt = airportinfo("KSFO");<br />
var wp = createWPFrom(apt);<br />
var fp = flightplan();<br />
fp.appendWP(wp);<br />
|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.<br />
|example2 = var apt = airportinfo("KSFO");<br />
var wp = createWPFrom(apt, "pseudo");<br />
print(wp.wp_name);<br />
var fp = flightplan();<br />
fp.appendWP(wp);<br />
print(fp.getPlanSize());<br />
fp.clearWPType("pseudo");<br />
print(fp.getPlanSize());<br />
}}<br />
<br />
=== defined() ===<br />
{{Nasal doc<br />
|syntax = defined(symbol);<br />
|source = {{fgdata file|Nasal/globals.nas|t=Source}}<br />
|text = Returns 1 (true) or 0 (false) depending on whether a variable exists.<br />
|param1 = symbol<br />
|param1text = A string that will be what the function searches for.<br />
|example1 = var number = 12;<br />
var check_exist = func {<br />
print("Variable 'number' ", defined("number") == 1 ? "exists" : "does not exist"); # 'number' does exist<br />
print("Variable 'number2' ", defined("number2") == 1 ? "exists" : "does not exist"); # 'number2' does not exist<br />
}<br />
check_exist();<br />
}}<br />
<br />
=== directory() ===<br />
{{Nasal doc<br />
|syntax = directory(path);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=572|t=Source}}<br />
|text = Returns a vector containing a list of the folders and files in a given file path or <code>'''nil'''</code> if the path doesn't exist. Hidden folders and files are not added to the vector.<br />
{{inote|The first two elements of the vector will be <code>'.'</code> and <code>'..'</code>. These are for navigating back up the file tree, but have no use in Nasal. They can be safely removed from the vector.}}<br />
|param1 = path<br />
|param1text = Absolute file path.<br />
|example1text = Gets the folders and files in [[$FG_ROOT]], and then removes the extra first two elements (see note above). <br />
|example1 = var dir = directory(getprop("/sim/fg-root")); # get directory<br />
dir = subvec(dir, 2); # strips off the first two elements<br />
debug.dump(dir); # dump the vector<br />
}}<br />
<br />
=== fgcommand() ===<br />
{{Nasal doc<br />
|syntax = fgcommand(cmd[, args]);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=456|t=Part 1}} {{!}} {{fgdata file|Nasal/globals.nas|t=Part 2}}<br />
|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.<br />
|param1 = cmd<br />
|param1text = String that is the name of the command that is to be run.<br />
|param2 = args<br />
|param2text = If the fgcommand takes arguments, they are inputted using this argument. Can either be a <code>props.Node</code> object, or a hash (see examples below).<br />
|example1 = fgcommand("null"); # does nothing<br />
|example2 = var args = props.Node.new({'script': 'print("Running fgcommand");'});<br />
if (fgcommand("nasal", args)) { # prints "Running fgcommand" and then one of these print statements<br />
print("Fgcommand succeeded");<br />
} else {<br />
print("Fgcommand encountered a problem");<br />
}<br />
|example3 = var args = { 'dialog-name': 'about' };<br />
fgcommand("dialog-show", args); # shows the 'about' dialog<br />
}}<br />
<br />
=== findAirportsByICAO() ===<br />
{{Nasal doc<br />
|syntax = findAirportsByICAO(search[, type]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1096|t=Source}}<br />
|text = Returns a vector containing <code>airport</code> ghost objects which are (by default) airports whose ICAO code matches the search string. The results are sorted by range from closest to furthest.<br />
|param1 = search<br />
|param1text = Search string for the function. Can either be a partial or a full ICAO code.<br />
:{{icaution|The more matches there are for the given code, the longer the function will take. Passing just one character (e.g., "K"), might make FlightGear hang for a certain amount of time.}}<br />
|param2 = type<br />
|param2text = This will narrow the search to airports of a certain type. By default, only airports are searched for. May be one of "airport," "heliport," or "seaport."<br />
|example1 = var apts = findAirportsByICAO("KSF"); # finds all airports matching "KSF"<br />
foreach(var apt; apts){<br />
print(apt.name, " (", apt.id, ")"); # prints them<br />
}<br />
|example2 = var apts = findAirportsByICAO("SP0", "seaport"); # finds all seaplane bases matching "SP0"<br />
foreach(var apt; apts){<br />
print(apt.name, " (", apt.id, ")"); # prints them<br />
}<br />
|example3 = var apt = findAirportsByICAO("XBET"); # one way to check if an airport does exist"<br />
if (size(apt) == 0) {<br />
print("Airport does not exist"); # this one will be printed<br />
} else {<br />
print("Airport does exist");<br />
}<br />
}}<br />
<br />
=== findAirportsWithinRange() ===<br />
{{Nasal doc<br />
|syntax = findAirportsWithinRange([pos, ]range[, type]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1066|t=Source}}<br />
|text = Returns a vector of <code>airport</code> 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.<br />
|param1 = pos<br />
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost type<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A geo.Coord object<br />
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: <code>findAirportsWithinRange(lat, lon, range, type);</code>.<br />
|param2 = range<br />
|param2text = Mandatory number giving the range in nautical miles within which to search for airports/heliports/seaplane bases.only airports are searched for.<br />
|param3 = type<br />
|param3text = This will narrow the search to airports of a certain type. By default, only airports are searched for. May be one of "airport," "heliport," or "seaport."<br />
|example1text = Searches for airports within 10 nm of [[KSFO]].<br />
|example1 = var pos = airportinfo("KSFO");<br />
var apts = findAirportsWithinRange(pos, 10);<br />
foreach(var apt; apts){<br />
print(apt.name, " (", apt.id, ")");<br />
}<br />
|example2text = Searches for seaplane bases within 15 nm of [[KSFO]].<br />
|example2 = var pos = airportinfo("KSFO");<br />
var apts = findAirportsWithinRange(pos, 15, "seaport");<br />
foreach(var apt; apts){<br />
print(apt.name, " (", apt.id, ")");<br />
}<br />
|example3text = Searches for airports within 10 nm of your current position.<br />
|example3 = var apts = findAirportsWithinRange(10);<br />
foreach(var apt; apts){<br />
print(apt.name, " (", apt.id, ")");<br />
}<br />
}}<br />
<br />
=== findFixesByID() ===<br />
{{Nasal doc<br />
|syntax = findFixesByID([pos, ]id);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1627|t=Source}}<br />
|text = Returns a vector containing <code>fix</code> ghost objects matching a given ID, sorted by range from a certain position.<br />
{{inote|Fixes are (usually) also known as waypoints.}}<br />
|param1 = pos<br />
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost type<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A geo.Coord object<br />
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: <code>findFixesByID(lat, lon, id);</code>.<br />
|param2 = id<br />
|param2text = Full or partial ID of the fix to search for.<br />
:{{inote|1=Inputting a partial ID does not work correctly (see [http://forum.flightgear.org/viewtopic.php?f=30&t=28129 here]). It is best to just input a full ID.}}<br />
|example1 = var fixes = findFixesByID("POGIC");<br />
foreach(var fix; fixes){<br />
print(fix.id, " - lat: ", fix.lat, " {{!}} lon: ", fix.lon); # prints information about POGIC<br />
}<br />
|example2 = var fix = findFixesByID("ZUNAP");<br />
fix = fix[0];<br />
var (course, dist) = courseAndDistance(fix);<br />
print("Turn to heading ", math.round(course), ". You have ", sprintf("%.2f", dist), " nm to go to reach ", fixes[0].id);<br />
}}<br />
<br />
=== findNavaidByFrequency() ===<br />
{{Nasal doc<br />
|syntax = findNavaidByFrequency([pos, ]freq[, type]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1547|t=Source}}<br />
|text = Returns a <code>navaid</code> ghost object for a navaid matching a given frequency. If there is more than one navaid with that frequency, the nearest station is returned.<br />
|param1 = pos<br />
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost type<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A geo.Coord object<br />
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: <code>findNavaidByFrequency(lat, lon, freq, type);</code>.<br />
|param2 = freq<br />
|param2text = Frequency, in megahertz, of the navaid to search for.<br />
|param3 = type<br />
|param3text = This will narrow the search to navaids of a certain type. Defaults to "all." For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.<br />
|example1 = var navaid = findNavaidByFrequency(11.17);<br />
print("ID: ", navaid.id); # prints info about the navaid<br />
print("Name: ", navaid.name);<br />
print("Latitude: ", navaid.lat);<br />
print("Longitude: ", navaid.lon);<br />
print("Elevation (AMSL): ", navaid.elevation, " m");<br />
print("Type: ", navaid.type);<br />
print("Frequency: ", sprintf("%.3f", navaid.frequency / 1000), " Mhz");<br />
print("Range: ", navaid.range_nm, " nm");<br />
if(navaid.course) print("Course: ", navaid.course);<br />
}}<br />
<br />
=== findNavaidsByFrequency() ===<br />
{{Nasal doc<br />
|syntax = findNavaidsByFrequency([pos, ]freq[, type]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1572|t=Source}}<br />
|text = Returns a vector conatining <code>navaid</code> ghost objects for navaids that match a given frequency, sorted from nearest to furthest.<br />
|param1 = pos<br />
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost type<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A geo.Coord object<br />
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: <code>findNavaidsByFrequency(lat, lon, freq, type);</code>.<br />
|param2 = freq<br />
|param2text = Frequency, in megahertz, of the navaid to search for.<br />
|param3 = type<br />
|param3text = This will narrow the search to navaids of a certain type. Defaults to "all." For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.<br />
|example1 = var navaids = findNavaidsByFrequency(10.955);<br />
foreach(var navaid; navaids){<br />
print("--");<br />
print("ID: ", navaid.id); # prints info about the navaid<br />
print("Name: ", navaid.name);<br />
print("Latitude: ", navaid.lat);<br />
print("Longitude: ", navaid.lon);<br />
print("Elevation (AMSL): ", navaid.elevation, " m");<br />
print("Type: ", navaid.type);<br />
print("Frequency: ", sprintf("%.3f", navaid.frequency / 1000), " Mhz");<br />
print("Range: ", navaid.range_nm, " nm");<br />
if(navaid.course) print("Course: ", navaid.course);<br />
print("--");<br />
}<br />
}}<br />
<br />
=== findNavaidsByID() ===<br />
{{Nasal doc<br />
|syntax = findNavaidsByID([pos, ]id[, type]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1600|t=Source}}<br />
|text = Returns a vector containing <code>navaid</code> ghost objects matching a given ID, sorted by range from a certain position.<br />
|param1 = pos<br />
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost type<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A geo.Coord object<br />
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: <code>findNavaidsByID(lat, lon, id, type);</code>.<br />
|param2 = id<br />
|param2text = Full or partial ID of the fix to search for.<br />
:{{inote|1=Inputting a partial ID does not work correctly (see [http://forum.flightgear.org/viewtopic.php?f=30&t=28129 here]). It is best to just input a full ID.}}<br />
|param3 = type<br />
|param3text = This will narrow the search to navaids of a certain type. Defaults to "all." For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.<br />
|example1 = var navaid = findNavaidsByID("MXW");<br />
navaid = navaid[0];<br />
print("ID: ", navaid.id); # prints info about 'MXW' (a VOR station)<br />
print("Name: ", navaid.name);<br />
print("Latitude: ", navaid.lat);<br />
print("Longitude: ", navaid.lon);<br />
print("Elevation (AMSL): ", navaid.elevation, " m");<br />
print("Type: ", navaid.type);<br />
print("Frequency: ", sprintf("%.3f", navaid.frequency / 1000), " Mhz");<br />
print("Range: ", navaid.range_nm, " nm");<br />
}}<br />
<br />
=== findNavaidsWithinRange() ===<br />
{{Nasal doc<br />
|syntax = findNavaidsWithinRange([pos, ]range[, type]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1518|t=Source}}<br />
|text = Returns a vector of <code>navaid</code> 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.<br />
|param1 = pos<br />
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost type<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A geo.Coord object<br />
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: <code>findNavaidsWithinRange(lat, lon, range, type);</code>.<br />
|param2 = range<br />
|param2text = Mandatory number giving the range in nautical miles within which to search for navaids. Defaults to "all." For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.<br />
|param3 = type<br />
|param3text = This will narrow the search to navaids of a certain type.<br />
|example1text = Searches for navaids within 10 nm of [[KSFO]].<br />
|example1 = var pos = airportinfo("KSFO");<br />
var navs = findNavaidsWithinRange(pos, 10);<br />
foreach(var nav; navs){<br />
print(nav.name, " (ID: ", nav.id, ")");<br />
}<br />
|example2text = Searches for navaids within 10 nm of your current position.<br />
|example2 = var navs = findNavaidsWithinRange(10);<br />
foreach(var nav; navs){<br />
print(nav.name, " (ID: ", nav.id, ")");<br />
}<br />
}}<br />
<br />
=== finddata() ===<br />
{{Nasal doc<br />
|syntax = finddata(path);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=603|t=Source}}<br />
|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]]. <br />
|param1 = path<br />
|param1text = A relative path as a string.<br />
|example1 = var path = finddata("Aircraft/Generic");<br />
print(path); # prints the absolute path to $FG_ROOT/Aircraft/Generic<br />
|example2 = var path = finddata("Airports");<br />
print(path); # prints the absolute path to <TerraSync dir>/Airports<br />
|example3 = var path = finddata("preferences.xml");<br />
print(path); # prints the absolute path to $FG_ROOT/preferences.xml<br />
}}<br />
<br />
=== flightplan() ===<br />
{{Nasal doc<br />
|syntax = flightplan([path]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1738|t=Source}}<br />
|text = {{see also|Nasal Flightplan}}<br />
Returns a flight plan object, either one for the current flight plan, or one loaded from a given path.<br />
|param1 = path<br />
|param1text = Optional path to flight plan XML file.<br />
|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.<br />
|example1 = var fp = flightplan();<br />
print(fp.getWP(fp.current).id);<br />
|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 <tt>''[[$FG_HOME]]/fp-demo.xml''</tt>.<br />
|example2 = var path = getprop('/sim/fg-home') ~ '/fp-demo.xml';<br />
var fp = flightplan(path);<br />
for(var i = 0; i < fp.getPlanSize(); i += 1){<br />
print(fp.getWP(i).id);<br />
}<br />
}}<br />
<br />
=== geodinfo() ===<br />
{{Nasal doc<br />
|syntax = geodinfo(lat, lon[, max_alt]);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=981|t=Source}}<br />
|text = Returns a vector containing two entries or <code>'''nil'''</code> 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 <tt>''[[$FG_ROOT]]/Materials''</tt>), or <code>'''nil'''</code> 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}}):<br />
* '''light_coverage:''' The coverage of a single point of light in m<sup>2</sup>.<br />
* '''bumpiness:''' Normalized bumpiness factor for the material.<br />
* '''load_resistance:''' The amount of pressure in N/m<sup>2</sup> the material can withstand without deformation.<br />
* '''solid:''' 1 (true) or false (0) depending on whether the material is solid or not.<br />
* '''names:''' Vector of scenery types (usually generated by [[TerraGear]]) that will use this material. <br />
* '''friction_factor:''' Normalized friction factor of the material.<br />
* '''rolling_friction:''' The rolling friction coefficient of the material.<br />
<br />
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.<br />
|param1 = lat<br />
|param1text = Latitude, inputted as a number.<br />
|param2 = lon<br />
|param2text = Longitude, inputted as a number.<br />
|param3 = max_alt<br />
|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, <code>'''nil'''</code> will be returned.<br />
|example1text = Dumps information about ground underneath the aircraft.<br />
|example1 = var pos = geo.aircraft_position();<br />
var info = geodinfo(pos.lat(), pos.lon());<br />
debug.dump(info);<br />
|example2text = Prints whether the ground underneath the aircraft is solid or is water.<br />
|example2 = var pos = geo.aircraft_position();<br />
var info = geodinfo(pos.lat(), pos.lon());<br />
if (info != nil and info[1] != nil) {<br />
print("The ground underneath the aircraft is ", info[1].solid == 1 ? "solid." : "water.");<br />
}<br />
}}<br />
<br />
=== geodtocart() ===<br />
{{Nasal doc<br />
|syntax = geodtocart(lat, lon, alt);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=962|t=Source}}<br />
|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.<br />
|param1 = lat<br />
|param1text = Latitude, in degrees.<br />
|param2 = lon<br />
|param2text = Longitude, in degrees.<br />
|param3 = alt<br />
|param3text = Altitude, in metres.<br />
|example1 = var (x, y, z) = geodtocart(0, 0, 0); # point is the intersection of the prime meridian and equator.<br />
print("x: ", x); # prints "x: 6378137"<br />
print("y: ", y); # prints "y: 0"<br />
print("z: ", z); # prints "y: 0"<br />
}}<br />
<br />
=== get_cart_ground_intersection() ===<br />
Introduced in 2017.2.1, see [[Terrain Detection]].<br />
<br />
=== getprop() ===<br />
{{Nasal doc<br />
|syntax = getprop(path[, path[, ...]]);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=345|t=Source}}<br />
|text = Returns the value of a node in the [[Property Tree]] or <code>'''nil'''</code> if the node does not exist or the value is not a number (NaN).<br />
|param1 = path<br />
|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.<br />
|example1 = print("You have FlightGear v", getprop("/sim/version/flightgear")); # prints FlightGear version<br />
|example2text = Note that the example below will only work in FlightGear 3.2 and above.<br />
|example2 = for(var i = 0; i < 8; i += 1){<br />
print("View #", i + 1, " is named ", getprop("/sim/view", i, "name"));<br />
}<br />
|example3text = Same as above, but is supported by all versions of FlightGear.<br />
|example3 = for(var i = 0; i < 8; i += 1){<br />
print("View #", i + 1, " is named ", getprop("/sim/view[" ~ i ~ "]/name"));<br />
}<br />
}}<br />
==== See also ====<br />
{{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: <br />
To get a Node rather than its value, use <code>props.globals.getNode()</code> - see [[Nasal_library/props]]. }}<br />
<br />
=== greatCircleMove() ===<br />
{{Nasal doc<br />
|syntax = greatCircleMove([pos, ]course, dist);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1681|t=Source}}<br />
|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).<br />
|param1 = pos<br />
|param1text = Optional position to calculate from. If not given, the aircraft's current position will be used. Can be one of:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost object.<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A <code>geo.Coord</code> object<br />
:* A lat/lon pair, that is, a pair of numbers (latitude followed by longitude) separated by a comma: <code>greatCircleMove(lat,lon, ...)</code>.<br />
|param2 = course<br />
|param2text = Course to new set of coordinates, in degrees (in the range 0–360).<br />
|param3 = dist<br />
|param3text = Distance in nautical miles to the new set of coordinates.<br />
|example1 = var pos = greatCircleMove(0,0, 0, 1);<br />
debug.dump(pos); # print hash with coordinates<br />
|example2 = var fix = findFixesByID("POGIC");<br />
fix = fix[0];<br />
var pos = greatCircleMove(fix, 45, 10);<br />
debug.dump(pos); # print hash with coordinates<br />
}}<br />
<br />
=== interpolate() ===<br />
{{Nasal doc<br />
|private = _interpolate()<br />
|syntax = interpolate(prop, value1, time1[, value2, time2[, ...]]);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=522|t=Part 1}} {{!}} {{fgdata file|Nasal/globals.nas|t=Part 2}}<br />
|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.<br />
|param1 = prop<br />
|param1text = String or <code>props.Node</code> object that indicates a node in the property tree to be used.<br />
|param2 = value''n''<br />
|param2text = Target value to change the property to in the set amount of time. This should be a number.<br />
|param3 = time''n''<br />
|param3text = Time in seconds, that will be taken for the interpolation.<br />
|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.<br />
|example1 = setprop("/test", 0); # (re-)set property<br />
interpolate("/test",<br />
50, 5, # interpolate to 50 in 5 seconds<br />
10, 2, # interpolate to 10 in 2 seconds<br />
0, 5); # interpolate to 0 in 5 seconds<br />
|example2 = # Apply the left brake at 20% per second<br />
var prop = "controls/gear/brake-left";<br />
var dist = 1 - getprop(prop);<br />
if (dist == 1) {<br />
interpolate(prop, 1, dist / 0.2);<br />
}<br />
}}<br />
<br />
=== isa() ===<br />
{{Nasal doc<br />
|syntax = isa(object, class);<br />
|source = {{fgdata file|Nasal/globals.nas|t=Source}}<br />
|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.<br />
|param1 = object<br />
|param1text = Object to check.<br />
|param2 = class<br />
|param2text = Class/object to check that '''object''' inherits from or is an instance of.<br />
|example1 = var coord = geo.Coord.new();<br />
if(isa(coord, geo.Coord)){<br />
print("Variable 'coord' is an instance of class 'geo.Coord'"); # this one will be printed<br />
} else {<br />
print("Variable 'coord' is not an instance of class 'geo.Coord'");<br />
}<br />
|example2 = var coord = geo.Coord.new();<br />
if(isa(coord, props.Node)){<br />
print("Variable 'coord' is an instance of class 'props.Node'");<br />
} else {<br />
print("Variable 'coord' is not an instance of class 'props.Node'"); # this one will be printed<br />
}<br />
|example3text = The example below demonstrates checking of inheritance.<br />
|example3 = var Const = {<br />
constant: 2,<br />
getConst: func {<br />
return me.constant;<br />
}<br />
};<br />
<br />
var Add = {<br />
new: func {<br />
return { parents: [Add, Const] };<br />
},<br />
<br />
addToConst: func(a){<br />
return a * me.getConst();<br />
}<br />
};<br />
<br />
var m = Add.new();<br />
print(m.addToConst(4));<br />
<br />
if(isa(m, Add)) print("Variable 'm' is an instance of class 'Add'"); # will be printed<br />
if(isa(m, Const)) print("Variable 'm' is an instance of class 'Const'"); # will also be printed<br />
}}<br />
<br />
=== logprint() ===<br />
{{Nasal doc<br />
|syntax = logprint(priority[, msg[, msg[, ...]]]);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=431|t=Source}}<br />
|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 <code>[[Commonly used debugging tools#fgfs.log|fgfs.log]]</code> file as coming from the Nasal file itself rather than from {{flightgear file|src/Scripting/NasalSys.cxx}}.<br />
|param1 = priority<br />
|param1text = Number specifying the priority level of the outputted message:<br />
:{{{!}} class="wikitable"<br />
! Number !! Debug type<br />
{{!-}}<br />
{{!}} 1 {{!!}} Bulk<br />
{{!-}}<br />
{{!}} 2 {{!!}} Debug<br />
{{!-}}<br />
{{!}} 3 {{!!}} Info<br />
{{!-}}<br />
{{!}} 4 {{!!}} Warn<br />
{{!-}}<br />
{{!}} 5 {{!!}} Alert<br />
{{!}}}<br />
|param2 = msg<br />
|param2text = The message. There is no limit to the arguments you give give. They will be concatenated together before logging.<br />
|example1 = # logs the value of pi to three decimal places with log level 3<br />
logprint(3, "pi = ", sprintf("%.3f", math.pi));<br />
|example2 = logprint(5, "Alert! This is an important message!");<br />
}}<br />
{{note| <br />
The following constants have been added to the development branch of FlightGear ("next") and will be releases with FG 2020.1 so you won't have to remember the numbers anymore:<br />
<br />
LOG_BULK, LOG_WARN, LOG_DEBUG, LOG_INFO, LOG_ALERT, DEV_WARN, DEV_ALERT }}<br />
<br />
=== magvar() ===<br />
{{Nasal doc<br />
|syntax = magvar([pos]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1642|t=Source}}<br />
|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.<br />
{{{!}} class="wikitable"<br />
! FlightGear versions !! Model !! Reference date<br />
{{!-}}<br />
{{!}} 3.6 and above {{!!}} {{wikipedia|World Magnetic Model}} (WMM) 2015 {{!!}} 1 January 2015<br />
{{!-}}<br />
{{!}} 0.9.11-pre1 to 3.4 {{!!}} WMM 2005 {{!!}} 1 January 2005<br />
{{!-}}<br />
{{!}} 0.7.3 to 0.9.10 {{!!}} WMM 2000 {{!!}} 1 January 2000<br />
{{!}}}<br />
|param1 = pos<br />
|param1text = Optional position to calculate from. If not given, the aircraft's current position will be used. Can be one of:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost object.<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A <code>geo.Coord</code> object<br />
:* A lat/lon pair, that is, a pair of numbers (latitude followed by longitude) separated by a comma: <code>magvar(lat,lon)</code>.<br />
|example1 = print(magvar(0, 0)); # prints the magnetic variation at 0, 0<br />
}}<br />
<br />
=== maketimer() ===<br />
{{Nasal doc<br />
|syntax = maketimer(interval[, self], function);<br />
|source = ''Implemented using the {{API Link|flightgear|class|TimerObj}} class.''<br>{{flightgear file|src/Scripting/NasalSys.cxx|l=90|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=533|t=Part 2}}<br />
|version = 2.12<br />
|commit = {{flightgear commit|ab939f|t=commit}}<br />
|text = Returns a timer object containing the following methods and members:<br />
* '''start()''': Starts the timer.<br />
* '''stop()''': Stops the timer.<br />
* '''restart(interval)''': Restarts the timer with the given interval.<br />
* '''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).<br />
* '''isRunning''': Read-only bool telling whether the timer is currently running.<br />
* '''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.<br />
Unlike {{func link|settimer()}}, which it replaces, <code>maketimer()</code> provides more control over the timer. In addition, it can help reduce memory usage.<br />
|param1 = interval<br />
|param1text = Interval in seconds for the timer.<br />
|param2 = self<br />
|param2text = Optional parameter specifying what any <code>'''me'''</code> references in the function being called will refer to.<br />
|param3 = function<br />
|param3text = Function to be called at the given interval.<br />
|example1 = var timer = maketimer(1, func(){<br />
print("Hello, World!"); # print "Hello, World!" once every second (call timer.stop() to stop it)<br />
});<br />
timer.start();<br />
|example2 = var timer = maketimer(1, math, func(){<br />
print(me.math); # 'me' reference is the 'math' namespace<br />
});<br />
timer.singleShot = 1; # timer will only be run once<br />
timer.start();<br />
|example3 = var timer = maketimer(1, func(){<br />
print("Hello, World!"); # print "Hello, World!" once every second (call timer.stop() to stop it)<br />
});<br />
timer.start();<br />
print(timer.isRunning); # prints 1<br />
|example4text = In the example below, "Hello, World!" will be printed after one second the first time, and after two seconds thereafter.<br />
|example4 = var update = func(){<br />
print("Hello, World!");<br />
timer.restart(2); # restarts the timer with a two second interval<br />
}<br />
<br />
var timer = maketimer(1, update);<br />
timer.singleShot = 1;<br />
timer.start();<br />
}}<br />
<br />
=== maketimestamp() ===<br />
{{Nasal doc<br />
|syntax = maketimestamp()<br />
|source = ''Implemented using the {{API Link|flightgear|class|TimeStampObj}} class.''<br>{{flightgear file|src/Scripting/NasalSys.cxx|l=214|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=589|t=Part 2}}<br />
|version = 2019.2<br />
|commit = {{flightgear commit|7db06300|t=commit}}<br />
|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:<br />
* '''stamp()''': Resets the timing operation. Call this first.<br />
* '''elapsedMSec()''': returns number of milliseconds elapsed since stamp() called. Resolution may vary depending on platform but is usually at least millisecond accuracy.<br />
* '''elapsedUSec()''': returns number of microseconds elapsed since stamp() called. Resolution may vary depending on platform but is usually at least millisecond accuracy.<br />
|<br />
|example1text = In the example below the number of milliseconds elapsed will be printed.<br />
|example1 = var timestamp = maketimestamp();<br />
timestamp.stamp();<br />
print(timestamp.elapsedMSec(), "ms elapsed");<br />
print(timestamp.elapsedMSec(), "ms elapsed");<br />
}}<br />
<br />
=== md5() ===<br />
{{Nasal doc<br />
|syntax = md5(string);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=758|t=Source}}<br />
|version = 3.2<br />
|commit = {{flightgear commit|cfbf9e|t=commit}}<br />
|text = Returns a the {{wikipedia|MD5}} hash (as a string) of the inputted string.<br />
|param1 = string<br />
|param1text = String the generate the hash of. Mandatory.<br />
|example1text = The below code should output <code>65a8e27d8879283831b664bd8b7f0ad4</code>.<br />
|example1 = print(md5("Hello, World!"));<br />
}}<br />
<br />
=== navinfo() ===<br />
{{Nasal doc<br />
|syntax = navinfo(lat, lon, type, id);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1453|t=Source}}<br />
|text = Returns vector <code>navaid</code> ghost objects matching the given '''type''' and '''id''' or <code>'''nil'''</code> on error.<br />
|param1 = lat ''and'' lon<br />
|param1text = If given, the returned navaids will be put into order of ascending distance from the location.<br />
|param2 = type<br />
|param2text = Narrows the search to the given type. Must be one of "any," "fix," "vor," "ndb," "ils," "dme," or "tacan." Defaults to the equivalent of "any."<br />
|param3 = id<br />
|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.<br />
|example1 = navinfo("vor"); # returns all VORs<br />
|example2 = navinfo("HAM"); # return all navaids whose names start with "HAM"<br />
|example3 = navinfo("vor", "HAM"); # return all VORs whose names start with "HAM"<br />
|example4 = navinfo(34,48,"vor","HAM"); # return all VORs whose names start with "HAM" and sorted by distance relative to 34°, 48°<br />
}}<br />
<br />
=== parse_markdown() ===<br />
{{Nasal doc<br />
|syntax = parse_markdown(markdown);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=745|t=Part 1}} {{!}} {{simgear file|simgear/misc/SimpleMarkdown.cxx|t=Part 2}} <br />
|version = 3.2<br />
|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:<br />
* It strips whitespace from the beginning of the string.<br />
* It supports [http://daringfireball.net/projects/markdown/syntax#p paragraphs and line breaks].<br />
* It collapses whitespace.<br />
* It converts unordered [http://daringfireball.net/projects/markdown/syntax#list lists] to use a bullet character (&bull;). Note that the bullet character is implemented in hexadecimal UTF-8 character bytes (<code>E2 80 A2</code>), as so may not work properly when the being displayed in an encoding other than UTF-8.<br />
|param1 = markdown<br />
|param1text = String containing Markdown to be parsed.<br />
|example1text = <br />
Save the below code as <tt>''[[$FG_ROOT]]/gui/dialogs/test.xml''</tt>, 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 (<tt>Debug > Reload GUI</tt>).<br />
<syntaxhighlight lang="xml" highlight="41-50"><br />
<?xml version="1.0" encoding="UTF-8"?><br />
<br />
<PropertyList><br />
<br />
<name>test</name><br />
<layout>vbox</layout><br />
<br />
<group><br />
<layout>hbox</layout><br />
<br />
<empty><br />
<stretch>true</stretch><br />
</empty><br />
<br />
<text><br />
<label>parse_markdown() test dialog</label><br />
</text><br />
<br />
<empty><br />
<stretch>true</stretch><br />
</empty><br />
<br />
<button><br />
<legend></legend><br />
<pref-width>16</pref-width><br />
<pref-height>16</pref-height><br />
<binding><br />
<command>dialog-close</command><br />
</binding><br />
</button><br />
<br />
</group><br />
<br />
<canvas><br />
<name>Canvas plot</name><br />
<stretch>true</stretch><br />
<pref-width>400</pref-width><br />
<pref-height>300</pref-height><br />
<nasal><br />
<load><![CDATA[<br />
var text = 'Items:<br />
* apples<br />
* oranges<br />
* pears<br />
<br />
Some text.<br />
Some more items:<br />
* apples<br />
* oranges<br />
* pears';<br />
<br />
var parsed = parse_markdown(text);<br />
<br />
var root_canvas = canvas.get(cmdarg());<br />
root_canvas.setColorBackground(255, 255, 255);<br />
var root = root_canvas.createGroup();<br />
<br />
var text_dis = root.createChild("text")<br />
.setText(parsed)<br />
.setTranslation(5, 5)<br />
.setFont("LiberationFonts\LiberationSans-Regular.ttf")<br />
.setFontSize(15)<br />
.setColor(0, 0, 0)<br />
.setDrawMode(canvas.Text.TEXT)<br />
.setAlignment("left-top");<br />
]]></load><br />
</nasal><br />
</canvas><br />
<br />
</PropertyList><br />
</syntaxhighlight><br />
|example1 = fgcommand("dialog-show", {"dialog-name": "test"});<br />
|example2text = The example below parses Markdown and outputs it in a HTML document. The parsed text is placed in <syntaxhighlight lang="xml" inline><pre></pre></syntaxhighlight> tags. To change the Markdown to be parsed, simply edit the variable <tt>markdown</tt> at the top of the code.<br />
|example2 = <nowiki>var markdown = 'Items:<br />
* apples<br />
* oranges<br />
* pears<br />
<br />
Some text.<br />
Some more items:<br />
* apples<br />
* oranges<br />
* pears';<br />
<br />
var parsed = parse_markdown(markdown);<br />
<br />
debug.dump(parsed);<br />
<br />
var path = string.normpath(getprop("/sim/fg-home") ~ '/Export/parse_markdown()-test.html');<br />
<br />
var file = io.open(path, "w");<br />
<br />
var html = "<!DOCTYPE html>\n\n<html>\n\n<head>\n\t<meta charset=\"UTF-8\">\n\t<title>parse_markdown() test generated by Nasal</title>\n</head>\n\n<body>\n\t<pre>" ~ parsed ~ "</pre>\n</body>\n\n</html>";<br />
<br />
io.write(file, html);<br />
io.close(file);<br />
print("Done, file ready for viewing (" ~ path ~ ")");</nowiki><br />
}}<br />
<br />
=== parsexml() ===<br />
{{Nasal doc<br />
|syntax = parsexml(path[, start[, end[, data[, pro_ins]]]]);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=710|t=Source}}<br />
|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 <code>'''nil'''</code> is returned on error.<br />
|param1 = path<br />
|param1text = Mandatory absolute path to the XML file to be parsed.<br />
|param2 = start<br />
|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.<br />
|param3 = end<br />
|param3text = Optional callback function that will be called for every ending tag. The function should take one argument: the tag name.<br />
|param4 = data<br />
|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.<br />
|param5 = pro_ins<br />
|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.<br />
|example1text = Save the below XML code in <tt>''[[$FG_HOME]]/Export/demo.xml''</tt>. Then, execute the Nasal code below in the Nasal Console. The XML will be parsed and each bit of the code will be printed.<br />
<syntaxhighlight lang="xml"><br />
<?xml version="1.0" encoding="UTF-8"?><br />
<br />
<?xml-stylesheet type="text/xsl" href="style.xsl"?><br />
<br />
<foo><br />
<blah type="string"><![CDATA[ <sender>John Smith</sender> ]]></blah><br />
<blah2 type="string">Orange &amp; lemons</blah2><br />
</foo><br />
</syntaxhighlight><br />
|example1 = var start = func(name, attr){<br />
print("Starting tag: '", name, "'");<br />
foreach(var a; keys(attr)){<br />
print("\twith attribute ", a, '="', attr[a], '"');<br />
}<br />
}<br />
<br />
var end = func(name){<br />
print("Ending tag: '", name, "'");<br />
}<br />
<br />
var data = func(data){<br />
print("Data = '", data, "'");<br />
}<br />
<br />
var pro_instr = func(target, data){<br />
print("Processing instruction: target = '", target, "', data = '", data, "'");<br />
}<br />
<br />
parsexml(getprop("/sim/fg-home") ~ '/Export/demo.xml', start, end, data, pro_instr);<br />
}}<br />
<br />
=== print() ===<br />
{{Nasal doc<br />
|syntax = print(data[, data[, ...]]);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=415|t=Source}}<br />
|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.<br />
|param1 = data<br />
|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.<br />
|example1 = print("Just", " a ", "test"); # prints "Just a test"<br />
|example2 = print("pi = ", math.pi); # prints "pi = 3.141592..."<br />
}}<br />
<br />
=== printf() ===<br />
{{Nasal doc<br />
|syntax = printf(format[, arg[, arg, [...]]]);<br />
|source = {{fgdata file|Nasal/globals.nas|t=Source}}<br />
|text = Creates and prints a formatted string. For a description of its arguments, see {{func link|sprintf()}} (it is, in fact, implemented using <code>sprintf()</code>).<br />
|example1 = printf("In hexadecimal, 100000 = %X", 186A0); # prints "In hexadecimal, 100000 = 186A0"<br />
}}<br />
<br />
=== printlog() ===<br />
{{Nasal doc<br />
|syntax = printlog(level, data[, data[, ...]]);<br />
|source = {{fgdata file|Nasal/globals.nas|t=Source}}<br />
|text = Prints the given message with the given log level. If the log level is higher or equal to <code>/sim/logging/priority</code>, it is printed.<br />
|param1 = level<br />
|param1text = Mandatory log level as a string. Must be one of "none," "bulk," "debug," "info," "warn," or "alert." Note that "none" will mean that the message will never be printed.<br />
|param2 = data<br />
|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.<br />
|example1 = printlog("alert", "This is an alert"); # message will be printed<br />
|example2 = printlog("info", "Just informing you about something"); # message will be printed only if log level is set to "info" or less<br />
}}<br />
<br />
=== rand() ===<br />
{{Nasal doc<br />
|syntax = rand();<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=554|t=Source}}<br />
|text = Returns a random floating point number between 0 (inclusive) and 1 (exclusive). It takes no arguments.<br />
|example1 = print(rand()); # prints random number<br />
}}<br />
<br />
=== registerFlightPlanDelegate() ===<br />
{{Nasal doc<br />
|syntax = registerFlightPlanDelegate(init_func);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1879|t=Source}}<br />
|text = Registers a flight plan delegate. See <tt>''{{fgdata file|Nasal/route_manager.nas|t=$FG_ROOT/Nasal/route_manager.nas}}''</tt> for examples.<br />
|param1 = init_func<br />
|param1text = Initialization function which will be called during FlightGear's startup.<br />
}}<br />
=== removecommand() ===<br />
{{Nasal doc<br />
|syntax = removecommand(cmd);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=668|t=Source}}<br />
|text = Removes the given fgcommand. Returns <code>'''nil'''</code>.<br />
{{caution|This will remove '''any''' [[fgcommands|fgcommand]], even those implemented in C++, so use with caution!}}<br />
|param1 = cmd<br />
|param1text = String specifying the name of the command to remove.<br />
|example1 = addcommand("hello", func(){<br />
print("Hello");<br />
});<br />
fgcommand("hello"); # "Hello" will be printed<br />
removecommand("hello"); # removes it<br />
}}<br />
<br />
=== removelistener() ===<br />
{{Nasal doc<br />
|syntax = removelistener(id);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=1384|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=506|t=Part 2}}<br />
|text = Removes and deactivates the given listener and returns the number of listeners left or <code>'''nil'''</code> on error.<br />
{{note|It is good practice to remove listeners when they are not required anymore. This prevents the listeners reducing FlightGear's run performance.}}<br />
|param1 = id<br />
|param1text = ID of listener as returned by {{func link|setlistener()}}.<br />
|example1 = var ls = setlistener("/sim/test", func(){<br />
print("Property '/sim/test' has been changed");<br />
});<br />
setprop("/sim/test", "blah"); # trigger listener<br />
var rem = removelistener(ls); # remove listener<br />
print("There are ", rem, " listeners remaining");<br />
}}<br />
<br />
=== resolvepath() ===<br />
{{Nasal doc<br />
|syntax = resolvepath(path);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=604|t=Source}}<br />
|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.<br />
|param1 = path<br />
|param1text = Relative path to be completed.<br />
|example1 = print(resolvepath("Nasal/globals.nas")); # prints the equivalent of $FG_ROOT/Nasal/globals.nas<br />
|example2 = print(resolvepath("blah")); # prints nothing; could not be resolved<br />
|example3 = var file_path = resolvepath("Aircraft/SenecaII/some-file");<br />
if (file_path != ""){<br />
gui.popupTip("some-file found", 2);<br />
} else {<br />
gui.popupTip("some-file not found", 2);<br />
}<br />
}}<br />
<br />
=== setlistener() ===<br />
{{Nasal doc<br />
|syntax = setlistener(node, code[, init[, type]]);<br />
|private = _setlistener()<br />
|source = {{flightgear file|src/Scripting/Nasal|l=499|t=Part 1}} {{!}} {{flightgear file|src/Scripting/Nasal|l=1350|t=Part 2}}<br>''Listener implemented using the {{API Link|flightgear|class|FGNasalListener}} class.''<br />
|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()}}.<br />
{{note|Listeners are known to be a source of resource leaks. To avoid this, please take measures such as:<br />
* Using {{func link|removelistener()}} when they are not needed any more.<br />
* Using a single initialization listener.<br />
* Avoiding tying listeners to properties that are rapidly updated (e.g., many times per frame).<br />
}}<br />
|param1 = node<br />
|param1text = Mandatory string or <code>props.Node</code> object pointing to a property in the [[Property Tree]].<br />
|param2 = code<br />
|param2text = Mandatory callback function to execute when the listener is triggered. The function can take up to four arguments in the following order:<br />
* '''changed''': a <code>props.Node</code> object pointing to the changed node.<br />
* '''listen''': a <code>props.Node</code> object pointing to the listened-to node. Note that this argument maybe different depending on the '''type'''.<br />
* '''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.<br />
* '''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.<br />
|param3 = init<br />
|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).<br />
|param4 = type<br />
|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.<br />
|example1 = var prop = props.globals.initNode("/sim/test", "", "STRING"); # create property<br />
var id = setlistener("/sim/test", func(n){ # create listener<br />
print("Value: ", n.getValue());<br />
});<br />
setprop("/sim/test", "blah"); # trigger listener<br />
removelistener(id); # remove listener<br />
prop.remove(); # remove property<br />
}}<br />
<br />
=== setprop() ===<br />
{{Nasal doc<br />
|syntax = setprop(path[, path[, ...]], value);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=385|t=Source}}<br />
|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.<br />
{{note|If you want to remove a property, you will have to use one of the <code>props</code> helpers:<br />
<syntaxhighlight lang="nasal"><br />
props.globals.getNode("foo/bar").remove(); # take out the complete node<br />
props.globals.getNode("foo").removeChild("bar"); # take out a certain child node<br />
</syntaxhighlight><br />
}}<br />
|param1 = path<br />
|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.<br />
|param2 = value<br />
|param2text = Value to write to the given property. Must be either a string or a number.<br />
|example1 = setprop("/sim/demo", "This is a demo");<br />
|example2text = Note that the example below will only work in FlightGear 3.2 and above.<br />
|example2 = for(var i = 0; i < 3; i += 1){<br />
setprop("/sim/demo", i, "Demo #" ~ i));<br />
}<br />
|example3text = Same as above, but is supported by all versions of FlightGear.<br />
|example3 = for(var i = 0; i < 3; i += 1){<br />
setprop("/sim/demo[" ~ i ~ "]", "Demo #" ~ i));<br />
}<br />
}}<br />
==== See also ====<br />
{{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: <br />
To get a Node rather than its value, use <code>props.globals.getNode()</code> - see [[Nasal_library/props]]. }}<br />
<br />
=== settimer() ===<br />
{{Nasal doc<br />
|syntax = settimer(function, delta[, realtime]);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=499|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=1286|t=Part 2}}<br />
|text = Runs the given function a specified amount of seconds after the current time. Returns <code>'''nil'''</code>.<br />
{{note|Improper use of <code>settimer()</code> may cause resource leaks. It is also highly recommended that the newer {{func link|maketimer()}} should be used instead of this function.}} <br />
|param1 = function<br />
|param1text = Mandatory function that will be called. It may be necessary to enclose code in an anonymous function (see example).<br />
|param2 = delta<br />
|param2text = Mandatory amount of time in seconds after which the function will be called.<br />
|param3 = realtime<br />
|param3text = If 1 (true), "real time" will be used instead of "simulation time." Defaults to 0 (false). Note that if "simulation time" is used, the timer will not run while FlightGear is paused.<br />
|example1 = var myFunc = func(){<br />
print("Hello");<br />
}<br />
<br />
settimer(myFunc, 2); # runs myFunc after 2 seconds<br />
|example2 = var sqr = func(a){<br />
return a * a;<br />
}<br />
<br />
settimer(func(){<br />
print(sqr(2)); # will print 4 after 2 seconds<br />
}, 2);<br />
}}<br />
<br />
=== srand() ===<br />
{{Nasal doc<br />
|syntax = srand();<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=559|t=Source}}<br />
|text = Makes the pseudorandom number generator (see {{func link|rand()}}) generate a new {{wikipedia|random seed|noicon=1}} based on time. Returns 0.<br />
}}<br />
=== systime() ===<br />
{{Nasal doc<br />
|syntax = systime();<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=770|t=Source}}<br />
|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).<br />
{{note|1=High resolution timers under Windows can produce inaccurate or fixed sub-millisecond results.<ref>{{cite web|url=http://forum.flightgear.org/viewtopic.php?f=30&t=29259|title=Nasal: systime() ??!?|author=Necolatis|date=Apr 2nd, 2016}}</ref> 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]}}<br />
|example1 = print("Unix time: ", systime()); # prints Unix time<br />
|example2 = var myFunc = func(){<br />
for(var i = 0; i <= 10; i += 1){<br />
print("Interation #", i);<br />
}<br />
}<br />
var t = systime(); # record time<br />
myFunc(); # run function<br />
var t2 = systime(); # record new time<br />
print("myFunc() took ", t2 - t, " seconds"); # print result<br />
}}<br />
<br />
=== thisfunc() ===<br />
{{Nasal doc<br />
|syntax = thisfunc();<br />
|source = {{fgdata file|Nasal/globals.nas|t=Source}}<br />
|text = Returns the function from which this function is called. This allows a function to reliably and safely call itself from within a closure.<br />
|example1 = var stringify_vec = func(input){<br />
if (typeof(input) == "scalar"){<br />
return sprintf("%s", input);<br />
} elsif (typeof(input) == "vector") {<br />
if (size(input) == 0) return "[]";<br />
var this = thisfunc();<br />
var buffer = "[";<br />
for(var i = 0; i < size(input); i += 1){<br />
buffer ~= this(input[i]);<br />
if (i == size(input) - 1) {<br />
buffer ~= "]";<br />
} else {<br />
buffer ~= ", ";<br />
}<br />
}<br />
return buffer;<br />
} else {<br />
die("stringify_vec(): Error! Invalid input. Must be a vector or scalar");<br />
}<br />
}<br />
<br />
var test_vec = ["a", "b", "c", 1, 2, 3];<br />
debug.dump(stringify_vec(test_vec)); # prints "[a, b, c, 1, 2, 3]"<br />
test_vec = [];<br />
debug.dump(stringify_vec(test_vec)); # prints "[]"<br />
test_vec = {};<br />
debug.dump(stringify_vec(test_vec)); # will throw an error<br />
}}<br />
<br />
=== tileIndex() ===<br />
{{Nasal doc<br />
|syntax = tileIndex();<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1720|t=Source}}<br />
|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 <code>942050</code>, corresponding to <tt>''[[$FG_SCENERY]]/Terrain/w130n30/w123n3/942050.stg''</tt>.<br />
|example1 = print(tileIndex()); # print index<br />
}}<br />
<br />
=== tilePath() ===<br />
{{Nasal doc<br />
|syntax = tilePath();<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1712|t=Source}}<br />
|text = Returns the base path of the tile at the aircraft's current position as a string. For example, at KSFO, this would be <code>w130n30/w123n3</code>, corresponding to <tt>''[[$FG_SCENERY]]/Terrain/w130n30/w123n3''</tt>.<br />
|example1 = print(tilePath()); # print path<br />
}}<br />
<br />
=== values() ===<br />
{{Nasal doc<br />
|syntax = values(hash);<br />
|source = {{fgdata file|Nasal/globals.nas|t=Source}}<br />
|text = Returns a vector containing the values of the given hash.<br />
|param1 = hash<br />
|param1text = Mandatory hash to get the values of.<br />
|example1 = var hash = {<br />
"a": 1,<br />
"b": 2,<br />
"c": 3<br />
};<br />
<br />
foreach(var val; values(hash)){<br />
print(val);<br />
}<br />
|example2text = The below example does exactly the same thing as the above example, but does not use <code>values()</code>:<br />
|example2 = var hash = {<br />
"a": 1,<br />
"b": 2,<br />
"c": 3<br />
};<br />
<br />
foreach(var key; keys(hash)){<br />
print(hash[key]);<br />
}<br />
}}<br />
<br />
== Extension functions new in FG 2020.1 ==<br />
The following functions have been added to the nasal core library and will be released with FlightGear version 2020.1. <br />
Befor the release they are available in the development branch "next".<br />
<br />
=== isfunc() ===<br />
Returns 1 if type or argument is a function, otherwise 0.<br />
<br />
=== isghost() ===<br />
Returns 1 if type or argument is a ghost, otherwise 0.<br />
<br />
=== ishash() ===<br />
Returns 1 if type or argument is a hash, otherwise 0.<br />
<br />
=== isint() ===<br />
Returns 1 if argument has a numeric value and x == floor(x), e.g. integer<br />
<br />
=== isnum() ===<br />
{{Nasal doc<br />
|syntax = isnum(x);<br />
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}<br />
|text = Returns 1 if x has a numeric value otherwise 0. <br />
}}<br />
<br />
=== isstr() ===<br />
Returns 1 if type or argument is a string, otherwise 0.<br />
<br />
=== isvec() ===<br />
Returns 1 if type or argument is a vector, otherwise 0.<br />
<br />
=== vecindex() ===<br />
{{Nasal doc<br />
|syntax = vecindex(vector, value);<br />
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}<br />
|text = Returns the index of value or nil, if value is not found in vector.<br />
}}<br />
<br />
== Variables ==<br />
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}}.<br />
<br />
=== D2R ===<br />
{{Nasal doc<br />
|syntax = var radians = degrees * D2R;<br />
|text = Converts an angle from degrees to radians when multiplied by the angle in degrees. Equal to <code>π / 180</code>.<br />
}}<br />
=== FPS2KT ===<br />
{{Nasal doc<br />
|syntax = var knots = feet_per_second * FPS2KT;<br />
|text = Converts a velocity from feet per second to knots when multiplied by the velocity in feet per second. Approximately equal to 0.5925.<br />
}}<br />
=== FT2M ===<br />
{{Nasal doc<br />
|syntax = var metres = feet * FT2M;<br />
|text = Converts a length from feet to metres when multiplied by the length in feet. Equal to 0.3048.<br />
}}<br />
=== GAL2L ===<br />
{{Nasal doc<br />
|syntax = var litres = gallons * GAL2L;<br />
|text = Converts a volume from US liquid gallons to litres when multiplied by the volume in gallons. Approximately equal to 3.7854.<br />
}}<br />
=== IN2M ===<br />
{{Nasal doc<br />
|syntax = var metres = inches * IN2M;<br />
|text = Converts a length from inches to metres when multiplied by the length in inches. Equal to 0.0254.<br />
}}<br />
=== KG2LB ===<br />
{{Nasal doc<br />
|syntax = var pounds = kilograms * KG2LB;<br />
|text = Converts a mass from kilograms to pounds when multiplied by the mass in kilograms. Approximately equal to 2.2046.<br />
}}<br />
=== KT2FPS ===<br />
{{Nasal doc<br />
|syntax = var feet_per_second = knots * KT2FPS;<br />
|text = Converts a velocity from knots to feet per second when multiplied by the velocity in knots. Approximately equal to 1.6878.<br />
}}<br />
=== KT2MPS ===<br />
{{Nasal doc<br />
|syntax = var metres_per_second = knots * KT2MPS;<br />
|text = Converts a velocity from knots to metres per second when multiplied by the velocity in knots. Approximately equal to 0.5144.<br />
}}<br />
=== L2GAL ===<br />
{{Nasal doc<br />
|syntax = var gallons = litres * L2GAL;<br />
|text = Converts a volume from litres to US liquid gallons when multiplied by the volume in litres. Approximately equal to 0.2642.<br />
}}<br />
=== LB2KG ===<br />
{{Nasal doc<br />
|syntax = var kilograms = pounds * LB2KG;<br />
|text = Converts a mass from pounds to kilograms when multiplied by the mass in pounds. Approximately equal to 0.4536.<br />
}}<br />
=== M2FT ===<br />
{{Nasal doc<br />
|syntax = var feet = metres * M2FT;<br />
|text = Converts a length from metres to feet when multiplied by the length in metres. Approximately equal to 3.2808.<br />
}}<br />
=== M2IN ===<br />
{{Nasal doc<br />
|syntax = var inches = metres * M2IN;<br />
|text = Converts a length from metres to inches when multiplied by the length in metres. Approximately equal to 39.3701.<br />
}}<br />
=== M2NM ===<br />
{{Nasal doc<br />
|syntax = var nautical_miles = metres * M2NM;<br />
|text = Converts a length from metres to nautical miles when multiplied by the length in metres. Approximately equal to 0.00054.<br />
}}<br />
=== MPS2KT ===<br />
{{Nasal doc<br />
|syntax = var knots = metres_per_second * MPS2KT;<br />
|text = Converts a velocity from metres per second to knots when multiplied by the velocity in metres per second. Approximately equal to 1.9438.<br />
}}<br />
=== NM2M ===<br />
{{Nasal doc<br />
|syntax = var metres = nautical_miles * NM2M;<br />
|text = Converts a length from nautical miles to metres when multiplied by the length in nautical miles. Equal to 1,852.<br />
}}<br />
=== R2D ===<br />
{{Nasal doc<br />
|syntax = var degrees = radians * R2D;<br />
|text = Converts an angle from radians to degrees when multiplied by the angle in radians. Equal to <code>180 / π</code>.<br />
}}<br />
<br />
{{Appendix}}<br />
<br />
<br />
{{Nasal namespaces}}</div>Jsbhttps://wiki.flightgear.org/w/index.php?title=Modules.nas&diff=123766Modules.nas2020-04-17T07:00:22Z<p>Jsb: /* Reloadable modules / frameworks */</p>
<hr />
<div>== Nasal runtime re-loadable modules ==<br />
=== Introduction and motivation ===<br />
The embedded scripting language of FlightGear, [[Nasal]] comes with a limited number of [[Nasal library|core functions]].<br />
<br />
The language has been [[Howto:Extend Nasal|extended by C++ functions]], as well as by libraries written in Nasal (for example [[Nasal library/props|props]], [[Nasal library/io|io]], [[Nasal library/math|math]], [[Nasal library/debug|debug]] etc). <br />
<br />
The latter are stored in the [[FGData]] repository under <code>/Nasal</code> and loaded automatically by FlightGear.<br />
<br />
Optional code, which is loaded only on demand, can be handled either by the legacy module system implemented in C++ or by <code>modules.nas</code> if run-time re-load support is required.<br />
<br />
For more information on how and when these files are loaded, see [[Nasal Initialization]].<br />
<br />
=== Differences between add-ons and modules ===<br />
While there are many similarities between add-ons and modules, there are also some differences: <br />
<br />
; Distribution<br />
: Modules are distributed with FlightGear as part of FGData or they are aircraft specific and delivered with the aircraft.<br />
: Add-ons have to be downloaded separately by a FlightGear user from wherever the author of the add-on publishes the add-on.<br />
<br />
; Loading<br />
: Modules can be loaded for example by an aircraft if the aircraft developer wants to make use of the module.<br />
: Add-ons are selected by the user before launching FlightGear, thus they may or '''may not be available''' at runtime.<br />
<br />
== HOWTOs and examples ==<br />
First some examples, the API documentation follows below.<br />
<br />
=== Nasal modules in an aircraft ===<br />
Nasal code for an aircraft (for example for development) is usually loaded by a XML declaration in the <code>[[Aircraft-set.xml]]</code> file like this:<br />
<syntaxhighlight lang="xml"><br />
<PropertyList><br />
<nasal><br />
<foo> <!-- Nasal namespace foo --><br />
<file>Nasal/foo.nas</file><br />
</foo><br />
<bar> <!-- Nasal namespace bar --><br />
<file>Aircraft/Generic/foo.nas</file><br />
</bar><br />
<myAircraft> <!-- Nasal namespace myAircraft--><br />
<file>Nasal/efis_module.nas</file><br />
</myAircraft><br />
</nasal><br />
</PropertyList><br />
</syntaxhighlight><br />
<br />
This is fine unless you are developing a Nasal subsystem for the aircraft and have to restart FlightGear every time you edit a few lines. <br />
For this case you could consider to use the Module class (defined in <code>FGDATA/Nasal/modules.nas</code>) and make your code re-loadable at runtime, <br />
which means you do not have to restart whole FlightGear just for a few lines of edited Nasal code.<br />
<br />
Example <code>Nasal/efis_module.nas</code>:<br />
<syntaxhighlight lang="nasal"><br />
#-- load EFIS as reloadable module<br />
var my_efis = modules.Module.new("myAircraft_EFIS"); # Module name<br />
my_efis.setDebug(1); # 0=(mostly) silent; 1=print setlistener and maketimer calls to console; 2=print also each listener hit, be very careful with this! <br />
my_efis.setFilePath(getprop("/sim/aircraft-dir")~"/Nasal/EFIS");<br />
my_efis.setMainFile("myAircraft-efis.nas");<br />
my_efis.load();<br />
</syntaxhighlight><br />
<br />
==== Menu item for reloading ====<br />
Now add a menu item for easy reloading like this:<br />
{{caution|The reload mechanism has been changed to fgcommand, the reload property used in the pre-release has been removed. <br />
The advantage is you just need to know the module name but not any complicated property path or Nasal namespace.}}<br />
{{note|The module name passed to the fgcommand must match the name passed to <code>modules.Module.new()</code>}}<br />
<syntaxhighlight lang="xml"><br />
<menubar><br />
<default><br />
<menu n="100"><br />
<!-- normal aircraft menu --><br />
</menu><br />
<menu n="101"><br />
<label>Aircraft Development</label><br />
<item><br />
<label>Reload EFIS</label><br />
<binding><br />
<command>nasal-module-reload</command><br />
<module>myAircraft_EFIS</module><br />
</binding><br />
</item><br />
</menu><br />
</default><br />
</menubar><br />
</syntaxhighlight><br />
<br />
{{tip|You can also call <code>myAircraft.my_efis.reload();</code> to trigger the reload. <code>myAircraft</code> is the namespace given in the <code>Aircraft-set.xml</code> file, <code>my_efis</code> is the module object (see above).}}<br />
<br />
=== Nasal frameworks ===<br />
Another use case for <code>Modules.nas</code> is frameworks that need reload support.<br />
<br />
A framework usually provides more or less generic functionality that has to be adapted (configured) for a specific use case (for example a specific aircraft).<br />
<br />
Reload support may become very convenient, if the customizing based on such framework involves many iterations of edit-reload-test.<br />
<br />
First living example is the <code>canvas_efis</code> framework which can be included into an aircraft with a single line of code: <br />
<br />
var efis_module = modules.load("canvas_efis");<br />
<br />
(see also last chapter at the end of this article)<br />
<br />
== Structure of Nasal reloadable modules ==<br />
A module must contain at least the file <code>main.nas</code> (default, other filename is possible) and should contain the functions <code>main()</code> and <code>unload()</code>.<br />
<br />
==== main() function ====<br />
The <code>main.nas</code> file must contain a <code>main()</code> function that will be called on load with either the module object as parameter or the parameters passed to the <code>module.load()</code> function.<br />
<br />
==== unload() function ====<br />
If you want the module to be re-/un-loadable, you must make sure to track resources and remove them on onload.<br />
For this the <code>main.nas</code> shall contain a function <code>unload()</code> that removes any resources which were created by the module.<br />
<br />
'''Example:''' any canvas created by the module should have called its <code>del()</code> method here.<br />
<br />
{{note|Calls to <code>setlistener()<code> and <code>maketimer()<code> are automatically tracked by the <code>Module</code> class for you, timers will be stopped automatically, listeners will be removed automatically on <code>unload()<code>.}}<br />
<br />
==== Rules for Module names ====<br />
# Module names shall contain only letters (<code>a-z, A-Z</code>), numbers (<code>0-9</code>) and underscores (<code>_</code>).<br />
# The name must contain at least one letter so it cannot be confused with a number<br />
# The name shall not match any existing Nasal file (<code>.nas</code>) or directory in <code>FGDATA/Nasal</code> to avoid namespace clashes<br />
<br />
{{note|Reloadable modules shipped with FlightGear are stored in subdirectories of <code>FGDATA/Nasal/modules/</code> as these subdirectories will not be loaded automatically by FlightGear on start.<br />
<br />
However, <code>modules.nas</code> will scan this directory to create a list of available modules (list of subdirectories).<br />
<br />
Each module corresponds to one subdirectory and the directory name is also the module name.<br />
}}<br />
<br />
== modules.nas ==<br />
In <code>modules.nas</code> the class <code>Module</code> is defined.<br />
<br />
A module object holds information about the path and filename of the Nasal script and supports unloading and reloading the code at runtime<br />
(for example without restarting FlightGear as a whole) by tracking some critical resources like [[listeners]] and [[timers]].<br />
<br />
{{note|Parts of this functionality were added to the [[addons]] manager earlier and have now been extracted to avoid code duplication.}}<br />
<br />
{{caution|Within a module <code>setlistener()</code> is overloaded and the default value for the 4th argument (runtime) is changed to <code>0</code>, so the listener will run only if the property value has changed.}}<br />
<br />
=== library functions ===<br />
==== modules.isAvailable() ====<br />
{{Nasal doc<br />
|syntax = modules.isAvailable(module_name);<br />
|text = This function returns true, if there is a module (subdirectory) in <code>FGDATA/Nasal/modules/</code> with the given name.<br />
|param1 = module_name<br />
|param1text = The name of the module (in the subdirectory in <code>Nasal/modules</code>) to load.<br />
|example1 = <br />
if (modules.isAvailable("foo_bar")) {<br />
modules.load("foo_bar");<br />
} <br />
}}<br />
<br />
==== modules.setDebug() ====<br />
{{Nasal doc<br />
|syntax = modules.setDebug(module_name, [debug=1]);<br />
|text = This function enables debugging for a module. It must be called '''before''' <code>load()</code>!<br />
|param1 = module_name<br />
|param1text = The name of the module (in the subdirectory in <code>Nasal/modules</code>) to load.<br />
|param2 = debug<br />
|param2text = Defaults to <code>1</code> (true), use <code>0</code> to disable debug. If debug > <code>1</code>, each listener hit will be printed, be careful not to listen to rapidly changing props, do not set <code>runtime=1</code> in <code>setlistener()</code>.<br />
|example1 = <br />
var debug = 1;<br />
modules.setDebug("foo_bar", debug); <br />
}}<br />
<br />
==== modules.load() ====<br />
{{Nasal doc<br />
|syntax = modules.load(module_name, [namespace_name]);<br />
|text = This function attempts to load a module from <code>FGDATA/Nasal/modules/</code><br />
|param1 = module_name<br />
|param1text = The name of the module (in the subdirectory in <code>Nasal/modules</code>) to load.<br />
|param2 = namespace_name<br />
|param2text = Optional, load module to a different namespace.<br />
|example1 = <br />
modules.load("foo_bar"); <br />
}}<br />
<br />
=== Methods of class Module ===<br />
==== setFilePath() ====<br />
{{Nasal doc<br />
|syntax = mymod.setFilePath(path);<br />
|text = Configure where to look for the main file, for example in the aircraft (sub-)directory.<br />
|param1 = path<br />
|param1text = File path where the module is stored.<br />
}}<br />
<br />
==== setMainFile() ====<br />
{{Nasal doc<br />
|syntax = mymod.setMainFile(filename);<br />
|text = Configure the nasal file to load.<br />
|param1 = filename<br />
|param1text = File that will be loaded by <code>load()</code>.<br />
|example1 =<br />
# if you develop a new nasal system for your aircraft, it might be handy to implement it as module<br />
# so you can reload the file quickly without restarting FlightGear<br />
var my_foo_sys = modules.Module.new("my_aircraft_foo");<br />
my_foo_sys.setDebug(1);<br />
my_foo_sys.setFilePath(getprop("/sim/aircraft-dir")~"/Nasal");<br />
my_foo_sys.setMainFile("foo.nas");<br />
my_foo_sys.load();<br />
}}<br />
<br />
==== setNamespace() ====<br />
{{Nasal doc<br />
|syntax = mymod.setNamespace(namespace);<br />
|text = Configure the Nasal namespace to use. Be really careful when using existing namespaces! <code>unload()</code> or <code>reload()</code> will destroy them!<br />
|param1 = namespace<br />
|param1text = The Nasal namespace the module code will be loaded into.<br />
}}<br />
<br />
==== setDebug() ====<br />
{{Nasal doc<br />
|syntax = mymod.setDebug(debug=1);<br />
|text = Activate debugging for this module. '''Must be called before calling <code>load()</code>!'''<br />
|param1 = debug<br />
|param1text = <br />
0: no debugging; <br />
1 (default if no argument given): print calls to redirected <code>setlister()</code> and <code>maketimer()</code>; <br />
<br />
2: listeners print property path when hit (Use with caution! '''Do not call <code>setlistener()</code> with <code>runtime=1</code>'''.)<br />
}}<br />
<br />
==== load() ====<br />
{{Nasal doc<br />
|syntax = mymod.load([args]);<br />
|text = This function attempts to load the module into its namespace.<br />
|param1 = optional args<br />
|param1text = Arguments are passed to the <code>main()</code> function of the module. If empty, the module object will be passed to <code>main()</code>.<br />
}}<br />
<br />
==== unload() ====<br />
{{Nasal doc<br />
|syntax = mymod.unload();<br />
|text = This function attempts to remove tracked resources and remove the module by killing its namespace.<br />
}}<br />
<br />
==== reload() ====<br />
{{Nasal doc<br />
|syntax = mymod.reload();<br />
|text = Shorthand, calls <code>unload()</code> and <code>load()</code>.<br />
}}<br />
<br />
==== get() ====<br />
{{Nasal doc<br />
|syntax = mymod.get(var_name);<br />
|text = Returns a variable from the modules namespace.<br />
|param1 = var_name<br />
|param1text = The variable to get.<br />
|example1 =<br />
var foo = modules.load("foo");<br />
var bar = foo.get("bar"); # get variable "bar" defined in FGDATA/Nasal/modules/foo/main.nas (or a file included by this file)<br />
}}<br />
<br />
== Property tree interface for modules.nas ==<br />
In the property tree there is a subtree <code>/nasal</code> to control modules and get some statistics.<br />
<br />
The properties available depend on the type of module ("load-once" or "reloadable", see [[Nasal Initialization]] for more information on the differences).<br />
<br />
=== Reloadable modules / frameworks ===<br />
Modules handled by <code>modules.nas</code> will have their properties in <code><nowiki>/nasal/modules/<moduleName></nowiki></code> where <code><nowiki><moduleName></nowiki></code> is given by the developer when calling either <code><nowiki>Module.new("<moduleName>")</nowiki></code> or <code><nowiki>modules.load("<moduleName>")</nowiki></code>.<br />
<br />
In the latter case <code><nowiki><moduleName></nowiki></code> specifies the subdirectory <code><nowiki>FGDATA/Nasal/modules/<moduleName></nowiki></code> in which some framework is stored.<br />
<br />
{| class="wikitable"<br />
|-<br />
! property !! type !! content<br />
|-<br />
| <code>loaded</code> || bool || true if module was loaded without errors<br />
|-<br />
| <code>listeners</code> || int || Number of tracked listeners<br />
|-<br />
| <code>listener-hits</code> || int || If debugging is enabled, this prop shows the total number of hits to all tracked listeners.<br />
|-<br />
| <code>timers</code> || int || Number of tracked timers (maketimer).<br />
|}<br />
<br />
=== Legacy load-once modules ===<br />
A legacy load-once module is a direct (1st level) subdirectory of <code>FGDATA/Nasal/</code> and its corresponding property tree is <code><nowiki>/nasal/<moduleName>/</nowiki></code> where <code><nowiki><moduleName></nowiki></code> equals the name of the subdirectory.<br />
<br />
It is handled by C++ code and must have a corresponding entry in <code>FGDATA/defaults.xml</code> which defines a property "enabled" and optionally a property "module".<br />
<br />
If enabled is set to false in <code>defaults.xml</code>, the C++ code will setup a listener and load the module as soon as enabled is set to true.<br />
<br />
The property name "module" is a bit misleading, it is used to define into which namespace the files shall be loaded.<br />
<br />
For each Nasal file in the subdirectory a <code>file[i]</code> property is created holding the full path+filename.<br />
<br />
The bool <code>loaded</code> property shows the status of the module.<br />
<br />
== Existing modules with reload support ==<br />
Stable Nasal frameworks which support reloading can be added to <code>FGDATA/Nasal/modules/<module_name></code>. <br />
This allows an aircraft developer to configure the framework for a specific aircraft and make use of the reload magic while developing the configuration.<br />
<br />
{| class="wikitable"<br />
|-<br />
! Module name !! Desctiption !! time added <br />
|-<br />
| [[Canvas EFIS Framework|<code>canvas_efis</code>]] || framework to manage canvas based EFIS screens || 02/2020<br />
|}<br />
<br />
[[Category:Nasal]]</div>Jsbhttps://wiki.flightgear.org/w/index.php?title=Modules.nas&diff=123765Modules.nas2020-04-17T06:59:35Z<p>Jsb: Reload mechanism changed to fgcommand</p>
<hr />
<div>== Nasal runtime re-loadable modules ==<br />
=== Introduction and motivation ===<br />
The embedded scripting language of FlightGear, [[Nasal]] comes with a limited number of [[Nasal library|core functions]].<br />
<br />
The language has been [[Howto:Extend Nasal|extended by C++ functions]], as well as by libraries written in Nasal (for example [[Nasal library/props|props]], [[Nasal library/io|io]], [[Nasal library/math|math]], [[Nasal library/debug|debug]] etc). <br />
<br />
The latter are stored in the [[FGData]] repository under <code>/Nasal</code> and loaded automatically by FlightGear.<br />
<br />
Optional code, which is loaded only on demand, can be handled either by the legacy module system implemented in C++ or by <code>modules.nas</code> if run-time re-load support is required.<br />
<br />
For more information on how and when these files are loaded, see [[Nasal Initialization]].<br />
<br />
=== Differences between add-ons and modules ===<br />
While there are many similarities between add-ons and modules, there are also some differences: <br />
<br />
; Distribution<br />
: Modules are distributed with FlightGear as part of FGData or they are aircraft specific and delivered with the aircraft.<br />
: Add-ons have to be downloaded separately by a FlightGear user from wherever the author of the add-on publishes the add-on.<br />
<br />
; Loading<br />
: Modules can be loaded for example by an aircraft if the aircraft developer wants to make use of the module.<br />
: Add-ons are selected by the user before launching FlightGear, thus they may or '''may not be available''' at runtime.<br />
<br />
== HOWTOs and examples ==<br />
First some examples, the API documentation follows below.<br />
<br />
=== Nasal modules in an aircraft ===<br />
Nasal code for an aircraft (for example for development) is usually loaded by a XML declaration in the <code>[[Aircraft-set.xml]]</code> file like this:<br />
<syntaxhighlight lang="xml"><br />
<PropertyList><br />
<nasal><br />
<foo> <!-- Nasal namespace foo --><br />
<file>Nasal/foo.nas</file><br />
</foo><br />
<bar> <!-- Nasal namespace bar --><br />
<file>Aircraft/Generic/foo.nas</file><br />
</bar><br />
<myAircraft> <!-- Nasal namespace myAircraft--><br />
<file>Nasal/efis_module.nas</file><br />
</myAircraft><br />
</nasal><br />
</PropertyList><br />
</syntaxhighlight><br />
<br />
This is fine unless you are developing a Nasal subsystem for the aircraft and have to restart FlightGear every time you edit a few lines. <br />
For this case you could consider to use the Module class (defined in <code>FGDATA/Nasal/modules.nas</code>) and make your code re-loadable at runtime, <br />
which means you do not have to restart whole FlightGear just for a few lines of edited Nasal code.<br />
<br />
Example <code>Nasal/efis_module.nas</code>:<br />
<syntaxhighlight lang="nasal"><br />
#-- load EFIS as reloadable module<br />
var my_efis = modules.Module.new("myAircraft_EFIS"); # Module name<br />
my_efis.setDebug(1); # 0=(mostly) silent; 1=print setlistener and maketimer calls to console; 2=print also each listener hit, be very careful with this! <br />
my_efis.setFilePath(getprop("/sim/aircraft-dir")~"/Nasal/EFIS");<br />
my_efis.setMainFile("myAircraft-efis.nas");<br />
my_efis.load();<br />
</syntaxhighlight><br />
<br />
==== Menu item for reloading ====<br />
Now add a menu item for easy reloading like this:<br />
{{caution|The reload mechanism has been changed to fgcommand, the reload property used in the pre-release has been removed. <br />
The advantage is you just need to know the module name but not any complicated property path or Nasal namespace.}}<br />
{{note|The module name passed to the fgcommand must match the name passed to <code>modules.Module.new()</code>}}<br />
<syntaxhighlight lang="xml"><br />
<menubar><br />
<default><br />
<menu n="100"><br />
<!-- normal aircraft menu --><br />
</menu><br />
<menu n="101"><br />
<label>Aircraft Development</label><br />
<item><br />
<label>Reload EFIS</label><br />
<binding><br />
<command>nasal-module-reload</command><br />
<module>myAircraft_EFIS</module><br />
</binding><br />
</item><br />
</menu><br />
</default><br />
</menubar><br />
</syntaxhighlight><br />
<br />
{{tip|You can also call <code>myAircraft.my_efis.reload();</code> to trigger the reload. <code>myAircraft</code> is the namespace given in the <code>Aircraft-set.xml</code> file, <code>my_efis</code> is the module object (see above).}}<br />
<br />
=== Nasal frameworks ===<br />
Another use case for <code>Modules.nas</code> is frameworks that need reload support.<br />
<br />
A framework usually provides more or less generic functionality that has to be adapted (configured) for a specific use case (for example a specific aircraft).<br />
<br />
Reload support may become very convenient, if the customizing based on such framework involves many iterations of edit-reload-test.<br />
<br />
First living example is the <code>canvas_efis</code> framework which can be included into an aircraft with a single line of code: <br />
<br />
var efis_module = modules.load("canvas_efis");<br />
<br />
(see also last chapter at the end of this article)<br />
<br />
== Structure of Nasal reloadable modules ==<br />
A module must contain at least the file <code>main.nas</code> (default, other filename is possible) and should contain the functions <code>main()</code> and <code>unload()</code>.<br />
<br />
==== main() function ====<br />
The <code>main.nas</code> file must contain a <code>main()</code> function that will be called on load with either the module object as parameter or the parameters passed to the <code>module.load()</code> function.<br />
<br />
==== unload() function ====<br />
If you want the module to be re-/un-loadable, you must make sure to track resources and remove them on onload.<br />
For this the <code>main.nas</code> shall contain a function <code>unload()</code> that removes any resources which were created by the module.<br />
<br />
'''Example:''' any canvas created by the module should have called its <code>del()</code> method here.<br />
<br />
{{note|Calls to <code>setlistener()<code> and <code>maketimer()<code> are automatically tracked by the <code>Module</code> class for you, timers will be stopped automatically, listeners will be removed automatically on <code>unload()<code>.}}<br />
<br />
==== Rules for Module names ====<br />
# Module names shall contain only letters (<code>a-z, A-Z</code>), numbers (<code>0-9</code>) and underscores (<code>_</code>).<br />
# The name must contain at least one letter so it cannot be confused with a number<br />
# The name shall not match any existing Nasal file (<code>.nas</code>) or directory in <code>FGDATA/Nasal</code> to avoid namespace clashes<br />
<br />
{{note|Reloadable modules shipped with FlightGear are stored in subdirectories of <code>FGDATA/Nasal/modules/</code> as these subdirectories will not be loaded automatically by FlightGear on start.<br />
<br />
However, <code>modules.nas</code> will scan this directory to create a list of available modules (list of subdirectories).<br />
<br />
Each module corresponds to one subdirectory and the directory name is also the module name.<br />
}}<br />
<br />
== modules.nas ==<br />
In <code>modules.nas</code> the class <code>Module</code> is defined.<br />
<br />
A module object holds information about the path and filename of the Nasal script and supports unloading and reloading the code at runtime<br />
(for example without restarting FlightGear as a whole) by tracking some critical resources like [[listeners]] and [[timers]].<br />
<br />
{{note|Parts of this functionality were added to the [[addons]] manager earlier and have now been extracted to avoid code duplication.}}<br />
<br />
{{caution|Within a module <code>setlistener()</code> is overloaded and the default value for the 4th argument (runtime) is changed to <code>0</code>, so the listener will run only if the property value has changed.}}<br />
<br />
=== library functions ===<br />
==== modules.isAvailable() ====<br />
{{Nasal doc<br />
|syntax = modules.isAvailable(module_name);<br />
|text = This function returns true, if there is a module (subdirectory) in <code>FGDATA/Nasal/modules/</code> with the given name.<br />
|param1 = module_name<br />
|param1text = The name of the module (in the subdirectory in <code>Nasal/modules</code>) to load.<br />
|example1 = <br />
if (modules.isAvailable("foo_bar")) {<br />
modules.load("foo_bar");<br />
} <br />
}}<br />
<br />
==== modules.setDebug() ====<br />
{{Nasal doc<br />
|syntax = modules.setDebug(module_name, [debug=1]);<br />
|text = This function enables debugging for a module. It must be called '''before''' <code>load()</code>!<br />
|param1 = module_name<br />
|param1text = The name of the module (in the subdirectory in <code>Nasal/modules</code>) to load.<br />
|param2 = debug<br />
|param2text = Defaults to <code>1</code> (true), use <code>0</code> to disable debug. If debug > <code>1</code>, each listener hit will be printed, be careful not to listen to rapidly changing props, do not set <code>runtime=1</code> in <code>setlistener()</code>.<br />
|example1 = <br />
var debug = 1;<br />
modules.setDebug("foo_bar", debug); <br />
}}<br />
<br />
==== modules.load() ====<br />
{{Nasal doc<br />
|syntax = modules.load(module_name, [namespace_name]);<br />
|text = This function attempts to load a module from <code>FGDATA/Nasal/modules/</code><br />
|param1 = module_name<br />
|param1text = The name of the module (in the subdirectory in <code>Nasal/modules</code>) to load.<br />
|param2 = namespace_name<br />
|param2text = Optional, load module to a different namespace.<br />
|example1 = <br />
modules.load("foo_bar"); <br />
}}<br />
<br />
=== Methods of class Module ===<br />
==== setFilePath() ====<br />
{{Nasal doc<br />
|syntax = mymod.setFilePath(path);<br />
|text = Configure where to look for the main file, for example in the aircraft (sub-)directory.<br />
|param1 = path<br />
|param1text = File path where the module is stored.<br />
}}<br />
<br />
==== setMainFile() ====<br />
{{Nasal doc<br />
|syntax = mymod.setMainFile(filename);<br />
|text = Configure the nasal file to load.<br />
|param1 = filename<br />
|param1text = File that will be loaded by <code>load()</code>.<br />
|example1 =<br />
# if you develop a new nasal system for your aircraft, it might be handy to implement it as module<br />
# so you can reload the file quickly without restarting FlightGear<br />
var my_foo_sys = modules.Module.new("my_aircraft_foo");<br />
my_foo_sys.setDebug(1);<br />
my_foo_sys.setFilePath(getprop("/sim/aircraft-dir")~"/Nasal");<br />
my_foo_sys.setMainFile("foo.nas");<br />
my_foo_sys.load();<br />
}}<br />
<br />
==== setNamespace() ====<br />
{{Nasal doc<br />
|syntax = mymod.setNamespace(namespace);<br />
|text = Configure the Nasal namespace to use. Be really careful when using existing namespaces! <code>unload()</code> or <code>reload()</code> will destroy them!<br />
|param1 = namespace<br />
|param1text = The Nasal namespace the module code will be loaded into.<br />
}}<br />
<br />
==== setDebug() ====<br />
{{Nasal doc<br />
|syntax = mymod.setDebug(debug=1);<br />
|text = Activate debugging for this module. '''Must be called before calling <code>load()</code>!'''<br />
|param1 = debug<br />
|param1text = <br />
0: no debugging; <br />
1 (default if no argument given): print calls to redirected <code>setlister()</code> and <code>maketimer()</code>; <br />
<br />
2: listeners print property path when hit (Use with caution! '''Do not call <code>setlistener()</code> with <code>runtime=1</code>'''.)<br />
}}<br />
<br />
==== load() ====<br />
{{Nasal doc<br />
|syntax = mymod.load([args]);<br />
|text = This function attempts to load the module into its namespace.<br />
|param1 = optional args<br />
|param1text = Arguments are passed to the <code>main()</code> function of the module. If empty, the module object will be passed to <code>main()</code>.<br />
}}<br />
<br />
==== unload() ====<br />
{{Nasal doc<br />
|syntax = mymod.unload();<br />
|text = This function attempts to remove tracked resources and remove the module by killing its namespace.<br />
}}<br />
<br />
==== reload() ====<br />
{{Nasal doc<br />
|syntax = mymod.reload();<br />
|text = Shorthand, calls <code>unload()</code> and <code>load()</code>.<br />
}}<br />
<br />
==== get() ====<br />
{{Nasal doc<br />
|syntax = mymod.get(var_name);<br />
|text = Returns a variable from the modules namespace.<br />
|param1 = var_name<br />
|param1text = The variable to get.<br />
|example1 =<br />
var foo = modules.load("foo");<br />
var bar = foo.get("bar"); # get variable "bar" defined in FGDATA/Nasal/modules/foo/main.nas (or a file included by this file)<br />
}}<br />
<br />
== Property tree interface for modules.nas ==<br />
In the property tree there is a subtree <code>/nasal</code> to control modules and get some statistics.<br />
<br />
The properties available depend on the type of module ("load-once" or "reloadable", see [[Nasal Initialization]] for more information on the differences).<br />
<br />
=== Reloadable modules / frameworks ===<br />
Modules handled by <code>modules.nas</code> will have their properties in <code><nowiki>/nasal/modules/<moduleName></nowiki></code> where <code><nowiki><moduleName></nowiki></code> is given by the developer when calling either <code><nowiki>Module.new("<moduleName>")</nowiki></code> or <code><nowiki>modules.load("<moduleName>")</nowiki></code>.<br />
<br />
In the latter case <code><nowiki><moduleName></nowiki></code> specifies the subdirectory <code><nowiki>FGDATA/Nasal/modules/<moduleName></nowiki></code> in which some framework is stored.<br />
<br />
{| class="wikitable"<br />
|-<br />
! property !! type !! content<br />
|-<br />
| <code>loaded</code> || bool || true if module was loaded without errors<br />
|-<br />
| <code>reload</code> || bool || set this to true to trigger reload<br />
|-<br />
| <code>listeners</code> || int || Number of tracked listeners<br />
|-<br />
| <code>listener-hits</code> || int || If debugging is enabled, this prop shows the total number of hits to all tracked listeners.<br />
|-<br />
| <code>timers</code> || int || Number of tracked timers (maketimer).<br />
|}<br />
<br />
=== Legacy load-once modules ===<br />
A legacy load-once module is a direct (1st level) subdirectory of <code>FGDATA/Nasal/</code> and its corresponding property tree is <code><nowiki>/nasal/<moduleName>/</nowiki></code> where <code><nowiki><moduleName></nowiki></code> equals the name of the subdirectory.<br />
<br />
It is handled by C++ code and must have a corresponding entry in <code>FGDATA/defaults.xml</code> which defines a property "enabled" and optionally a property "module".<br />
<br />
If enabled is set to false in <code>defaults.xml</code>, the C++ code will setup a listener and load the module as soon as enabled is set to true.<br />
<br />
The property name "module" is a bit misleading, it is used to define into which namespace the files shall be loaded.<br />
<br />
For each Nasal file in the subdirectory a <code>file[i]</code> property is created holding the full path+filename.<br />
<br />
The bool <code>loaded</code> property shows the status of the module.<br />
<br />
== Existing modules with reload support ==<br />
Stable Nasal frameworks which support reloading can be added to <code>FGDATA/Nasal/modules/<module_name></code>. <br />
This allows an aircraft developer to configure the framework for a specific aircraft and make use of the reload magic while developing the configuration.<br />
<br />
{| class="wikitable"<br />
|-<br />
! Module name !! Desctiption !! time added <br />
|-<br />
| [[Canvas EFIS Framework|<code>canvas_efis</code>]] || framework to manage canvas based EFIS screens || 02/2020<br />
|}<br />
<br />
[[Category:Nasal]]</div>Jsbhttps://wiki.flightgear.org/w/index.php?title=Nasal_library/props&diff=123764Nasal library/props2020-04-16T21:53:42Z<p>Jsb: Add functions new in FG 2020.1</p>
<hr />
<div>{{Nasal Navigation|nocat=1}}<br />
This page contains documentation for the '''<code>props</code> namespace''' in [[Nasal]]. This namespace provides APIs for working with property trees (including the main [[Property Tree]]) via {{API Link|simgear|class|SGPropertyNode}}. The <code>props</code> namespace is sourced from {{fgdata file|Nasal/props.nas}} and {{flightgear file|src/Scripting/nasal-props.cxx}}.<br />
<br />
== Class ==<br />
=== Node ===<br />
{{Nasal doc<br />
|mode = class<br />
|text = The main class, used widely for manipulating property trees.<br />
}}<br />
==== new() ====<br />
{{Nasal doc<br />
|syntax = props.Node.new([values]);<br />
|text = Constructor function. Returns a new <code>props.Node</code> instance.<br />
|param1 = values<br />
|param1text = An optional hash that will be the initial property structure.<br />
|example1 = var node = props.Node.new();<br />
props.dump(node);<br />
|example2 = var tree = {<br />
a: 1,<br />
b: ["a", "b", "c"],<br />
c: {<br />
d: 1 * 4<br />
}<br />
};<br />
<br />
var node = props.Node.new(tree);<br />
props.dump(node);<br />
}}<br />
<br />
==== addChild() ====<br />
{{Nasal doc<br />
|syntax = props.Node.addChild(name[, min_idx[, append]]);<br />
|version = 2.10<br />
|commit = {{fgdata commit|a15d58|t=commit}}<br />
|text = Add a new, blank child to the node. Returns the newly-created node.<br />
|param1 = name<br />
|param1text = The name of the node as a string.<br />
|param2 = min_idx<br />
|param2text = This specifies the minimum index to add the new one to. This takes precedence over '''append'''. Defaults to 0.<br />
|param3 = append<br />
|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).<br />
|example1 = var node = props.Node.new();<br />
node.addChild("a");<br />
props.dump(node);<br />
|example2 = var node = props.Node.new();<br />
node.addChild("a", 1);<br />
props.dump(node); # a[1]<br />
|example3 = var node = props.Node.new();<br />
node.addChild("a", 1);<br />
props.dump(node); # a[1]<br />
node.addChild("a", 0, 0);<br />
props.dump(node); # a[1] and a[0]<br />
|example4 = var node = props.Node.new();<br />
node.addChild("a", 1);<br />
props.dump(node); # a[1]<br />
node.addChild("a", 0, 1);<br />
props.dump(node); # a[1] and a[2]<br />
}}<br />
<br />
==== addChildren() ====<br />
{{Nasal doc<br />
|syntax = props.Node.addChildren(name, count[, min_idx[, append]]);<br />
|version = 2.10<br />
|commit = {{fgdata commit|7f1117|t=commit}}<br />
|text = Adds multiple children with the same name to this node. Returns <code>'''nil</code>.<br />
|param1 = name<br />
|param1text = The name of the nodes as a string.<br />
|param2 = count<br />
|param2text = Number of new children to add.<br />
|param3 = min_idx<br />
|param3text = Performs the same function as in <code>[[#addChild.28.29|Node.addChild()]]</code>.<br />
|param4 = append<br />
|param4text = Performs the same function as in <code>[[#addChild.28.29|Node.addChild()]]</code>.<br />
|example1 = var node = props.Node.new();<br />
node.addChildren("a", 2);<br />
props.dump(node); # a[0] and a[1]<br />
|example2 = var node = props.Node.new();<br />
node.addChildren("a", 2, 1);<br />
props.dump(node); # a[1] and a[2]<br />
|example3 = var node = props.Node.new();<br />
node.addChild("a", 2);<br />
props.dump(node); # a[2]<br />
node.addChildren("a", 2, 0, 0);<br />
props.dump(node); # a[2], a[0], and a[1]<br />
}}<br />
<br />
==== adjustValue() (since FG 2020.1) ====<br />
{{Nasal doc<br />
|syntax = props.Node.adjustValue(delta);<br />
|text = Adds delta (numeric) to current value respecting the node type. <br />
|param1 = delta<br />
|param1text = Numeric value (can be negative) to add to current node value.<br />
}}<br />
<br />
==== alias() ====<br />
{{Nasal doc<br />
|syntax = props.Node.alias(node);<br />
|text = Aliases this node to another one. Returns 1 on success and 0 on failure.<br />
|param1 = node<br />
|param1text = The node to alias to. Can be one of:<br />
* A path to a property in the [[Property Tree]] as a string.<br />
* A <code>prop</code> ghost.<br />
* A <code>props.Node</code> object.<br />
|example1 = var node = props.Node.new();<br />
node.alias("/position/altitude-ft");<br />
props.dump(node); # equals the current altitude<br />
|example2 = var node1 = props.Node.new();<br />
node1.setDoubleValue(2.34);<br />
var node2 = props.Node.new();<br />
node2.alias(node1);<br />
props.dump(node2); # equals 2.34<br />
}}<br />
<br />
==== clearValue() ====<br />
{{Nasal doc<br />
|syntax = props.Node.clearValue();<br />
|text = Clears the value and type of the node.<br />
|example1 = var node = props.Node.new();<br />
node.setDoubleValue(2.34);<br />
props.dump(node); # prints "{DOUBLE} = 2.35"<br />
node.clearValue();<br />
props.dump(node); # prints "{NONE} = nil"<br />
}}<br />
<br />
==== decrement() (since FG 2020.1) ====<br />
{{Nasal doc<br />
|syntax = props.Node.decrement(n = 1);<br />
|text = Decrements integer property by n (default: n = 1)<br />
|param1 = n<br />
|param1text = Value to subtract, will be converted to int, defaults to 1<br />
}}<br />
<br />
==== equals() ====<br />
{{Nasal doc<br />
|syntax = props.Node.equals(node);<br />
|version = 2.12<br />
|commit = {{fgdata commit|d80722|t=commit}}<br />
|text = Checks whether the node refers to the same one as another. Returns 1 (true) if it is, and 0 (false) if otherwise.<br />
|param1 = node<br />
|param1text = Node to check against. May be either a <code>prop</code> ghost or a <code>props.Node</code> object.<br />
|example1 = var n = props.Node.new();<br />
var a = n;<br />
print(a.equals(n)); # prints "1" (true)<br />
}}<br />
<br />
==== getAliasTarget() ====<br />
{{Nasal doc<br />
|syntax = props.Node.getAliasTarget();<br />
|text = Returns the alias target of a node as another <code>props.Node</code> instance.<br />
|example1 = setprop("/test", 2.35);<br />
var node = props.Node.new();<br />
node.alias("/test");<br />
var tgt = node.getAliasTarget();<br />
print(tgt.getPath()); # prints "/test"<br />
}}<br />
<br />
==== getAttribute() ====<br />
{{Nasal doc<br />
|syntax = props.Node.getAttribute([rel_path, ]name);<br />
props.Node.getAttribute();<br />
|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<br />
{{{!}} class="wikitable"<br />
! String !! Return value<br />
{{!-}}<br />
{{!}} last {{!!}} The highest used attribute code (should be 128). See for {{simgear file|simgear/props/props.hxx|l=767}} the codes.<br />
{{!-}}<br />
{{!}} children {{!!}} Number of child nodes.<br />
{{!-}}<br />
{{!}} listeners {{!!}} Number of listeners connected to this node.<br />
{{!-}}<br />
{{!}} references {{!!}} Number of times the node has previously been referenced.<br />
{{!-}}<br />
{{!}} tied {{!!}} Whether the node is tied.<br />
{{!-}}<br />
{{!}} alias {{!!}} Whether the node is aliased.<br />
{{!-}}<br />
{{!}} readable {{!!}} Whether the node can be read.<br />
{{!-}}<br />
{{!}} writable {{!!}} Whether the node can be written to.<br />
{{!-}}<br />
{{!}} archive {{!!}} Whether the node will be saved when the "save" [[fgcommands|fgcommand]] is triggered.<br />
{{!-}}<br />
{{!}} trace-read {{!!}} Whether the reading of the node will be logged when <code>--log-level=info</code>.<br />
{{!-}}<br />
{{!}} trace-write {{!!}} Whether the writing to the node will be logged when <code>--log-level=info</code>.<br />
{{!-}}<br />
{{!}} userarchive {{!!}} Whether the node will be saved to the [[FlightGear configuration via XML#autosave.xml|autosave file]] (only works for actual properties).<br />
{{!-}}<br />
{{!}} preserve {{!!}} Whether the value of node will be preserved during resets (only works for actual properties).<br />
{{!}}}<br />
|param1 = rel_path<br />
|param1text = Optional relative path as a string.<br />
|param2 = name<br />
|param2text = Attribute as a string. See the above table for a full list.<br />
|example1 = var node = props.Node.new();<br />
var child = node.addChild("a");<br />
print(node.getAttribute("children")); # prints "1"<br />
|example2text = Example using relative path<br />
|example2 = var node = props.Node.new();<br />
var child = node.addChild("a");<br />
print(node.getAttribute("a", "readable")); # prints "1" (node can be read from)<br />
|example3 = var node = props.Node.new();<br />
var node2 = props.Node.new();<br />
node2.alias(node);<br />
print(node2.getAttribute("alias")); # prints "1" (true)<br />
|example4 = print(props.globals.getNode("/sim/signals/fdm-initialized").getAttribute("listeners")); # prints the number of listeners<br />
|example5 = print(props.globals.getNode("/sim/time/elapsed-sec").getAttribute("tied")); # prints "1" (true), meaning it is tied<br />
|example6 = var node = props.Node.new();<br />
print(node.getAttribute("writable")); # prints "1" (true), meaning the node can be written to<br />
|example7text = Example using no arguments<br />
|example7 = var node = props.Node.new();<br />
print(node.getAttribute()); # prints "3" (true), meaning the node can be read from (1) and written to (2)<br />
}}<br />
<br />
==== getBoolValue() ====<br />
{{Nasal doc<br />
|syntax = props.Node.getBoolValue();<br />
|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 <code>"false"</code>, false will be returned. Otherwise, true will be returned. Remember that boolean values are represented in Nasal as 1 (true) and 0 (false).<br />
|example1 = var node = props.Node.new();<br />
node.setBoolValue(1);<br />
print(node.getBoolValue() ? "true" : "false"); # prints "true"<br />
node.setBoolValue(0);<br />
print(node.getBoolValue() ? "true" : "false"); # prints "false"<br />
|example2 = var node = props.Node.new();<br />
node.setDoubleValue(1.23);<br />
print(node.getBoolValue() ? "true" : "false"); # prints "true"<br />
node.setDoubleValue(-1.23);<br />
print(node.getBoolValue() ? "true" : "false"); # prints "true"<br />
node.setDoubleValue(0.0);<br />
print(node.getBoolValue() ? "true" : "false"); # prints "false"<br />
|example3 = var node = props.Node.new();<br />
node.setIntValue(2);<br />
print(node.getBoolValue() ? "true" : "false"); # prints "true"<br />
node.setIntValue(-2);<br />
print(node.getBoolValue() ? "true" : "false"); # prints "true"<br />
node.setIntValue(0);<br />
print(node.getBoolValue() ? "true" : "false"); # prints "false"<br />
|example4 = var node = props.Node.new();<br />
node.setValue("Hello, World!");<br />
print(node.getBoolValue() ? "true" : "false"); # prints "true"<br />
node.setValue("false");<br />
print(node.getBoolValue() ? "true" : "false"); # prints "false"<br />
}}<br />
<br />
==== getChild() ====<br />
{{Nasal doc<br />
|syntax = props.Node.getChild(rel_path[, idx[, create]]);<br />
|text = Returns a child of a node as another <code>props.Node</code> instance.<br />
|param1 = rel_path<br />
|param1text = Relative path to the child node as a string.<br />
|param2 = idx<br />
|param2text = Optional index for the child node as an integer.<br />
|param3 = create<br />
|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 <code>'''nil'''</code> if no child exists. Defaults to 0.<br />
|example1 = var node = props.Node.new();<br />
node.addChild("a");<br />
var c = node.getChild("a");<br />
|example2 = var node = props.Node.new();<br />
node.addChildren("a", 3);<br />
node.getNode("a[1]").setDoubleValue(2.35);<br />
var c = node.getChild("a", 1);<br />
print(c.getValue()); # prints "2.35"<br />
|example3 = var node = props.Node.new();<br />
var c = node.getChild("a", 1, 1);<br />
props.dump(node); # new child a[1] will have appeared<br />
}}<br />
<br />
==== getChildren() ====<br />
{{Nasal doc<br />
|syntax = props.Node.getChildren([name]);<br />
|text = Returns a vector of child nodes, optionally those with a certain name, as <code>props.Node</code> instances.<br />
|param1 = name<br />
|param1text = Optional name of the child nodes as a string. If not given, all children will be returned.<br />
|example1 = var node = props.Node.new();<br />
node.addChildren("a", 3);<br />
node.addChildren("b", 3);<br />
debug.dump(node.getChildren()); # all child nodes in the vector<br />
|example2 = var node = props.Node.new();<br />
node.addChildren("a", 3);<br />
node.addChildren("b", 3);<br />
debug.dump(node.getChildren("b")); # only children with the name "b" in the vector<br />
}}<br />
<br />
==== getIndex() ====<br />
{{Nasal doc<br />
|syntax = props.Node.getIndex();<br />
|text = Returns the index of a node as an integer.<br />
|example1 = var node = props.Node.new();<br />
node.addChildren("a", 3);<br />
print(node.getChild("a", 1).getIndex()); # prints "1"<br />
|example2 = var node = props.Node.new();<br />
node.addChild("b");<br />
print(node.getChild("b").getIndex()); # prints "0"<br />
}}<br />
<br />
==== getName() ====<br />
{{Nasal doc<br />
|syntax = props.Node.getName();<br />
|text = Returns the name of the node as a string.<br />
|example1 = var node = props.Node.new();<br />
var c = node.addChild("a");<br />
debug.dump(c.getName()); # prints "a"<br />
|example2 = var node = props.Node.new();<br />
node.addChildren("a", 3);<br />
debug.dump(node.getChild("a", 2).getName()); # prints "a"<br />
}}<br />
<br />
==== getNode() ====<br />
{{Nasal doc<br />
|syntax = props.Node.getNode(rel_path[, create]);<br />
|text = Returns a subnode as another <code>props.Node</code> instance.<br />
|param1 = rel_path<br />
|param1text = Relative path to the subnode as a string.<br />
|param2 = create<br />
|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 <code>'''nil'''</code>. Default to 0 (false).<br />
|example1 = var tree = {<br />
"a": 1,<br />
"b": "Hi",<br />
"c": {<br />
"d": "Hello, World!"<br />
}<br />
};<br />
var node = props.Node.new(tree);<br />
print(node.getNode("c/d").getValue()); # prints "Hello, World!"<br />
|example2 = var tree = {<br />
"a": 1,<br />
"b": "Hi",<br />
"c": {}<br />
};<br />
var node = props.Node.new(tree);<br />
node.getNode("c/d", 1).setDoubleValue(2.35);<br />
props.dump(node); # c/d now exists<br />
|example3 = var ac = props.globals.getNode("sim/aircraft");<br />
print("Current aircraft is: ", ac.getValue());<br />
}}<br />
<br />
==== getParent() ====<br />
{{Nasal doc<br />
|syntax = props.Node.getParent();<br />
|text = Returns the parent of a node, or <code>'''nil'''</code> if there is no parent.<br />
|example1 = var tree = {<br />
"a": 1,<br />
"b": "Hi",<br />
"c": {<br />
"d": "Hello, World!"<br />
}<br />
};<br />
var node = props.Node.new(tree);<br />
props.dump(node.getNode("c/d").getParent()); # dumps "c"<br />
|example2 = var node = props.Node.new();<br />
debug.dump(node.getParent()); # prints nil<br />
}}<br />
<br />
==== getPath() ====<br />
{{Nasal doc<br />
|syntax = props.Node.getPath();<br />
|text = Returns the path of the node as a string.<br />
|example1 = var tree = {<br />
"a": 1,<br />
"b": "Hi",<br />
"c": {<br />
"d": "Hello, World!"<br />
}<br />
};<br />
var node = props.Node.new(tree);<br />
print(node.getNode("c/d").getPath()); # prints "/c/d"<br />
}}<br />
<br />
==== getType() ====<br />
{{Nasal doc<br />
|syntax = props.Node.getType();<br />
|text = Returns node's type as a string. It should be one of "NONE", "ALIAS", "BOOL", "INT", "LONG" (long integer), "FLOAT", "DOUBLE", "STRING", "VEC3D", "VEC4D", "UNSPECIFIED".<br />
|example1 = var node = props.Node.new();<br />
print(node.getType()); # prints "NONE"<br />
|example2 = var node = props.Node.new();<br />
node.setIntValue(12);<br />
print(node.getType()); # prints "INT"<br />
|example3 = var node = props.Node.new();<br />
node.setValue([0.4, 0.2, 1]);<br />
debug.dump(node.getValue()); # prints "[0.4, 0.2, 1]"<br />
print(node.getType()); # prints "VEC3D"<br />
}}<br />
<br />
==== getValue() ====<br />
{{Nasal doc<br />
|syntax = props.Node.getValue([rel_path]);<br />
|text = {{hatnote|See also {{func link|getBoolValue()|page=this}}.}}<br />
<br />
Returns the value of the node.<br />
|param1 = rel_path<br />
|param1text = Optional relative path to a subnode as a string.<br />
|example1 = var node = props.Node.new();<br />
node.setDoubleValue(2.35);<br />
print(node.getValue()); # prints "2.35"<br />
|example2 = var node = props.Node.new();<br />
node.setValue("Hi");<br />
print(node.getValue()); # prints "Hi"<br />
|example3 = var node = props.Node.new();<br />
node.addChild("a").setValue("Hi");<br />
print(node.getValue("a")); # prints "Hi"<br />
|example4 = var node = props.Node.new();<br />
node.setValue([0, 0.5, 1]);<br />
debug.dump(node.getValue()); # prints "[0, 0.5, 1]"<br />
}}<br />
<br />
==== getValues() ====<br />
{{Nasal doc<br />
|syntax = props.Node.getValues();<br />
|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).<br />
|example1 = var tree = {<br />
"string": "Hi",<br />
"number": 1.2,<br />
"subnode": {<br />
"idx-node": [1, 2, 3]<br />
}<br />
};<br />
var node = props.Node.new(tree);<br />
node.addChild("bool").setBoolValue(1);<br />
props.dump(node); # dump to node tree<br />
debug.dump(node.getValues()); # dump the node converted to hash<br />
|example2 = var tree = {<br />
"a": [1, 2, 3]<br />
};<br />
var node = props.Node.new(tree);<br />
props.dump(node); # a[0] = 1, a[1] = 2, a[2] = 3<br />
debug.dump(node.getValues()); # a: [1, 2, 3]<br />
}}<br />
<br />
==== increment() (since FG 2020.1) ====<br />
{{Nasal doc<br />
|syntax = props.Node.increment(n = 1);<br />
|text = Increments integer property by n (default: n = 1)<br />
|param1 = n<br />
|param1text = Value to add, will be converted to int, defaults to 1<br />
}}<br />
<br />
==== initNode() ====<br />
{{Nasal doc<br />
|syntax = props.Node.initNode([path[, value[, type[, force]]]]);<br />
|text = Initializes a node if it doesn't exist and returns that node as a <code>props.Node</code> object.<br />
|param1 = path<br />
|param1text = Optional path to a subnode as a string. If not given, the node itself will be initialized.<br />
|param2 = value<br />
|param2text = Optional default value to initialize the node with.<br />
|param3 = type<br />
|param3text = Optional string that will set the type of the node. Must be one of <code>"DOUBLE"</code>, <code>"INT"</code>, <code>"BOOL"</code>, or string <code>"STRING"</code>.<br />
|param4 = force<br />
|param4text = If set to 1 (true), the node's type will be forced to change.<br />
|example1 = var node = props.Node.new();<br />
var a = node.initNode("a");<br />
props.dump(a);<br />
|example2 = var node = props.Node.new();<br />
var a = node.initNode("a", "Hi");<br />
props.dump(a);<br />
|example3 = var node = props.Node.new();<br />
var a = node.initNode("a", 1.25, "INT");<br />
props.dump(a); # a = 1<br />
|example4 = var node = props.Node.new();<br />
node.addChild("a").setBoolValue(0);<br />
props.dump(node.getChild("a")); # a = 0 (type: bool)<br />
var a = node.initNode("a", 1.25, "INT", 1);<br />
props.dump(a); # a = 0 (type: int)<br />
}}<br />
<br />
==== isInt() (since FG 2020.1) ====<br />
{{Nasal doc<br />
|syntax = props.Node.isInt();<br />
|text = Returns true (1) if node '''type''' is "INT" or "LONG" (long integer) otherwise false (0).<br />
}}<br />
<br />
==== isNumeric() (since FG 2020.1) ====<br />
{{Nasal doc<br />
|syntax = props.Node.isNumeric();<br />
|text = Returns true (1) if node '''type''' is "INT", "LONG", "FLOAT" or "DOUBLE" otherwise false (0).<br />
}}<br />
<br />
==== remove() ====<br />
{{Nasal doc<br />
|syntax = props.Node.remove();<br />
|text = Removes the node and returns the removed node.<br />
|example1 = var node = props.Node.new();<br />
node.addChild("a");<br />
props.dump(node);<br />
node.getChild("a").remove();<br />
props.dump(node); # child "a" does not exist anymore<br />
}}<br />
<br />
==== removeAllChildren() ====<br />
{{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]]}}<br />
{{Nasal doc<br />
|syntax = props.Node.removeAllChildren();<br />
|version = 3.2<br />
|commit = {{fgdata commit|4766ed|t=commit}}<br />
|text = Removes all child nodes and returns the node.<br />
|example1 = var tree = {<br />
"a": 1,<br />
"b": 2,<br />
"c": 3<br />
};<br />
var node = props.Node.new(tree);<br />
props.dump(node);<br />
node.removeAllChildren();<br />
props.dump(node); # all children have been removed<br />
}}<br />
<br />
==== removeChild() ====<br />
{{Nasal doc<br />
|syntax = props.Node.removeChild(rel_path, idx);<br />
|text = Removes a given child node child nodes and returns the node.<br />
|param1 = rel_path<br />
|param1text = Relative path to a subnode as a string.<br />
|param2 = idx<br />
|param2text = Index of the subnode to remove as an integer.<br />
|example1 = var node = props.Node.new();<br />
node.addChild("a");<br />
props.dump(node);<br />
node.removeChild("a", 0);<br />
props.dump(node); # child "a" has been removed<br />
|example2 = var node = props.Node.new();<br />
node.addChildren("a", 2);<br />
props.dump(node);<br />
node.removeChild("a", 0);<br />
props.dump(node); # just a[1] remains<br />
}}<br />
<br />
==== removeChildren() ====<br />
{{Nasal doc<br />
|syntax = props.Node.removeChildren([name]);<br />
|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}}).<br />
|param1 = name<br />
|param1text = Optional name of children to remove as a string.<br />
|example1 = var node = props.Node.new();<br />
node.addChildren("a", 2);<br />
node.addChildren("b", 2);<br />
props.dump(node);<br />
node.removeChildren("a");<br />
props.dump(node); # just children named "b" remain<br />
|example2 = var node = props.Node.new();<br />
node.addChildren("a", 2);<br />
node.addChildren("b", 2);<br />
props.dump(node);<br />
node.removeChildren();<br />
props.dump(node); # all children removed<br />
}}<br />
<br />
==== setAttribute() ====<br />
{{Nasal doc<br />
|syntax = props.Node.setAttribute([rel_path, ]attr, value);<br />
props.Node.setAttribute(attrs);<br />
|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.<br />
{{{!}} class="wikitable"<br />
! String !! Description<br />
{{!-}}<br />
{{!}} readable {{!!}} Whether the node can be read.<br />
{{!-}}<br />
{{!}} writable {{!!}} Whether the node can be written to.<br />
{{!-}}<br />
{{!}} archive {{!!}} Whether the node will be saved when the "save" [[fgcommands|fgcommand]] is triggered.<br />
{{!-}}<br />
{{!}} trace-read {{!!}} Whether the reading of the node will be logged when <code>--log-level=info</code>.<br />
{{!-}}<br />
{{!}} trace-write {{!!}} Whether the writing to the node will be logged when <code>--log-level=info</code>.<br />
{{!-}}<br />
{{!}} userarchive {{!!}} Whether the node will be saved to the [[FlightGear configuration via XML#autosave.xml|autosave file]] (only works for actual properties).<br />
{{!-}}<br />
{{!}} preserve {{!!}} Whether the value of node will be preserved during resets (only works for actual properties).<br />
{{!}}}<br />
|param1 = rel_path<br />
|param1text = Optional relative path as a string.<br />
|param2 = attr<br />
|param2text = Name of attribute to set as a string. See above.<br />
|param3 = value<br />
|param3text = Boolean value to set the property to.<br />
|param4 = attrs<br />
|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.<br />
|example1 = var node = props.Node.new();<br />
node.setAttribute("trace-write", 1);<br />
node.setIntValue(12); # will be traced<br />
|example2 = var node = props.Node.new();<br />
node.setAttribute("readable", 0);<br />
var val = node.getValue();<br />
debug.dump(val); # prints "nil"<br />
|example3 = var node = props.Node.new();<br />
node.addChild("a");<br />
node.setAttribute("a", "trace-write", 1);<br />
node.getChild("a").setIntValue(12); # will be traced<br />
|example4 = var node = props.Node.new();<br />
node.setAttribute(35); # read + write + trace-write<br />
node.setIntValue(12); # will be traced<br />
}}<br />
<br />
==== setBoolValue() ====<br />
{{Nasal doc<br />
|syntax = props.Node.setBoolValue([rel_path, ]value);<br />
|text = {{note|For setting the values of [[Property Tree]] nodes, it is recommended to use {{func link|setprop()}} if possible, due to performance differences.}}<br />
<br />
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 "true" or "false". Returns 1 (true) if the operation was successful.<br />
|param1 = rel_path<br />
|param1text = Optional relative path as a string.<br />
|param2 = value<br />
|param2text = Value to set the node to, will be interpreted into a boolean. If it is <code>'''nil'''</code>, 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.<br />
|example1 = var node = props.Node.new();<br />
node.setBoolValue(nil);<br />
props.dump(node); # node = 0 (false)<br />
|example2 = var node = props.Node.new();<br />
node.setBoolValue("Hi");<br />
props.dump(node); # node = 1 (true)<br />
|example3 = var node = props.Node.new();<br />
node.setBoolValue(0);<br />
props.dump(node); # node = 0 (false)<br />
node.setBoolValue(1.25);<br />
props.dump(node); # node = 1 (true)<br />
|example4 = var node = props.Node.new();<br />
node.setValue("String");<br />
props.dump(node); # node = "String" (type: string)<br />
node.setBoolValue(1);<br />
props.dump(node); # node = "true"<br />
|example5 = var node = props.Node.new();<br />
node.setDoubleValue(12.32);<br />
props.dump(node); # node = 12.32 (type: double)<br />
node.setBoolValue(1);<br />
props.dump(node); # node = 1<br />
|example6 = var node = props.Node.new();<br />
node.addChild("a");<br />
node.setBoolValue("a", 1);<br />
props.dump(node); # /a = 1<br />
}}<br />
<br />
==== setDoubleValue() ====<br />
{{Nasal doc<br />
|syntax = props.Node.setDoubleValue([rel_path, ]value);<br />
|text = {{note|For setting the values of [[Property Tree]] nodes, it is recommended to use {{func link|setprop()}} if possible, due to performance differences.}}<br />
<br />
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.<br />
|param1 = rel_path<br />
|param1text = Optional relative path as a string.<br />
|param2 = value<br />
|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.<br />
|example1 = var node = props.Node.new();<br />
node.setDoubleValue(1.1);<br />
props.dump(node); # node = 1.1 (type: double)<br />
|example2 = var node = props.Node.new();<br />
node.setDoubleValue("1.1");<br />
props.dump(node); # node = 1.1 (type: double)<br />
|example3 = var node = props.Node.new();<br />
node.setBoolValue(1);<br />
node.setDoubleValue("1.1");<br />
props.dump(node); # node = 1 (type: bool)<br />
node.setDoubleValue(0.0);<br />
props.dump(node); # node = 0 (type: bool)<br />
|example4 = var node = props.Node.new();<br />
node.setIntValue(1);<br />
node.setDoubleValue(1.1);<br />
props.dump(node); # node = 1 (type: int)<br />
node.setDoubleValue("-1.1");<br />
props.dump(node); # node = -1 (type: int)<br />
|example5 = var node = props.Node.new();<br />
node.addChild("a");<br />
node.setDoubleValue("a", 12.2);<br />
props.dump(node); # /a = 12.2<br />
}}<br />
<br />
==== setIntValue() ====<br />
{{Nasal doc<br />
|syntax = props.Node.setIntValue([rel_path, ]value);<br />
|text = {{note|For setting the values of [[Property Tree]] nodes, it is recommended to use {{func link|setprop()}} if possible, due to performance differences.}}<br />
<br />
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.<br />
|param1 = rel_path<br />
|param1text = Optional relative path as a string.<br />
|param2 = value<br />
|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.<br />
|example1 = var node = props.Node.new();<br />
node.setIntValue(12);<br />
props.dump(node); # node = 12<br />
node.setIntValue("6");<br />
props.dump(node); # node = 6<br />
|example2 = var node = props.Node.new();<br />
node.setIntValue(12.2);<br />
props.dump(node); # node = 12<br />
node.setIntValue(-12.2);<br />
props.dump(node); # node = 12<br />
|example3 = var node = props.Node.new();<br />
node.setBoolValue(1);<br />
node.setIntValue(12.5);<br />
props.dump(node); # node = 1 (type: bool)<br />
node.setIntValue(0);<br />
props.dump(node); # node = 0 (type: bool)<br />
|example4 = var node = props.Node.new();<br />
node.setValue("Hi");<br />
node.setIntValue(12);<br />
props.dump(node); # node = "12" (type: string)<br />
|example5 = var node = props.Node.new();<br />
node.addChild("a");<br />
node.setIntValue("a", 12);<br />
props.dump(node); # /a = 12<br />
}}<br />
<br />
==== setValue() ====<br />
{{Nasal doc<br />
|syntax = props.Node.setValue([rel_path, ]value);<br />
|text = {{hatnote|See also {{func link|setBoolValue()|page=this}}, {{func link|setDoubleValue()|page=this}}, and {{func link|setIntValue()|page=this}}.}}<br />
<br />
{{note|For setting the values of [[Property Tree]] nodes, it is recommended to use {{func link|setprop()}} if possible, due to performance differences.}}<br />
<br />
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.<br />
<br />
{{{!}} class="wikitable"<br />
{{!}} colspan="2" style="text-align:right" {{!}} '''value''' type → <br />
! rowspan="2" {{!}} Number<br />
! rowspan="2" {{!}} String <br />
! rowspan="2" {{!}} Vector<br />
{{!-}}<br />
{{!}} style="text-align:left" {{!}} Current node<br>type ↓<br />
{{!}} style="text-align:right" {{!}} Result ↘<br />
{{!-}}<br />
! colspan="2" {{!}} None/unspecified<br />
{{!}}<br />
* Type set to <code>double</code><br />
* Node set to '''value'''.<br />
{{!}}<br />
* Type set to <code>string</code><br />
* Node set to '''value'''.<br />
{{!}}<br />
* Type set to <code>vec*d</code><br />
* Node set to '''value'''.<br />
{{!-}}<br />
! colspan="2" {{!}} Bool<br />
{{!}}<br />
* If '''value''' != 0, node set to true.<br />
* If '''value''' == 0, node set to false.<br />
{{!}}<br />
* If '''value''' == "true", node set to true.<br />
* If '''value''' can be converted to an integer,<br>and != 0, node set to true.<br />
{{!}} Node set to true.<br />
{{!-}}<br />
! colspan="2" {{!}} Integer<br />
{{!}} Node set to truncated '''value'''<br />
{{!}} Node set to '''value ''' converted and truncated to an integer.<br />
{{!}} Node set to 1.<br />
{{!-}}<br />
! colspan="2" {{!}} Double<br />
{{!}} Node set to '''value'''.<br />
{{!}} Node set to '''value''' converted to number.<br />
{{!}} Throws an error (vector is not a number).<br />
{{!-}}<br />
! colspan="2" {{!}} String<br />
{{!}} Node set to '''value''' converted to string. {{!!}} Node set to '''value'''. {{!!}} Node not set.<br />
{{!}}}<br />
|param1 = rel_path<br />
|param1text = Optional relative path as a string.<br />
|param2 = value<br />
|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.<br />
|example1 = var node = props.Node.new();<br />
node.setValue("Hi");<br />
props.dump(node); # node = "Hi"<br />
|example2 = var node = props.Node.new();<br />
node.addChild("a");<br />
node.setValue("a", "Hi");<br />
props.dump(node); # \a = "Hi"<br />
|example3 = var node = props.Node.new();<br />
node.setValue([0.4, 0.2, 1]);<br />
debug.dump(node.getValue()); # prints "[0.4, 0.2, 1]"<br />
print(node.getType()); # prints "VEC3D"<br />
}}<br />
<br />
==== setValues() ====<br />
{{Nasal doc<br />
|syntax = props.Node.setValues(val);<br />
|text = {{hatnote|See also {{func link|getValues()|page=this}}.}}<br />
<br />
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).<br />
|param1 = val<br />
|param1text = A hash that will become the property tree.<br />
|example1 = var val = {<br />
"a": 100 # "a" will become the subnode's name, and 100 its value<br />
};<br />
var node = props.Node.new();<br />
node.setValues(val);<br />
props.dump(node); # dump tree<br />
|example2 = var val = {<br />
"a": 1,<br />
"b": "Hi",<br />
"c": {<br />
"d": [1, 2, 3]<br />
}<br />
};<br />
var node = props.Node.new();<br />
node.setValues(val);<br />
props.dump(node); # dump tree<br />
}}<br />
<br />
==== unalias() ====<br />
{{Nasal doc<br />
|syntax = props.Node.unalias();<br />
|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).<br />
|example2 = var node1 = props.Node.new();<br />
node1.setDoubleValue(2.35);<br />
var node2 = props.Node.new();<br />
node2.alias(node1);<br />
<br />
props.dump(node2); # equals 2.35<br />
node2.unalias();<br />
props.dump(node2); # no value or type<br />
}}<br />
<br />
== Functions ==<br />
=== compileCondition() ===<br />
{{see also|Conditions}}<br />
<br />
{{Nasal doc<br />
|syntax = props.compileCondition(node);<br />
|version = 3.2<br />
|commit = {{fgdata commit|43f8ce|t=commit}}<br />
|text = Compiles a [[conditions|condition]] property branch and returns a <code>Condition</code> ghost object or <code>'''nil'''</code> on error. This ghost will contain a <code>test()</code> function that will return the result of the condition as either 1 (true) or 0 (false).<br />
|param1 = node<br />
|param1text = Either a props.Node containing the condition, or a string specifying a place in the [[Property Tree]] where there is a condition branch.<br />
|example1 = var tree = {<br />
"equals": {<br />
"property": '/test',<br />
"value": 12<br />
}<br />
};<br />
var node = props.Node.new(tree);<br />
setprop("/test", 12);<br />
<br />
var cond = props.compileCondition(node);<br />
print(cond.test()); # prints "1" (true)<br />
setprop("/test", 15);<br />
print(cond.test()); # prints "0" (false)<br />
|example2 = var tree = {<br />
"equals": {<br />
"property": '/test',<br />
"value": 12<br />
}<br />
};<br />
props.globals.getNode("test2/condition", 1).setValues(tree); # place it in the Property Tree<br />
setprop("/test", 12);<br />
<br />
var cond = props.compileCondition(node);<br />
print(cond.test()); # prints "1" (true)<br />
setprop("/test", 15);<br />
print(cond.test()); # prints "0" (false)<br />
}}<br />
<br />
=== condition() ===<br />
{{Nasal doc<br />
|syntax = props.condition(node);<br />
|text = Evaluates a [[conditions|condition]] property branch and returns the result as a boolean.<br />
|param1 = node<br />
|param1text = Either a props.Node containing the condition, or a string specifying a place in the [[Property Tree]] where there is a condition branch.<br />
|example1 = var tree = {<br />
"equals": {<br />
"property": '/test',<br />
"value": 12<br />
}<br />
};<br />
var node = props.Node.new(tree);<br />
setprop("/test", 12);<br />
<br />
print(props.condition(node)); # prints "1" (true)<br />
setprop("/test", 15);<br />
print(props.condition(node)); # prints "0" (false)<br />
|example2 = var tree = {<br />
"equals": {<br />
"property": '/test',<br />
"value": 12<br />
}<br />
};<br />
props.globals.getNode("test2/condition", 1).setValues(tree); # place it in the Property Tree<br />
setprop("/test", 12);<br />
<br />
print(props.condition(node)); # prints "1" (true)<br />
setprop("/test", 15);<br />
print(props.condition(node)); # prints "0" (false)<br />
}}<br />
<br />
=== copy() ===<br />
{{Nasal doc<br />
|syntax = props.copy(src, dest[, attr]);<br />
|text = Copies the property tree of the source into the destination node. Note that aliased properties will not be copied.<br />
|param1 = src<br />
|param1text = Source <code>props.Node object</code> to copy from.<br />
|param2 = dest<br />
|param2text = Destination <code>props.Node object</code> to copy to.<br />
|param3 = attr<br />
|param3text = If set to 1 (true), attributes will also be copied. Defaults to 0 (false).<br />
|example1 = var tree = {<br />
"a": 1.5,<br />
"b": "Hi",<br />
"c": {<br />
"d": [1, 2, 3]<br />
}<br />
};<br />
var src = props.Node.new(tree);<br />
var dest = props.Node.new();<br />
props.copy(src, dest);<br />
props.dump(dest);<br />
|example2 = var src = props.Node.new();<br />
var a = src.addChild("a");<br />
a.setAttribute("trace-write", 1);<br />
a.setIntValue(12);<br />
var dest = props.Node.new();<br />
props.copy(src, dest, 1);<br />
print(dest.getNode("a").getAttribute("trace-write")); # prints "1" (true)<br />
}}<br />
<br />
=== dump() ===<br />
{{Nasal doc<br />
|syntax = props.dump(node);<br />
|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.<br />
|param1 = node<br />
|param1text = Node to dump.<br />
|example1 = var node = var tree = {<br />
"a": 12,<br />
"b": "Hi",<br />
"c": {<br />
"d": [1, 2, 3]<br />
}<br />
};<br />
var node = props.Node.new(tree);<br />
props.dump(node); # dump into console<br />
|example2 = # Dump the entire Property Tree<br />
# Warning! This is an intensive operation!<br />
props.dump(props.globals);<br />
}}<br />
<br />
=== getNode() ===<br />
{{Nasal doc<br />
|syntax = props.getNode();<br />
|version = 3.2<br />
|commit = {{fgdata commit|807062|t=commit}}<br />
|text = Shortcut for <syntaxhighlight lang="nasal" inline>props.globals.getNode()</syntaxhighlight>. See {{func link|getNode()||Node|page=this}} for full documentation.<br />
|example1 = print("Current aircraft is: ", props.getNode("/sim/aircraft").getValue());<br />
}}<br />
<br />
=== nodeList() ===<br />
{{Nasal doc<br />
|syntax = props.nodeList(arg[, arg[, ...]]);<br />
|text = Converts its arguments into a vector of node objects if possible and returns that vector. <br />
|param1 = arg<br />
|param1text = Object to operate on. Must be a node object, string, vector, hash, function, or <code>prop</code> 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.<br />
|example1 = var node = props.Node.new();<br />
var f = func(){<br />
var n = props.Node.new();<br />
return n._g;<br />
}<br />
var list = props.nodeList(node,<br />
"/sim/aircraft",<br />
["/sim/fg-root"],<br />
{ "path": "/sim/fg-home" },<br />
f<br />
);<br />
debug.dump(list); # dump list<br />
|example2 = var root = "/sim/version/";<br />
var info = [<br />
root ~ "build-id",<br />
root ~ "build-number",<br />
root ~ "flightgear",<br />
root ~ "hla-support",<br />
root ~ "openscenegraph",<br />
root ~ "revision",<br />
root ~ "simgear"<br />
];<br />
info = props.nodeList(info); # turn into list of nodes<br />
foreach(var n; info){<br />
print(n.getValue()); # dump info<br />
}<br />
}}<br />
<br />
=== runBinding() ===<br />
{{Nasal doc<br />
|syntax = props.runBinding(node[, module]);<br />
|text = Runs a [[Bindings|binding]] element in a node object. Returns 1 (true) on success and 0 (false) on failure.<br />
|param1 = node<br />
|param1text = A {{tag|binding}} element as a node object.<br />
|param2 = module<br />
|param2text = Optional string specifying a module to run Nasal scripts in if the command is <code>nasal</code>. This argument will not override any {{tag|module}} element in the '''node'''<br />
|example1 = var tree = {<br />
"command": "dialog-show",<br />
"dialog-name": "map" # open map<br />
};<br />
var binding = props.Node.new(tree);<br />
props.runBinding(binding);<br />
|example2 = var tree = {<br />
"command": "nasal",<br />
"script": 'print(pi)' # prints value of math.pi<br />
};<br />
var binding = props.Node.new(tree);<br />
props.runBinding(binding, "math");<br />
|example3 = var tree = {<br />
"command": "nasal",<br />
"script": 'print(pi)', # prints value of math.pi<br />
"module": "math" # this is used<br />
};<br />
var binding = props.Node.new(tree);<br />
props.runBinding(binding, "debug");<br />
}}<br />
<br />
=== setAll() ===<br />
{{Nasal doc<br />
|syntax = props.setAll(base, child, value);<br />
|text = Sets indexed subnodes in the Property Tree with the same name to the same value.<br />
|param1 = base<br />
|param1text = Base path to the nodes.<br />
|param2 = child<br />
|param2text = Path to child nodes.<br />
|param3 = value<br />
|param3text = Value to set the subnodes to.<br />
|example1 = # apply 50% throttle to all engines<br />
props.setAll("/controls/engines/engine", "throttle", 0.5);<br />
|example2 = var nodes = props.globals.addChildren("/test", 3);<br />
foreach(var node; nodes){<br />
node.addChild("a");<br />
}<br />
props.setAll("/test", "a", "Hi"); # set all children (test[*]/a) to "Hi"<br />
}}<br />
<br />
=== wrap() ===<br />
{{Nasal doc<br />
|syntax = props.wrap(node);<br />
|text = Turns <code>prop</code> ghosts, either in a vector or single, into <code>props.Node</code> objects.<br />
|param1 = node<br />
|param1text = <code>prop</code> ghost or vector of such ghosts.<br />
|example1 = var ghost = canvas._newCanvasGhost();<br />
var node = props.wrap(ghost._node_ghost);<br />
props.dump(node);<br />
|example2 = var vector = [canvas._newCanvasGhost()._node_ghost, props.Node.new()._g];<br />
var nodes = props.wrap(vector);<br />
foreach(var node; nodes){<br />
props.dump(node);<br />
print("----");<br />
}<br />
}}<br />
<br />
=== wrapNode() ===<br />
{{Nasal doc<br />
|syntax = props.wrapNode(node);<br />
|text = Turns a <code>prop</code> ghost into a <code>props.Node</code> object.<br />
|param1 = node<br />
|param1text = <code>prop</code> ghost to convert.<br />
|example1 = var ghost = canvas._newCanvasGhost();<br />
var node = props.wrapNode(ghost._node_ghost);<br />
props.dump(node);<br />
}}<br />
<br />
== Variable ==<br />
=== globals ===<br />
{{Nasal doc<br />
|syntax = props.globals;<br />
|text = Exposes the [[Property Tree]] as a <code>props.Node</code> object.<br />
<br />
|example1 = print("Current aircraft: ", props.globals.getNode("/sim/aircraft").getValue());<br />
|example2text = Alternative using {{func link|getprop()}}.<br />
|example2 = print("Current aircraft: ", getprop("/sim/aircraft"));<br />
}}<br />
<br />
{{Nasal namespaces}}</div>Jsbhttps://wiki.flightgear.org/w/index.php?title=Nasal_library&diff=123737Nasal library2020-04-16T15:01:40Z<p>Jsb: Add Extension functions new in FG 2020.1</p>
<hr />
<div>{{Nasal Navigation|nocat=1}}<br />
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:<br />
* {{flightgear file|src/Scripting}}<br />
* {{simgear file|simgear/nasal}}<br />
<br />
All these functions and variables are in the global namespace, that is, they are directly accessible (e.g., one can call <syntaxhighlight lang="nasal" inline>magvar()</syntaxhighlight> instead of <syntaxhighlight lang="nasal" inline>namespace.magvar()</syntaxhighlight>). However, if a namespace must be used, <code>globals</code> is the correct namespace, but using it is not recommended. For a more complete explanation, see [[Nasal Namespaces in-depth]].<br />
<br />
{{tip|Copy & paste the examples into your [[Nasal Console]] and execute them to see what they do.|width=70%}}<br />
<br />
== Core library functions ==<br />
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:<br />
* http://plausible.org/nasal/lib.html ([http://web.archive.org/web/20101010094553/http://plausible.org/nasal/lib.html archive])<br />
* {{simgear file|simgear/nasal/lib.c}} ([http://sourceforge.net/p/flightgear/simgear/ci/next/log/?path=/simgear/nasal/lib.c history])<br />
<br />
=== append() ===<br />
{{Nasal doc<br />
|syntax = append(vector, element[, element[, ...]]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=42|t=Source}}<br />
|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.<br />
|param1 = vector<br />
|param1text = The vector to which the arguments will be appended.<br />
|param2 = element<br />
|param2text = An element to be added to the vector.<br />
|example1 = <br />
var vector = [1, 2, 3]; # Initialize the vector<br />
append(vector, 4); # Append the number 4 to the end of the vector<br />
debug.dump(vector); # Print the contents of the vector<br />
|example2 = <br />
var vector = [1, 2, 3]; # Initialize the vector<br />
append(vector, 4, 5, 6); # Append the numbers 4, 5, and 6 to the end of the vector<br />
debug.dump(vector); # Print the contents of the vector<br />
}}<br />
<br />
=== bind() ===<br />
{{Nasal doc<br />
|syntax = bind(function, locals[, outer_scope]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=502|t=Source}}<br />
|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.<br />
|param1 = function<br />
|param1text = Function to evaluate.<br />
|param2 = locals<br />
|param2text = Hash containing values that will become the namespace (first closure) for the function.<br />
|param3 = outer_scope<br />
|param3text = Optional function which is bound to the next closure. This can be bound to yet another, making a linked list.<br />
|example1 = # This is a namespace/hash with a single member, named "key," which is initialized to 12 <br />
var Namespace = {<br />
key: 12<br />
};<br />
<br />
# This is different namespace/hash containing a function<br />
# dividing a variable "key" (which is unavailable/nil in this namespace) by 2<br />
var AnotherNamespace = {<br />
ret: func {<br />
key /= 2;<br />
}<br />
};<br />
<br />
# To see that key is not available, try to call AnotherNamespace.ret() first<br />
call(AnotherNamespace.ret, [], nil, nil, var errors = []);<br />
if(size(errors)){<br />
print("Key could not be divided/resolved!");<br />
debug.printerror(errors);<br />
}<br />
<br />
# Associate the AnotherNamespace.ret() function with the first namespace<br />
# so that "key" is now available<br />
var function = bind(AnotherNamespace.ret, Namespace);<br />
<br />
# Invoke the new function<br />
function();<br />
<br />
# Print out the value of Namespace.key<br />
# It was changed to 12 from 6 by AnotherNamespace.ret()<br />
print(Namespace.key);<br />
}}<br />
<br />
=== call() ===<br />
{{Nasal doc<br />
|syntax = call(func[, args[, me[, locals[, error]]]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=247|t=Source}}<br />
|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. <br />
|param1 = func<br />
|param1text = Function to execute.<br />
|param2 = args<br />
|param2text = Vector containing arguments to give to the called function.<br />
|param3 = me<br />
|param3text = <code>'''me'''</code> reference for the function call (i.e., for method calls). If given, this will override any <code>'''me'''</code> value existing in the namespace (locals argument).<br />
|param4 = locals<br />
|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.<br />
|param5 = error<br />
|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}}.<br />
|example1 =<br />
# prints "Called from call()"<br />
call(func {<br />
print("Called from call()");<br />
});<br />
|example2 =<br />
# prints "a = 1 : b = 2<br />
call(func(a, b){<br />
print("a = ", a, " : b = ", b);<br />
},<br />
[1, 2]<br />
);<br />
|example3 =<br />
var Hash = {<br />
new: func {<br />
var m = { parents: [Hash] };<br />
<br />
m.el1 = "string1";<br />
m.el2 = "string2";<br />
<br />
return m;<br />
}<br />
};<br />
<br />
# prints "me.el1 = string1", then "me.el2 = string2" on the next line<br />
call(func(a, b){ <br />
print("me.el", a, " = ", me["el" ~ a]); <br />
print("me.el", b, " = ", me["el" ~ b]);<br />
},<br />
[1, 2],<br />
Hash.new()<br />
);<br />
|example4 =<br />
# prints the value of math.pi<br />
call(func {<br />
print(pi);<br />
}, nil, nil, <br />
math<br />
);<br />
|example5 =<br />
call(func {<br />
print(math.ip); # math.ip doesn't exist<br />
}, nil, nil, nil,<br />
var errs = []<br />
);<br />
debug.printerror(errs); # The error is caught and printed using debug.printerror()<br />
}}<br />
<br />
=== caller() ===<br />
{{Nasal doc<br />
|syntax = caller([level]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=404|t=Source}}<br />
|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.<br />
<br />
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. <br />
|param1 = level<br />
|param1text = Optional integer specifying the stack level to return a result from. Defaults to 1 (i.e. the caller of the currently executing function).<br />
|example1 =<br />
var myFunction = func(a, b){<br />
debug.dump(caller(0)[0]); # prints a hash of local variables, including arguments a and b<br />
return 2 * 2;<br />
};<br />
<br />
print("2 x 2 = ", myFunction(2, 2));<br />
|example2 =<br />
var get_arg_value = func(){<br />
print("Argument to myFunc = ", caller(1)[0]['a']); # print the value of myFunc's single argument, using caller()<br />
};<br />
<br />
var myFunc = func(a){<br />
get_arg_value();<br />
};<br />
<br />
myFunc(3);<br />
|example3text = This is a real example taken from {{fgdata file|Nasal/canvas/MapStructure.nas}}. Function <code>r()</code> (above the TODOs) returns a hash with the key/value pairs as per its arguments. For example, something like this is returned: <code>{ name: "<name>", vis: 1, zindex: nil }</code>.<br />
|example3 =<br />
var MapStructure_selfTest = func() {<br />
var temp = {};<br />
temp.dlg = canvas.Window.new([600,400],"dialog");<br />
temp.canvas = temp.dlg.createCanvas().setColorBackground(1,1,1,0.5);<br />
temp.root = temp.canvas.createGroup();<br />
var TestMap = temp.root.createChild("map");<br />
TestMap.setController("Aircraft position");<br />
TestMap.setRange(25); # TODO: implement zooming/panning via mouse/wheel here, for lack of buttons :-/<br />
TestMap.setTranslation(<br />
temp.canvas.get("view[0]")/2,<br />
temp.canvas.get("view[1]")/2<br />
);<br />
var r = func(name,vis=1,zindex=nil) return caller(0)[0];<br />
# TODO: we'll need some z-indexing here, right now it's just random<br />
# TODO: use foreach/keys to show all layers in this case by traversing SymbolLayer.registry direclty ?<br />
# maybe encode implicit z-indexing for each lcontroller ctor call ? - i.e. preferred above/below order ?<br />
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'), ] ) <br />
TestMap.addLayer(factory: canvas.SymbolLayer, type_arg: type.name,<br />
visible: type.vis, priority: type.zindex,<br />
);<br />
}; # MapStructure_selfTest<br />
}}<br />
<br />
=== chr() ===<br />
{{Nasal doc<br />
|syntax = chr(code);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=175|t=Source}}<br />
|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 (<code>"string"</code>) supports control chracters. Strings in single quotes (<code>'string'</code>) do not.}}<br />
{{{!}} class="wikitable"<br />
! Code !! Name !! Equivalent to<br />
{{!-}}<br />
{{!}} 10 {{!!}} {{Wikipedia|Newline}} {{!!}} <code>\n</code><br />
{{!-}}<br />
{{!}} 9 {{!!}} {{Wikipedia|Tab key#Tab characters|Horizontal tab}} {{!!}} <code>\t</code><br />
{{!-}}<br />
{{!}} 13 {{!!}} {{Wikipedia|Carriage return}} {{!!}} <code>\r</code><br />
{{!}}}<br />
|param1 = code<br />
|param1text = Integer character code for the desired glyph.<br />
|example1 = print("Code 65 = ", chr(65)); # prints "Code 65 = A"<br />
|example2text = This example displays all of the characters in a list, in the format <code>Code '''n''' = >'''char'''<</code>, '''n''' being the index, and '''char''' being the character.<br />
|example2 =<br />
for(var i = 0; i <= 255; i += 1){<br />
print("Code ", i, " = >", chr(i), "<");<br />
}<br />
}}<br />
<br />
=== closure() ===<br />
{{Nasal doc<br />
|syntax = closure(func[, level]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=421|t=Source}}<br />
|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'''. <br />
|param1 = func<br />
|param1text = Function to evaluate.<br />
|param2 = level<br />
|param2text = Optional integer specifying the scope level. Defaults to 0 (the namespace of '''func''').<br />
|example1 =<br />
var get_math_e = func {<br />
return e; # return the value of math.e<br />
}<br />
<br />
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<br />
debug.dump(closure(myFunction)); # print the namespace of get_math_e<br />
<br />
print(myFunction());<br />
}}<br />
<br />
=== cmp() ===<br />
{{Nasal doc<br />
|syntax = cmp(a, b);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=112|t=Source}}<br />
|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'''. <br />
|param1 = a<br />
|param1text = First string argument for comparison.<br />
|param2 = b<br />
|param2text = Second string argument for comparison.<br />
|example1 = print(cmp("1", "two")); # prints -1<br />
|example2 = print(cmp("string", "string")); # prints 0<br />
|example3 = print(cmp("one", "2")); # prints 1<br />
|example4 = print(cmp("string1", "string2")); # prints -1<br />
}}<br />
<br />
=== compile() ===<br />
{{Nasal doc<br />
|syntax = compile(code[, filename]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=220|t=Source}}<br />
|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'''.<br />
|param1 = code<br />
|param1text = String containing Nasal code to be compiled.<br />
|param2 = filename<br />
|param2text = Optional string used for error messages/logging. Defaults to <code><compile></code><br />
|example1 = <br />
var myCode = 'print("hello");';<br />
var helloFunc = compile(myCode, "myCode");<br />
helloFunc();<br />
|example2text = <code>compile</code> 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 <tt>''[[$FG_ROOT]]/gui/dialogs/test.xml''</tt>.<br />
<syntaxhighlight lang="xml"><br />
<?xml version="1.0"?><br />
<br />
<PropertyList><br />
<br />
<nasal><![CDATA[<br />
print("You have FlightGear v", getprop("/sim/version/flightgear"));<br />
]]></nasal><br />
<br />
</PropertyList><br />
</syntaxhighlight><br />
Now, start FlightGear and execute this code in the [[Nasal Console]].<br />
|example2 =<br />
# Build the path<br />
var FGRoot = getprop("/sim/fg-root");<br />
var filename = "/gui/dialogs/test.xml";<br />
var path = FGRoot ~ filename;<br />
<br />
var blob = io.read_properties(path);<br />
var script = blob.getValues().nasal; # Get the nasal string<br />
<br />
# Compile the script. We're passing the filename here for better runtime diagnostics <br />
var code = call(func {<br />
compile(script, filename);<br />
}, nil, nil, var compilation_errors = []);<br />
<br />
if(size(compilation_errors)){<br />
die("Error compiling code in: " ~ filename);<br />
}<br />
<br />
# Invoke the compiled script, equivalent to code(); <br />
# We're using call() here to detect errors:<br />
call(code, [], nil, nil, var runtime_errors = []);<br />
<br />
if(size(runtime_errors)){<br />
die("Error calling code compiled loaded from: " ~ filename);<br />
}<br />
}}<br />
<br />
=== contains() ===<br />
{{Nasal doc<br />
|syntax = contains(hash, key);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=184|t=Source}}<br />
|text = Returns 1 (True) if the hash contains the specified key, or 0 (False) if not.<br />
|param1 = hash<br />
|param1text = The hash to search in.<br />
|param2 = key<br />
|param2text = The scalar to be searched for, contained as a key in the hash.<br />
|example1 =<br />
# Initialize a hash<br />
var hash = {<br />
element: "value"<br />
};<br />
print(contains(hash, "element") ? "Yes" : "No"); # This will print "Yes"<br />
|example2 =<br />
# Initialize a hash<br />
var hash = {<br />
element: "value"<br />
};<br />
print(contains(hash, "element2") ? "Yes" : "No"); # This will print "No"<br />
}}<br />
<br />
=== delete() ===<br />
{{Nasal doc<br />
|syntax = delete(hash, key);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=83|t=Source}}<br />
|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 <code>'''nil'''</code> 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.<br />
|param1 = hash<br />
|param1text = The hash from which to delete the key.<br />
|param2 = key<br />
|param2text = The scalar to be deleted, contained as a key in the hash.<br />
|example1 =<br />
# Initialize the hash<br />
var hash = {<br />
element1: "value1",<br />
element2: "value2"<br />
};<br />
delete(hash, "element1"); # Delete element1<br />
debug.dump(hash); # prints the hash, which is now minus element1<br />
}}<br />
<br />
=== die() ===<br />
{{Nasal doc<br />
|syntax = die(error);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=288|t=Source}}<br />
|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()}}.<br />
|param1 = error<br />
|param1text = String describing the error.<br />
:{{inote|This parameter is technically optional, but it is highly recommended to use it.}}<br />
|example1 = <br />
print("Will print");<br />
die("Don't go any further!"); <br />
print("Won't print"); # Will not be printed because die() stops the process<br />
}}<br />
<br />
=== find() ===<br />
{{Nasal doc<br />
|syntax = find(needle, haystack);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=450|t=Source}}<br />
|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.<br />
|param1 = needle<br />
|param1text = String to search for.<br />
|param2 = haystack<br />
|param2text = String to search in.<br />
|example1 = print(find("c", "abcdef")); # prints 2<br />
|example2 = print(find("x", "abcdef")); # prints -1<br />
|example3 = print(find("cd", "abcdef")); # prints 2<br />
}}<br />
<br />
=== ghosttype() ===<br />
{{Nasal doc<br />
|syntax = ghosttype(ghost);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=207|t=Source}}<br />
|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++ <code>naGhostType</code> 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.<br />
|param1 = ghost<br />
|param1text = Ghost to return a description for.<br />
|example1 = print(ghosttype(airportinfo())); # prints "airport"<br />
}}<br />
<br />
=== id() ===<br />
{{Nasal doc<br />
|syntax = id(object);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=570|t=Source}}<br />
|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 <code>'''<type>''':'''<id>'''</code>, where '''<type>''' is the type of object, and '''<id>''' is the ID.<br />
|param1 = object<br />
|param1text = Can be either of a string, a vector, a hash, a code, a function, or a ghost.<br />
|example1 = print(id("A")); # prints "str:000000001624A590"<br />
}}<br />
<br />
=== int() ===<br />
{{Nasal doc<br />
|syntax = int(number);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=90|t=Source}}<br />
|text = Returns the integer part of the numeric value of the single argument, or <code>'''nil'''</code> if none exists.<br />
|param1 = number<br />
|param1text = Number or string with just a number in it to return an integer from.<br />
|example1 = print(int(23)); # prints "23"<br />
|example2 = print(int(23.123)); # prints "23"<br />
|example3 = debug.dump(int("string")); # prints "nil"<br />
}}<br />
<br />
=== keys() ===<br />
{{Nasal doc<br />
|syntax = keys(hash);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=33|t=Source}}<br />
|text = Returns a vector containing the list of keys found in the single hash argument. <br />
|param1 = hash<br />
|param1text = The hash to return the keys from.<br />
|example1 = <br />
# Initialize a hash<br />
var hash = {<br />
element1: "value",<br />
element2: "value"<br />
};<br />
debug.dump(keys(hash)); # print the vector<br />
}}<br />
<br />
=== left() ===<br />
{{Nasal doc<br />
|syntax = left(string, length);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=149|t=Source}}<br />
|version = 2.12<br />
|commit = {{simgear commit|bd7163|t=commit}}<br />
|text = Returns a substring of '''string''', starting from the left.<br />
|param1 = string<br />
|param1text = String to return part of.<br />
|param2 = length<br />
|param2text = Integer specifying the length of the substring to return.<br />
|example1 = print(left("string", 2)); # prints "st"<br />
}}<br />
<br />
=== num() ===<br />
{{Nasal doc<br />
|syntax = num(number);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=102|t=Source}}<br />
|text = Returns the numerical value of the single string argument, or <code>'''nil'''</code> if none exists. <br />
|param1 = number<br />
|param1text = String with just a number in it to return a number from.<br />
|example1 = print(num("23")); # prints "23"<br />
|example2 = print(num("23.123")); # prints "23.123"<br />
|example3 = debug.dump(num("string")); # prints "nil"<br />
}}<br />
<br />
=== pop() ===<br />
{{Nasal doc<br />
|syntax = pop(vector);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=50|t=Source}}<br />
|text = Removes and returns the last element of the single vector argument, or <code>'''nil'''</code> if the vector is empty. <br />
|param1 = vector<br />
|param1text = Vector to remove an element from.<br />
|example1 = <br />
var vector = [1, 2, 3];<br />
pop(vector);<br />
debug.dump(vector); # prints "[1, 2]"<br />
|example2 = <br />
var vector = [1, 2, 3];<br />
debug.dump(pop(vector)); # prints "3"<br />
|example3 = <br />
var vector = [];<br />
debug.dump(pop(vector)); # prints "nil"<br />
}}<br />
<br />
=== right() ===<br />
{{Nasal doc<br />
|syntax = right(string, length);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=161|t=Source}}<br />
|version = 2.12<br />
|commit = {{simgear commit|bd7163|t=commit}}<br />
|text = Returns a substring of '''string''', starting from the right.<br />
|param1 = string<br />
|param1text = String to return part of.<br />
|param2 = length<br />
|param2text = Integer specifying the length of the substring to return.<br />
|example1 = print(right("string", 2)); # prints "ng"<br />
}}<br />
<br />
=== setsize() ===<br />
{{Nasal doc<br />
|syntax = setsize(vector, size);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=56|t=Source}}<br />
|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 <code>'''nil'''</code> entries. Returns the vector operated upon. <br />
|param1 = vector<br />
|param1text = The vector to be operated on.<br />
|param2 = size<br />
|param2text = The desired size of the vector in number of entries.<br />
|example1 = <br />
var vector = [1, 2, 3]; # Initialize a vector<br />
setsize(vector, 4);<br />
debug.dump(vector); # print the vector<br />
|example2 = <br />
var vector = [1, 2, 3]; # Initialize a vector<br />
setsize(vector, 2);<br />
debug.dump(vector); # print the vector<br />
}}<br />
<br />
=== size() ===<br />
{{Nasal doc<br />
|syntax = size(object);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=23|t=Source}}<br />
|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 <code>'''nil'''</code> or a number, this error will be thrown: <code>object has no size()</code>.<br />
|param1 = object<br />
|param1text = Object to find the size of. Must be a string, a vector or a hash.<br />
|example1 = <br />
var string = "string";<br />
print(size(string)); # prints "6"<br />
|example2 =<br />
var vector = [1, 2, 3];<br />
print(size(vector)); # prints "3"<br />
|example3 =<br />
var hash = {<br />
element1: "value1",<br />
element2: "value2",<br />
element3: "value3"<br />
};<br />
print(size(hash)); # prints "3"<br />
}}<br />
<br />
=== sort() ===<br />
{{Nasal doc<br />
|syntax = sort(vector, function);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=542|t=Source}}<br />
|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/}}, <code>sort()</code> 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.<br />
|param1 = vector<br />
|param1text = Input vector to sort.<br />
|param2 = function<br />
|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.<br />
{{{!}} class="wikitable"<br />
! Return value !! Meaning<br />
{{!-}}<br />
{{!}} less than 0 {{!!}} first argument should go before second argument<br />
{{!-}}<br />
{{!}} 0 {{!!}} first argument equals second argument<br />
{{!-}}<br />
{{!}} greater than 0 {{!!}} first argument should go after second argument<br />
{{!}}}<br />
<br />
|example1text = This example sorts elements from smallest to greatest.<br />
|example1 = <br />
var sort_rules = func(a, b){<br />
if(a < b){<br />
return -1; # A should before b in the returned vector<br />
}elsif(a == b){<br />
return 0; # A is equivalent to b <br />
}else{<br />
return 1; # A should after b in the returned vector<br />
}<br />
}<br />
debug.dump(sort([3, 2, 5, 6, 4, 1], sort_rules)); # prints "[1, 2, 3, 4, 5, 6]"<br />
|example2text = This example sorts elements from greatest to smallest.<br />
|example2 = <br />
# Outputs the elements in reverse order (greatest to smallest)<br />
var sort_rules = func(a, b){<br />
if(a < b){<br />
return 1; # -1 in the above example<br />
}elsif(a == b){<br />
return 0;<br />
}else{<br />
return -1; # 1 in the above example<br />
}<br />
}<br />
debug.dump(sort([3, 2, 5, 6, 4, 1], sort_rules)); # prints "[6, 5, 4, 3, 2, 1]"<br />
|example3text = This example sorts a vector of strings (runways for example) from smallest to greatest.<br />
|example3 = <br />
var runways = ["09R","27R","26L","09L","15"];<br />
var rwy = sort(runways,func(a,b) cmp(a,b));<br />
debug.dump(rwy); # prints ['09L','09R','15','26L','27R']<br />
}}<br />
<br />
=== split() ===<br />
{{Nasal doc<br />
|syntax = split(delimiter, string);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=460|t=Source}}<br />
|text = Splits the input string into a vector of substrings bounded by occurrences of the delimiter substring.<br />
|param1 = delimiter<br />
|param1text = String that will split the substrings in the returned vector.<br />
|param2 = string<br />
|param2text = String to split up.<br />
|example1 = debug.dump(split("cd", "abcdef")); # prints "['ab', 'ef']"<br />
|example2 = debug.dump(split(".", "3.2.0")); # prints "[3, 2, 0]"<br />
|example3 = debug.dump(split("/", "path/to/file")); # prints "['path', 'to', 'file']"<br />
}}<br />
<br />
=== sprintf() ===<br />
{{Nasal doc<br />
|syntax = sprintf(format[, arg[, arg, [...]]]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=355|t=Source}}<br />
|text = Creates and returns a string formatted using ANSI C {{func link|vsnprintf()|link=http://en.cppreference.com/w/c/io/vfprintf}} <ref><br />
{{Cite web<br />
|url = http://sourceforge.net/p/flightgear/simgear/ci/next/tree/simgear/nasal/lib.c#l308<br />
|title = fgdata/simgear/simgear/nasal/lib.c, line 308<br />
|accessdate = October 2015<br />
}}<br />
</ref>. Below is a table of supported format specifiers.<br />
{{{!}} class="wikitable" width="75%"<br />
{{!}}+ %[flags][width][.precision]specifier<br />
! colspan="2" {{!}} Flags<br />
{{!-}}<br />
! Flag !! Output<br />
{{!-}}<br />
{{!}} <code>+</code> {{!!}} 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.<br />
{{!-}}<br />
{{!}} ''space'' {{!!}} Prefixes non-signed numbers with a space.<br />
{{!-}}<br />
{{!}} <code>-</code> {{!!}} Left-align the output of this placeholder (the default is to right-align the output) when the width option is specified.<br />
{{!-}}<br />
{{!}} <code>0</code> {{!!}} Use 0 instead of spaces to pad a field when the width option is specified.<br />
{{!-}}<br />
{{!}} <code>#</code> {{!!}} Used with <code>o</code>, <code>x</code> or <code>X</code> specifiers the value is preceded with <tt>0</tt>, <tt>0x</tt> or <tt>0X</tt> respectively for values different than zero. Used with <code>e</code>, <code>E</code> and <code>f</code>, 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 <code>g</code> or <code>G</code> the result is the same as with <code>e</code> or <code>E</code> but trailing zeros are not removed.<br />
{{!-}}<br />
! colspan="2" {{!}} Width<br />
{{!-}}<br />
{{!}} colspan="2" {{!}} Integer specifying the minimum number of characters to be returned. This includes the decimal point and decimal fraction.<br />
{{!-}}<br />
! colspan="2" {{!}} Precision<br />
{{!-}}<br />
{{!}} colspan="2" {{!}} Integer preceded by a dot specifying the number of decimal places to be written.<br />
{{!-}}<br />
! colspan="2" {{!}} Specifiers<br />
{{!-}}<br />
! Specifier !! Output<br />
{{!-}}<br />
{{!}} <code>d</code>, <code>i</code> {{!!}} Signed decimal number.<br />
{{!-}}<br />
{{!}} <code>s</code> {{!!}} A string<br />
{{!-}}<br />
{{!}} <code>%</code> {{!!}} Percent (%) character.<br />
{{!-}}<br />
{{!}} <code>c</code> {{!!}} 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.<br />
{{!-}}<br />
{{!}} <code>o</code> {{!!}} Unsigned integer as an octal number.<br />
{{!-}}<br />
{{!}} <code>u</code> {{!!}} Unsigned decimal integer.<br />
{{!-}}<br />
{{!}} <code>x</code>, <code>X</code> {{!!}} Unsigned integer as a hexadecimal number. If <code>x</code> is used, any letters in the number are lowercase, while <code>X</code> gives uppercase.<br />
{{!-}}<br />
{{!}} <code>e</code>, <code>E</code> {{!!}} Double value in scientific notation (i.e., ''[-]ddd.ddd'''e'''[+/-]ddd''), with an exponent being denoted by <tt>e</tt> or <tt>E</tt> depending on whether an upper or lowercase is used respectively.<br />
{{!-}}<br />
{{!}} <code>f</code> {{!!}} Floating-point number, in fixed decimal notation, by default with 6 decimal places.<br />
{{!-}}<br />
{{!}} <code>F</code> {{!!}} Appears to be available<ref><br />
{{Cite web<br />
|url = http://sourceforge.net/p/flightgear/simgear/ci/next/tree/simgear/nasal/lib.c#l389<br />
|title = fgdata/simgear/simgear/nasal/lib.c, line 389<br />
|accessdate = October 2015<br />
}}<br />
</ref>, but doesn't work.<br />
{{!-}}<br />
{{!}} <code>g</code>, <code>G</code> {{!!}} Double in either normal or exponential notation, whichever is more appropriate for its magnitude. <code>g</code> uses lower-case letters, <code>G</code> 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.<br />
{{!}}}<br />
<br />
|param1 = format<br />
|param1text = String specifying the format. Can be used with or without a format specifiers. See below for examples.<br />
|param2 = arg<br />
|param2text = Argument specifying a value to replace a format placeholder (such as <code>%d</code>) in the format string. Not required if there are no format specifiers.<br />
<br />
|example1 = print(sprintf("%i", 54)); # prints "54"<br />
|example2 = print(sprintf("Pi = %+.10f", math.pi)); # prints "Pi = +3.1415926536"<br />
|example3 = <br />
print(sprintf("%6d", 23)); # prints " 23"<br />
print(sprintf("%06d", 23)); # prints "000023"<br />
|example4 =<br />
var FGVer = getprop("/sim/version/flightgear");<br />
print(sprintf("You have FlightGear v%s", FGVer)); # prints "You have FlightGear v<your version>"<br />
|example5 = <br />
print(sprintf("Hexadecimal 100000 = %X", 100000)); # prints "Hexadecimal 100000 = 186A0"<br />
print(sprintf("Hexadecimal 100000 = %x", 100000)); # prints "Hexadecimal 100000 = 186a0"<br />
|example6 = print(sprintf("Code 65 is %c", 65)); # prints "Code 65 is A"<br />
|example7 = <br />
print(sprintf("%e", 54)); # prints "5.400000e+001"<br />
print(sprintf("%E", 54)); # prints "5.400000E+001"<br />
|example8 = print(sprintf("%o", 54)); # prints "66"<br />
|example9 = print(sprintf("50%% of 100 is %i", 100 / 2)); # prints "50% of 100 is 50"<br />
|example10 =<br />
print(sprintf("%.2f", 1.4)); #prints "1.40"<br />
print(sprintf("%.1f", 1.4)); #prints "1.4"<br />
print(sprintf("% 4.1f", 1.4)); #prints " 1.4"<br />
print(sprintf("%04.1f", 1.4)); #prints "01.4"<br />
print(sprintf("% 6.1f", 1.4)); #prints " 1.4"<br />
print(sprintf("%06.1f", 1.4)); #prints "0001.4"<br />
}}<br />
<br />
=== streq() ===<br />
{{Nasal doc<br />
|syntax = streq(a, b);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=107|t=Source}}<br />
|text = Tests the string values of the two arguments for equality. This function is needed because the <code>'''=='''</code> 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.}}<br />
|param1 = a<br />
|param1text = First argument for testing equality.<br />
|param2 = b<br />
|param2text = Second argument for testing equality.<br />
|example1 = print(streq("0", "0")); # prints "1" (True)<br />
|example2 = <br />
print(0 == 0.0); # prints "1" (True)<br />
print(streq("0", "0.0")); # prints "0" (False)<br />
}}<br />
<br />
=== substr() ===<br />
{{Nasal doc<br />
|syntax = substr(string, start [, length]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=129|t=Source}}<br />
|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).<br />
|param1 = string<br />
|param1text = String to return a substring from.<br />
|param2 = start<br />
|param2text = Integer specifying the start of a substring. Negative values specify a position from the end of the string.<br />
|param3 = length<br />
|param3text = Optional argument specifying the length of the substring. Defaults to the end of the string.<br />
|example1 = print(substr("abcde", 1, 3)); # prints "bcd"<br />
|example2 = print(substr("abcde", 1)); # prints "bcde"<br />
|example3 = print(substr("abcde", 2, 1)); # prints "c"<br />
|example4 = print(substr("abcde", -2)); # prints "de"<br />
|example5 = print(substr("abcde", -3, 2)); # prints "cd"<br />
}}<br />
<br />
=== subvec() ===<br />
{{Nasal doc<br />
|syntax = subvec(vector, start[, length]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=63|t=Source}}<br />
|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). <br />
|param1 = vector<br />
|param1text = The vector to take the sub-vector from.<br />
|param2 = start<br />
|param2text = The starting point of the sub-vector within the given vector.<br />
|param3 = length<br />
|param3text = Optional argument specifying the length of the sub-vector, from the starting point.<br />
'''Notes:'''<br />
* Omitting the ''vector'' and ''start'' arguments is not an error (possibly it should be) but the return value is ''nil''.<br />
* A negative ''start'' argument ''is'' an error. This seems wrong. Perhaps the language designer could comment.<br />
* A value of ''start'' greater than ''size(vector)'' causes an error. A value equal to ''size(vector)'' returns an empty vector.<br />
* 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.<br />
|example1 = <br />
var vector = [1, 2, 3];<br />
debug.dump(subvec(vector, 0)); # prints "[1, 2, 3]"<br />
|example2 = <br />
var vector = [1, 2, 3];<br />
debug.dump(subvec(vector, 1)); # prints "[2, 3]"<br />
|example3 = <br />
var vector = [1, 2, 3];<br />
debug.dump(subvec(vector, 1, 1)); # prints "[2]"<br />
}}<br />
<br />
=== typeof() ===<br />
{{Nasal doc<br />
|syntax = typeof(object);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=193|t=Source}}<br />
|text = Returns a string indicating the whether the object is <code>'''nil'''</code>, a scalar (number or string), a vector, a hash, a function, or a ghost.<br />
|param1 = object<br />
|param1text = Object to return the type of.<br />
|example1 = <br />
var object = nil;<br />
print(typeof(object)); # prints "nil"<br />
|example2 = <br />
var object = "Hello world!";<br />
print(typeof(object)); # prints "scalar"<br />
|example3 = <br />
var object = math.pi;<br />
print(typeof(object)); # prints "scalar"<br />
|example4 = <br />
var object = [1, 2, 3];<br />
print(typeof(object)); # prints "vector"<br />
|example5 = <br />
var object = {};<br />
print(typeof(object)); # prints "hash"<br />
|example6 = <br />
var object = func {};<br />
print(typeof(object)); # prints "func"<br />
|example7 =<br />
var object = airportinfo();<br />
print(typeof(object)); # prints "ghost"<br />
}}<br />
<br />
<!-- == Extension modules ==<br />
=== thread ===<br />
{{WIP}}<br />
<br />
{{Nasal doc<br />
|syntax = thread.newthread(func);<br />
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}<br />
|text = <br />
|example1text = start a new worker thread<br />
|example1 = thread.newthread( func() {} );<br />
}}<br />
<br />
{{Nasal doc<br />
|syntax = thread.newlock();<br />
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}<br />
|text = <br />
|example1text = create a new lock<br />
|example1 = var lock = thread.newlock()<br />
}}<br />
<br />
{{Nasal doc<br />
|syntax = thread.lock();<br />
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}<br />
|text = <br />
|example1text = lock a lock<br />
|example1 = var lock = thread.newlock()<br />
}}<br />
<br />
{{Nasal doc<br />
|syntax = thread.unlock();<br />
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}<br />
|text = <br />
|example1text = unlock a lock<br />
|example1 = var lock = thread.unlock()<br />
}}<br />
<br />
<br />
{{Nasal doc<br />
|syntax = thread.newsem();<br />
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}<br />
|text = <br />
|example1text = create a new {{Wikipedia|semaphore}}<br />
|example1 = var semaphore = thread.newsem()<br />
}}<br />
<br />
<br />
{{Nasal doc<br />
|syntax = thread.semdown();<br />
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}<br />
|text = <br />
|example1text = semaphore down<br />
|example1 = thread.semdown(semaphore)<br />
}}<br />
<br />
<br />
{{Nasal doc<br />
|syntax = thread.semup();<br />
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}<br />
|text = <br />
|example1text = semaphore up<br />
|example1 = thread.semup(semaphore)<br />
}} --><br />
<br />
== Extension functions ==<br />
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:<br />
* {{flightgear file|src/Scripting/NasalPositioned.cxx}}<br />
* {{flightgear file|src/Scripting/NasalSys.cxx}}<br />
* {{fgdata file|Nasal/globals.nas}}<br />
<br />
=== abort() ===<br />
{{Nasal doc<br />
|syntax = abort();<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=565|t=Source}}<br />
|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 "exit" [[Fgcommands|fgcommand]], which will exit FlightGear more gracefully (see example below).<br />
|example1text = This example will immediately stop FlightGear with an error, such as "FlightGear has stopped working."<br />
|example1 = abort();<br />
|example2text = For exiting FlightGear in a better way, please use the following code:<br />
|example2 = fgcommand("exit");<br />
}}<br />
<br />
=== abs() ===<br />
{{Nasal doc<br />
|syntax = abs(number);<br />
|source = {{fgdata file|Nasal/globals.nas|t=Source}}<br />
|text = This simple function returns the {{wikipedia|absolute value|noicon=1}} of the provided number.<br />
|param1 = number<br />
|param1text = This argument is required and should be a number.<br />
|example1 = print(abs(1)); # prints "1"<br />
|example2 = print(abs(-1)); # prints "1"<br />
}}<br />
<br />
=== aircraftToCart() ===<br />
This new function in FG 2017.2.1 takes coordinates in aircraft structural coordinate system, and translate them into geocentric coordinates.<br />
Example for (5,6,7):<br />
<syntaxhighlight lang="nasal"><br />
var pos = aircraftToCart({x: -5, y: 6, z: -7});<br />
var coord = geo.Coord.new();<br />
coord.set_xyz(pos.x, pos.y, pos.z);<br />
</syntaxhighlight><br />
Notice: x and z is inverted sign on purpose.<br />
if you want lat. lon, alt from that, just call: (degrees and meters)<br />
<br />
<syntaxhighlight lang="nasal"><br />
coord.lat()<br />
coord.lon()<br />
coord.alt()<br />
</syntaxhighlight><br />
<br />
=== addcommand() ===<br />
{{Nasal doc<br />
|syntax = addcommand(name, code);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=659|t=Source}}<br />
|version = 2.12<br />
|commit = {{flightgear commit|7b663c|t=commit}}<br />
|text = {{see also|Howto:Add new fgcommands to FlightGear}}<br />
<br />
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.<br />
|param1 = name<br />
|param1text = This will become the name of the new fgcommand. Must be a string.<br />
|param2 = code<br />
|param2text = The code that will be executed when the fgcommand is run. Must be a function.<br />
|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.<br />
|example1 = addcommand("myFGCmd", func(node) {<br />
print("fgcommand 'myFGCmd' has been run.");<br />
props.dump( node );<br />
});<br />
fgcommand("myFGCmd", props.Node.new({foo:1, bar:2}) );<br />
|example2text = This example demonstrates how parameters are defined in a new fgcommand.<br />
|example2 = addcommand("myFGCmd", func(node){<br />
print(node.getNode("number").getValue()); # prints the value of "number," which is 12<br />
});<br />
fgcommand("myFGCmd", props.Node.new({"number": 12}));<br />
}}<br />
<br />
=== airportinfo() ===<br />
{{Nasal doc<br />
|syntax = airportinfo();<br />
airportinfo(type);<br />
airportinfo(id);<br />
airportinfo(lat, lon[, type]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1024|t=Source}}<br />
|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:<br />
* '''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.<br />
* '''lon''': Longitude of the location.<br />
* '''lat''': Latitude of the location.<br />
* '''has_metar''': True or false depending whether the airport has a [[METAR]] code defined for it.<br />
* '''elevation''': Elevation of the location in metres.<br />
* '''id''': ICAO code of the airport (or ID of the seaplane base/heliport).<br />
* '''name''': Name of the airport/heliport/seaplane base.<br />
* '''runways'''<br />
** '''<runway name>'''<br />
*** '''id''': Name of runway.<br />
*** '''lat''': Latitude of the runway.<br />
*** '''lon''': Longitude of the runway.<br />
*** '''heading''': Heading of the runway.<br />
*** '''length''': Length of the runway in metres.<br />
*** '''width''': Width of the runway in metres.<br />
*** '''surface''': Runway surface type.<br />
*** '''threshold''': Length of the runway's {{wikipedia|displaced threshold}} in metres. Will return 0 if there is none.<br />
*** '''stopway''': Length of the runway's stopway (the area before the threshold) in metres. Will return 0 if there is none.<br />
*** '''reciprocal''': <code>runway</code> ghost of the reciprocal runway.<br />
*** '''ils_frequency_mhz''': ILS frequency in megahertz.<br />
*** '''ils''': <code>navaid</code> ghost of the ILS transmitter.<br />
* '''helipads'''<br />
** '''<helipad name>'''<br />
*** '''id''': Name of helipad.<br />
*** '''lat''': Latitude of the helipad.<br />
*** '''lon''': Longitude of the helipad.<br />
*** '''heading''': Heading of the helipad.<br />
*** '''length''': Length of the helipad in metres.<br />
*** '''width''': Width of the helipad in metres.<br />
*** '''surface''': Helipad surface type.<br />
* '''taxiways'''<br />
** '''<taxiway name>'''<br />
*** '''id''': Name of taxiway.<br />
*** '''lat''': Latitude of the taxiway.<br />
*** '''lon''': Longitude of the taxiway.<br />
*** '''heading''': Heading of the taxiway.<br />
*** '''length''': Length of the taxiway in metres.<br />
*** '''width''': Width of the taxiway in metres.<br />
*** '''surface''': Taxiway surface type.<br />
<br />
Information is extracted in the same way as accessing members of a Nasal hash. For example:<br />
<syntaxhighlight lang="nasal"><br />
# prints to lengths of the runways of the nearest airport in feet and metres<br />
var info = airportinfo();<br />
print("-- Lengths of the runways at ", info.name, " (", info.id, ") --");<br />
foreach(var rwy; keys(info.runways)){<br />
print(rwy, ": ", math.round(info.runways[rwy].length * M2FT), " ft (", info.runways[rwy].length, " m)");<br />
}<br />
</syntaxhighlight><br />
<br />
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.<br />
|param1 = id<br />
|param1text = The {{wikipedia|International Civil Aviation Organization airport code|ICAO code|noicon=1}} of an airport to retrieve information about.<br />
|param2 = type<br />
|param2text = When this argument is used, the function will return the closest airport of a certain type. Can be one of "heliport," "seaport," or "airport" (default).<br />
: {{inote|Running this function without any parameters is equivalent to this:<br />
: <syntaxhighlight lang="nasal"><br />
airportinfo("airport");<br />
</syntaxhighlight><br />
}}<br />
|param3 = lat ''and'' lon<br />
|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.<br />
|example1 = var info = airportinfo();<br />
print("Nearest airport: ", info.name, " (", info.id, ")"); # prints the name and ICAO code of the nearest airport<br />
|example2 = var info = airportinfo("heliport");<br />
print("Elevation of the nearest heliport: ", math.round(info.elevation * M2FT), " ft"); # prints the elevation and name of the nearest heliport<br />
|example3 = var info = airportinfo("KSQL");<br />
print("-- Runways of ", info.name, " (", info.id, "): --");<br />
foreach(var rwy; keys(info.runways)) {<br />
print(rwy); # prints the runways of KSQL<br />
}<br />
|example4 = var info = airportinfo(37.81909385, -122.4722484);<br />
print("Coordinates of the nearest airport: ", info.lat, ", ", info.lon); # print the name and ICAO of the nearest airport to the Golden Gate Bridge<br />
|example5 = var info = airportinfo(37.81909385, -122.4722484, "seaport");<br />
print("Nearest seaplane base: ", info.name, " (", info.id, ")"); # print the name and ID of the nearest seaplane base to the Golden Gate Bridge<br />
|example6text = This example prints the all information from an <code>airportinfo()</code> call.<br />
|example6 = var info = airportinfo("KSFO");<br />
print(info.name);<br />
print(info.id);<br />
print(info.lat);<br />
print(info.lon);<br />
print(info.has_metar);<br />
print(info.elevation);<br />
foreach(var rwy; keys(info.runways)){<br />
print("-- ", rwy, " --");<br />
print(info.runways[rwy].lat);<br />
print(info.runways[rwy].lon);<br />
print(info.runways[rwy].length);<br />
print(info.runways[rwy].width);<br />
print(info.runways[rwy].heading);<br />
print(info.runways[rwy].stopway);<br />
print(info.runways[rwy].threshold);<br />
}<br />
}}<br />
<br />
=== airwaysRoute() ===<br />
{{Nasal doc<br />
|syntax = airwaysRoute(start, end[, type]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1933|t=Source}}<br />
|text = {{see also|Nasal Flightplan}}<br />
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.<br />
|param1 = start<br />
|param1text = Start waypoint, in the form of a waypoint ghost, such as that provided by {{func link|flightplan()}}.<br />
|param2 = end<br />
|param2text = Same as above.<br />
|param3 = type<br />
|param3text = Instructs the function to compute a high level route (when set to "highlevel"), or a low level route (when set to "lowlevel"). Defaults to "highlevel."<br />
|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.<br />
|example1 = var fp = flightplan();<br />
var start = fp.getWP(0);<br />
var end = fp.getWP(fp.getPlanSize() - 1);<br />
var rt = airwaysRoute(start, end);<br />
foreach(var wp; rt){<br />
print(wp.wp_name); # print the waypoints in the computed route<br />
}<br />
|example2text = Exactly the same as above, but computes a low level path.<br />
|example2 = var fp = flightplan();<br />
var start = fp.getWP(0);<br />
var end = fp.getWP(fp.getPlanSize() - 1);<br />
var rt = airwaysRoute(start, end, "lowlevel");<br />
foreach(var wp; rt){<br />
print(wp.wp_name); # print the waypoints in the computed route<br />
}<br />
}}<br />
<br />
=== assert() ===<br />
{{Nasal doc<br />
|syntax = assert(condition[, message]);<br />
|source = {{fgdata file|Nasal/globals.nas|t=Source}}<br />
|version = 3.2<br />
|commit = {{fgdata commit|8b16a7|t=commit}}<br />
|text = Returns either true if the condition evaluates as true, or aborts with a {{func link|die()}} call, which can be customised.<br />
|param1 = condition<br />
|param1text = Condition to evaluate.<br />
|param2 = message<br />
|param2text = Optional message that will be used in any {{func link|die()}} call. Defaults to "assertion failed!"<br />
|example1 = var a = 1;<br />
var b = 2;<br />
print(assert(a < b)); # prints "1" (true)<br />
|example2 = var a = 1;<br />
var b = 2;<br />
assert(a > b, 'a is not greater than b'); # aborts with a custom error message<br />
}}<br />
<br />
=== carttogeod() ===<br />
{{Nasal doc<br />
|syntax = carttogeod(x, y, z);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=945|t=Source}}<br />
|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).<ref>{{simgear file|simgear/math/sg_geodesy.hxx|l=43}}</ref><br />
|param1 = x<br />
|param1text = Mandatory x-axis value, in metres.<br />
|param2 = y<br />
|param2text = Mandatory y-axis value, in metres.<br />
|param3 = z<br />
|param3text = Mandatory z-axis value, in metres.<br />
|example1 = var (lat, lon, alt) = carttogeod(6378137, 0, 0); # point is the intersection of the prime meridian and equator.<br />
print("Latitude: ", lat); # prints lat, lon and alt, which are all zero, see above<br />
print("Longitude: ", lon);<br />
print("Altitude: ", alt);<br />
}}<br />
<br />
=== cmdarg() ===<br />
{{Nasal doc<br />
|private = _cmdarg()<br />
|syntax = cmdarg();<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=513|t=Part 1}} {{!}} {{fgdata file|Nasal/globals.nas|t=Part 2}}<br />
|text = <code>cmdarg()</code> 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. <br />
It is used by Nasal scripts embedded in XML files. It returns a <code>props.Node</code> object (see {{fgdata file|Nasal/props.nas}}), and you can use all of its methods on the returned value. <code>cmdarg()</code> should only be used in four types/places of XML files:<br />
* Bindings: This is needed so that the value of a joystick's axis can be accessed internally.<br />
* 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). <br />
* 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.<br />
* Animation XML files: If the animation XML file is used in an AI/MP model, <code>cmdarg()</code> will return the root of the AI model in the <code>/ai/models/</code> directory. Examples: <code>/ai/models/aircraft[3]/</code>, <code>/ai/models/multiplayer[1]/</code><br />
<br />
You should not use <code>cmdarg()</code> in places other than those stated above. Although it won't cause an error, it will return the value of the last legitimate <code>cmdarg()</code> call. <br />
<br />
Also, you should not delay <code>cmdarg()</code> using {{func link|maketimer()}}, {{func link|settimer()}} or {{func link|setlistener()}}, because it will return an unrelated property.<br />
|example1 = fgcommand("dialog-show", {"dialog-name": "cmdarg-demo"});<br />
|example1text = <br>This example demonstrates the usage of <code>cmdarg()</code> in a binding. Save the below XML snippet as <tt>[[$FG_ROOT]]/gui/dialogs/cmdarg-demo.xml</tt>. 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.<br />
<syntaxhighlight lang="xml"><br />
<?xml version="1.0" encoding="UTF-8"?><br />
<br />
<PropertyList><br />
<br />
<name>cmdarg-demo</name><br />
<layout>vbox</layout><br />
<br />
<text><br />
<label>Click "Close" to activate the demonstration (a message in the console).</label><br />
</text><br />
<br />
<button><br />
<legend>Close</legend><br />
<binding><br />
<command>nasal</command><br />
<script>print("Button binding root: '" ~ cmdarg().getPath() ~ "'");</script><br />
</binding><br />
<binding><br />
<command>dialog-close</command><br />
</binding><br />
</button><br />
<br />
</PropertyList><br />
</syntaxhighlight><br />
|example2text = This example demonstrates the usage of <code>cmdarg()</code> in Nasal code within dialogs. Open <tt>[[$FG_ROOT]]/gui/dialogs/cmdarg-demo.xml</tt> from the previous example, copy & 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 "I've been changed!"<br />
<syntaxhighlight lang="xml"><br />
<?xml version="1.0" encoding="UTF-8"?><br />
<br />
<PropertyList><br />
<br />
<name>cmdarg-demo</name><br />
<layout>vbox</layout><br />
<br />
<text><br />
<label>Click "Click me!" to activate the demonstration (the button's label will change).</label><br />
</text><br />
<br />
<button><br />
<legend>Click me!</legend><br />
<binding><br />
<command>nasal</command><br />
<script>change_label();</script><br />
</binding><br />
</button><br />
<br />
<button><br />
<legend>Close</legend><br />
<binding><br />
<command>dialog-close</command><br />
</binding><br />
</button><br />
<br />
<nasal><br />
<open><![CDATA[<br />
var dlg_root = cmdarg();<br />
var dlg_name = {"dialog-name": "cmdarg-demo"};<br />
var change_label = func {<br />
dlg_root.getNode("button[0]/legend").setValue("I've been changed!");<br />
fgcommand("dialog-close", dlg_name);<br />
fgcommand("dialog-show", dlg_name);<br />
}<br />
]]></open><br />
</nasal><br />
<br />
</PropertyList><br />
</syntaxhighlight><br />
|example3text = For an example of <code>cmdarg()</code> used with Canvas, please see [[Howto:Adding a canvas to a GUI dialog#FGPlot|Howto:Adding a canvas to a GUI dialog]].<br />
}}<br />
<br />
=== courseAndDistance() ===<br />
{{Nasal doc<br />
|syntax = courseAndDistance(to);<br />
courseAndDistance(from, to);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1668|t=Source}}<br />
|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:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost type<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A geo.Coord object<br />
|param1 = from<br />
|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.<br />
|param2 = to<br />
|param2text = Like the first parameter, but defines the second point.<br />
|example1text = This example demonstrates the usage of the function with the <code>airport</code> ghost type.<br />
|example1 = var from = airportinfo("KSFO");<br />
var to = airportinfo("KSQL");<br />
var (course, dist) = courseAndDistance(from, to);<br />
print(course); # prints course from KSFO to KSQL<br />
print(dist); # prints distance in nm from KSFO to KSQL<br />
|example2text = This example demonstrates the usage of the function with hashes containing ''lat'' and ''lon''.<br />
|example2 = var from = {lat: 0, lon: 0};<br />
var to = {lat: 1, lon: 1};<br />
var (course, dist) = courseAndDistance(from, to);<br />
print(course);<br />
print(dist);<br />
|example3text = This example demonstrates usage of a geo.Coord object.<br />
|example3 = var from = geo.Coord.new().set_latlon(0, 0);<br />
var to = geo.Coord.new().set_latlon(1, 1);<br />
var (course, dist) = courseAndDistance(from, to);<br />
print(course);<br />
print(dist);<br />
|example4text = This example demonstrates usage of differing parameter types.<br />
|example4 = var from = airportinfo("KSFO");<br />
var to = geo.Coord.new().set_latlon(0, 0);<br />
var (course, dist) = courseAndDistance(from, to);<br />
print(course);<br />
print(dist);<br />
|example5text = The same as above, but the other way round.<br />
|example5 = var to = {lat: 1, lon: 1};<br />
var (course, dist) = courseAndDistance(0, 0, to);<br />
print(course);<br />
print(dist);<br />
|example6text = Usage of just one parameter.<br />
|example6 = var dest = airportinfo("KSQL");<br />
var (course, dist) = courseAndDistance(dest);<br />
print("Turn to heading ", math.round(course), ". You have ", sprintf("%.2f", dist), " nm to go");<br />
}}<br />
<br />
=== createDiscontinuity() ===<br />
{{Nasal doc<br />
|syntax = createDiscontinuity();<br />
|text = Returns a <code>waypoint</code> ghost object. A route discontinuity is inserted by an {{abbr|FMS|Flight Management System}} when it is unsure how to connect two waypoints.<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2045|t=Source}}<br />
|version = 2016.1<br />
|commit = {{flightgear commit|caead6|t=commit}}<br />
}}<br />
=== createViaTo() ===<br />
{{Nasal doc<br />
|syntax = createViaTo(airway, waypoint);<br />
|text = Returns a <code>waypoint</code> ghost object. It represents a route "via '''airway''' to '''waypoint'''".<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2009|t=Source}}<br />
|version = 2016.1<br />
|commit = {{flightgear commit|caead6|t=commit}}<br />
|param1 = airway<br />
|param1text = The name of an airway.<br />
|param2 = waypoint<br />
|param2text = Must be in the airway and one of:<br />
* The name of a waypoint.<br />
* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, or <code>fix</code> ghost object.<br />
}}<br />
=== createWP() ===<br />
{{Nasal doc<br />
|syntax = createWP(pos, name[, flag]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1964|t=Source}}<br />
|text = Creates a new waypoint ghost object.<br />
|param1 = pos<br />
|param1text = Dictates the position of the new waypoint. It can be one of the following:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost type<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A geo.Coord object<br />
:* Two numbers separated by a comma, as if the function is taking three arguments. See example 4 below.<br />
|param2 = name<br />
|param2text = String that will become the name of the new waypoint.<br />
|param3 = flag<br />
|param3text = Optional string that will tell FlightGear what type of waypoint it is. Must be one of "sid," "star," "approach," "missed," or "pseudo."<br />
|example1text = Creates a waypoint directly in front and 1 km away and appends it to the flight plan.<br />
|example1 = var pos = geo.aircraft_position().apply_course_distance(getprop("/orientation/heading-deg"), 1000);<br />
var wp = createWP(pos, "NEWWP");<br />
var fp = flightplan();<br />
fp.appendWP(wp);<br />
|example2 = var pos = geo.aircraft_position().apply_course_distance(getprop("/orientation/heading-deg"), 1000);<br />
var wp = createWP({lat: pos.lat(), lon: pos.lon()}, "NEWWP");<br />
var fp = flightplan();<br />
fp.appendWP(wp);<br />
|example3 = var apt = airportinfo();<br />
var wp = createWP(apt, "NEWWP");<br />
var fp = flightplan();<br />
fp.appendWP(wp);<br />
|example4 = var pos = geo.aircraft_position().apply_course_distance(getprop("/orientation/heading-deg"), 1000);<br />
var wp = createWP(pos.lat(), pos.lon(), "NEWWP");<br />
var fp = flightplan();<br />
fp.appendWP(wp);<br />
|example5text = Creates a new waypoint and adds it to the flight plan. Waypoints of the type "pseudo" are then removed from the flight plan, including the new waypoint. The {{func link|print()}} statements show this.<br />
|example5 = var pos = geo.aircraft_position().apply_course_distance(getprop("/orientation/heading-deg"), 1000);<br />
var wp = createWP(pos, "NEWWP", "pseudo");<br />
var fp = flightplan();<br />
fp.appendWP(wp);<br />
print(fp.getPlanSize());<br />
fp.clearWPType("pseudo");<br />
print(fp.getPlanSize());<br />
}}<br />
<br />
=== createWPFrom() ===<br />
{{Nasal doc<br />
|syntax = createWPFrom(object[, flag]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1989|t=Source}}<br />
|text = Creates a new waypoint object from another object.<br />
|param1 = object<br />
|param1text = A ghost object. Must be a ghost type that is one of "airport," "navaid," "runway," or "fix."<br />
|param2 = flag<br />
|param2text = Optional string that will tell FlightGear what type of waypoint it is. Must be one of "sid," "star," "approach," "missed," or "pseudo."<br />
|example1text = Creates a new waypoint and appends it to the flight plan.<br />
|example1 = var apt = airportinfo("KSFO");<br />
var wp = createWPFrom(apt);<br />
var fp = flightplan();<br />
fp.appendWP(wp);<br />
|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.<br />
|example2 = var apt = airportinfo("KSFO");<br />
var wp = createWPFrom(apt, "pseudo");<br />
print(wp.wp_name);<br />
var fp = flightplan();<br />
fp.appendWP(wp);<br />
print(fp.getPlanSize());<br />
fp.clearWPType("pseudo");<br />
print(fp.getPlanSize());<br />
}}<br />
<br />
=== defined() ===<br />
{{Nasal doc<br />
|syntax = defined(symbol);<br />
|source = {{fgdata file|Nasal/globals.nas|t=Source}}<br />
|text = Returns 1 (true) or 0 (false) depending on whether a variable exists.<br />
|param1 = symbol<br />
|param1text = A string that will be what the function searches for.<br />
|example1 = var number = 12;<br />
var check_exist = func {<br />
print("Variable 'number' ", defined("number") == 1 ? "exists" : "does not exist"); # 'number' does exist<br />
print("Variable 'number2' ", defined("number2") == 1 ? "exists" : "does not exist"); # 'number2' does not exist<br />
}<br />
check_exist();<br />
}}<br />
<br />
=== directory() ===<br />
{{Nasal doc<br />
|syntax = directory(path);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=572|t=Source}}<br />
|text = Returns a vector containing a list of the folders and files in a given file path or <code>'''nil'''</code> if the path doesn't exist. Hidden folders and files are not added to the vector.<br />
{{inote|The first two elements of the vector will be <code>'.'</code> and <code>'..'</code>. These are for navigating back up the file tree, but have no use in Nasal. They can be safely removed from the vector.}}<br />
|param1 = path<br />
|param1text = Absolute file path.<br />
|example1text = Gets the folders and files in [[$FG_ROOT]], and then removes the extra first two elements (see note above). <br />
|example1 = var dir = directory(getprop("/sim/fg-root")); # get directory<br />
dir = subvec(dir, 2); # strips off the first two elements<br />
debug.dump(dir); # dump the vector<br />
}}<br />
<br />
=== fgcommand() ===<br />
{{Nasal doc<br />
|syntax = fgcommand(cmd[, args]);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=456|t=Part 1}} {{!}} {{fgdata file|Nasal/globals.nas|t=Part 2}}<br />
|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.<br />
|param1 = cmd<br />
|param1text = String that is the name of the command that is to be run.<br />
|param2 = args<br />
|param2text = If the fgcommand takes arguments, they are inputted using this argument. Can either be a <code>props.Node</code> object, or a hash (see examples below).<br />
|example1 = fgcommand("null"); # does nothing<br />
|example2 = var args = props.Node.new({'script': 'print("Running fgcommand");'});<br />
if (fgcommand("nasal", args)) { # prints "Running fgcommand" and then one of these print statements<br />
print("Fgcommand succeeded");<br />
} else {<br />
print("Fgcommand encountered a problem");<br />
}<br />
|example3 = var args = { 'dialog-name': 'about' };<br />
fgcommand("dialog-show", args); # shows the 'about' dialog<br />
}}<br />
<br />
=== findAirportsByICAO() ===<br />
{{Nasal doc<br />
|syntax = findAirportsByICAO(search[, type]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1096|t=Source}}<br />
|text = Returns a vector containing <code>airport</code> ghost objects which are (by default) airports whose ICAO code matches the search string. The results are sorted by range from closest to furthest.<br />
|param1 = search<br />
|param1text = Search string for the function. Can either be a partial or a full ICAO code.<br />
:{{icaution|The more matches there are for the given code, the longer the function will take. Passing just one character (e.g., "K"), might make FlightGear hang for a certain amount of time.}}<br />
|param2 = type<br />
|param2text = This will narrow the search to airports of a certain type. By default, only airports are searched for. May be one of "airport," "heliport," or "seaport."<br />
|example1 = var apts = findAirportsByICAO("KSF"); # finds all airports matching "KSF"<br />
foreach(var apt; apts){<br />
print(apt.name, " (", apt.id, ")"); # prints them<br />
}<br />
|example2 = var apts = findAirportsByICAO("SP0", "seaport"); # finds all seaplane bases matching "SP0"<br />
foreach(var apt; apts){<br />
print(apt.name, " (", apt.id, ")"); # prints them<br />
}<br />
|example3 = var apt = findAirportsByICAO("XBET"); # one way to check if an airport does exist"<br />
if (size(apt) == 0) {<br />
print("Airport does not exist"); # this one will be printed<br />
} else {<br />
print("Airport does exist");<br />
}<br />
}}<br />
<br />
=== findAirportsWithinRange() ===<br />
{{Nasal doc<br />
|syntax = findAirportsWithinRange([pos, ]range[, type]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1066|t=Source}}<br />
|text = Returns a vector of <code>airport</code> 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.<br />
|param1 = pos<br />
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost type<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A geo.Coord object<br />
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: <code>findAirportsWithinRange(lat, lon, range, type);</code>.<br />
|param2 = range<br />
|param2text = Mandatory number giving the range in nautical miles within which to search for airports/heliports/seaplane bases.only airports are searched for.<br />
|param3 = type<br />
|param3text = This will narrow the search to airports of a certain type. By default, only airports are searched for. May be one of "airport," "heliport," or "seaport."<br />
|example1text = Searches for airports within 10 nm of [[KSFO]].<br />
|example1 = var pos = airportinfo("KSFO");<br />
var apts = findAirportsWithinRange(pos, 10);<br />
foreach(var apt; apts){<br />
print(apt.name, " (", apt.id, ")");<br />
}<br />
|example2text = Searches for seaplane bases within 15 nm of [[KSFO]].<br />
|example2 = var pos = airportinfo("KSFO");<br />
var apts = findAirportsWithinRange(pos, 15, "seaport");<br />
foreach(var apt; apts){<br />
print(apt.name, " (", apt.id, ")");<br />
}<br />
|example3text = Searches for airports within 10 nm of your current position.<br />
|example3 = var apts = findAirportsWithinRange(10);<br />
foreach(var apt; apts){<br />
print(apt.name, " (", apt.id, ")");<br />
}<br />
}}<br />
<br />
=== findFixesByID() ===<br />
{{Nasal doc<br />
|syntax = findFixesByID([pos, ]id);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1627|t=Source}}<br />
|text = Returns a vector containing <code>fix</code> ghost objects matching a given ID, sorted by range from a certain position.<br />
{{inote|Fixes are (usually) also known as waypoints.}}<br />
|param1 = pos<br />
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost type<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A geo.Coord object<br />
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: <code>findFixesByID(lat, lon, id);</code>.<br />
|param2 = id<br />
|param2text = Full or partial ID of the fix to search for.<br />
:{{inote|1=Inputting a partial ID does not work correctly (see [http://forum.flightgear.org/viewtopic.php?f=30&t=28129 here]). It is best to just input a full ID.}}<br />
|example1 = var fixes = findFixesByID("POGIC");<br />
foreach(var fix; fixes){<br />
print(fix.id, " - lat: ", fix.lat, " {{!}} lon: ", fix.lon); # prints information about POGIC<br />
}<br />
|example2 = var fix = findFixesByID("ZUNAP");<br />
fix = fix[0];<br />
var (course, dist) = courseAndDistance(fix);<br />
print("Turn to heading ", math.round(course), ". You have ", sprintf("%.2f", dist), " nm to go to reach ", fixes[0].id);<br />
}}<br />
<br />
=== findNavaidByFrequency() ===<br />
{{Nasal doc<br />
|syntax = findNavaidByFrequency([pos, ]freq[, type]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1547|t=Source}}<br />
|text = Returns a <code>navaid</code> ghost object for a navaid matching a given frequency. If there is more than one navaid with that frequency, the nearest station is returned.<br />
|param1 = pos<br />
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost type<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A geo.Coord object<br />
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: <code>findNavaidByFrequency(lat, lon, freq, type);</code>.<br />
|param2 = freq<br />
|param2text = Frequency, in megahertz, of the navaid to search for.<br />
|param3 = type<br />
|param3text = This will narrow the search to navaids of a certain type. Defaults to "all." For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.<br />
|example1 = var navaid = findNavaidByFrequency(11.17);<br />
print("ID: ", navaid.id); # prints info about the navaid<br />
print("Name: ", navaid.name);<br />
print("Latitude: ", navaid.lat);<br />
print("Longitude: ", navaid.lon);<br />
print("Elevation (AMSL): ", navaid.elevation, " m");<br />
print("Type: ", navaid.type);<br />
print("Frequency: ", sprintf("%.3f", navaid.frequency / 1000), " Mhz");<br />
print("Range: ", navaid.range_nm, " nm");<br />
if(navaid.course) print("Course: ", navaid.course);<br />
}}<br />
<br />
=== findNavaidsByFrequency() ===<br />
{{Nasal doc<br />
|syntax = findNavaidsByFrequency([pos, ]freq[, type]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1572|t=Source}}<br />
|text = Returns a vector conatining <code>navaid</code> ghost objects for navaids that match a given frequency, sorted from nearest to furthest.<br />
|param1 = pos<br />
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost type<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A geo.Coord object<br />
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: <code>findNavaidsByFrequency(lat, lon, freq, type);</code>.<br />
|param2 = freq<br />
|param2text = Frequency, in megahertz, of the navaid to search for.<br />
|param3 = type<br />
|param3text = This will narrow the search to navaids of a certain type. Defaults to "all." For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.<br />
|example1 = var navaids = findNavaidsByFrequency(10.955);<br />
foreach(var navaid; navaids){<br />
print("--");<br />
print("ID: ", navaid.id); # prints info about the navaid<br />
print("Name: ", navaid.name);<br />
print("Latitude: ", navaid.lat);<br />
print("Longitude: ", navaid.lon);<br />
print("Elevation (AMSL): ", navaid.elevation, " m");<br />
print("Type: ", navaid.type);<br />
print("Frequency: ", sprintf("%.3f", navaid.frequency / 1000), " Mhz");<br />
print("Range: ", navaid.range_nm, " nm");<br />
if(navaid.course) print("Course: ", navaid.course);<br />
print("--");<br />
}<br />
}}<br />
<br />
=== findNavaidsByID() ===<br />
{{Nasal doc<br />
|syntax = findNavaidsByID([pos, ]id[, type]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1600|t=Source}}<br />
|text = Returns a vector containing <code>navaid</code> ghost objects matching a given ID, sorted by range from a certain position.<br />
|param1 = pos<br />
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost type<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A geo.Coord object<br />
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: <code>findNavaidsByID(lat, lon, id, type);</code>.<br />
|param2 = id<br />
|param2text = Full or partial ID of the fix to search for.<br />
:{{inote|1=Inputting a partial ID does not work correctly (see [http://forum.flightgear.org/viewtopic.php?f=30&t=28129 here]). It is best to just input a full ID.}}<br />
|param3 = type<br />
|param3text = This will narrow the search to navaids of a certain type. Defaults to "all." For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.<br />
|example1 = var navaid = findNavaidsByID("MXW");<br />
navaid = navaid[0];<br />
print("ID: ", navaid.id); # prints info about 'MXW' (a VOR station)<br />
print("Name: ", navaid.name);<br />
print("Latitude: ", navaid.lat);<br />
print("Longitude: ", navaid.lon);<br />
print("Elevation (AMSL): ", navaid.elevation, " m");<br />
print("Type: ", navaid.type);<br />
print("Frequency: ", sprintf("%.3f", navaid.frequency / 1000), " Mhz");<br />
print("Range: ", navaid.range_nm, " nm");<br />
}}<br />
<br />
=== findNavaidsWithinRange() ===<br />
{{Nasal doc<br />
|syntax = findNavaidsWithinRange([pos, ]range[, type]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1518|t=Source}}<br />
|text = Returns a vector of <code>navaid</code> 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.<br />
|param1 = pos<br />
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost type<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A geo.Coord object<br />
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: <code>findNavaidsWithinRange(lat, lon, range, type);</code>.<br />
|param2 = range<br />
|param2text = Mandatory number giving the range in nautical miles within which to search for navaids. Defaults to "all." For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.<br />
|param3 = type<br />
|param3text = This will narrow the search to navaids of a certain type.<br />
|example1text = Searches for navaids within 10 nm of [[KSFO]].<br />
|example1 = var pos = airportinfo("KSFO");<br />
var navs = findNavaidsWithinRange(pos, 10);<br />
foreach(var nav; navs){<br />
print(nav.name, " (ID: ", nav.id, ")");<br />
}<br />
|example2text = Searches for navaids within 10 nm of your current position.<br />
|example2 = var navs = findNavaidsWithinRange(10);<br />
foreach(var nav; navs){<br />
print(nav.name, " (ID: ", nav.id, ")");<br />
}<br />
}}<br />
<br />
=== finddata() ===<br />
{{Nasal doc<br />
|syntax = finddata(path);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=603|t=Source}}<br />
|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]]. <br />
|param1 = path<br />
|param1text = A relative path as a string.<br />
|example1 = var path = finddata("Aircraft/Generic");<br />
print(path); # prints the absolute path to $FG_ROOT/Aircraft/Generic<br />
|example2 = var path = finddata("Airports");<br />
print(path); # prints the absolute path to <TerraSync dir>/Airports<br />
|example3 = var path = finddata("preferences.xml");<br />
print(path); # prints the absolute path to $FG_ROOT/preferences.xml<br />
}}<br />
<br />
=== flightplan() ===<br />
{{Nasal doc<br />
|syntax = flightplan([path]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1738|t=Source}}<br />
|text = {{see also|Nasal Flightplan}}<br />
Returns a flight plan object, either one for the current flight plan, or one loaded from a given path.<br />
|param1 = path<br />
|param1text = Optional path to flight plan XML file.<br />
|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.<br />
|example1 = var fp = flightplan();<br />
print(fp.getWP(fp.current).id);<br />
|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 <tt>''[[$FG_HOME]]/fp-demo.xml''</tt>.<br />
|example2 = var path = getprop('/sim/fg-home') ~ '/fp-demo.xml';<br />
var fp = flightplan(path);<br />
for(var i = 0; i < fp.getPlanSize(); i += 1){<br />
print(fp.getWP(i).id);<br />
}<br />
}}<br />
<br />
=== geodinfo() ===<br />
{{Nasal doc<br />
|syntax = geodinfo(lat, lon[, max_alt]);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=981|t=Source}}<br />
|text = Returns a vector containing two entries or <code>'''nil'''</code> 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 <tt>''[[$FG_ROOT]]/Materials''</tt>), or <code>'''nil'''</code> 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}}):<br />
* '''light_coverage:''' The coverage of a single point of light in m<sup>2</sup>.<br />
* '''bumpiness:''' Normalized bumpiness factor for the material.<br />
* '''load_resistance:''' The amount of pressure in N/m<sup>2</sup> the material can withstand without deformation.<br />
* '''solid:''' 1 (true) or false (0) depending on whether the material is solid or not.<br />
* '''names:''' Vector of scenery types (usually generated by [[TerraGear]]) that will use this material. <br />
* '''friction_factor:''' Normalized friction factor of the material.<br />
* '''rolling_friction:''' The rolling friction coefficient of the material.<br />
<br />
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.<br />
|param1 = lat<br />
|param1text = Latitude, inputted as a number.<br />
|param2 = lon<br />
|param2text = Longitude, inputted as a number.<br />
|param3 = max_alt<br />
|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, <code>'''nil'''</code> will be returned.<br />
|example1text = Dumps information about ground underneath the aircraft.<br />
|example1 = var pos = geo.aircraft_position();<br />
var info = geodinfo(pos.lat(), pos.lon());<br />
debug.dump(info);<br />
|example2text = Prints whether the ground underneath the aircraft is solid or is water.<br />
|example2 = var pos = geo.aircraft_position();<br />
var info = geodinfo(pos.lat(), pos.lon());<br />
if (info != nil and info[1] != nil) {<br />
print("The ground underneath the aircraft is ", info[1].solid == 1 ? "solid." : "water.");<br />
}<br />
}}<br />
<br />
=== geodtocart() ===<br />
{{Nasal doc<br />
|syntax = geodtocart(lat, lon, alt);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=962|t=Source}}<br />
|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.<br />
|param1 = lat<br />
|param1text = Latitude, in degrees.<br />
|param2 = lon<br />
|param2text = Longitude, in degrees.<br />
|param3 = alt<br />
|param3text = Altitude, in metres.<br />
|example1 = var (x, y, z) = geodtocart(0, 0, 0); # point is the intersection of the prime meridian and equator.<br />
print("x: ", x); # prints "x: 6378137"<br />
print("y: ", y); # prints "y: 0"<br />
print("z: ", z); # prints "y: 0"<br />
}}<br />
<br />
=== get_cart_ground_intersection() ===<br />
Introduced in 2017.2.1, see [[Terrain Detection]].<br />
<br />
=== getprop() ===<br />
{{Nasal doc<br />
|syntax = getprop(path[, path[, ...]]);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=345|t=Source}}<br />
|text = Returns the value of a node in the [[Property Tree]] or <code>'''nil'''</code> if the node does not exist or the value is not a number (NaN).<br />
|param1 = path<br />
|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.<br />
|example1 = print("You have FlightGear v", getprop("/sim/version/flightgear")); # prints FlightGear version<br />
|example2text = Note that the example below will only work in FlightGear 3.2 and above.<br />
|example2 = for(var i = 0; i < 8; i += 1){<br />
print("View #", i + 1, " is named ", getprop("/sim/view", i, "name"));<br />
}<br />
|example3text = Same as above, but is supported by all versions of FlightGear.<br />
|example3 = for(var i = 0; i < 8; i += 1){<br />
print("View #", i + 1, " is named ", getprop("/sim/view[" ~ i ~ "]/name"));<br />
}<br />
}}<br />
==== See also ====<br />
{{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: <br />
To get a Node rather than its value, use <code>props.globals.getNode()</code> - see [[Nasal_library/props]]. }}<br />
<br />
=== greatCircleMove() ===<br />
{{Nasal doc<br />
|syntax = greatCircleMove([pos, ]course, dist);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1681|t=Source}}<br />
|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).<br />
|param1 = pos<br />
|param1text = Optional position to calculate from. If not given, the aircraft's current position will be used. Can be one of:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost object.<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A <code>geo.Coord</code> object<br />
:* A lat/lon pair, that is, a pair of numbers (latitude followed by longitude) separated by a comma: <code>greatCircleMove(lat,lon, ...)</code>.<br />
|param2 = course<br />
|param2text = Course to new set of coordinates, in degrees (in the range 0–360).<br />
|param3 = dist<br />
|param3text = Distance in nautical miles to the new set of coordinates.<br />
|example1 = var pos = greatCircleMove(0,0, 0, 1);<br />
debug.dump(pos); # print hash with coordinates<br />
|example2 = var fix = findFixesByID("POGIC");<br />
fix = fix[0];<br />
var pos = greatCircleMove(fix, 45, 10);<br />
debug.dump(pos); # print hash with coordinates<br />
}}<br />
<br />
=== interpolate() ===<br />
{{Nasal doc<br />
|private = _interpolate()<br />
|syntax = interpolate(prop, value1, time1[, value2, time2[, ...]]);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=522|t=Part 1}} {{!}} {{fgdata file|Nasal/globals.nas|t=Part 2}}<br />
|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.<br />
|param1 = prop<br />
|param1text = String or <code>props.Node</code> object that indicates a node in the property tree to be used.<br />
|param2 = value''n''<br />
|param2text = Target value to change the property to in the set amount of time. This should be a number.<br />
|param3 = time''n''<br />
|param3text = Time in seconds, that will be taken for the interpolation.<br />
|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.<br />
|example1 = setprop("/test", 0); # (re-)set property<br />
interpolate("/test",<br />
50, 5, # interpolate to 50 in 5 seconds<br />
10, 2, # interpolate to 10 in 2 seconds<br />
0, 5); # interpolate to 0 in 5 seconds<br />
|example2 = # Apply the left brake at 20% per second<br />
var prop = "controls/gear/brake-left";<br />
var dist = 1 - getprop(prop);<br />
if (dist == 1) {<br />
interpolate(prop, 1, dist / 0.2);<br />
}<br />
}}<br />
<br />
=== isa() ===<br />
{{Nasal doc<br />
|syntax = isa(object, class);<br />
|source = {{fgdata file|Nasal/globals.nas|t=Source}}<br />
|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.<br />
|param1 = object<br />
|param1text = Object to check.<br />
|param2 = class<br />
|param2text = Class/object to check that '''object''' inherits from or is an instance of.<br />
|example1 = var coord = geo.Coord.new();<br />
if(isa(coord, geo.Coord)){<br />
print("Variable 'coord' is an instance of class 'geo.Coord'"); # this one will be printed<br />
} else {<br />
print("Variable 'coord' is not an instance of class 'geo.Coord'");<br />
}<br />
|example2 = var coord = geo.Coord.new();<br />
if(isa(coord, props.Node)){<br />
print("Variable 'coord' is an instance of class 'props.Node'");<br />
} else {<br />
print("Variable 'coord' is not an instance of class 'props.Node'"); # this one will be printed<br />
}<br />
|example3text = The example below demonstrates checking of inheritance.<br />
|example3 = var Const = {<br />
constant: 2,<br />
getConst: func {<br />
return me.constant;<br />
}<br />
};<br />
<br />
var Add = {<br />
new: func {<br />
return { parents: [Add, Const] };<br />
},<br />
<br />
addToConst: func(a){<br />
return a * me.getConst();<br />
}<br />
};<br />
<br />
var m = Add.new();<br />
print(m.addToConst(4));<br />
<br />
if(isa(m, Add)) print("Variable 'm' is an instance of class 'Add'"); # will be printed<br />
if(isa(m, Const)) print("Variable 'm' is an instance of class 'Const'"); # will also be printed<br />
}}<br />
<br />
=== logprint() ===<br />
{{Nasal doc<br />
|syntax = logprint(priority[, msg[, msg[, ...]]]);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=431|t=Source}}<br />
|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 <code>[[Commonly used debugging tools#fgfs.log|fgfs.log]]</code> file as coming from the Nasal file itself rather than from {{flightgear file|src/Scripting/NasalSys.cxx}}.<br />
|param1 = priority<br />
|param1text = Number specifying the priority level of the outputted message:<br />
:{{{!}} class="wikitable"<br />
! Number !! Debug type<br />
{{!-}}<br />
{{!}} 1 {{!!}} Bulk<br />
{{!-}}<br />
{{!}} 2 {{!!}} Debug<br />
{{!-}}<br />
{{!}} 3 {{!!}} Info<br />
{{!-}}<br />
{{!}} 4 {{!!}} Warn<br />
{{!-}}<br />
{{!}} 5 {{!!}} Alert<br />
{{!}}}<br />
|param2 = msg<br />
|param2text = The message. There is no limit to the arguments you give give. They will be concatenated together before logging.<br />
|example1 = # logs the value of pi to three decimal places with log level 3<br />
logprint(3, "pi = ", sprintf("%.3f", math.pi));<br />
|example2 = logprint(5, "Alert! This is an important message!");<br />
}}<br />
{{note| <br />
The following constants have been added to the development branch of FlightGear ("next") and will be releases with FG 2020.1 so you won't have to remember the numbers anymore:<br />
<br />
LOG_BULK, LOG_WARN, LOG_DEBUG, LOG_INFO, LOG_ALERT, DEV_WARN, DEV_ALERT }}<br />
<br />
=== magvar() ===<br />
{{Nasal doc<br />
|syntax = magvar([pos]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1642|t=Source}}<br />
|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.<br />
{{{!}} class="wikitable"<br />
! FlightGear versions !! Model !! Reference date<br />
{{!-}}<br />
{{!}} 3.6 and above {{!!}} {{wikipedia|World Magnetic Model}} (WMM) 2015 {{!!}} 1 January 2015<br />
{{!-}}<br />
{{!}} 0.9.11-pre1 to 3.4 {{!!}} WMM 2005 {{!!}} 1 January 2005<br />
{{!-}}<br />
{{!}} 0.7.3 to 0.9.10 {{!!}} WMM 2000 {{!!}} 1 January 2000<br />
{{!}}}<br />
|param1 = pos<br />
|param1text = Optional position to calculate from. If not given, the aircraft's current position will be used. Can be one of:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost object.<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A <code>geo.Coord</code> object<br />
:* A lat/lon pair, that is, a pair of numbers (latitude followed by longitude) separated by a comma: <code>magvar(lat,lon)</code>.<br />
|example1 = print(magvar(0, 0)); # prints the magnetic variation at 0, 0<br />
}}<br />
<br />
=== maketimer() ===<br />
{{Nasal doc<br />
|syntax = maketimer(interval[, self], function);<br />
|source = ''Implemented using the {{API Link|flightgear|class|TimerObj}} class.''<br>{{flightgear file|src/Scripting/NasalSys.cxx|l=90|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=533|t=Part 2}}<br />
|version = 2.12<br />
|commit = {{flightgear commit|ab939f|t=commit}}<br />
|text = Returns a timer object containing the following methods and members:<br />
* '''start()''': Starts the timer.<br />
* '''stop()''': Stops the timer.<br />
* '''restart(interval)''': Restarts the timer with the given interval.<br />
* '''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).<br />
* '''isRunning''': Read-only bool telling whether the timer is currently running.<br />
* '''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.<br />
Unlike {{func link|settimer()}}, which it replaces, <code>maketimer()</code> provides more control over the timer. In addition, it can help reduce memory usage.<br />
|param1 = interval<br />
|param1text = Interval in seconds for the timer.<br />
|param2 = self<br />
|param2text = Optional parameter specifying what any <code>'''me'''</code> references in the function being called will refer to.<br />
|param3 = function<br />
|param3text = Function to be called at the given interval.<br />
|example1 = var timer = maketimer(1, func(){<br />
print("Hello, World!"); # print "Hello, World!" once every second (call timer.stop() to stop it)<br />
});<br />
timer.start();<br />
|example2 = var timer = maketimer(1, math, func(){<br />
print(me.math); # 'me' reference is the 'math' namespace<br />
});<br />
timer.singleShot = 1; # timer will only be run once<br />
timer.start();<br />
|example3 = var timer = maketimer(1, func(){<br />
print("Hello, World!"); # print "Hello, World!" once every second (call timer.stop() to stop it)<br />
});<br />
timer.start();<br />
print(timer.isRunning); # prints 1<br />
|example4text = In the example below, "Hello, World!" will be printed after one second the first time, and after two seconds thereafter.<br />
|example4 = var update = func(){<br />
print("Hello, World!");<br />
timer.restart(2); # restarts the timer with a two second interval<br />
}<br />
<br />
var timer = maketimer(1, update);<br />
timer.singleShot = 1;<br />
timer.start();<br />
}}<br />
<br />
=== maketimestamp() ===<br />
{{Nasal doc<br />
|syntax = maketimestamp()<br />
|source = ''Implemented using the {{API Link|flightgear|class|TimeStampObj}} class.''<br>{{flightgear file|src/Scripting/NasalSys.cxx|l=214|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=589|t=Part 2}}<br />
|version = 2019.2<br />
|commit = {{flightgear commit|7db06300|t=commit}}<br />
|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:<br />
* '''stamp()''': Resets the timing operation. Call this first.<br />
* '''elapsedMSec()''': returns number of milliseconds elapsed since stamp() called. Resolution may vary depending on platform but is usually at least millisecond accuracy.<br />
* '''elapsedUSec()''': returns number of microseconds elapsed since stamp() called. Resolution may vary depending on platform but is usually at least millisecond accuracy.<br />
|<br />
|example1text = In the example below the number of milliseconds elapsed will be printed.<br />
|example1 = var timestamp = maketimestamp();<br />
timestamp.stamp();<br />
print(timestamp.elapsedMSec(), "ms elapsed");<br />
print(timestamp.elapsedMSec(), "ms elapsed");<br />
}}<br />
<br />
=== md5() ===<br />
{{Nasal doc<br />
|syntax = md5(string);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=758|t=Source}}<br />
|version = 3.2<br />
|commit = {{flightgear commit|cfbf9e|t=commit}}<br />
|text = Returns a the {{wikipedia|MD5}} hash (as a string) of the inputted string.<br />
|param1 = string<br />
|param1text = String the generate the hash of. Mandatory.<br />
|example1text = The below code should output <code>65a8e27d8879283831b664bd8b7f0ad4</code>.<br />
|example1 = print(md5("Hello, World!"));<br />
}}<br />
<br />
=== navinfo() ===<br />
{{Nasal doc<br />
|syntax = navinfo(lat, lon, type, id);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1453|t=Source}}<br />
|text = Returns vector <code>navaid</code> ghost objects matching the given '''type''' and '''id''' or <code>'''nil'''</code> on error.<br />
|param1 = lat ''and'' lon<br />
|param1text = If given, the returned navaids will be put into order of ascending distance from the location.<br />
|param2 = type<br />
|param2text = Narrows the search to the given type. Must be one of "any," "fix," "vor," "ndb," "ils," "dme," or "tacan." Defaults to the equivalent of "any."<br />
|param3 = id<br />
|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.<br />
|example1 = navinfo("vor"); # returns all VORs<br />
|example2 = navinfo("HAM"); # return all navaids whose names start with "HAM"<br />
|example3 = navinfo("vor", "HAM"); # return all VORs whose names start with "HAM"<br />
|example4 = navinfo(34,48,"vor","HAM"); # return all VORs whose names start with "HAM" and sorted by distance relative to 34°, 48°<br />
}}<br />
<br />
=== parse_markdown() ===<br />
{{Nasal doc<br />
|syntax = parse_markdown(markdown);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=745|t=Part 1}} {{!}} {{simgear file|simgear/misc/SimpleMarkdown.cxx|t=Part 2}} <br />
|version = 3.2<br />
|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:<br />
* It strips whitespace from the beginning of the string.<br />
* It supports [http://daringfireball.net/projects/markdown/syntax#p paragraphs and line breaks].<br />
* It collapses whitespace.<br />
* It converts unordered [http://daringfireball.net/projects/markdown/syntax#list lists] to use a bullet character (&bull;). Note that the bullet character is implemented in hexadecimal UTF-8 character bytes (<code>E2 80 A2</code>), as so may not work properly when the being displayed in an encoding other than UTF-8.<br />
|param1 = markdown<br />
|param1text = String containing Markdown to be parsed.<br />
|example1text = <br />
Save the below code as <tt>''[[$FG_ROOT]]/gui/dialogs/test.xml''</tt>, 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 (<tt>Debug > Reload GUI</tt>).<br />
<syntaxhighlight lang="xml" highlight="41-50"><br />
<?xml version="1.0" encoding="UTF-8"?><br />
<br />
<PropertyList><br />
<br />
<name>test</name><br />
<layout>vbox</layout><br />
<br />
<group><br />
<layout>hbox</layout><br />
<br />
<empty><br />
<stretch>true</stretch><br />
</empty><br />
<br />
<text><br />
<label>parse_markdown() test dialog</label><br />
</text><br />
<br />
<empty><br />
<stretch>true</stretch><br />
</empty><br />
<br />
<button><br />
<legend></legend><br />
<pref-width>16</pref-width><br />
<pref-height>16</pref-height><br />
<binding><br />
<command>dialog-close</command><br />
</binding><br />
</button><br />
<br />
</group><br />
<br />
<canvas><br />
<name>Canvas plot</name><br />
<stretch>true</stretch><br />
<pref-width>400</pref-width><br />
<pref-height>300</pref-height><br />
<nasal><br />
<load><![CDATA[<br />
var text = 'Items:<br />
* apples<br />
* oranges<br />
* pears<br />
<br />
Some text.<br />
Some more items:<br />
* apples<br />
* oranges<br />
* pears';<br />
<br />
var parsed = parse_markdown(text);<br />
<br />
var root_canvas = canvas.get(cmdarg());<br />
root_canvas.setColorBackground(255, 255, 255);<br />
var root = root_canvas.createGroup();<br />
<br />
var text_dis = root.createChild("text")<br />
.setText(parsed)<br />
.setTranslation(5, 5)<br />
.setFont("LiberationFonts\LiberationSans-Regular.ttf")<br />
.setFontSize(15)<br />
.setColor(0, 0, 0)<br />
.setDrawMode(canvas.Text.TEXT)<br />
.setAlignment("left-top");<br />
]]></load><br />
</nasal><br />
</canvas><br />
<br />
</PropertyList><br />
</syntaxhighlight><br />
|example1 = fgcommand("dialog-show", {"dialog-name": "test"});<br />
|example2text = The example below parses Markdown and outputs it in a HTML document. The parsed text is placed in <syntaxhighlight lang="xml" inline><pre></pre></syntaxhighlight> tags. To change the Markdown to be parsed, simply edit the variable <tt>markdown</tt> at the top of the code.<br />
|example2 = <nowiki>var markdown = 'Items:<br />
* apples<br />
* oranges<br />
* pears<br />
<br />
Some text.<br />
Some more items:<br />
* apples<br />
* oranges<br />
* pears';<br />
<br />
var parsed = parse_markdown(markdown);<br />
<br />
debug.dump(parsed);<br />
<br />
var path = string.normpath(getprop("/sim/fg-home") ~ '/Export/parse_markdown()-test.html');<br />
<br />
var file = io.open(path, "w");<br />
<br />
var html = "<!DOCTYPE html>\n\n<html>\n\n<head>\n\t<meta charset=\"UTF-8\">\n\t<title>parse_markdown() test generated by Nasal</title>\n</head>\n\n<body>\n\t<pre>" ~ parsed ~ "</pre>\n</body>\n\n</html>";<br />
<br />
io.write(file, html);<br />
io.close(file);<br />
print("Done, file ready for viewing (" ~ path ~ ")");</nowiki><br />
}}<br />
<br />
=== parsexml() ===<br />
{{Nasal doc<br />
|syntax = parsexml(path[, start[, end[, data[, pro_ins]]]]);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=710|t=Source}}<br />
|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 <code>'''nil'''</code> is returned on error.<br />
|param1 = path<br />
|param1text = Mandatory absolute path to the XML file to be parsed.<br />
|param2 = start<br />
|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.<br />
|param3 = end<br />
|param3text = Optional callback function that will be called for every ending tag. The function should take one argument: the tag name.<br />
|param4 = data<br />
|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.<br />
|param5 = pro_ins<br />
|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.<br />
|example1text = Save the below XML code in <tt>''[[$FG_HOME]]/Export/demo.xml''</tt>. Then, execute the Nasal code below in the Nasal Console. The XML will be parsed and each bit of the code will be printed.<br />
<syntaxhighlight lang="xml"><br />
<?xml version="1.0" encoding="UTF-8"?><br />
<br />
<?xml-stylesheet type="text/xsl" href="style.xsl"?><br />
<br />
<foo><br />
<blah type="string"><![CDATA[ <sender>John Smith</sender> ]]></blah><br />
<blah2 type="string">Orange &amp; lemons</blah2><br />
</foo><br />
</syntaxhighlight><br />
|example1 = var start = func(name, attr){<br />
print("Starting tag: '", name, "'");<br />
foreach(var a; keys(attr)){<br />
print("\twith attribute ", a, '="', attr[a], '"');<br />
}<br />
}<br />
<br />
var end = func(name){<br />
print("Ending tag: '", name, "'");<br />
}<br />
<br />
var data = func(data){<br />
print("Data = '", data, "'");<br />
}<br />
<br />
var pro_instr = func(target, data){<br />
print("Processing instruction: target = '", target, "', data = '", data, "'");<br />
}<br />
<br />
parsexml(getprop("/sim/fg-home") ~ '/Export/demo.xml', start, end, data, pro_instr);<br />
}}<br />
<br />
=== print() ===<br />
{{Nasal doc<br />
|syntax = print(data[, data[, ...]]);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=415|t=Source}}<br />
|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.<br />
|param1 = data<br />
|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.<br />
|example1 = print("Just", " a ", "test"); # prints "Just a test"<br />
|example2 = print("pi = ", math.pi); # prints "pi = 3.141592..."<br />
}}<br />
<br />
=== printf() ===<br />
{{Nasal doc<br />
|syntax = printf(format[, arg[, arg, [...]]]);<br />
|source = {{fgdata file|Nasal/globals.nas|t=Source}}<br />
|text = Creates and prints a formatted string. For a description of its arguments, see {{func link|sprintf()}} (it is, in fact, implemented using <code>sprintf()</code>).<br />
|example1 = printf("In hexadecimal, 100000 = %X", 186A0); # prints "In hexadecimal, 100000 = 186A0"<br />
}}<br />
<br />
=== printlog() ===<br />
{{Nasal doc<br />
|syntax = printlog(level, data[, data[, ...]]);<br />
|source = {{fgdata file|Nasal/globals.nas|t=Source}}<br />
|text = Prints the given message with the given log level. If the log level is higher or equal to <code>/sim/logging/priority</code>, it is printed.<br />
|param1 = level<br />
|param1text = Mandatory log level as a string. Must be one of "none," "bulk," "debug," "info," "warn," or "alert." Note that "none" will mean that the message will never be printed.<br />
|param2 = data<br />
|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.<br />
|example1 = printlog("alert", "This is an alert"); # message will be printed<br />
|example2 = printlog("info", "Just informing you about something"); # message will be printed only if log level is set to "info" or less<br />
}}<br />
<br />
=== rand() ===<br />
{{Nasal doc<br />
|syntax = rand();<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=554|t=Source}}<br />
|text = Returns a random floating point number between 0 (inclusive) and 1 (exclusive). It takes no arguments.<br />
|example1 = print(rand()); # prints random number<br />
}}<br />
<br />
=== registerFlightPlanDelegate() ===<br />
{{Nasal doc<br />
|syntax = registerFlightPlanDelegate(init_func);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1879|t=Source}}<br />
|text = Registers a flight plan delegate. See <tt>''{{fgdata file|Nasal/route_manager.nas|t=$FG_ROOT/Nasal/route_manager.nas}}''</tt> for examples.<br />
|param1 = init_func<br />
|param1text = Initialization function which will be called during FlightGear's startup.<br />
}}<br />
=== removecommand() ===<br />
{{Nasal doc<br />
|syntax = removecommand(cmd);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=668|t=Source}}<br />
|text = Removes the given fgcommand. Returns <code>'''nil'''</code>.<br />
{{caution|This will remove '''any''' [[fgcommands|fgcommand]], even those implemented in C++, so use with caution!}}<br />
|param1 = cmd<br />
|param1text = String specifying the name of the command to remove.<br />
|example1 = addcommand("hello", func(){<br />
print("Hello");<br />
});<br />
fgcommand("hello"); # "Hello" will be printed<br />
removecommand("hello"); # removes it<br />
}}<br />
<br />
=== removelistener() ===<br />
{{Nasal doc<br />
|syntax = removelistener(id);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=1384|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=506|t=Part 2}}<br />
|text = Removes and deactivates the given listener and returns the number of listeners left or <code>'''nil'''</code> on error.<br />
{{note|It is good practice to remove listeners when they are not required anymore. This prevents the listeners reducing FlightGear's run performance.}}<br />
|param1 = id<br />
|param1text = ID of listener as returned by {{func link|setlistener()}}.<br />
|example1 = var ls = setlistener("/sim/test", func(){<br />
print("Property '/sim/test' has been changed");<br />
});<br />
setprop("/sim/test", "blah"); # trigger listener<br />
var rem = removelistener(ls); # remove listener<br />
print("There are ", rem, " listeners remaining");<br />
}}<br />
<br />
=== resolvepath() ===<br />
{{Nasal doc<br />
|syntax = resolvepath(path);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=604|t=Source}}<br />
|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.<br />
|param1 = path<br />
|param1text = Relative path to be completed.<br />
|example1 = print(resolvepath("Nasal/globals.nas")); # prints the equivalent of $FG_ROOT/Nasal/globals.nas<br />
|example2 = print(resolvepath("blah")); # prints nothing; could not be resolved<br />
|example3 = var file_path = resolvepath("Aircraft/SenecaII/some-file");<br />
if (file_path != ""){<br />
gui.popupTip("some-file found", 2);<br />
} else {<br />
gui.popupTip("some-file not found", 2);<br />
}<br />
}}<br />
<br />
=== setlistener() ===<br />
{{Nasal doc<br />
|syntax = setlistener(node, code[, init[, type]]);<br />
|private = _setlistener()<br />
|source = {{flightgear file|src/Scripting/Nasal|l=499|t=Part 1}} {{!}} {{flightgear file|src/Scripting/Nasal|l=1350|t=Part 2}}<br>''Listener implemented using the {{API Link|flightgear|class|FGNasalListener}} class.''<br />
|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()}}.<br />
{{note|Listeners are known to be a source of resource leaks. To avoid this, please take measures such as:<br />
* Using {{func link|removelistener()}} when they are not needed any more.<br />
* Using a single initialization listener.<br />
* Avoiding tying listeners to properties that are rapidly updated (e.g., many times per frame).<br />
}}<br />
|param1 = node<br />
|param1text = Mandatory string or <code>props.Node</code> object pointing to a property in the [[Property Tree]].<br />
|param2 = code<br />
|param2text = Mandatory callback function to execute when the listener is triggered. The function can take up to four arguments in the following order:<br />
* '''changed''': a <code>props.Node</code> object pointing to the changed node.<br />
* '''listen''': a <code>props.Node</code> object pointing to the listened-to node. Note that this argument maybe different depending on the '''type'''.<br />
* '''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.<br />
* '''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.<br />
|param3 = init<br />
|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).<br />
|param4 = type<br />
|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.<br />
|example1 = var prop = props.globals.initNode("/sim/test", "", "STRING"); # create property<br />
var id = setlistener("/sim/test", func(n){ # create listener<br />
print("Value: ", n.getValue());<br />
});<br />
setprop("/sim/test", "blah"); # trigger listener<br />
removelistener(id); # remove listener<br />
prop.remove(); # remove property<br />
}}<br />
<br />
=== setprop() ===<br />
{{Nasal doc<br />
|syntax = setprop(path[, path[, ...]], value);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=385|t=Source}}<br />
|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.<br />
{{note|If you want to remove a property, you will have to use one of the <code>props</code> helpers:<br />
<syntaxhighlight lang="nasal"><br />
props.globals.getNode("foo/bar").remove(); # take out the complete node<br />
props.globals.getNode("foo").removeChild("bar"); # take out a certain child node<br />
</syntaxhighlight><br />
}}<br />
|param1 = path<br />
|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.<br />
|param2 = value<br />
|param2text = Value to write to the given property. Must be either a string or a number.<br />
|example1 = setprop("/sim/demo", "This is a demo");<br />
|example2text = Note that the example below will only work in FlightGear 3.2 and above.<br />
|example2 = for(var i = 0; i < 3; i += 1){<br />
setprop("/sim/demo", i, "Demo #" ~ i));<br />
}<br />
|example3text = Same as above, but is supported by all versions of FlightGear.<br />
|example3 = for(var i = 0; i < 3; i += 1){<br />
setprop("/sim/demo[" ~ i ~ "]", "Demo #" ~ i));<br />
}<br />
}}<br />
==== See also ====<br />
{{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: <br />
To get a Node rather than its value, use <code>props.globals.getNode()</code> - see [[Nasal_library/props]]. }}<br />
<br />
=== settimer() ===<br />
{{Nasal doc<br />
|syntax = settimer(function, delta[, realtime]);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=499|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=1286|t=Part 2}}<br />
|text = Runs the given function a specified amount of seconds after the current time. Returns <code>'''nil'''</code>.<br />
{{note|Improper use of <code>settimer()</code> may cause resource leaks. It is also highly recommended that the newer {{func link|maketimer()}} should be used instead of this function.}} <br />
|param1 = function<br />
|param1text = Mandatory function that will be called. It may be necessary to enclose code in an anonymous function (see example).<br />
|param2 = delta<br />
|param2text = Mandatory amount of time in seconds after which the function will be called.<br />
|param3 = realtime<br />
|param3text = If 1 (true), "real time" will be used instead of "simulation time." Defaults to 0 (false). Note that if "simulation time" is used, the timer will not run while FlightGear is paused.<br />
|example1 = var myFunc = func(){<br />
print("Hello");<br />
}<br />
<br />
settimer(myFunc, 2); # runs myFunc after 2 seconds<br />
|example2 = var sqr = func(a){<br />
return a * a;<br />
}<br />
<br />
settimer(func(){<br />
print(sqr(2)); # will print 4 after 2 seconds<br />
}, 2);<br />
}}<br />
<br />
=== srand() ===<br />
{{Nasal doc<br />
|syntax = srand();<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=559|t=Source}}<br />
|text = Makes the pseudorandom number generator (see {{func link|rand()}}) generate a new {{wikipedia|random seed|noicon=1}} based on time. Returns 0.<br />
}}<br />
=== systime() ===<br />
{{Nasal doc<br />
|syntax = systime();<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=770|t=Source}}<br />
|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).<br />
{{note|1=High resolution timers under Windows can produce inaccurate or fixed sub-millisecond results.<ref>{{cite web|url=http://forum.flightgear.org/viewtopic.php?f=30&t=29259|title=Nasal: systime() ??!?|author=Necolatis|date=Apr 2nd, 2016}}</ref> 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]}}<br />
|example1 = print("Unix time: ", systime()); # prints Unix time<br />
|example2 = var myFunc = func(){<br />
for(var i = 0; i <= 10; i += 1){<br />
print("Interation #", i);<br />
}<br />
}<br />
var t = systime(); # record time<br />
myFunc(); # run function<br />
var t2 = systime(); # record new time<br />
print("myFunc() took ", t2 - t, " seconds"); # print result<br />
}}<br />
<br />
=== thisfunc() ===<br />
{{Nasal doc<br />
|syntax = thisfunc();<br />
|source = {{fgdata file|Nasal/globals.nas|t=Source}}<br />
|text = Returns the function from which this function is called. This allows a function to reliably and safely call itself from within a closure.<br />
|example1 = var stringify_vec = func(input){<br />
if (typeof(input) == "scalar"){<br />
return sprintf("%s", input);<br />
} elsif (typeof(input) == "vector") {<br />
if (size(input) == 0) return "[]";<br />
var this = thisfunc();<br />
var buffer = "[";<br />
for(var i = 0; i < size(input); i += 1){<br />
buffer ~= this(input[i]);<br />
if (i == size(input) - 1) {<br />
buffer ~= "]";<br />
} else {<br />
buffer ~= ", ";<br />
}<br />
}<br />
return buffer;<br />
} else {<br />
die("stringify_vec(): Error! Invalid input. Must be a vector or scalar");<br />
}<br />
}<br />
<br />
var test_vec = ["a", "b", "c", 1, 2, 3];<br />
debug.dump(stringify_vec(test_vec)); # prints "[a, b, c, 1, 2, 3]"<br />
test_vec = [];<br />
debug.dump(stringify_vec(test_vec)); # prints "[]"<br />
test_vec = {};<br />
debug.dump(stringify_vec(test_vec)); # will throw an error<br />
}}<br />
<br />
=== tileIndex() ===<br />
{{Nasal doc<br />
|syntax = tileIndex();<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1720|t=Source}}<br />
|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 <code>942050</code>, corresponding to <tt>''[[$FG_SCENERY]]/Terrain/w130n30/w123n3/942050.stg''</tt>.<br />
|example1 = print(tileIndex()); # print index<br />
}}<br />
<br />
=== tilePath() ===<br />
{{Nasal doc<br />
|syntax = tilePath();<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1712|t=Source}}<br />
|text = Returns the base path of the tile at the aircraft's current position as a string. For example, at KSFO, this would be <code>w130n30/w123n3</code>, corresponding to <tt>''[[$FG_SCENERY]]/Terrain/w130n30/w123n3''</tt>.<br />
|example1 = print(tilePath()); # print path<br />
}}<br />
<br />
=== values() ===<br />
{{Nasal doc<br />
|syntax = values(hash);<br />
|source = {{fgdata file|Nasal/globals.nas|t=Source}}<br />
|text = Returns a vector containing the values of the given hash.<br />
|param1 = hash<br />
|param1text = Mandatory hash to get the values of.<br />
|example1 = var hash = {<br />
"a": 1,<br />
"b": 2,<br />
"c": 3<br />
};<br />
<br />
foreach(var val; values(hash)){<br />
print(val);<br />
}<br />
|example2text = The below example does exactly the same thing as the above example, but does not use <code>values()</code>:<br />
|example2 = var hash = {<br />
"a": 1,<br />
"b": 2,<br />
"c": 3<br />
};<br />
<br />
foreach(var key; keys(hash)){<br />
print(hash[key]);<br />
}<br />
}}<br />
<br />
== Extension functions new in FG 2020.1 ==<br />
The following functions have been added to the nasal core library and will be released with FlightGear version 2020.1. <br />
Befor the release they are available in the development branch "next".<br />
<br />
=== isint() ===<br />
Returns 1 if argument has a numeric value and x == floor(x), e.g. integer<br />
<br />
=== isnum() ===<br />
{{Nasal doc<br />
|syntax = isnum(x);<br />
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}<br />
|text = Returns 1 if x has a numeric value otherwise 0. <br />
}}<br />
<br />
=== isghost() ===<br />
Returns 1 if type or argument is a ghost, otherwise 0.<br />
<br />
=== isstr() ===<br />
Returns 1 if type or argument is a string, otherwise 0.<br />
<br />
=== isvec() ===<br />
Returns 1 if type or argument is a vector, otherwise 0.<br />
<br />
=== ishash() ===<br />
Returns 1 if type or argument is a hash, otherwise 0.<br />
<br />
=== isfunc() ===<br />
Returns 1 if type or argument is a function, otherwise 0.<br />
<br />
=== vecindex() ===<br />
{{Nasal doc<br />
|syntax = vecindex(vector, value);<br />
|source = {{simgear file|simgear/nasal/lib.c|t=Source}}<br />
|text = Returns the index of value or nil, if value is not found in vector.<br />
}}<br />
== Variables ==<br />
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}}.<br />
<br />
=== D2R ===<br />
{{Nasal doc<br />
|syntax = var radians = degrees * D2R;<br />
|text = Converts an angle from degrees to radians when multiplied by the angle in degrees. Equal to <code>π / 180</code>.<br />
}}<br />
=== FPS2KT ===<br />
{{Nasal doc<br />
|syntax = var knots = feet_per_second * FPS2KT;<br />
|text = Converts a velocity from feet per second to knots when multiplied by the velocity in feet per second. Approximately equal to 0.5925.<br />
}}<br />
=== FT2M ===<br />
{{Nasal doc<br />
|syntax = var metres = feet * FT2M;<br />
|text = Converts a length from feet to metres when multiplied by the length in feet. Equal to 0.3048.<br />
}}<br />
=== GAL2L ===<br />
{{Nasal doc<br />
|syntax = var litres = gallons * GAL2L;<br />
|text = Converts a volume from US liquid gallons to litres when multiplied by the volume in gallons. Approximately equal to 3.7854.<br />
}}<br />
=== IN2M ===<br />
{{Nasal doc<br />
|syntax = var metres = inches * IN2M;<br />
|text = Converts a length from inches to metres when multiplied by the length in inches. Equal to 0.0254.<br />
}}<br />
=== KG2LB ===<br />
{{Nasal doc<br />
|syntax = var pounds = kilograms * KG2LB;<br />
|text = Converts a mass from kilograms to pounds when multiplied by the mass in kilograms. Approximately equal to 2.2046.<br />
}}<br />
=== KT2FPS ===<br />
{{Nasal doc<br />
|syntax = var feet_per_second = knots * KT2FPS;<br />
|text = Converts a velocity from knots to feet per second when multiplied by the velocity in knots. Approximately equal to 1.6878.<br />
}}<br />
=== KT2MPS ===<br />
{{Nasal doc<br />
|syntax = var metres_per_second = knots * KT2MPS;<br />
|text = Converts a velocity from knots to metres per second when multiplied by the velocity in knots. Approximately equal to 0.5144.<br />
}}<br />
=== L2GAL ===<br />
{{Nasal doc<br />
|syntax = var gallons = litres * L2GAL;<br />
|text = Converts a volume from litres to US liquid gallons when multiplied by the volume in litres. Approximately equal to 0.2642.<br />
}}<br />
=== LB2KG ===<br />
{{Nasal doc<br />
|syntax = var kilograms = pounds * LB2KG;<br />
|text = Converts a mass from pounds to kilograms when multiplied by the mass in pounds. Approximately equal to 0.4536.<br />
}}<br />
=== M2FT ===<br />
{{Nasal doc<br />
|syntax = var feet = metres * M2FT;<br />
|text = Converts a length from metres to feet when multiplied by the length in metres. Approximately equal to 3.2808.<br />
}}<br />
=== M2IN ===<br />
{{Nasal doc<br />
|syntax = var inches = metres * M2IN;<br />
|text = Converts a length from metres to inches when multiplied by the length in metres. Approximately equal to 39.3701.<br />
}}<br />
=== M2NM ===<br />
{{Nasal doc<br />
|syntax = var nautical_miles = metres * M2NM;<br />
|text = Converts a length from metres to nautical miles when multiplied by the length in metres. Approximately equal to 0.00054.<br />
}}<br />
=== MPS2KT ===<br />
{{Nasal doc<br />
|syntax = var knots = metres_per_second * MPS2KT;<br />
|text = Converts a velocity from metres per second to knots when multiplied by the velocity in metres per second. Approximately equal to 1.9438.<br />
}}<br />
=== NM2M ===<br />
{{Nasal doc<br />
|syntax = var metres = nautical_miles * NM2M;<br />
|text = Converts a length from nautical miles to metres when multiplied by the length in nautical miles. Equal to 1,852.<br />
}}<br />
=== R2D ===<br />
{{Nasal doc<br />
|syntax = var degrees = radians * R2D;<br />
|text = Converts an angle from radians to degrees when multiplied by the angle in radians. Equal to <code>180 / π</code>.<br />
}}<br />
<br />
{{Appendix}}<br />
<br />
<br />
{{Nasal namespaces}}</div>Jsbhttps://wiki.flightgear.org/w/index.php?title=Nasal_library&diff=123726Nasal library2020-04-16T14:38:57Z<p>Jsb: /* setprop() */</p>
<hr />
<div>{{Nasal Navigation|nocat=1}}<br />
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:<br />
* {{flightgear file|src/Scripting}}<br />
* {{simgear file|simgear/nasal}}<br />
<br />
All these functions and variables are in the global namespace, that is, they are directly accessible (e.g., one can call <syntaxhighlight lang="nasal" inline>magvar()</syntaxhighlight> instead of <syntaxhighlight lang="nasal" inline>namespace.magvar()</syntaxhighlight>). However, if a namespace must be used, <code>globals</code> is the correct namespace, but using it is not recommended. For a more complete explanation, see [[Nasal Namespaces in-depth]].<br />
<br />
{{tip|Copy & paste the examples into your [[Nasal Console]] and execute them to see what they do.|width=70%}}<br />
<br />
== Core library functions ==<br />
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:<br />
* http://plausible.org/nasal/lib.html ([http://web.archive.org/web/20101010094553/http://plausible.org/nasal/lib.html archive])<br />
* {{simgear file|simgear/nasal/lib.c}} ([http://sourceforge.net/p/flightgear/simgear/ci/next/log/?path=/simgear/nasal/lib.c history])<br />
<br />
=== append() ===<br />
{{Nasal doc<br />
|syntax = append(vector, element[, element[, ...]]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=42|t=Source}}<br />
|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.<br />
|param1 = vector<br />
|param1text = The vector to which the arguments will be appended.<br />
|param2 = element<br />
|param2text = An element to be added to the vector.<br />
|example1 = <br />
var vector = [1, 2, 3]; # Initialize the vector<br />
append(vector, 4); # Append the number 4 to the end of the vector<br />
debug.dump(vector); # Print the contents of the vector<br />
|example2 = <br />
var vector = [1, 2, 3]; # Initialize the vector<br />
append(vector, 4, 5, 6); # Append the numbers 4, 5, and 6 to the end of the vector<br />
debug.dump(vector); # Print the contents of the vector<br />
}}<br />
<br />
=== bind() ===<br />
{{Nasal doc<br />
|syntax = bind(function, locals[, outer_scope]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=502|t=Source}}<br />
|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.<br />
|param1 = function<br />
|param1text = Function to evaluate.<br />
|param2 = locals<br />
|param2text = Hash containing values that will become the namespace (first closure) for the function.<br />
|param3 = outer_scope<br />
|param3text = Optional function which is bound to the next closure. This can be bound to yet another, making a linked list.<br />
|example1 = # This is a namespace/hash with a single member, named "key," which is initialized to 12 <br />
var Namespace = {<br />
key: 12<br />
};<br />
<br />
# This is different namespace/hash containing a function<br />
# dividing a variable "key" (which is unavailable/nil in this namespace) by 2<br />
var AnotherNamespace = {<br />
ret: func {<br />
key /= 2;<br />
}<br />
};<br />
<br />
# To see that key is not available, try to call AnotherNamespace.ret() first<br />
call(AnotherNamespace.ret, [], nil, nil, var errors = []);<br />
if(size(errors)){<br />
print("Key could not be divided/resolved!");<br />
debug.printerror(errors);<br />
}<br />
<br />
# Associate the AnotherNamespace.ret() function with the first namespace<br />
# so that "key" is now available<br />
var function = bind(AnotherNamespace.ret, Namespace);<br />
<br />
# Invoke the new function<br />
function();<br />
<br />
# Print out the value of Namespace.key<br />
# It was changed to 12 from 6 by AnotherNamespace.ret()<br />
print(Namespace.key);<br />
}}<br />
<br />
=== call() ===<br />
{{Nasal doc<br />
|syntax = call(func[, args[, me[, locals[, error]]]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=247|t=Source}}<br />
|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. <br />
|param1 = func<br />
|param1text = Function to execute.<br />
|param2 = args<br />
|param2text = Vector containing arguments to give to the called function.<br />
|param3 = me<br />
|param3text = <code>'''me'''</code> reference for the function call (i.e., for method calls). If given, this will override any <code>'''me'''</code> value existing in the namespace (locals argument).<br />
|param4 = locals<br />
|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.<br />
|param5 = error<br />
|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}}.<br />
|example1 =<br />
# prints "Called from call()"<br />
call(func {<br />
print("Called from call()");<br />
});<br />
|example2 =<br />
# prints "a = 1 : b = 2<br />
call(func(a, b){<br />
print("a = ", a, " : b = ", b);<br />
},<br />
[1, 2]<br />
);<br />
|example3 =<br />
var Hash = {<br />
new: func {<br />
var m = { parents: [Hash] };<br />
<br />
m.el1 = "string1";<br />
m.el2 = "string2";<br />
<br />
return m;<br />
}<br />
};<br />
<br />
# prints "me.el1 = string1", then "me.el2 = string2" on the next line<br />
call(func(a, b){ <br />
print("me.el", a, " = ", me["el" ~ a]); <br />
print("me.el", b, " = ", me["el" ~ b]);<br />
},<br />
[1, 2],<br />
Hash.new()<br />
);<br />
|example4 =<br />
# prints the value of math.pi<br />
call(func {<br />
print(pi);<br />
}, nil, nil, <br />
math<br />
);<br />
|example5 =<br />
call(func {<br />
print(math.ip); # math.ip doesn't exist<br />
}, nil, nil, nil,<br />
var errs = []<br />
);<br />
debug.printerror(errs); # The error is caught and printed using debug.printerror()<br />
}}<br />
<br />
=== caller() ===<br />
{{Nasal doc<br />
|syntax = caller([level]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=404|t=Source}}<br />
|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.<br />
<br />
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. <br />
|param1 = level<br />
|param1text = Optional integer specifying the stack level to return a result from. Defaults to 1 (i.e. the caller of the currently executing function).<br />
|example1 =<br />
var myFunction = func(a, b){<br />
debug.dump(caller(0)[0]); # prints a hash of local variables, including arguments a and b<br />
return 2 * 2;<br />
};<br />
<br />
print("2 x 2 = ", myFunction(2, 2));<br />
|example2 =<br />
var get_arg_value = func(){<br />
print("Argument to myFunc = ", caller(1)[0]['a']); # print the value of myFunc's single argument, using caller()<br />
};<br />
<br />
var myFunc = func(a){<br />
get_arg_value();<br />
};<br />
<br />
myFunc(3);<br />
|example3text = This is a real example taken from {{fgdata file|Nasal/canvas/MapStructure.nas}}. Function <code>r()</code> (above the TODOs) returns a hash with the key/value pairs as per its arguments. For example, something like this is returned: <code>{ name: "<name>", vis: 1, zindex: nil }</code>.<br />
|example3 =<br />
var MapStructure_selfTest = func() {<br />
var temp = {};<br />
temp.dlg = canvas.Window.new([600,400],"dialog");<br />
temp.canvas = temp.dlg.createCanvas().setColorBackground(1,1,1,0.5);<br />
temp.root = temp.canvas.createGroup();<br />
var TestMap = temp.root.createChild("map");<br />
TestMap.setController("Aircraft position");<br />
TestMap.setRange(25); # TODO: implement zooming/panning via mouse/wheel here, for lack of buttons :-/<br />
TestMap.setTranslation(<br />
temp.canvas.get("view[0]")/2,<br />
temp.canvas.get("view[1]")/2<br />
);<br />
var r = func(name,vis=1,zindex=nil) return caller(0)[0];<br />
# TODO: we'll need some z-indexing here, right now it's just random<br />
# TODO: use foreach/keys to show all layers in this case by traversing SymbolLayer.registry direclty ?<br />
# maybe encode implicit z-indexing for each lcontroller ctor call ? - i.e. preferred above/below order ?<br />
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'), ] ) <br />
TestMap.addLayer(factory: canvas.SymbolLayer, type_arg: type.name,<br />
visible: type.vis, priority: type.zindex,<br />
);<br />
}; # MapStructure_selfTest<br />
}}<br />
<br />
=== chr() ===<br />
{{Nasal doc<br />
|syntax = chr(code);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=175|t=Source}}<br />
|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 (<code>"string"</code>) supports control chracters. Strings in single quotes (<code>'string'</code>) do not.}}<br />
{{{!}} class="wikitable"<br />
! Code !! Name !! Equivalent to<br />
{{!-}}<br />
{{!}} 10 {{!!}} {{Wikipedia|Newline}} {{!!}} <code>\n</code><br />
{{!-}}<br />
{{!}} 9 {{!!}} {{Wikipedia|Tab key#Tab characters|Horizontal tab}} {{!!}} <code>\t</code><br />
{{!-}}<br />
{{!}} 13 {{!!}} {{Wikipedia|Carriage return}} {{!!}} <code>\r</code><br />
{{!}}}<br />
|param1 = code<br />
|param1text = Integer character code for the desired glyph.<br />
|example1 = print("Code 65 = ", chr(65)); # prints "Code 65 = A"<br />
|example2text = This example displays all of the characters in a list, in the format <code>Code '''n''' = >'''char'''<</code>, '''n''' being the index, and '''char''' being the character.<br />
|example2 =<br />
for(var i = 0; i <= 255; i += 1){<br />
print("Code ", i, " = >", chr(i), "<");<br />
}<br />
}}<br />
<br />
=== closure() ===<br />
{{Nasal doc<br />
|syntax = closure(func[, level]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=421|t=Source}}<br />
|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'''. <br />
|param1 = func<br />
|param1text = Function to evaluate.<br />
|param2 = level<br />
|param2text = Optional integer specifying the scope level. Defaults to 0 (the namespace of '''func''').<br />
|example1 =<br />
var get_math_e = func {<br />
return e; # return the value of math.e<br />
}<br />
<br />
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<br />
debug.dump(closure(myFunction)); # print the namespace of get_math_e<br />
<br />
print(myFunction());<br />
}}<br />
<br />
=== cmp() ===<br />
{{Nasal doc<br />
|syntax = cmp(a, b);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=112|t=Source}}<br />
|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'''. <br />
|param1 = a<br />
|param1text = First string argument for comparison.<br />
|param2 = b<br />
|param2text = Second string argument for comparison.<br />
|example1 = print(cmp("1", "two")); # prints -1<br />
|example2 = print(cmp("string", "string")); # prints 0<br />
|example3 = print(cmp("one", "2")); # prints 1<br />
|example4 = print(cmp("string1", "string2")); # prints -1<br />
}}<br />
<br />
=== compile() ===<br />
{{Nasal doc<br />
|syntax = compile(code[, filename]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=220|t=Source}}<br />
|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'''.<br />
|param1 = code<br />
|param1text = String containing Nasal code to be compiled.<br />
|param2 = filename<br />
|param2text = Optional string used for error messages/logging. Defaults to <code><compile></code><br />
|example1 = <br />
var myCode = 'print("hello");';<br />
var helloFunc = compile(myCode, "myCode");<br />
helloFunc();<br />
|example2text = <code>compile</code> 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 <tt>''[[$FG_ROOT]]/gui/dialogs/test.xml''</tt>.<br />
<syntaxhighlight lang="xml"><br />
<?xml version="1.0"?><br />
<br />
<PropertyList><br />
<br />
<nasal><![CDATA[<br />
print("You have FlightGear v", getprop("/sim/version/flightgear"));<br />
]]></nasal><br />
<br />
</PropertyList><br />
</syntaxhighlight><br />
Now, start FlightGear and execute this code in the [[Nasal Console]].<br />
|example2 =<br />
# Build the path<br />
var FGRoot = getprop("/sim/fg-root");<br />
var filename = "/gui/dialogs/test.xml";<br />
var path = FGRoot ~ filename;<br />
<br />
var blob = io.read_properties(path);<br />
var script = blob.getValues().nasal; # Get the nasal string<br />
<br />
# Compile the script. We're passing the filename here for better runtime diagnostics <br />
var code = call(func {<br />
compile(script, filename);<br />
}, nil, nil, var compilation_errors = []);<br />
<br />
if(size(compilation_errors)){<br />
die("Error compiling code in: " ~ filename);<br />
}<br />
<br />
# Invoke the compiled script, equivalent to code(); <br />
# We're using call() here to detect errors:<br />
call(code, [], nil, nil, var runtime_errors = []);<br />
<br />
if(size(runtime_errors)){<br />
die("Error calling code compiled loaded from: " ~ filename);<br />
}<br />
}}<br />
<br />
=== contains() ===<br />
{{Nasal doc<br />
|syntax = contains(hash, key);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=184|t=Source}}<br />
|text = Returns 1 (True) if the hash contains the specified key, or 0 (False) if not.<br />
|param1 = hash<br />
|param1text = The hash to search in.<br />
|param2 = key<br />
|param2text = The scalar to be searched for, contained as a key in the hash.<br />
|example1 =<br />
# Initialize a hash<br />
var hash = {<br />
element: "value"<br />
};<br />
print(contains(hash, "element") ? "Yes" : "No"); # This will print "Yes"<br />
|example2 =<br />
# Initialize a hash<br />
var hash = {<br />
element: "value"<br />
};<br />
print(contains(hash, "element2") ? "Yes" : "No"); # This will print "No"<br />
}}<br />
<br />
=== delete() ===<br />
{{Nasal doc<br />
|syntax = delete(hash, key);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=83|t=Source}}<br />
|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 <code>'''nil'''</code> 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.<br />
|param1 = hash<br />
|param1text = The hash from which to delete the key.<br />
|param2 = key<br />
|param2text = The scalar to be deleted, contained as a key in the hash.<br />
|example1 =<br />
# Initialize the hash<br />
var hash = {<br />
element1: "value1",<br />
element2: "value2"<br />
};<br />
delete(hash, "element1"); # Delete element1<br />
debug.dump(hash); # prints the hash, which is now minus element1<br />
}}<br />
<br />
=== die() ===<br />
{{Nasal doc<br />
|syntax = die(error);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=288|t=Source}}<br />
|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()}}.<br />
|param1 = error<br />
|param1text = String describing the error.<br />
:{{inote|This parameter is technically optional, but it is highly recommended to use it.}}<br />
|example1 = <br />
print("Will print");<br />
die("Don't go any further!"); <br />
print("Won't print"); # Will not be printed because die() stops the process<br />
}}<br />
<br />
=== find() ===<br />
{{Nasal doc<br />
|syntax = find(needle, haystack);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=450|t=Source}}<br />
|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.<br />
|param1 = needle<br />
|param1text = String to search for.<br />
|param2 = haystack<br />
|param2text = String to search in.<br />
|example1 = print(find("c", "abcdef")); # prints 2<br />
|example2 = print(find("x", "abcdef")); # prints -1<br />
|example3 = print(find("cd", "abcdef")); # prints 2<br />
}}<br />
<br />
=== ghosttype() ===<br />
{{Nasal doc<br />
|syntax = ghosttype(ghost);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=207|t=Source}}<br />
|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++ <code>naGhostType</code> 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.<br />
|param1 = ghost<br />
|param1text = Ghost to return a description for.<br />
|example1 = print(ghosttype(airportinfo())); # prints "airport"<br />
}}<br />
<br />
=== id() ===<br />
{{Nasal doc<br />
|syntax = id(object);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=570|t=Source}}<br />
|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 <code>'''<type>''':'''<id>'''</code>, where '''<type>''' is the type of object, and '''<id>''' is the ID.<br />
|param1 = object<br />
|param1text = Can be either of a string, a vector, a hash, a code, a function, or a ghost.<br />
|example1 = print(id("A")); # prints "str:000000001624A590"<br />
}}<br />
<br />
=== int() ===<br />
{{Nasal doc<br />
|syntax = int(number);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=90|t=Source}}<br />
|text = Returns the integer part of the numeric value of the single argument, or <code>'''nil'''</code> if none exists.<br />
|param1 = number<br />
|param1text = Number or string with just a number in it to return an integer from.<br />
|example1 = print(int(23)); # prints "23"<br />
|example2 = print(int(23.123)); # prints "23"<br />
|example3 = debug.dump(int("string")); # prints "nil"<br />
}}<br />
<br />
=== keys() ===<br />
{{Nasal doc<br />
|syntax = keys(hash);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=33|t=Source}}<br />
|text = Returns a vector containing the list of keys found in the single hash argument. <br />
|param1 = hash<br />
|param1text = The hash to return the keys from.<br />
|example1 = <br />
# Initialize a hash<br />
var hash = {<br />
element1: "value",<br />
element2: "value"<br />
};<br />
debug.dump(keys(hash)); # print the vector<br />
}}<br />
<br />
=== left() ===<br />
{{Nasal doc<br />
|syntax = left(string, length);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=149|t=Source}}<br />
|version = 2.12<br />
|commit = {{simgear commit|bd7163|t=commit}}<br />
|text = Returns a substring of '''string''', starting from the left.<br />
|param1 = string<br />
|param1text = String to return part of.<br />
|param2 = length<br />
|param2text = Integer specifying the length of the substring to return.<br />
|example1 = print(left("string", 2)); # prints "st"<br />
}}<br />
<br />
=== num() ===<br />
{{Nasal doc<br />
|syntax = num(number);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=102|t=Source}}<br />
|text = Returns the numerical value of the single string argument, or <code>'''nil'''</code> if none exists. <br />
|param1 = number<br />
|param1text = String with just a number in it to return a number from.<br />
|example1 = print(num("23")); # prints "23"<br />
|example2 = print(num("23.123")); # prints "23.123"<br />
|example3 = debug.dump(num("string")); # prints "nil"<br />
}}<br />
<br />
=== pop() ===<br />
{{Nasal doc<br />
|syntax = pop(vector);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=50|t=Source}}<br />
|text = Removes and returns the last element of the single vector argument, or <code>'''nil'''</code> if the vector is empty. <br />
|param1 = vector<br />
|param1text = Vector to remove an element from.<br />
|example1 = <br />
var vector = [1, 2, 3];<br />
pop(vector);<br />
debug.dump(vector); # prints "[1, 2]"<br />
|example2 = <br />
var vector = [1, 2, 3];<br />
debug.dump(pop(vector)); # prints "3"<br />
|example3 = <br />
var vector = [];<br />
debug.dump(pop(vector)); # prints "nil"<br />
}}<br />
<br />
=== right() ===<br />
{{Nasal doc<br />
|syntax = right(string, length);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=161|t=Source}}<br />
|version = 2.12<br />
|commit = {{simgear commit|bd7163|t=commit}}<br />
|text = Returns a substring of '''string''', starting from the right.<br />
|param1 = string<br />
|param1text = String to return part of.<br />
|param2 = length<br />
|param2text = Integer specifying the length of the substring to return.<br />
|example1 = print(right("string", 2)); # prints "ng"<br />
}}<br />
<br />
=== setsize() ===<br />
{{Nasal doc<br />
|syntax = setsize(vector, size);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=56|t=Source}}<br />
|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 <code>'''nil'''</code> entries. Returns the vector operated upon. <br />
|param1 = vector<br />
|param1text = The vector to be operated on.<br />
|param2 = size<br />
|param2text = The desired size of the vector in number of entries.<br />
|example1 = <br />
var vector = [1, 2, 3]; # Initialize a vector<br />
setsize(vector, 4);<br />
debug.dump(vector); # print the vector<br />
|example2 = <br />
var vector = [1, 2, 3]; # Initialize a vector<br />
setsize(vector, 2);<br />
debug.dump(vector); # print the vector<br />
}}<br />
<br />
=== size() ===<br />
{{Nasal doc<br />
|syntax = size(object);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=23|t=Source}}<br />
|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 <code>'''nil'''</code> or a number, this error will be thrown: <code>object has no size()</code>.<br />
|param1 = object<br />
|param1text = Object to find the size of. Must be a string, a vector or a hash.<br />
|example1 = <br />
var string = "string";<br />
print(size(string)); # prints "6"<br />
|example2 =<br />
var vector = [1, 2, 3];<br />
print(size(vector)); # prints "3"<br />
|example3 =<br />
var hash = {<br />
element1: "value1",<br />
element2: "value2",<br />
element3: "value3"<br />
};<br />
print(size(hash)); # prints "3"<br />
}}<br />
<br />
=== sort() ===<br />
{{Nasal doc<br />
|syntax = sort(vector, function);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=542|t=Source}}<br />
|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/}}, <code>sort()</code> 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.<br />
|param1 = vector<br />
|param1text = Input vector to sort.<br />
|param2 = function<br />
|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.<br />
{{{!}} class="wikitable"<br />
! Return value !! Meaning<br />
{{!-}}<br />
{{!}} less than 0 {{!!}} first argument should go before second argument<br />
{{!-}}<br />
{{!}} 0 {{!!}} first argument equals second argument<br />
{{!-}}<br />
{{!}} greater than 0 {{!!}} first argument should go after second argument<br />
{{!}}}<br />
<br />
|example1text = This example sorts elements from smallest to greatest.<br />
|example1 = <br />
var sort_rules = func(a, b){<br />
if(a < b){<br />
return -1; # A should before b in the returned vector<br />
}elsif(a == b){<br />
return 0; # A is equivalent to b <br />
}else{<br />
return 1; # A should after b in the returned vector<br />
}<br />
}<br />
debug.dump(sort([3, 2, 5, 6, 4, 1], sort_rules)); # prints "[1, 2, 3, 4, 5, 6]"<br />
|example2text = This example sorts elements from greatest to smallest.<br />
|example2 = <br />
# Outputs the elements in reverse order (greatest to smallest)<br />
var sort_rules = func(a, b){<br />
if(a < b){<br />
return 1; # -1 in the above example<br />
}elsif(a == b){<br />
return 0;<br />
}else{<br />
return -1; # 1 in the above example<br />
}<br />
}<br />
debug.dump(sort([3, 2, 5, 6, 4, 1], sort_rules)); # prints "[6, 5, 4, 3, 2, 1]"<br />
|example3text = This example sorts a vector of strings (runways for example) from smallest to greatest.<br />
|example3 = <br />
var runways = ["09R","27R","26L","09L","15"];<br />
var rwy = sort(runways,func(a,b) cmp(a,b));<br />
debug.dump(rwy); # prints ['09L','09R','15','26L','27R']<br />
}}<br />
<br />
=== split() ===<br />
{{Nasal doc<br />
|syntax = split(delimiter, string);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=460|t=Source}}<br />
|text = Splits the input string into a vector of substrings bounded by occurrences of the delimiter substring.<br />
|param1 = delimiter<br />
|param1text = String that will split the substrings in the returned vector.<br />
|param2 = string<br />
|param2text = String to split up.<br />
|example1 = debug.dump(split("cd", "abcdef")); # prints "['ab', 'ef']"<br />
|example2 = debug.dump(split(".", "3.2.0")); # prints "[3, 2, 0]"<br />
|example3 = debug.dump(split("/", "path/to/file")); # prints "['path', 'to', 'file']"<br />
}}<br />
<br />
=== sprintf() ===<br />
{{Nasal doc<br />
|syntax = sprintf(format[, arg[, arg, [...]]]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=355|t=Source}}<br />
|text = Creates and returns a string formatted using ANSI C {{func link|vsnprintf()|link=http://en.cppreference.com/w/c/io/vfprintf}} <ref><br />
{{Cite web<br />
|url = http://sourceforge.net/p/flightgear/simgear/ci/next/tree/simgear/nasal/lib.c#l308<br />
|title = fgdata/simgear/simgear/nasal/lib.c, line 308<br />
|accessdate = October 2015<br />
}}<br />
</ref>. Below is a table of supported format specifiers.<br />
{{{!}} class="wikitable" width="75%"<br />
{{!}}+ %[flags][width][.precision]specifier<br />
! colspan="2" {{!}} Flags<br />
{{!-}}<br />
! Flag !! Output<br />
{{!-}}<br />
{{!}} <code>+</code> {{!!}} 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.<br />
{{!-}}<br />
{{!}} ''space'' {{!!}} Prefixes non-signed numbers with a space.<br />
{{!-}}<br />
{{!}} <code>-</code> {{!!}} Left-align the output of this placeholder (the default is to right-align the output) when the width option is specified.<br />
{{!-}}<br />
{{!}} <code>0</code> {{!!}} Use 0 instead of spaces to pad a field when the width option is specified.<br />
{{!-}}<br />
{{!}} <code>#</code> {{!!}} Used with <code>o</code>, <code>x</code> or <code>X</code> specifiers the value is preceded with <tt>0</tt>, <tt>0x</tt> or <tt>0X</tt> respectively for values different than zero. Used with <code>e</code>, <code>E</code> and <code>f</code>, 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 <code>g</code> or <code>G</code> the result is the same as with <code>e</code> or <code>E</code> but trailing zeros are not removed.<br />
{{!-}}<br />
! colspan="2" {{!}} Width<br />
{{!-}}<br />
{{!}} colspan="2" {{!}} Integer specifying the minimum number of characters to be returned. This includes the decimal point and decimal fraction.<br />
{{!-}}<br />
! colspan="2" {{!}} Precision<br />
{{!-}}<br />
{{!}} colspan="2" {{!}} Integer preceded by a dot specifying the number of decimal places to be written.<br />
{{!-}}<br />
! colspan="2" {{!}} Specifiers<br />
{{!-}}<br />
! Specifier !! Output<br />
{{!-}}<br />
{{!}} <code>d</code>, <code>i</code> {{!!}} Signed decimal number.<br />
{{!-}}<br />
{{!}} <code>s</code> {{!!}} A string<br />
{{!-}}<br />
{{!}} <code>%</code> {{!!}} Percent (%) character.<br />
{{!-}}<br />
{{!}} <code>c</code> {{!!}} 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.<br />
{{!-}}<br />
{{!}} <code>o</code> {{!!}} Unsigned integer as an octal number.<br />
{{!-}}<br />
{{!}} <code>u</code> {{!!}} Unsigned decimal integer.<br />
{{!-}}<br />
{{!}} <code>x</code>, <code>X</code> {{!!}} Unsigned integer as a hexadecimal number. If <code>x</code> is used, any letters in the number are lowercase, while <code>X</code> gives uppercase.<br />
{{!-}}<br />
{{!}} <code>e</code>, <code>E</code> {{!!}} Double value in scientific notation (i.e., ''[-]ddd.ddd'''e'''[+/-]ddd''), with an exponent being denoted by <tt>e</tt> or <tt>E</tt> depending on whether an upper or lowercase is used respectively.<br />
{{!-}}<br />
{{!}} <code>f</code> {{!!}} Floating-point number, in fixed decimal notation, by default with 6 decimal places.<br />
{{!-}}<br />
{{!}} <code>F</code> {{!!}} Appears to be available<ref><br />
{{Cite web<br />
|url = http://sourceforge.net/p/flightgear/simgear/ci/next/tree/simgear/nasal/lib.c#l389<br />
|title = fgdata/simgear/simgear/nasal/lib.c, line 389<br />
|accessdate = October 2015<br />
}}<br />
</ref>, but doesn't work.<br />
{{!-}}<br />
{{!}} <code>g</code>, <code>G</code> {{!!}} Double in either normal or exponential notation, whichever is more appropriate for its magnitude. <code>g</code> uses lower-case letters, <code>G</code> 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.<br />
{{!}}}<br />
<br />
|param1 = format<br />
|param1text = String specifying the format. Can be used with or without a format specifiers. See below for examples.<br />
|param2 = arg<br />
|param2text = Argument specifying a value to replace a format placeholder (such as <code>%d</code>) in the format string. Not required if there are no format specifiers.<br />
<br />
|example1 = print(sprintf("%i", 54)); # prints "54"<br />
|example2 = print(sprintf("Pi = %+.10f", math.pi)); # prints "Pi = +3.1415926536"<br />
|example3 = <br />
print(sprintf("%6d", 23)); # prints " 23"<br />
print(sprintf("%06d", 23)); # prints "000023"<br />
|example4 =<br />
var FGVer = getprop("/sim/version/flightgear");<br />
print(sprintf("You have FlightGear v%s", FGVer)); # prints "You have FlightGear v<your version>"<br />
|example5 = <br />
print(sprintf("Hexadecimal 100000 = %X", 100000)); # prints "Hexadecimal 100000 = 186A0"<br />
print(sprintf("Hexadecimal 100000 = %x", 100000)); # prints "Hexadecimal 100000 = 186a0"<br />
|example6 = print(sprintf("Code 65 is %c", 65)); # prints "Code 65 is A"<br />
|example7 = <br />
print(sprintf("%e", 54)); # prints "5.400000e+001"<br />
print(sprintf("%E", 54)); # prints "5.400000E+001"<br />
|example8 = print(sprintf("%o", 54)); # prints "66"<br />
|example9 = print(sprintf("50%% of 100 is %i", 100 / 2)); # prints "50% of 100 is 50"<br />
|example10 =<br />
print(sprintf("%.2f", 1.4)); #prints "1.40"<br />
print(sprintf("%.1f", 1.4)); #prints "1.4"<br />
print(sprintf("% 4.1f", 1.4)); #prints " 1.4"<br />
print(sprintf("%04.1f", 1.4)); #prints "01.4"<br />
print(sprintf("% 6.1f", 1.4)); #prints " 1.4"<br />
print(sprintf("%06.1f", 1.4)); #prints "0001.4"<br />
}}<br />
<br />
=== streq() ===<br />
{{Nasal doc<br />
|syntax = streq(a, b);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=107|t=Source}}<br />
|text = Tests the string values of the two arguments for equality. This function is needed because the <code>'''=='''</code> 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.}}<br />
|param1 = a<br />
|param1text = First argument for testing equality.<br />
|param2 = b<br />
|param2text = Second argument for testing equality.<br />
|example1 = print(streq("0", "0")); # prints "1" (True)<br />
|example2 = <br />
print(0 == 0.0); # prints "1" (True)<br />
print(streq("0", "0.0")); # prints "0" (False)<br />
}}<br />
<br />
=== substr() ===<br />
{{Nasal doc<br />
|syntax = substr(string, start [, length]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=129|t=Source}}<br />
|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).<br />
|param1 = string<br />
|param1text = String to return a substring from.<br />
|param2 = start<br />
|param2text = Integer specifying the start of a substring. Negative values specify a position from the end of the string.<br />
|param3 = length<br />
|param3text = Optional argument specifying the length of the substring. Defaults to the end of the string.<br />
|example1 = print(substr("abcde", 1, 3)); # prints "bcd"<br />
|example2 = print(substr("abcde", 1)); # prints "bcde"<br />
|example3 = print(substr("abcde", 2, 1)); # prints "c"<br />
|example4 = print(substr("abcde", -2)); # prints "de"<br />
|example5 = print(substr("abcde", -3, 2)); # prints "cd"<br />
}}<br />
<br />
=== subvec() ===<br />
{{Nasal doc<br />
|syntax = subvec(vector, start[, length]);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=63|t=Source}}<br />
|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). <br />
|param1 = vector<br />
|param1text = The vector to take the sub-vector from.<br />
|param2 = start<br />
|param2text = The starting point of the sub-vector within the given vector.<br />
|param3 = length<br />
|param3text = Optional argument specifying the length of the sub-vector, from the starting point.<br />
'''Notes:'''<br />
* Omitting the ''vector'' and ''start'' arguments is not an error (possibly it should be) but the return value is ''nil''.<br />
* A negative ''start'' argument ''is'' an error. This seems wrong. Perhaps the language designer could comment.<br />
* A value of ''start'' greater than ''size(vector)'' causes an error. A value equal to ''size(vector)'' returns an empty vector.<br />
* 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.<br />
|example1 = <br />
var vector = [1, 2, 3];<br />
debug.dump(subvec(vector, 0)); # prints "[1, 2, 3]"<br />
|example2 = <br />
var vector = [1, 2, 3];<br />
debug.dump(subvec(vector, 1)); # prints "[2, 3]"<br />
|example3 = <br />
var vector = [1, 2, 3];<br />
debug.dump(subvec(vector, 1, 1)); # prints "[2]"<br />
}}<br />
<br />
=== typeof() ===<br />
{{Nasal doc<br />
|syntax = typeof(object);<br />
|source = {{simgear file|simgear/nasal/lib.c|l=193|t=Source}}<br />
|text = Returns a string indicating the whether the object is <code>'''nil'''</code>, a scalar (number or string), a vector, a hash, a function, or a ghost.<br />
|param1 = object<br />
|param1text = Object to return the type of.<br />
|example1 = <br />
var object = nil;<br />
print(typeof(object)); # prints "nil"<br />
|example2 = <br />
var object = "Hello world!";<br />
print(typeof(object)); # prints "scalar"<br />
|example3 = <br />
var object = math.pi;<br />
print(typeof(object)); # prints "scalar"<br />
|example4 = <br />
var object = [1, 2, 3];<br />
print(typeof(object)); # prints "vector"<br />
|example5 = <br />
var object = {};<br />
print(typeof(object)); # prints "hash"<br />
|example6 = <br />
var object = func {};<br />
print(typeof(object)); # prints "func"<br />
|example7 =<br />
var object = airportinfo();<br />
print(typeof(object)); # prints "ghost"<br />
}}<br />
<br />
<!-- == Extension modules ==<br />
=== thread ===<br />
{{WIP}}<br />
<br />
{{Nasal doc<br />
|syntax = thread.newthread(func);<br />
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}<br />
|text = <br />
|example1text = start a new worker thread<br />
|example1 = thread.newthread( func() {} );<br />
}}<br />
<br />
{{Nasal doc<br />
|syntax = thread.newlock();<br />
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}<br />
|text = <br />
|example1text = create a new lock<br />
|example1 = var lock = thread.newlock()<br />
}}<br />
<br />
{{Nasal doc<br />
|syntax = thread.lock();<br />
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}<br />
|text = <br />
|example1text = lock a lock<br />
|example1 = var lock = thread.newlock()<br />
}}<br />
<br />
{{Nasal doc<br />
|syntax = thread.unlock();<br />
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}<br />
|text = <br />
|example1text = unlock a lock<br />
|example1 = var lock = thread.unlock()<br />
}}<br />
<br />
<br />
{{Nasal doc<br />
|syntax = thread.newsem();<br />
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}<br />
|text = <br />
|example1text = create a new {{Wikipedia|semaphore}}<br />
|example1 = var semaphore = thread.newsem()<br />
}}<br />
<br />
<br />
{{Nasal doc<br />
|syntax = thread.semdown();<br />
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}<br />
|text = <br />
|example1text = semaphore down<br />
|example1 = thread.semdown(semaphore)<br />
}}<br />
<br />
<br />
{{Nasal doc<br />
|syntax = thread.semup();<br />
|source = {{simgear file|simgear/nasal/threadlib.c|l=101|t=Source}}<br />
|text = <br />
|example1text = semaphore up<br />
|example1 = thread.semup(semaphore)<br />
}} --><br />
<br />
== Extension functions ==<br />
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:<br />
* {{flightgear file|src/Scripting/NasalPositioned.cxx}}<br />
* {{flightgear file|src/Scripting/NasalSys.cxx}}<br />
* {{fgdata file|Nasal/globals.nas}}<br />
<br />
=== abort() ===<br />
{{Nasal doc<br />
|syntax = abort();<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=565|t=Source}}<br />
|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 "exit" [[Fgcommands|fgcommand]], which will exit FlightGear more gracefully (see example below).<br />
|example1text = This example will immediately stop FlightGear with an error, such as "FlightGear has stopped working."<br />
|example1 = abort();<br />
|example2text = For exiting FlightGear in a better way, please use the following code:<br />
|example2 = fgcommand("exit");<br />
}}<br />
<br />
=== abs() ===<br />
{{Nasal doc<br />
|syntax = abs(number);<br />
|source = {{fgdata file|Nasal/globals.nas|t=Source}}<br />
|text = This simple function returns the {{wikipedia|absolute value|noicon=1}} of the provided number.<br />
|param1 = number<br />
|param1text = This argument is required and should be a number.<br />
|example1 = print(abs(1)); # prints "1"<br />
|example2 = print(abs(-1)); # prints "1"<br />
}}<br />
<br />
=== aircraftToCart() ===<br />
This new function in FG 2017.2.1 takes coordinates in aircraft structural coordinate system, and translate them into geocentric coordinates.<br />
Example for (5,6,7):<br />
<syntaxhighlight lang="nasal"><br />
var pos = aircraftToCart({x: -5, y: 6, z: -7});<br />
var coord = geo.Coord.new();<br />
coord.set_xyz(pos.x, pos.y, pos.z);<br />
</syntaxhighlight><br />
Notice: x and z is inverted sign on purpose.<br />
if you want lat. lon, alt from that, just call: (degrees and meters)<br />
<br />
<syntaxhighlight lang="nasal"><br />
coord.lat()<br />
coord.lon()<br />
coord.alt()<br />
</syntaxhighlight><br />
<br />
=== addcommand() ===<br />
{{Nasal doc<br />
|syntax = addcommand(name, code);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=659|t=Source}}<br />
|version = 2.12<br />
|commit = {{flightgear commit|7b663c|t=commit}}<br />
|text = {{see also|Howto:Add new fgcommands to FlightGear}}<br />
<br />
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.<br />
|param1 = name<br />
|param1text = This will become the name of the new fgcommand. Must be a string.<br />
|param2 = code<br />
|param2text = The code that will be executed when the fgcommand is run. Must be a function.<br />
|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.<br />
|example1 = addcommand("myFGCmd", func(node) {<br />
print("fgcommand 'myFGCmd' has been run.");<br />
props.dump( node );<br />
});<br />
fgcommand("myFGCmd", props.Node.new({foo:1, bar:2}) );<br />
|example2text = This example demonstrates how parameters are defined in a new fgcommand.<br />
|example2 = addcommand("myFGCmd", func(node){<br />
print(node.getNode("number").getValue()); # prints the value of "number," which is 12<br />
});<br />
fgcommand("myFGCmd", props.Node.new({"number": 12}));<br />
}}<br />
<br />
=== airportinfo() ===<br />
{{Nasal doc<br />
|syntax = airportinfo();<br />
airportinfo(type);<br />
airportinfo(id);<br />
airportinfo(lat, lon[, type]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1024|t=Source}}<br />
|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:<br />
* '''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.<br />
* '''lon''': Longitude of the location.<br />
* '''lat''': Latitude of the location.<br />
* '''has_metar''': True or false depending whether the airport has a [[METAR]] code defined for it.<br />
* '''elevation''': Elevation of the location in metres.<br />
* '''id''': ICAO code of the airport (or ID of the seaplane base/heliport).<br />
* '''name''': Name of the airport/heliport/seaplane base.<br />
* '''runways'''<br />
** '''<runway name>'''<br />
*** '''id''': Name of runway.<br />
*** '''lat''': Latitude of the runway.<br />
*** '''lon''': Longitude of the runway.<br />
*** '''heading''': Heading of the runway.<br />
*** '''length''': Length of the runway in metres.<br />
*** '''width''': Width of the runway in metres.<br />
*** '''surface''': Runway surface type.<br />
*** '''threshold''': Length of the runway's {{wikipedia|displaced threshold}} in metres. Will return 0 if there is none.<br />
*** '''stopway''': Length of the runway's stopway (the area before the threshold) in metres. Will return 0 if there is none.<br />
*** '''reciprocal''': <code>runway</code> ghost of the reciprocal runway.<br />
*** '''ils_frequency_mhz''': ILS frequency in megahertz.<br />
*** '''ils''': <code>navaid</code> ghost of the ILS transmitter.<br />
* '''helipads'''<br />
** '''<helipad name>'''<br />
*** '''id''': Name of helipad.<br />
*** '''lat''': Latitude of the helipad.<br />
*** '''lon''': Longitude of the helipad.<br />
*** '''heading''': Heading of the helipad.<br />
*** '''length''': Length of the helipad in metres.<br />
*** '''width''': Width of the helipad in metres.<br />
*** '''surface''': Helipad surface type.<br />
* '''taxiways'''<br />
** '''<taxiway name>'''<br />
*** '''id''': Name of taxiway.<br />
*** '''lat''': Latitude of the taxiway.<br />
*** '''lon''': Longitude of the taxiway.<br />
*** '''heading''': Heading of the taxiway.<br />
*** '''length''': Length of the taxiway in metres.<br />
*** '''width''': Width of the taxiway in metres.<br />
*** '''surface''': Taxiway surface type.<br />
<br />
Information is extracted in the same way as accessing members of a Nasal hash. For example:<br />
<syntaxhighlight lang="nasal"><br />
# prints to lengths of the runways of the nearest airport in feet and metres<br />
var info = airportinfo();<br />
print("-- Lengths of the runways at ", info.name, " (", info.id, ") --");<br />
foreach(var rwy; keys(info.runways)){<br />
print(rwy, ": ", math.round(info.runways[rwy].length * M2FT), " ft (", info.runways[rwy].length, " m)");<br />
}<br />
</syntaxhighlight><br />
<br />
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.<br />
|param1 = id<br />
|param1text = The {{wikipedia|International Civil Aviation Organization airport code|ICAO code|noicon=1}} of an airport to retrieve information about.<br />
|param2 = type<br />
|param2text = When this argument is used, the function will return the closest airport of a certain type. Can be one of "heliport," "seaport," or "airport" (default).<br />
: {{inote|Running this function without any parameters is equivalent to this:<br />
: <syntaxhighlight lang="nasal"><br />
airportinfo("airport");<br />
</syntaxhighlight><br />
}}<br />
|param3 = lat ''and'' lon<br />
|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.<br />
|example1 = var info = airportinfo();<br />
print("Nearest airport: ", info.name, " (", info.id, ")"); # prints the name and ICAO code of the nearest airport<br />
|example2 = var info = airportinfo("heliport");<br />
print("Elevation of the nearest heliport: ", math.round(info.elevation * M2FT), " ft"); # prints the elevation and name of the nearest heliport<br />
|example3 = var info = airportinfo("KSQL");<br />
print("-- Runways of ", info.name, " (", info.id, "): --");<br />
foreach(var rwy; keys(info.runways)) {<br />
print(rwy); # prints the runways of KSQL<br />
}<br />
|example4 = var info = airportinfo(37.81909385, -122.4722484);<br />
print("Coordinates of the nearest airport: ", info.lat, ", ", info.lon); # print the name and ICAO of the nearest airport to the Golden Gate Bridge<br />
|example5 = var info = airportinfo(37.81909385, -122.4722484, "seaport");<br />
print("Nearest seaplane base: ", info.name, " (", info.id, ")"); # print the name and ID of the nearest seaplane base to the Golden Gate Bridge<br />
|example6text = This example prints the all information from an <code>airportinfo()</code> call.<br />
|example6 = var info = airportinfo("KSFO");<br />
print(info.name);<br />
print(info.id);<br />
print(info.lat);<br />
print(info.lon);<br />
print(info.has_metar);<br />
print(info.elevation);<br />
foreach(var rwy; keys(info.runways)){<br />
print("-- ", rwy, " --");<br />
print(info.runways[rwy].lat);<br />
print(info.runways[rwy].lon);<br />
print(info.runways[rwy].length);<br />
print(info.runways[rwy].width);<br />
print(info.runways[rwy].heading);<br />
print(info.runways[rwy].stopway);<br />
print(info.runways[rwy].threshold);<br />
}<br />
}}<br />
<br />
=== airwaysRoute() ===<br />
{{Nasal doc<br />
|syntax = airwaysRoute(start, end[, type]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1933|t=Source}}<br />
|text = {{see also|Nasal Flightplan}}<br />
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.<br />
|param1 = start<br />
|param1text = Start waypoint, in the form of a waypoint ghost, such as that provided by {{func link|flightplan()}}.<br />
|param2 = end<br />
|param2text = Same as above.<br />
|param3 = type<br />
|param3text = Instructs the function to compute a high level route (when set to "highlevel"), or a low level route (when set to "lowlevel"). Defaults to "highlevel."<br />
|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.<br />
|example1 = var fp = flightplan();<br />
var start = fp.getWP(0);<br />
var end = fp.getWP(fp.getPlanSize() - 1);<br />
var rt = airwaysRoute(start, end);<br />
foreach(var wp; rt){<br />
print(wp.wp_name); # print the waypoints in the computed route<br />
}<br />
|example2text = Exactly the same as above, but computes a low level path.<br />
|example2 = var fp = flightplan();<br />
var start = fp.getWP(0);<br />
var end = fp.getWP(fp.getPlanSize() - 1);<br />
var rt = airwaysRoute(start, end, "lowlevel");<br />
foreach(var wp; rt){<br />
print(wp.wp_name); # print the waypoints in the computed route<br />
}<br />
}}<br />
<br />
=== assert() ===<br />
{{Nasal doc<br />
|syntax = assert(condition[, message]);<br />
|source = {{fgdata file|Nasal/globals.nas|t=Source}}<br />
|version = 3.2<br />
|commit = {{fgdata commit|8b16a7|t=commit}}<br />
|text = Returns either true if the condition evaluates as true, or aborts with a {{func link|die()}} call, which can be customised.<br />
|param1 = condition<br />
|param1text = Condition to evaluate.<br />
|param2 = message<br />
|param2text = Optional message that will be used in any {{func link|die()}} call. Defaults to "assertion failed!"<br />
|example1 = var a = 1;<br />
var b = 2;<br />
print(assert(a < b)); # prints "1" (true)<br />
|example2 = var a = 1;<br />
var b = 2;<br />
assert(a > b, 'a is not greater than b'); # aborts with a custom error message<br />
}}<br />
<br />
=== carttogeod() ===<br />
{{Nasal doc<br />
|syntax = carttogeod(x, y, z);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=945|t=Source}}<br />
|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).<ref>{{simgear file|simgear/math/sg_geodesy.hxx|l=43}}</ref><br />
|param1 = x<br />
|param1text = Mandatory x-axis value, in metres.<br />
|param2 = y<br />
|param2text = Mandatory y-axis value, in metres.<br />
|param3 = z<br />
|param3text = Mandatory z-axis value, in metres.<br />
|example1 = var (lat, lon, alt) = carttogeod(6378137, 0, 0); # point is the intersection of the prime meridian and equator.<br />
print("Latitude: ", lat); # prints lat, lon and alt, which are all zero, see above<br />
print("Longitude: ", lon);<br />
print("Altitude: ", alt);<br />
}}<br />
<br />
=== cmdarg() ===<br />
{{Nasal doc<br />
|private = _cmdarg()<br />
|syntax = cmdarg();<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=513|t=Part 1}} {{!}} {{fgdata file|Nasal/globals.nas|t=Part 2}}<br />
|text = <code>cmdarg()</code> 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. <br />
It is used by Nasal scripts embedded in XML files. It returns a <code>props.Node</code> object (see {{fgdata file|Nasal/props.nas}}), and you can use all of its methods on the returned value. <code>cmdarg()</code> should only be used in four types/places of XML files:<br />
* Bindings: This is needed so that the value of a joystick's axis can be accessed internally.<br />
* 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). <br />
* 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.<br />
* Animation XML files: If the animation XML file is used in an AI/MP model, <code>cmdarg()</code> will return the root of the AI model in the <code>/ai/models/</code> directory. Examples: <code>/ai/models/aircraft[3]/</code>, <code>/ai/models/multiplayer[1]/</code><br />
<br />
You should not use <code>cmdarg()</code> in places other than those stated above. Although it won't cause an error, it will return the value of the last legitimate <code>cmdarg()</code> call. <br />
<br />
Also, you should not delay <code>cmdarg()</code> using {{func link|maketimer()}}, {{func link|settimer()}} or {{func link|setlistener()}}, because it will return an unrelated property.<br />
|example1 = fgcommand("dialog-show", {"dialog-name": "cmdarg-demo"});<br />
|example1text = <br>This example demonstrates the usage of <code>cmdarg()</code> in a binding. Save the below XML snippet as <tt>[[$FG_ROOT]]/gui/dialogs/cmdarg-demo.xml</tt>. 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.<br />
<syntaxhighlight lang="xml"><br />
<?xml version="1.0" encoding="UTF-8"?><br />
<br />
<PropertyList><br />
<br />
<name>cmdarg-demo</name><br />
<layout>vbox</layout><br />
<br />
<text><br />
<label>Click "Close" to activate the demonstration (a message in the console).</label><br />
</text><br />
<br />
<button><br />
<legend>Close</legend><br />
<binding><br />
<command>nasal</command><br />
<script>print("Button binding root: '" ~ cmdarg().getPath() ~ "'");</script><br />
</binding><br />
<binding><br />
<command>dialog-close</command><br />
</binding><br />
</button><br />
<br />
</PropertyList><br />
</syntaxhighlight><br />
|example2text = This example demonstrates the usage of <code>cmdarg()</code> in Nasal code within dialogs. Open <tt>[[$FG_ROOT]]/gui/dialogs/cmdarg-demo.xml</tt> from the previous example, copy & 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 "I've been changed!"<br />
<syntaxhighlight lang="xml"><br />
<?xml version="1.0" encoding="UTF-8"?><br />
<br />
<PropertyList><br />
<br />
<name>cmdarg-demo</name><br />
<layout>vbox</layout><br />
<br />
<text><br />
<label>Click "Click me!" to activate the demonstration (the button's label will change).</label><br />
</text><br />
<br />
<button><br />
<legend>Click me!</legend><br />
<binding><br />
<command>nasal</command><br />
<script>change_label();</script><br />
</binding><br />
</button><br />
<br />
<button><br />
<legend>Close</legend><br />
<binding><br />
<command>dialog-close</command><br />
</binding><br />
</button><br />
<br />
<nasal><br />
<open><![CDATA[<br />
var dlg_root = cmdarg();<br />
var dlg_name = {"dialog-name": "cmdarg-demo"};<br />
var change_label = func {<br />
dlg_root.getNode("button[0]/legend").setValue("I've been changed!");<br />
fgcommand("dialog-close", dlg_name);<br />
fgcommand("dialog-show", dlg_name);<br />
}<br />
]]></open><br />
</nasal><br />
<br />
</PropertyList><br />
</syntaxhighlight><br />
|example3text = For an example of <code>cmdarg()</code> used with Canvas, please see [[Howto:Adding a canvas to a GUI dialog#FGPlot|Howto:Adding a canvas to a GUI dialog]].<br />
}}<br />
<br />
=== courseAndDistance() ===<br />
{{Nasal doc<br />
|syntax = courseAndDistance(to);<br />
courseAndDistance(from, to);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1668|t=Source}}<br />
|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:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost type<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A geo.Coord object<br />
|param1 = from<br />
|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.<br />
|param2 = to<br />
|param2text = Like the first parameter, but defines the second point.<br />
|example1text = This example demonstrates the usage of the function with the <code>airport</code> ghost type.<br />
|example1 = var from = airportinfo("KSFO");<br />
var to = airportinfo("KSQL");<br />
var (course, dist) = courseAndDistance(from, to);<br />
print(course); # prints course from KSFO to KSQL<br />
print(dist); # prints distance in nm from KSFO to KSQL<br />
|example2text = This example demonstrates the usage of the function with hashes containing ''lat'' and ''lon''.<br />
|example2 = var from = {lat: 0, lon: 0};<br />
var to = {lat: 1, lon: 1};<br />
var (course, dist) = courseAndDistance(from, to);<br />
print(course);<br />
print(dist);<br />
|example3text = This example demonstrates usage of a geo.Coord object.<br />
|example3 = var from = geo.Coord.new().set_latlon(0, 0);<br />
var to = geo.Coord.new().set_latlon(1, 1);<br />
var (course, dist) = courseAndDistance(from, to);<br />
print(course);<br />
print(dist);<br />
|example4text = This example demonstrates usage of differing parameter types.<br />
|example4 = var from = airportinfo("KSFO");<br />
var to = geo.Coord.new().set_latlon(0, 0);<br />
var (course, dist) = courseAndDistance(from, to);<br />
print(course);<br />
print(dist);<br />
|example5text = The same as above, but the other way round.<br />
|example5 = var to = {lat: 1, lon: 1};<br />
var (course, dist) = courseAndDistance(0, 0, to);<br />
print(course);<br />
print(dist);<br />
|example6text = Usage of just one parameter.<br />
|example6 = var dest = airportinfo("KSQL");<br />
var (course, dist) = courseAndDistance(dest);<br />
print("Turn to heading ", math.round(course), ". You have ", sprintf("%.2f", dist), " nm to go");<br />
}}<br />
<br />
=== createDiscontinuity() ===<br />
{{Nasal doc<br />
|syntax = createDiscontinuity();<br />
|text = Returns a <code>waypoint</code> ghost object. A route discontinuity is inserted by an {{abbr|FMS|Flight Management System}} when it is unsure how to connect two waypoints.<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2045|t=Source}}<br />
|version = 2016.1<br />
|commit = {{flightgear commit|caead6|t=commit}}<br />
}}<br />
=== createViaTo() ===<br />
{{Nasal doc<br />
|syntax = createViaTo(airway, waypoint);<br />
|text = Returns a <code>waypoint</code> ghost object. It represents a route "via '''airway''' to '''waypoint'''".<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=2009|t=Source}}<br />
|version = 2016.1<br />
|commit = {{flightgear commit|caead6|t=commit}}<br />
|param1 = airway<br />
|param1text = The name of an airway.<br />
|param2 = waypoint<br />
|param2text = Must be in the airway and one of:<br />
* The name of a waypoint.<br />
* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, or <code>fix</code> ghost object.<br />
}}<br />
=== createWP() ===<br />
{{Nasal doc<br />
|syntax = createWP(pos, name[, flag]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1964|t=Source}}<br />
|text = Creates a new waypoint ghost object.<br />
|param1 = pos<br />
|param1text = Dictates the position of the new waypoint. It can be one of the following:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost type<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A geo.Coord object<br />
:* Two numbers separated by a comma, as if the function is taking three arguments. See example 4 below.<br />
|param2 = name<br />
|param2text = String that will become the name of the new waypoint.<br />
|param3 = flag<br />
|param3text = Optional string that will tell FlightGear what type of waypoint it is. Must be one of "sid," "star," "approach," "missed," or "pseudo."<br />
|example1text = Creates a waypoint directly in front and 1 km away and appends it to the flight plan.<br />
|example1 = var pos = geo.aircraft_position().apply_course_distance(getprop("/orientation/heading-deg"), 1000);<br />
var wp = createWP(pos, "NEWWP");<br />
var fp = flightplan();<br />
fp.appendWP(wp);<br />
|example2 = var pos = geo.aircraft_position().apply_course_distance(getprop("/orientation/heading-deg"), 1000);<br />
var wp = createWP({lat: pos.lat(), lon: pos.lon()}, "NEWWP");<br />
var fp = flightplan();<br />
fp.appendWP(wp);<br />
|example3 = var apt = airportinfo();<br />
var wp = createWP(apt, "NEWWP");<br />
var fp = flightplan();<br />
fp.appendWP(wp);<br />
|example4 = var pos = geo.aircraft_position().apply_course_distance(getprop("/orientation/heading-deg"), 1000);<br />
var wp = createWP(pos.lat(), pos.lon(), "NEWWP");<br />
var fp = flightplan();<br />
fp.appendWP(wp);<br />
|example5text = Creates a new waypoint and adds it to the flight plan. Waypoints of the type "pseudo" are then removed from the flight plan, including the new waypoint. The {{func link|print()}} statements show this.<br />
|example5 = var pos = geo.aircraft_position().apply_course_distance(getprop("/orientation/heading-deg"), 1000);<br />
var wp = createWP(pos, "NEWWP", "pseudo");<br />
var fp = flightplan();<br />
fp.appendWP(wp);<br />
print(fp.getPlanSize());<br />
fp.clearWPType("pseudo");<br />
print(fp.getPlanSize());<br />
}}<br />
<br />
=== createWPFrom() ===<br />
{{Nasal doc<br />
|syntax = createWPFrom(object[, flag]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1989|t=Source}}<br />
|text = Creates a new waypoint object from another object.<br />
|param1 = object<br />
|param1text = A ghost object. Must be a ghost type that is one of "airport," "navaid," "runway," or "fix."<br />
|param2 = flag<br />
|param2text = Optional string that will tell FlightGear what type of waypoint it is. Must be one of "sid," "star," "approach," "missed," or "pseudo."<br />
|example1text = Creates a new waypoint and appends it to the flight plan.<br />
|example1 = var apt = airportinfo("KSFO");<br />
var wp = createWPFrom(apt);<br />
var fp = flightplan();<br />
fp.appendWP(wp);<br />
|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.<br />
|example2 = var apt = airportinfo("KSFO");<br />
var wp = createWPFrom(apt, "pseudo");<br />
print(wp.wp_name);<br />
var fp = flightplan();<br />
fp.appendWP(wp);<br />
print(fp.getPlanSize());<br />
fp.clearWPType("pseudo");<br />
print(fp.getPlanSize());<br />
}}<br />
<br />
=== defined() ===<br />
{{Nasal doc<br />
|syntax = defined(symbol);<br />
|source = {{fgdata file|Nasal/globals.nas|t=Source}}<br />
|text = Returns 1 (true) or 0 (false) depending on whether a variable exists.<br />
|param1 = symbol<br />
|param1text = A string that will be what the function searches for.<br />
|example1 = var number = 12;<br />
var check_exist = func {<br />
print("Variable 'number' ", defined("number") == 1 ? "exists" : "does not exist"); # 'number' does exist<br />
print("Variable 'number2' ", defined("number2") == 1 ? "exists" : "does not exist"); # 'number2' does not exist<br />
}<br />
check_exist();<br />
}}<br />
<br />
=== directory() ===<br />
{{Nasal doc<br />
|syntax = directory(path);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=572|t=Source}}<br />
|text = Returns a vector containing a list of the folders and files in a given file path or <code>'''nil'''</code> if the path doesn't exist. Hidden folders and files are not added to the vector.<br />
{{inote|The first two elements of the vector will be <code>'.'</code> and <code>'..'</code>. These are for navigating back up the file tree, but have no use in Nasal. They can be safely removed from the vector.}}<br />
|param1 = path<br />
|param1text = Absolute file path.<br />
|example1text = Gets the folders and files in [[$FG_ROOT]], and then removes the extra first two elements (see note above). <br />
|example1 = var dir = directory(getprop("/sim/fg-root")); # get directory<br />
dir = subvec(dir, 2); # strips off the first two elements<br />
debug.dump(dir); # dump the vector<br />
}}<br />
<br />
=== fgcommand() ===<br />
{{Nasal doc<br />
|syntax = fgcommand(cmd[, args]);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=456|t=Part 1}} {{!}} {{fgdata file|Nasal/globals.nas|t=Part 2}}<br />
|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.<br />
|param1 = cmd<br />
|param1text = String that is the name of the command that is to be run.<br />
|param2 = args<br />
|param2text = If the fgcommand takes arguments, they are inputted using this argument. Can either be a <code>props.Node</code> object, or a hash (see examples below).<br />
|example1 = fgcommand("null"); # does nothing<br />
|example2 = var args = props.Node.new({'script': 'print("Running fgcommand");'});<br />
if (fgcommand("nasal", args)) { # prints "Running fgcommand" and then one of these print statements<br />
print("Fgcommand succeeded");<br />
} else {<br />
print("Fgcommand encountered a problem");<br />
}<br />
|example3 = var args = { 'dialog-name': 'about' };<br />
fgcommand("dialog-show", args); # shows the 'about' dialog<br />
}}<br />
<br />
=== findAirportsByICAO() ===<br />
{{Nasal doc<br />
|syntax = findAirportsByICAO(search[, type]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1096|t=Source}}<br />
|text = Returns a vector containing <code>airport</code> ghost objects which are (by default) airports whose ICAO code matches the search string. The results are sorted by range from closest to furthest.<br />
|param1 = search<br />
|param1text = Search string for the function. Can either be a partial or a full ICAO code.<br />
:{{icaution|The more matches there are for the given code, the longer the function will take. Passing just one character (e.g., "K"), might make FlightGear hang for a certain amount of time.}}<br />
|param2 = type<br />
|param2text = This will narrow the search to airports of a certain type. By default, only airports are searched for. May be one of "airport," "heliport," or "seaport."<br />
|example1 = var apts = findAirportsByICAO("KSF"); # finds all airports matching "KSF"<br />
foreach(var apt; apts){<br />
print(apt.name, " (", apt.id, ")"); # prints them<br />
}<br />
|example2 = var apts = findAirportsByICAO("SP0", "seaport"); # finds all seaplane bases matching "SP0"<br />
foreach(var apt; apts){<br />
print(apt.name, " (", apt.id, ")"); # prints them<br />
}<br />
|example3 = var apt = findAirportsByICAO("XBET"); # one way to check if an airport does exist"<br />
if (size(apt) == 0) {<br />
print("Airport does not exist"); # this one will be printed<br />
} else {<br />
print("Airport does exist");<br />
}<br />
}}<br />
<br />
=== findAirportsWithinRange() ===<br />
{{Nasal doc<br />
|syntax = findAirportsWithinRange([pos, ]range[, type]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1066|t=Source}}<br />
|text = Returns a vector of <code>airport</code> 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.<br />
|param1 = pos<br />
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost type<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A geo.Coord object<br />
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: <code>findAirportsWithinRange(lat, lon, range, type);</code>.<br />
|param2 = range<br />
|param2text = Mandatory number giving the range in nautical miles within which to search for airports/heliports/seaplane bases.only airports are searched for.<br />
|param3 = type<br />
|param3text = This will narrow the search to airports of a certain type. By default, only airports are searched for. May be one of "airport," "heliport," or "seaport."<br />
|example1text = Searches for airports within 10 nm of [[KSFO]].<br />
|example1 = var pos = airportinfo("KSFO");<br />
var apts = findAirportsWithinRange(pos, 10);<br />
foreach(var apt; apts){<br />
print(apt.name, " (", apt.id, ")");<br />
}<br />
|example2text = Searches for seaplane bases within 15 nm of [[KSFO]].<br />
|example2 = var pos = airportinfo("KSFO");<br />
var apts = findAirportsWithinRange(pos, 15, "seaport");<br />
foreach(var apt; apts){<br />
print(apt.name, " (", apt.id, ")");<br />
}<br />
|example3text = Searches for airports within 10 nm of your current position.<br />
|example3 = var apts = findAirportsWithinRange(10);<br />
foreach(var apt; apts){<br />
print(apt.name, " (", apt.id, ")");<br />
}<br />
}}<br />
<br />
=== findFixesByID() ===<br />
{{Nasal doc<br />
|syntax = findFixesByID([pos, ]id);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1627|t=Source}}<br />
|text = Returns a vector containing <code>fix</code> ghost objects matching a given ID, sorted by range from a certain position.<br />
{{inote|Fixes are (usually) also known as waypoints.}}<br />
|param1 = pos<br />
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost type<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A geo.Coord object<br />
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: <code>findFixesByID(lat, lon, id);</code>.<br />
|param2 = id<br />
|param2text = Full or partial ID of the fix to search for.<br />
:{{inote|1=Inputting a partial ID does not work correctly (see [http://forum.flightgear.org/viewtopic.php?f=30&t=28129 here]). It is best to just input a full ID.}}<br />
|example1 = var fixes = findFixesByID("POGIC");<br />
foreach(var fix; fixes){<br />
print(fix.id, " - lat: ", fix.lat, " {{!}} lon: ", fix.lon); # prints information about POGIC<br />
}<br />
|example2 = var fix = findFixesByID("ZUNAP");<br />
fix = fix[0];<br />
var (course, dist) = courseAndDistance(fix);<br />
print("Turn to heading ", math.round(course), ". You have ", sprintf("%.2f", dist), " nm to go to reach ", fixes[0].id);<br />
}}<br />
<br />
=== findNavaidByFrequency() ===<br />
{{Nasal doc<br />
|syntax = findNavaidByFrequency([pos, ]freq[, type]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1547|t=Source}}<br />
|text = Returns a <code>navaid</code> ghost object for a navaid matching a given frequency. If there is more than one navaid with that frequency, the nearest station is returned.<br />
|param1 = pos<br />
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost type<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A geo.Coord object<br />
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: <code>findNavaidByFrequency(lat, lon, freq, type);</code>.<br />
|param2 = freq<br />
|param2text = Frequency, in megahertz, of the navaid to search for.<br />
|param3 = type<br />
|param3text = This will narrow the search to navaids of a certain type. Defaults to "all." For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.<br />
|example1 = var navaid = findNavaidByFrequency(11.17);<br />
print("ID: ", navaid.id); # prints info about the navaid<br />
print("Name: ", navaid.name);<br />
print("Latitude: ", navaid.lat);<br />
print("Longitude: ", navaid.lon);<br />
print("Elevation (AMSL): ", navaid.elevation, " m");<br />
print("Type: ", navaid.type);<br />
print("Frequency: ", sprintf("%.3f", navaid.frequency / 1000), " Mhz");<br />
print("Range: ", navaid.range_nm, " nm");<br />
if(navaid.course) print("Course: ", navaid.course);<br />
}}<br />
<br />
=== findNavaidsByFrequency() ===<br />
{{Nasal doc<br />
|syntax = findNavaidsByFrequency([pos, ]freq[, type]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1572|t=Source}}<br />
|text = Returns a vector conatining <code>navaid</code> ghost objects for navaids that match a given frequency, sorted from nearest to furthest.<br />
|param1 = pos<br />
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost type<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A geo.Coord object<br />
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: <code>findNavaidsByFrequency(lat, lon, freq, type);</code>.<br />
|param2 = freq<br />
|param2text = Frequency, in megahertz, of the navaid to search for.<br />
|param3 = type<br />
|param3text = This will narrow the search to navaids of a certain type. Defaults to "all." For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.<br />
|example1 = var navaids = findNavaidsByFrequency(10.955);<br />
foreach(var navaid; navaids){<br />
print("--");<br />
print("ID: ", navaid.id); # prints info about the navaid<br />
print("Name: ", navaid.name);<br />
print("Latitude: ", navaid.lat);<br />
print("Longitude: ", navaid.lon);<br />
print("Elevation (AMSL): ", navaid.elevation, " m");<br />
print("Type: ", navaid.type);<br />
print("Frequency: ", sprintf("%.3f", navaid.frequency / 1000), " Mhz");<br />
print("Range: ", navaid.range_nm, " nm");<br />
if(navaid.course) print("Course: ", navaid.course);<br />
print("--");<br />
}<br />
}}<br />
<br />
=== findNavaidsByID() ===<br />
{{Nasal doc<br />
|syntax = findNavaidsByID([pos, ]id[, type]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1600|t=Source}}<br />
|text = Returns a vector containing <code>navaid</code> ghost objects matching a given ID, sorted by range from a certain position.<br />
|param1 = pos<br />
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost type<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A geo.Coord object<br />
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: <code>findNavaidsByID(lat, lon, id, type);</code>.<br />
|param2 = id<br />
|param2text = Full or partial ID of the fix to search for.<br />
:{{inote|1=Inputting a partial ID does not work correctly (see [http://forum.flightgear.org/viewtopic.php?f=30&t=28129 here]). It is best to just input a full ID.}}<br />
|param3 = type<br />
|param3text = This will narrow the search to navaids of a certain type. Defaults to "all." For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.<br />
|example1 = var navaid = findNavaidsByID("MXW");<br />
navaid = navaid[0];<br />
print("ID: ", navaid.id); # prints info about 'MXW' (a VOR station)<br />
print("Name: ", navaid.name);<br />
print("Latitude: ", navaid.lat);<br />
print("Longitude: ", navaid.lon);<br />
print("Elevation (AMSL): ", navaid.elevation, " m");<br />
print("Type: ", navaid.type);<br />
print("Frequency: ", sprintf("%.3f", navaid.frequency / 1000), " Mhz");<br />
print("Range: ", navaid.range_nm, " nm");<br />
}}<br />
<br />
=== findNavaidsWithinRange() ===<br />
{{Nasal doc<br />
|syntax = findNavaidsWithinRange([pos, ]range[, type]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1518|t=Source}}<br />
|text = Returns a vector of <code>navaid</code> 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.<br />
|param1 = pos<br />
|param1text = Optional position to search around. If not given, the aircraft's current position will be used. Can be one of:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost type<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A geo.Coord object<br />
:* Two numbers separated by a comma, as if the function is taking three arguments. Example: <code>findNavaidsWithinRange(lat, lon, range, type);</code>.<br />
|param2 = range<br />
|param2text = Mandatory number giving the range in nautical miles within which to search for navaids. Defaults to "all." For the full list of accepted type arguments, see {{flightgear file|src/Navaids/positioned.cxx|l=127}}.<br />
|param3 = type<br />
|param3text = This will narrow the search to navaids of a certain type.<br />
|example1text = Searches for navaids within 10 nm of [[KSFO]].<br />
|example1 = var pos = airportinfo("KSFO");<br />
var navs = findNavaidsWithinRange(pos, 10);<br />
foreach(var nav; navs){<br />
print(nav.name, " (ID: ", nav.id, ")");<br />
}<br />
|example2text = Searches for navaids within 10 nm of your current position.<br />
|example2 = var navs = findNavaidsWithinRange(10);<br />
foreach(var nav; navs){<br />
print(nav.name, " (ID: ", nav.id, ")");<br />
}<br />
}}<br />
<br />
=== finddata() ===<br />
{{Nasal doc<br />
|syntax = finddata(path);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=603|t=Source}}<br />
|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]]. <br />
|param1 = path<br />
|param1text = A relative path as a string.<br />
|example1 = var path = finddata("Aircraft/Generic");<br />
print(path); # prints the absolute path to $FG_ROOT/Aircraft/Generic<br />
|example2 = var path = finddata("Airports");<br />
print(path); # prints the absolute path to <TerraSync dir>/Airports<br />
|example3 = var path = finddata("preferences.xml");<br />
print(path); # prints the absolute path to $FG_ROOT/preferences.xml<br />
}}<br />
<br />
=== flightplan() ===<br />
{{Nasal doc<br />
|syntax = flightplan([path]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1738|t=Source}}<br />
|text = {{see also|Nasal Flightplan}}<br />
Returns a flight plan object, either one for the current flight plan, or one loaded from a given path.<br />
|param1 = path<br />
|param1text = Optional path to flight plan XML file.<br />
|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.<br />
|example1 = var fp = flightplan();<br />
print(fp.getWP(fp.current).id);<br />
|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 <tt>''[[$FG_HOME]]/fp-demo.xml''</tt>.<br />
|example2 = var path = getprop('/sim/fg-home') ~ '/fp-demo.xml';<br />
var fp = flightplan(path);<br />
for(var i = 0; i < fp.getPlanSize(); i += 1){<br />
print(fp.getWP(i).id);<br />
}<br />
}}<br />
<br />
=== geodinfo() ===<br />
{{Nasal doc<br />
|syntax = geodinfo(lat, lon[, max_alt]);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=981|t=Source}}<br />
|text = Returns a vector containing two entries or <code>'''nil'''</code> 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 <tt>''[[$FG_ROOT]]/Materials''</tt>), or <code>'''nil'''</code> 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}}):<br />
* '''light_coverage:''' The coverage of a single point of light in m<sup>2</sup>.<br />
* '''bumpiness:''' Normalized bumpiness factor for the material.<br />
* '''load_resistance:''' The amount of pressure in N/m<sup>2</sup> the material can withstand without deformation.<br />
* '''solid:''' 1 (true) or false (0) depending on whether the material is solid or not.<br />
* '''names:''' Vector of scenery types (usually generated by [[TerraGear]]) that will use this material. <br />
* '''friction_factor:''' Normalized friction factor of the material.<br />
* '''rolling_friction:''' The rolling friction coefficient of the material.<br />
<br />
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.<br />
|param1 = lat<br />
|param1text = Latitude, inputted as a number.<br />
|param2 = lon<br />
|param2text = Longitude, inputted as a number.<br />
|param3 = max_alt<br />
|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, <code>'''nil'''</code> will be returned.<br />
|example1text = Dumps information about ground underneath the aircraft.<br />
|example1 = var pos = geo.aircraft_position();<br />
var info = geodinfo(pos.lat(), pos.lon());<br />
debug.dump(info);<br />
|example2text = Prints whether the ground underneath the aircraft is solid or is water.<br />
|example2 = var pos = geo.aircraft_position();<br />
var info = geodinfo(pos.lat(), pos.lon());<br />
if (info != nil and info[1] != nil) {<br />
print("The ground underneath the aircraft is ", info[1].solid == 1 ? "solid." : "water.");<br />
}<br />
}}<br />
<br />
=== geodtocart() ===<br />
{{Nasal doc<br />
|syntax = geodtocart(lat, lon, alt);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=962|t=Source}}<br />
|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.<br />
|param1 = lat<br />
|param1text = Latitude, in degrees.<br />
|param2 = lon<br />
|param2text = Longitude, in degrees.<br />
|param3 = alt<br />
|param3text = Altitude, in metres.<br />
|example1 = var (x, y, z) = geodtocart(0, 0, 0); # point is the intersection of the prime meridian and equator.<br />
print("x: ", x); # prints "x: 6378137"<br />
print("y: ", y); # prints "y: 0"<br />
print("z: ", z); # prints "y: 0"<br />
}}<br />
<br />
=== get_cart_ground_intersection() ===<br />
Introduced in 2017.2.1, see [[Terrain Detection]].<br />
<br />
=== getprop() ===<br />
{{Nasal doc<br />
|syntax = getprop(path[, path[, ...]]);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=345|t=Source}}<br />
|text = Returns the value of a node in the [[Property Tree]] or <code>'''nil'''</code> if the node does not exist or the value is not a number (NaN).<br />
|param1 = path<br />
|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.<br />
|example1 = print("You have FlightGear v", getprop("/sim/version/flightgear")); # prints FlightGear version<br />
|example2text = Note that the example below will only work in FlightGear 3.2 and above.<br />
|example2 = for(var i = 0; i < 8; i += 1){<br />
print("View #", i + 1, " is named ", getprop("/sim/view", i, "name"));<br />
}<br />
|example3text = Same as above, but is supported by all versions of FlightGear.<br />
|example3 = for(var i = 0; i < 8; i += 1){<br />
print("View #", i + 1, " is named ", getprop("/sim/view[" ~ i ~ "]/name"));<br />
}<br />
}}<br />
==== See also ====<br />
{{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: <br />
To get a Node rather than its value, use <code>props.globals.getNode()</code> - see [[Nasal_library/props]]. }}<br />
<br />
=== greatCircleMove() ===<br />
{{Nasal doc<br />
|syntax = greatCircleMove([pos, ]course, dist);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1681|t=Source}}<br />
|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).<br />
|param1 = pos<br />
|param1text = Optional position to calculate from. If not given, the aircraft's current position will be used. Can be one of:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost object.<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A <code>geo.Coord</code> object<br />
:* A lat/lon pair, that is, a pair of numbers (latitude followed by longitude) separated by a comma: <code>greatCircleMove(lat,lon, ...)</code>.<br />
|param2 = course<br />
|param2text = Course to new set of coordinates, in degrees (in the range 0–360).<br />
|param3 = dist<br />
|param3text = Distance in nautical miles to the new set of coordinates.<br />
|example1 = var pos = greatCircleMove(0,0, 0, 1);<br />
debug.dump(pos); # print hash with coordinates<br />
|example2 = var fix = findFixesByID("POGIC");<br />
fix = fix[0];<br />
var pos = greatCircleMove(fix, 45, 10);<br />
debug.dump(pos); # print hash with coordinates<br />
}}<br />
<br />
=== interpolate() ===<br />
{{Nasal doc<br />
|private = _interpolate()<br />
|syntax = interpolate(prop, value1, time1[, value2, time2[, ...]]);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=522|t=Part 1}} {{!}} {{fgdata file|Nasal/globals.nas|t=Part 2}}<br />
|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.<br />
|param1 = prop<br />
|param1text = String or <code>props.Node</code> object that indicates a node in the property tree to be used.<br />
|param2 = value''n''<br />
|param2text = Target value to change the property to in the set amount of time. This should be a number.<br />
|param3 = time''n''<br />
|param3text = Time in seconds, that will be taken for the interpolation.<br />
|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.<br />
|example1 = setprop("/test", 0); # (re-)set property<br />
interpolate("/test",<br />
50, 5, # interpolate to 50 in 5 seconds<br />
10, 2, # interpolate to 10 in 2 seconds<br />
0, 5); # interpolate to 0 in 5 seconds<br />
|example2 = # Apply the left brake at 20% per second<br />
var prop = "controls/gear/brake-left";<br />
var dist = 1 - getprop(prop);<br />
if (dist == 1) {<br />
interpolate(prop, 1, dist / 0.2);<br />
}<br />
}}<br />
<br />
=== isa() ===<br />
{{Nasal doc<br />
|syntax = isa(object, class);<br />
|source = {{fgdata file|Nasal/globals.nas|t=Source}}<br />
|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.<br />
|param1 = object<br />
|param1text = Object to check.<br />
|param2 = class<br />
|param2text = Class/object to check that '''object''' inherits from or is an instance of.<br />
|example1 = var coord = geo.Coord.new();<br />
if(isa(coord, geo.Coord)){<br />
print("Variable 'coord' is an instance of class 'geo.Coord'"); # this one will be printed<br />
} else {<br />
print("Variable 'coord' is not an instance of class 'geo.Coord'");<br />
}<br />
|example2 = var coord = geo.Coord.new();<br />
if(isa(coord, props.Node)){<br />
print("Variable 'coord' is an instance of class 'props.Node'");<br />
} else {<br />
print("Variable 'coord' is not an instance of class 'props.Node'"); # this one will be printed<br />
}<br />
|example3text = The example below demonstrates checking of inheritance.<br />
|example3 = var Const = {<br />
constant: 2,<br />
getConst: func {<br />
return me.constant;<br />
}<br />
};<br />
<br />
var Add = {<br />
new: func {<br />
return { parents: [Add, Const] };<br />
},<br />
<br />
addToConst: func(a){<br />
return a * me.getConst();<br />
}<br />
};<br />
<br />
var m = Add.new();<br />
print(m.addToConst(4));<br />
<br />
if(isa(m, Add)) print("Variable 'm' is an instance of class 'Add'"); # will be printed<br />
if(isa(m, Const)) print("Variable 'm' is an instance of class 'Const'"); # will also be printed<br />
}}<br />
<br />
=== logprint() ===<br />
{{Nasal doc<br />
|syntax = logprint(priority[, msg[, msg[, ...]]]);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=431|t=Source}}<br />
|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 <code>[[Commonly used debugging tools#fgfs.log|fgfs.log]]</code> file as coming from the Nasal file itself rather than from {{flightgear file|src/Scripting/NasalSys.cxx}}.<br />
|param1 = priority<br />
|param1text = Number specifying the priority level of the outputted message:<br />
:{{{!}} class="wikitable"<br />
! Number !! Debug type<br />
{{!-}}<br />
{{!}} 1 {{!!}} Bulk<br />
{{!-}}<br />
{{!}} 2 {{!!}} Debug<br />
{{!-}}<br />
{{!}} 3 {{!!}} Info<br />
{{!-}}<br />
{{!}} 4 {{!!}} Warn<br />
{{!-}}<br />
{{!}} 5 {{!!}} Alert<br />
{{!}}}<br />
|param2 = msg<br />
|param2text = The message. There is no limit to the arguments you give give. They will be concatenated together before logging.<br />
|example1 = # logs the value of pi to three decimal places with log level 3<br />
logprint(3, "pi = ", sprintf("%.3f", math.pi));<br />
|example2 = logprint(5, "Alert! This is an important message!");<br />
}}<br />
{{note| <br />
The following constants have been added to the development branch of FlightGear ("next") and will be releases with FG 2020.1 so you won't have to remember the numbers anymore:<br />
<br />
LOG_BULK, LOG_WARN, LOG_DEBUG, LOG_INFO, LOG_ALERT, DEV_WARN, DEV_ALERT }}<br />
<br />
=== magvar() ===<br />
{{Nasal doc<br />
|syntax = magvar([pos]);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1642|t=Source}}<br />
|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.<br />
{{{!}} class="wikitable"<br />
! FlightGear versions !! Model !! Reference date<br />
{{!-}}<br />
{{!}} 3.6 and above {{!!}} {{wikipedia|World Magnetic Model}} (WMM) 2015 {{!!}} 1 January 2015<br />
{{!-}}<br />
{{!}} 0.9.11-pre1 to 3.4 {{!!}} WMM 2005 {{!!}} 1 January 2005<br />
{{!-}}<br />
{{!}} 0.7.3 to 0.9.10 {{!!}} WMM 2000 {{!!}} 1 January 2000<br />
{{!}}}<br />
|param1 = pos<br />
|param1text = Optional position to calculate from. If not given, the aircraft's current position will be used. Can be one of:<br />
:* An <code>airport</code>, <code>navaid</code>, <code>runway</code>, <code>taxiway</code>, <code>fix</code>, or <code>waypoint</code> ghost object.<br />
:* A hash with ''lat'' and ''lon'' members<br />
:* A <code>geo.Coord</code> object<br />
:* A lat/lon pair, that is, a pair of numbers (latitude followed by longitude) separated by a comma: <code>magvar(lat,lon)</code>.<br />
|example1 = print(magvar(0, 0)); # prints the magnetic variation at 0, 0<br />
}}<br />
<br />
=== maketimer() ===<br />
{{Nasal doc<br />
|syntax = maketimer(interval[, self], function);<br />
|source = ''Implemented using the {{API Link|flightgear|class|TimerObj}} class.''<br>{{flightgear file|src/Scripting/NasalSys.cxx|l=90|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=533|t=Part 2}}<br />
|version = 2.12<br />
|commit = {{flightgear commit|ab939f|t=commit}}<br />
|text = Returns a timer object containing the following methods and members:<br />
* '''start()''': Starts the timer.<br />
* '''stop()''': Stops the timer.<br />
* '''restart(interval)''': Restarts the timer with the given interval.<br />
* '''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).<br />
* '''isRunning''': Read-only bool telling whether the timer is currently running.<br />
* '''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.<br />
Unlike {{func link|settimer()}}, which it replaces, <code>maketimer()</code> provides more control over the timer. In addition, it can help reduce memory usage.<br />
|param1 = interval<br />
|param1text = Interval in seconds for the timer.<br />
|param2 = self<br />
|param2text = Optional parameter specifying what any <code>'''me'''</code> references in the function being called will refer to.<br />
|param3 = function<br />
|param3text = Function to be called at the given interval.<br />
|example1 = var timer = maketimer(1, func(){<br />
print("Hello, World!"); # print "Hello, World!" once every second (call timer.stop() to stop it)<br />
});<br />
timer.start();<br />
|example2 = var timer = maketimer(1, math, func(){<br />
print(me.math); # 'me' reference is the 'math' namespace<br />
});<br />
timer.singleShot = 1; # timer will only be run once<br />
timer.start();<br />
|example3 = var timer = maketimer(1, func(){<br />
print("Hello, World!"); # print "Hello, World!" once every second (call timer.stop() to stop it)<br />
});<br />
timer.start();<br />
print(timer.isRunning); # prints 1<br />
|example4text = In the example below, "Hello, World!" will be printed after one second the first time, and after two seconds thereafter.<br />
|example4 = var update = func(){<br />
print("Hello, World!");<br />
timer.restart(2); # restarts the timer with a two second interval<br />
}<br />
<br />
var timer = maketimer(1, update);<br />
timer.singleShot = 1;<br />
timer.start();<br />
}}<br />
<br />
=== maketimestamp() ===<br />
{{Nasal doc<br />
|syntax = maketimestamp()<br />
|source = ''Implemented using the {{API Link|flightgear|class|TimeStampObj}} class.''<br>{{flightgear file|src/Scripting/NasalSys.cxx|l=214|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=589|t=Part 2}}<br />
|version = 2019.2<br />
|commit = {{flightgear commit|7db06300|t=commit}}<br />
|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:<br />
* '''stamp()''': Resets the timing operation. Call this first.<br />
* '''elapsedMSec()''': returns number of milliseconds elapsed since stamp() called. Resolution may vary depending on platform but is usually at least millisecond accuracy.<br />
* '''elapsedUSec()''': returns number of microseconds elapsed since stamp() called. Resolution may vary depending on platform but is usually at least millisecond accuracy.<br />
|<br />
|example1text = In the example below the number of milliseconds elapsed will be printed.<br />
|example1 = var timestamp = maketimestamp();<br />
timestamp.stamp();<br />
print(timestamp.elapsedMSec(), "ms elapsed");<br />
print(timestamp.elapsedMSec(), "ms elapsed");<br />
}}<br />
<br />
=== md5() ===<br />
{{Nasal doc<br />
|syntax = md5(string);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=758|t=Source}}<br />
|version = 3.2<br />
|commit = {{flightgear commit|cfbf9e|t=commit}}<br />
|text = Returns a the {{wikipedia|MD5}} hash (as a string) of the inputted string.<br />
|param1 = string<br />
|param1text = String the generate the hash of. Mandatory.<br />
|example1text = The below code should output <code>65a8e27d8879283831b664bd8b7f0ad4</code>.<br />
|example1 = print(md5("Hello, World!"));<br />
}}<br />
<br />
=== navinfo() ===<br />
{{Nasal doc<br />
|syntax = navinfo(lat, lon, type, id);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1453|t=Source}}<br />
|text = Returns vector <code>navaid</code> ghost objects matching the given '''type''' and '''id''' or <code>'''nil'''</code> on error.<br />
|param1 = lat ''and'' lon<br />
|param1text = If given, the returned navaids will be put into order of ascending distance from the location.<br />
|param2 = type<br />
|param2text = Narrows the search to the given type. Must be one of "any," "fix," "vor," "ndb," "ils," "dme," or "tacan." Defaults to the equivalent of "any."<br />
|param3 = id<br />
|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.<br />
|example1 = navinfo("vor"); # returns all VORs<br />
|example2 = navinfo("HAM"); # return all navaids whose names start with "HAM"<br />
|example3 = navinfo("vor", "HAM"); # return all VORs whose names start with "HAM"<br />
|example4 = navinfo(34,48,"vor","HAM"); # return all VORs whose names start with "HAM" and sorted by distance relative to 34°, 48°<br />
}}<br />
<br />
=== parse_markdown() ===<br />
{{Nasal doc<br />
|syntax = parse_markdown(markdown);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=745|t=Part 1}} {{!}} {{simgear file|simgear/misc/SimpleMarkdown.cxx|t=Part 2}} <br />
|version = 3.2<br />
|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:<br />
* It strips whitespace from the beginning of the string.<br />
* It supports [http://daringfireball.net/projects/markdown/syntax#p paragraphs and line breaks].<br />
* It collapses whitespace.<br />
* It converts unordered [http://daringfireball.net/projects/markdown/syntax#list lists] to use a bullet character (&bull;). Note that the bullet character is implemented in hexadecimal UTF-8 character bytes (<code>E2 80 A2</code>), as so may not work properly when the being displayed in an encoding other than UTF-8.<br />
|param1 = markdown<br />
|param1text = String containing Markdown to be parsed.<br />
|example1text = <br />
Save the below code as <tt>''[[$FG_ROOT]]/gui/dialogs/test.xml''</tt>, 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 (<tt>Debug > Reload GUI</tt>).<br />
<syntaxhighlight lang="xml" highlight="41-50"><br />
<?xml version="1.0" encoding="UTF-8"?><br />
<br />
<PropertyList><br />
<br />
<name>test</name><br />
<layout>vbox</layout><br />
<br />
<group><br />
<layout>hbox</layout><br />
<br />
<empty><br />
<stretch>true</stretch><br />
</empty><br />
<br />
<text><br />
<label>parse_markdown() test dialog</label><br />
</text><br />
<br />
<empty><br />
<stretch>true</stretch><br />
</empty><br />
<br />
<button><br />
<legend></legend><br />
<pref-width>16</pref-width><br />
<pref-height>16</pref-height><br />
<binding><br />
<command>dialog-close</command><br />
</binding><br />
</button><br />
<br />
</group><br />
<br />
<canvas><br />
<name>Canvas plot</name><br />
<stretch>true</stretch><br />
<pref-width>400</pref-width><br />
<pref-height>300</pref-height><br />
<nasal><br />
<load><![CDATA[<br />
var text = 'Items:<br />
* apples<br />
* oranges<br />
* pears<br />
<br />
Some text.<br />
Some more items:<br />
* apples<br />
* oranges<br />
* pears';<br />
<br />
var parsed = parse_markdown(text);<br />
<br />
var root_canvas = canvas.get(cmdarg());<br />
root_canvas.setColorBackground(255, 255, 255);<br />
var root = root_canvas.createGroup();<br />
<br />
var text_dis = root.createChild("text")<br />
.setText(parsed)<br />
.setTranslation(5, 5)<br />
.setFont("LiberationFonts\LiberationSans-Regular.ttf")<br />
.setFontSize(15)<br />
.setColor(0, 0, 0)<br />
.setDrawMode(canvas.Text.TEXT)<br />
.setAlignment("left-top");<br />
]]></load><br />
</nasal><br />
</canvas><br />
<br />
</PropertyList><br />
</syntaxhighlight><br />
|example1 = fgcommand("dialog-show", {"dialog-name": "test"});<br />
|example2text = The example below parses Markdown and outputs it in a HTML document. The parsed text is placed in <syntaxhighlight lang="xml" inline><pre></pre></syntaxhighlight> tags. To change the Markdown to be parsed, simply edit the variable <tt>markdown</tt> at the top of the code.<br />
|example2 = <nowiki>var markdown = 'Items:<br />
* apples<br />
* oranges<br />
* pears<br />
<br />
Some text.<br />
Some more items:<br />
* apples<br />
* oranges<br />
* pears';<br />
<br />
var parsed = parse_markdown(markdown);<br />
<br />
debug.dump(parsed);<br />
<br />
var path = string.normpath(getprop("/sim/fg-home") ~ '/Export/parse_markdown()-test.html');<br />
<br />
var file = io.open(path, "w");<br />
<br />
var html = "<!DOCTYPE html>\n\n<html>\n\n<head>\n\t<meta charset=\"UTF-8\">\n\t<title>parse_markdown() test generated by Nasal</title>\n</head>\n\n<body>\n\t<pre>" ~ parsed ~ "</pre>\n</body>\n\n</html>";<br />
<br />
io.write(file, html);<br />
io.close(file);<br />
print("Done, file ready for viewing (" ~ path ~ ")");</nowiki><br />
}}<br />
<br />
=== parsexml() ===<br />
{{Nasal doc<br />
|syntax = parsexml(path[, start[, end[, data[, pro_ins]]]]);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=710|t=Source}}<br />
|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 <code>'''nil'''</code> is returned on error.<br />
|param1 = path<br />
|param1text = Mandatory absolute path to the XML file to be parsed.<br />
|param2 = start<br />
|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.<br />
|param3 = end<br />
|param3text = Optional callback function that will be called for every ending tag. The function should take one argument: the tag name.<br />
|param4 = data<br />
|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.<br />
|param5 = pro_ins<br />
|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.<br />
|example1text = Save the below XML code in <tt>''[[$FG_HOME]]/Export/demo.xml''</tt>. Then, execute the Nasal code below in the Nasal Console. The XML will be parsed and each bit of the code will be printed.<br />
<syntaxhighlight lang="xml"><br />
<?xml version="1.0" encoding="UTF-8"?><br />
<br />
<?xml-stylesheet type="text/xsl" href="style.xsl"?><br />
<br />
<foo><br />
<blah type="string"><![CDATA[ <sender>John Smith</sender> ]]></blah><br />
<blah2 type="string">Orange &amp; lemons</blah2><br />
</foo><br />
</syntaxhighlight><br />
|example1 = var start = func(name, attr){<br />
print("Starting tag: '", name, "'");<br />
foreach(var a; keys(attr)){<br />
print("\twith attribute ", a, '="', attr[a], '"');<br />
}<br />
}<br />
<br />
var end = func(name){<br />
print("Ending tag: '", name, "'");<br />
}<br />
<br />
var data = func(data){<br />
print("Data = '", data, "'");<br />
}<br />
<br />
var pro_instr = func(target, data){<br />
print("Processing instruction: target = '", target, "', data = '", data, "'");<br />
}<br />
<br />
parsexml(getprop("/sim/fg-home") ~ '/Export/demo.xml', start, end, data, pro_instr);<br />
}}<br />
<br />
=== print() ===<br />
{{Nasal doc<br />
|syntax = print(data[, data[, ...]]);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=415|t=Source}}<br />
|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.<br />
|param1 = data<br />
|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.<br />
|example1 = print("Just", " a ", "test"); # prints "Just a test"<br />
|example2 = print("pi = ", math.pi); # prints "pi = 3.141592..."<br />
}}<br />
<br />
=== printf() ===<br />
{{Nasal doc<br />
|syntax = printf(format[, arg[, arg, [...]]]);<br />
|source = {{fgdata file|Nasal/globals.nas|t=Source}}<br />
|text = Creates and prints a formatted string. For a description of its arguments, see {{func link|sprintf()}} (it is, in fact, implemented using <code>sprintf()</code>).<br />
|example1 = printf("In hexadecimal, 100000 = %X", 186A0); # prints "In hexadecimal, 100000 = 186A0"<br />
}}<br />
<br />
=== printlog() ===<br />
{{Nasal doc<br />
|syntax = printlog(level, data[, data[, ...]]);<br />
|source = {{fgdata file|Nasal/globals.nas|t=Source}}<br />
|text = Prints the given message with the given log level. If the log level is higher or equal to <code>/sim/logging/priority</code>, it is printed.<br />
|param1 = level<br />
|param1text = Mandatory log level as a string. Must be one of "none," "bulk," "debug," "info," "warn," or "alert." Note that "none" will mean that the message will never be printed.<br />
|param2 = data<br />
|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.<br />
|example1 = printlog("alert", "This is an alert"); # message will be printed<br />
|example2 = printlog("info", "Just informing you about something"); # message will be printed only if log level is set to "info" or less<br />
}}<br />
<br />
=== rand() ===<br />
{{Nasal doc<br />
|syntax = rand();<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=554|t=Source}}<br />
|text = Returns a random floating point number between 0 (inclusive) and 1 (exclusive). It takes no arguments.<br />
|example1 = print(rand()); # prints random number<br />
}}<br />
<br />
=== registerFlightPlanDelegate() ===<br />
{{Nasal doc<br />
|syntax = registerFlightPlanDelegate(init_func);<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1879|t=Source}}<br />
|text = Registers a flight plan delegate. See <tt>''{{fgdata file|Nasal/route_manager.nas|t=$FG_ROOT/Nasal/route_manager.nas}}''</tt> for examples.<br />
|param1 = init_func<br />
|param1text = Initialization function which will be called during FlightGear's startup.<br />
}}<br />
=== removecommand() ===<br />
{{Nasal doc<br />
|syntax = removecommand(cmd);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=668|t=Source}}<br />
|text = Removes the given fgcommand. Returns <code>'''nil'''</code>.<br />
{{caution|This will remove '''any''' [[fgcommands|fgcommand]], even those implemented in C++, so use with caution!}}<br />
|param1 = cmd<br />
|param1text = String specifying the name of the command to remove.<br />
|example1 = addcommand("hello", func(){<br />
print("Hello");<br />
});<br />
fgcommand("hello"); # "Hello" will be printed<br />
removecommand("hello"); # removes it<br />
}}<br />
<br />
=== removelistener() ===<br />
{{Nasal doc<br />
|syntax = removelistener(id);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=1384|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=506|t=Part 2}}<br />
|text = Removes and deactivates the given listener and returns the number of listeners left or <code>'''nil'''</code> on error.<br />
{{note|It is good practice to remove listeners when they are not required anymore. This prevents the listeners reducing FlightGear's run performance.}}<br />
|param1 = id<br />
|param1text = ID of listener as returned by {{func link|setlistener()}}.<br />
|example1 = var ls = setlistener("/sim/test", func(){<br />
print("Property '/sim/test' has been changed");<br />
});<br />
setprop("/sim/test", "blah"); # trigger listener<br />
var rem = removelistener(ls); # remove listener<br />
print("There are ", rem, " listeners remaining");<br />
}}<br />
<br />
=== resolvepath() ===<br />
{{Nasal doc<br />
|syntax = resolvepath(path);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=604|t=Source}}<br />
|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.<br />
|param1 = path<br />
|param1text = Relative path to be completed.<br />
|example1 = print(resolvepath("Nasal/globals.nas")); # prints the equivalent of $FG_ROOT/Nasal/globals.nas<br />
|example2 = print(resolvepath("blah")); # prints nothing; could not be resolved<br />
|example3 = var file_path = resolvepath("Aircraft/SenecaII/some-file");<br />
if (file_path != ""){<br />
gui.popupTip("some-file found", 2);<br />
} else {<br />
gui.popupTip("some-file not found", 2);<br />
}<br />
}}<br />
<br />
=== setlistener() ===<br />
{{Nasal doc<br />
|syntax = setlistener(node, code[, init[, type]]);<br />
|private = _setlistener()<br />
|source = {{flightgear file|src/Scripting/Nasal|l=499|t=Part 1}} {{!}} {{flightgear file|src/Scripting/Nasal|l=1350|t=Part 2}}<br>''Listener implemented using the {{API Link|flightgear|class|FGNasalListener}} class.''<br />
|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()}}.<br />
{{note|Listeners are known to be a source of resource leaks. To avoid this, please take measures such as:<br />
* Using {{func link|removelistener()}} when they are not needed any more.<br />
* Using a single initialization listener.<br />
* Avoiding tying listeners to properties that are rapidly updated (e.g., many times per frame).<br />
}}<br />
|param1 = node<br />
|param1text = Mandatory string or <code>props.Node</code> object pointing to a property in the [[Property Tree]].<br />
|param2 = code<br />
|param2text = Mandatory callback function to execute when the listener is triggered. The function can take up to four arguments in the following order:<br />
* '''changed''': a <code>props.Node</code> object pointing to the changed node.<br />
* '''listen''': a <code>props.Node</code> object pointing to the listened-to node. Note that this argument maybe different depending on the '''type'''.<br />
* '''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.<br />
* '''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.<br />
|param3 = init<br />
|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).<br />
|param4 = type<br />
|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.<br />
|example1 = var prop = props.globals.initNode("/sim/test", "", "STRING"); # create property<br />
var id = setlistener("/sim/test", func(n){ # create listener<br />
print("Value: ", n.getValue());<br />
});<br />
setprop("/sim/test", "blah"); # trigger listener<br />
removelistener(id); # remove listener<br />
prop.remove(); # remove property<br />
}}<br />
<br />
=== setprop() ===<br />
{{Nasal doc<br />
|syntax = setprop(path[, path[, ...]], value);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=385|t=Source}}<br />
|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.<br />
{{note|If you want to remove a property, you will have to use one of the <code>props</code> helpers:<br />
<syntaxhighlight lang="nasal"><br />
props.globals.getNode("foo/bar").remove(); # take out the complete node<br />
props.globals.getNode("foo").removeChild("bar"); # take out a certain child node<br />
</syntaxhighlight><br />
}}<br />
|param1 = path<br />
|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.<br />
|param2 = value<br />
|param2text = Value to write to the given property. Must be either a string or a number.<br />
|example1 = setprop("/sim/demo", "This is a demo");<br />
|example2text = Note that the example below will only work in FlightGear 3.2 and above.<br />
|example2 = for(var i = 0; i < 3; i += 1){<br />
setprop("/sim/demo", i, "Demo #" ~ i));<br />
}<br />
|example3text = Same as above, but is supported by all versions of FlightGear.<br />
|example3 = for(var i = 0; i < 3; i += 1){<br />
setprop("/sim/demo[" ~ i ~ "]", "Demo #" ~ i));<br />
}<br />
}}<br />
==== See also ====<br />
{{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: <br />
To get a Node rather than its value, use <code>props.globals.getNode()</code> - see [[Nasal_library/props]]. }}<br />
<br />
=== settimer() ===<br />
{{Nasal doc<br />
|syntax = settimer(function, delta[, realtime]);<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=499|t=Part 1}} {{!}} {{flightgear file|src/Scripting/NasalSys.cxx|l=1286|t=Part 2}}<br />
|text = Runs the given function a specified amount of seconds after the current time. Returns <code>'''nil'''</code>.<br />
{{note|Improper use of <code>settimer()</code> may cause resource leaks. It is also highly recommended that the newer {{func link|maketimer()}} should be used instead of this function.}} <br />
|param1 = function<br />
|param1text = Mandatory function that will be called. It may be necessary to enclose code in an anonymous function (see example).<br />
|param2 = delta<br />
|param2text = Mandatory amount of time in seconds after which the function will be called.<br />
|param3 = realtime<br />
|param3text = If 1 (true), "real time" will be used instead of "simulation time." Defaults to 0 (false). Note that if "simulation time" is used, the timer will not run while FlightGear is paused.<br />
|example1 = var myFunc = func(){<br />
print("Hello");<br />
}<br />
<br />
settimer(myFunc, 2); # runs myFunc after 2 seconds<br />
|example2 = var sqr = func(a){<br />
return a * a;<br />
}<br />
<br />
settimer(func(){<br />
print(sqr(2)); # will print 4 after 2 seconds<br />
}, 2);<br />
}}<br />
<br />
=== srand() ===<br />
{{Nasal doc<br />
|syntax = srand();<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=559|t=Source}}<br />
|text = Makes the pseudorandom number generator (see {{func link|rand()}}) generate a new {{wikipedia|random seed|noicon=1}} based on time. Returns 0.<br />
}}<br />
=== systime() ===<br />
{{Nasal doc<br />
|syntax = systime();<br />
|source = {{flightgear file|src/Scripting/NasalSys.cxx|l=770|t=Source}}<br />
|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).<br />
{{note|1=High resolution timers under Windows can produce inaccurate or fixed sub-millisecond results.<ref>{{cite web|url=http://forum.flightgear.org/viewtopic.php?f=30&t=29259|title=Nasal: systime() ??!?|author=Necolatis|date=Apr 2nd, 2016}}</ref> 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]}}<br />
|example1 = print("Unix time: ", systime()); # prints Unix time<br />
|example2 = var myFunc = func(){<br />
for(var i = 0; i <= 10; i += 1){<br />
print("Interation #", i);<br />
}<br />
}<br />
var t = systime(); # record time<br />
myFunc(); # run function<br />
var t2 = systime(); # record new time<br />
print("myFunc() took ", t2 - t, " seconds"); # print result<br />
}}<br />
<br />
=== thisfunc() ===<br />
{{Nasal doc<br />
|syntax = thisfunc();<br />
|source = {{fgdata file|Nasal/globals.nas|t=Source}}<br />
|text = Returns the function from which this function is called. This allows a function to reliably and safely call itself from within a closure.<br />
|example1 = var stringify_vec = func(input){<br />
if (typeof(input) == "scalar"){<br />
return sprintf("%s", input);<br />
} elsif (typeof(input) == "vector") {<br />
if (size(input) == 0) return "[]";<br />
var this = thisfunc();<br />
var buffer = "[";<br />
for(var i = 0; i < size(input); i += 1){<br />
buffer ~= this(input[i]);<br />
if (i == size(input) - 1) {<br />
buffer ~= "]";<br />
} else {<br />
buffer ~= ", ";<br />
}<br />
}<br />
return buffer;<br />
} else {<br />
die("stringify_vec(): Error! Invalid input. Must be a vector or scalar");<br />
}<br />
}<br />
<br />
var test_vec = ["a", "b", "c", 1, 2, 3];<br />
debug.dump(stringify_vec(test_vec)); # prints "[a, b, c, 1, 2, 3]"<br />
test_vec = [];<br />
debug.dump(stringify_vec(test_vec)); # prints "[]"<br />
test_vec = {};<br />
debug.dump(stringify_vec(test_vec)); # will throw an error<br />
}}<br />
<br />
=== tileIndex() ===<br />
{{Nasal doc<br />
|syntax = tileIndex();<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1720|t=Source}}<br />
|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 <code>942050</code>, corresponding to <tt>''[[$FG_SCENERY]]/Terrain/w130n30/w123n3/942050.stg''</tt>.<br />
|example1 = print(tileIndex()); # print index<br />
}}<br />
<br />
=== tilePath() ===<br />
{{Nasal doc<br />
|syntax = tilePath();<br />
|source = {{flightgear file|src/Scripting/NasalPositioned.cxx|l=1712|t=Source}}<br />
|text = Returns the base path of the tile at the aircraft's current position as a string. For example, at KSFO, this would be <code>w130n30/w123n3</code>, corresponding to <tt>''[[$FG_SCENERY]]/Terrain/w130n30/w123n3''</tt>.<br />
|example1 = print(tilePath()); # print path<br />
}}<br />
<br />
=== values() ===<br />
{{Nasal doc<br />
|syntax = values(hash);<br />
|source = {{fgdata file|Nasal/globals.nas|t=Source}}<br />
|text = Returns a vector containing the values of the given hash.<br />
|param1 = hash<br />
|param1text = Mandatory hash to get the values of.<br />
|example1 = var hash = {<br />
"a": 1,<br />
"b": 2,<br />
"c": 3<br />
};<br />
<br />
foreach(var val; values(hash)){<br />
print(val);<br />
}<br />
|example2text = The below example does exactly the same thing as the above example, but does not use <code>values()</code>:<br />
|example2 = var hash = {<br />
"a": 1,<br />
"b": 2,<br />
"c": 3<br />
};<br />
<br />
foreach(var key; keys(hash)){<br />
print(hash[key]);<br />
}<br />
}}<br />
<br />
== Variables ==<br />
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}}.<br />
<br />
=== D2R ===<br />
{{Nasal doc<br />
|syntax = var radians = degrees * D2R;<br />
|text = Converts an angle from degrees to radians when multiplied by the angle in degrees. Equal to <code>π / 180</code>.<br />
}}<br />
=== FPS2KT ===<br />
{{Nasal doc<br />
|syntax = var knots = feet_per_second * FPS2KT;<br />
|text = Converts a velocity from feet per second to knots when multiplied by the velocity in feet per second. Approximately equal to 0.5925.<br />
}}<br />
=== FT2M ===<br />
{{Nasal doc<br />
|syntax = var metres = feet * FT2M;<br />
|text = Converts a length from feet to metres when multiplied by the length in feet. Equal to 0.3048.<br />
}}<br />
=== GAL2L ===<br />
{{Nasal doc<br />
|syntax = var litres = gallons * GAL2L;<br />
|text = Converts a volume from US liquid gallons to litres when multiplied by the volume in gallons. Approximately equal to 3.7854.<br />
}}<br />
=== IN2M ===<br />
{{Nasal doc<br />
|syntax = var metres = inches * IN2M;<br />
|text = Converts a length from inches to metres when multiplied by the length in inches. Equal to 0.0254.<br />
}}<br />
=== KG2LB ===<br />
{{Nasal doc<br />
|syntax = var pounds = kilograms * KG2LB;<br />
|text = Converts a mass from kilograms to pounds when multiplied by the mass in kilograms. Approximately equal to 2.2046.<br />
}}<br />
=== KT2FPS ===<br />
{{Nasal doc<br />
|syntax = var feet_per_second = knots * KT2FPS;<br />
|text = Converts a velocity from knots to feet per second when multiplied by the velocity in knots. Approximately equal to 1.6878.<br />
}}<br />
=== KT2MPS ===<br />
{{Nasal doc<br />
|syntax = var metres_per_second = knots * KT2MPS;<br />
|text = Converts a velocity from knots to metres per second when multiplied by the velocity in knots. Approximately equal to 0.5144.<br />
}}<br />
=== L2GAL ===<br />
{{Nasal doc<br />
|syntax = var gallons = litres * L2GAL;<br />
|text = Converts a volume from litres to US liquid gallons when multiplied by the volume in litres. Approximately equal to 0.2642.<br />
}}<br />
=== LB2KG ===<br />
{{Nasal doc<br />
|syntax = var kilograms = pounds * LB2KG;<br />
|text = Converts a mass from pounds to kilograms when multiplied by the mass in pounds. Approximately equal to 0.4536.<br />
}}<br />
=== M2FT ===<br />
{{Nasal doc<br />
|syntax = var feet = metres * M2FT;<br />
|text = Converts a length from metres to feet when multiplied by the length in metres. Approximately equal to 3.2808.<br />
}}<br />
=== M2IN ===<br />
{{Nasal doc<br />
|syntax = var inches = metres * M2IN;<br />
|text = Converts a length from metres to inches when multiplied by the length in metres. Approximately equal to 39.3701.<br />
}}<br />
=== M2NM ===<br />
{{Nasal doc<br />
|syntax = var nautical_miles = metres * M2NM;<br />
|text = Converts a length from metres to nautical miles when multiplied by the length in metres. Approximately equal to 0.00054.<br />
}}<br />
=== MPS2KT ===<br />
{{Nasal doc<br />
|syntax = var knots = metres_per_second * MPS2KT;<br />
|text = Converts a velocity from metres per second to knots when multiplied by the velocity in metres per second. Approximately equal to 1.9438.<br />
}}<br />
=== NM2M ===<br />
{{Nasal doc<br />
|syntax = var metres = nautical_miles * NM2M;<br />
|text = Converts a length from nautical miles to metres when multiplied by the length in nautical miles. Equal to 1,852.<br />
}}<br />
=== R2D ===<br />
{{Nasal doc<br />
|syntax = var degrees = radians * R2D;<br />
|text = Converts an angle from radians to degrees when multiplied by the angle in radians. Equal to <code>180 / π</code>.<br />
}}<br />
<br />
{{Appendix}}<br />
<br />
<br />
{{Nasal namespaces}}</div>Jsb