refactor: 移除 EditorRobotContext 接口中的 ensureCorrectLayerOrder 方法,并在 EditorRobotService 中删除相关调用,简化代码结构
This commit is contained in:
parent
f41e6cf1d3
commit
56f365bd04
@ -21,7 +21,7 @@ import type {
|
||||
StandardSceneRoute,
|
||||
} from '@api/scene';
|
||||
import sTheme from '@core/theme.service';
|
||||
import { CanvasLayer, LockState, Meta2d, type Meta2dStore, type Pen, s8 } from '@meta2d/core';
|
||||
import { LockState, Meta2d, type Meta2dStore, type Pen } from '@meta2d/core';
|
||||
import { useObservable } from '@vueuse/rxjs';
|
||||
import { clone, get, isEmpty, isNil, isString, pick, } from 'lodash-es';
|
||||
import { debounceTime, filter, map, Subject, switchMap } from 'rxjs';
|
||||
@ -36,7 +36,10 @@ import {
|
||||
drawStorageLocation,
|
||||
drawStorageMore,
|
||||
} from './draw/storage-location-drawer';
|
||||
import { drawRobot,EditorRobotService } from './editor-robot.service';
|
||||
import { AreaManager } from './editor/area-manager.service';
|
||||
import { drawRobot,EditorRobotService } from './editor/editor-robot.service';
|
||||
import { PointManager } from './editor/point-manager.service';
|
||||
import { RouteManager } from './editor/route-manager.service';
|
||||
import { LayerManagerService } from './layer-manager.service';
|
||||
import { createStorageLocationUpdater,StorageLocationService } from './storage-location.service';
|
||||
import { AutoStorageGenerator } from './utils/auto-storage-generator';
|
||||
@ -65,6 +68,13 @@ export class EditorService extends Meta2d {
|
||||
/** 自动生成库位工具实例 */
|
||||
private readonly autoStorageGenerator!: AutoStorageGenerator;
|
||||
|
||||
/** 点位管理模块 */
|
||||
private readonly pointManager: PointManager;
|
||||
/** 路线管理模块 */
|
||||
private readonly routeManager: RouteManager;
|
||||
/** 区域管理模块 */
|
||||
private readonly areaManager: AreaManager;
|
||||
|
||||
//#region 场景文件管理
|
||||
/**
|
||||
* 加载场景文件到编辑器
|
||||
@ -1048,9 +1058,7 @@ export class EditorService extends Meta2d {
|
||||
);
|
||||
|
||||
public getPointRect(pen?: MapPen): Rect | null {
|
||||
if (isNil(pen)) return null;
|
||||
const { x, y, width, height } = this.getPenRect(pen);
|
||||
return { x: x + width / 2, y: y + height / 2, width, height };
|
||||
return this.pointManager.getPointRect(pen);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1060,99 +1068,18 @@ export class EditorService extends Meta2d {
|
||||
* @param id 点位ID,未指定则自动生成
|
||||
*/
|
||||
public async addPoint(p: Point, type = MapPointType.普通点, id?: string): Promise<void> {
|
||||
id ||= s8();
|
||||
const pointInfo: MapPointInfo = { type };
|
||||
|
||||
// 为充电点/停靠点设置默认启用状态
|
||||
if ([MapPointType.充电点, MapPointType.停靠点].includes(type)) {
|
||||
pointInfo.enabled = 1;
|
||||
}
|
||||
|
||||
const pen: MapPen = {
|
||||
...p,
|
||||
...this.#mapPoint(type),
|
||||
...this.#mapPointImage(type),
|
||||
id,
|
||||
name: 'point',
|
||||
tags: ['point'],
|
||||
label: `P${id}`,
|
||||
point: pointInfo,
|
||||
locked: LockState.DisableEdit,
|
||||
};
|
||||
pen.x! -= pen.width! / 2;
|
||||
pen.y! -= pen.height! / 2;
|
||||
const addedPen = await this.addPen(pen, false, true, true);
|
||||
|
||||
// 将新创建的点位移到最上层
|
||||
this.layerManager.adjustElementLayer(addedPen);
|
||||
|
||||
// 如果是动作点且有库位信息,创建库位pen对象
|
||||
if (type === MapPointType.动作点 && pointInfo.associatedStorageLocations?.length) {
|
||||
this.createStorageLocationPens(addedPen.id!, pointInfo.associatedStorageLocations);
|
||||
}
|
||||
await this.pointManager.addPoint(p, type, id);
|
||||
}
|
||||
|
||||
public updatePoint(id: string, info: Partial<MapPointInfo>, autoCreateStorage = true): void {
|
||||
const { point } = this.getPenById(id) ?? {};
|
||||
if (!point?.type) return;
|
||||
const o = { ...point, ...info };
|
||||
this.setValue({ id, point: o }, { render: true, history: true, doEvent: true });
|
||||
|
||||
// 如果是动作点且库位信息发生变化,重新创建库位pen对象
|
||||
if (point.type === MapPointType.动作点 && info.associatedStorageLocations && autoCreateStorage) {
|
||||
this.createStorageLocationPens(id, info.associatedStorageLocations);
|
||||
}
|
||||
this.pointManager.updatePoint(id, info, autoCreateStorage);
|
||||
}
|
||||
|
||||
public changePointType(id: string, type: MapPointType): void {
|
||||
const pen = this.getPenById(id);
|
||||
const rect = this.getPointRect(pen);
|
||||
if (isNil(rect)) return;
|
||||
|
||||
// 如果原点位是动作点,需要先删除相关的库位元素
|
||||
if (pen?.point?.type === MapPointType.动作点) {
|
||||
this.removeStorageLocationPens(id);
|
||||
}
|
||||
|
||||
const point = this.#mapPoint(type);
|
||||
const pointInfo: MapPointInfo = { type };
|
||||
|
||||
// 为充电点/停靠点设置默认启用状态
|
||||
if ([MapPointType.充电点, MapPointType.停靠点].includes(type)) {
|
||||
pointInfo.enabled = 1;
|
||||
}
|
||||
|
||||
this.setValue(
|
||||
{
|
||||
id,
|
||||
x: rect.x - point.width / 2,
|
||||
y: rect.y - point.height / 2,
|
||||
...point,
|
||||
...this.#mapPointImage(type),
|
||||
point: pointInfo,
|
||||
},
|
||||
{ render: true, history: true, doEvent: true },
|
||||
);
|
||||
|
||||
// 如果是大点位类型(需要图片),异步重新加载主题确保图片正确显示
|
||||
if (type >= 10) {
|
||||
requestAnimationFrame(() => {
|
||||
this.reloadTheme();
|
||||
});
|
||||
}
|
||||
this.pointManager.changePointType(id, type);
|
||||
}
|
||||
|
||||
#mapPoint(type: MapPointType): Required<Pick<MapPen, 'width' | 'height' | 'lineWidth' | 'iconSize'>> {
|
||||
const width = type < 10 ? 24 : 48;
|
||||
const height = type < 10 ? 24 : 60;
|
||||
const lineWidth = type < 10 ? 2 : 3;
|
||||
const iconSize = type < 10 ? 4 : 10;
|
||||
return { width, height, lineWidth, iconSize };
|
||||
}
|
||||
#mapPointImage(type: MapPointType): Required<Pick<MapPen, 'image' | 'canvasLayer'>> {
|
||||
const theme = this.data().theme;
|
||||
const image = type < 10 ? '' : `${import.meta.env.BASE_URL}/point/${type}-${theme}.png`;
|
||||
return { image, canvasLayer: CanvasLayer.CanvasMain };
|
||||
}
|
||||
|
||||
|
||||
//#endregion
|
||||
|
||||
@ -1167,181 +1094,47 @@ export class EditorService extends Meta2d {
|
||||
{ initialValue: new Array<MapPen>() },
|
||||
);
|
||||
|
||||
public getRouteLabel(id?: string, d?: number): string {
|
||||
if (!id) return '';
|
||||
const pen = this.getPenById(id);
|
||||
if (isNil(pen)) return '';
|
||||
const [a1, a2] = pen.anchors ?? [];
|
||||
if (!a1?.connectTo || !a2?.connectTo) return '';
|
||||
const p1 = this.getPenById(a1.connectTo);
|
||||
const p2 = this.getPenById(a2.connectTo);
|
||||
if (isNil(p1) || isNil(p2)) return '';
|
||||
const { direction = 1 } = pen.route ?? {};
|
||||
return `${p1.label}${(d ?? direction) > 0 ? '→' : '←'}${p2.label}`;
|
||||
public getRouteLabel(id?: string, directionOverride?: number): string {
|
||||
return this.routeManager.getRouteLabel(id, directionOverride);
|
||||
}
|
||||
|
||||
/**
|
||||
* 在两个点位之间添加路线
|
||||
* @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;
|
||||
const line = this.connectLine(p1, p2, undefined, undefined, false);
|
||||
id ||= line.id!;
|
||||
this.changePenId(line.id!, id);
|
||||
const pen: MapPen = {
|
||||
tags: ['route'],
|
||||
route: { type },
|
||||
lineWidth: 1,
|
||||
locked: LockState.DisableEdit,
|
||||
canvasLayer: CanvasLayer.CanvasMain,
|
||||
};
|
||||
this.setValue({ id, ...pen }, { render: false, history: false, doEvent: false });
|
||||
this.updateLineType(line, type);
|
||||
|
||||
// 将路线移到底层,确保点位能覆盖在路线之上
|
||||
this.bottom([line]);
|
||||
|
||||
this.active(id);
|
||||
this.render();
|
||||
this.routeManager.addRoute(p, type, id);
|
||||
}
|
||||
|
||||
public updateRoute(id: string, info: Partial<MapRouteInfo>): void {
|
||||
const { route } = this.getPenById(id) ?? {};
|
||||
if (!route?.type) return;
|
||||
const o = { ...route, ...info };
|
||||
this.setValue({ id, route: o }, { render: true, history: true, doEvent: true });
|
||||
this.routeManager.updateRoute(id, info);
|
||||
}
|
||||
|
||||
public changeRouteType(id: string, type: MapRouteType): void {
|
||||
const pen = this.getPenById(id);
|
||||
if (isNil(pen)) return;
|
||||
this.updateLineType(pen, type);
|
||||
this.setValue({ id, route: { type } }, { render: true, history: true, doEvent: true });
|
||||
this.routeManager.changeRouteType(id, type);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取两个点位之间的所有路线
|
||||
* @param point1Id 第一个点位ID
|
||||
* @param point2Id 第二个点位ID
|
||||
* @returns 连接这两个点位的所有路线数组
|
||||
*/
|
||||
public getRoutesBetweenPoints(point1Id: string, point2Id: string): MapPen[] {
|
||||
return this.find('route').filter(route => {
|
||||
const [a1, a2] = route.anchors ?? [];
|
||||
if (!a1?.connectTo || !a2?.connectTo) return false;
|
||||
return (a1.connectTo === point1Id && a2.connectTo === point2Id) ||
|
||||
(a1.connectTo === point2Id && a2.connectTo === point1Id);
|
||||
});
|
||||
return this.routeManager.getRoutesBetweenPoints(point1Id, point2Id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定路线的反向路线
|
||||
* @param routeId 路线ID
|
||||
* @returns 反向路线,如果不存在则返回null
|
||||
*/
|
||||
public getReverseRoute(routeId: string): MapPen | null {
|
||||
const route = this.getPenById(routeId);
|
||||
if (!route) return null;
|
||||
|
||||
const [a1, a2] = route.anchors ?? [];
|
||||
if (!a1?.connectTo || !a2?.connectTo) return null;
|
||||
|
||||
const reverseRoutes = this.getRoutesBetweenPoints(a1.connectTo, a2.connectTo);
|
||||
return reverseRoutes.find(r => r.id !== routeId) || null;
|
||||
return this.routeManager.getReverseRoute(routeId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建双向路线
|
||||
* @param p 两个点位的数组
|
||||
* @param type 路线类型,默认为直线
|
||||
* @param forwardId 正向路线ID,未指定则自动生成
|
||||
* @param reverseId 反向路线ID,未指定则自动生成
|
||||
*/
|
||||
public addBidirectionalRoute(
|
||||
p: [MapPen, MapPen],
|
||||
type = MapRouteType.直线,
|
||||
forwardId?: string,
|
||||
reverseId?: string
|
||||
p: [MapPen, MapPen],
|
||||
type = MapRouteType.直线,
|
||||
forwardId?: string,
|
||||
reverseId?: string,
|
||||
): void {
|
||||
const [p1, p2] = p;
|
||||
if (!p1?.anchors?.length || !p2?.anchors?.length) return;
|
||||
|
||||
// 创建正向路线
|
||||
const forwardLine = this.connectLine(p1, p2, undefined, undefined, false);
|
||||
forwardId ||= forwardLine.id!;
|
||||
this.changePenId(forwardLine.id!, forwardId);
|
||||
const forwardPen: MapPen = {
|
||||
tags: ['route'],
|
||||
route: { type, direction: 1 },
|
||||
lineWidth: 1,
|
||||
locked: LockState.DisableEdit,
|
||||
canvasLayer: CanvasLayer.CanvasMain,
|
||||
};
|
||||
this.setValue({ id: forwardId, ...forwardPen }, { render: false, history: false, doEvent: false });
|
||||
this.updateLineType(forwardLine, type);
|
||||
|
||||
// 创建反向路线
|
||||
const reverseLine = this.connectLine(p2, p1, undefined, undefined, false);
|
||||
reverseId ||= reverseLine.id!;
|
||||
this.changePenId(reverseLine.id!, reverseId);
|
||||
const reversePen: MapPen = {
|
||||
tags: ['route'],
|
||||
route: { type, direction: -1 },
|
||||
lineWidth: 1,
|
||||
locked: LockState.DisableEdit,
|
||||
canvasLayer: CanvasLayer.CanvasMain,
|
||||
};
|
||||
this.setValue({ id: reverseId, ...reversePen }, { render: false, history: false, doEvent: false });
|
||||
this.updateLineType(reverseLine, type);
|
||||
|
||||
// 将路线移到底层,确保点位能覆盖在路线之上
|
||||
this.bottom([forwardLine, reverseLine]);
|
||||
|
||||
// 将反向路线移到正向路线之上,解决重叠选择问题
|
||||
this.top([reverseLine]);
|
||||
|
||||
this.active(forwardId);
|
||||
this.render();
|
||||
this.routeManager.addBidirectionalRoute(p, type, forwardId, reverseId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除双向路线
|
||||
* @param routeId 任意一条路线的ID
|
||||
*/
|
||||
public removeBidirectionalRoute(routeId: string): void {
|
||||
const route = this.getPenById(routeId);
|
||||
if (!route) return;
|
||||
|
||||
const [a1, a2] = route.anchors ?? [];
|
||||
if (!a1?.connectTo || !a2?.connectTo) return;
|
||||
|
||||
// 找到所有连接这两个点位的路线
|
||||
const routesBetweenPoints = this.getRoutesBetweenPoints(a1.connectTo, a2.connectTo);
|
||||
|
||||
// 删除所有相关路线
|
||||
routesBetweenPoints.forEach(r => {
|
||||
this.delete([r]);
|
||||
});
|
||||
|
||||
this.render();
|
||||
this.routeManager.removeBidirectionalRoute(routeId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查两个点位之间是否存在双向路线
|
||||
* @param point1Id 第一个点位ID
|
||||
* @param point2Id 第二个点位ID
|
||||
* @returns 是否存在双向路线
|
||||
*/
|
||||
public hasBidirectionalRoute(point1Id: string, point2Id: string): boolean {
|
||||
const routes = this.getRoutesBetweenPoints(point1Id, point2Id);
|
||||
return routes.length >= 2;
|
||||
return this.routeManager.hasBidirectionalRoute(point1Id, point2Id);
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region 区域
|
||||
/** 画布上所有区域对象列表,响应式更新 */
|
||||
public readonly areas = useObservable<MapPen[], MapPen[]>(
|
||||
@ -1354,84 +1147,17 @@ export class EditorService extends Meta2d {
|
||||
);
|
||||
|
||||
public getBoundAreas(id: string = '', name: 'point' | 'line', type: MapAreaType): MapPen[] {
|
||||
if (!id) return [];
|
||||
return this.find(`area-${type}`).filter(({ area }) => {
|
||||
if (name === 'point') return area?.points?.includes(id);
|
||||
if (name === 'line') return area?.routes?.includes(id);
|
||||
return false;
|
||||
});
|
||||
return this.areaManager.getBoundAreas(id, name, type);
|
||||
}
|
||||
|
||||
/**
|
||||
* 在指定区域添加功能区域
|
||||
* @param p1 区域起始坐标
|
||||
* @param p2 区域结束坐标
|
||||
* @param type 区域类型,默认为库区
|
||||
* @param id 区域ID,未指定则自动生成
|
||||
*/
|
||||
public async addArea(p1: Point, p2: Point, type = MapAreaType.库区, id?: string) {
|
||||
const w = Math.abs(p1.x - p2.x);
|
||||
const h = Math.abs(p1.y - p2.y);
|
||||
if (w < 50 || h < 60) return;
|
||||
const points = new Array<string>();
|
||||
const routes = new Array<string>();
|
||||
if (!id) {
|
||||
id = s8();
|
||||
const selected = <MapPen[]>this.store.active;
|
||||
switch (type) {
|
||||
case MapAreaType.库区:
|
||||
selected?.filter(({ point }) => point?.type === MapPointType.动作点).forEach(({ id }) => points.push(id!));
|
||||
break;
|
||||
case MapAreaType.互斥区:
|
||||
selected?.filter(({ point }) => point?.type).forEach(({ id }) => points.push(id!));
|
||||
// 互斥区不再绑定路段
|
||||
break;
|
||||
case MapAreaType.非互斥区:
|
||||
selected?.filter(({ point }) => point?.type).forEach(({ id }) => points.push(id!));
|
||||
break;
|
||||
case MapAreaType.约束区:
|
||||
selected?.filter(({ point }) => point?.type).forEach(({ id }) => points.push(id!));
|
||||
break;
|
||||
case MapAreaType.描述区:
|
||||
// 描述区不需要绑定点位或路线
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
const areaInfo: MapAreaInfo = { type, points, routes };
|
||||
if (type === MapAreaType.库区) {
|
||||
areaInfo.inoutflag = 2;
|
||||
}
|
||||
const pen: MapPen = {
|
||||
id,
|
||||
name: 'area',
|
||||
tags: ['area', `area-${type}`],
|
||||
label: `A${id}`,
|
||||
x: Math.min(p1.x, p2.x),
|
||||
y: Math.min(p1.y, p2.y),
|
||||
width: w,
|
||||
height: h,
|
||||
lineWidth: 1,
|
||||
area: areaInfo,
|
||||
locked: LockState.None,
|
||||
};
|
||||
const area = await this.addPen(pen, true, true, true);
|
||||
this.bottom(area);
|
||||
|
||||
// 如果是库区且包含动作点,触发自动生成库位的确认对话框
|
||||
if (type === MapAreaType.库区 && points.length > 0) {
|
||||
this.autoStorageGenerator.triggerAutoCreateStorageDialog(pen, points);
|
||||
}
|
||||
public async addArea(p1: Point, p2: Point, type = MapAreaType.库区, id?: string): Promise<void> {
|
||||
await this.areaManager.addArea(p1, p2, type, id);
|
||||
}
|
||||
|
||||
public updateArea(id: string, info: Partial<MapAreaInfo>): void {
|
||||
const { area } = this.getPenById(id) ?? {};
|
||||
if (!area?.type) return;
|
||||
const o = { ...area, ...info };
|
||||
this.setValue({ id, area: o }, { render: true, history: true, doEvent: true });
|
||||
this.areaManager.updateArea(id, info);
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
/**
|
||||
@ -1458,6 +1184,10 @@ export class EditorService extends Meta2d {
|
||||
// 初始化BinTask管理服务
|
||||
this.binTaskManager = new BinTaskManagerService(this);
|
||||
|
||||
this.pointManager = new PointManager(this, this.layerManager);
|
||||
this.routeManager = new RouteManager(this);
|
||||
this.areaManager = new AreaManager(this, this.autoStorageGenerator);
|
||||
|
||||
// 设置颜色配置服务的编辑器实例
|
||||
colorConfig.setEditorService(this);
|
||||
|
||||
@ -1499,11 +1229,7 @@ export class EditorService extends Meta2d {
|
||||
this.setTheme(theme);
|
||||
|
||||
this.setOptions({ color: get(sTheme.editor, 'color') });
|
||||
this.find('point').forEach((pen) => {
|
||||
if (!pen.point?.type) return;
|
||||
if (pen.point.type < 10) return;
|
||||
this.canvas.updateValue(pen, this.#mapPointImage(pen.point.type));
|
||||
});
|
||||
this.pointManager.refreshPointImages();
|
||||
this.find('robot').forEach((pen) => {
|
||||
if (!pen.robot?.type) return;
|
||||
// 从pen的text属性获取机器人名称
|
||||
|
90
src/services/editor/area-manager.service.ts
Normal file
90
src/services/editor/area-manager.service.ts
Normal file
@ -0,0 +1,90 @@
|
||||
import {
|
||||
type MapAreaInfo,
|
||||
MapAreaType,
|
||||
type MapPen,
|
||||
MapPointType,
|
||||
type Point,
|
||||
} from '@api/map';
|
||||
import { LockState, s8 } from '@meta2d/core';
|
||||
|
||||
import type { EditorService } from '../editor.service';
|
||||
import type { AutoStorageGenerator } from '../utils/auto-storage-generator';
|
||||
|
||||
export class AreaManager {
|
||||
constructor(
|
||||
private readonly editor: EditorService,
|
||||
private readonly autoStorageGenerator: AutoStorageGenerator,
|
||||
) {}
|
||||
|
||||
public getBoundAreas(id: string, name: 'point' | 'line', type: MapAreaType): MapPen[] {
|
||||
if (!id) return [];
|
||||
return this.editor.find(`area-${type}`).filter(({ area }) => {
|
||||
if (name === 'point') return area?.points?.includes(id);
|
||||
if (name === 'line') return area?.routes?.includes(id);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
public async addArea(p1: Point, p2: Point, type = MapAreaType.库区, id?: string): Promise<void> {
|
||||
const w = Math.abs(p1.x - p2.x);
|
||||
const h = Math.abs(p1.y - p2.y);
|
||||
if (w < 50 || h < 60) return;
|
||||
|
||||
const points: string[] = [];
|
||||
const routes: string[] = [];
|
||||
|
||||
if (!id) {
|
||||
id = s8();
|
||||
const selected = this.editor.store.active as MapPen[] | undefined;
|
||||
switch (type) {
|
||||
case MapAreaType.库区:
|
||||
selected
|
||||
?.filter(({ point }) => point?.type === MapPointType.动作点)
|
||||
.forEach(({ id: pointId }) => points.push(pointId!));
|
||||
break;
|
||||
case MapAreaType.互斥区:
|
||||
case MapAreaType.非互斥区:
|
||||
case MapAreaType.约束区:
|
||||
selected?.filter(({ point }) => point?.type).forEach(({ id: pointId }) => points.push(pointId!));
|
||||
break;
|
||||
case MapAreaType.描述区:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const areaInfo: MapAreaInfo = { type, points, routes };
|
||||
if (type === MapAreaType.库区) {
|
||||
areaInfo.inoutflag = 2;
|
||||
}
|
||||
|
||||
const pen: MapPen = {
|
||||
id,
|
||||
name: 'area',
|
||||
tags: ['area', `area-${type}`],
|
||||
label: `A${id}`,
|
||||
x: Math.min(p1.x, p2.x),
|
||||
y: Math.min(p1.y, p2.y),
|
||||
width: w,
|
||||
height: h,
|
||||
lineWidth: 1,
|
||||
area: areaInfo,
|
||||
locked: LockState.None,
|
||||
};
|
||||
|
||||
const area = await this.editor.addPen(pen, true, true, true);
|
||||
this.editor.bottom(area);
|
||||
|
||||
if (type === MapAreaType.库区 && points.length > 0) {
|
||||
this.autoStorageGenerator.triggerAutoCreateStorageDialog(pen, points);
|
||||
}
|
||||
}
|
||||
|
||||
public updateArea(id: string, info: Partial<MapAreaInfo>): void {
|
||||
const { area } = this.editor.getPenById(id) ?? {};
|
||||
if (!area?.type) return;
|
||||
const mergedArea = { ...area, ...info };
|
||||
this.editor.setValue({ id, area: mergedArea }, { render: true, history: true, doEvent: true });
|
||||
}
|
||||
}
|
@ -11,7 +11,7 @@ import { reactive } from 'vue';
|
||||
import cargoIcon from '../assets/icons/png/cargo.png';
|
||||
import chargingIcon from '../assets/icons/png/charging.png';
|
||||
import notAcceptingOrdersIcon from '../assets/icons/png/notAcceptingOrders.png';
|
||||
import colorConfig from './color/color-config.service';
|
||||
import colorConfig from '../color/color-config.service';
|
||||
|
||||
interface RobotStatusPosition {
|
||||
x: number;
|
||||
@ -31,7 +31,6 @@ export interface EditorRobotContext {
|
||||
getPenRect(pen: MapPen): Rect;
|
||||
find(target: string): MapPen[];
|
||||
inactive(): void;
|
||||
ensureCorrectLayerOrder(): void;
|
||||
}
|
||||
|
||||
export class EditorRobotService {
|
||||
@ -276,7 +275,6 @@ export class EditorRobotService {
|
||||
}),
|
||||
);
|
||||
|
||||
this.ctx.ensureCorrectLayerOrder();
|
||||
}
|
||||
|
||||
public updateAllRobotImageSizes(): void {
|
117
src/services/editor/point-manager.service.ts
Normal file
117
src/services/editor/point-manager.service.ts
Normal file
@ -0,0 +1,117 @@
|
||||
import { type MapPen, type MapPointInfo, MapPointType, type Point, type Rect } from '@api/map';
|
||||
import { CanvasLayer, LockState, s8 } from '@meta2d/core';
|
||||
import { isNil } from 'lodash-es';
|
||||
|
||||
import type { EditorService } from '../editor.service';
|
||||
import type { LayerManagerService } from '../layer-manager.service';
|
||||
|
||||
export class PointManager {
|
||||
constructor(
|
||||
private readonly editor: EditorService,
|
||||
private readonly layerManager: LayerManagerService,
|
||||
) {}
|
||||
|
||||
public getPointRect(pen?: MapPen): Rect | null {
|
||||
if (isNil(pen)) return null;
|
||||
const { x, y, width, height } = this.editor.getPenRect(pen);
|
||||
return { x: x + width / 2, y: y + height / 2, width, height };
|
||||
}
|
||||
|
||||
public async addPoint(p: Point, type = MapPointType.普通点, id?: string): Promise<void> {
|
||||
id ||= s8();
|
||||
const pointInfo: MapPointInfo = { type };
|
||||
|
||||
if ([MapPointType.充电点, MapPointType.停靠点].includes(type)) {
|
||||
pointInfo.enabled = 1;
|
||||
}
|
||||
|
||||
const pen: MapPen = {
|
||||
...p,
|
||||
...this.mapPoint(type),
|
||||
...this.mapPointImage(type),
|
||||
id,
|
||||
name: 'point',
|
||||
tags: ['point'],
|
||||
label: `P${id}`,
|
||||
point: pointInfo,
|
||||
locked: LockState.DisableEdit,
|
||||
};
|
||||
pen.x! -= pen.width! / 2;
|
||||
pen.y! -= pen.height! / 2;
|
||||
const addedPen = await this.editor.addPen(pen, false, true, true);
|
||||
|
||||
this.layerManager.adjustElementLayer(addedPen);
|
||||
|
||||
if (type === MapPointType.动作点 && pointInfo.associatedStorageLocations?.length) {
|
||||
this.editor.createStorageLocationPens(addedPen.id!, pointInfo.associatedStorageLocations);
|
||||
}
|
||||
}
|
||||
|
||||
public updatePoint(id: string, info: Partial<MapPointInfo>, autoCreateStorage = true): void {
|
||||
const { point } = this.editor.getPenById(id) ?? {};
|
||||
if (!point?.type) return;
|
||||
const mergedPoint = { ...point, ...info };
|
||||
this.editor.setValue({ id, point: mergedPoint }, { render: true, history: true, doEvent: true });
|
||||
|
||||
if (point.type === MapPointType.动作点 && info.associatedStorageLocations && autoCreateStorage) {
|
||||
this.editor.createStorageLocationPens(id, info.associatedStorageLocations);
|
||||
}
|
||||
}
|
||||
|
||||
public changePointType(id: string, type: MapPointType): void {
|
||||
const pen = this.editor.getPenById(id);
|
||||
const rect = this.getPointRect(pen);
|
||||
if (isNil(rect)) return;
|
||||
|
||||
if (pen?.point?.type === MapPointType.动作点) {
|
||||
this.editor.removeStorageLocationPens(id);
|
||||
}
|
||||
|
||||
const point = this.mapPoint(type);
|
||||
const pointInfo: MapPointInfo = { type };
|
||||
|
||||
if ([MapPointType.充电点, MapPointType.停靠点].includes(type)) {
|
||||
pointInfo.enabled = 1;
|
||||
}
|
||||
|
||||
this.editor.setValue(
|
||||
{
|
||||
id,
|
||||
x: rect.x - point.width / 2,
|
||||
y: rect.y - point.height / 2,
|
||||
...point,
|
||||
...this.mapPointImage(type),
|
||||
point: pointInfo,
|
||||
},
|
||||
{ render: true, history: true, doEvent: true },
|
||||
);
|
||||
|
||||
if (type >= 10) {
|
||||
requestAnimationFrame(() => {
|
||||
this.editor.reloadTheme();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public refreshPointImages(): void {
|
||||
this.editor.find('point').forEach((pen) => {
|
||||
const type = pen.point?.type;
|
||||
if (!type || type < 10) return;
|
||||
this.editor.canvas.updateValue(pen, this.mapPointImage(type));
|
||||
});
|
||||
}
|
||||
|
||||
private mapPoint(type: MapPointType): Required<Pick<MapPen, 'width' | 'height' | 'lineWidth' | 'iconSize'>> {
|
||||
const width = type < 10 ? 24 : 48;
|
||||
const height = type < 10 ? 24 : 60;
|
||||
const lineWidth = type < 10 ? 2 : 3;
|
||||
const iconSize = type < 10 ? 4 : 10;
|
||||
return { width, height, lineWidth, iconSize };
|
||||
}
|
||||
|
||||
private mapPointImage(type: MapPointType): Required<Pick<MapPen, 'image' | 'canvasLayer'>> {
|
||||
const theme = this.editor.data().theme;
|
||||
const image = type < 10 ? '' : `${import.meta.env.BASE_URL}/point/${type}-${theme}.png`;
|
||||
return { image, canvasLayer: CanvasLayer.CanvasMain };
|
||||
}
|
||||
}
|
135
src/services/editor/route-manager.service.ts
Normal file
135
src/services/editor/route-manager.service.ts
Normal file
@ -0,0 +1,135 @@
|
||||
import { type MapPen, type MapRouteInfo, MapRouteType } from '@api/map';
|
||||
import { CanvasLayer, LockState } from '@meta2d/core';
|
||||
|
||||
import type { EditorService } from '../editor.service';
|
||||
|
||||
export class RouteManager {
|
||||
constructor(private readonly editor: EditorService) {}
|
||||
|
||||
public getRouteLabel(id?: string, overrideDirection?: number): string {
|
||||
if (!id) return '';
|
||||
const pen = this.editor.getPenById(id);
|
||||
if (!pen) return '';
|
||||
const [a1, a2] = pen.anchors ?? [];
|
||||
if (!a1?.connectTo || !a2?.connectTo) return '';
|
||||
const p1 = this.editor.getPenById(a1.connectTo);
|
||||
const p2 = this.editor.getPenById(a2.connectTo);
|
||||
if (!p1 || !p2) return '';
|
||||
const { direction = 1 } = pen.route ?? {};
|
||||
return `${p1.label}${(overrideDirection ?? direction) > 0 ? '→' : '←'}${p2.label}`;
|
||||
}
|
||||
|
||||
public addRoute(p: [MapPen, MapPen], type = MapRouteType.直线, id?: string): void {
|
||||
const [p1, p2] = p;
|
||||
if (!p1?.anchors?.length || !p2?.anchors?.length) return;
|
||||
const line = this.editor.connectLine(p1, p2, undefined, undefined, false);
|
||||
id ||= line.id!;
|
||||
this.editor.changePenId(line.id!, id);
|
||||
const pen: MapPen = {
|
||||
tags: ['route'],
|
||||
route: { type },
|
||||
lineWidth: 1,
|
||||
locked: LockState.DisableEdit,
|
||||
canvasLayer: CanvasLayer.CanvasMain,
|
||||
};
|
||||
this.editor.setValue({ id, ...pen }, { render: false, history: false, doEvent: false });
|
||||
this.editor.updateLineType(line, type);
|
||||
this.editor.bottom([line]);
|
||||
|
||||
this.editor.active(id);
|
||||
this.editor.render();
|
||||
}
|
||||
|
||||
public updateRoute(id: string, info: Partial<MapRouteInfo>): void {
|
||||
const { route } = this.editor.getPenById(id) ?? {};
|
||||
if (!route?.type) return;
|
||||
const mergedRoute = { ...route, ...info };
|
||||
this.editor.setValue({ id, route: mergedRoute }, { render: true, history: true, doEvent: true });
|
||||
}
|
||||
|
||||
public changeRouteType(id: string, type: MapRouteType): void {
|
||||
const pen = this.editor.getPenById(id);
|
||||
if (!pen) return;
|
||||
this.editor.updateLineType(pen, type);
|
||||
this.editor.setValue({ id, route: { type } }, { render: true, history: true, doEvent: true });
|
||||
}
|
||||
|
||||
public getRoutesBetweenPoints(point1Id: string, point2Id: string): MapPen[] {
|
||||
return this.editor.find('route').filter((route) => {
|
||||
const [a1, a2] = route.anchors ?? [];
|
||||
if (!a1?.connectTo || !a2?.connectTo) return false;
|
||||
return (
|
||||
(a1.connectTo === point1Id && a2.connectTo === point2Id) ||
|
||||
(a1.connectTo === point2Id && a2.connectTo === point1Id)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
public getReverseRoute(routeId: string): MapPen | null {
|
||||
const route = this.editor.getPenById(routeId);
|
||||
if (!route) return null;
|
||||
const [a1, a2] = route.anchors ?? [];
|
||||
if (!a1?.connectTo || !a2?.connectTo) return null;
|
||||
const reverseRoutes = this.getRoutesBetweenPoints(a1.connectTo, a2.connectTo);
|
||||
return reverseRoutes.find((r) => r.id !== routeId) || null;
|
||||
}
|
||||
|
||||
public addBidirectionalRoute(
|
||||
p: [MapPen, MapPen],
|
||||
type = MapRouteType.直线,
|
||||
forwardId?: string,
|
||||
reverseId?: string,
|
||||
): void {
|
||||
const [p1, p2] = p;
|
||||
if (!p1?.anchors?.length || !p2?.anchors?.length) return;
|
||||
|
||||
const forwardLine = this.editor.connectLine(p1, p2, undefined, undefined, false);
|
||||
forwardId ||= forwardLine.id!;
|
||||
this.editor.changePenId(forwardLine.id!, forwardId);
|
||||
const forwardPen: MapPen = {
|
||||
tags: ['route'],
|
||||
route: { type, direction: 1 },
|
||||
lineWidth: 1,
|
||||
locked: LockState.DisableEdit,
|
||||
canvasLayer: CanvasLayer.CanvasMain,
|
||||
};
|
||||
this.editor.setValue({ id: forwardId, ...forwardPen }, { render: false, history: false, doEvent: false });
|
||||
this.editor.updateLineType(forwardLine, type);
|
||||
|
||||
const reverseLine = this.editor.connectLine(p2, p1, undefined, undefined, false);
|
||||
reverseId ||= reverseLine.id!;
|
||||
this.editor.changePenId(reverseLine.id!, reverseId);
|
||||
const reversePen: MapPen = {
|
||||
tags: ['route'],
|
||||
route: { type, direction: -1 },
|
||||
lineWidth: 1,
|
||||
locked: LockState.DisableEdit,
|
||||
canvasLayer: CanvasLayer.CanvasMain,
|
||||
};
|
||||
this.editor.setValue({ id: reverseId, ...reversePen }, { render: false, history: false, doEvent: false });
|
||||
this.editor.updateLineType(reverseLine, type);
|
||||
|
||||
this.editor.bottom([forwardLine, reverseLine]);
|
||||
this.editor.top([reverseLine]);
|
||||
|
||||
this.editor.active(forwardId);
|
||||
this.editor.render();
|
||||
}
|
||||
|
||||
public removeBidirectionalRoute(routeId: string): void {
|
||||
const route = this.editor.getPenById(routeId);
|
||||
if (!route) return;
|
||||
const [a1, a2] = route.anchors ?? [];
|
||||
if (!a1?.connectTo || !a2?.connectTo) return;
|
||||
const routesBetweenPoints = this.getRoutesBetweenPoints(a1.connectTo, a2.connectTo);
|
||||
routesBetweenPoints.forEach((r) => {
|
||||
this.editor.delete([r]);
|
||||
});
|
||||
this.editor.render();
|
||||
}
|
||||
|
||||
public hasBidirectionalRoute(point1Id: string, point2Id: string): boolean {
|
||||
const routes = this.getRoutesBetweenPoints(point1Id, point2Id);
|
||||
return routes.length >= 2;
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user