feat: 更新地图点位、路线和区域类型枚举,添加详细注释和配置说明

This commit is contained in:
xudan 2025-06-17 15:34:56 +08:00
parent d06bd10bf1
commit e4047e62a0
3 changed files with 1736 additions and 41 deletions

View File

@ -1,77 +1,160 @@
import { KeydownType, type Options } from '@meta2d/core';
import { invert } from 'lodash-es';
/**
*
*
* 1-910
*/
//#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',
};

View File

@ -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 ?? {};

File diff suppressed because it is too large Load Diff