mirror of
https://github.com/norohind/EDMC_SpanshRouter.git
synced 2025-05-08 11:21:06 +03:00
855 lines
36 KiB
Python
855 lines
36 KiB
Python
import ast
|
|
import csv
|
|
import io
|
|
import json
|
|
import logging
|
|
import os
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
import tkinter as tk
|
|
import tkinter.filedialog as filedialog
|
|
import tkinter.messagebox as confirmDialog
|
|
import traceback
|
|
import webbrowser
|
|
from time import sleep
|
|
from tkinter import *
|
|
|
|
import requests
|
|
from config import appname
|
|
from monitor import monitor
|
|
|
|
from . import AutoCompleter, PlaceHolder
|
|
from .updater import SpanshUpdater
|
|
|
|
# We need a name of plugin dir, not SpanshRouter.py dir
|
|
plugin_name = os.path.basename(os.path.dirname(os.path.dirname(__file__)))
|
|
logger = logging.getLogger(f'{appname}.{plugin_name}')
|
|
|
|
|
|
class SpanshRouter():
|
|
def __init__(self, plugin_dir):
|
|
version_file = os.path.join(plugin_dir, "version.json")
|
|
with open(version_file, 'r') as version_fd:
|
|
self.plugin_version = version_fd.read()
|
|
|
|
self.update_available = False
|
|
self.roadtoriches = False
|
|
self.fleetcarrier = False
|
|
self.galaxy = False
|
|
self.next_stop = "No route planned"
|
|
self.route = []
|
|
self.next_wp_label = "Next waypoint: "
|
|
self.jumpcountlbl_txt = "Estimated jumps left: "
|
|
self.bodieslbl_txt = "Bodies to scan at: "
|
|
self.fleetstocklbl_txt = "Time to restock Tritium"
|
|
self.refuellbl_txt = "Time to scoop some fuel"
|
|
self.bodies = ""
|
|
self.parent = None
|
|
self.plugin_dir = plugin_dir
|
|
self.save_route_path = os.path.join(plugin_dir, 'route.csv')
|
|
self.export_route_path = os.path.join(plugin_dir, 'Export for TCE.exp')
|
|
self.offset_file_path = os.path.join(plugin_dir, 'offset')
|
|
self.offset = 0
|
|
self.jumps_left = 0
|
|
self.error_txt = tk.StringVar()
|
|
self.plot_error = "Error while trying to plot a route, please try again."
|
|
self.system_header = "System Name"
|
|
self.bodyname_header = "Body Name"
|
|
self.bodysubtype_header = "Body Subtype"
|
|
self.jumps_header = "Jumps"
|
|
self.restocktritium_header = "Restock Tritium"
|
|
self.refuel_header = "Refuel"
|
|
self.pleaserefuel = False
|
|
|
|
# -- GUI part --
|
|
def init_gui(self, parent):
|
|
self.parent = parent
|
|
self.frame = tk.Frame(parent, borderwidth=2)
|
|
self.frame.grid(sticky=tk.NSEW, columnspan=2)
|
|
|
|
# Route info
|
|
self.waypoint_prev_btn = tk.Button(self.frame, text="^", command=self.goto_prev_waypoint)
|
|
self.waypoint_btn = tk.Button(self.frame, text=self.next_wp_label + '\n' + self.next_stop, command=self.copy_waypoint)
|
|
self.waypoint_next_btn = tk.Button(self.frame, text="v", command=self.goto_next_waypoint)
|
|
self.jumpcounttxt_lbl = tk.Label(self.frame, text=self.jumpcountlbl_txt + str(self.jumps_left))
|
|
self.bodies_lbl = tk.Label(self.frame, justify=LEFT, text=self.bodieslbl_txt + self.bodies)
|
|
self.fleetrestock_lbl = tk.Label(self.frame, justify=LEFT, text=self.fleetstocklbl_txt)
|
|
self.refuel_lbl = tk.Label(self.frame, justify=LEFT, text=self.refuellbl_txt)
|
|
self.error_lbl = tk.Label(self.frame, textvariable=self.error_txt)
|
|
|
|
# Plotting GUI
|
|
self.source_ac = AutoCompleter(self.frame, "Source System", width=30)
|
|
self.dest_ac = AutoCompleter(self.frame, "Destination System", width=30)
|
|
self.range_entry = PlaceHolder(self.frame, "Range (LY)", width=10)
|
|
self.efficiency_slider = tk.Scale(self.frame, from_=1, to=100, orient=tk.HORIZONTAL, label="Efficiency (%)")
|
|
self.efficiency_slider.set(60)
|
|
self.plot_gui_btn = tk.Button(self.frame, text="Plot route", command=self.show_plot_gui)
|
|
self.plot_route_btn = tk.Button(self.frame, text="Calculate", command=self.plot_route)
|
|
self.cancel_plot = tk.Button(self.frame, text="Cancel", command=lambda: self.show_plot_gui(False))
|
|
|
|
self.csv_route_btn = tk.Button(self.frame, text="Import file", command=self.plot_file)
|
|
self.export_route_btn = tk.Button(self.frame, text="Export for TCE", command=self.export_route)
|
|
self.clear_route_btn = tk.Button(self.frame, text="Clear route", command=self.clear_route)
|
|
|
|
row = 0
|
|
self.waypoint_prev_btn.grid(row=row, columnspan=2)
|
|
row += 1
|
|
self.waypoint_btn.grid(row=row, columnspan=2)
|
|
row += 1
|
|
self.waypoint_next_btn.grid(row=row, columnspan=2)
|
|
row += 1
|
|
self.bodies_lbl.grid(row=row, columnspan=2, sticky=tk.W)
|
|
row += 1
|
|
self.fleetrestock_lbl.grid(row=row, columnspan=2, sticky=tk.W)
|
|
row += 1
|
|
self.refuel_lbl.grid(row=row,columnspan=2, sticky=tk.W)
|
|
row += 1
|
|
self.source_ac.grid(row=row,columnspan=2, pady=(10,0)) # The AutoCompleter takes two rows to show the list when needed, so we skip one
|
|
row += 2
|
|
self.dest_ac.grid(row=row,columnspan=2, pady=(10,0))
|
|
row += 2
|
|
self.range_entry.grid(row=row, pady=10, sticky=tk.W)
|
|
row += 1
|
|
self.efficiency_slider.grid(row=row, pady=10, columnspan=2, sticky=tk.EW)
|
|
row += 1
|
|
self.csv_route_btn.grid(row=row, pady=10, padx=0)
|
|
self.plot_route_btn.grid(row=row, pady=10, padx=0)
|
|
self.plot_gui_btn.grid(row=row, column=1, pady=10, padx=5, sticky=tk.W)
|
|
self.cancel_plot.grid(row=row, column=1, pady=10, padx=5, sticky=tk.E)
|
|
row += 1
|
|
self.export_route_btn.grid(row=row, pady=10, padx=0)
|
|
self.clear_route_btn.grid(row=row, column=1, pady=10, padx=5, sticky=tk.W)
|
|
row += 1
|
|
self.jumpcounttxt_lbl.grid(row=row, pady=5, sticky=tk.W)
|
|
row += 1
|
|
self.error_lbl.grid(row=row, columnspan=2)
|
|
self.error_lbl.grid_remove()
|
|
row += 1
|
|
|
|
# Check if we're having a valid range on the fly
|
|
self.range_entry.var.trace('w', self.check_range)
|
|
|
|
self.show_plot_gui(False)
|
|
|
|
if not self.route.__len__() > 0:
|
|
self.waypoint_prev_btn.grid_remove()
|
|
self.waypoint_btn.grid_remove()
|
|
self.waypoint_next_btn.grid_remove()
|
|
self.jumpcounttxt_lbl.grid_remove()
|
|
self.bodies_lbl.grid_remove()
|
|
self.fleetrestock_lbl.grid_remove()
|
|
self.export_route_btn.grid_remove()
|
|
self.clear_route_btn.grid_remove()
|
|
|
|
self.update_gui()
|
|
|
|
return self.frame
|
|
|
|
def show_plot_gui(self, show=True):
|
|
if show:
|
|
self.waypoint_prev_btn.grid_remove()
|
|
self.waypoint_btn.grid_remove()
|
|
self.waypoint_next_btn.grid_remove()
|
|
self.jumpcounttxt_lbl.grid_remove()
|
|
self.bodies_lbl.grid_remove()
|
|
self.fleetrestock_lbl.grid_remove()
|
|
self.export_route_btn.grid_remove()
|
|
self.clear_route_btn.grid_remove()
|
|
|
|
self.plot_gui_btn.grid_remove()
|
|
self.csv_route_btn.grid_remove()
|
|
self.source_ac.grid()
|
|
# Prefill the "Source" entry with the current system
|
|
self.source_ac.set_text(monitor.state['SystemName'] if monitor.state['SystemName'] is not None else "Source System", monitor.state['SystemName'] is None)
|
|
self.dest_ac.grid()
|
|
self.range_entry.grid()
|
|
self.efficiency_slider.grid()
|
|
self.plot_route_btn.grid()
|
|
self.cancel_plot.grid()
|
|
|
|
self.show_route_gui(False)
|
|
|
|
else:
|
|
if len(self.source_ac.var.get()) == 0:
|
|
self.source_ac.put_placeholder()
|
|
if len(self.dest_ac.var.get()) == 0:
|
|
self.dest_ac.put_placeholder()
|
|
self.source_ac.hide_list()
|
|
self.source_ac.grid_remove()
|
|
self.dest_ac.hide_list()
|
|
self.dest_ac.grid_remove()
|
|
self.range_entry.grid_remove()
|
|
self.efficiency_slider.grid_remove()
|
|
self.plot_gui_btn.grid_remove()
|
|
self.plot_route_btn.grid_remove()
|
|
self.cancel_plot.grid_remove()
|
|
self.plot_gui_btn.grid()
|
|
self.csv_route_btn.grid()
|
|
self.show_route_gui(True)
|
|
|
|
def set_source_ac(self, text):
|
|
self.source_ac.delete(0, tk.END)
|
|
self.source_ac.insert(0, text)
|
|
self.source_ac.set_default_style()
|
|
|
|
def show_route_gui(self, show):
|
|
self.hide_error()
|
|
if not show or not self.route.__len__() > 0:
|
|
self.waypoint_prev_btn.grid_remove()
|
|
self.waypoint_btn.grid_remove()
|
|
self.waypoint_next_btn.grid_remove()
|
|
self.jumpcounttxt_lbl.grid_remove()
|
|
self.bodies_lbl.grid_remove()
|
|
self.fleetrestock_lbl.grid_remove()
|
|
self.refuel_lbl.grid_remove()
|
|
self.export_route_btn.grid_remove()
|
|
self.clear_route_btn.grid_remove()
|
|
else:
|
|
self.waypoint_btn["text"] = self.next_wp_label + '\n' + self.next_stop
|
|
if self.jumps_left > 0:
|
|
self.jumpcounttxt_lbl["text"] = self.jumpcountlbl_txt + str(self.jumps_left)
|
|
self.jumpcounttxt_lbl.grid()
|
|
else:
|
|
self.jumpcounttxt_lbl.grid_remove()
|
|
|
|
if self.roadtoriches:
|
|
self.bodies_lbl["text"] = self.bodieslbl_txt + self.bodies
|
|
self.bodies_lbl.grid()
|
|
else:
|
|
self.bodies_lbl.grid_remove()
|
|
|
|
self.fleetrestock_lbl.grid_remove()
|
|
if self.fleetcarrier:
|
|
if self.offset > 0:
|
|
restock = self.route[self.offset - 1][2]
|
|
if restock.lower() == "yes":
|
|
self.fleetrestock_lbl["text"] = f"At: {self.route[self.offset - 1][0]}\n {self.fleetstocklbl_txt}"
|
|
self.fleetrestock_lbl.grid()
|
|
|
|
if self.galaxy:
|
|
if self.pleaserefuel:
|
|
self.refuel_lbl['text'] = self.refuellbl_txt
|
|
self.refuel_lbl.grid()
|
|
else:
|
|
self.refuel_lbl.grid_remove()
|
|
|
|
self.waypoint_prev_btn.grid()
|
|
self.waypoint_btn.grid()
|
|
self.waypoint_next_btn.grid()
|
|
|
|
if self.offset == 0:
|
|
self.waypoint_prev_btn.config(state=tk.DISABLED)
|
|
else:
|
|
self.waypoint_prev_btn.config(state=tk.NORMAL)
|
|
|
|
if self.offset == self.route.__len__()-1:
|
|
self.waypoint_next_btn.config(state=tk.DISABLED)
|
|
else:
|
|
self.waypoint_next_btn.config(state=tk.NORMAL)
|
|
|
|
self.export_route_btn.grid()
|
|
self.clear_route_btn.grid()
|
|
|
|
def update_gui(self):
|
|
self.show_route_gui(True)
|
|
|
|
def show_error(self, error):
|
|
self.error_txt.set(error)
|
|
self.error_lbl.grid()
|
|
|
|
def hide_error(self):
|
|
self.error_lbl.grid_remove()
|
|
|
|
def enable_plot_gui(self, enable):
|
|
if enable:
|
|
self.source_ac.config(state=tk.NORMAL)
|
|
self.source_ac.update_idletasks()
|
|
self.dest_ac.config(state=tk.NORMAL)
|
|
self.dest_ac.update_idletasks()
|
|
self.efficiency_slider.config(state=tk.NORMAL)
|
|
self.efficiency_slider.update_idletasks()
|
|
self.range_entry.config(state=tk.NORMAL)
|
|
self.range_entry.update_idletasks()
|
|
self.plot_route_btn.config(state=tk.NORMAL, text="Calculate")
|
|
self.plot_route_btn.update_idletasks()
|
|
self.cancel_plot.config(state=tk.NORMAL)
|
|
self.cancel_plot.update_idletasks()
|
|
else:
|
|
self.source_ac.config(state=tk.DISABLED)
|
|
self.source_ac.update_idletasks()
|
|
self.dest_ac.config(state=tk.DISABLED)
|
|
self.dest_ac.update_idletasks()
|
|
self.efficiency_slider.config(state=tk.DISABLED)
|
|
self.efficiency_slider.update_idletasks()
|
|
self.range_entry.config(state=tk.DISABLED)
|
|
self.range_entry.update_idletasks()
|
|
self.plot_route_btn.config(state=tk.DISABLED, text="Computing...")
|
|
self.plot_route_btn.update_idletasks()
|
|
self.cancel_plot.config(state=tk.DISABLED)
|
|
self.cancel_plot.update_idletasks()
|
|
|
|
# -- END GUI part --
|
|
|
|
|
|
def open_last_route(self):
|
|
try:
|
|
has_headers = False
|
|
with open(self.save_route_path, 'r', newline='') as csvfile:
|
|
# Check if the file has a header for compatibility with previous versions
|
|
dict_route_reader = csv.DictReader(csvfile)
|
|
if dict_route_reader.fieldnames[0] == self.system_header:
|
|
has_headers = True
|
|
|
|
if has_headers:
|
|
self.plot_csv(self.save_route_path, clear_previous_route=False)
|
|
else:
|
|
with open(self.save_route_path, 'r', newline='') as csvfile:
|
|
route_reader = csv.reader(csvfile)
|
|
|
|
for row in route_reader:
|
|
if row not in (None, "", []):
|
|
self.route.append(row)
|
|
|
|
try:
|
|
with open(self.offset_file_path, 'r') as offset_fh:
|
|
self.offset = int(offset_fh.readline())
|
|
|
|
except:
|
|
self.offset = 0
|
|
|
|
self.jumps_left = 0
|
|
for row in self.route[self.offset:]:
|
|
if row[1] not in [None, "", []]:
|
|
self.jumps_left += int(row[1])
|
|
|
|
self.next_stop = self.route[self.offset][0]
|
|
self.update_bodies_text()
|
|
self.copy_waypoint()
|
|
self.update_gui()
|
|
|
|
except IOError:
|
|
logger.info("No previously saved route")
|
|
except:
|
|
exc_type, exc_value, exc_traceback = sys.exc_info()
|
|
lines = traceback.format_exception(exc_type, exc_value, exc_traceback)
|
|
logger.warning(''.join('!! ' + line for line in lines))
|
|
|
|
def copy_waypoint(self):
|
|
if sys.platform == "linux" or sys.platform == "linux2":
|
|
command = subprocess.Popen(["echo", "-n", self.next_stop], stdout=subprocess.PIPE)
|
|
subprocess.Popen(["xclip", "-selection", "c"], stdin=command.stdout)
|
|
else:
|
|
self.parent.clipboard_clear()
|
|
self.parent.clipboard_append(self.next_stop)
|
|
self.parent.update()
|
|
|
|
def goto_next_waypoint(self):
|
|
if self.offset < self.route.__len__()-1:
|
|
self.update_route(1)
|
|
|
|
def goto_prev_waypoint(self):
|
|
if self.offset > 0:
|
|
self.update_route(-1)
|
|
|
|
def update_route(self, direction=1):
|
|
if direction > 0:
|
|
if self.route[self.offset][1] not in [None, "", []]:
|
|
if not self.galaxy:
|
|
self.jumps_left -= int(self.route[self.offset][1])
|
|
else:
|
|
self.jumps_left -= 1
|
|
self.offset += 1
|
|
else:
|
|
self.offset -= 1
|
|
if self.route[self.offset][1] not in [None, "", []]:
|
|
if not self.galaxy:
|
|
self.jumps_left += int(self.route[self.offset][1])
|
|
else:
|
|
self.jumps_left += 1
|
|
|
|
if self.offset >= self.route.__len__():
|
|
self.next_stop = "End of the road!"
|
|
self.update_gui()
|
|
else:
|
|
self.next_stop = self.route[self.offset][0]
|
|
self.update_bodies_text()
|
|
|
|
if self.galaxy:
|
|
self.pleaserefuel = self.route[self.offset][1] == "Yes"
|
|
|
|
self.update_gui()
|
|
self.copy_waypoint()
|
|
self.save_offset()
|
|
|
|
def goto_changelog_page(self):
|
|
changelog_url = 'https://github.com/CMDR-Kiel42/EDMC_SpanshRouter/blob/master/CHANGELOG.md#'
|
|
changelog_url += self.spansh_updater.version.replace('.', '')
|
|
webbrowser.open(changelog_url)
|
|
|
|
def plot_file(self):
|
|
ftypes = [
|
|
('All supported files', '*.csv *.txt'),
|
|
('CSV files', '*.csv'),
|
|
('Text files', '*.txt'),
|
|
]
|
|
filename = filedialog.askopenfilename(filetypes = ftypes, initialdir=os.path.expanduser('~'))
|
|
|
|
if filename.__len__() > 0:
|
|
try:
|
|
ftype_supported = False
|
|
if filename.endswith(".csv"):
|
|
ftype_supported = True
|
|
self.plot_csv(filename)
|
|
|
|
elif filename.endswith(".txt"):
|
|
ftype_supported = True
|
|
self.plot_edts(filename)
|
|
|
|
if ftype_supported:
|
|
self.offset = 0
|
|
self.next_stop = self.route[0][0]
|
|
if self.galaxy:
|
|
self.pleaserefuel = self.route[0][1] == "Yes"
|
|
self.update_bodies_text()
|
|
self.copy_waypoint()
|
|
self.update_gui()
|
|
self.save_all_route()
|
|
else:
|
|
self.show_error("Unsupported file type")
|
|
except:
|
|
exc_type, exc_value, exc_traceback = sys.exc_info()
|
|
lines = traceback.format_exception(exc_type, exc_value, exc_traceback)
|
|
logger.warning(''.join('!! ' + line for line in lines))
|
|
self.enable_plot_gui(True)
|
|
self.show_error("(1) An error occured while reading the file.")
|
|
|
|
def plot_csv(self, filename, clear_previous_route=True):
|
|
with io.open(filename, 'r', encoding='utf-8-sig', newline='') as csvfile:
|
|
self.roadtoriches = False
|
|
self.fleetcarrier = False
|
|
self.galaxy = False
|
|
|
|
if clear_previous_route:
|
|
self.clear_route(False)
|
|
|
|
route_reader = csv.DictReader(csvfile)
|
|
|
|
# Get column header names as string
|
|
headerline = ','.join(route_reader.fieldnames)
|
|
|
|
# Define the differnt internal formats based on the CSV header row
|
|
internalbasicheader1 = "System Name"
|
|
internalbasicheader2 = "System Name,Jumps"
|
|
internalrichesheader = "System Name,Jumps,Body Name,Body Subtype"
|
|
internalfleetcarrierheader = "System Name,Jumps,Restock Tritium"
|
|
internalgalaxyheader = "System Name,Refuel"
|
|
# Define the differnt import file formats based on the CSV header row
|
|
neutronimportheader = "System Name,Distance To Arrival,Distance Remaining,Neutron Star,Jumps"
|
|
road2richesimportheader = "System Name,Body Name,Body Subtype,Is Terraformable,Distance To Arrival,Estimated Scan Value,Estimated Mapping Value,Jumps"
|
|
fleetcarrierimportheader = "System Name,Distance,Distance Remaining,Fuel Used,Icy Ring,Pristine,Restock Tritium"
|
|
galaxyimportheader = "System Name,Distance,Distance Remaining,Fuel Left,Fuel Used,Refuel,Neutron Star"
|
|
|
|
if (headerline == internalbasicheader1) or (headerline == internalbasicheader2) or (headerline == neutronimportheader):
|
|
for row in route_reader:
|
|
if row not in (None, "", []):
|
|
self.route.append([
|
|
row[self.system_header],
|
|
row.get(self.jumps_header, "") # Jumps column is optional
|
|
])
|
|
if row.get(self.jumps_header): # Jumps column is optional
|
|
self.jumps_left += int(row[self.jumps_header])
|
|
|
|
elif headerline == internalrichesheader:
|
|
self.roadtoriches = True
|
|
|
|
for row in route_reader:
|
|
if row not in (None, "", []):
|
|
# Convert string representations of lists to actual Lists
|
|
bodynames = ast.literal_eval(row[self.bodyname_header])
|
|
bodysubtypes = ast.literal_eval(row[self.bodysubtype_header])
|
|
|
|
self.route.append([
|
|
row[self.system_header],
|
|
row[self.jumps_header],
|
|
bodynames,
|
|
bodysubtypes
|
|
])
|
|
self.jumps_left += int(row[self.jumps_header])
|
|
|
|
elif headerline == internalfleetcarrierheader:
|
|
self.fleetcarrier = True
|
|
|
|
for row in route_reader:
|
|
if row not in (None, "", []):
|
|
self.route.append([
|
|
row[self.system_header],
|
|
row[self.jumps_header],
|
|
row[self.restocktritium_header]
|
|
])
|
|
self.jumps_left += int(row[self.jumps_header])
|
|
|
|
elif headerline == road2richesimportheader:
|
|
self.roadtoriches = True
|
|
|
|
bodynames = []
|
|
bodysubtypes = []
|
|
|
|
for row in route_reader:
|
|
bodyname = row[self.bodyname_header]
|
|
bodysubtype = row[self.bodysubtype_header]
|
|
|
|
# Update the current system with additional bodies from new CSV row
|
|
if self.route.__len__() > 0 and row[self.system_header] == self.route[-1][0]:
|
|
self.route[-1][2].append(bodyname)
|
|
self.route[-1][3].append(bodysubtype)
|
|
continue
|
|
|
|
if row not in (None, "", []):
|
|
bodynames.append(bodyname)
|
|
bodysubtypes.append(bodysubtype)
|
|
|
|
self.route.append([
|
|
row[self.system_header],
|
|
row[self.jumps_header],
|
|
bodynames.copy(),
|
|
bodysubtypes.copy()
|
|
])
|
|
# Clear bodies for next system
|
|
bodynames.clear()
|
|
bodysubtypes.clear()
|
|
|
|
self.jumps_left += int(row[self.jumps_header])
|
|
|
|
elif headerline == fleetcarrierimportheader:
|
|
self.fleetcarrier = True
|
|
|
|
for row in route_reader:
|
|
if row not in (None, "", []):
|
|
self.route.append([
|
|
row[self.system_header],
|
|
1, # Jumps is faked as every row is 1 jump
|
|
row[self.restocktritium_header]
|
|
])
|
|
self.jumps_left += 1 # Jumps is faked as every row is 1 jump
|
|
elif (headerline == internalgalaxyheader) or (headerline == galaxyimportheader):
|
|
self.galaxy = True
|
|
|
|
for row in route_reader:
|
|
if row not in (None, "", []):
|
|
self.route.append([
|
|
row[self.system_header],
|
|
row[self.refuel_header]
|
|
])
|
|
self.jumps_left += 1
|
|
else:
|
|
self.show_error("Could not detect file format")
|
|
|
|
def plot_route(self):
|
|
self.hide_error()
|
|
try:
|
|
source = self.source_ac.get().strip()
|
|
dest = self.dest_ac.get().strip()
|
|
efficiency = self.efficiency_slider.get()
|
|
|
|
# Hide autocomplete lists in case they're still shown
|
|
self.source_ac.hide_list()
|
|
self.dest_ac.hide_list()
|
|
|
|
if ( source and source != self.source_ac.placeholder and
|
|
dest and dest != self.dest_ac.placeholder ):
|
|
|
|
try:
|
|
range_ly = float(self.range_entry.get())
|
|
except ValueError:
|
|
self.show_error("Invalid range")
|
|
return
|
|
|
|
job_url="https://spansh.co.uk/api/route?"
|
|
|
|
results = requests.post(job_url, params={
|
|
"efficiency": efficiency,
|
|
"range": range_ly,
|
|
"from": source,
|
|
"to": dest
|
|
}, headers={'User-Agent': "EDMC_SpanshRouter 1.0"})
|
|
|
|
if results.status_code == 202:
|
|
self.enable_plot_gui(False)
|
|
|
|
tries = 0
|
|
while(tries < 20):
|
|
response = json.loads(results.content)
|
|
job = response["job"]
|
|
|
|
results_url = "https://spansh.co.uk/api/results/" + job
|
|
route_response = requests.get(results_url, timeout=5)
|
|
if route_response.status_code != 202:
|
|
break
|
|
tries += 1
|
|
sleep(1)
|
|
|
|
if route_response:
|
|
if route_response.status_code == 200:
|
|
route = json.loads(route_response.content)["result"]["system_jumps"]
|
|
self.clear_route(show_dialog=False)
|
|
for waypoint in route:
|
|
self.route.append([waypoint["system"], str(waypoint["jumps"])])
|
|
self.jumps_left += waypoint["jumps"]
|
|
self.enable_plot_gui(True)
|
|
self.show_plot_gui(False)
|
|
self.offset = 1 if self.route[0][0] == monitor.state['SystemName'] else 0
|
|
self.next_stop = self.route[self.offset][0]
|
|
self.copy_waypoint()
|
|
self.update_gui()
|
|
self.save_all_route()
|
|
else:
|
|
logger.warning(f"Failed to query plotted route from Spansh, code: {str(route_response.status_code)}; text: {route_response.text}")
|
|
self.enable_plot_gui(True)
|
|
failure = json.loads(results.content)
|
|
|
|
if route_response.status_code == 400 and "error" in failure:
|
|
self.show_error(failure["error"])
|
|
if "starting system" in failure["error"]:
|
|
self.source_ac["fg"] = "red"
|
|
if "finishing system" in failure["error"]:
|
|
self.dest_ac["fg"] = "red"
|
|
else:
|
|
self.show_error(self.plot_error)
|
|
else:
|
|
logger.warning("Query to Spansh timed out")
|
|
self.enable_plot_gui(True)
|
|
self.show_error("The query to Spansh was too long and timed out, please try again.")
|
|
else:
|
|
logger.warning(f"Failed to query plotted route from Spansh: code {str(results.status_code)}; text: {results.text}")
|
|
self.enable_plot_gui(True)
|
|
failure = json.loads(results.content)
|
|
|
|
if results.status_code == 400 and "error" in failure:
|
|
self.show_error(failure["error"])
|
|
if "starting system" in failure["error"]:
|
|
self.source_ac["fg"] = "red"
|
|
if "finishing system" in failure["error"]:
|
|
self.dest_ac["fg"] = "red"
|
|
else:
|
|
self.show_error(self.plot_error)
|
|
|
|
except:
|
|
exc_type, exc_value, exc_traceback = sys.exc_info()
|
|
lines = traceback.format_exception(exc_type, exc_value, exc_traceback)
|
|
logger.warning(''.join('!! ' + line for line in lines))
|
|
self.enable_plot_gui(True)
|
|
self.show_error(self.plot_error)
|
|
|
|
def plot_edts(self, filename):
|
|
try:
|
|
with open(filename, 'r') as txtfile:
|
|
route_txt = txtfile.readlines()
|
|
self.clear_route(False)
|
|
for row in route_txt:
|
|
if row not in (None, "", []):
|
|
if row.lstrip().startswith('==='):
|
|
jumps = int(re.findall("\d+ jump", row)[0].rstrip(' jumps'))
|
|
self.jumps_left += jumps
|
|
|
|
system = row[row.find('>') + 1:]
|
|
if ',' in system:
|
|
systems = system.split(',')
|
|
for system in systems:
|
|
self.route.append([system.strip(), jumps])
|
|
jumps = 1
|
|
self.jumps_left += jumps
|
|
else:
|
|
self.route.append([system.strip(), jumps])
|
|
except:
|
|
exc_type, exc_value, exc_traceback = sys.exc_info()
|
|
lines = traceback.format_exception(exc_type, exc_value, exc_traceback)
|
|
logger.warning(''.join('!! ' + line for line in lines))
|
|
self.enable_plot_gui(True)
|
|
self.show_error("(2) An error occured while reading the file.")
|
|
|
|
def export_route(self):
|
|
if self.route.__len__() == 0:
|
|
logger.info("No route to export")
|
|
return
|
|
|
|
route_start = self.route[0][0]
|
|
route_end = self.route[-1][0]
|
|
route_name = f"{route_start} to {route_end}"
|
|
#logger.info(f"Route name: {route_name}")
|
|
|
|
ftypes = [('TCE Flight Plan files', '*.exp')]
|
|
filename = filedialog.asksaveasfilename(filetypes = ftypes, initialdir=os.path.expanduser('~'), initialfile=f"{route_name}.exp")
|
|
|
|
if filename.__len__() > 0:
|
|
try:
|
|
with open(filename, 'w') as csvfile:
|
|
for row in self.route:
|
|
csvfile.write(f"{route_name},{row[0]}\n")
|
|
except:
|
|
exc_type, exc_value, exc_traceback = sys.exc_info()
|
|
lines = traceback.format_exception(exc_type, exc_value, exc_traceback)
|
|
#logger.error(''.join('!! ' + line for line in lines))
|
|
self.show_error("An error occured while writing the file.")
|
|
|
|
def clear_route(self, show_dialog=True):
|
|
clear = confirmDialog.askyesno("SpanshRouter","Are you sure you want to clear the current route?") if show_dialog else True
|
|
|
|
if clear:
|
|
self.offset = 0
|
|
self.route = []
|
|
self.next_waypoint = ""
|
|
self.jumps_left = 0
|
|
self.roadtoriches = False
|
|
self.fleetcarrier = False
|
|
self.galaxy = False
|
|
try:
|
|
os.remove(self.save_route_path)
|
|
except:
|
|
logger.info("No route to delete")
|
|
try:
|
|
os.remove(self.offset_file_path)
|
|
except:
|
|
logger.info("No offset file to delete")
|
|
|
|
self.update_gui()
|
|
|
|
def save_all_route(self):
|
|
self.save_route()
|
|
self.save_offset()
|
|
|
|
def save_route(self):
|
|
if self.route.__len__() != 0:
|
|
with open(self.save_route_path, 'w', newline='') as csvfile:
|
|
if self.roadtoriches:
|
|
# Write output: System, Jumps, Bodies[], BodySubTypes[]
|
|
fieldnames = [self.system_header, self.jumps_header, self.bodyname_header, self.bodysubtype_header]
|
|
writer = csv.writer(csvfile)
|
|
writer.writerow(fieldnames)
|
|
for row in self.route:
|
|
writer.writerow(row)
|
|
|
|
if self.fleetcarrier:
|
|
# Write output: System, Jumps,
|
|
fieldnames = [self.system_header, self.jumps_header, self.restocktritium_header]
|
|
writer = csv.writer(csvfile)
|
|
writer.writerow(fieldnames)
|
|
for row in self.route:
|
|
writer.writerow(row)
|
|
|
|
if self.galaxy:
|
|
# Write output: System, Refuel
|
|
fieldnames = [self.system_header, self.refuel_header]
|
|
writer = csv.writer(csvfile)
|
|
writer.writerow(fieldnames)
|
|
writer.writerows(self.route)
|
|
else:
|
|
# Write output: System, Jumps
|
|
fieldnames = [self.system_header, self.jumps_header]
|
|
writer = csv.writer(csvfile)
|
|
writer.writerow(fieldnames)
|
|
writer.writerows(self.route)
|
|
else:
|
|
try:
|
|
os.remove(self.save_route_path)
|
|
except:
|
|
logger.info("No route to delete")
|
|
|
|
def save_offset(self):
|
|
if self.route.__len__() != 0:
|
|
with open(self.offset_file_path, 'w') as offset_fh:
|
|
offset_fh.write(str(self.offset))
|
|
else:
|
|
try:
|
|
os.remove(self.offset_file_path)
|
|
except:
|
|
logger.info("No offset to delete")
|
|
|
|
def update_bodies_text(self):
|
|
if not self.roadtoriches: return
|
|
|
|
# For the bodies to scan use the current system, which is one before the next stop
|
|
lastsystemoffset = self.offset - 1
|
|
if lastsystemoffset < 0:
|
|
lastsystemoffset = 0 # Display bodies of the first system
|
|
|
|
lastsystem = self.route[lastsystemoffset][0]
|
|
bodynames = self.route[lastsystemoffset][2]
|
|
bodysubtypes = self.route[lastsystemoffset][3]
|
|
|
|
waterbodies = []
|
|
rockybodies = []
|
|
metalbodies = []
|
|
earthlikebodies = []
|
|
unknownbodies = []
|
|
|
|
for num, name in enumerate(bodysubtypes):
|
|
shortbodyname = bodynames[num].replace(lastsystem + " ", "")
|
|
if name.lower() == "high metal content world":
|
|
metalbodies.append(shortbodyname)
|
|
elif name.lower() == "rocky body":
|
|
rockybodies.append(shortbodyname)
|
|
elif name.lower() == "earth-like world":
|
|
earthlikebodies.append(shortbodyname)
|
|
elif name.lower() == "water world":
|
|
waterbodies.append(shortbodyname)
|
|
else:
|
|
unknownbodies.append(shortbodyname)
|
|
|
|
bodysubtypeandname = ""
|
|
if len(metalbodies) > 0: bodysubtypeandname += f"\n Metal: " + ', '.join(metalbodies)
|
|
if len(rockybodies) > 0: bodysubtypeandname += f"\n Rocky: " + ', '.join(rockybodies)
|
|
if len(earthlikebodies) > 0: bodysubtypeandname += f"\n Earth: " + ', '.join(earthlikebodies)
|
|
if len(waterbodies) > 0: bodysubtypeandname += f"\n Water: " + ', '.join(waterbodies)
|
|
if len(unknownbodies) > 0: bodysubtypeandname += f"\n Unknown: " + ', '.join(unknownbodies)
|
|
|
|
self.bodies = f"\n{lastsystem}:{bodysubtypeandname}"
|
|
|
|
|
|
def check_range(self, name, index, mode):
|
|
value = self.range_entry.var.get()
|
|
if value.__len__() > 0 and value != self.range_entry.placeholder:
|
|
try:
|
|
float(value)
|
|
self.range_entry.set_error_style(False)
|
|
self.hide_error()
|
|
except ValueError:
|
|
self.show_error("Invalid range")
|
|
self.range_entry.set_error_style()
|
|
|
|
def cleanup_old_version(self):
|
|
try:
|
|
if (os.path.exists(os.path.join(self.plugin_dir, "AutoCompleter.py"))
|
|
and os.path.exists(os.path.join(self.plugin_dir, "SpanshRouter"))):
|
|
files_list = os.listdir(self.plugin_dir)
|
|
|
|
for filename in files_list:
|
|
if (filename != "load.py"
|
|
and (filename.endswith(".py") or filename.endswith(".pyc") or filename.endswith(".pyo"))):
|
|
os.remove(os.path.join(self.plugin_dir, filename))
|
|
except:
|
|
exc_type, exc_value, exc_traceback = sys.exc_info()
|
|
lines = traceback.format_exception(exc_type, exc_value, exc_traceback)
|
|
logger.warning(''.join('!! ' + line for line in lines))
|
|
|
|
def check_for_update(self):
|
|
return # Autoupdates is disabled
|
|
self.cleanup_old_version()
|
|
version_url = "https://raw.githubusercontent.com/CMDR-Kiel42/EDMC_SpanshRouter/master/version.json"
|
|
try:
|
|
response = requests.get(version_url, timeout=2)
|
|
if response.status_code == 200:
|
|
if self.plugin_version != response.text:
|
|
self.update_available = True
|
|
self.spansh_updater = SpanshUpdater(response.text, self.plugin_dir)
|
|
|
|
else:
|
|
logger.warning(f"Could not query latest SpanshRouter version, code: {str(response.status_code)}; text: {response.text}")
|
|
except:
|
|
exc_type, exc_value, exc_traceback = sys.exc_info()
|
|
lines = traceback.format_exception(exc_type, exc_value, exc_traceback)
|
|
logger.warning(''.join('!! ' + line for line in lines))
|
|
|
|
def install_update(self):
|
|
self.spansh_updater.install()
|