Put actual stuff in

This commit is contained in:
Tobias Dammers 2021-09-22 22:44:32 +02:00
parent 8c9e6324ed
commit 0a86a3cc3b
6 changed files with 946 additions and 0 deletions

67
README Normal file
View File

@ -0,0 +1,67 @@
FG SimBrief Importer Add-On
===========================
What Is This?
-------------
This is an add-on for FlightGear that adds a SimBrief Import dialog.
(See https://www.simbrief.com/).
What It Does
------------
The SimBrief importer can import various aspects of a SimBrief flight plan into
FlightGear. The import functionality attempts to support as many aircraft types
as possible, but due to the nature of the beast, it does not work equally well
with all of them.
Ideally, the following functionality will be supported:
- Flight Plan Route: This one imports the departure and destination airports,
and all enroute waypoints, into the default flightplan. If "Activate
immediately" is checked, it will also activate the flightplan.
- Departure RWY, SID: This will attempt to select the planned departure runway,
and the planned SID, from the flightplan. Note that this will only work if
your FlightGear navdata matches the selections from SimBrief, which may not
be the case, especially if you're using the default FG navdata and/or an
outdated AIRAC cycle in SimBrief.
- Arrival RWY, STAR: This will attempt to select the planned arrival runway and
STAR. The same caveats apply as with the departure runway and SID.
- Performance Init: This sets a handful of key performance parameters;
currently: cruise altitude and callsign.
- Payload: Imports passenger and cargo weights, and attempts to distribute them
sensibly over available payload weight slots.
- Fuel: Imports block fuel as per the flightplan, and attempts to distribute it
sensibly over available fuel tanks. Because the importer has no idea where
those tanks are located, or what their priorities are, it starts at the top,
and uses a very crude logic to detect left/right pairs. (A more sophisticated
fuel distribution system may be provided in the future).
- Winds Aloft: Runs a background process that sets winds aloft according to the
forecast winds in the flightplan. This will only work with Basic Weather,
since the Advanced Weather engine runs its own wind simulation that will
overwrite winds aloft regardless of what we set.
Supported Aircraft Types (as per 09/2021)
-----------------------------------------
| FPL | Dep/SID | Arr/STAR | Perf Init | Payload | Fuel |
+------------------------+-----+---------+----------+-----------+---------+------+
| E-Jet-family-YV | E-Jet uses its own version of the simbrief importer |
+------------------------+-----+---------+----------+-----------+---------+------+
| A320-family | A320 uses its own simbrief importer |
+------------------------+-----+---------+----------+-----------+---------+------+
| 747-8i | yes | yes | yes | yes | yes | yes |
+------------------------+-----+---------+----------+-----------+---------+------+
| 747-400 | yes | yes | yes | yes | no | yes |
+------------------------+-----+---------+----------+-----------+---------+------+
| 737-800YV | yes | yes | yes | yes | no | yes |
+------------------------+-----+---------+----------+-----------+---------+------+
| 777 | yes | yes | yes | yes | no | yes |
+------------------------+-----+---------+----------+-----------+---------+------+
| Citation-II | yes | yes | yes | yes | yes | yes |
+------------------------+-----+---------+----------+-----------+---------+------+
| CRJ700-family | yes | yes | yes | yes | yes | yes |
+------------------------+-----+---------+----------+-----------+---------+------+
| QSeries | yes | yes | yes | yes | no | yes |
+------------------------+-----+---------+----------+-----------+---------+------+

28
addon-config.xml Normal file
View File

@ -0,0 +1,28 @@
<?xml version="1.0"?>
<PropertyList>
<sim>
<simbrief>
<username type="string" userarchive="y" />
<options>
<autocommit type="bool" userarchive="y">0</autocommit>
<import-fp type="bool" userarchive="y">1</import-fp>
<import-departure type="bool" userarchive="y">0</import-departure>
<import-arrival type="bool" userarchive="y">0</import-arrival>
<import-payload type="bool" userarchive="y">0</import-payload>
<import-fob type="bool" userarchive="y">0</import-fob>
<import-perfinit type="bool" userarchive="y">0</import-perfinit>
<import-winds-aloft type="bool" userarchive="y">0</import-winds-aloft>
</options>
<downloading type="bool">0</downloading>
<text-status type="string"></text-status>
</simbrief>
</sim>
<!--
<path>/sim/simbrief/username</path>
<path>/sim/simbrief/options/autocommit</path>
<path>/sim/simbrief/options/import-payload</path>
<path>/sim/simbrief/options/import-fob</path>
<path>/sim/simbrief/options/import-perfinit</path>
<path>/sim/simbrief/options/import-winds-aloft</path>
-->
</PropertyList>

584
addon-main.nas Normal file
View File

@ -0,0 +1,584 @@
var urlencode = func(str) {
var out = '';
var c = '';
var n = 0;
for (var i = 0; i < size(str); i += 1) {
n = str[i];
if (string.isalnum(n)) {
out = out ~ chr(n);
}
elsif (n == 32) {
out = out ~ '+';
}
else {
out = out ~ sprintf('%%%02x', n);
}
}
return out;
};
var download = func (username, onSuccess, onFailure=nil) {
if (getprop('/sim/simbrief/downloading')) {
print("SimBrief download already active");
}
setprop('/sim/simbrief/downloading', 1);
setprop('/sim/simbrief/text-status', 'downloading...');
var filename = getprop('/sim/fg-home') ~ "/Export/simbrief.xml";
var url = "https://www.simbrief.com/api/xml.fetcher.php?username=" ~ urlencode(username);
if (onFailure == nil) {
onFailure = func (r) {
setprop('/sim/simbrief/text-status', sprintf('HTTP error (%s/%s)', r.status, r.reason));
printf("SimBrief download from %s failed with HTTP status %s",
url, r.status);
}
}
http.save(url, filename)
.done(func (r) {
setprop('/sim/simbrief/text-status', 'parsing...');
printf("SimBrief download from %s complete.", url);
var errs = [];
call(onSuccess, [filename], nil, {}, errs);
if (size(errs) > 0) {
setprop('/sim/simbrief/text-status', 'parser errors, see log for details');
debug.printerror(errs);
}
else {
setprop('/sim/simbrief/text-status', 'all done!');
}
})
.fail(onFailure)
.always(func {
setprop('/sim/simbrief/downloading', 0);
});
};
var read = func (filename=nil) {
if (filename == nil) {
filename = getprop('/sim/fg-home') ~ "/Export/simbrief.xml";
}
var xml = io.readxml(filename);
var ofpNode = xml.getChild('OFP');
if (ofpNode == nil) {
print("Error loading SimBrief OFP");
return nil;
}
else {
return ofpNode;
}
};
var toFlightplan = func (ofp, fp=nil) {
# get departure and destination
var departureID = ofp.getNode('origin/icao_code').getValue();
var departures = findAirportsByICAO(departureID);
if (departures == nil or size(departures) == 0) {
printf("Airport not found: %s", departureID);
return nil;
}
var destinationID = ofp.getNode('destination/icao_code').getValue();
var destinations = findAirportsByICAO(destinationID);
if (destinations == nil or size(destinations) == 0) {
printf("Airport not found: %s", destinationID);
return nil;
}
# cruise parameters
var initialAltitude = ofp.getNode('general/initial_altitude').getValue();
var cruiseAltitude = initialAltitude;
var seenTOC = 0;
# collect enroute waypoints
var wps = [];
var ofpNavlog = ofp.getNode('navlog');
var ofpFixes = ofpNavlog.getChildren('fix');
var sidID = nil;
var starID = nil;
foreach (var ofpFix; ofpFixes) {
if (ofpFix.getNode('is_sid_star').getBoolValue()) {
if ((ofpFix.getValue('stage') == 'CLB') and
(getprop('/sim/simbrief/options/import-departure') or 0) and
(sidID == nil)) {
sidID = ofpFix.getValue('via_airway');
}
elsif ((ofpFix.getValue('stage') == 'DSC') and
(getprop('/sim/simbrief/options/import-arrival') or 0) and
(starID == nil)) {
starID = ofpFix.getValue('via_airway');
}
# skip: we only want enroute waypoints
continue;
}
var ident = ofpFix.getNode('ident').getValue();
if (ident == 'TOC' or ident == 'TOD') {
# skip TOC and TOD: the FMS should deal with those dynamically
if (ident == 'TOC') {
seenTOC = 1;
}
continue;
}
var altNode = ofpFix.getNode('altitude_feet');
var alt = (altNode == nil) ? nil : altNode.getValue();
var coords = geo.Coord.new();
coords.set_latlon(
ofpFix.getNode('pos_lat').getValue(),
ofpFix.getNode('pos_long').getValue());
printf("%s %f %f", ident, coords.lat(), coords.lon());
var wp = createWP(coords, ident);
if (seenTOC and alt == initialAltitude) {
# this is the waypoint where we expect to reach initial cruise
# altitude
# reset 'seen TOC' flag to avoid setting alt restrictions on
# subsequent waypoints
seenTOC = 0;
# we'll use an "at" restriction here: we don't want to climb any
# higher, hence "above" would be wrong, and we want the VNAV to do
# its best to reach the altitude before this point, so "below"
# would also be wrong.
# This doesn't work, and I don't know why.
# wp.setAltitude(alt, 'at');
}
else if (alt > cruiseAltitude) {
# this is a step climb target waypoint
cruiseAltitude = alt;
# This doesn't work, and I don't know why.
# wp.setAltitude(alt, 'at');
}
append(wps, wp);
}
# we have everything we need; it's now safe-ish to overwrite or
# create the actual flightplan
if (fp == nil) {
fp = createFlightplan();
}
fp.cleanPlan();
fp.sid = nil;
fp.sid_trans = nil;
fp.star = nil;
fp.star_trans = nil;
fp.approach = nil;
fp.approach_trans = nil;
fp.departure = departures[0];
fp.destination = destinations[0];
fp.insertWaypoints(wps, 1);
if (getprop('/sim/simbrief/options/import-departure') or 0) {
departureRunwayID = ofp.getNode('origin').getValue('plan_rwy');
printf("Trying to select departure: %s / %s", sidID, departureRunwayID);
fp.departure_runway = departures[0].runways[departureRunwayID];
if (sidID != nil) {
fp.sid = departures[0].getSid(sidID);
if (fp.sid == nil)
fp.sid = departures[0].getSid(sidID ~ '.' ~ departureRunwayID);
if (fp.sid == nil) {
printf("SID not found: %s", sidID);
}
}
}
if (getprop('/sim/simbrief/options/import-arrival') or 0) {
destinationRunwayID = ofp.getNode('destination').getValue('plan_rwy');
printf("Trying to select arrival: %s / %s", starID, destinationRunwayID);
fp.destination_runway = destinations[0].runways[destinationRunwayID];
if (starID != nil) {
fp.star = destinations[0].getStar(starID);
if (fp.star == nil)
fp.star = destinations[0].getStar(starID ~ '.' ~ destinationRunwayID);
if (fp.star == nil) {
printf("STAR not found: %s", starID);
}
}
}
return fp;
};
var importFOB = func (ofp) {
var unit = ofp.getNode('params/units').getValue();
var fuelFactor = ((unit == 'lbs') ? LB2KG : 1);
# From here on, we'll do everything in kilograms (kg)
var fob = ofp.getNode('fuel/plan_ramp').getValue() * fuelFactor;
var unallocated = fob;
var tankNodes = props.globals.getNode('/consumables/fuel').getChildren('tank');
var numTanks = size(tankNodes);
printf("Fuel to allocate: %1.1f kg", fob);
var allocate = func(tankNumber, maxAmount = nil) {
var tankNode = tankNodes[tankNumber];
if (tankNode == nil) {
printf("Tank #%i not installed", tankNumber);
return;
}
var tankNameNode = tankNode.getNode('name');
if (tankNameNode == nil) {
printf("Tank #%i not installed", tankNumber);
return;
}
var tankName = tankNameNode.getValue();
var amount = unallocated;
if (maxAmount != nil) {
amount = math.min(amount, maxAmount);
}
var tankCapacity =
tankNode.getNode('capacity-m3').getValue() *
tankNode.getNode('density-kgpm3').getValue();
amount = math.min(amount, tankCapacity);
printf("Allocating %1.1f/%1.1f kg to %s", amount, unallocated, tankName);
tankNode.getNode('level-kg').setValue(amount);
unallocated -= amount;
}
var allocatePair = func (tank1, tank2) {
var cap1 = tankNodes[tank1].getValue('capacity-m3') or 1;
var cap2 = tankNodes[tank2].getValue('capacity-m3') or 1;
var cap = cap1 + cap2;
var allocate1 = unallocated * cap1 / cap;
var allocate2 = unallocated * cap2 / cap;
allocate(tank1, allocate1);
allocate(tank2, allocate2);
}
var i = 0;
while (i < numTanks) {
var tankNode = tankNodes[i];
var tankName = tankNode.getValue('name') or 'unnamed';
if (i >= numTanks - 1 or string.imatch(tankName, "*center*")) {
allocate(i);
i = i + 1;
}
else {
allocatePair(i, i + 1);
i = i + 2;
}
}
printf("Fuel not allocated: %1.1f kg", unallocated);
};
var importPayload = func (ofp) {
var unit = ofp.getNode('params/units').getValue();
var factor = ((unit == 'lbs') ? 1 : KG2LB);
var weightNodes = props.globals.getNode('/sim').getChildren('weight');
var cargoWeightNodes = [];
var paxWeightNodes = [];
foreach (var node; weightNodes) {
var nodeName = node.getValue('name');
printf("Checking weight node %s", nodeName);
if (string.imatch(nodeName, "*passenger*") or
string.imatch(nodeName, "*pax*") or
string.imatch(nodeName, "*cabin*") or
string.imatch(nodeName, "*class*") or
string.imatch(nodeName, "*baggage*") or
string.imatch(nodeName, "*seat*")) {
append(paxWeightNodes, node);
}
elsif (string.imatch(nodeName, "*cargo*") or
string.imatch(nodeName, "*payload*")) {
append(cargoWeightNodes, node);
}
}
if (size(paxWeightNodes) == 0 and size(cargoWeightNodes) == 0) {
printf("Alas, this aircraft does not seem to use the standard weights system. Please configure payload manually.");
return;
}
# Everything in lbs
var cargoUnallocated = ofp.getNode('weights/cargo').getValue() * factor;
var paxUnallocated = ofp.getNode('weights/payload').getValue() * factor - cargoUnallocated;
var distribute = func (what, nodes, unallocated) {
printf("Allocating %s: %1.1f lbs", what, unallocated);
var totalF = 0;
foreach (var node; nodes) {
var f = node.getValue('max-lb') - node.getValue('min-lb');
node.setValue('weight-lb', node.getValue('min-lb'));
unallocated = unallocated - node.getValue('min-lb');
totalF = totalF + f;
printf("Allocating %1.1f/%1.1f lbs to %s", node.getValue('min-lb'), unallocated, node.getValue('name'));
}
printf("Remaining %s after minimum weights: %1.1f lbs", what, unallocated);
var remaining = unallocated;
if (remaining > 0) {
foreach (var node; nodes) {
var maxAdd = node.getValue('max-lb') - node.getValue('min-lb');
var f = maxAdd / totalF;
var toAdd = math.min(f * remaining, maxAdd, unallocated);
node.setValue('weight-lb',
node.getValue('weight-lb') +
toAdd);
unallocated = unallocated - toAdd;
printf("Allocating %1.1f/%1.1f lbs to %s", toAdd, unallocated, node.getValue('name'));
}
}
printf("Remaining unallocated %s: %1.1f lbs", what, unallocated);
return unallocated;
}
if (size(paxWeightNodes) == 0) {
printf("No passenger space found, forcing passengers into cargo hold");
cargoUnallocated = cargoUnallocated + paxUnallocated;
paxUnallocated = 0;
}
cargoUnallocated = distribute("cargo", cargoWeightNodes, cargoUnallocated);
paxUnallocated = distribute("passengers", paxWeightNodes, paxUnallocated + cargoUnallocated);
};
var importPerfInit = func (ofp) {
# climb profile: kts-below-FL100/kts-above-FL100/mach
var climbProfile = split('/', ofp.getNode('general/climb_profile').getValue());
# descent profile: mach/kts-above-FL100/kts-below-FL100
var descentProfile = split('/', ofp.getNode('general/descent_profile').getValue());
var cruiseMach = ofp.getNode('general/cruise_mach').getValue();
var airline = ofp.getNode('general/icao_airline').getValue();
var flightNumber = ofp.getNode('general/flight_number').getValue();
var callsign = (airline == nil) ? flightNumber : (airline ~ flightNumber);
var cruiseAlt = ofp.getNode('general/initial_altitude').getValue();
setprop("/sim/multiplay/callsign", callsign);
setprop("/autopilot/route-manager/cruise/altitude-ft", cruiseAlt);
if (props.globals.getNode("/controls/flight/speed-schedule") != nil) {
setprop("/controls/flight/speed-schedule/climb-below-10k", climbProfile[0]);
setprop("/controls/flight/speed-schedule/climb-kts", climbProfile[1]);
setprop("/controls/flight/speed-schedule/climb-mach", climbProfile[2] / 100);
setprop("/controls/flight/speed-schedule/cruise-mach", cruiseMach);
setprop("/controls/flight/speed-schedule/descent-mach", descentProfile[0] / 100);
setprop("/controls/flight/speed-schedule/descent-kts", descentProfile[1]);
setprop("/controls/flight/speed-schedule/descent-below-10k", descentProfile[2]);
}
};
var aloftTimer = nil;
var aloftPoints = [];
var setAloftWinds = func (aloftPoint) {
# printf("setAloftWinds()");
# debug.dump(aloftPoint);
forindex (var i; aloftPoint.layers) {
var node = props.globals.getNode("/environment/config/aloft/entry[" ~ i ~ "]");
node.getChild('elevation-ft').setValue(aloftPoint.layers[i].alt);
node.getChild('wind-from-heading-deg').setValue(aloftPoint.layers[i].dir);
node.getChild('wind-speed-kt').setValue(aloftPoint.layers[i].spd);
node.getChild('temperature-degc').setValue(aloftPoint.layers[i].temp);
}
};
var interpolate = func (f, a, b) {
return a + f * (b - a);
};
var interpolateDegrees = func (f, a, b) {
return geo.normdeg(a + geo.normdeg180(b - a) * f);
};
var interpolateComponentWise = func (f, ipf, a, b) {
var s = math.min(size(a), size(b));
var result = [];
for (var i = 0; i < s; i = i+1) {
append(result, ipf(f, a[i], b[i]));
}
return result;
};
var interpolateLayers = func (f, a, b) {
if (b == nil) return a;
if (a == nil) return b;
return {
alt: interpolate(f, a.alt, b.alt),
spd: interpolate(f, a.spd, b.spd),
temp: interpolate(f, a.temp, b.temp),
dir: interpolateDegrees(f, a.dir, b.dir),
};
};
var interpolateAloftPoints = func (f, a, b) {
if (b == nil) return a;
if (a == nil) return b;
return {
layers: interpolateComponentWise(f, interpolateLayers, a.layers, b.layers),
};
};
var updateAloft = func () {
# printf("updateAloft()");
var pos = geo.aircraft_position();
foreach (var p; aloftPoints) {
p.dist = pos.distance_to(p.coord);
}
var sorted = sort(aloftPoints, func (a, b) { return (a.dist - b.dist); });
var pointA = sorted[0];
var pointB = sorted[1];
var f = (pointB.dist < 0.1) ? 0 : (pointB.dist / (pointA.dist + pointB.dist));
# foreach (var s; sorted) {
# printf(s.dist);
# }
# debug.dump(f, pointA, pointB);
var aloftPoint = interpolateAloftPoints(f, pointA, pointB);
# printf("Aloft wind interpolation: %f between %s and %s",
# f, pointA.name, pointB.name);
# debug.dump(aloftPoint.layers);
setAloftWinds(aloftPoint);
};
var startAloftUpdater = func () {
if (aloftTimer == nil) {
aloftTimer = maketimer(10, updateAloft);
aloftTimer.simulatedTime = 1;
}
if (aloftTimer.isRunning) return;
aloftTimer.start();
};
var importWindsAloft = func (ofp) {
# # disable default winds and set winds-aloft mode
# setprop("/local-weather/config/wind-model", "aloft waypoints");
# setprop("/environment/params/metar-updates-winds-aloft", 0);
# if (defined('local_weather')) {
# # clear out the advanced weather winds-aloft interpolation points
# setsize(local_weather.windIpointArray, 0);
# }
# now go through the flightplan waypoints and create a wind interpolation point for each of them.
var ofpNavlog = ofp.getNode('navlog');
var ofpFixes = ofpNavlog.getChildren('fix');
foreach (var ofpFix; ofpFixes) {
var lat = ofpFix.getNode('pos_lat').getValue();
var lon = ofpFix.getNode('pos_long').getValue();
var args = [lat, lon];
var layers = [];
var uneven = 0;
foreach (var ofpWindLayer; ofpFix.getNode('wind_data').getChildren('level')) {
var dir = ofpWindLayer.getNode('wind_dir').getValue();
var spd = ofpWindLayer.getNode('wind_spd').getValue();
var alt = ofpWindLayer.getNode('altitude').getValue();
var temp = ofpWindLayer.getNode('oat').getValue();
if (alt != 14000) {
# advanced weather ignores this one for some reason
append(args, dir, spd);
}
# pick up every other layer: simbrief reports 10 layers starting
# at sea level, but we can only use 5, and we don't need sea level
# (as that comes from METAR)
if (uneven) {
append(layers, { alt: alt, dir: dir, spd: spd, temp: temp });
}
uneven = !uneven;
}
# if (defined('local_weather')) {
# call(local_weather.set_wind_ipoint, args);
# }
var aloftPos = geo.Coord.new();
aloftPos.set_latlon(lat, lon);
var aloftPoint = { coord: aloftPos, dist: 0.0, layers: layers, name: ofpFix.getNode('ident').getValue() };
append(aloftPoints, aloftPoint);
}
startAloftUpdater();
};
var loadFP = func () {
var username = getprop('/sim/simbrief/username');
if (username == nil or username == '') {
print("Username not set");
return;
}
download(username, func (filename) {
var ofpNode = read(filename);
if (ofpNode == nil) {
print("Error loading simbrief XML file");
return;
}
if (getprop('/sim/simbrief/options/import-fp') or 0) {
var modifyableFlightplan = flightplan();
var haveFMS = globals['fms'] != nil
and typeof(fms) == 'hash'
and typeof(fms['getModifyableFlightplan']) == 'func'
and typeof(fms['commitFlightplan']) == 'func'
and typeof(fms['kickRouteManager']) == 'func';
if (haveFMS) {
modifyableFlightplan = fms.getModifyableFlightplan();
}
var fp = toFlightplan(ofpNode, modifyableFlightplan);
if (fp == nil) {
print("Error parsing flight plan");
}
else {
if (haveFMS) {
if (getprop('/sim/simbrief/options/autocommit') or 0) {
fms.commitFlightplan();
}
fms.kickRouteManager();
}
else {
if (getprop('/sim/simbrief/options/autocommit') or 0) {
fgcommand("activate-flightplan", props.Node.new({"activate": 1}));
}
}
}
}
if (getprop('/sim/simbrief/options/import-fob') or 0) {
importFOB(ofpNode);
}
if (getprop('/sim/simbrief/options/import-payload') or 0) {
importPayload(ofpNode);
}
if (getprop('/sim/simbrief/options/import-perfinit') or 0) {
importPerfInit(ofpNode);
}
if (getprop('/sim/simbrief/options/import-winds-aloft') or 0) {
importWindsAloft(ofpNode);
}
});
};
var findMenuNode = func (create=0) {
var equipmentMenuNode = props.globals.getNode('/sim/menubar/default/menu[5]');
foreach (var item; equipmentMenuNode.getChildren('item')) {
if (item.getValue('name') == 'addon-simbrief') {
return item;
}
}
if (create) {
return equipmentMenuNode.addChild('item');
}
else {
return nil;
}
};
var main = func(addon) {
if (globals['simbrief'] == nil) {
print("SIMBRIEF");
globals['simbrief'] = {
'loadFP': loadFP
};
var myMenuNode = findMenuNode(1);
myMenuNode.setValues({
enabled: 'true',
name: 'addon-simbrief',
label: 'SimBrief Import',
binding: {
'command': 'dialog-show',
'dialog-name': 'addon-simbrief-dialog',
},
});
fgcommand('reinit', {'subsystem': 'gui'});
}
else {
print("SIMBRIEF importer already present, not activating add-on");
}
};

9
addon-menubar-items.xml Normal file
View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<PropertyList>
<meta>
<file-type type="string">FlightGear add-on menu bar items</file-type>
<format-version type="int">1</format-version>
</meta>
<menubar-items>
</menubar-items>
</PropertyList>

72
addon-metadata.xml Normal file
View File

@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8"?>
<PropertyList>
<meta>
<file-type type="string">FlightGear add-on metadata</file-type>
<format-version type="int">1</format-version>
</meta>
<addon>
<identifier type="string">nl.tobiasdammers.fg-simbrief-addon</identifier>
<name type="string">SimBrief importer</name>
<version type="string">0.1.0</version>
<authors>
<author>
<name type="string">Tobias Dammers</name>
<email type="string">tdammers@gmail.com</email>
<url type="string">https://github.com/tdammers</url>
</author>
</authors>
<maintainers>
<maintainer>
<name type="string">Tobias Dammers</name>
<email type="string">tdammers@gmail.com</email>
<url type="string">https://github.com/tdammers</url>
</maintainer>
</maintainers>
<short-description type="string">
SimBrief importer
</short-description>
<long-description type="string">
</long-description>
<license>
<designation type="string">
MIT
</designation>
<url type="string">https://mit-license.org/</url>
</license>
<min-FG-version type="string">2017.4.0</min-FG-version>
<max-FG-version type="string">none</max-FG-version>
<urls>
<home-page type="string">
https://github.com/tdammers/fg-simbrief-addon
</home-page>
<download type="string">
https://github.com/tdammers/fg-simbrief-addon
</download>
<support type="string">
</support>
<code-repository type="string">
https://github.com/tdammers/fg-simbrief-addon
</code-repository>
</urls>
<tags>
<tag type="string">simbrief</tag>
<tag type="string">flightplan</tag>
<tag type="string">routemanager</tag>
</tags>
</addon>
</PropertyList>

View File

@ -0,0 +1,186 @@
<?xml version="1.0"?>
<PropertyList>
<name>addon-simbrief-dialog</name>
<layout>vbox</layout>
<pref-width>500</pref-width>
<group>
<layout>hbox</layout>
<empty>
<stretch>1</stretch>
</empty>
<text>
<label>SimBrief</label>
</text>
<empty>
<stretch>1</stretch>
</empty>
<button>
<pref-width>16</pref-width>
<pref-height>16</pref-height>
<legend></legend>
<keynum>27</keynum>
<border>2</border>
<binding>
<command>dialog-close</command>
</binding>
</button>
</group>
<hrule/>
<group>
<layout>vbox</layout>
<group>
<layout>hbox</layout>
<text>
<label>SimBrief username</label>
<pref-width>100</pref-width>
<halign>left</halign>
</text>
<input>
<property>/sim/simbrief/username</property>
<binding>
<command>dialog-apply</command>
</binding>
<halign>left</halign>
</input>
</group>
<empty>
<pref-height>10</pref-height>
</empty>
<group>
<layout>hbox</layout>
<empty>
<stretch>1</stretch>
</empty>
<text>
<label>What to import</label>
</text>
<empty>
<stretch>1</stretch>
</empty>
</group>
<hrule/>
<group>
<layout>hbox</layout>
<checkbox>
<property>/sim/simbrief/options/import-fp</property>
<label>Flight Plan Route</label>
<binding>
<command>dialog-apply</command>
</binding>
<halign>left</halign>
<valign>top</valign>
<pref-width>100</pref-width>
</checkbox>
<group>
<pref-width>100</pref-width>
<layout>vbox</layout>
<checkbox>
<property>/sim/simbrief/options/import-departure</property>
<label>Departure RWY, SID</label>
<binding>
<command>dialog-apply</command>
</binding>
<halign>left</halign>
<pref-width>100</pref-width>
</checkbox>
<checkbox>
<property>/sim/simbrief/options/import-arrival</property>
<label>Arrival RWY, STAR</label>
<binding>
<command>dialog-apply</command>
</binding>
<halign>left</halign>
<pref-width>100</pref-width>
</checkbox>
<checkbox>
<property>/sim/simbrief/options/autocommit</property>
<label>Activate immediately</label>
<binding>
<command>dialog-apply</command>
</binding>
<halign>left</halign>
<pref-width>100</pref-width>
</checkbox>
</group>
</group>
<group>
<layout>hbox</layout>
<checkbox>
<property>/sim/simbrief/options/import-perfinit</property>
<label>Performance init</label>
<binding>
<command>dialog-apply</command>
</binding>
<halign>left</halign>
<pref-width>100</pref-width>
</checkbox>
</group>
<group>
<layout>hbox</layout>
<checkbox>
<property>/sim/simbrief/options/import-payload</property>
<label>Payload</label>
<binding>
<command>dialog-apply</command>
</binding>
<halign>left</halign>
<pref-width>100</pref-width>
</checkbox>
</group>
<group>
<layout>hbox</layout>
<checkbox>
<property>/sim/simbrief/options/import-fob</property>
<label>Fuel</label>
<binding>
<command>dialog-apply</command>
</binding>
<halign>left</halign>
<pref-width>100</pref-width>
</checkbox>
</group>
<group>
<layout>hbox</layout>
<checkbox>
<property>/sim/simbrief/options/import-winds-aloft</property>
<label>Winds Aloft</label>
<binding>
<command>dialog-apply</command>
</binding>
<halign>left</halign>
<pref-width>100</pref-width>
</checkbox>
</group>
<hrule/>
<group>
<layout>hbox</layout>
<text>
<label>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</label>
<format>%s</format>
<property>/sim/simbrief/text-status</property>
<live type="bool">true</live>
</text>
</group>
<group>
<layout>hbox</layout>
<button>
<legend>Import</legend>
<binding>
<command>nasal</command>
<script>simbrief.loadFP()</script>
</binding>
</button>
</group>
</group>
</PropertyList>