feat: 新增约束区类型及最大可容纳AMR数配置,优化场景编辑功能
This commit is contained in:
parent
0479943204
commit
33f665d5fd
@ -102,6 +102,8 @@ export enum MapAreaType {
|
|||||||
互斥区 = 11,
|
互斥区 = 11,
|
||||||
/** 非互斥区 - 可以同时有多个机器人进入的区域 */
|
/** 非互斥区 - 可以同时有多个机器人进入的区域 */
|
||||||
非互斥区,
|
非互斥区,
|
||||||
|
/** 约束区 - 机器人运动受到特定约束的区域 */
|
||||||
|
约束区,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -43,6 +43,7 @@ export interface MapAreaInfo {
|
|||||||
type: MapAreaType; // 区域类型
|
type: MapAreaType; // 区域类型
|
||||||
points?: Array<string>; // 绑定点位id集合
|
points?: Array<string>; // 绑定点位id集合
|
||||||
routes?: Array<string>; // 绑定线路id集合
|
routes?: Array<string>; // 绑定线路id集合
|
||||||
|
maxAmr?: number; // 最大可容纳AMR数
|
||||||
}
|
}
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
|
@ -64,6 +64,7 @@ export interface StandardSceneArea {
|
|||||||
type: number; // 区域类型
|
type: number; // 区域类型
|
||||||
points?: Array<string>; // 绑定点位id集合
|
points?: Array<string>; // 绑定点位id集合
|
||||||
routes?: Array<string>; // 绑定线路id集合
|
routes?: Array<string>; // 绑定线路id集合
|
||||||
|
maxAmr?: number; // 最大可容纳AMR数
|
||||||
config?: object; // 其它属性配置(可按需增加)
|
config?: object; // 其它属性配置(可按需增加)
|
||||||
properties?: unknown; // 附加数据(前端不做任何处理)
|
properties?: unknown; // 附加数据(前端不做任何处理)
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,9 @@ $icons: (
|
|||||||
area12-active,
|
area12-active,
|
||||||
area12-detail,
|
area12-detail,
|
||||||
area12,
|
area12,
|
||||||
|
area13-active,
|
||||||
|
area13-detail,
|
||||||
|
area13,
|
||||||
battery_charge,
|
battery_charge,
|
||||||
battery,
|
battery,
|
||||||
connect_off,
|
connect_off,
|
||||||
|
BIN
src/assets/icons/dark/area13-active.png
Normal file
BIN
src/assets/icons/dark/area13-active.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 660 B |
BIN
src/assets/icons/dark/area13-detail.png
Normal file
BIN
src/assets/icons/dark/area13-detail.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 590 B |
BIN
src/assets/icons/dark/area13.png
Normal file
BIN
src/assets/icons/dark/area13.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 578 B |
@ -28,6 +28,8 @@
|
|||||||
"fill-11": "#FF9A9A33",
|
"fill-11": "#FF9A9A33",
|
||||||
"stroke-12": "#0DBB8A99",
|
"stroke-12": "#0DBB8A99",
|
||||||
"fill-12": "#0DBB8A33",
|
"fill-12": "#0DBB8A33",
|
||||||
|
"stroke-13": "#e61e4aad",
|
||||||
|
"fill-13": "#e61e4a33",
|
||||||
"strokeActive": "#FCC947"
|
"strokeActive": "#FCC947"
|
||||||
},
|
},
|
||||||
"robot": {
|
"robot": {
|
||||||
|
@ -65,6 +65,21 @@ const routes = computed<string[]>(
|
|||||||
</a-input>
|
</a-input>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
|
<a-row v-if="area.type === MapAreaType.约束区" :gutter="[8, 8]">
|
||||||
|
<a-col :span="24">
|
||||||
|
<a-typography-text>{{ $t('最大可容纳AMR数') }}:</a-typography-text>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="24">
|
||||||
|
<a-input-number
|
||||||
|
class="full"
|
||||||
|
:placeholder="$t('请输入数量')"
|
||||||
|
:min="0"
|
||||||
|
:max="99"
|
||||||
|
:value="pen.area?.maxAmr"
|
||||||
|
@change="editor.updateArea(id, { maxAmr: $event as number })"
|
||||||
|
/>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
|
||||||
<a-row :gutter="[8, 8]">
|
<a-row :gutter="[8, 8]">
|
||||||
<a-col :span="24">
|
<a-col :span="24">
|
||||||
@ -108,7 +123,7 @@ const routes = computed<string[]>(
|
|||||||
</a-collapse-panel>
|
</a-collapse-panel>
|
||||||
|
|
||||||
<a-collapse-panel
|
<a-collapse-panel
|
||||||
v-if="[MapAreaType.互斥区, MapAreaType.非互斥区].includes(area.type)"
|
v-if="[MapAreaType.互斥区, MapAreaType.非互斥区, MapAreaType.约束区].includes(area.type)"
|
||||||
:header="$t('绑定站点')"
|
:header="$t('绑定站点')"
|
||||||
>
|
>
|
||||||
<template #extra>
|
<template #extra>
|
||||||
|
@ -42,7 +42,7 @@ const canDelete = computed<boolean>(() => editor.value.current.value?.name === '
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<a-space class="toolbar" :size="0">
|
<a-space class="toolbar" :size="0">
|
||||||
<a-button
|
<!-- <a-button
|
||||||
class="icon-btn tool-btn"
|
class="icon-btn tool-btn"
|
||||||
:class="{ active: mode === MapAreaType.库区 }"
|
:class="{ active: mode === MapAreaType.库区 }"
|
||||||
size="large"
|
size="large"
|
||||||
@ -50,6 +50,15 @@ const canDelete = computed<boolean>(() => editor.value.current.value?.name === '
|
|||||||
@click="mode = MapAreaType.库区"
|
@click="mode = MapAreaType.库区"
|
||||||
>
|
>
|
||||||
<i class="icon" :class="mode === MapAreaType.库区 ? 'area1-active' : 'area1'" />
|
<i class="icon" :class="mode === MapAreaType.库区 ? 'area1-active' : 'area1'" />
|
||||||
|
</a-button> -->
|
||||||
|
<a-button
|
||||||
|
class="icon-btn tool-btn"
|
||||||
|
:class="{ active: mode === MapAreaType.约束区 }"
|
||||||
|
size="large"
|
||||||
|
:title="$t('添加约束区')"
|
||||||
|
@click="mode = MapAreaType.约束区"
|
||||||
|
>
|
||||||
|
<i class="icon" :class="mode === MapAreaType.约束区 ? 'area1-active' : 'area1'" />
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-button
|
<a-button
|
||||||
class="icon-btn tool-btn ml-12"
|
class="icon-btn tool-btn ml-12"
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
import type { RobotRealtimeInfo } from '@api/robot';
|
import type { RobotRealtimeInfo } from '@api/robot';
|
||||||
import { getSceneByGroupId, getSceneById, monitorSceneById } from '@api/scene';
|
import { getSceneByGroupId, getSceneById, monitorSceneById } from '@api/scene';
|
||||||
import { EditorService } from '@core/editor.service';
|
import { EditorService } from '@core/editor.service';
|
||||||
|
import { useViewState } from '@core/useViewState';
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
import { isNil } from 'lodash-es';
|
import { isNil } from 'lodash-es';
|
||||||
import { computed, onMounted, onUnmounted, provide, ref, shallowRef, watch } from 'vue';
|
import { computed, onMounted, onUnmounted, provide, ref, shallowRef, watch } from 'vue';
|
||||||
|
|
||||||
@ -52,6 +54,8 @@ onMounted(async () => {
|
|||||||
await readScene();
|
await readScene();
|
||||||
await editor.value?.initRobots();
|
await editor.value?.initRobots();
|
||||||
await monitorScene();
|
await monitorScene();
|
||||||
|
// 自动恢复视图状态
|
||||||
|
await handleRestoreViewState();
|
||||||
});
|
});
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
client.value?.close();
|
client.value?.close();
|
||||||
@ -80,6 +84,32 @@ const selectRobot = (id: string) => {
|
|||||||
current.value = { type: 'robot', id };
|
current.value = { type: 'robot', id };
|
||||||
editor.value?.inactive();
|
editor.value?.inactive();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 视图状态管理
|
||||||
|
const { saveViewState, restoreViewState, isSaving } = useViewState();
|
||||||
|
|
||||||
|
// 保存当前视图状态
|
||||||
|
const handleSaveViewState = async () => {
|
||||||
|
if (!editor.value) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await saveViewState(editor.value, props.sid, props.id);
|
||||||
|
message.success('视图状态保存成功');
|
||||||
|
} catch {
|
||||||
|
message.error('视图状态保存失败');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 恢复视图状态
|
||||||
|
const handleRestoreViewState = async () => {
|
||||||
|
if (!editor.value) return;
|
||||||
|
|
||||||
|
const restored = await restoreViewState(editor.value, props.sid, props.id);
|
||||||
|
if (!restored) {
|
||||||
|
// 如果没有保存的状态,使用默认的centerView
|
||||||
|
editor.value.centerView();
|
||||||
|
}
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -87,6 +117,7 @@ const selectRobot = (id: string) => {
|
|||||||
<a-layout-header class="p-16" style="height: 64px">
|
<a-layout-header class="p-16" style="height: 64px">
|
||||||
<a-flex justify="space-between" align="center">
|
<a-flex justify="space-between" align="center">
|
||||||
<a-typography-text class="title">{{ title }}--场景仿真</a-typography-text>
|
<a-typography-text class="title">{{ title }}--场景仿真</a-typography-text>
|
||||||
|
<!-- <a-button type="primary" :loading="isSaving" @click="handleSaveViewState"> 保存比例 </a-button> -->
|
||||||
</a-flex>
|
</a-flex>
|
||||||
</a-layout-header>
|
</a-layout-header>
|
||||||
|
|
||||||
@ -96,9 +127,9 @@ const selectRobot = (id: string) => {
|
|||||||
<a-tab-pane key="1" :tab="$t('机器人')">
|
<a-tab-pane key="1" :tab="$t('机器人')">
|
||||||
<RobotGroups v-if="editor" :token="EDITOR_KEY" :sid="sid" :current="current?.id" @change="selectRobot" />
|
<RobotGroups v-if="editor" :token="EDITOR_KEY" :sid="sid" :current="current?.id" @change="selectRobot" />
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="2" :tab="$t('库区')">
|
<!-- <a-tab-pane key="2" :tab="$t('库区')">
|
||||||
<PenGroups v-if="editor" :token="EDITOR_KEY" :current="current?.id" only-area1 />
|
<PenGroups v-if="editor" :token="EDITOR_KEY" :current="current?.id" only-area1 />
|
||||||
</a-tab-pane>
|
</a-tab-pane> -->
|
||||||
<a-tab-pane key="3" :tab="$t('高级组')">
|
<a-tab-pane key="3" :tab="$t('高级组')">
|
||||||
<PenGroups v-if="editor" :token="EDITOR_KEY" :current="current?.id" />
|
<PenGroups v-if="editor" :token="EDITOR_KEY" :current="current?.id" />
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
|
@ -29,13 +29,13 @@ const saveScene = async () => {
|
|||||||
if (!json) return Promise.reject('无法获取场景数据');
|
if (!json) return Promise.reject('无法获取场景数据');
|
||||||
const res = await saveSceneById(props.id, json);
|
const res = await saveSceneById(props.id, json);
|
||||||
if (!res) return Promise.reject('保存失败');
|
if (!res) return Promise.reject('保存失败');
|
||||||
|
|
||||||
// 保存成功后重置历史记录状态,表示当前场景已保存
|
// 保存成功后重置历史记录状态,表示当前场景已保存
|
||||||
if (editor.value?.store) {
|
if (editor.value?.store) {
|
||||||
editor.value.store.historyIndex = undefined;
|
editor.value.store.historyIndex = undefined;
|
||||||
editor.value.store.histories = [];
|
editor.value.store.histories = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
message.success(t('场景保存成功'));
|
message.success(t('场景保存成功'));
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
};
|
};
|
||||||
@ -47,7 +47,6 @@ const pushScene = async () => {
|
|||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
const title = ref<string>('');
|
const title = ref<string>('');
|
||||||
@ -162,9 +161,9 @@ const selectRobot = (id: string) => {
|
|||||||
show-group-edit
|
show-group-edit
|
||||||
/>
|
/>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="2" :tab="$t('库区')">
|
<!-- <a-tab-pane key="2" :tab="$t('库区')">
|
||||||
<PenGroups v-if="editor" :token="EDITOR_KEY" :current="current?.id" only-area1 />
|
<PenGroups v-if="editor" :token="EDITOR_KEY" :current="current?.id" only-area1 />
|
||||||
</a-tab-pane>
|
</a-tab-pane> -->
|
||||||
<a-tab-pane key="3" :tab="$t('高级组')">
|
<a-tab-pane key="3" :tab="$t('高级组')">
|
||||||
<PenGroups v-if="editor" :token="EDITOR_KEY" :current="current?.id" />
|
<PenGroups v-if="editor" :token="EDITOR_KEY" :current="current?.id" />
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
|
@ -225,7 +225,7 @@ export class EditorService extends Meta2d {
|
|||||||
#mapSceneArea(pen: MapPen): StandardSceneArea | null {
|
#mapSceneArea(pen: MapPen): StandardSceneArea | null {
|
||||||
if (!pen.id || isEmpty(pen.area)) return null;
|
if (!pen.id || isEmpty(pen.area)) return null;
|
||||||
const { id, label, desc, properties } = pen;
|
const { id, label, desc, properties } = pen;
|
||||||
const { type, points, routes } = pen.area;
|
const { type, points, routes, maxAmr } = pen.area;
|
||||||
const { x, y, width, height } = this.getPenRect(pen);
|
const { x, y, width, height } = this.getPenRect(pen);
|
||||||
// 进行坐标转换:左上角原点 -> 中心点原点,同时应用ratio缩放
|
// 进行坐标转换:左上角原点 -> 中心点原点,同时应用ratio缩放
|
||||||
const transformedCoords = this.#transformCoordinate(x, y);
|
const transformedCoords = this.#transformCoordinate(x, y);
|
||||||
@ -244,7 +244,7 @@ export class EditorService extends Meta2d {
|
|||||||
if (MapAreaType.库区 === type) {
|
if (MapAreaType.库区 === type) {
|
||||||
area.points = points?.filter((v) => this.getPenById(v)?.point?.type === MapPointType.动作点);
|
area.points = points?.filter((v) => this.getPenById(v)?.point?.type === MapPointType.动作点);
|
||||||
}
|
}
|
||||||
if ([MapAreaType.互斥区, MapAreaType.非互斥区].includes(type)) {
|
if ([MapAreaType.互斥区, MapAreaType.非互斥区, MapAreaType.约束区].includes(type)) {
|
||||||
area.points = points?.filter((v) => {
|
area.points = points?.filter((v) => {
|
||||||
const { point } = this.getPenById(v) ?? {};
|
const { point } = this.getPenById(v) ?? {};
|
||||||
if (isNil(point)) return false;
|
if (isNil(point)) return false;
|
||||||
@ -255,6 +255,9 @@ export class EditorService extends Meta2d {
|
|||||||
if (MapAreaType.互斥区 === type) {
|
if (MapAreaType.互斥区 === type) {
|
||||||
area.routes = routes?.filter((v) => !isEmpty(this.getPenById(v)?.area));
|
area.routes = routes?.filter((v) => !isEmpty(this.getPenById(v)?.area));
|
||||||
}
|
}
|
||||||
|
if (MapAreaType.约束区 === type) {
|
||||||
|
area.maxAmr = maxAmr;
|
||||||
|
}
|
||||||
return area;
|
return area;
|
||||||
}
|
}
|
||||||
//#endregion
|
//#endregion
|
||||||
@ -777,6 +780,10 @@ export class EditorService extends Meta2d {
|
|||||||
case MapAreaType.非互斥区:
|
case MapAreaType.非互斥区:
|
||||||
selected?.filter(({ point }) => point?.type).forEach(({ id }) => points.push(id!));
|
selected?.filter(({ point }) => point?.type).forEach(({ id }) => points.push(id!));
|
||||||
break;
|
break;
|
||||||
|
case MapAreaType.约束区:
|
||||||
|
selected?.filter(({ point }) => point?.type).forEach(({ id }) => points.push(id!));
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user