133 lines
4.0 KiB
Python
133 lines
4.0 KiB
Python
# target_simulator/core/models.py
|
|
|
|
"""
|
|
Defines the core data models for the application, including Target and Scenario.
|
|
|
|
This module is responsible for the business logic representation of simulation
|
|
entities, completely independent of the GUI or communication layers.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
import json
|
|
from dataclasses import dataclass, asdict
|
|
from typing import Dict, List, Optional
|
|
|
|
# Constants for target limitations, derived from the user manual.
|
|
MIN_TARGET_ID = 0
|
|
MAX_TARGET_ID = 15
|
|
|
|
|
|
@dataclass
|
|
class Target:
|
|
"""
|
|
Represents a single target with its state and kinematic properties.
|
|
|
|
Attributes reflect the parameters used in the MMI commands like 'tgtinit'.
|
|
"""
|
|
target_id: int
|
|
range_nm: float
|
|
azimuth_deg: float
|
|
velocity_fps: float
|
|
heading_deg: float
|
|
altitude_ft: float
|
|
active: bool = True
|
|
traceable: bool = True
|
|
|
|
def __post_init__(self):
|
|
"""Validate target_id after initialization."""
|
|
if not (MIN_TARGET_ID <= self.target_id <= MAX_TARGET_ID):
|
|
raise ValueError(
|
|
f"Target ID must be between {MIN_TARGET_ID} and {MAX_TARGET_ID}."
|
|
)
|
|
|
|
def to_dict(self) -> Dict:
|
|
"""Serializes the Target object to a dictionary."""
|
|
return asdict(self)
|
|
|
|
|
|
class Scenario:
|
|
"""
|
|
Manages a collection of targets that constitute a simulation scenario.
|
|
|
|
Provides functionality to add, remove, and retrieve targets, as well as
|
|
saving and loading the entire scenario to/from a file.
|
|
"""
|
|
def __init__(self, name: str = "Untitled Scenario"):
|
|
self.name = name
|
|
self.targets: Dict[int, Target] = {}
|
|
|
|
def add_target(self, target: Target) -> None:
|
|
"""
|
|
Adds a target to the scenario. Overwrites if ID already exists.
|
|
|
|
Args:
|
|
target: The Target object to add.
|
|
"""
|
|
self.targets[target.target_id] = target
|
|
|
|
def get_target(self, target_id: int) -> Optional[Target]:
|
|
"""
|
|
Retrieves a target by its ID.
|
|
|
|
Args:
|
|
target_id: The ID of the target to retrieve.
|
|
|
|
Returns:
|
|
The Target object if found, otherwise None.
|
|
"""
|
|
return self.targets.get(target_id)
|
|
|
|
def remove_target(self, target_id: int) -> None:
|
|
"""
|
|
Removes a target from the scenario by its ID.
|
|
|
|
Args:
|
|
target_id: The ID of the target to remove.
|
|
"""
|
|
if target_id in self.targets:
|
|
del self.targets[target_id]
|
|
|
|
def get_all_targets(self) -> List[Target]:
|
|
"""Returns a list of all targets in the scenario."""
|
|
return list(self.targets.values())
|
|
|
|
def save_to_file(self, filepath: str) -> None:
|
|
"""
|
|
Saves the current scenario to a JSON file.
|
|
|
|
Args:
|
|
filepath: The path to the file where the scenario will be saved.
|
|
"""
|
|
scenario_data = {
|
|
"name": self.name,
|
|
"targets": [target.to_dict() for target in self.get_all_targets()]
|
|
}
|
|
with open(filepath, 'w', encoding='utf-8') as f:
|
|
json.dump(scenario_data, f, indent=4)
|
|
|
|
@classmethod
|
|
def load_from_file(cls, filepath: str) -> Scenario:
|
|
"""
|
|
Loads a scenario from a JSON file and returns a new Scenario instance.
|
|
|
|
Args:
|
|
filepath: The path to the scenario file to load.
|
|
|
|
Returns:
|
|
A new Scenario object populated with data from the file.
|
|
"""
|
|
with open(filepath, 'r', encoding='utf-8') as f:
|
|
scenario_data = json.load(f)
|
|
|
|
scenario_name = scenario_data.get("name", "Loaded Scenario")
|
|
new_scenario = cls(name=scenario_name)
|
|
|
|
for target_data in scenario_data.get("targets", []):
|
|
try:
|
|
target = Target(**target_data)
|
|
new_scenario.add_target(target)
|
|
except (TypeError, ValueError) as e:
|
|
# Log or print a warning about invalid target data in the file
|
|
print(f"Skipping invalid target data: {target_data}. Error: {e}")
|
|
|
|
return new_scenario |