From ab22246f5444c2f33af85eb6dce7b40c73af6376 Mon Sep 17 00:00:00 2001 From: VALLONGOL Date: Mon, 3 Nov 2025 11:26:55 +0100 Subject: [PATCH] sistemata preview della traiettoria simulata in editing --- settings.json | 2 +- target_simulator/core/simulation_engine.py | 41 ++++++++++++++++++- target_simulator/gui/ppi_display.py | 46 ++++++++++++++-------- 3 files changed, 71 insertions(+), 18 deletions(-) diff --git a/settings.json b/settings.json index fbb94c9..ace6158 100644 --- a/settings.json +++ b/settings.json @@ -3,7 +3,7 @@ "scan_limit": 60, "max_range": 100, "geometry": "1599x1089+587+179", - "last_selected_scenario": "scenario1", + "last_selected_scenario": "scenario_dritto", "connection": { "target": { "type": "sfp", diff --git a/target_simulator/core/simulation_engine.py b/target_simulator/core/simulation_engine.py index bea223f..52fbba8 100644 --- a/target_simulator/core/simulation_engine.py +++ b/target_simulator/core/simulation_engine.py @@ -28,6 +28,7 @@ class SimulationEngine(threading.Thread): communicator: Optional[CommunicatorInterface], simulation_hub: Optional[SimulationStateHub] = None, archive: Optional[str] = None, + update_queue: Optional[Queue] = None, ): super().__init__(daemon=True, name="SimulationEngineThread") self.logger = get_logger(__name__) @@ -35,13 +36,19 @@ class SimulationEngine(threading.Thread): self.communicator = communicator # Backwards-compat: older callers passed an update_queue as the # second positional argument. Detect a Queue and treat it as the - # update queue while leaving simulation_hub unset. + # update queue while leaving simulation_hub unset. Also accept an + # explicit `update_queue` keyword argument for newer callers. self.update_queue: Optional[Queue] = None if isinstance(simulation_hub, Queue): self.update_queue = simulation_hub self.simulation_hub = None else: self.simulation_hub = simulation_hub # Hub for data analysis + + # If caller provided update_queue explicitly, prefer it (overrides + # any queue accidentally passed in simulation_hub position). + if isinstance(update_queue, Queue): + self.update_queue = update_queue self.archive = archive # Archive path if needed self.time_multiplier = 1.0 self.update_interval_s = 1.0 @@ -149,12 +156,44 @@ class SimulationEngine(threading.Thread): if self.scenario.is_finished(): self.logger.info("Scenario finished. Stopping engine.") + # Notify any GUI preview consumers that the simulation finished + try: + if self.update_queue: + try: + self.update_queue.put_nowait("SIMULATION_FINISHED") + except Exception: + # fallback to blocking put to ensure delivery + try: + self.update_queue.put("SIMULATION_FINISHED") + except Exception: + pass + except Exception: + pass break # --- Throttled Communication Step --- if (current_time - self._last_update_time) >= self.update_interval_s: self._last_update_time = current_time + # If an update_queue is provided (e.g., preview mode), send the + # current simulated targets to the queue so the UI can render + # them even when no communicator is present. + if self.update_queue: + try: + # Send a shallow list of target objects (UI will copy if needed) + targets_snapshot = [t for t in self.scenario.get_all_targets() if t.active] + try: + self.update_queue.put_nowait(targets_snapshot) + except Exception: + # fallback to blocking put + try: + self.update_queue.put(targets_snapshot) + except Exception: + pass + except Exception: + # Don't let queue failures stop the engine + pass + if self.communicator and self.communicator.is_open: commands_to_send = [] diff --git a/target_simulator/gui/ppi_display.py b/target_simulator/gui/ppi_display.py index ad67521..8a908b3 100644 --- a/target_simulator/gui/ppi_display.py +++ b/target_simulator/gui/ppi_display.py @@ -97,7 +97,11 @@ class PPIDisplay(ttk.Frame): top_frame.pack(side=tk.TOP, fill=tk.X, padx=5, pady=5) self.controls_frame = ttk.Frame(top_frame) self.controls_frame.pack(side=tk.LEFT, fill=tk.X, expand=True) - ttk.Label(self.controls_frame, text="Range (NM):").pack(side=tk.LEFT) + # Use grid inside controls_frame so we can stack the antenna animation + # toggle immediately below the range selector as requested. + ttk.Label(self.controls_frame, text="Range (NM):").grid( + row=0, column=0, sticky="w" + ) all_steps = [10, 20, 40, 80, 100, 160, 240, 320] valid_steps = sorted([s for s in all_steps if s <= self.max_range]) if not valid_steps or self.max_range not in valid_steps: @@ -111,7 +115,17 @@ class PPIDisplay(ttk.Frame): state="readonly", width=5, ) - self.range_selector.pack(side=tk.LEFT, padx=5) + self.range_selector.grid(row=0, column=1, padx=5, sticky="w") + # Place the antenna animate toggle directly under the range selector + # to match the requested layout: range combobox, then antenna flag. + cb_antenna_local = ttk.Checkbutton( + self.controls_frame, + text="Animate Antenna", + variable=self.animate_antenna_var, + command=self._on_antenna_animate_changed, + ) + cb_antenna_local.grid(row=1, column=0, columnspan=2, sticky="w", pady=(4, 0)) + options_frame = ttk.LabelFrame(top_frame, text="Display Options") options_frame.pack(side=tk.RIGHT, padx=(10, 0)) cb_sim_points = ttk.Checkbutton( @@ -142,24 +156,24 @@ class PPIDisplay(ttk.Frame): command=self._on_display_options_changed, ) cb_real_trail.grid(row=1, column=1, sticky="w", padx=5) - # Antenna animate toggle (single Checkbutton) - cb_antenna = ttk.Checkbutton( - options_frame, - text="Animate Antenna", - variable=self.animate_antenna_var, - command=self._on_antenna_animate_changed, - ) - cb_antenna.grid(row=2, column=0, columnspan=2, sticky="w", padx=5, pady=(6, 0)) + # Legend: stack simulated and real swatches vertically to reduce width legend_frame = ttk.Frame(top_frame) legend_frame.pack(side=tk.RIGHT, padx=(10, 5)) - sim_sw = tk.Canvas(legend_frame, width=16, height=12, highlightthickness=0) + # Use grid so items are stacked + sim_sw = tk.Canvas( + legend_frame, width=16, height=12, highlightthickness=0 + ) sim_sw.create_rectangle(0, 0, 16, 12, fill="green", outline="black") - sim_sw.pack(side=tk.LEFT, padx=(0, 4)) - ttk.Label(legend_frame, text="Simulated").pack(side=tk.LEFT, padx=(0, 8)) - real_sw = tk.Canvas(legend_frame, width=16, height=12, highlightthickness=0) + sim_sw.grid(row=0, column=0, padx=(0, 4), pady=(0, 2)) + ttk.Label(legend_frame, text="Simulated").grid( + row=0, column=1, sticky="w", padx=(0, 8), pady=(0, 2) + ) + real_sw = tk.Canvas( + legend_frame, width=16, height=12, highlightthickness=0 + ) real_sw.create_rectangle(0, 0, 16, 12, fill="red", outline="black") - real_sw.pack(side=tk.LEFT, padx=(2, 4)) - ttk.Label(legend_frame, text="Real").pack(side=tk.LEFT) + real_sw.grid(row=1, column=0, padx=(0, 4)) + ttk.Label(legend_frame, text="Real").grid(row=1, column=1, sticky="w", padx=(0, 8)) def _create_plot(self): fig = Figure(figsize=(5, 5), dpi=100, facecolor="#3E3E3E")