Issue #69 Import CSL2XSB script

This commit is contained in:
Mat Sutcliffe
2020-08-09 01:43:49 +01:00
parent 807f13a3f3
commit 73435fb76a
4 changed files with 706 additions and 0 deletions

104
scripts/csl2xsb/.gitignore vendored Normal file
View File

@@ -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/

421
scripts/csl2xsb/CSL2XSB.py Normal file
View File

@@ -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)

21
scripts/csl2xsb/LICENSE Normal file
View File

@@ -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.

160
scripts/csl2xsb/README.md Normal file
View File

@@ -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`.