mirror of
https://github.com/swift-project/pilotclient.git
synced 2026-03-22 06:45:37 +08:00
Issue #69 Import CSL2XSB script
This commit is contained in:
104
scripts/csl2xsb/.gitignore
vendored
Normal file
104
scripts/csl2xsb/.gitignore
vendored
Normal 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
421
scripts/csl2xsb/CSL2XSB.py
Normal 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
21
scripts/csl2xsb/LICENSE
Normal 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
160
scripts/csl2xsb/README.md
Normal 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`.
|
||||
Reference in New Issue
Block a user