sistemata preview della traiettoria simulata in editing

This commit is contained in:
VALLONGOL 2025-11-03 11:26:55 +01:00
parent 8a4f621f0c
commit ab22246f54
3 changed files with 71 additions and 18 deletions

View File

@ -3,7 +3,7 @@
"scan_limit": 60, "scan_limit": 60,
"max_range": 100, "max_range": 100,
"geometry": "1599x1089+587+179", "geometry": "1599x1089+587+179",
"last_selected_scenario": "scenario1", "last_selected_scenario": "scenario_dritto",
"connection": { "connection": {
"target": { "target": {
"type": "sfp", "type": "sfp",

View File

@ -28,6 +28,7 @@ class SimulationEngine(threading.Thread):
communicator: Optional[CommunicatorInterface], communicator: Optional[CommunicatorInterface],
simulation_hub: Optional[SimulationStateHub] = None, simulation_hub: Optional[SimulationStateHub] = None,
archive: Optional[str] = None, archive: Optional[str] = None,
update_queue: Optional[Queue] = None,
): ):
super().__init__(daemon=True, name="SimulationEngineThread") super().__init__(daemon=True, name="SimulationEngineThread")
self.logger = get_logger(__name__) self.logger = get_logger(__name__)
@ -35,13 +36,19 @@ class SimulationEngine(threading.Thread):
self.communicator = communicator self.communicator = communicator
# Backwards-compat: older callers passed an update_queue as the # Backwards-compat: older callers passed an update_queue as the
# second positional argument. Detect a Queue and treat it 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 self.update_queue: Optional[Queue] = None
if isinstance(simulation_hub, Queue): if isinstance(simulation_hub, Queue):
self.update_queue = simulation_hub self.update_queue = simulation_hub
self.simulation_hub = None self.simulation_hub = None
else: else:
self.simulation_hub = simulation_hub # Hub for data analysis 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.archive = archive # Archive path if needed
self.time_multiplier = 1.0 self.time_multiplier = 1.0
self.update_interval_s = 1.0 self.update_interval_s = 1.0
@ -149,12 +156,44 @@ class SimulationEngine(threading.Thread):
if self.scenario.is_finished(): if self.scenario.is_finished():
self.logger.info("Scenario finished. Stopping engine.") 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 break
# --- Throttled Communication Step --- # --- Throttled Communication Step ---
if (current_time - self._last_update_time) >= self.update_interval_s: if (current_time - self._last_update_time) >= self.update_interval_s:
self._last_update_time = current_time 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: if self.communicator and self.communicator.is_open:
commands_to_send = [] commands_to_send = []

View File

@ -97,7 +97,11 @@ class PPIDisplay(ttk.Frame):
top_frame.pack(side=tk.TOP, fill=tk.X, padx=5, pady=5) top_frame.pack(side=tk.TOP, fill=tk.X, padx=5, pady=5)
self.controls_frame = ttk.Frame(top_frame) self.controls_frame = ttk.Frame(top_frame)
self.controls_frame.pack(side=tk.LEFT, fill=tk.X, expand=True) 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] all_steps = [10, 20, 40, 80, 100, 160, 240, 320]
valid_steps = sorted([s for s in all_steps if s <= self.max_range]) 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: if not valid_steps or self.max_range not in valid_steps:
@ -111,7 +115,17 @@ class PPIDisplay(ttk.Frame):
state="readonly", state="readonly",
width=5, 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 = ttk.LabelFrame(top_frame, text="Display Options")
options_frame.pack(side=tk.RIGHT, padx=(10, 0)) options_frame.pack(side=tk.RIGHT, padx=(10, 0))
cb_sim_points = ttk.Checkbutton( cb_sim_points = ttk.Checkbutton(
@ -142,24 +156,24 @@ class PPIDisplay(ttk.Frame):
command=self._on_display_options_changed, command=self._on_display_options_changed,
) )
cb_real_trail.grid(row=1, column=1, sticky="w", padx=5) cb_real_trail.grid(row=1, column=1, sticky="w", padx=5)
# Antenna animate toggle (single Checkbutton) # Legend: stack simulated and real swatches vertically to reduce width
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_frame = ttk.Frame(top_frame) legend_frame = ttk.Frame(top_frame)
legend_frame.pack(side=tk.RIGHT, padx=(10, 5)) 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.create_rectangle(0, 0, 16, 12, fill="green", outline="black")
sim_sw.pack(side=tk.LEFT, padx=(0, 4)) sim_sw.grid(row=0, column=0, padx=(0, 4), pady=(0, 2))
ttk.Label(legend_frame, text="Simulated").pack(side=tk.LEFT, padx=(0, 8)) ttk.Label(legend_frame, text="Simulated").grid(
real_sw = tk.Canvas(legend_frame, width=16, height=12, highlightthickness=0) 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.create_rectangle(0, 0, 16, 12, fill="red", outline="black")
real_sw.pack(side=tk.LEFT, padx=(2, 4)) real_sw.grid(row=1, column=0, padx=(0, 4))
ttk.Label(legend_frame, text="Real").pack(side=tk.LEFT) ttk.Label(legend_frame, text="Real").grid(row=1, column=1, sticky="w", padx=(0, 8))
def _create_plot(self): def _create_plot(self):
fig = Figure(figsize=(5, 5), dpi=100, facecolor="#3E3E3E") fig = Figure(figsize=(5, 5), dpi=100, facecolor="#3E3E3E")