feat: 更新地图点位、路线和区域类型枚举,添加详细注释和配置说明
This commit is contained in:
parent
d06bd10bf1
commit
e4047e62a0
@ -1,77 +1,160 @@
|
||||
import { KeydownType, type Options } from '@meta2d/core';
|
||||
import { invert } from 'lodash-es';
|
||||
|
||||
/**
|
||||
* 地图点位类型枚举
|
||||
* 定义了场景编辑器中所有支持的点位类型
|
||||
* 数值1-9为小点位,10以上为大点位(有特殊图标)
|
||||
*/
|
||||
//#region 点位
|
||||
export enum MapPointType {
|
||||
/** 普通点 - 基础导航点,机器人可通过 */
|
||||
普通点 = 1,
|
||||
/** 等待点 - 机器人等待或暂停的位置 */
|
||||
等待点,
|
||||
/** 避让点 - 机器人主动避让其他机器人的点位 */
|
||||
避让点,
|
||||
/** 临时避让点 - 动态生成的临时避让位置,有特殊标记 */
|
||||
临时避让点,
|
||||
|
||||
/** 电梯点 - 机器人乘坐电梯的专用点位 */
|
||||
电梯点 = 11,
|
||||
/** 自动门点 - 需要自动门控制的通行点位 */
|
||||
自动门点,
|
||||
/** 充电点 - 机器人充电的专用位置,可绑定特定机器人 */
|
||||
充电点,
|
||||
/** 停靠点 - 机器人停靠等待的位置,可绑定特定机器人 */
|
||||
停靠点,
|
||||
/** 动作点 - 机器人执行特定动作的位置(如取货、放货) */
|
||||
动作点,
|
||||
/** 禁行点 - 禁止机器人通过的点位 */
|
||||
禁行点,
|
||||
}
|
||||
|
||||
/**
|
||||
* 点位类型映射数组,用于UI显示和选择
|
||||
* 过滤掉非数字类型的枚举项
|
||||
*/
|
||||
export const MAP_POINT_TYPES = Object.freeze(
|
||||
<[string, MapPointType][]>Object.entries(MapPointType).filter(([, v]) => typeof v === 'number'),
|
||||
);
|
||||
//#endregion
|
||||
|
||||
/**
|
||||
* 地图路线类型枚举
|
||||
* 定义了连接点位之间的路径类型
|
||||
*/
|
||||
//#region 线路
|
||||
export enum MapRouteType {
|
||||
/** 直线 - 两点间直线连接 */
|
||||
直线 = 'line',
|
||||
/** 二阶贝塞尔曲线 - 带一个控制点的曲线 */
|
||||
二阶贝塞尔曲线 = 'bezier2',
|
||||
/** 三阶贝塞尔曲线 - 带两个控制点的曲线,更灵活 */
|
||||
三阶贝塞尔曲线 = 'bezier3',
|
||||
}
|
||||
|
||||
/**
|
||||
* 路线类型反向映射,用于从字符串值获取枚举键
|
||||
*/
|
||||
export const MAP_ROUTE_TYPE = invert(MapRouteType);
|
||||
|
||||
/**
|
||||
* 路线类型映射数组,用于UI显示和选择
|
||||
*/
|
||||
export const MAP_ROUTE_TYPES = Object.freeze(<[string, MapRouteType][]>Object.entries(MapRouteType));
|
||||
|
||||
/**
|
||||
* 路线通行类型枚举
|
||||
* 定义了路线的通行权限和限制
|
||||
*/
|
||||
export enum MapRoutePassType {
|
||||
/** 无限制 - 所有机器人都可以通行 */
|
||||
无,
|
||||
/** 仅空载可通行 - 只有空载的机器人可以通过 */
|
||||
仅空载可通行,
|
||||
/** 仅载货可通行 - 只有载货的机器人可以通过 */
|
||||
仅载货可通行,
|
||||
/** 禁行 - 禁止所有机器人通行,显示为虚线 */
|
||||
禁行 = 10,
|
||||
}
|
||||
|
||||
/**
|
||||
* 路线通行类型映射数组,用于UI显示和选择
|
||||
*/
|
||||
export const MAP_ROUTE_PASS_TYPES = Object.freeze(
|
||||
<[string, MapRoutePassType][]>Object.entries(MapRoutePassType).filter(([, v]) => typeof v === 'number'),
|
||||
);
|
||||
//#endregion
|
||||
|
||||
/**
|
||||
* 地图区域类型枚举
|
||||
* 定义了场景中不同功能的区域类型
|
||||
*/
|
||||
//#region 区域
|
||||
export enum MapAreaType {
|
||||
/** 库区 - 仓储作业区域,包含动作点 */
|
||||
库区 = 1,
|
||||
|
||||
/** 互斥区 - 同时只能有一个机器人进入的区域 */
|
||||
互斥区 = 11,
|
||||
/** 非互斥区 - 可以同时有多个机器人进入的区域 */
|
||||
非互斥区,
|
||||
}
|
||||
|
||||
/**
|
||||
* 区域类型映射数组,用于UI显示和选择
|
||||
*/
|
||||
export const MAP_AREA_TYPES = Object.freeze(
|
||||
<[string, MapAreaType][]>Object.entries(MapAreaType).filter(([, v]) => typeof v === 'number'),
|
||||
);
|
||||
//#endregion
|
||||
|
||||
/**
|
||||
* 场景编辑器核心配置
|
||||
* 基于Meta2D引擎的编辑器配置参数
|
||||
*/
|
||||
export const EDITOR_CONFIG: Options = {
|
||||
/** 键盘事件类型 - 禁用所有键盘快捷键 */
|
||||
keydown: KeydownType.None,
|
||||
/** 严格作用域 - 限制编辑操作范围 */
|
||||
strictScope: true,
|
||||
/** 移动连接线 - 禁用拖动时自动移动连接的线条 */
|
||||
moveConnectedLine: false,
|
||||
/** 禁用输入 - 禁用文本输入功能 */
|
||||
disableInput: true,
|
||||
/** 禁用旋转 - 禁用图形旋转功能 */
|
||||
disableRotate: true,
|
||||
/** 禁用尺寸调整 - 禁用图形大小调整 */
|
||||
disableSize: true,
|
||||
/** 禁用锚点 - 禁用连接锚点显示 */
|
||||
disableAnchor: true,
|
||||
/** 禁用空线条 - 不允许创建没有连接点的线条 */
|
||||
disableEmptyLine: true,
|
||||
/** 禁用重复线条 - 不允许在同一对点之间创建多条线 */
|
||||
disableRepeatLine: true,
|
||||
/** 最小缩放比例 - 画布最小缩放到24% */
|
||||
minScale: 0.24,
|
||||
/** 最大缩放比例 - 画布最大缩放到401% */
|
||||
maxScale: 4.01,
|
||||
scaleOff: 0.01,
|
||||
/** 缩放步长 - 每次滚轮滚动的缩放幅度(5%) */
|
||||
scaleOff: 0.05,
|
||||
/** 默认锚点 - 不设置默认锚点 */
|
||||
defaultAnchors: [],
|
||||
/** 全局透明度 - 普通状态下图形透明度(0为不透明) */
|
||||
globalAlpha: 0,
|
||||
/** 激活状态全局透明度 - 选中状态下图形透明度 */
|
||||
activeGlobalAlpha: 0,
|
||||
/** 默认字体大小 - 14像素 */
|
||||
fontSize: 14,
|
||||
/** 行高倍数 - 1.5倍行高 */
|
||||
lineHeight: 1.5,
|
||||
/** 字体族 - 使用系统默认字体 */
|
||||
fontFamily: 'system-ui',
|
||||
/** 文本旋转 - 禁用文本跟随图形旋转 */
|
||||
textRotate: false,
|
||||
/** 文本水平对齐 - 居中对齐 */
|
||||
textAlign: 'center',
|
||||
/** 文本垂直基线 - 顶部对齐 */
|
||||
textBaseline: 'top',
|
||||
};
|
||||
|
@ -27,8 +27,25 @@ import { clone, get, isEmpty, isNil, isString, nth, omitBy, pick, remove, some }
|
||||
import { BehaviorSubject, debounceTime, filter, map, Subject, switchMap } from 'rxjs';
|
||||
import { reactive, watch } from 'vue';
|
||||
|
||||
/**
|
||||
* 场景编辑器服务类
|
||||
* 继承自Meta2D,提供完整的场景编辑功能
|
||||
*
|
||||
* 主要功能:
|
||||
* - 场景文件的加载、保存和管理
|
||||
* - 点位、路线、区域的创建和编辑
|
||||
* - 机器人组的管理和实时状态更新
|
||||
* - 鼠标事件的处理和响应式数据流
|
||||
* - 自定义绘制和渲染逻辑
|
||||
*/
|
||||
export class EditorService extends Meta2d {
|
||||
//#region 场景文件
|
||||
//#region 场景文件管理
|
||||
/**
|
||||
* 加载场景文件到编辑器
|
||||
* @param map 场景文件的JSON字符串,为空则创建新场景
|
||||
* @param editable 是否可编辑状态,控制编辑器锁定状态
|
||||
* @param detail 群组场景详情,包含机器人组和机器人信息
|
||||
*/
|
||||
public async load(map?: string, editable = false, detail?: Partial<GroupSceneDetail>): Promise<void> {
|
||||
const scene: StandardScene = map ? JSON.parse(map) : {};
|
||||
if (!isEmpty(detail?.group)) {
|
||||
@ -45,6 +62,10 @@ export class EditorService extends Meta2d {
|
||||
this.store.historyIndex = undefined;
|
||||
this.store.histories = [];
|
||||
}
|
||||
/**
|
||||
* 保存当前场景为JSON字符串
|
||||
* @returns 包含完整场景数据的JSON字符串
|
||||
*/
|
||||
public save(): string {
|
||||
const scene: StandardScene = {
|
||||
robotGroups: this.robotGroups.value,
|
||||
@ -57,11 +78,20 @@ export class EditorService extends Meta2d {
|
||||
return JSON.stringify(scene);
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载机器人数据到编辑器
|
||||
* @param groups 机器人组列表
|
||||
* @param robots 机器人信息列表
|
||||
*/
|
||||
#loadRobots(groups?: RobotGroup[], robots?: RobotInfo[]): void {
|
||||
this.#robotMap.clear();
|
||||
robots?.forEach((v) => this.#robotMap.set(v.id, v));
|
||||
this.#robotGroups$$.next(groups ?? []);
|
||||
}
|
||||
/**
|
||||
* 从场景数据加载点位到画布
|
||||
* @param points 标准场景点位数据数组
|
||||
*/
|
||||
async #loadScenePoints(points?: StandardScenePoint[]): Promise<void> {
|
||||
if (!points?.length) return;
|
||||
await Promise.all(
|
||||
@ -75,6 +105,10 @@ export class EditorService extends Meta2d {
|
||||
}),
|
||||
);
|
||||
}
|
||||
/**
|
||||
* 从场景数据加载路线到画布
|
||||
* @param routes 标准场景路线数据数组
|
||||
*/
|
||||
#loadSceneRoutes(routes?: StandardSceneRoute[]): void {
|
||||
if (!routes?.length) return;
|
||||
routes.map((v) => {
|
||||
@ -206,6 +240,10 @@ export class EditorService extends Meta2d {
|
||||
}
|
||||
//#endregion
|
||||
|
||||
/**
|
||||
* 设置编辑器状态
|
||||
* @param editable 是否可编辑,true为可编辑状态,false为只读状态
|
||||
*/
|
||||
public setState(editable?: boolean): void {
|
||||
this.lock(editable ? LockState.None : LockState.DisableEdit);
|
||||
}
|
||||
@ -214,7 +252,10 @@ export class EditorService extends Meta2d {
|
||||
return super.data();
|
||||
}
|
||||
|
||||
/** 鼠标事件流主体,用于内部事件分发 */
|
||||
readonly #mouse$$ = new Subject<{ type: 'click' | 'mousedown' | 'mouseup'; value: Point }>();
|
||||
|
||||
/** 鼠标点击事件的响应式流,防抖处理后的点击坐标 */
|
||||
public readonly mouseClick = useObservable<Point>(
|
||||
this.#mouse$$.pipe(
|
||||
filter(({ type }) => type === 'click'),
|
||||
@ -222,6 +263,8 @@ export class EditorService extends Meta2d {
|
||||
map(({ value }) => value),
|
||||
),
|
||||
);
|
||||
|
||||
/** 鼠标拖拽事件的响应式流,返回起始点和结束点坐标,用于创建区域 */
|
||||
public readonly mouseBrush = useObservable<[Point, Point]>(
|
||||
this.#mouse$$.pipe(
|
||||
filter(({ type }) => type === 'mousedown'),
|
||||
@ -234,8 +277,11 @@ export class EditorService extends Meta2d {
|
||||
),
|
||||
);
|
||||
|
||||
//#region 机器人组
|
||||
//#region 机器人组管理
|
||||
/** 机器人信息映射表,响应式存储所有机器人数据 */
|
||||
readonly #robotMap = reactive<Map<RobotInfo['id'], RobotInfo>>(new Map());
|
||||
|
||||
/** 获取所有机器人信息数组 */
|
||||
public get robots(): RobotInfo[] {
|
||||
return Array.from(this.#robotMap.values());
|
||||
}
|
||||
@ -319,13 +365,18 @@ export class EditorService extends Meta2d {
|
||||
}
|
||||
//#endregion
|
||||
|
||||
/** 画布变化事件流,用于触发响应式数据更新 */
|
||||
readonly #change$$ = new Subject<boolean>();
|
||||
|
||||
/** 当前选中的图形对象,响应式更新 */
|
||||
public readonly current = useObservable<MapPen>(
|
||||
this.#change$$.pipe(
|
||||
debounceTime(100),
|
||||
map(() => <MapPen>clone(this.store.active?.[0])),
|
||||
),
|
||||
);
|
||||
|
||||
/** 当前选中的图形ID列表,响应式更新 */
|
||||
public readonly selected = useObservable<string[], string[]>(
|
||||
this.#change$$.pipe(
|
||||
filter((v) => !v),
|
||||
@ -334,6 +385,8 @@ export class EditorService extends Meta2d {
|
||||
),
|
||||
{ initialValue: new Array<string>() },
|
||||
);
|
||||
|
||||
/** 画布上所有图形对象列表,响应式更新 */
|
||||
public readonly pens = useObservable<MapPen[]>(
|
||||
this.#change$$.pipe(
|
||||
filter((v) => v),
|
||||
@ -432,6 +485,7 @@ export class EditorService extends Meta2d {
|
||||
//#endregion
|
||||
|
||||
//#region 点位
|
||||
/** 画布上所有点位对象列表,响应式更新 */
|
||||
public readonly points = useObservable<MapPen[], MapPen[]>(
|
||||
this.#change$$.pipe(
|
||||
filter((v) => v),
|
||||
@ -447,6 +501,12 @@ export class EditorService extends Meta2d {
|
||||
return { x: x + width / 2, y: y + height / 2, width, height };
|
||||
}
|
||||
|
||||
/**
|
||||
* 在指定位置添加点位
|
||||
* @param p 点位坐标
|
||||
* @param type 点位类型,默认为普通点
|
||||
* @param id 点位ID,未指定则自动生成
|
||||
*/
|
||||
public async addPoint(p: Point, type = MapPointType.普通点, id?: string): Promise<void> {
|
||||
id ||= s8();
|
||||
const pen: MapPen = {
|
||||
@ -503,6 +563,7 @@ export class EditorService extends Meta2d {
|
||||
//#endregion
|
||||
|
||||
//#region 线路
|
||||
/** 画布上所有路线对象列表,响应式更新,包含动态生成的标签 */
|
||||
public readonly routes = useObservable<MapPen[], MapPen[]>(
|
||||
this.#change$$.pipe(
|
||||
filter((v) => v),
|
||||
@ -525,6 +586,12 @@ export class EditorService extends Meta2d {
|
||||
return `${p1.label}${(d ?? direction) > 0 ? '→' : '←'}${p2.label}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 在两个点位之间添加路线
|
||||
* @param p 两个点位的数组
|
||||
* @param type 路线类型,默认为直线
|
||||
* @param id 路线ID,未指定则自动生成
|
||||
*/
|
||||
public addRoute(p: [MapPen, MapPen], type = MapRouteType.直线, id?: string): void {
|
||||
const [p1, p2] = p;
|
||||
if (!p1?.anchors?.length || !p2?.anchors?.length) return;
|
||||
@ -553,6 +620,7 @@ export class EditorService extends Meta2d {
|
||||
//#endregion
|
||||
|
||||
//#region 区域
|
||||
/** 画布上所有区域对象列表,响应式更新 */
|
||||
public readonly areas = useObservable<MapPen[], MapPen[]>(
|
||||
this.#change$$.pipe(
|
||||
filter((v) => v),
|
||||
@ -571,6 +639,13 @@ export class EditorService extends Meta2d {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 在指定区域添加功能区域
|
||||
* @param p1 区域起始坐标
|
||||
* @param p2 区域结束坐标
|
||||
* @param type 区域类型,默认为库区
|
||||
* @param id 区域ID,未指定则自动生成
|
||||
*/
|
||||
public async addArea(p1: Point, p2: Point, type = MapAreaType.库区, id?: string) {
|
||||
const scale = this.data().scale ?? 1;
|
||||
const w = Math.abs(p1.x - p2.x);
|
||||
@ -621,13 +696,21 @@ export class EditorService extends Meta2d {
|
||||
}
|
||||
//#endregion
|
||||
|
||||
/**
|
||||
* 构造函数 - 初始化场景编辑器
|
||||
* @param container 编辑器容器DOM元素
|
||||
*/
|
||||
constructor(container: HTMLDivElement) {
|
||||
super(container, EDITOR_CONFIG);
|
||||
|
||||
// 禁用第6个子元素的拖放功能
|
||||
(<HTMLDivElement>container.children.item(5)).ondrop = null;
|
||||
// 监听所有画布事件
|
||||
this.on('*', (e, v) => this.#listen(e, v));
|
||||
// 注册自定义绘制函数和锚点
|
||||
this.#register();
|
||||
|
||||
// 监听主题变化并重新加载样式
|
||||
watch(
|
||||
() => sTheme.theme,
|
||||
(v) => this.#load(v),
|
||||
@ -711,7 +794,12 @@ export class EditorService extends Meta2d {
|
||||
}
|
||||
}
|
||||
|
||||
//#region 绘制函数
|
||||
//#region 自定义绘制函数
|
||||
/**
|
||||
* 绘制点位的自定义函数
|
||||
* @param ctx Canvas 2D绘制上下文
|
||||
* @param pen 点位图形对象
|
||||
*/
|
||||
function drawPoint(ctx: CanvasRenderingContext2D, pen: MapPen): void {
|
||||
const theme = sTheme.editor;
|
||||
const { active, iconSize: r = 0, fontSize = 14, lineHeight = 1.5, fontFamily } = pen.calculative ?? {};
|
||||
@ -777,6 +865,10 @@ function drawPoint(ctx: CanvasRenderingContext2D, pen: MapPen): void {
|
||||
ctx.fillText(label, x + w / 2, y - fontSize * lineHeight);
|
||||
ctx.restore();
|
||||
}
|
||||
/**
|
||||
* 设置点位的连接锚点
|
||||
* @param pen 点位图形对象
|
||||
*/
|
||||
function anchorPoint(pen: MapPen): void {
|
||||
pen.anchors = [
|
||||
{ penId: pen.id, id: '0', x: 0.5, y: 0.5 },
|
||||
@ -787,6 +879,11 @@ function anchorPoint(pen: MapPen): void {
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制路线的自定义函数
|
||||
* @param ctx Canvas 2D绘制上下文
|
||||
* @param pen 路线图形对象
|
||||
*/
|
||||
function drawLine(ctx: CanvasRenderingContext2D, pen: MapPen): void {
|
||||
const theme = sTheme.editor;
|
||||
const { active, lineWidth: s = 1 } = pen.calculative ?? {};
|
||||
@ -874,6 +971,11 @@ function lineBezier3(_: Meta2dStore, pen: MapPen): void {
|
||||
pen.calculative.worldAnchors[1].prev = { x: x2 + dx2 * s, y: y2 + dy2 * s };
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制区域的自定义函数
|
||||
* @param ctx Canvas 2D绘制上下文
|
||||
* @param pen 区域图形对象
|
||||
*/
|
||||
function drawArea(ctx: CanvasRenderingContext2D, pen: MapPen): void {
|
||||
const theme = sTheme.editor;
|
||||
const { active, fontSize = 14, lineHeight = 1.5, fontFamily } = pen.calculative ?? {};
|
||||
@ -895,6 +997,11 @@ function drawArea(ctx: CanvasRenderingContext2D, pen: MapPen): void {
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制机器人的自定义函数
|
||||
* @param ctx Canvas 2D绘制上下文
|
||||
* @param pen 机器人图形对象
|
||||
*/
|
||||
function drawRobot(ctx: CanvasRenderingContext2D, pen: MapPen): void {
|
||||
const theme = sTheme.editor;
|
||||
const { lineWidth: s = 1 } = pen.calculative ?? {};
|
||||
|
1579
场景编辑器组件详细分析.md
1579
场景编辑器组件详细分析.md
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user