aggiunto le tre componenti delle accelerazioni e sistemata la preview nella simulazione
This commit is contained in:
parent
7eb4a7379e
commit
d899c04ba8
@ -201,7 +201,7 @@
|
|||||||
{
|
{
|
||||||
"maneuver_type": "Fly to Point",
|
"maneuver_type": "Fly to Point",
|
||||||
"duration_s": 1.0,
|
"duration_s": 1.0,
|
||||||
"target_range_nm": 80.0,
|
"target_range_nm": 28.0,
|
||||||
"target_azimuth_deg": 0.0,
|
"target_azimuth_deg": 0.0,
|
||||||
"target_altitude_ft": 10000.0,
|
"target_altitude_ft": 10000.0,
|
||||||
"target_velocity_fps": 506.343,
|
"target_velocity_fps": 506.343,
|
||||||
@ -213,7 +213,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"maneuver_type": "Fly for Duration",
|
"maneuver_type": "Fly for Duration",
|
||||||
"duration_s": 10.0,
|
"duration_s": 300.0,
|
||||||
"target_altitude_ft": 10000.0,
|
"target_altitude_ft": 10000.0,
|
||||||
"target_velocity_fps": 506.343,
|
"target_velocity_fps": 506.343,
|
||||||
"target_heading_deg": 180.0,
|
"target_heading_deg": 180.0,
|
||||||
@ -221,6 +221,26 @@
|
|||||||
"lateral_acceleration_g": 0.0,
|
"lateral_acceleration_g": 0.0,
|
||||||
"vertical_acceleration_g": 0.0,
|
"vertical_acceleration_g": 0.0,
|
||||||
"turn_direction": "Right"
|
"turn_direction": "Right"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"maneuver_type": "Dynamic Maneuver",
|
||||||
|
"duration_s": 9.0,
|
||||||
|
"maneuver_speed_fps": 1519.0289999999995,
|
||||||
|
"longitudinal_acceleration_g": 0.0,
|
||||||
|
"lateral_acceleration_g": 9.0,
|
||||||
|
"vertical_acceleration_g": 0.0,
|
||||||
|
"turn_direction": "Right"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"maneuver_type": "Fly for Duration",
|
||||||
|
"duration_s": 100.0,
|
||||||
|
"target_altitude_ft": 10000.0,
|
||||||
|
"target_velocity_fps": 1012.686,
|
||||||
|
"target_heading_deg": -90.0,
|
||||||
|
"longitudinal_acceleration_g": 0.0,
|
||||||
|
"lateral_acceleration_g": 0.0,
|
||||||
|
"vertical_acceleration_g": 0.0,
|
||||||
|
"turn_direction": "Right"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"use_spline": false
|
"use_spline": false
|
||||||
|
|||||||
@ -15,7 +15,7 @@ MAX_TARGET_ID = 15
|
|||||||
KNOTS_TO_FPS = 1.68781
|
KNOTS_TO_FPS = 1.68781
|
||||||
FPS_TO_KNOTS = 1 / KNOTS_TO_FPS
|
FPS_TO_KNOTS = 1 / KNOTS_TO_FPS
|
||||||
NM_TO_FT = 6076.12
|
NM_TO_FT = 6076.12
|
||||||
G_IN_FPS2 = 32.174 # Standard gravity in ft/s^2
|
G_IN_FPS2 = 32.174
|
||||||
|
|
||||||
class ManeuverType(Enum):
|
class ManeuverType(Enum):
|
||||||
FLY_TO_POINT = "Fly to Point"
|
FLY_TO_POINT = "Fly to Point"
|
||||||
@ -31,39 +31,32 @@ class Waypoint:
|
|||||||
"""Represents a segment of a target's trajectory, defining a maneuver."""
|
"""Represents a segment of a target's trajectory, defining a maneuver."""
|
||||||
maneuver_type: ManeuverType = ManeuverType.FLY_FOR_DURATION
|
maneuver_type: ManeuverType = ManeuverType.FLY_FOR_DURATION
|
||||||
|
|
||||||
# --- Common Parameters ---
|
|
||||||
duration_s: Optional[float] = 10.0
|
duration_s: Optional[float] = 10.0
|
||||||
|
|
||||||
# --- Parameters for FLY_TO_POINT ---
|
|
||||||
target_range_nm: Optional[float] = None
|
target_range_nm: Optional[float] = None
|
||||||
target_azimuth_deg: Optional[float] = None
|
target_azimuth_deg: Optional[float] = None
|
||||||
target_altitude_ft: Optional[float] = None
|
target_altitude_ft: Optional[float] = None
|
||||||
|
|
||||||
# --- Parameters for FLY_FOR_DURATION (constant state) & FLY_TO_POINT (final state) ---
|
|
||||||
target_velocity_fps: Optional[float] = None
|
target_velocity_fps: Optional[float] = None
|
||||||
target_heading_deg: Optional[float] = None
|
target_heading_deg: Optional[float] = None
|
||||||
|
|
||||||
# --- Parameters for DYNAMIC_MANEUVER ---
|
maneuver_speed_fps: Optional[float] = None
|
||||||
longitudinal_acceleration_g: Optional[float] = 0.0
|
longitudinal_acceleration_g: Optional[float] = 0.0
|
||||||
lateral_acceleration_g: Optional[float] = 0.0
|
lateral_acceleration_g: Optional[float] = 0.0
|
||||||
vertical_acceleration_g: Optional[float] = 0.0
|
vertical_acceleration_g: Optional[float] = 0.0
|
||||||
turn_direction: Optional[TurnDirection] = TurnDirection.RIGHT
|
turn_direction: Optional[TurnDirection] = TurnDirection.RIGHT
|
||||||
|
|
||||||
def to_dict(self) -> Dict:
|
def to_dict(self) -> Dict:
|
||||||
"""Serializes the waypoint to a dictionary."""
|
|
||||||
data = {"maneuver_type": self.maneuver_type.value}
|
data = {"maneuver_type": self.maneuver_type.value}
|
||||||
for f in fields(self):
|
for f in fields(self):
|
||||||
if not f.name.startswith('_') and f.name != "maneuver_type":
|
if not f.name.startswith('_') and f.name != "maneuver_type":
|
||||||
val = getattr(self, f.name)
|
val = getattr(self, f.name)
|
||||||
if isinstance(val, Enum):
|
if isinstance(val, Enum): data[f.name] = val.value
|
||||||
data[f.name] = val.value
|
elif val is not None: data[f.name] = val
|
||||||
elif val is not None:
|
|
||||||
data[f.name] = val
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Target:
|
class Target:
|
||||||
"""Represents a dynamic 3D target with a programmable trajectory."""
|
|
||||||
target_id: int
|
target_id: int
|
||||||
trajectory: List[Waypoint] = field(default_factory=list)
|
trajectory: List[Waypoint] = field(default_factory=list)
|
||||||
active: bool = True
|
active: bool = True
|
||||||
@ -80,7 +73,6 @@ class Target:
|
|||||||
_pos_x_ft: float = field(init=False, repr=False, default=0.0)
|
_pos_x_ft: float = field(init=False, repr=False, default=0.0)
|
||||||
_pos_y_ft: float = field(init=False, repr=False, default=0.0)
|
_pos_y_ft: float = field(init=False, repr=False, default=0.0)
|
||||||
_pos_z_ft: float = field(init=False, repr=False, default=0.0)
|
_pos_z_ft: float = field(init=False, repr=False, default=0.0)
|
||||||
|
|
||||||
_sim_time_s: float = field(init=False, default=0.0)
|
_sim_time_s: float = field(init=False, default=0.0)
|
||||||
_total_duration_s: float = field(init=False, default=0.0)
|
_total_duration_s: float = field(init=False, default=0.0)
|
||||||
_path: List[Tuple[float, float, float, float]] = field(init=False, repr=False, default_factory=list)
|
_path: List[Tuple[float, float, float, float]] = field(init=False, repr=False, default_factory=list)
|
||||||
@ -93,7 +85,6 @@ class Target:
|
|||||||
def reset_simulation(self):
|
def reset_simulation(self):
|
||||||
self._sim_time_s = 0.0
|
self._sim_time_s = 0.0
|
||||||
self._generate_path()
|
self._generate_path()
|
||||||
|
|
||||||
if self._path:
|
if self._path:
|
||||||
initial_pos = self._path[0]
|
initial_pos = self._path[0]
|
||||||
self._pos_x_ft, self._pos_y_ft, self._pos_z_ft = initial_pos[1], initial_pos[2], initial_pos[3]
|
self._pos_x_ft, self._pos_y_ft, self._pos_z_ft = initial_pos[1], initial_pos[2], initial_pos[3]
|
||||||
@ -124,22 +115,17 @@ class Target:
|
|||||||
|
|
||||||
first_wp = waypoints[0]
|
first_wp = waypoints[0]
|
||||||
keyframes = []
|
keyframes = []
|
||||||
|
pos_ft = [(first_wp.target_range_nm or 0.0)*NM_TO_FT*math.sin(math.radians(first_wp.target_azimuth_deg or 0.0)),
|
||||||
pos_ft = [
|
|
||||||
(first_wp.target_range_nm or 0.0) * NM_TO_FT * math.sin(math.radians(first_wp.target_azimuth_deg or 0.0)),
|
|
||||||
(first_wp.target_range_nm or 0.0)*NM_TO_FT*math.cos(math.radians(first_wp.target_azimuth_deg or 0.0)),
|
(first_wp.target_range_nm or 0.0)*NM_TO_FT*math.cos(math.radians(first_wp.target_azimuth_deg or 0.0)),
|
||||||
first_wp.target_altitude_ft or 0.0
|
first_wp.target_altitude_ft or 0.0]
|
||||||
]
|
|
||||||
|
|
||||||
|
speed_fps = first_wp.target_velocity_fps or 0.0
|
||||||
heading_rad = math.radians(first_wp.target_heading_deg or 0.0)
|
heading_rad = math.radians(first_wp.target_heading_deg or 0.0)
|
||||||
pitch_rad = 0.0
|
pitch_rad = 0.0
|
||||||
speed_fps = first_wp.target_velocity_fps or 0.0
|
|
||||||
|
|
||||||
vel_ft_s = [
|
vel_ft_s = [speed_fps*math.cos(pitch_rad)*math.sin(heading_rad),
|
||||||
speed_fps * math.cos(pitch_rad) * math.sin(heading_rad),
|
|
||||||
speed_fps*math.cos(pitch_rad)*math.cos(heading_rad),
|
speed_fps*math.cos(pitch_rad)*math.cos(heading_rad),
|
||||||
speed_fps * math.sin(pitch_rad)
|
speed_fps*math.sin(pitch_rad)]
|
||||||
]
|
|
||||||
|
|
||||||
keyframes.append((total_duration_s, pos_ft[0], pos_ft[1], pos_ft[2]))
|
keyframes.append((total_duration_s, pos_ft[0], pos_ft[1], pos_ft[2]))
|
||||||
|
|
||||||
@ -148,13 +134,10 @@ class Target:
|
|||||||
if i == 0: continue
|
if i == 0: continue
|
||||||
|
|
||||||
if wp.maneuver_type == ManeuverType.FLY_TO_POINT:
|
if wp.maneuver_type == ManeuverType.FLY_TO_POINT:
|
||||||
start_pos = list(pos_ft)
|
start_pos, start_time = list(pos_ft), total_duration_s
|
||||||
start_time = total_duration_s
|
target_pos = [(wp.target_range_nm or 0.0)*NM_TO_FT*math.sin(math.radians(wp.target_azimuth_deg or 0.0)),
|
||||||
target_pos = [
|
|
||||||
(wp.target_range_nm or 0.0) * NM_TO_FT * math.sin(math.radians(wp.target_azimuth_deg or 0.0)),
|
|
||||||
(wp.target_range_nm or 0.0)*NM_TO_FT*math.cos(math.radians(wp.target_azimuth_deg or 0.0)),
|
(wp.target_range_nm or 0.0)*NM_TO_FT*math.cos(math.radians(wp.target_azimuth_deg or 0.0)),
|
||||||
wp.target_altitude_ft or pos_ft[2]
|
wp.target_altitude_ft or pos_ft[2]]
|
||||||
]
|
|
||||||
time_step, num_steps = 0.1, max(1, int(duration / 0.1))
|
time_step, num_steps = 0.1, max(1, int(duration / 0.1))
|
||||||
for step in range(1, num_steps + 1):
|
for step in range(1, num_steps + 1):
|
||||||
progress = step / num_steps
|
progress = step / num_steps
|
||||||
@ -163,28 +146,41 @@ class Target:
|
|||||||
keyframes.append((current_time, pos_ft[0], pos_ft[1], pos_ft[2]))
|
keyframes.append((current_time, pos_ft[0], pos_ft[1], pos_ft[2]))
|
||||||
total_duration_s += duration
|
total_duration_s += duration
|
||||||
if wp.target_velocity_fps is not None and wp.target_heading_deg is not None:
|
if wp.target_velocity_fps is not None and wp.target_heading_deg is not None:
|
||||||
heading_rad = math.radians(wp.target_heading_deg)
|
|
||||||
speed_fps = wp.target_velocity_fps
|
speed_fps = wp.target_velocity_fps
|
||||||
|
heading_rad = math.radians(wp.target_heading_deg)
|
||||||
vel_ft_s = [speed_fps * math.sin(heading_rad), speed_fps * math.cos(heading_rad), 0]
|
vel_ft_s = [speed_fps * math.sin(heading_rad), speed_fps * math.cos(heading_rad), 0]
|
||||||
|
|
||||||
elif wp.maneuver_type == ManeuverType.FLY_FOR_DURATION:
|
elif wp.maneuver_type == ManeuverType.FLY_FOR_DURATION:
|
||||||
speed_fps = wp.target_velocity_fps if wp.target_velocity_fps is not None else speed_fps
|
speed_fps = wp.target_velocity_fps if wp.target_velocity_fps is not None else speed_fps
|
||||||
heading_rad = math.radians(wp.target_heading_deg) if wp.target_heading_deg is not None else heading_rad
|
heading_rad = math.radians(wp.target_heading_deg) if wp.target_heading_deg is not None else heading_rad
|
||||||
|
|
||||||
dist_moved = speed_fps * duration
|
dist_moved = speed_fps * duration
|
||||||
pos_ft[0] += dist_moved * math.sin(heading_rad)
|
pos_ft[0] += dist_moved * math.sin(heading_rad)
|
||||||
pos_ft[1] += dist_moved * math.cos(heading_rad)
|
pos_ft[1] += dist_moved * math.cos(heading_rad)
|
||||||
if wp.target_altitude_ft is not None: pos_ft[2] = wp.target_altitude_ft
|
if wp.target_altitude_ft is not None: pos_ft[2] = wp.target_altitude_ft
|
||||||
|
|
||||||
total_duration_s += duration
|
total_duration_s += duration
|
||||||
keyframes.append((total_duration_s, pos_ft[0], pos_ft[1], pos_ft[2]))
|
keyframes.append((total_duration_s, pos_ft[0], pos_ft[1], pos_ft[2]))
|
||||||
|
|
||||||
elif wp.maneuver_type == ManeuverType.DYNAMIC_MANEUVER:
|
elif wp.maneuver_type == ManeuverType.DYNAMIC_MANEUVER:
|
||||||
|
if wp.maneuver_speed_fps is not None:
|
||||||
|
speed_fps = wp.maneuver_speed_fps
|
||||||
|
current_heading_rad = math.atan2(vel_ft_s[0], vel_ft_s[1])
|
||||||
|
current_speed_2d = math.sqrt(vel_ft_s[0]**2 + vel_ft_s[1]**2)
|
||||||
|
current_pitch_rad = math.atan2(vel_ft_s[2], current_speed_2d) if current_speed_2d > 0 else 0
|
||||||
|
vel_ft_s = [speed_fps*math.cos(current_pitch_rad)*math.sin(current_heading_rad),
|
||||||
|
speed_fps*math.cos(current_pitch_rad)*math.cos(current_heading_rad),
|
||||||
|
speed_fps*math.sin(current_pitch_rad)]
|
||||||
|
|
||||||
time_step, num_steps = 0.1, int(duration / 0.1)
|
time_step, num_steps = 0.1, int(duration / 0.1)
|
||||||
accel_lon_fps2 = (wp.longitudinal_acceleration_g or 0.0) * G_IN_FPS2
|
accel_lon_fps2 = (wp.longitudinal_acceleration_g or 0.0) * G_IN_FPS2
|
||||||
accel_lat_fps2 = (wp.lateral_acceleration_g or 0.0) * G_IN_FPS2
|
accel_lat_fps2 = (wp.lateral_acceleration_g or 0.0) * G_IN_FPS2
|
||||||
accel_ver_fps2 = (wp.vertical_acceleration_g or 0.0) * G_IN_FPS2
|
accel_ver_fps2 = (wp.vertical_acceleration_g or 0.0) * G_IN_FPS2
|
||||||
dir_multiplier = 1.0 if wp.turn_direction == TurnDirection.RIGHT else -1.0
|
|
||||||
|
# --- MODIFICATION START ---
|
||||||
|
# A "Right" turn from the pilot's perspective is a clockwise rotation,
|
||||||
|
# which corresponds to a NEGATIVE angle in standard math.
|
||||||
|
dir_multiplier = -1.0 if wp.turn_direction == TurnDirection.RIGHT else 1.0
|
||||||
|
# --- MODIFICATION END ---
|
||||||
|
|
||||||
for _ in range(num_steps):
|
for _ in range(num_steps):
|
||||||
pos_ft = [pos_ft[j] + vel_ft_s[j] * time_step for j in range(3)]
|
pos_ft = [pos_ft[j] + vel_ft_s[j] * time_step for j in range(3)]
|
||||||
current_speed_fps = math.sqrt(sum(v**2 for v in vel_ft_s))
|
current_speed_fps = math.sqrt(sum(v**2 for v in vel_ft_s))
|
||||||
@ -250,29 +246,25 @@ class Target:
|
|||||||
self.current_altitude_ft = self._pos_z_ft
|
self.current_altitude_ft = self._pos_z_ft
|
||||||
|
|
||||||
def to_dict(self) -> Dict:
|
def to_dict(self) -> Dict:
|
||||||
return { "target_id": self.target_id, "active": self.active, "traceable": self.traceable, "trajectory": [wp.to_dict() for wp in self.trajectory], "use_spline": self.use_spline }
|
return {"target_id": self.target_id, "active": self.active, "traceable": self.traceable,
|
||||||
|
"trajectory": [wp.to_dict() for wp in self.trajectory], "use_spline": self.use_spline}
|
||||||
|
|
||||||
class Scenario:
|
class Scenario:
|
||||||
def __init__(self, name: str = "Untitled Scenario"):
|
def __init__(self, name: str = "Untitled Scenario"):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.targets: Dict[int, Target] = {}
|
self.targets: Dict[int, Target] = {}
|
||||||
def add_target(self, target: Target):
|
def add_target(self, target: Target): self.targets[target.target_id] = target
|
||||||
self.targets[target.target_id] = target
|
def get_target(self, target_id: int) -> Optional[Target]: return self.targets.get(target_id)
|
||||||
def get_target(self, target_id: int) -> Optional[Target]:
|
|
||||||
return self.targets.get(target_id)
|
|
||||||
def remove_target(self, target_id: int):
|
def remove_target(self, target_id: int):
|
||||||
if target_id in self.targets: del self.targets[target_id]
|
if target_id in self.targets: del self.targets[target_id]
|
||||||
def get_all_targets(self) -> List[Target]:
|
def get_all_targets(self) -> List[Target]: return list(self.targets.values())
|
||||||
return list(self.targets.values())
|
|
||||||
def reset_simulation(self):
|
def reset_simulation(self):
|
||||||
for target in self.targets.values(): target.reset_simulation()
|
for target in self.targets.values(): target.reset_simulation()
|
||||||
def update_state(self, delta_time_s: float):
|
def update_state(self, delta_time_s: float):
|
||||||
for target in self.targets.values(): target.update_state(delta_time_s)
|
for target in self.targets.values(): target.update_state(delta_time_s)
|
||||||
def is_finished(self) -> bool:
|
def is_finished(self) -> bool: return all(not target.active for target in self.targets.values())
|
||||||
return all(not target.active for target in self.targets.values())
|
|
||||||
def to_dict(self) -> Dict:
|
def to_dict(self) -> Dict:
|
||||||
return { "name": self.name, "targets": [target.to_dict() for target in self.get_all_targets()] }
|
return {"name": self.name, "targets": [t.to_dict() for t in self.get_all_targets()]}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, data: Dict) -> Scenario:
|
def from_dict(cls, data: Dict) -> Scenario:
|
||||||
scenario = cls(name=data.get("name", "Loaded Scenario"))
|
scenario = cls(name=data.get("name", "Loaded Scenario"))
|
||||||
@ -280,21 +272,19 @@ class Scenario:
|
|||||||
try:
|
try:
|
||||||
waypoints = []
|
waypoints = []
|
||||||
for wp_data in target_data.get("trajectory", []):
|
for wp_data in target_data.get("trajectory", []):
|
||||||
# --- BACKWARD COMPATIBILITY ---
|
|
||||||
# Convert old Constant Turn to new Dynamic Maneuver
|
|
||||||
if wp_data.get("maneuver_type") == "Constant Turn":
|
if wp_data.get("maneuver_type") == "Constant Turn":
|
||||||
wp_data["maneuver_type"] = "Dynamic Maneuver"
|
wp_data["maneuver_type"] = "Dynamic Maneuver"
|
||||||
wp_data["longitudinal_acceleration_g"] = 0.0
|
wp_data["longitudinal_acceleration_g"] = 0.0
|
||||||
wp_data["vertical_acceleration_g"] = 0.0
|
wp_data["vertical_acceleration_g"] = 0.0
|
||||||
|
|
||||||
wp_data["maneuver_type"] = ManeuverType(wp_data["maneuver_type"])
|
wp_data["maneuver_type"] = ManeuverType(wp_data["maneuver_type"])
|
||||||
if "turn_direction" in wp_data and wp_data["turn_direction"]:
|
if "turn_direction" in wp_data and wp_data["turn_direction"]:
|
||||||
wp_data["turn_direction"] = TurnDirection(wp_data["turn_direction"])
|
wp_data["turn_direction"] = TurnDirection(wp_data["turn_direction"])
|
||||||
valid_keys = {f.name for f in fields(Waypoint)}
|
valid_keys = {f.name for f in fields(Waypoint)}
|
||||||
filtered_wp_data = {k: v for k, v in wp_data.items() if k in valid_keys}
|
filtered_wp_data = {k: v for k, v in wp_data.items() if k in valid_keys}
|
||||||
waypoints.append(Waypoint(**filtered_wp_data))
|
waypoints.append(Waypoint(**filtered_wp_data))
|
||||||
|
target = Target(target_id=target_data["target_id"], active=target_data.get("active", True),
|
||||||
target = Target(target_id=target_data["target_id"], active=target_data.get("active", True), traceable=target_data.get("traceable", True), trajectory=waypoints, use_spline=target_data.get("use_spline", False))
|
traceable=target_data.get("traceable", True), trajectory=waypoints,
|
||||||
|
use_spline=target_data.get("use_spline", False))
|
||||||
scenario.add_target(target)
|
scenario.add_target(target)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Skipping invalid target data: {target_data}. Error: {e}")
|
print(f"Skipping invalid target data: {target_data}. Error: {e}")
|
||||||
|
|||||||
@ -6,8 +6,9 @@ A modal dialog window for creating or editing a single Waypoint.
|
|||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk, messagebox
|
from tkinter import ttk, messagebox
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
import math
|
||||||
|
|
||||||
from target_simulator.core.models import Waypoint, ManeuverType, TurnDirection, KNOTS_TO_FPS, FPS_TO_KNOTS
|
from target_simulator.core.models import Waypoint, ManeuverType, TurnDirection, KNOTS_TO_FPS, FPS_TO_KNOTS, G_IN_FPS2
|
||||||
|
|
||||||
class WaypointEditorWindow(tk.Toplevel):
|
class WaypointEditorWindow(tk.Toplevel):
|
||||||
"""A dialog for entering and editing data for a single waypoint."""
|
"""A dialog for entering and editing data for a single waypoint."""
|
||||||
@ -23,6 +24,7 @@ class WaypointEditorWindow(tk.Toplevel):
|
|||||||
|
|
||||||
self.is_first_waypoint = is_first_waypoint
|
self.is_first_waypoint = is_first_waypoint
|
||||||
self.result_waypoint: Optional[Waypoint] = None
|
self.result_waypoint: Optional[Waypoint] = None
|
||||||
|
self._is_updating_turn_def = False # Flag to prevent update loops
|
||||||
|
|
||||||
self._setup_variables()
|
self._setup_variables()
|
||||||
self._create_widgets()
|
self._create_widgets()
|
||||||
@ -40,7 +42,6 @@ class WaypointEditorWindow(tk.Toplevel):
|
|||||||
"""Initializes all tk Variables."""
|
"""Initializes all tk Variables."""
|
||||||
self.maneuver_type_var = tk.StringVar()
|
self.maneuver_type_var = tk.StringVar()
|
||||||
|
|
||||||
# --- Fly to Point ---
|
|
||||||
self.ftp_duration_var = tk.DoubleVar(value=10.0)
|
self.ftp_duration_var = tk.DoubleVar(value=10.0)
|
||||||
self.ftp_range_var = tk.DoubleVar(value=20.0)
|
self.ftp_range_var = tk.DoubleVar(value=20.0)
|
||||||
self.ftp_az_var = tk.DoubleVar(value=0.0)
|
self.ftp_az_var = tk.DoubleVar(value=0.0)
|
||||||
@ -48,23 +49,23 @@ class WaypointEditorWindow(tk.Toplevel):
|
|||||||
self.ftp_vel_var = tk.DoubleVar(value=300.0)
|
self.ftp_vel_var = tk.DoubleVar(value=300.0)
|
||||||
self.ftp_hdg_var = tk.DoubleVar(value=180.0)
|
self.ftp_hdg_var = tk.DoubleVar(value=180.0)
|
||||||
|
|
||||||
# --- Fly for Duration ---
|
|
||||||
self.ffd_duration_var = tk.DoubleVar(value=10.0)
|
self.ffd_duration_var = tk.DoubleVar(value=10.0)
|
||||||
self.ffd_vel_var = tk.DoubleVar(value=300.0)
|
self.ffd_vel_var = tk.DoubleVar(value=300.0)
|
||||||
self.ffd_hdg_var = tk.DoubleVar(value=90.0)
|
self.ffd_hdg_var = tk.DoubleVar(value=90.0)
|
||||||
self.ffd_alt_var = tk.DoubleVar(value=10000.0)
|
self.ffd_alt_var = tk.DoubleVar(value=10000.0)
|
||||||
|
|
||||||
# --- Dynamic Maneuver ---
|
|
||||||
self.dm_duration_var = tk.DoubleVar(value=10.0)
|
self.dm_duration_var = tk.DoubleVar(value=10.0)
|
||||||
|
self.dm_speed_kn_var = tk.DoubleVar(value=300.0)
|
||||||
self.dm_lon_accel_g_var = tk.DoubleVar(value=0.0)
|
self.dm_lon_accel_g_var = tk.DoubleVar(value=0.0)
|
||||||
self.dm_lat_accel_g_var = tk.DoubleVar(value=0.0)
|
|
||||||
self.dm_ver_accel_g_var = tk.DoubleVar(value=0.0)
|
self.dm_ver_accel_g_var = tk.DoubleVar(value=0.0)
|
||||||
|
self.dm_turn_def_var = tk.StringVar(value="g_force")
|
||||||
|
self.dm_lat_accel_g_var = tk.DoubleVar(value=1.0)
|
||||||
|
self.dm_turn_rate_var = tk.DoubleVar(value=3.0)
|
||||||
self.dm_turn_dir_var = tk.StringVar(value=TurnDirection.RIGHT.value)
|
self.dm_turn_dir_var = tk.StringVar(value=TurnDirection.RIGHT.value)
|
||||||
|
|
||||||
def _load_waypoint_data(self, wp: Waypoint):
|
def _load_waypoint_data(self, wp: Waypoint):
|
||||||
"""Populates the fields with data from an existing waypoint."""
|
"""Populates the fields with data from an existing waypoint."""
|
||||||
self.maneuver_type_var.set(wp.maneuver_type.value)
|
self.maneuver_type_var.set(wp.maneuver_type.value)
|
||||||
|
|
||||||
if wp.maneuver_type == ManeuverType.FLY_TO_POINT:
|
if wp.maneuver_type == ManeuverType.FLY_TO_POINT:
|
||||||
self.ftp_duration_var.set(wp.duration_s or 10.0)
|
self.ftp_duration_var.set(wp.duration_s or 10.0)
|
||||||
self.ftp_range_var.set(wp.target_range_nm or 0.0)
|
self.ftp_range_var.set(wp.target_range_nm or 0.0)
|
||||||
@ -72,20 +73,20 @@ class WaypointEditorWindow(tk.Toplevel):
|
|||||||
self.ftp_alt_var.set(wp.target_altitude_ft or 10000.0)
|
self.ftp_alt_var.set(wp.target_altitude_ft or 10000.0)
|
||||||
self.ftp_vel_var.set((wp.target_velocity_fps or 0.0) * FPS_TO_KNOTS)
|
self.ftp_vel_var.set((wp.target_velocity_fps or 0.0) * FPS_TO_KNOTS)
|
||||||
self.ftp_hdg_var.set(wp.target_heading_deg or 0.0)
|
self.ftp_hdg_var.set(wp.target_heading_deg or 0.0)
|
||||||
|
|
||||||
elif wp.maneuver_type == ManeuverType.FLY_FOR_DURATION:
|
elif wp.maneuver_type == ManeuverType.FLY_FOR_DURATION:
|
||||||
self.ffd_duration_var.set(wp.duration_s or 10.0)
|
self.ffd_duration_var.set(wp.duration_s or 10.0)
|
||||||
self.ffd_vel_var.set((wp.target_velocity_fps or 0.0) * FPS_TO_KNOTS)
|
self.ffd_vel_var.set((wp.target_velocity_fps or 0.0) * FPS_TO_KNOTS)
|
||||||
self.ffd_hdg_var.set(wp.target_heading_deg or 0.0)
|
self.ffd_hdg_var.set(wp.target_heading_deg or 0.0)
|
||||||
self.ffd_alt_var.set(wp.target_altitude_ft or 10000.0)
|
self.ffd_alt_var.set(wp.target_altitude_ft or 10000.0)
|
||||||
|
|
||||||
elif wp.maneuver_type == ManeuverType.DYNAMIC_MANEUVER:
|
elif wp.maneuver_type == ManeuverType.DYNAMIC_MANEUVER:
|
||||||
self.dm_duration_var.set(wp.duration_s or 10.0)
|
self.dm_duration_var.set(wp.duration_s or 10.0)
|
||||||
|
self.dm_speed_kn_var.set((wp.maneuver_speed_fps or 300.0) * FPS_TO_KNOTS)
|
||||||
self.dm_lon_accel_g_var.set(wp.longitudinal_acceleration_g or 0.0)
|
self.dm_lon_accel_g_var.set(wp.longitudinal_acceleration_g or 0.0)
|
||||||
self.dm_lat_accel_g_var.set(wp.lateral_acceleration_g or 0.0)
|
self.dm_lat_accel_g_var.set(wp.lateral_acceleration_g or 0.0)
|
||||||
self.dm_ver_accel_g_var.set(wp.vertical_acceleration_g or 0.0)
|
self.dm_ver_accel_g_var.set(wp.vertical_acceleration_g or 0.0)
|
||||||
if wp.turn_direction:
|
if wp.turn_direction: self.dm_turn_dir_var.set(wp.turn_direction.value)
|
||||||
self.dm_turn_dir_var.set(wp.turn_direction.value)
|
# Sync the turn rate spinbox from the loaded G value
|
||||||
|
self._update_turn_rate_from_g()
|
||||||
|
|
||||||
def _center_window(self):
|
def _center_window(self):
|
||||||
self.update_idletasks()
|
self.update_idletasks()
|
||||||
@ -97,24 +98,19 @@ class WaypointEditorWindow(tk.Toplevel):
|
|||||||
def _create_widgets(self):
|
def _create_widgets(self):
|
||||||
main_frame = ttk.Frame(self, padding=10)
|
main_frame = ttk.Frame(self, padding=10)
|
||||||
main_frame.pack(fill=tk.BOTH, expand=True)
|
main_frame.pack(fill=tk.BOTH, expand=True)
|
||||||
|
|
||||||
type_frame = ttk.Frame(main_frame)
|
type_frame = ttk.Frame(main_frame)
|
||||||
type_frame.pack(fill=tk.X, pady=(0, 10))
|
type_frame.pack(fill=tk.X, pady=(0, 10))
|
||||||
ttk.Label(type_frame, text="Maneuver Type:").pack(side=tk.LEFT)
|
ttk.Label(type_frame, text="Maneuver Type:").pack(side=tk.LEFT)
|
||||||
self.maneuver_combo = ttk.Combobox(type_frame, textvariable=self.maneuver_type_var,
|
self.maneuver_combo = ttk.Combobox(type_frame, textvariable=self.maneuver_type_var, values=[m.value for m in ManeuverType], state="readonly")
|
||||||
values=[m.value for m in ManeuverType], state="readonly")
|
|
||||||
self.maneuver_combo.pack(side=tk.LEFT, expand=True, fill=tk.X, padx=5)
|
self.maneuver_combo.pack(side=tk.LEFT, expand=True, fill=tk.X, padx=5)
|
||||||
self.maneuver_combo.bind("<<ComboboxSelected>>", self._on_maneuver_type_change)
|
self.maneuver_combo.bind("<<ComboboxSelected>>", self._on_maneuver_type_change)
|
||||||
|
|
||||||
self.fly_to_point_frame = self._create_fly_to_point_frame(main_frame)
|
self.fly_to_point_frame = self._create_fly_to_point_frame(main_frame)
|
||||||
self.fly_for_duration_frame = self._create_fly_for_duration_frame(main_frame)
|
self.fly_for_duration_frame = self._create_fly_for_duration_frame(main_frame)
|
||||||
self.dynamic_maneuver_frame = self._create_dynamic_maneuver_frame(main_frame)
|
self.dynamic_maneuver_frame = self._create_dynamic_maneuver_frame(main_frame)
|
||||||
|
|
||||||
button_frame = ttk.Frame(main_frame)
|
button_frame = ttk.Frame(main_frame)
|
||||||
button_frame.pack(fill=tk.X, pady=(15, 0), side=tk.BOTTOM)
|
button_frame.pack(fill=tk.X, pady=(15, 0), side=tk.BOTTOM)
|
||||||
ttk.Button(button_frame, text="OK", command=self._on_ok).pack(side=tk.RIGHT, padx=5)
|
ttk.Button(button_frame, text="OK", command=self._on_ok).pack(side=tk.RIGHT, padx=5)
|
||||||
ttk.Button(button_frame, text="Cancel", command=self._on_cancel).pack(side=tk.RIGHT)
|
ttk.Button(button_frame, text="Cancel", command=self._on_cancel).pack(side=tk.RIGHT)
|
||||||
|
|
||||||
if self.is_first_waypoint:
|
if self.is_first_waypoint:
|
||||||
self.maneuver_type_var.set(ManeuverType.FLY_TO_POINT.value)
|
self.maneuver_type_var.set(ManeuverType.FLY_TO_POINT.value)
|
||||||
self.maneuver_combo.config(state=tk.DISABLED)
|
self.maneuver_combo.config(state=tk.DISABLED)
|
||||||
@ -153,64 +149,96 @@ class WaypointEditorWindow(tk.Toplevel):
|
|||||||
return frame
|
return frame
|
||||||
|
|
||||||
def _create_dynamic_maneuver_frame(self, parent) -> ttk.Frame:
|
def _create_dynamic_maneuver_frame(self, parent) -> ttk.Frame:
|
||||||
frame = ttk.LabelFrame(parent, text="Applied Accelerations", padding=10)
|
frame = ttk.LabelFrame(parent, text="Dynamic Maneuver Controls", padding=10)
|
||||||
frame.columnconfigure(1, weight=1)
|
frame.columnconfigure(1, weight=1)
|
||||||
ttk.Label(frame, text="Duration (s):").grid(row=0, column=0, sticky=tk.W, pady=2)
|
ttk.Label(frame, text="Duration (s):").grid(row=0, column=0, sticky=tk.W, pady=2)
|
||||||
ttk.Spinbox(frame, from_=0.1, to=3600*24, textvariable=self.dm_duration_var, width=10).grid(row=0, column=1, sticky=tk.EW, pady=2)
|
ttk.Spinbox(frame, from_=0.1, to=3600*24, textvariable=self.dm_duration_var, width=10).grid(row=0, column=1, sticky=tk.EW, pady=2)
|
||||||
ttk.Label(frame, text="Longitudinal Accel (g):").grid(row=1, column=0, sticky=tk.W, pady=2)
|
ttk.Label(frame, text="Maneuver Speed (kn):").grid(row=1, column=0, sticky=tk.W, pady=2)
|
||||||
ttk.Spinbox(frame, from_=-5, to=10, format="%.2f", increment=0.1, textvariable=self.dm_lon_accel_g_var, width=10).grid(row=1, column=1, sticky=tk.EW, pady=2)
|
speed_spinbox = ttk.Spinbox(frame, from_=0, to=2000, textvariable=self.dm_speed_kn_var, width=10, command=self._update_g_from_turn_rate)
|
||||||
ttk.Label(frame, text="Lateral Accel (g):").grid(row=2, column=0, sticky=tk.W, pady=2)
|
speed_spinbox.grid(row=1, column=1, sticky=tk.EW, pady=2)
|
||||||
ttk.Spinbox(frame, from_=0, to=9, format="%.2f", increment=0.1, textvariable=self.dm_lat_accel_g_var, width=10).grid(row=2, column=1, sticky=tk.EW, pady=2)
|
turn_frame = ttk.LabelFrame(frame, text="Turn Definition", padding=5)
|
||||||
ttk.Label(frame, text="Turn Direction:").grid(row=3, column=0, sticky=tk.W, pady=2)
|
turn_frame.grid(row=2, column=0, columnspan=2, sticky=tk.EW, pady=5)
|
||||||
turn_dir_combo = ttk.Combobox(frame, textvariable=self.dm_turn_dir_var, values=[d.value for d in TurnDirection], state="readonly", width=8)
|
g_force_rb = ttk.Radiobutton(turn_frame, text="By G-Force", variable=self.dm_turn_def_var, value="g_force", command=self._on_turn_definition_change)
|
||||||
turn_dir_combo.grid(row=3, column=1, sticky=tk.W, pady=2)
|
g_force_rb.grid(row=0, column=0, sticky=tk.W)
|
||||||
|
self.dm_lat_g_spinbox = ttk.Spinbox(turn_frame, from_=0, to=9, format="%.2f", increment=0.1, textvariable=self.dm_lat_accel_g_var, width=8, command=self._update_turn_rate_from_g)
|
||||||
|
self.dm_lat_g_spinbox.grid(row=0, column=1, padx=5)
|
||||||
|
turn_rate_rb = ttk.Radiobutton(turn_frame, text="By Turn Rate (°/s)", variable=self.dm_turn_def_var, value="turn_rate", command=self._on_turn_definition_change)
|
||||||
|
turn_rate_rb.grid(row=1, column=0, sticky=tk.W)
|
||||||
|
self.dm_turn_rate_spinbox = ttk.Spinbox(turn_frame, from_=0, to=30, format="%.2f", increment=0.1, textvariable=self.dm_turn_rate_var, width=8, command=self._update_g_from_turn_rate)
|
||||||
|
self.dm_turn_rate_spinbox.grid(row=1, column=1, padx=5)
|
||||||
|
ttk.Label(turn_frame, text="Direction:").grid(row=0, column=2, sticky=tk.W, padx=(10, 2))
|
||||||
|
ttk.Combobox(turn_frame, textvariable=self.dm_turn_dir_var, values=[d.value for d in TurnDirection], state="readonly", width=7).grid(row=0, column=3, sticky=tk.W)
|
||||||
|
self._on_turn_definition_change()
|
||||||
|
ttk.Label(frame, text="Longitudinal Accel (g):").grid(row=3, column=0, sticky=tk.W, pady=2)
|
||||||
|
ttk.Spinbox(frame, from_=-5, to=10, format="%.2f", increment=0.1, textvariable=self.dm_lon_accel_g_var, width=10).grid(row=3, column=1, sticky=tk.EW, pady=2)
|
||||||
ttk.Label(frame, text="Vertical Accel (g):").grid(row=4, column=0, sticky=tk.W, pady=2)
|
ttk.Label(frame, text="Vertical Accel (g):").grid(row=4, column=0, sticky=tk.W, pady=2)
|
||||||
ttk.Spinbox(frame, from_=-5, to=5, format="%.2f", increment=0.1, textvariable=self.dm_ver_accel_g_var, width=10).grid(row=4, column=1, sticky=tk.EW, pady=2)
|
ttk.Spinbox(frame, from_=-5, to=5, format="%.2f", increment=0.1, textvariable=self.dm_ver_accel_g_var, width=10).grid(row=4, column=1, sticky=tk.EW, pady=2)
|
||||||
return frame
|
return frame
|
||||||
|
|
||||||
def _on_maneuver_type_change(self, event=None):
|
def _update_g_from_turn_rate(self):
|
||||||
|
if self._is_updating_turn_def: return
|
||||||
|
self._is_updating_turn_def = True
|
||||||
try:
|
try:
|
||||||
m_type = ManeuverType(self.maneuver_type_var.get())
|
rate_deg_s = self.dm_turn_rate_var.get()
|
||||||
except ValueError: return
|
speed_kn = self.dm_speed_kn_var.get()
|
||||||
|
if speed_kn > 0:
|
||||||
|
accel_fps2 = math.radians(rate_deg_s) * (speed_kn * KNOTS_TO_FPS)
|
||||||
|
g_load = accel_fps2 / G_IN_FPS2
|
||||||
|
self.dm_lat_accel_g_var.set(round(g_load, 2))
|
||||||
|
except (ValueError, tk.TclError): pass
|
||||||
|
finally: self._is_updating_turn_def = False
|
||||||
|
|
||||||
|
def _update_turn_rate_from_g(self):
|
||||||
|
if self._is_updating_turn_def: return
|
||||||
|
self._is_updating_turn_def = True
|
||||||
|
try:
|
||||||
|
g_load = self.dm_lat_accel_g_var.get()
|
||||||
|
speed_kn = self.dm_speed_kn_var.get()
|
||||||
|
speed_fps = speed_kn * KNOTS_TO_FPS
|
||||||
|
if speed_fps > 0:
|
||||||
|
accel_fps2 = g_load * G_IN_FPS2
|
||||||
|
rate_rad_s = accel_fps2 / speed_fps
|
||||||
|
self.dm_turn_rate_var.set(round(math.degrees(rate_rad_s), 2))
|
||||||
|
except (ValueError, tk.TclError): pass
|
||||||
|
finally: self._is_updating_turn_def = False
|
||||||
|
|
||||||
|
def _on_turn_definition_change(self):
|
||||||
|
if self.dm_turn_def_var.get() == "g_force":
|
||||||
|
self.dm_lat_g_spinbox.config(state=tk.NORMAL)
|
||||||
|
self.dm_turn_rate_spinbox.config(state=tk.DISABLED)
|
||||||
|
self._update_turn_rate_from_g()
|
||||||
|
else:
|
||||||
|
self.dm_lat_g_spinbox.config(state=tk.DISABLED)
|
||||||
|
self.dm_turn_rate_spinbox.config(state=tk.NORMAL)
|
||||||
|
self._update_g_from_turn_rate()
|
||||||
|
|
||||||
|
def _on_maneuver_type_change(self, event=None):
|
||||||
|
try: m_type = ManeuverType(self.maneuver_type_var.get())
|
||||||
|
except ValueError: return
|
||||||
self.fly_to_point_frame.pack_forget()
|
self.fly_to_point_frame.pack_forget()
|
||||||
self.fly_for_duration_frame.pack_forget()
|
self.fly_for_duration_frame.pack_forget()
|
||||||
self.dynamic_maneuver_frame.pack_forget()
|
self.dynamic_maneuver_frame.pack_forget()
|
||||||
|
if m_type == ManeuverType.FLY_TO_POINT: self.fly_to_point_frame.pack(fill=tk.BOTH, expand=True)
|
||||||
if m_type == ManeuverType.FLY_TO_POINT:
|
elif m_type == ManeuverType.FLY_FOR_DURATION: self.fly_for_duration_frame.pack(fill=tk.BOTH, expand=True)
|
||||||
self.fly_to_point_frame.pack(fill=tk.BOTH, expand=True)
|
elif m_type == ManeuverType.DYNAMIC_MANEUVER: self.dynamic_maneuver_frame.pack(fill=tk.BOTH, expand=True)
|
||||||
elif m_type == ManeuverType.FLY_FOR_DURATION:
|
|
||||||
self.fly_for_duration_frame.pack(fill=tk.BOTH, expand=True)
|
|
||||||
elif m_type == ManeuverType.DYNAMIC_MANEUVER:
|
|
||||||
self.dynamic_maneuver_frame.pack(fill=tk.BOTH, expand=True)
|
|
||||||
|
|
||||||
def _on_ok(self):
|
def _on_ok(self):
|
||||||
try:
|
try:
|
||||||
m_type = ManeuverType(self.maneuver_type_var.get())
|
m_type = ManeuverType(self.maneuver_type_var.get())
|
||||||
wp = Waypoint(maneuver_type=m_type)
|
wp = Waypoint(maneuver_type=m_type)
|
||||||
|
|
||||||
if m_type == ManeuverType.FLY_TO_POINT:
|
if m_type == ManeuverType.FLY_TO_POINT:
|
||||||
wp.duration_s = self.ftp_duration_var.get()
|
wp.duration_s, wp.target_range_nm, wp.target_azimuth_deg, wp.target_altitude_ft = self.ftp_duration_var.get(), self.ftp_range_var.get(), self.ftp_az_var.get(), self.ftp_alt_var.get()
|
||||||
wp.target_range_nm = self.ftp_range_var.get()
|
|
||||||
wp.target_azimuth_deg = self.ftp_az_var.get()
|
|
||||||
wp.target_altitude_ft = self.ftp_alt_var.get()
|
|
||||||
if self.is_first_waypoint:
|
if self.is_first_waypoint:
|
||||||
wp.target_velocity_fps = self.ftp_vel_var.get() * KNOTS_TO_FPS
|
wp.target_velocity_fps, wp.target_heading_deg = self.ftp_vel_var.get()*KNOTS_TO_FPS, self.ftp_hdg_var.get()
|
||||||
wp.target_heading_deg = self.ftp_hdg_var.get()
|
|
||||||
|
|
||||||
elif m_type == ManeuverType.FLY_FOR_DURATION:
|
elif m_type == ManeuverType.FLY_FOR_DURATION:
|
||||||
wp.duration_s = self.ffd_duration_var.get()
|
wp.duration_s, wp.target_velocity_fps, wp.target_heading_deg, wp.target_altitude_ft = self.ffd_duration_var.get(), self.ffd_vel_var.get()*KNOTS_TO_FPS, self.ffd_hdg_var.get(), self.ffd_alt_var.get()
|
||||||
wp.target_velocity_fps = self.ffd_vel_var.get() * KNOTS_TO_FPS
|
|
||||||
wp.target_heading_deg = self.ffd_hdg_var.get()
|
|
||||||
wp.target_altitude_ft = self.ffd_alt_var.get()
|
|
||||||
|
|
||||||
elif m_type == ManeuverType.DYNAMIC_MANEUVER:
|
elif m_type == ManeuverType.DYNAMIC_MANEUVER:
|
||||||
wp.duration_s = self.dm_duration_var.get()
|
wp.duration_s, wp.maneuver_speed_fps, wp.longitudinal_acceleration_g, wp.vertical_acceleration_g, wp.turn_direction = self.dm_duration_var.get(), self.dm_speed_kn_var.get()*KNOTS_TO_FPS, self.dm_lon_accel_g_var.get(), self.dm_ver_accel_g_var.get(), TurnDirection(self.dm_turn_dir_var.get())
|
||||||
wp.longitudinal_acceleration_g = self.dm_lon_accel_g_var.get()
|
if self.dm_turn_def_var.get() == "g_force":
|
||||||
|
wp.lateral_acceleration_g = self.dm_lat_accel_g_var.get()
|
||||||
|
else:
|
||||||
|
self._update_g_from_turn_rate()
|
||||||
wp.lateral_acceleration_g = self.dm_lat_accel_g_var.get()
|
wp.lateral_acceleration_g = self.dm_lat_accel_g_var.get()
|
||||||
wp.vertical_acceleration_g = self.dm_ver_accel_g_var.get()
|
|
||||||
wp.turn_direction = TurnDirection(self.dm_turn_dir_var.get())
|
|
||||||
|
|
||||||
self.result_waypoint = wp
|
self.result_waypoint = wp
|
||||||
self.destroy()
|
self.destroy()
|
||||||
except (ValueError, tk.TclError) as e:
|
except (ValueError, tk.TclError) as e:
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user