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,163 @@
use tauri::command;
use crate::error::AppError;
use crate::models::{MissionPlan, SubTask, SubTaskType, Task};
#[command]
pub fn add_task(mission: MissionPlan) -> MissionPlan {
let mut m = mission;
let new_id = m.next_task_id();
m.tasks.push(Task::new(new_id));
m.task_count = m.tasks.len() as u32;
m
}
#[command]
pub fn copy_task(mission: MissionPlan, task_id: u32) -> Result<MissionPlan, AppError> {
let mut m = mission;
let src = m.tasks.iter()
.find(|t| t.id == task_id)
.ok_or(AppError::TaskNotFound(task_id))?
.clone();
let new_id = m.next_task_id();
let mut new_task = src;
new_task.id = new_id;
new_task.scheduled_time = "2000-01-01T00:00:00".to_string();
new_task.start_time = None;
new_task.end_time = None;
new_task.duration_minutes = 0.0;
new_task.estimated_duration_minutes = 0.0;
new_task.status = "Waiting".to_string();
// Reset subtask IDs
for sub in &mut new_task.sub_tasks {
sub.id = uuid::Uuid::new_v4().to_string();
sub.start_time = None;
sub.end_time = None;
sub.duration_minutes = 0.0;
sub.estimated_duration_minutes = 0.0;
sub.status = "Waiting".to_string();
}
m.tasks.push(new_task);
m.task_count = m.tasks.len() as u32;
Ok(m)
}
#[command]
pub fn remove_task(mission: MissionPlan, task_id: u32) -> Result<MissionPlan, AppError> {
let mut m = mission;
let pos = m.tasks.iter().position(|t| t.id == task_id)
.ok_or(AppError::TaskNotFound(task_id))?;
m.tasks.remove(pos);
m.task_count = m.tasks.len() as u32;
Ok(m)
}
#[command]
pub fn add_sub_task(
mission: MissionPlan,
task_id: u32,
sub_task_type: SubTaskType,
) -> Result<MissionPlan, AppError> {
let mut m = mission;
let task = m.tasks.iter_mut()
.find(|t| t.id == task_id)
.ok_or(AppError::TaskNotFound(task_id))?;
// Check: same type already exists
if task.has_type(&sub_task_type) {
return Err(AppError::Validation(format!(
"Task #{} already has a {} sub-task",
task_id,
sub_task_type.label()
)));
}
task.sub_tasks.push(SubTask::new(sub_task_type));
Ok(m)
}
#[command]
pub fn remove_sub_task(
mission: MissionPlan,
task_id: u32,
sub_task_id: String,
) -> Result<MissionPlan, AppError> {
let mut m = mission;
let task = m.tasks.iter_mut()
.find(|t| t.id == task_id)
.ok_or(AppError::TaskNotFound(task_id))?;
let pos = task.sub_tasks.iter().position(|s| s.id == sub_task_id)
.ok_or(AppError::SubTaskNotFound(sub_task_id))?;
task.sub_tasks.remove(pos);
Ok(m)
}
#[command]
pub fn update_sub_task(
mission: MissionPlan,
task_id: u32,
sub_task: SubTask,
) -> Result<MissionPlan, AppError> {
let mut m = mission;
let task = m.tasks.iter_mut()
.find(|t| t.id == task_id)
.ok_or(AppError::TaskNotFound(task_id))?;
let pos = task.sub_tasks.iter().position(|s| s.id == sub_task.id)
.ok_or(AppError::SubTaskNotFound(sub_task.id.clone()))?;
task.sub_tasks[pos] = sub_task;
Ok(m)
}
#[command]
pub fn update_task(
mission: MissionPlan,
task: Task,
) -> Result<MissionPlan, AppError> {
let mut m = mission;
let pos = m.tasks.iter().position(|t| t.id == task.id)
.ok_or(AppError::TaskNotFound(task.id))?;
m.tasks[pos] = task;
Ok(m)
}
#[command]
pub fn reorder_sub_tasks(
mission: MissionPlan,
task_id: u32,
from_index: usize,
to_index: usize,
) -> Result<MissionPlan, AppError> {
let mut m = mission;
let task = m.tasks.iter_mut()
.find(|t| t.id == task_id)
.ok_or(AppError::TaskNotFound(task_id))?;
if from_index >= task.sub_tasks.len() || to_index >= task.sub_tasks.len() {
return Err(AppError::Validation("Index out of bounds".into()));
}
let sub = task.sub_tasks.remove(from_index);
task.sub_tasks.insert(to_index, sub);
Ok(m)
}
#[command]
pub fn validate_sub_task_order(
mission: MissionPlan,
task_id: u32,
) -> Result<bool, AppError> {
let task = mission.tasks.iter()
.find(|t| t.id == task_id)
.ok_or(AppError::TaskNotFound(task_id))?;
let mut found_camera = false;
for sub in &task.sub_tasks {
if sub.sub_task_type.is_camera_type() {
found_camera = true;
} else if found_camera && sub.sub_task_type.is_hyperspectral() {
return Ok(false);
}
}
Ok(true)
}