diff --git a/src/core/base/BaseRenderer.ts b/src/core/base/BaseRenderer.ts index b33f9f6..01a239f 100644 --- a/src/core/base/BaseRenderer.ts +++ b/src/core/base/BaseRenderer.ts @@ -147,7 +147,7 @@ export default abstract class BaseRenderer { if (item.dt.storeAt?.item) { // 如果是库存物品, 则需要将位置和角度设置为库存位置 - const rack = this.tempViewport.stateManager.findItemById(item.dt.storeAt?.item) + const rack = Model.find(item.dt.storeAt?.item) if (!rack) { console.error(`Cannot find rack for item ${item.id} at ${item.dt.storeAt?.item}`) } diff --git a/src/core/manager/EnvManager.ts b/src/core/manager/EnvManager.ts index cf1022c..4a8b5f2 100644 --- a/src/core/manager/EnvManager.ts +++ b/src/core/manager/EnvManager.ts @@ -9,6 +9,7 @@ import { AmrMsg } from '@/core/manager/amr/AmrMessageDefine' export default class EnvManager { private amrMessageManager: AmrMessageManager = new AmrMessageManager() public client: mqtt.MqttClient = null + readonly stopSubscribe: StopSubscribe[] = [] onMqttConnect = (packet: IConnackPacket) => { console.log('Connected') @@ -57,7 +58,10 @@ export default class EnvManager { const env = worldModel.state.runState.currentEnv try { worldModel.backendMessageReceiver.setProjectEnv(worldModel.state.project_uuid, worldModel.state.runState.currentEnvId) - await LCC.loadInv() + await this.loadInvToModel() + this.stopSubscribe.push( + worldModel.backendMessageReceiver.subscribe('InvUpdate', this.onInvUpdateMessage.bind(this)) + ) this.client = mqtt.connect(env.envConfig.mqtt.websocket, { path: '/mqtt', @@ -83,6 +87,47 @@ export default class EnvManager { } } + async onInvUpdateMessage(type: BackendTopicType, topic: string, body: InvUpdateVo) { + if (!window['Model']) { + // 如果没有3D模型加载,则不处理库存更新 + return + } + + const lpnItem = Model.find(body.lpn) + if (lpnItem) { + Model.deleteItem(lpnItem.id) + } + + if (body.after != null) { + // 将托盘挪到目标位置 + const after = body.after + Model.createInv('pallet', body.lpn, after.rack, after.bay, after.level, after.cell) + } + } + + // 加载库存到3D视图上 + async loadInvToModel() { + if (!window['Model']) { + return + } + + const invRes = await LCC.loadInv() + if (!invRes.success) { + return + } + + for (const row of invRes.data) { + const bay = row.bay + const cell = row.cell // : 0 + const level = row.level // : 0 + const loc_code = row.loc_code // : "rack1_0_0_0" + const lpn = row.lpn // : "LPN1" + const rack = row.rack // : "rack1" + const container_type = row.container_type // : "pallet" + Model.createInv(container_type, lpn, rack, bay, level, cell) + } + } + // 加载执行器到3D视图上 async loadExecutorToModel() { if (!window['Model']) { @@ -128,6 +173,12 @@ export default class EnvManager { system.showLoading() try { worldModel.state.runState.isRunning = false + + for (const stopFn of this.stopSubscribe) { + stopFn() + } + this.stopSubscribe.length = 0 + if (this.client) { this.client.removeAllListeners() this.client.end() diff --git a/src/core/manager/RuntimeManager.ts b/src/core/manager/RuntimeManager.ts index cb8687a..9072d46 100644 --- a/src/core/manager/RuntimeManager.ts +++ b/src/core/manager/RuntimeManager.ts @@ -78,4 +78,24 @@ export default class RuntimeManager { this.tmpExecutors.add(item.id) this.viewport.entityManager.createOrUpdateEntityOnlyRuntime(item) } + + removeEntity(itemId: string) { + this.tmpExecutors.delete(itemId) + + // 从各个货架中移除 + for (const [rackId, items] of this.storeRackMap.entries()) { + if (items.has(itemId)) { + items.delete(itemId) + if (items.size === 0) { + this.storeRackMap.delete(rackId) + } + } + } + + this.viewport.entityManager.deleteEntityOnlyRuntime(itemId) + } + + has(itemId: string) { + return this.tmpExecutors.has(itemId) + } } diff --git a/src/core/script/LCCScript.ts b/src/core/script/LCCScript.ts index d2cbb91..c2d20a9 100644 --- a/src/core/script/LCCScript.ts +++ b/src/core/script/LCCScript.ts @@ -57,25 +57,38 @@ export default class LCCScript implements LCC { } // 从后台读取所有库存 - async loadInv(): Promise> { - const res = await Request.request.post('/api/workbench/LccController@loadInv', { - projectUuid: worldModel.state.project_uuid, + async loadInv(): Promise> { + return Request.request.post('/api/workbench/InvController@loadInv', { + projectUUID: worldModel.state.project_uuid, catalogCode: worldModel.state.catalogCode, envId: worldModel.state.runState.currentEnvId }) - for (const row of res.data) { - const bay = row.bay - const cell = row.cell // : 0 - const level = row.level // : 0 - const loc_code = row.loc_code // : "rack1_0_0_0" - const lpn = row.lpn // : "LPN1" - const rack = row.rack // : "rack1" - const container_type = row.container_type // : "pallet" - if (window['Model']) { - Model.createInv(container_type, lpn, rack, bay, level, cell) - } - } - return res.data + } + + createInv(lpn: string, targetStore: string): Promise> { + return Request.request.post('/api/workbench/InvController@createInv', { + projectUUID: worldModel.state.project_uuid, + envId: worldModel.state.runState.currentEnvId, + lpn: lpn, + targetStore: targetStore + }) + } + + moveInv(lpn: string, targetStore: string): Promise> { + return Request.request.post('/api/workbench/InvController@moveInv', { + projectUUID: worldModel.state.project_uuid, + envId: worldModel.state.runState.currentEnvId, + lpn: lpn, + targetStore: targetStore + }) + } + + deleteInv(lpn: string): Promise> { + return Request.request.post('/api/workbench/InvController@deleteInv', { + projectUUID: worldModel.state.project_uuid, + envId: worldModel.state.runState.currentEnvId, + lpn: lpn + }) } saveAndSyncScripts(scriptList: { name: string; content: string }[]): Promise> { diff --git a/src/core/script/ModelManager.ts b/src/core/script/ModelManager.ts index 3105d63..b9c06e9 100644 --- a/src/core/script/ModelManager.ts +++ b/src/core/script/ModelManager.ts @@ -86,6 +86,11 @@ export default class ModelManager implements IControls, Model { } deleteItem(itemId) { + if(this.viewport.runtimeManager.has(itemId)){ + // 临时执行器 + this.viewport.runtimeManager.removeEntity(itemId) + return + } this.viewport.stateManager.update(({ deleteEntity }) => { deleteEntity(itemId) }) diff --git a/src/types/LCC.d.ts b/src/types/LCC.d.ts index 6a067ca..fd1c3cc 100644 --- a/src/types/LCC.d.ts +++ b/src/types/LCC.d.ts @@ -16,11 +16,32 @@ declare interface LCC { /** * 读取当前项目所有库存, 并放到 Model 上 */ - loadInv(): Promise> + loadInv(): Promise> + + /** + * 创造库存 + * @param lpn 容器号 + * @param targetStore 目标存储位置 rack3/0/0/0 + */ + createInv(lpn: string, targetStore: string): Promise> + + /** + * 转移库存 + * @param lpn 容器号 + * @param targetStore 目标存储位置 rack3/0/0/0 + */ + moveInv(lpn: string, targetStore: string): Promise> + + /** + * 删除库存 + * @param lpn 容器号 + */ + deleteInv(lpn: string): Promise> /** * 获取所有车,并放到 Model 上 */ + // loadExecutor(): Promise /** @@ -77,6 +98,8 @@ type DeviceStatusFn = (type: BackendTopicType, topic: string, body: AgvStatusVo) type ServerStateFn = (type: BackendTopicType, topic: string, body: ServerStatusVo) => void +type InvUpdateFn = (type: BackendTopicType, topic: string, body: InvUpdateVo) => void + type StopSubscribe = () => void interface ServerAuthorizationConfigVo { @@ -169,6 +192,45 @@ interface AgvStatusVo { virtualExecutorPayload: string; // 车的虚拟执行器负载 } +interface BasLocationVo { + /** + * 位置编码 + */ + locCode: string + + /** + * 货位类型 + */ + locType: string + + /** + * 货位 + */ + rack: string + + /** + * 列 + */ + bay: number + + /** + * 层 + */ + level: number + + /** + * 格 + */ + cell: number +} + +interface InvUpdateVo { + lpn: string + before: BasLocationVo | null + after: BasLocationVo | null + qty: number +} + /** * 服务器状态信息 */