Initial commit: Happa Mission Plan v0.1.0

Task planning and path generation software for automated data collection
equipment (hyperspectral cameras, DSLR, depth cameras).

- Generate mission plans (JSON) with subtasks per device
- Generate scan path binary files (.RecordLine3)
- Multi-rectangle area planning with reference map support
- Device-specific FOV defaults (Pika L 17.6°, Pika NIR 21.7°, etc.)
- Timeline scheduling with constraint validation
- Tauri 2.x + Vue 3 + Naive UI + Pinia
This commit is contained in:
xin
2026-06-17 17:17:39 +08:00
commit d732580c3e
80 changed files with 10842 additions and 0 deletions

View File

@ -0,0 +1,59 @@
use serde::{Deserialize, Serialize};
use super::task::Task;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MissionScanConfig {
#[serde(rename = "xMin")]
pub x_min: f64,
#[serde(rename = "xMax")]
pub x_max: f64,
#[serde(rename = "yMin")]
pub y_min: f64,
#[serde(rename = "yMax")]
pub y_max: f64,
}
impl Default for MissionScanConfig {
fn default() -> Self {
Self {
x_min: 0.0,
x_max: 100.0,
y_min: 0.0,
y_max: 100.0,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MissionPlan {
pub version: String,
#[serde(rename = "taskCount")]
pub task_count: u32,
pub tasks: Vec<Task>,
#[serde(rename = "scanConfig", default = "MissionScanConfig::default")]
pub scan_config: MissionScanConfig,
#[serde(rename = "backgroundImage", skip_serializing_if = "Option::is_none")]
pub background_image: Option<String>,
}
impl MissionPlan {
pub fn new() -> Self {
Self {
version: "1.0".to_string(),
task_count: 0,
tasks: Vec::new(),
scan_config: MissionScanConfig::default(),
background_image: None,
}
}
pub fn next_task_id(&self) -> u32 {
self.tasks.iter().map(|t| t.id).max().unwrap_or(0) + 1
}
}
impl Default for MissionPlan {
fn default() -> Self {
Self::new()
}
}

View File

@ -0,0 +1,9 @@
pub mod mission;
pub mod task;
pub mod path_line;
pub mod path_plan;
pub use mission::{MissionPlan, MissionScanConfig};
pub use task::{SubTask, SubTaskType, Task};
pub use path_line::{PathLineFile, PathLineRecord};
pub use path_plan::{ScanMode, ScanParams, ScanRegion, CameraParams, PlannerDefaults};

View File

@ -0,0 +1,23 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PathLineRecord {
#[serde(rename = "targetYPosition")]
pub target_y_position: f64,
#[serde(rename = "speedTargetYPosition")]
pub speed_target_y_position: f64,
#[serde(rename = "targetXMinPosition")]
pub target_x_min_position: f64,
#[serde(rename = "speedTargetXMinPosition")]
pub speed_target_x_min_position: f64,
#[serde(rename = "targetXMaxPosition")]
pub target_x_max_position: f64,
#[serde(rename = "speedTargetXMaxPosition")]
pub speed_target_x_max_position: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PathLineFile {
pub count: u64,
pub records: Vec<PathLineRecord>,
}

View File

@ -0,0 +1,73 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ScanRegion {
#[serde(rename = "xMin")]
pub x_min: f64,
#[serde(rename = "xMax")]
pub x_max: f64,
#[serde(rename = "yMin")]
pub y_min: f64,
#[serde(rename = "yMax")]
pub y_max: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CameraParams {
#[serde(rename = "fovDegrees")]
pub fov_degrees: f64,
#[serde(rename = "heightCm")]
pub height_cm: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ScanMode {
Zigzag,
OneWay,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ScanParams {
pub region: ScanRegion,
pub camera: CameraParams,
#[serde(rename = "coverageRate")]
pub coverage_rate: f64,
#[serde(rename = "speedYCmS")]
pub speed_y_cm_s: f64,
#[serde(rename = "speedXStartCmS")]
pub speed_x_start_cm_s: f64,
#[serde(rename = "speedXScanCmS")]
pub speed_x_scan_cm_s: f64,
pub mode: ScanMode,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PlannerDefaults {
#[serde(rename = "fovDegrees")]
pub fov_degrees: f64,
#[serde(rename = "heightCm")]
pub height_cm: f64,
#[serde(rename = "coverageRate")]
pub coverage_rate: f64,
#[serde(rename = "speedYCmS")]
pub speed_y_cm_s: f64,
#[serde(rename = "speedXStartCmS")]
pub speed_x_start_cm_s: f64,
#[serde(rename = "speedXScanCmS")]
pub speed_x_scan_cm_s: f64,
pub mode: String,
}
impl Default for PlannerDefaults {
fn default() -> Self {
Self {
fov_degrees: 30.0,
height_cm: 50.0,
coverage_rate: 30.0,
speed_y_cm_s: 5.0,
speed_x_start_cm_s: 20.0,
speed_x_scan_cm_s: 10.0,
mode: "Zigzag".to_string(),
}
}
}

View File

@ -0,0 +1,130 @@
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum SubTaskType {
#[serde(rename = "HyperSpectual400_1000nm")]
HyperSpectual400_1000nm,
#[serde(rename = "HyperSpectual1000_1700nm")]
HyperSpectual1000_1700nm,
#[serde(rename = "SingleLensReflex")]
SingleLensReflex,
#[serde(rename = "DepthCamera")]
DepthCamera,
}
impl SubTaskType {
pub fn is_hyperspectral(&self) -> bool {
matches!(self, Self::HyperSpectual400_1000nm | Self::HyperSpectual1000_1700nm)
}
pub fn is_camera_type(&self) -> bool {
matches!(self, Self::SingleLensReflex | Self::DepthCamera)
}
pub fn label(&self) -> &'static str {
match self {
Self::HyperSpectual400_1000nm => "Pika L",
Self::HyperSpectual1000_1700nm => "Pika NIR",
Self::SingleLensReflex => "单反相机",
Self::DepthCamera => "深度相机",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SubTask {
#[serde(default = "default_subtask_id")]
pub id: String,
#[serde(rename = "type")]
pub sub_task_type: SubTaskType,
#[serde(rename = "captureIntervalSeconds")]
pub capture_interval_seconds: Option<u32>,
#[serde(rename = "defaultRenderBand")]
pub default_render_band: Option<u32>,
#[serde(rename = "exposureTime")]
pub exposure_time: Option<u32>,
#[serde(rename = "frameRate")]
pub frame_rate: Option<u32>,
#[serde(rename = "pathLineFilePath")]
pub path_line_file_path: String,
#[serde(rename = "durationMinutes")]
pub duration_minutes: f64,
#[serde(rename = "estimatedDurationMinutes")]
pub estimated_duration_minutes: f64,
#[serde(rename = "endTime")]
pub end_time: Option<String>,
#[serde(rename = "startTime")]
pub start_time: Option<String>,
pub status: String,
}
fn default_subtask_id() -> String {
Uuid::new_v4().to_string()
}
impl SubTask {
pub fn new(sub_task_type: SubTaskType) -> Self {
Self {
id: default_subtask_id(),
sub_task_type,
capture_interval_seconds: None,
default_render_band: Some(550),
exposure_time: None,
frame_rate: None,
path_line_file_path: String::new(),
duration_minutes: 0.0,
estimated_duration_minutes: 0.0,
end_time: None,
start_time: None,
status: "Waiting".to_string(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Task {
pub id: u32,
#[serde(rename = "savePath")]
pub save_path: String,
#[serde(rename = "scheduledTime")]
pub scheduled_time: String,
#[serde(rename = "HalogenLampPreheatingTime_Minute")]
pub halogen_lamp_preheating_time_minute: f64,
#[serde(rename = "durationMinutes")]
pub duration_minutes: f64,
#[serde(rename = "estimatedDurationMinutes")]
pub estimated_duration_minutes: f64,
#[serde(rename = "endTime")]
pub end_time: Option<String>,
#[serde(rename = "startTime")]
pub start_time: Option<String>,
pub status: String,
#[serde(rename = "subTasks")]
pub sub_tasks: Vec<SubTask>,
}
impl Task {
pub fn new(id: u32) -> Self {
Self {
id,
save_path: String::new(),
scheduled_time: "2000-01-01T00:00:00".to_string(),
halogen_lamp_preheating_time_minute: 0.1,
duration_minutes: 0.0,
estimated_duration_minutes: 0.0,
end_time: None,
start_time: None,
status: "Waiting".to_string(),
sub_tasks: Vec::new(),
}
}
pub fn has_hyperspectral(&self) -> bool {
self.sub_tasks.iter().any(|s| s.sub_task_type.is_hyperspectral())
}
pub fn has_type(&self, stype: &SubTaskType) -> bool {
self.sub_tasks.iter().any(|s| s.sub_task_type == *stype)
}
}