diff --git a/target_simulator/gui/sfp_debug_window.py b/target_simulator/gui/sfp_debug_window.py index bf89267..4559380 100644 --- a/target_simulator/gui/sfp_debug_window.py +++ b/target_simulator/gui/sfp_debug_window.py @@ -161,6 +161,26 @@ class SfpDebugWindow(tk.Toplevel): # Save CSV button under the paned window btn_frame = ttk.Frame(ris_frame) btn_frame.pack(fill=tk.X, padx=5, pady=(0, 5)) + # View mode for scenario/targets: raw vs simplified + self.scenario_view_mode = tk.StringVar(value="simplified") + mode_frame = ttk.Frame(btn_frame) + mode_frame.pack(side=tk.LEFT, padx=(4, 0)) + ttk.Label(mode_frame, text="View:").pack(side=tk.LEFT, padx=(0, 6)) + ttk.Radiobutton(mode_frame, text="Raw", value="raw", variable=self.scenario_view_mode).pack(side=tk.LEFT) + ttk.Radiobutton(mode_frame, text="Simplified", value="simplified", variable=self.scenario_view_mode).pack(side=tk.LEFT) + + # Decimals control for simplified view (user-settable) + self.simplified_decimals = tk.IntVar(value=4) + ttk.Label(mode_frame, text=" Decimals:").pack(side=tk.LEFT, padx=(8, 2)) + try: + # Use tk.Spinbox; ttk.Spinbox may not be available in all tkinter versions + sp = tk.Spinbox(mode_frame, from_=0, to=8, width=3, textvariable=self.simplified_decimals) + sp.pack(side=tk.LEFT) + except Exception: + # fallback to an Entry if Spinbox is not supported + e = ttk.Entry(mode_frame, textvariable=self.simplified_decimals, width=3) + e.pack(side=tk.LEFT) + self.ris_save_csv_btn = ttk.Button(btn_frame, text="Save CSV", command=lambda: self._on_save_ris_csv()) self.ris_save_csv_btn.pack(side=tk.RIGHT) @@ -596,11 +616,6 @@ class SfpDebugWindow(tk.Toplevel): self.scenario_tree.delete(iid) scenario = struct.get("scenario", {}) if isinstance(struct, dict) else {} if scenario: - # Insert in deterministic order and convert to human-friendly units - # Conversions inferred from C++ implementation: - # - angles are sent in radians -> display degrees - # - velocities are m/s -> display ft/s - # - altitudes are meters -> display feet import math def to_deg(v): @@ -636,101 +651,162 @@ class SfpDebugWindow(tk.Toplevel): ("longitude", "longitude", "°"), ("true_heading", "true_heading", "°"), ] + + view_mode = self.scenario_view_mode.get() if hasattr(self, "scenario_view_mode") else "simplified" + dec_simp = int(self.simplified_decimals.get() if hasattr(self, "simplified_decimals") else 4) + + def fmt_raw_number(v, key_name=None): + try: + fv = float(v) + if key_name in ("latitude", "longitude"): + return f"{fv:.8f}" + return f"{fv:.6f}" + except Exception: + return str(v) + + def fmt_simplified_number(v, unit_str, decimals=4): + try: + fv = float(v) + return f"{fv:.{decimals}f} {unit_str}" if unit_str else f"{fv:.{decimals}f}" + except Exception: + return str(v) + for label, key, unit in order: if key in scenario: val = scenario.get(key) - # apply conversions and show raw in parentheses - if key == "platform_azimuth" or key == "true_heading": - if isinstance(val, (int, float)): - conv = to_deg(val) - display_val = f"{conv:.6f} ° ({val:.6f} rad)" + if view_mode == "raw": + if key in ("platform_azimuth", "true_heading", "ant_nav_az", "ant_nav_el"): + display_val = fmt_raw_number(val, key) + elif key in ("vx", "vy", "vz", "baro_altitude"): + display_val = fmt_raw_number(val, key) + elif key in ("latitude", "longitude"): + display_val = fmt_raw_number(val, key) + elif key == "flags": + try: + display_val = f"{int(val)} (0x{int(val):X})" + except Exception: + display_val = str(val) else: - display_val = val - elif key in ("ant_nav_az", "ant_nav_el"): - # antenna angles, display degrees with raw radians - if isinstance(val, (int, float)): - conv = to_deg(val) - display_val = f"{conv:.6f} ° ({val:.6f} rad)" - else: - display_val = val - elif key == "flags": - # show flags as int and hex - try: - display_val = f"{int(val)} (0x{int(val):X})" - except Exception: display_val = str(val) - elif key == "mode": - # mode is an integer code - try: - display_val = str(int(val)) - except Exception: - display_val = str(val) - elif key in ("vx", "vy", "vz"): - if isinstance(val, (int, float)): - conv = m_s_to_ft_s(val) - display_val = f"{conv:.3f} ft/s ({val:.3f} m/s)" - else: - display_val = val - elif key == "baro_altitude": - if isinstance(val, (int, float)): - conv = m_to_ft(val) - display_val = f"{conv:.3f} ft ({val:.3f} m)" - else: - display_val = val - elif key in ("latitude", "longitude"): - if isinstance(val, (int, float)): - # Show decimal degrees and DMS (alt display) - dd = float(val) - deg = int(dd) - md = abs((dd - deg) * 60) - minutes = int(md) - seconds = (md - minutes) * 60 - dms = f"{deg}°{minutes:02d}'{seconds:.3f}\"" - display_val = f"{dd:.9f} ° ({dms})" - else: - display_val = val else: - display_val = val - self.scenario_tree.insert("", tk.END, values=(f"{label} {('('+unit+')' if unit else '')}", display_val)) + # simplified view: show converted value and unit adjacent to number + if key in ("platform_azimuth", "true_heading"): + if isinstance(val, (int, float)): + conv = to_deg(val) + display_val = fmt_simplified_number(conv, "°", dec_simp) + else: + display_val = str(val) + elif key in ("ant_nav_az", "ant_nav_el"): + if isinstance(val, (int, float)): + conv = to_deg(val) + display_val = fmt_simplified_number(conv, "°", dec_simp) + else: + display_val = str(val) + elif key in ("vx", "vy", "vz"): + if isinstance(val, (int, float)): + conv = m_s_to_ft_s(val) + display_val = fmt_simplified_number(conv, "ft/s", dec_simp) + else: + display_val = str(val) + elif key == "baro_altitude": + if isinstance(val, (int, float)): + conv = m_to_ft(val) + display_val = fmt_simplified_number(conv, "ft", dec_simp) + else: + display_val = str(val) + elif key in ("latitude", "longitude"): + if isinstance(val, (int, float)): + # show decimal degrees with higher precision per request + display_val = f"{float(val):.{8}f} °" + else: + display_val = str(val) + elif key == "flags": + try: + display_val = f"{int(val)} (0x{int(val):X})" + except Exception: + display_val = str(val) + elif key == "mode": + display_val = str(int(val)) if isinstance(val, (int, float)) else str(val) + else: + display_val = str(val) + + # Show label without unit; unit is appended to the value in simplified view + self.scenario_tree.insert("", tk.END, values=(f"{label}", display_val)) # targets for iid in self.ris_tree.get_children(): self.ris_tree.delete(iid) targets = struct.get("targets", []) if isinstance(struct, dict) else [] - # Update target column headers to show units + # Update target column headers to show units depending on view try: - self.ris_tree.heading("heading", text="heading (°)") - self.ris_tree.heading("x", text="x (m)") - self.ris_tree.heading("y", text="y (m)") - self.ris_tree.heading("z", text="z (ft)") + view_mode = self.scenario_view_mode.get() if hasattr(self, "scenario_view_mode") else "simplified" + # Column headers should be plain; units shown next to values + self.ris_tree.heading("heading", text="heading") + self.ris_tree.heading("x", text="x") + self.ris_tree.heading("y", text="y") + self.ris_tree.heading("z", text="z") except Exception: pass + view_mode = self.scenario_view_mode.get() if hasattr(self, "scenario_view_mode") else "simplified" + dec_simp = int(self.simplified_decimals.get() if hasattr(self, "simplified_decimals") else 4) for t in targets: - # Convert heading (rad->deg) and z (m->ft) - try: - heading = float(t.get("heading")) - heading_deg = heading * (180.0 / 3.141592653589793) - except Exception: - heading_deg = t.get("heading") - try: - x = float(t.get("x")) - y = float(t.get("y")) - z_m = float(t.get("z")) - z_ft = z_m * 3.280839895 - except Exception: - x = t.get("x") - y = t.get("y") - z_ft = t.get("z") + if view_mode == "raw": + # format raw with reasonable precision + try: + heading_raw = float(t.get("heading")) + heading_val = f"{heading_raw:.6f}" + except Exception: + heading_val = str(t.get("heading")) + try: + x_raw = float(t.get("x")) + y_raw = float(t.get("y")) + z_raw = float(t.get("z")) + x_val = f"{x_raw:.6f}" + y_val = f"{y_raw:.6f}" + z_val = f"{z_raw:.6f}" + except Exception: + x_val = t.get("x") + y_val = t.get("y") + z_val = t.get("z") + + vals = ( + t.get("index"), + t.get("flags"), + heading_val, + x_val, + y_val, + z_val, + ) + else: + # simplified: converted values with units next to number + try: + heading = float(t.get("heading")) + heading_deg = heading * (180.0 / 3.141592653589793) + heading_val = f"{heading_deg:.{dec_simp}f} °" + except Exception: + heading_val = str(t.get("heading")) + try: + x = float(t.get("x")) + y = float(t.get("y")) + z_m = float(t.get("z")) + x_val = f"{x:.{dec_simp}f} m" + y_val = f"{y:.{dec_simp}f} m" + z_val = f"{(z_m * 3.280839895):.{dec_simp}f} ft" + except Exception: + x_val = str(t.get("x")) + y_val = str(t.get("y")) + z_val = str(t.get("z")) + + vals = ( + t.get("index"), + t.get("flags"), + heading_val, + x_val, + y_val, + z_val, + ) - vals = ( - t.get("index"), - t.get("flags"), - f"{heading_deg:.3f} ° ({t.get('heading')})", - f"{x:.3f} m ({t.get('x')})", - f"{y:.3f} m ({t.get('y')})", - f"{z_ft:.3f} ft ({t.get('z')})", - ) self.ris_tree.insert("", tk.END, values=vals) except Exception: # ignore malformed JSON for now