From abfb41787864dc726eaf959a95e50e7c01d382f0 Mon Sep 17 00:00:00 2001 From: Cat Lady Date: Thu, 11 Dec 2025 19:46:11 +0100 Subject: [PATCH] Implemented displaying LY values for "Galaxy" plotter. --- SpanshRouter/SpanshRouter.py | 148 +++++++++++++++++++++++++---------- 1 file changed, 108 insertions(+), 40 deletions(-) diff --git a/SpanshRouter/SpanshRouter.py b/SpanshRouter/SpanshRouter.py index 351aed0..c620fe2 100644 --- a/SpanshRouter/SpanshRouter.py +++ b/SpanshRouter/SpanshRouter.py @@ -606,41 +606,47 @@ class SpanshRouter(): 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: + 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 + route_reader = csv.DictReader(csvfile) + + # Get column header names as string (preserve order) + headerline = ','.join(route_reader.fieldnames) if route_reader.fieldnames else "" + + # Define the different 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 + # External/other import formats 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,Tritium in tank,Tritium in market,Fuel Used,Icy Ring,Pristine,Restock Tritium" galaxyimportheader = "System Name,Distance,Distance Remaining,Fuel Left,Fuel Used,Refuel,Neutron Star" - # Handle plain CSVs (two variants) and the neutron-import format separately. + # Helper to check and pick distance keys flexibly + def get_distance_fields(row): + # prefer "Distance To Arrival" (neutron style), fall back to "Distance" + dist_to_arrival = row.get("Distance To Arrival", "") or row.get("Distance", "") + dist_remaining = row.get("Distance Remaining", "") or "" + return dist_to_arrival, dist_remaining + + # Handle neutron-import style if headerline == neutronimportheader: # CSV has: System Name,Distance To Arrival,Distance Remaining,Neutron Star,Jumps for row in route_reader: if row not in (None, "", []): - # Keep index 1 as "jumps" for compatibility with existing code, - # but also store distance_to_arrival and distance_remaining at indexes 2 and 3. jumps = row.get(self.jumps_header, "") dist_to_arrival = row.get("Distance To Arrival", "") dist_remaining = row.get("Distance Remaining", "") + # keep index 1 compatible with existing code (jumps) self.route.append([ row[self.system_header], jumps, @@ -653,23 +659,26 @@ class SpanshRouter(): except: pass + # legacy/simple CSV: System Name [, Jumps] elif (headerline == internalbasicheader1) or (headerline == internalbasicheader2): - # legacy/simple CSV: System Name [, Jumps] 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 + 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]) + if row.get(self.jumps_header): # Jumps column is optional + try: + self.jumps_left += int(row[self.jumps_header]) + except: + pass + # internal riches format (lists stored as Python-likes) 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]) @@ -679,8 +688,12 @@ class SpanshRouter(): bodynames, bodysubtypes ]) - self.jumps_left += int(row[self.jumps_header]) + try: + self.jumps_left += int(row[self.jumps_header]) + except: + pass + # internal fleetcarrier format elif headerline == internalfleetcarrierheader: self.fleetcarrier = True @@ -691,8 +704,12 @@ class SpanshRouter(): row[self.jumps_header], row[self.restocktritium_header] ]) - self.jumps_left += int(row[self.jumps_header]) + try: + self.jumps_left += int(row[self.jumps_header]) + except: + pass + # road2riches import elif headerline == road2richesimportheader: self.roadtoriches = True @@ -723,29 +740,75 @@ class SpanshRouter(): bodynames.clear() bodysubtypes.clear() - self.jumps_left += int(row[self.jumps_header]) + try: + self.jumps_left += int(row[self.jumps_header]) + except: + pass + # external fleetcarrier import elif headerline == fleetcarrierimportheader: self.fleetcarrier = True for row in route_reader: if row not in (None, "", []): + # treat every row as one jump in this import format self.route.append([ row[self.system_header], - 1, # Jumps is faked as every row is 1 jump - row[self.restocktritium_header] + 1, # Jumps is faked as every row is 1 jump + row.get(self.restocktritium_header, "") ]) - self.jumps_left += 1 # Jumps is faked as every row is 1 jump - elif (headerline == internalgalaxyheader) or (headerline == galaxyimportheader): + self.jumps_left += 1 + + # galaxy-type imports: handle both the long upstream format + # and the simpler internal 'System Name,Refuel' header. + # We detect galaxy files by presence of "Refuel" in headerline. + elif "Refuel" in headerline and self.system_header in headerline: 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 + system = row.get(self.system_header, "") + # keep route[i][1] as Refuel so existing galaxy logic keeps working + refuel = row.get(self.refuel_header, "") + # attempt to read distances if present (either "Distance" or "Distance To Arrival") + dist_to_arrival, dist_remaining = get_distance_fields(row) + + # Build route row: + # - always keep [system, refuel] (so update_route / pleaserefuel continue to work) + # - append distances if we have them so compute_distances can display LY + route_row = [system, refuel] + if dist_to_arrival != "" or dist_remaining != "": + route_row.append(dist_to_arrival) + route_row.append(dist_remaining) + + self.route.append(route_row) + + # galaxy logic: treat each row as one jump for jump counting + try: + self.jumps_left += 1 + except: + self.jumps_left += 1 + + else: + # Unknown header: try a best-effort basic parse as System Name,Jumps + for row in route_reader: + if row not in (None, "", []): + # fallback to first two columns if possible + try: + system = row.get(self.system_header, "") + jumps = row.get(self.jumps_header, "") + except Exception: + # as ultimate fallback, iterate values + vals = list(row.values()) + system = vals[0] if len(vals) > 0 else "" + jumps = vals[1] if len(vals) > 1 else "" + self.route.append([system, jumps]) + if jumps: + try: + self.jumps_left += int(jumps) + except: + pass + # After loading route, initialize current waypoint if len(self.route) > 0: self.offset = 0 @@ -753,7 +816,6 @@ class SpanshRouter(): # Compute LY distances immediately (so GUI shows them before pressing arrows) self.compute_distances() self.update_gui() - else: self.show_error("Could not detect file format") @@ -988,6 +1050,12 @@ class SpanshRouter(): self.save_offset() def save_route(self): + """Save current route to disk. + + For galaxy routes we write System Name,Refuel,Distance To Arrival,Distance Remaining + so that distances are preserved across restarts and LY display works like for neutron. + Other formats keep previous behaviour. + """ # If no route, try to remove saved files and exit if len(self.route) == 0: try: @@ -996,13 +1064,6 @@ class SpanshRouter(): logger.info("No route to delete") return - # Decide which format to write - # 1) roadtoriches -> write System, Jumps, Body Name, Body Subtype (lists) - # 2) fleetcarrier -> write System, Jumps, Restock Tritium - # 3) galaxy -> write System, Refuel - # 4) if any row has >=4 elements -> write neutron-import style with Distance To Arrival and Distance Remaining - # 5) else -> simple System, Jumps - try: if self.roadtoriches: fieldnames = [self.system_header, self.jumps_header, self.bodyname_header, self.bodysubtype_header] @@ -1023,11 +1084,19 @@ class SpanshRouter(): return if self.galaxy: - fieldnames = [self.system_header, self.refuel_header] + # Save galaxy routes including distances if present: + # Write header so reload can restore distances: System Name,Refuel,Distance To Arrival,Distance Remaining + fieldnames = [self.system_header, self.refuel_header, "Distance To Arrival", "Distance Remaining"] with open(self.save_route_path, 'w', newline='') as csvfile: writer = csv.writer(csvfile) writer.writerow(fieldnames) - writer.writerows(self.route) + for row in self.route: + # normalize row into 4 columns: + system = row[0] if len(row) > 0 else "" + refuel = row[1] if len(row) > 1 else "" + dist_to_arrival = row[2] if len(row) > 2 else "" + dist_remaining = row[3] if len(row) > 3 else "" + writer.writerow([system, refuel, dist_to_arrival, dist_remaining]) return # Generic routes: check if any row contains distance columns (len >= 4) @@ -1063,7 +1132,6 @@ class SpanshRouter(): logger.warning(''.join('!! ' + line for line in lines)) self.show_error("An error occured while writing the route to disk.") - def save_offset(self): if self.route.__len__() != 0: with open(self.offset_file_path, 'w') as offset_fh: