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:
163
src-tauri/src/commands/task_commands.rs
Normal file
163
src-tauri/src/commands/task_commands.rs
Normal 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)
|
||||
}
|
||||
Reference in New Issue
Block a user