feat: add circle drawing support in path planner
This commit is contained in:
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "happa-mission-plan",
|
"name": "happa-mission-plan",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.1.0",
|
"version": "0.0.3",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "spectral-insight-mission-plan"
|
name = "spectral-insight-mission-plan"
|
||||||
version = "0.0.2"
|
version = "0.0.3"
|
||||||
description = "Spectral Insight Mission Plan"
|
description = "Spectral Insight Mission Plan"
|
||||||
authors = ["you"]
|
authors = ["you"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|||||||
@ -27,9 +27,11 @@ pub fn estimate_scan_time_minutes(params: ScanParams) -> f64 {
|
|||||||
services::path_generator::estimate_scan_time(¶ms)
|
services::path_generator::estimate_scan_time(¶ms)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[command]
|
#[command]
|
||||||
pub fn generate_scan_paths_multi(
|
pub fn generate_scan_paths_multi(
|
||||||
regions: Vec<ScanRegion>,
|
regions: Vec<ScanRegion>,
|
||||||
|
shapes: Vec<String>,
|
||||||
camera: CameraParams,
|
camera: CameraParams,
|
||||||
coverage_rate: f64,
|
coverage_rate: f64,
|
||||||
speed_y_cm_s: f64,
|
speed_y_cm_s: f64,
|
||||||
@ -38,7 +40,7 @@ pub fn generate_scan_paths_multi(
|
|||||||
mode: ScanMode,
|
mode: ScanMode,
|
||||||
) -> Result<PathLineFile, AppError> {
|
) -> Result<PathLineFile, AppError> {
|
||||||
let records = services::path_generator::generate_paths_multi(
|
let records = services::path_generator::generate_paths_multi(
|
||||||
®ions, &camera, coverage_rate,
|
®ions, &shapes, &camera, coverage_rate,
|
||||||
speed_y_cm_s, speed_x_start_cm_s, speed_x_scan_cm_s, &mode,
|
speed_y_cm_s, speed_x_start_cm_s, speed_x_scan_cm_s, &mode,
|
||||||
);
|
);
|
||||||
let count = records.len() as u64;
|
let count = records.len() as u64;
|
||||||
@ -48,6 +50,7 @@ pub fn generate_scan_paths_multi(
|
|||||||
#[command]
|
#[command]
|
||||||
pub fn estimate_scan_time_multi_minutes(
|
pub fn estimate_scan_time_multi_minutes(
|
||||||
regions: Vec<ScanRegion>,
|
regions: Vec<ScanRegion>,
|
||||||
|
shapes: Vec<String>,
|
||||||
camera: CameraParams,
|
camera: CameraParams,
|
||||||
coverage_rate: f64,
|
coverage_rate: f64,
|
||||||
speed_y_cm_s: f64,
|
speed_y_cm_s: f64,
|
||||||
@ -56,7 +59,7 @@ pub fn estimate_scan_time_multi_minutes(
|
|||||||
mode: ScanMode,
|
mode: ScanMode,
|
||||||
) -> f64 {
|
) -> f64 {
|
||||||
services::path_generator::estimate_scan_time_multi(
|
services::path_generator::estimate_scan_time_multi(
|
||||||
®ions, &camera, coverage_rate,
|
®ions, &shapes, &camera, coverage_rate,
|
||||||
speed_y_cm_s, speed_x_start_cm_s, speed_x_scan_cm_s, &mode,
|
speed_y_cm_s, speed_x_start_cm_s, speed_x_scan_cm_s, &mode,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -103,8 +103,10 @@ pub fn estimate_scan_time(params: &ScanParams) -> f64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Generate paths for multiple regions, concatenated into one record list.
|
/// Generate paths for multiple regions, concatenated into one record list.
|
||||||
|
|
||||||
pub fn generate_paths_multi(
|
pub fn generate_paths_multi(
|
||||||
regions: &[ScanRegion],
|
regions: &[ScanRegion],
|
||||||
|
shapes: &[String],
|
||||||
camera: &crate::models::CameraParams,
|
camera: &crate::models::CameraParams,
|
||||||
coverage_rate: f64,
|
coverage_rate: f64,
|
||||||
speed_y_cm_s: f64,
|
speed_y_cm_s: f64,
|
||||||
@ -113,24 +115,54 @@ pub fn generate_paths_multi(
|
|||||||
mode: &ScanMode,
|
mode: &ScanMode,
|
||||||
) -> Vec<PathLineRecord> {
|
) -> Vec<PathLineRecord> {
|
||||||
let mut all_records = Vec::new();
|
let mut all_records = Vec::new();
|
||||||
for region in regions {
|
for (i, region) in regions.iter().enumerate() {
|
||||||
let params = ScanParams {
|
let is_circle = shapes.get(i).map(|s| s == "Circle").unwrap_or(false);
|
||||||
region: region.clone(),
|
let step = calc_footprint(camera.height_cm, camera.fov_degrees)
|
||||||
camera: camera.clone(),
|
* (1.0 - coverage_rate / 100.0);
|
||||||
coverage_rate,
|
let line_count = if step <= 0.0 { 1 } else { ((region.y_max - region.y_min) / step).ceil() as u64 + 1 };
|
||||||
speed_y_cm_s,
|
|
||||||
speed_x_start_cm_s,
|
for li in 0..line_count {
|
||||||
speed_x_scan_cm_s,
|
let y = region.y_min + (li as f64) * step;
|
||||||
mode: mode.clone(),
|
if y > region.y_max { break; }
|
||||||
};
|
|
||||||
all_records.extend(generate_path(¶ms));
|
let (x_start, x_end) = if is_circle {
|
||||||
|
let cx = (region.x_min + region.x_max) / 2.0;
|
||||||
|
let cy = (region.y_min + region.y_max) / 2.0;
|
||||||
|
let rx = (region.x_max - region.x_min) / 2.0;
|
||||||
|
let ry = (region.y_max - region.y_min) / 2.0;
|
||||||
|
// Ellipse: ((x-cx)/rx)^2 + ((y-cy)/ry)^2 = 1
|
||||||
|
let dy = (y - cy) / ry;
|
||||||
|
if dy.abs() > 1.0 { continue; } // outside circle
|
||||||
|
let half_w = rx * (1.0 - dy * dy).sqrt();
|
||||||
|
(cx - half_w, cx + half_w)
|
||||||
|
} else {
|
||||||
|
(region.x_min, region.x_max)
|
||||||
|
};
|
||||||
|
|
||||||
|
let is_forward = li % 2 == 0;
|
||||||
|
let (scan_start, scan_end) = match mode {
|
||||||
|
ScanMode::Zigzag => {
|
||||||
|
if is_forward { (x_start, x_end) } else { (x_end, x_start) }
|
||||||
|
}
|
||||||
|
ScanMode::OneWay => (x_start, x_end),
|
||||||
|
};
|
||||||
|
|
||||||
|
all_records.push(PathLineRecord {
|
||||||
|
target_y_position: y,
|
||||||
|
speed_target_y_position: speed_y_cm_s,
|
||||||
|
target_x_min_position: scan_start,
|
||||||
|
speed_target_x_min_position: speed_x_start_cm_s,
|
||||||
|
target_x_max_position: scan_end,
|
||||||
|
speed_target_x_max_position: speed_x_scan_cm_s,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
all_records
|
all_records
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Estimate total time for multiple regions.
|
|
||||||
pub fn estimate_scan_time_multi(
|
pub fn estimate_scan_time_multi(
|
||||||
regions: &[ScanRegion],
|
regions: &[ScanRegion],
|
||||||
|
shapes: &[String],
|
||||||
camera: &crate::models::CameraParams,
|
camera: &crate::models::CameraParams,
|
||||||
coverage_rate: f64,
|
coverage_rate: f64,
|
||||||
speed_y_cm_s: f64,
|
speed_y_cm_s: f64,
|
||||||
@ -138,18 +170,13 @@ pub fn estimate_scan_time_multi(
|
|||||||
speed_x_scan_cm_s: f64,
|
speed_x_scan_cm_s: f64,
|
||||||
mode: &ScanMode,
|
mode: &ScanMode,
|
||||||
) -> f64 {
|
) -> f64 {
|
||||||
let mut total = 0.0;
|
let records = generate_paths_multi(regions, shapes, camera, coverage_rate,
|
||||||
for region in regions {
|
speed_y_cm_s, speed_x_start_cm_s, speed_x_scan_cm_s, mode);
|
||||||
let params = ScanParams {
|
if records.is_empty() { return 0.0; }
|
||||||
region: region.clone(),
|
|
||||||
camera: camera.clone(),
|
let total_x = records.len() as f64 * (regions.iter().map(|r| (r.x_max - r.x_min).abs()).sum::<f64>() / regions.len() as f64) / speed_x_scan_cm_s;
|
||||||
coverage_rate,
|
let return_time = if matches!(mode, ScanMode::OneWay) {
|
||||||
speed_y_cm_s,
|
(records.len() as f64 - 1.0) * speed_x_scan_cm_s / speed_x_start_cm_s.max(0.01)
|
||||||
speed_x_start_cm_s,
|
} else { 0.0 };
|
||||||
speed_x_scan_cm_s,
|
(total_x + return_time) / 60.0
|
||||||
mode: mode.clone(),
|
|
||||||
};
|
|
||||||
total += estimate_scan_time(¶ms);
|
|
||||||
}
|
|
||||||
total
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://schema.tauri.app/config/2",
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
"productName": "Spectral Insight Mission Plan",
|
"productName": "Spectral Insight Mission Plan",
|
||||||
"version": "0.0.2",
|
"version": "0.0.3",
|
||||||
"identifier": "com.spectral-insight.mission-plan",
|
"identifier": "com.spectral-insight.mission-plan",
|
||||||
"build": {
|
"build": {
|
||||||
"beforeDevCommand": "npm run dev",
|
"beforeDevCommand": "npm run dev",
|
||||||
|
|||||||
@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, watch } from 'vue';
|
import { ref, onMounted, watch } from 'vue';
|
||||||
import type { ScanRegion, ScanMode } from '../../types/path-plan';
|
import type { ScanRegion, ScanMode, ScanShape } from '../../types/path-plan';
|
||||||
import type { PathLineRecord } from '../../types/path-line';
|
import type { PathLineRecord } from '../../types/path-line';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
@ -27,6 +27,7 @@ const props = defineProps<{
|
|||||||
mode: ScanMode;
|
mode: ScanMode;
|
||||||
backgroundImage?: string;
|
backgroundImage?: string;
|
||||||
drawingMode: boolean;
|
drawingMode: boolean;
|
||||||
|
drawingShape?: ScanShape;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
@ -114,31 +115,51 @@ function draw() {
|
|||||||
const x = padding + (i / 10) * drawW;
|
const x = padding + (i / 10) * drawW;
|
||||||
const y = padding + (i / 10) * drawH;
|
const y = padding + (i / 10) * drawH;
|
||||||
ctx.beginPath(); ctx.moveTo(x, padding); ctx.lineTo(x, padding + drawH); ctx.stroke();
|
ctx.beginPath(); ctx.moveTo(x, padding); ctx.lineTo(x, padding + drawH); ctx.stroke();
|
||||||
ctx.beginPath(); ctx.moveTo(padding, y); ctx.lineTo(padding + drawW, y); ctx.stroke();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw all rectangles
|
// Draw all regions (rect or circle)
|
||||||
for (let i = 0; i < props.regions.length; i++) {
|
for (let i = 0; i < props.regions.length; i++) {
|
||||||
const r = props.regions[i];
|
const r = props.regions[i];
|
||||||
const p1 = worldToCanvas(r.xMin, r.yMin);
|
const p1 = worldToCanvas(r.xMin, r.yMin);
|
||||||
const p2 = worldToCanvas(r.xMax, r.yMax);
|
const p2 = worldToCanvas(r.xMax, r.yMax);
|
||||||
ctx.strokeStyle = COLORS[i % COLORS.length];
|
ctx.strokeStyle = COLORS[i % COLORS.length];
|
||||||
ctx.lineWidth = 2;
|
ctx.lineWidth = 2;
|
||||||
ctx.strokeRect(p1.x, p1.y, p2.x - p1.x, p2.y - p1.y);
|
if (r.shape === 'Circle') {
|
||||||
|
const cx = (p1.x + p2.x) / 2;
|
||||||
|
const cy = (p1.y + p2.y) / 2;
|
||||||
|
const rx = Math.abs(p2.x - p1.x) / 2;
|
||||||
|
const ry = Math.abs(p2.y - p1.y) / 2;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.ellipse(cx, cy, rx, ry, 0, 0, Math.PI * 2);
|
||||||
|
ctx.stroke();
|
||||||
|
} else {
|
||||||
|
ctx.strokeRect(p1.x, p1.y, p2.x - p1.x, p2.y - p1.y);
|
||||||
|
}
|
||||||
// Label
|
// Label
|
||||||
ctx.fillStyle = COLORS[i % COLORS.length];
|
ctx.fillStyle = COLORS[i % COLORS.length];
|
||||||
ctx.font = 'bold 12px sans-serif';
|
ctx.font = 'bold 12px sans-serif';
|
||||||
ctx.fillText(`#${i + 1}`, p1.x + 4, p1.y - 4);
|
ctx.fillText(`#${i + 1}`, p1.x + 4, p1.y - 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw current drawing rect
|
// Draw current drawing shape
|
||||||
if (drawingRect.value) {
|
if (drawingRect.value) {
|
||||||
const p1 = worldToCanvas(drawingRect.value.xMin, drawingRect.value.yMin);
|
const p1 = worldToCanvas(drawingRect.value.xMin, drawingRect.value.yMin);
|
||||||
const p2 = worldToCanvas(drawingRect.value.xMax, drawingRect.value.yMax);
|
const p2 = worldToCanvas(drawingRect.value.xMax, drawingRect.value.yMax);
|
||||||
ctx.strokeStyle = '#f00';
|
ctx.strokeStyle = '#f00';
|
||||||
ctx.lineWidth = 2;
|
ctx.lineWidth = 2;
|
||||||
ctx.setLineDash([5, 5]);
|
ctx.setLineDash([5, 5]);
|
||||||
ctx.strokeRect(p1.x, p1.y, p2.x - p1.x, p2.y - p1.y);
|
if (props.drawingShape === 'Circle') {
|
||||||
|
const cx = (p1.x + p2.x) / 2;
|
||||||
|
const cy = (p1.y + p2.y) / 2;
|
||||||
|
const rx = Math.abs(p2.x - p1.x) / 2;
|
||||||
|
const ry = Math.abs(p2.y - p1.y) / 2;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.ellipse(cx, cy, rx, ry, 0, 0, Math.PI * 2);
|
||||||
|
ctx.stroke();
|
||||||
|
} else {
|
||||||
|
ctx.strokeRect(p1.x, p1.y, p2.x - p1.x, p2.y - p1.y);
|
||||||
|
}
|
||||||
|
ctx.setLineDash([]);
|
||||||
ctx.setLineDash([]);
|
ctx.setLineDash([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -171,6 +192,7 @@ function onMouseMove(e: MouseEvent) {
|
|||||||
const p1 = toWorld(dragStart.value.x, dragStart.value.y);
|
const p1 = toWorld(dragStart.value.x, dragStart.value.y);
|
||||||
const p2 = toWorld(p.x, p.y);
|
const p2 = toWorld(p.x, p.y);
|
||||||
drawingRect.value = {
|
drawingRect.value = {
|
||||||
|
shape: props.drawingShape ? props.drawingShape : "Rect",
|
||||||
xMin: Math.min(p1.x, p2.x), xMax: Math.max(p1.x, p2.x),
|
xMin: Math.min(p1.x, p2.x), xMax: Math.max(p1.x, p2.x),
|
||||||
yMin: Math.min(p1.y, p2.y), yMax: Math.max(p1.y, p2.y),
|
yMin: Math.min(p1.y, p2.y), yMax: Math.max(p1.y, p2.y),
|
||||||
};
|
};
|
||||||
@ -183,7 +205,7 @@ function onMouseUp() {
|
|||||||
if (drawingRect.value) {
|
if (drawingRect.value) {
|
||||||
const r = drawingRect.value;
|
const r = drawingRect.value;
|
||||||
if (r.xMax - r.xMin > 1 && r.yMax - r.yMin > 1) {
|
if (r.xMax - r.xMin > 1 && r.yMax - r.yMin > 1) {
|
||||||
emit('add-region', r);
|
emit('add-region', { ...r, shape: props.drawingShape ? props.drawingShape : 'Rect' });
|
||||||
}
|
}
|
||||||
drawingRect.value = null;
|
drawingRect.value = null;
|
||||||
draw();
|
draw();
|
||||||
@ -195,7 +217,7 @@ onMounted(() => {
|
|||||||
window.addEventListener('resize', draw);
|
window.addEventListener('resize', draw);
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(() => [props.regions, props.records, props.mode, props.drawingMode], () => { draw(); });
|
watch(() => [props.regions, props.records, props.mode, props.drawingMode, props.drawingShape], () => { draw(); });
|
||||||
watch(() => props.backgroundImage, (v) => { loadBackgroundImage(v); });
|
watch(() => props.backgroundImage, (v) => { loadBackgroundImage(v); });
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,11 @@
|
|||||||
|
export type ScanShape = 'Rect' | 'Circle';
|
||||||
|
|
||||||
export interface ScanRegion {
|
export interface ScanRegion {
|
||||||
xMin: number;
|
xMin: number;
|
||||||
xMax: number;
|
xMax: number;
|
||||||
yMin: number;
|
yMin: number;
|
||||||
yMax: number;
|
yMax: number;
|
||||||
|
shape: ScanShape;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CameraParams {
|
export interface CameraParams {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="home">
|
<div class="home">
|
||||||
<n-space vertical size="large" class="home-content">
|
<n-space vertical size="large" class="home-content">
|
||||||
<h1>Happa Mission Plan</h1>
|
<h1>Spectral Insight Mission Plan</h1>
|
||||||
<p class="subtitle">设备自动采集任务规划系统</p>
|
<p class="subtitle">设备自动采集任务规划系统</p>
|
||||||
|
|
||||||
<n-space size="large">
|
<n-space size="large">
|
||||||
|
|||||||
@ -5,9 +5,13 @@
|
|||||||
<template #icon><n-icon><ArrowBackOutline /></n-icon></template>
|
<template #icon><n-icon><ArrowBackOutline /></n-icon></template>
|
||||||
返回
|
返回
|
||||||
</n-button>
|
</n-button>
|
||||||
<n-button size="small" :type="drawingMode ? 'warning' : 'default'" @click="toggleDrawing">
|
<n-button size="small" :type="drawingMode && currentShape === 'Rect' ? 'warning' : 'default'" @click="startDraw('Rect')">
|
||||||
<template #icon><n-icon><SquareOutline /></n-icon></template>
|
<template #icon><n-icon><SquareOutline /></n-icon></template>
|
||||||
{{ drawingMode ? '完成画框' : '画框' }}
|
画框
|
||||||
|
</n-button>
|
||||||
|
<n-button size="small" :type="drawingMode && currentShape === 'Circle' ? 'warning' : 'default'" @click="startDraw('Circle')">
|
||||||
|
<template #icon><n-icon><EllipseOutline /></n-icon></template>
|
||||||
|
画圆
|
||||||
</n-button>
|
</n-button>
|
||||||
<n-button size="small" @click="clearRects" :disabled="regions.length === 0">
|
<n-button size="small" @click="clearRects" :disabled="regions.length === 0">
|
||||||
<template #icon><n-icon><TrashOutline /></n-icon></template>
|
<template #icon><n-icon><TrashOutline /></n-icon></template>
|
||||||
@ -31,6 +35,7 @@
|
|||||||
:mode="scanMode"
|
:mode="scanMode"
|
||||||
:background-image="missionStore.mission.backgroundImage || undefined"
|
:background-image="missionStore.mission.backgroundImage || undefined"
|
||||||
:drawing-mode="drawingMode"
|
:drawing-mode="drawingMode"
|
||||||
|
:drawing-shape="currentShape"
|
||||||
@add-region="onAddRegion"
|
@add-region="onAddRegion"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
@ -55,13 +60,13 @@
|
|||||||
import { ref, computed } from 'vue';
|
import { ref, computed } from 'vue';
|
||||||
import { useRouter, useRoute } from 'vue-router';
|
import { useRouter, useRoute } from 'vue-router';
|
||||||
import { useMessage } from 'naive-ui';
|
import { useMessage } from 'naive-ui';
|
||||||
import { ArrowBackOutline, SquareOutline, TrashOutline, MapOutline, SaveOutline } from '@vicons/ionicons5';
|
import { ArrowBackOutline, SquareOutline, EllipseOutline, TrashOutline, MapOutline, SaveOutline } from '@vicons/ionicons5';
|
||||||
import { invoke } from '@tauri-apps/api/core';
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
import ScanCanvas from '../components/planner/ScanCanvas.vue';
|
import ScanCanvas from '../components/planner/ScanCanvas.vue';
|
||||||
import ScanParamPanel from '../components/planner/ScanParamPanel.vue';
|
import ScanParamPanel from '../components/planner/ScanParamPanel.vue';
|
||||||
import { useMissionStore } from '../stores/mission';
|
import { useMissionStore } from '../stores/mission';
|
||||||
import type { PathLineRecord, PathLineFile } from '../types/path-line';
|
import type { PathLineRecord, PathLineFile } from '../types/path-line';
|
||||||
import type { ScanRegion, CameraParams, ScanMode } from '../types/path-plan';
|
import type { ScanRegion, CameraParams, ScanMode, ScanShape } from '../types/path-plan';
|
||||||
import type { PlannerDefaults } from '../types/mission';
|
import type { PlannerDefaults } from '../types/mission';
|
||||||
import { DEVICE_FOV } from '../utils/constants';
|
import { DEVICE_FOV } from '../utils/constants';
|
||||||
|
|
||||||
@ -74,6 +79,7 @@ const taskId = route.query.taskId ? Number(route.query.taskId) : null;
|
|||||||
const subTaskId = route.query.subTaskId ? String(route.query.subTaskId) : null;
|
const subTaskId = route.query.subTaskId ? String(route.query.subTaskId) : null;
|
||||||
|
|
||||||
const drawingMode = ref(false);
|
const drawingMode = ref(false);
|
||||||
|
const currentShape = ref<ScanShape>('Rect');
|
||||||
|
|
||||||
function getDeviceFov(): number {
|
function getDeviceFov(): number {
|
||||||
if (taskId === null || subTaskId === null) return 30;
|
if (taskId === null || subTaskId === null) return 30;
|
||||||
@ -104,11 +110,17 @@ invoke<PlannerDefaults>('load_planner_defaults').then(p => {
|
|||||||
camera.value = { fovDegrees: camera.value.fovDegrees, heightCm: p.heightCm };
|
camera.value = { fovDegrees: camera.value.fovDegrees, heightCm: p.heightCm };
|
||||||
}).catch(() => {});
|
}).catch(() => {});
|
||||||
|
|
||||||
function toggleDrawing() {
|
function startDraw(shape: ScanShape) {
|
||||||
drawingMode.value = !drawingMode.value;
|
if (drawingMode.value && currentShape.value === shape) {
|
||||||
|
drawingMode.value = false;
|
||||||
|
} else {
|
||||||
|
drawingMode.value = true;
|
||||||
|
currentShape.value = shape;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onAddRegion(region: ScanRegion) {
|
function onAddRegion(region: ScanRegion) {
|
||||||
|
region.shape = currentShape.value;
|
||||||
regions.value.push(region);
|
regions.value.push(region);
|
||||||
message.success(`已添加框 #${regions.value.length}`);
|
message.success(`已添加框 #${regions.value.length}`);
|
||||||
}
|
}
|
||||||
@ -148,6 +160,7 @@ async function generatePath() {
|
|||||||
try {
|
try {
|
||||||
const file = await invoke<PathLineFile>('generate_scan_paths_multi', {
|
const file = await invoke<PathLineFile>('generate_scan_paths_multi', {
|
||||||
regions: regions.value,
|
regions: regions.value,
|
||||||
|
shapes: regions.value.map(r => r.shape),
|
||||||
camera: camera.value,
|
camera: camera.value,
|
||||||
coverageRate: coverageRate.value,
|
coverageRate: coverageRate.value,
|
||||||
speedYCmS: speedY.value,
|
speedYCmS: speedY.value,
|
||||||
@ -158,6 +171,7 @@ async function generatePath() {
|
|||||||
generatedRecords.value = file.records;
|
generatedRecords.value = file.records;
|
||||||
const estTime = await invoke<number>('estimate_scan_time_multi_minutes', {
|
const estTime = await invoke<number>('estimate_scan_time_multi_minutes', {
|
||||||
regions: regions.value,
|
regions: regions.value,
|
||||||
|
shapes: regions.value.map(r => r.shape),
|
||||||
camera: camera.value,
|
camera: camera.value,
|
||||||
coverageRate: coverageRate.value,
|
coverageRate: coverageRate.value,
|
||||||
speedYCmS: speedY.value,
|
speedYCmS: speedY.value,
|
||||||
|
|||||||
@ -1,5 +1,9 @@
|
|||||||
# Spectral Insight Mission Plan - 更新日志
|
# Spectral Insight Mission Plan - 更新日志
|
||||||
|
|
||||||
|
## v0.0.3 (2026-06-18)
|
||||||
|
|
||||||
|
- docs: add 说明书.md
|
||||||
|
|
||||||
## v0.0.2
|
## v0.0.2
|
||||||
|
|
||||||
- 路径查看弹窗改为全屏双栏布局(左侧表格 + 右侧 Canvas 画布)
|
- 路径查看弹窗改为全屏双栏布局(左侧表格 + 右侧 Canvas 画布)
|
||||||
|
|||||||
Reference in New Issue
Block a user