diff --git a/scripts/csl2xsb/.gitignore b/scripts/csl2xsb/.gitignore new file mode 100644 index 000000000..894a44cc0 --- /dev/null +++ b/scripts/csl2xsb/.gitignore @@ -0,0 +1,104 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ diff --git a/scripts/csl2xsb/CSL2XSB.py b/scripts/csl2xsb/CSL2XSB.py new file mode 100644 index 000000000..1ca45d421 --- /dev/null +++ b/scripts/csl2xsb/CSL2XSB.py @@ -0,0 +1,421 @@ +#!/usr/bin/python3 + +""" +Converts CSL packages to the original XSB format for use in LiveTraffic (and probably XSquawkBox) +For usage info call + python3 CSL2XSB.py -h + + +MIT License + +Copyright (c) 2019 B.Hoppe + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +from pathlib import Path, PurePath +import argparse # handling command line arguments + +_currAircraft = '' # name of currently processed aircraft +_warnings = 0 # number of warnings + +# dataRef replacement table: Very simple: +# if finding the string on the left-hand side (non-libxplanemp dataRef) +# then replace it with the string on the right-hand side (libxplanemp dataRef) +_DR = { + 'cjs/world_traffic/engine_rotation_angle1': 'libxplanemp/engines/engine_rotation_angle_deg1', + 'cjs/world_traffic/engine_rotation_angle2': 'libxplanemp/engines/engine_rotation_angle_deg2', + 'cjs/world_traffic/engine_rotation_angle3': 'libxplanemp/engines/engine_rotation_angle_deg3', + 'cjs/world_traffic/engine_rotation_angle4': 'libxplanemp/engines/engine_rotation_angle_deg4', + 'cjs/world_traffic/engine_rpm1': 'libxplanemp/engines/engine_rotation_speed_rpm1', + 'cjs/world_traffic/engine_rpm2': 'libxplanemp/engines/engine_rotation_speed_rpm2', + 'cjs/world_traffic/engine_rpm3': 'libxplanemp/engines/engine_rotation_speed_rpm3', + 'cjs/world_traffic/engine_rpm4': 'libxplanemp/engines/engine_rotation_speed_rpm4', + 'cjs/world_traffic/engine_rad_per_sec1': 'libxplanemp/engines/engine_rotation_speed_rad_sec1', + 'cjs/world_traffic/engine_rad_per_sec2': 'libxplanemp/engines/engine_rotation_speed_rad_sec2', + 'cjs/world_traffic/engine_rad_per_sec3': 'libxplanemp/engines/engine_rotation_speed_rad_sec3', + 'cjs/world_traffic/engine_rad_per_sec4': 'libxplanemp/engines/engine_rotation_speed_rad_sec4', + 'cjs/world_traffic/thrust_reverser_position': 'libxplanemp/engines/thrust_reverser_deploy_ratio', + 'cjs/world_traffic/touch_down': 'libxplanemp/misc/touch_down', + 'cjs/world_traffic/main_gear_deflection': 'libxplanemp/gear/tire_vertical_deflection_mtr', + 'cjs/world_traffic/main_gear_wheel_angle': 'libxplanemp/gear/tire_rotation_angle_deg', + 'cjs/world_traffic/nose_gear_deflection': 'libxplanemp/gear/nose_gear_deflection_mtr', + 'cjs/world_traffic/nose_gear_wheel_angle': 'libxplanemp/gear/tire_rotation_angle_deg', + 'cjs/world_traffic/nose_gear_steering_angle': 'libxplanemp/controls/nws_ratio', + 'cjs/wolrd_traffic/landing_lights_on': 'libxplanemp/controls/landing_lites_on', + } + +def OBJ8ReplaceDataRefs(line:str) -> str: + """Replaces dataRefs + + 1. Replaces by replacement table, e.g. to replace world_traffic dataRefs + with libxplanemp/PE dataRefs + 2. If commanded, replaces root 'libxplanemp' by 'LT' (or whatever has been specified) + """ + + global _warnings + + # replace dataRefs as per replacement table + for old, new in _DR.items(): + line = line.replace(old, new) + + # if requested replace libxplanemp with something else + if args.replaceDR is not None: + line = line.replace('libxplanemp/', args.replaceDR + '/') + + return line + + +def UpdateOBJ8File(in_p:Path, out_p:Path, textureLivery:str = None, textureLit:str = None): + """Updates the OBJ8 file: TEXTURE and dataRefs.""" + + global _warnings + + if args.verbose: + print (' -- Writing ', out_p.name, ' (from ' + in_p.name + '):') + + assert(in_p.is_file()) + in_f = None + out_f = None + try: + # open in/out files + in_f = in_p.open(mode='r',encoding='ascii',errors='replace') + out_f = out_p.open(mode='w',encoding='ascii',errors='replace') + + # read all lines, copy most of them 1:1 to the output + for line in in_f: + # remove the newline char from the end + line = line.rstrip('\n\r') + origLine = line + word = line.split() + numWords = len(word) + + if numWords >= 1: + # replace texture + if textureLivery is not None and word[0] == 'TEXTURE': + line = 'TEXTURE ' + textureLivery + + # replace LIT texture + if textureLit is not None and word[0] == 'TEXTURE_LIT': + line = 'TEXTURE_LIT ' + textureLit + + # dataRefs replacements (only if there is a chance for dataRefs in the line) + if '/' in line: + line = OBJ8ReplaceDataRefs(line) + + # write to output + if args.verbose and line != origLine: + print (' Written: ' + line + ' (instead of ' + origLine + ')') + out_f.write(line+'\n') + + except IOError as e: + parser.error('UpdateOBJ8File failed:\n' + e.filename + ':\n'+ e.strerror +'\n') + + finally: + # cleanup + if in_f is not None: + in_f.close() + if out_f is not None: + out_f.close() + + +def HandleXsbObj8Solid(path: Path, line: str) -> str: + """Deals with the OBJ8 SOLID line. + + Example: OBJ8 SOLID YES MD80/MD80.obj AZA.png MD80_LIT.png + Means: + - Identify that it has additional texture info and, hence, requires treatment + - Have the original OBJ8 file (MD80.obj) treated and MD80_AZA.obj created + Returns the line for the new xsb_aircraft file, which would be: + BJ8 SOLID YES MD80/MD80_AZA.obj + or None if no change occurred. + """ + + global _warnings + + # --- split the line into its parameters (after removing any line ending) --- + word = line.split() + numWords = len(word) + if numWords < 4: # too few parameters! + print (' ERROR - Too few parameters, skipped: ' + line) + return None + + # the object file's name (replace colons [X-CSL comes this way...?] with forward slash) + object_in_p = PurePath(word[3].replace(':','/')) + package_name = object_in_p.parts[0] + # the first part of the Object path just refers to the arbitrary CSL EXPORT_NAME, + # strip it to get the file name of the current OBJ8 file: + obj8_in_file_p = path / object_in_p.relative_to(package_name) + + # --- No additional parameters for livery textures? + # Example: OBJ8 SOLID YES MD80/MD80.obj + if numWords == 4: + if args.noupdate: # and no update of OBJ8? + if args.verbose: + print (' No change to: ' + line) + return None # return with no change + + # so we rename the existing file to ...orig + obj8_out_file_p = obj8_in_file_p + obj8_in_file_p = obj8_in_file_p.with_name(obj8_in_file_p.name + '.orig') + if not obj8_in_file_p.is_file(): # ...if it isn't there already + if not obj8_out_file_p.is_file(): + print (' ERROR - ' + _currAircraft + ': Cannot access OBJ8 file, skipped: ' + str(obj8_out_file_p)) + return None + obj8_out_file_p.rename(obj8_in_file_p) + if args.verbose: + print (' Renamed', obj8_out_file_p,'-->',obj8_in_file_p.name) + + # Update the OBJ8 file + UpdateOBJ8File(obj8_in_file_p, obj8_out_file_p) + + # but no change to the xsb_aircraft line + return line + + # --- Normal case: additional texture parameters on the line, + # so we need to create a new OBJ8 file + else: + # original OBJ8 file must be accessible + if not obj8_in_file_p.is_file(): + print (' ERROR - ' + _currAircraft + ': Cannot access OBJ8 file, skipped: ' + str(obj8_in_file_p)) + return None + + # livery texture and Lit texture (relative to the in-obj8-file) + textureLivery_p = obj8_in_file_p.parent / word[4] + if not textureLivery_p.is_file(): + if textureLivery_p.with_suffix('.png').is_file(): # X-CSL sometimes has wrong suffix in xsb_aircraft.txt, i.e. couldn't have worked, fix it, too. + print (' WARNING - {}: Could not find texture file {}, but found and used {}'.format(_currAircraft, textureLivery_p, textureLivery_p.with_suffix('.png').name)) + textureLivery_p = textureLivery_p.with_suffix('.png') + if textureLivery_p.with_suffix('.dds').is_file(): + print (' WARNING - {}: Could not find texture file {}, but found and used {}'.format(_currAircraft, textureLivery_p, textureLivery_p.with_suffix('.dds').name)) + textureLivery_p = textureLivery_p.with_suffix('.dds') + else: + print (' WARNING - '+_currAircraft+': Cannot find texture file, continue anyway: ', textureLivery_p) + _warnings += 1 + + # also Lit texture defined? (relative to the in-obj8-file) + textureLit_p = None + if numWords >= 6: + textureLit_p = obj8_in_file_p.parent / word[5] + if not textureLit_p.is_file(): + if textureLit_p.with_suffix('.png').is_file(): + print (' WARNING - {}: Could not find lit texture file {}, but found and used {}'.format(_currAircraft, textureLit_p, textureLit_p.with_suffix('.png').name)) + textureLit_p = textureLit_p.with_suffix('.png') + elif textureLit_p.with_suffix('.dds').is_file(): + print (' WARNING - {}: Could not find lit texture file {}, but found and used {}'.format(_currAircraft, textureLit_p, textureLit_p.with_suffix('.dds').name)) + textureLit_p = textureLit_p.with_suffix('.dds') + else: + print (' WARNING - '+_currAircraft+': Cannot find lit texture file, continue anyway: ', textureLit_p) + _warnings += 1 + + # compile the new object file's name, combined from original and livery files: + obj8_out_file_p = obj8_in_file_p.with_name(obj8_in_file_p.stem + '_' + textureLivery_p.stem + obj8_in_file_p.suffix) + object_out_p = PurePath(package_name) / obj8_out_file_p.relative_to(path) + + # Update the OBJ8 file + UpdateOBJ8File(obj8_in_file_p, obj8_out_file_p, \ + str(textureLivery_p.relative_to(obj8_out_file_p.parent)), \ + str(textureLit_p.relative_to(obj8_out_file_p.parent)) if textureLit_p is not None else None) + + # --- return the new line for the xsb_aircraft file --- + newLn = word[0] + ' ' + word[1] + ' ' + word[2] + ' ' + str(object_out_p) + return newLn + + +def ConvFolder(path: Path) -> int: + """Converts the CSL package in the given path, recursively for each folder. + + Returns the number of written OBJ8 objects.""" + + numObj = 0; + global _currAircraft + _currAircraft = '?' + commentingOut = 0 # Currently commenting out (due to outdated OBJECT format)? + + # --- Save the current version of xsb_aircraft as .orig, which we then read from --- + assert (path.is_dir()) + xsb_aircraft_p = path / 'xsb_aircraft.txt' + xsb_aircraft_orig_p = path / 'xsb_aircraft.txt.orig' + + # First check of any of the two exists, otherwise we consider this folder empty: + if (not xsb_aircraft_p.is_file() and not xsb_aircraft_orig_p.is_file()): + if args.verbose: + print ('===(skipped)', path) + + # --- Recursion: We check for containing folders and try again there! + if not args.norecursion: + for folder in path.iterdir(): + if folder.is_dir(): + numObj += ConvFolder(folder) + + return numObj; + + # So we will process this path + print ('=========> ', path) + + try: + # If the .orig version _not_ already exists then assume current xsb_aircraft.txt _is_ the original and rename it + # (Never change the .orig file!) + if not xsb_aircraft_orig_p.exists(): + xsb_aircraft_p.replace(xsb_aircraft_orig_p) + if args.verbose: + print ('Renamed',xsb_aircraft_p,'-->',xsb_aircraft_orig_p) + + # --- Read from .orig as the original master --- + xsb_aircraft_orig_f = xsb_aircraft_orig_p.open(mode='r',encoding='ascii',errors='replace') + if args.verbose: + print ('Reading from', xsb_aircraft_orig_p) + + # --- Write to a new xsb_aircraft.txt file (overwrite existing one!) --- + xsb_aircraft_f = xsb_aircraft_p.open(mode='w',encoding='ascii',errors='replace') + if args.verbose: + print ('Writing to ', xsb_aircraft_p) + + # --- Read all lines from .orig file + # Most of them are just copied 1:1 to the output file + for line in xsb_aircraft_orig_f: + # remove the newline char from the end + line = line.rstrip('\n\r') + origLine = line + word = line.split() + numWords = len(word) + + if numWords >= 2: + # This is a line with at least two words + + # OBJECT is an outdated format modern XPMP2 no longer supports + # Comment out the entire aircraft + if word[0] == 'OBJECT' or word[0] == 'AIRCRAFT': + # assume all else is the aircraft name + _currAircraft = ' '.join(word[1:]) + commentingOut = 1 + print (' WARNING - {}: Outdated format, commented out'.format(_currAircraft)) + line = "--- Outdated format, no longer valid\n# " + line + + # OBJ8_AIRCRAFT identifies the start of another aircraft. + # Technically, we don't need that info, but it's nice for user info + if word[0] == 'OBJ8_AIRCRAFT': + # assume all else is the aircraft name + _currAircraft = ' '.join(word[1:]) + if args.verbose: + print ('-- ' + _currAircraft) + # replace spaces in the aircraft name (PE does that) + if ' ' in _currAircraft: + _currAircraft = _currAircraft.replace(' ', '_') + # replace colons in the aircraft name (X-CSL does that, although there is no need for a path here) + if ':' in _currAircraft: + _currAircraft = _currAircraft.replace(':', '_') + # re-write the OBJ8_AIRCRAFT line + word[1] = _currAircraft + line = 'OBJ8_AIRCRAFT ' + _currAircraft + # Valid format, shall not be commented out + commentingOut = 0 + + # X-CSL uses non-existing ICAO code 'MD80', replace with MD81 + if word[1] == 'MD80' and \ + (word[0] == 'ICAO' or \ + word[0] == 'AIRLINE' or \ + word[0] == 'LIVERY'): + word[1] = 'MD81' + line = ' '.join(word) + + # -- now decide what to do with the line + + # ignore deprecated or PE-extension commands + if (word[0] == 'OBJ8' and word[1] == 'LOW_LOD') or \ + word[0] == 'HASGEAR' or \ + word[0] == 'TEXTURE': + line = None + + # OBJ8 is the one line we _really_ need to work on! + elif (word[0] == 'OBJ8'): + Obj8SolidLine = HandleXsbObj8Solid(path, line) + if Obj8SolidLine is not None: + # and we did something to the OBJ8 line: + line = Obj8SolidLine + numObj += 1 + + # -- write the resulting line out to the new xsb_aircraft file + if line is not None: + if commentingOut: + xsb_aircraft_f.write("# ") + + xsb_aircraft_f.write(line + '\n') + if args.verbose and origLine != line: + print (' Written: ' + line + ' (instead of: ' + origLine + ')') + else: + if args.verbose: + print (' Removed line: ' + origLine) + + # --- Done, cleanup + xsb_aircraft_f.close() + xsb_aircraft_orig_f.close() + print (' ', path, 'done, converted', numObj, 'OBJ8 files') + return numObj + + except IOError as e: + parser.error('Converting folder ' + str(path) + ' failed:\n' + e.filename + ':\n'+ e.strerror +'\n') + + + + +""" === MAIN === """ +# --- Handling command line argumens --- +parser = argparse.ArgumentParser(description='CSL2XSB 0.3.1: Convert CSL packages to XPMP2 format, convert some animation dataRefs. Tested with: Bluebell, X-CSL.',fromfile_prefix_chars='@') +parser.add_argument('path', help='Base path, searched recursively for CSL packages identified by existing xsb_aircraft.txt files', nargs='?', default='NULL') +parser.add_argument('--noupdate', help='Suppress update of OBJ8 files if there are no additional textures', action='store_true') +parser.add_argument('--norecursion', help='Do not search directories recursively', action='store_true') +parser.add_argument('-v', '--verbose', help='More detailed output about every change', action='store_true') +parser.add_argument('--replaceDR', metavar="TEXT", help="Replace dataRef's root 'libxplanemp' with TEXT.\nCAUTION: This works with LiveTraffic up to v1.5 only. CSLs' animations/lights will no longer work with standard multipayer clients nor with LiveTraffic starting from v2.0!") + +args = parser.parse_args() + +# If called with no argument (e.g. by double-clicking the script) ask the user interactively +# if (s)he likes to work on the current directory. +UserWantsIt = None +if args.path == 'NULL': + args.path = Path.cwd() + print (parser.description) + print ('Do you want to run CSL2XSB on the current directory "'+str(args.path)+'"?') + while True: + UserWantsIt = input ('Answer "y" or "n": ') + if UserWantsIt.upper() == 'N': + print ('You answered "N", so we exit without doing anything. Try "python CSL2XSBpy -h" for help.') + exit() + if UserWantsIt.upper() == 'Y': + break + +# normalize the path, resolves relative paths and makes nice directory delimiters +basePath = Path(args.path) +if not basePath.exists() or not basePath.is_dir(): + parser.error('Base bath "' + str(basePath) + '" does not exist or is no directory.') + +if args.verbose: + print ('Base path: ', basePath) + +# --- Do it --- +numConverted = ConvFolder(basePath) +print ('Done. Converted ' + str(numConverted) + ' OBJ8 files in total. Produced ' + str(_warnings) + ' warning(s).') + +# Running interactively? +if UserWantsIt is not None: + input ("Hit [Enter] to finish.") + +# --- Done --- +exit(0) diff --git a/scripts/csl2xsb/LICENSE b/scripts/csl2xsb/LICENSE new file mode 100644 index 000000000..00f0264f3 --- /dev/null +++ b/scripts/csl2xsb/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 B.Hoppe + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/scripts/csl2xsb/README.md b/scripts/csl2xsb/README.md new file mode 100644 index 000000000..70bac5f28 --- /dev/null +++ b/scripts/csl2xsb/README.md @@ -0,0 +1,160 @@ +# CSL2XSB +Converts CSL packages to the XSB format for use in [LiveTraffic](https://twinfan.gitbook.io/livetraffic/) or more specifically: as expected by +the [XPMP2 library](https://github.com/TwinFan/XPMP2), a success to the +classic libxplanemp. + +`CSL2XSB` updates some CSL dataRefs (engine/prop rotation, reversers) so they become available to LiveTraffic (and other XPMP2-driven plugins). + +Tested with the following CSL model providers: + +- [Bluebell Package](https://forums.x-plane.org/index.php?/files/file/37041-bluebell-obj8-csl-packages/) +- [X-CSL](https://csl.x-air.ru/?lang_id=43) + +As this is a Python 3 script you [need Python 3](https://www.python.org/downloads/). +Tested with Python 3.7.3. + +## Simple usage in Windows + +- [Download](https://www.python.org/downloads/) and install Python 3 using the latest "Windows x86-64 web-based installer" + - Important: Check (select) the option "Add Python 3.x to PATH" at the bottom of the "Install Python" window. + - Click on "Install Now". Python will install. + - When done, click "Close" in the "Setup was successfull" screen. Now you've got Python 3. +- Download a CSL package like from X-CSL. +- Make a copy of it! +- Put the `CSL2XSB.py` script into the base directory of the that copy. +- Double-lick the `CSL2XSB.py` script in the explorer to start it. It will ask you if you want to run the script in that current directory. Enter "y" and hit Enter. + +## Synopsis + +``` +usage: CSL2XSB.py [-h] [--noupdate] [--norecursion] [-v] [--replaceDR TEXT] + [path] + +CSL2XSB 0.3.1: Convert CSL packages to XPMP2 format, convert some animation +dataRefs. Tested with: Bluebell, X-CSL. + +positional arguments: + path Base path, searched recursively for CSL packages + identified by existing xsb_aircraft.txt files + +optional arguments: + -h, --help show this help message and exit + --noupdate Suppress update of OBJ8 files if there are no additional + textures + --norecursion Do not search directories recursively + -v, --verbose More detailed output about every change + --replaceDR TEXT Replace dataRef's root 'libxplanemp' with TEXT. CAUTION: + This works with LiveTraffic up to v1.5 only. CSLs' + animations/lights will no longer work with standard + multipayer clients nor with LiveTraffic starting from + v2.0! +``` + +This will likely produce many new files, especially new `.OBJ` files, so disk usage increases. + +## Background + +The format of CSL packages has originally been defined with the creation of the +[libxplanemp library](https://github.com/kuroneko/libxplanemp/wiki). +Since then, various dialects evolved, e.g. in X-IvAp or PilotEdge or the like. +That means that CSL packages created for these clients cannot to their +full extend be used in LiveTraffic, which uses the original format. +There are disputes about how future-proof each format is. + +To make other packages accessible to LiveTraffic (and likely, though not tested: +XSquawkBox) this Python script `CSL2XSB.py` converts their format. + +This only works for the - nowadays most common - OBJ8 format, which is the only +format supported by XPMP2ever. + +- `xsb_aircraft.txt` file: + - Validates entries in `xsb_aircraft.txt` file and corrects some common errors + - Warns about non-existing `.obj` or texture files, which will cause error + when actually using that model. + - Comments out models of unsupported format (`.acf` or OBJ7) + +- `.obj` files referred to by `xsb_aircraft.txt` file: + - Rewrites OBJ8 models for which texture have been defined in additional + parameters to the `OBJ8` command, which cannot be supported in XPMP2 and + X-Plane's instancing. + - Replaces a number of older dataRef names (dating back to WT times) with + those now offered by XPMP2, so that additional animations like props, rotors, + or reversers become accessible. + - Removes unsupported commands `TEXTURE`, `HASGEAR`, `OFFSET`. + +## Package-specific Information + +### X-CSL + +X-CSL packages can be downloaded [here](https://csl.x-air.ru/downloads?lang_id=43). +If you don't already have the package (e.g. because you use X-IvAp) +then download and start the installer. The installer will _not_ identify +LiveTraffic as a supported plugin. Instead, from the menu select +`File > Select Custom Path` and specify a path where the CSL packages are +to be downloaded to and where they later can be updated. + +Do not let `CSL2XSB.py` run on this original download. Always make a copy of +the entire folder into a folder LiveTraffic can reach, e.g. to +`<...>/LiveTraffic/Resources/X-CSL`. Now run the script on this copy, +e.g. like this: +``` +python CSL2XSB.py <...>/LiveTraffic/Resources/X-CSL +``` +(Note that in some environments like Mac OS you need to specifically call +`python3` instead of just `python`.) + +You can always repeat the above call and the script shall do it just again +(e.g. in case you modified any files manually). It keeps copies of original +files that it needs for a repeated run. + +What the script then does is, in brief, as follows: +1. It searches for `xsb_aircraft.txt` files. If it does not find any in the + current directory it will recursively dig deeper into the folder structure. + So it will eventually find all folders below `X-CSL`. +2. It copies the `xsb_aircraft.txt` file to `xsb_aircraft.txt.orig` and + reads that to create a new `xsb_aircraft.txt` file. +3. **The `OBJ8 SOLID/LIGHTS/GLASS` lines are at the core:** + Here, additional parameters often define the texture files to use. + The original format does not support these texture parameters. + Instead, the textures are to be defined in the `.OBJ` file. + - To remedy this, the script now also reads the `.OBJ` file and + writes a _new_ version of it replacing the `TEXTURE` and `TEXTURE_LIT` lines. + - This new `.OBJ` file is then referred to in the `OBJ8 SOLID/LIGHTS` line + in the output version of `xsb_aircraft.txt`. + (An original `OBJ8 GLASS` line will be written to output as `OBJ8 SOLID` as `GLASS` is now deprecated.) + - The availability of the referred texture and lit-texture files is tested. + Some of them do not exist in the package, which causes warnings by the script. + This is a problem in the original X-CSL package. In a few cases, + the script can find a replacement by just replacing the extension of the texture file. +4. Minor other changes: + - Replace the non-existing ICAO aircraft designator `MD80` with `MD81`. + - Remove deprecated lines like `LOW_LOD` from `xsb_aircraft.txt` + - Replace `:` or spaces in `OBJ8` aircraft names with `_`. + +The size of the complete X-CSL package increases from about 2 GB to about +3.2 GB due to the additionally created `.OBJ` files. + +The resulting folder structures and its files should be usable by LiveTraffic +and produce no more warnings in `Log.txt`. + +See LiveTraffic's [documentation on CSL settings](https://twinfan.gitbook.io/livetraffic/setup/configuration/settings-csl) for how to provide LiveTraffic with the path to the converted X-CSL package. + +### Bluebell + +The Bluebell package is the standard package recommended for usage with LiveTraffic. +Many CSL objects in the Bluebell package are capable of turning rotors or +open reversers. But as there was no `libxplanemp` CSL dataRef to control +these animation they stayed unchanged in the `.obj` files when the Bluebell +package was originally created, e.g. like `cjs/world_traffic/engine_rotation_angle1`. + +XPMP2 (used by LiveTraffic) now implements more CSL dataRefs than in the +standard `libxplanemp` version, e.g. for engine/prop rotation and +reversers animation, and tries to stick to a +[standard set by PilotEdge](https://www.pilotedge.net/pages/csl-authoring) +as far as possible. + +`CSL2XSB` replaces the unchanged dataRefs with the ones LiveTraffic now supports +so that rotors do rotate etc. The example above will be replaced with +`libxplanemp/engines/engine_rotation_angle_deg`. +For a complete list of replacement dataRefs see the very beginning of the script +in the map called `_DR`.