diff --git a/src/components/YvSrcEditorInner.vue b/src/components/YvSrcEditorInner.vue index f8444c7..d46035e 100644 --- a/src/components/YvSrcEditorInner.vue +++ b/src/components/YvSrcEditorInner.vue @@ -150,6 +150,13 @@ export default { () => null ) }, + clear(){ + if (this.editor) { + this.editor.setValue('') + this.$emit('update:modelValue', '') + this.$emit('change', '') + } + }, init() { // this.editor.updateOptions(option); this.editor.setValue(this.modelValue ?? '') diff --git a/src/core/base/BaseRenderer.ts b/src/core/base/BaseRenderer.ts index b33f9f6..cd8e9ba 100644 --- a/src/core/base/BaseRenderer.ts +++ b/src/core/base/BaseRenderer.ts @@ -108,7 +108,7 @@ export default abstract class BaseRenderer { } createPointForEntity(item: ItemJson, option?: RendererCudOption): Object3DLike { - const point = this.createPoint(item, option) + let point = this.createPoint(item, option) point.visible = ((typeof item.v !== 'undefined') ? item.v : true) if (item.dt.storeAt?.item) { @@ -118,7 +118,24 @@ export default abstract class BaseRenderer { setUserDataForItem(item, point) this.afterCreateOrUpdatePoint(item, option, point) this.tempViewport.entityManager.appendObject(item.id, point) - this.appendToScene(point) + if (typeof option.getParentObject3D === 'function') { + // 要求添加到指定 父对象 + const parent: THREE.Object3D = option.getParentObject3D(this.tempViewport, item) + if (point instanceof MeshWrap) { + const wrap = point as MeshWrap + const mesh = wrap.manager.wrapToObject3D(wrap) + this.tempViewport.entityManager.replaceObject(item.id, mesh) + parent.add(mesh) + point = mesh + + } else { + parent.add(point) + } + + } else { + // 默认添加到场景中 + this.appendToScene(point) + } return point } @@ -147,7 +164,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}`) } @@ -155,13 +172,14 @@ export default abstract class BaseRenderer { if (!rackRenderer) { console.error(`Cannot find renderer for rack type ${rack.t}`) } - const { position, rotation } = rackRenderer.getStorePlacement(rack, item.dt.storeAt.bay, item.dt.storeAt.level, item.dt.storeAt.cell) + const { position, rotation, getParentObject3D } = rackRenderer.getStorePlacement(rack, item.dt.storeAt.bay, item.dt.storeAt.level, item.dt.storeAt.cell) if (!position || !rotation) { console.error(`无法获取物品 ${item.id} 的存储位置`) } option.position = position option.rotation = rotation + option.getParentObject3D = getParentObject3D } if (_.isArray(option?.position) && _.isArray(option?.rotation)) { @@ -312,7 +330,10 @@ export default abstract class BaseRenderer { * 获取物品存储到地堆货位中,返回可存放的位置和角度一个 Position 和 Rotation */ getStorePlacement(storeItem: ItemJson, bay = 0, level = 0, cell = 0) - : { position: [number, number, number], rotation: [number, number, number] } { + : { + position: [number, number, number], rotation: [number, number, number], + getParentObject3D?: (viewport: Viewport, parent: ItemJson) => THREE.Object3D + } { throw new Error(' 不支持库存物品的添加. t=' + this.itemTypeName) } diff --git a/src/core/manager/BackendMessageReceiver.ts b/src/core/manager/BackendMessageReceiver.ts index bddf51a..c6cd985 100644 --- a/src/core/manager/BackendMessageReceiver.ts +++ b/src/core/manager/BackendMessageReceiver.ts @@ -1,6 +1,5 @@ import mqtt, { type IClientOptions, type IClientSubscribeOptions } from 'mqtt' import { reactive } from 'vue' -import type Viewport from '@/core/engine/Viewport.ts' // 定义MQTT消息类型 export interface MqttMessage { @@ -22,6 +21,7 @@ type ProcessFn = (message: MqttMessage, handler: BackendMessageHandler) => void interface HandlerNode { processFn: ProcessFn handler: BackendMessageHandler + handlerId: string } /** @@ -86,6 +86,7 @@ export default class BackendMessageReceiver { this.client.on('close', () => { this.state.status = ConnectionStatus.DISCONNECTED this.state.isConnected = false + debugger console.log('backendMQTT disconnected') }) @@ -175,7 +176,8 @@ export default class BackendMessageReceiver { if (!this.handlers.has(topic)) { this.handlers.set(topic, []) } - this.handlers.get(topic)?.push({ processFn, handler }) + const handlerId = system.createUUID() + this.handlers.get(topic)?.push({ processFn, handler, handlerId }) // 如果尚未订阅该主题 if (!this.state.subscribedTopics.includes(topic) && this.client) { @@ -185,54 +187,50 @@ export default class BackendMessageReceiver { this.client.subscribe(topic, options, (err) => { if (err) { console.error(`Failed to subscribe to ${topic}:`, err) + } else { + console.log(`BackendMQTT Subscribed to ${topic}`) } }) } return () => { // 取消订阅 - this.unsubscribe(type, handler) + this.unsubscribe(handlerId) } } // 取消订阅 - public unsubscribe(type: BackendTopicType, handler: BackendMessageHandler): void { + private unsubscribe(handlerId: string): void { // if (!this.client?.connected) { // throw new Error('Cannot unsubscribe - backendMQTT not connected') // } - const [topic, processFn] = this.getTopicStringByType(type) - if (!topic) { - throw new Error(`Invalid topic for type ${type}`) - } - // 移除特定处理函数 - if (handler && this.handlers.has(topic)) { - const handlers = this.handlers.get(topic) || [] - const newHandlers = handlers.filter(h => (h.processFn !== processFn && h.handler !== handler)) + for (const [topic, handlers] of this.handlers.entries()) { + const newHandlers = handlers.filter(node => node.handlerId !== handlerId) if (newHandlers.length > 0) { + this.handlers.set(topic, newHandlers) - } else { - this.handlers.delete(topic) - } - } else { - this.handlers.delete(topic) - } - // 如果没有处理器了,取消订阅 - if (!this.handlers.has(topic) && this.client) { + } else { - const options = {} - console.log(`client.unsubscribe(${topic},`, options) - this.client.unsubscribe(topic, options, (err) => { - if (err) { - console.error(`Failed to unsubscribe from ${topic}:`, err) - } else { - this.state.subscribedTopics = this.state.subscribedTopics.filter(t => t !== topic) - console.log(`Unsubscribed from ${topic}`) + // 如果没有处理器了,取消订阅 + this.handlers.delete(topic) + if (this.client) { + const options = {} + console.log(`client.unsubscribe(${topic},`, options) + this.client.unsubscribe(topic, options, (err) => { + if (err) { + console.error(`Failed to unsubscribe from ${topic}:`, err) + } else { + this.state.subscribedTopics = this.state.subscribedTopics.filter(t => t !== topic) + console.log(`BackendMQTT Unsubscribed from ${topic}`) + } + }) } - }) + + } } } @@ -351,7 +349,7 @@ export default class BackendMessageReceiver { private handleDeviceAlive(message: MqttMessage, handler: BackendMessageHandler) { try { const data = JSON.parse(message.payload.toString()) - handler('DeviceAlive', message.topic, _.cloneDeep(data)) + handler('DeviceAlive', message.topic, data) // 处理设备存活状态 } catch (error) { @@ -361,8 +359,7 @@ export default class BackendMessageReceiver { private handleLogMonitor(message: MqttMessage, handler: BackendMessageHandler) { try { - const data = JSON.parse(message.payload.toString()) - handler('Logs', message.topic, _.cloneDeep(data)) + handler('Logs', message.topic, message.payload.toString()) // 处理日志更新 } catch (error) { @@ -373,7 +370,7 @@ export default class BackendMessageReceiver { private handleAlarmMonitor(message: MqttMessage, handler: BackendMessageHandler) { try { const data = JSON.parse(message.payload.toString()) - handler('Alarm', message.topic, _.cloneDeep(data)) + handler('Alarm', message.topic, data) } catch (error) { console.error('Error parsing alarm data:', error) diff --git a/src/core/manager/EnvManager.ts b/src/core/manager/EnvManager.ts index cf1022c..90acf2c 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,6 @@ 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() this.client = mqtt.connect(env.envConfig.mqtt.websocket, { path: '/mqtt', @@ -71,6 +71,13 @@ export default class EnvManager { }) await this.loadExecutorToModel() + this.stopSubscribe.push( + worldModel.backendMessageReceiver.subscribe('InvUpdate', this.onInvUpdateMessage.bind(this)) + ) + this.stopSubscribe.push( + worldModel.backendMessageReceiver.subscribe('ServerState', this.onServerUpdateMessage.bind(this)) + ) + await this.loadInvToModel() this.client.on('connect', this.onMqttConnect) this.client.on('message', this.onMqttMessage) @@ -83,6 +90,61 @@ export default class EnvManager { } } + /** + * 监听在服务器停机之后,客户端连接也必须停机 + */ + onServerUpdateMessage(type: BackendTopicType, topic: string, data: ServerStatusVo) { + if (worldModel.state.runState.isRunning && worldModel.state.runState.currentEnvId === data.envId) { + // 处理服务器状态更新 + if (!data.isRunning) { + // 如果服务器停止了,则断开连接 + console.log(`Server stopped: ${data.envId}`) + system.msg(`Server is stopped, client disconnect!`, 'warning') + this.disconnectEnv().finally() + } + } + } + + async onInvUpdateMessage(type: BackendTopicType, topic: string, body: InvUpdateVo) { + console.log(`InvUpdate: ${type} ${topic}`, body) + + if (!window['Model']) { + // 如果没有3D模型加载,则不处理库存更新 + return + } + + Model.deleteInv(body.lpn) + + 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 +190,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() @@ -172,6 +240,6 @@ export default class EnvManager { * 卸载资源 */ dispose(): void { - this.disconnectEnv() + this.disconnectEnv().finally() } } 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..778402e 100644 --- a/src/core/script/ModelManager.ts +++ b/src/core/script/ModelManager.ts @@ -77,6 +77,10 @@ export default class ModelManager implements IControls, Model { return item } + find3D(id: string): any { + return this.viewport.entityManager.findObjectById(id) + } + getPositionByEntityId(entityId: string): THREE.Vector3 { const item = this.viewport.entityManager.findItemById(entityId) const matrix = getMatrixFromTf(item.tf) @@ -85,7 +89,16 @@ export default class ModelManager implements IControls, Model { return position } + deleteInv(itemId) { + this.viewport.runtimeManager.removeEntity(itemId) + } + 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/editor/widgets/logger/LoggerView.vue b/src/editor/widgets/logger/LoggerView.vue index 236b637..65a5421 100644 --- a/src/editor/widgets/logger/LoggerView.vue +++ b/src/editor/widgets/logger/LoggerView.vue @@ -11,12 +11,13 @@
- +
diff --git a/src/editor/widgets/monitor/MonitorView.vue b/src/editor/widgets/monitor/MonitorView.vue index 405b984..869be24 100644 --- a/src/editor/widgets/monitor/MonitorView.vue +++ b/src/editor/widgets/monitor/MonitorView.vue @@ -4,12 +4,16 @@ + 全上线 + - @@ -93,6 +97,18 @@ export default { this.undescribe() }, methods: { + simBootAll() { + const viewport = window['viewport'] + for (const deviceInfo of this.deviceList) { + if (deviceInfo.isOnline) { + continue + } + const view3DObject = Model.find3D(deviceInfo.id) + if (typeof view3DObject.boot === 'function') { + view3DObject.boot() + } + } + }, copyDeviceId(deviceInfo) { navigator.clipboard.writeText(deviceInfo.id).then(() => { system.msg('设备ID已复制到剪贴板', 'success') @@ -154,7 +170,7 @@ export default { worldModel.backendMessageReceiver.subscribe('DeviceStatus', this.onDeviceStatusMessage.bind(this)) ) }, - async refreshData(){ + async refreshData() { this.deviceList = [] const res = await LCC.queryDeviceInfoList() if (!res.success) { @@ -234,6 +250,9 @@ export default { }, idle() { return this.deviceList.filter(device => device.mode === 'AMR_FREE_MODE').length + }, + worldModelState() { + return worldModel.state } }, watch: { diff --git a/src/editor/widgets/server/EnvSelectConnect.vue b/src/editor/widgets/server/EnvSelectConnect.vue index 52de079..5dfda8c 100644 --- a/src/editor/widgets/server/EnvSelectConnect.vue +++ b/src/editor/widgets/server/EnvSelectConnect.vue @@ -55,10 +55,10 @@ export default { methods: { renderIcon, connectEnv() { - worldModel.envManager.connectEnv() + worldModel.envManager.connectEnv().finally() }, disconnectEnv() { - worldModel.envManager.disconnectEnv() + worldModel.envManager.disconnectEnv().finally() }, createEnv() { EnvManager.createEnv(this.worldModelState.project_uuid).then(() => { diff --git a/src/editor/widgets/server/ServerView.vue b/src/editor/widgets/server/ServerView.vue index c5e4252..e6ffbae 100644 --- a/src/editor/widgets/server/ServerView.vue +++ b/src/editor/widgets/server/ServerView.vue @@ -108,6 +108,13 @@ export default { worldModel.backendMessageReceiver.subscribe('ServerState', this.onServerStateMessage.bind(this)) ) }, + undescribe() { + // 取消订阅设备状态消息 + for (const stopFn of this.stopSubscribe) { + stopFn() + } + this.stopSubscribe = [] + }, async refreshData() { this.serverList = [] this.isLoading = true @@ -123,13 +130,6 @@ export default { this.isLoading = false } }, - undescribe() { - // 取消订阅设备状态消息 - for (const stopFn of this.stopSubscribe) { - stopFn() - } - this.stopSubscribe = [] - }, // 获取系统标签的样式类 getSystemTagClass(system) { diff --git a/src/modules/amr/ptr/PtrObject.ts b/src/modules/amr/ptr/PtrObject.ts index ac8623e..facf6cc 100644 --- a/src/modules/amr/ptr/PtrObject.ts +++ b/src/modules/amr/ptr/PtrObject.ts @@ -1,4 +1,4 @@ -import * as THREE from "three"; +import * as THREE from 'three' import { AmrErrorCode, AmrMsg, AmrMsg10010, AmrMsg10050, AmrMsg10060, AmrMsg10110, AmrMsg10120, AmrMsg20010, @@ -14,14 +14,14 @@ import { TaskModeChangeData, TaskStatusChangeData, TaskTypeChangeData -} from "@/core/manager/amr/AmrMessageDefine"; -import {worldModel} from "@/core/manager/WorldModel"; -import Viewport from "@/core/engine/Viewport"; -import {Euler} from "three/src/math/Euler"; -import gsap from "gsap"; +} from '@/core/manager/amr/AmrMessageDefine' +import { worldModel } from '@/core/manager/WorldModel' +import Viewport from '@/core/engine/Viewport' +import { Euler } from 'three/src/math/Euler' +import gsap from 'gsap' -type CStepTaskType = "MOVE" | "MOVE_BACKWARD" | "ROTATION" | "LOAD" | "UNLOAD" | "CHARGE" +type CStepTaskType = 'MOVE' | 'MOVE_BACKWARD' | 'ROTATION' | 'LOAD' | 'UNLOAD' | 'CHARGE' export interface StepTask { SeqNo: number; @@ -55,8 +55,8 @@ export default class PtrObject extends THREE.Object3D { public clock = new THREE.Clock() - override AGVModel = "" - override AGVFnModel = "" + override AGVModel = '' + override AGVFnModel = '' private boxBody: any = null private __toPos: THREE.Vector3 = null @@ -159,7 +159,7 @@ export default class PtrObject extends THREE.Object3D { private mqRetryTimeCount: number = 0 constructor(item: ItemJson, viewport: Viewport) { - super(); + super() this.viewport = viewport this.item = item this.vehicleId = parseInt(item.id) @@ -194,23 +194,23 @@ export default class PtrObject extends THREE.Object3D { // 开机 boot() { - this.bootTime = Date.now(); - this.computeLogicXYAndDirection(); + this.bootTime = Date.now() + this.computeLogicXYAndDirection() - const boxShape = new this.viewport.ammoModel.btBoxShape(new Ammo.btVector3(0.5, 0.5, 0.5)); + const boxShape = new this.viewport.ammoModel.btBoxShape(new Ammo.btVector3(0.5, 0.5, 0.5)) const mass = 10 const localInertia = new this.viewport.ammoModel.btVector3(0, 0, 0) boxShape.calculateLocalInertia(mass, localInertia) - const boxTransform = new this.viewport.ammoModel.btTransform(); - boxTransform.setIdentity(); - boxTransform.setOrigin(new this.viewport.ammoModel.btVector3(this.position.x, this.position.y, this.position.z)); + const boxTransform = new this.viewport.ammoModel.btTransform() + boxTransform.setIdentity() + boxTransform.setOrigin(new this.viewport.ammoModel.btVector3(this.position.x, this.position.y, this.position.z)) - const boxMotionState = new this.viewport.ammoModel.btDefaultMotionState(boxTransform); + const boxMotionState = new this.viewport.ammoModel.btDefaultMotionState(boxTransform) const rbInfo = new this.viewport.ammoModel.btRigidBodyConstructionInfo(mass, boxMotionState, boxShape, localInertia) - this.boxBody = new this.viewport.ammoModel.btRigidBody(rbInfo); - this.viewport.physicsWorld.addRigidBody(this.boxBody); + this.boxBody = new this.viewport.ammoModel.btRigidBody(rbInfo) + this.viewport.physicsWorld.addRigidBody(this.boxBody) if (!worldModel.state.runState.isVirtual) { return @@ -321,7 +321,7 @@ export default class PtrObject extends THREE.Object3D { ActuatorsData: [ { MechNo: 1, - Name: "Mech1", + Name: 'Mech1', PickMode: this.PickMode } ] @@ -349,10 +349,10 @@ export default class PtrObject extends THREE.Object3D { content.CurLogicX = this.currentLogicX content.CurLogicY = this.currentLogicY content.CurOrientation = this.currentOrientation - content.CurX = this.position.x; - content.CurY = this.position.z; - content.X = this.position.x; - content.Y = this.position.z; + content.CurX = this.position.x + content.CurY = this.position.z + content.X = this.position.x + content.Y = this.position.z const m20060 = new AmrMsg(content) this.sendMessage(m20060) } @@ -362,7 +362,7 @@ export default class PtrObject extends THREE.Object3D { } subscribeMessage(topic: string) { - worldModel.envManager.client.subscribe(topic, {qos: 0}) + worldModel.envManager.client.subscribe(topic, { qos: 0 }) } sendMessage(msg: AmrMsg) { @@ -385,7 +385,7 @@ export default class PtrObject extends THREE.Object3D { return } const content = new AmrMsg20100(this.vehicleId) - content.Temperature = {Battery: this.Battery} + content.Temperature = { Battery: this.Battery } const m20100 = new AmrMsg(content) worldModel.envManager.client.publish('/agv_robot/status', JSON.stringify(m20100)) } @@ -460,27 +460,27 @@ export default class PtrObject extends THREE.Object3D { } const ddra = Math.PI / 180 if (ra >= Math.PI * 2 - ddra || ra <= ddra) { - this.currentDirection = 0; + this.currentDirection = 0 } else if (ra >= Math.PI / 2 - ddra && ra <= Math.PI / 2 + ddra) { - this.currentDirection = 3; + this.currentDirection = 3 } else if (ra >= Math.PI - ddra && ra <= Math.PI + ddra) { - this.currentDirection = 2; + this.currentDirection = 2 } else if (ra >= Math.PI / 2 * 3 - ddra && ra <= Math.PI / 2 * 3 + ddra) { - this.currentDirection = 1; + this.currentDirection = 1 } else { - this.currentDirection = 15; + this.currentDirection = 15 } const pointItem = Model.getItemByXYZ(this.position.x, this.position.y, this.position.z) if (!pointItem || !pointItem.logicX || !pointItem.logicY) { - this.currentLogicX = -1; - this.currentLogicY = -1; + this.currentLogicX = -1 + this.currentLogicY = -1 } else { - this.currentLogicX = pointItem.logicX; - this.currentLogicY = pointItem.logicY; + this.currentLogicX = pointItem.logicX + this.currentLogicY = pointItem.logicY } this.currentOrientation = this.getAmrOrientation(this.rotation.y) } @@ -494,7 +494,7 @@ export default class PtrObject extends THREE.Object3D { } else { currentStepTask = { SeqNo: 0, - StepTaskType: "MOVE", + StepTaskType: 'MOVE', OperationType: 0, PickMode: 0, X: this.currentLogicX, @@ -513,7 +513,7 @@ export default class PtrObject extends THREE.Object3D { const linkCount = data.Link?.length || 0 if (linkCount > 0) { - let prevLink = {X: data.StartX, Y: data.StartY, Speed: 1000} + let prevLink = { X: data.StartX, Y: data.StartY, Speed: 1000 } for (let i = 0; i < data.Link.length; i++) { const link = data.Link[i] if ((currentStepTask.X == link.X && currentStepTask.Y == link.Y) @@ -549,7 +549,7 @@ export default class PtrObject extends THREE.Object3D { if (endDirection != currentStepTask.EndDirection) { const stepTask: StepTask = { SeqNo: data.SeqNo, - StepTaskType: "ROTATION", + StepTaskType: 'ROTATION', OperationType: 0, PickMode: 0, X: prevLink.X, @@ -567,7 +567,7 @@ export default class PtrObject extends THREE.Object3D { const stepTask: StepTask = { SeqNo: data.SeqNo, - StepTaskType: link.Speed > 0 ? "MOVE" : "MOVE_BACKWARD", + StepTaskType: link.Speed > 0 ? 'MOVE' : 'MOVE_BACKWARD', OperationType: 0, PickMode: 0, X: link.X, @@ -590,7 +590,7 @@ export default class PtrObject extends THREE.Object3D { || (linkCount > 0 && data.Link[linkCount - 1].X == data.EndX && data.Link[linkCount - 1].Y == data.EndY)) { if (data.OperationType == 0 && data.EndDirection >= 0 && data.EndDirection <= 3) { - endDirection = data.EndDirection; + endDirection = data.EndDirection } else if (data.OperationType == 3 && data.ChargeDirection >= 0 && data.ChargeDirection <= 3) { endDirection = data.ChargeDirection } else if (data.OperationType == 4 && data.GoodsSlotDirection >= 0 && data.GoodsSlotDirection <= 3) { @@ -610,7 +610,7 @@ export default class PtrObject extends THREE.Object3D { } const stepTask: StepTask = { SeqNo: data.SeqNo, - StepTaskType: "ROTATION", + StepTaskType: 'ROTATION', OperationType: 0, PickMode: 0, X: data.EndX, @@ -629,7 +629,7 @@ export default class PtrObject extends THREE.Object3D { const stepTask: StepTask = { SeqNo: data.SeqNo, - StepTaskType: "CHARGE", + StepTaskType: 'CHARGE', OperationType: 3, PickMode: 0, X: data.EndX, @@ -647,7 +647,7 @@ export default class PtrObject extends THREE.Object3D { const stepTask: StepTask = { SeqNo: data.SeqNo, - StepTaskType: data.PickMode == 1 ? "LOAD" : "UNLOAD", + StepTaskType: data.PickMode == 1 ? 'LOAD' : 'UNLOAD', OperationType: 4, PickMode: data.PickMode, X: data.EndX, @@ -676,7 +676,7 @@ export default class PtrObject extends THREE.Object3D { while (this.currentStepTaskList.length > 0) { const stepTask = this.currentStepTaskList[0] if (this.runningStepTask) { - if ((stepTask.StepTaskType == "MOVE" || stepTask.StepTaskType == "MOVE_BACKWARD") + if ((stepTask.StepTaskType == 'MOVE' || stepTask.StepTaskType == 'MOVE_BACKWARD') && this.rotationAnimation == null && this.actionAnimation == null && stepTask.EndDirection == this.runningStepTask.EndDirection && (stepTask.Speed > 0) == (this.runningStepTask.Speed > 0)) { @@ -693,13 +693,13 @@ export default class PtrObject extends THREE.Object3D { this.runningStepTask = stepTask this.currentStepTaskList.shift() this.runningStepTaskList.push(stepTask) - if (stepTask.StepTaskType == "MOVE" || stepTask.StepTaskType == "MOVE_BACKWARD") { + if (stepTask.StepTaskType == 'MOVE' || stepTask.StepTaskType == 'MOVE_BACKWARD') { this.addTravel(stepTask.X, stepTask.Y, stepTask.Speed / 1000) - } else if (stepTask.StepTaskType == "ROTATION") { + } else if (stepTask.StepTaskType == 'ROTATION') { this.addRotation(stepTask.EndDirection) - } else if (stepTask.StepTaskType == "LOAD") { + } else if (stepTask.StepTaskType == 'LOAD') { this.addLoad(stepTask.GoodsSlotHeight / 1000) - } else if (stepTask.StepTaskType == "UNLOAD") { + } else if (stepTask.StepTaskType == 'UNLOAD') { this.addUnload(stepTask.GoodsSlotHeight / 1000) } } @@ -775,7 +775,6 @@ export default class PtrObject extends THREE.Object3D { addTravel(logicX: number, logicY: number, speed: number = 1): Promise { - this.OperationType = 0 this.PickMode = 0 const pos = Model.getPositionByLogicXY(logicX, logicY) @@ -790,14 +789,14 @@ export default class PtrObject extends THREE.Object3D { } // 运动参数 - const accelForce = speed > 0 ? 4 : (-4 ) - let currentPhase = 'accelerate'; + const accelForce = speed > 0 ? 4 : (-4) + let currentPhase = 'accelerate' if (this.viewport.registerPhysicsUpdateCallBack.has(this.item.id)) { return null } - this.travelAnimation = "asd" + this.travelAnimation = 'asd' const force = new this.viewport.ammoModel.btVector3(accelForce, 0, 0) this.viewport.registerPhysicsUpdateCallBack.set(this.item.id, () => { @@ -826,7 +825,8 @@ export default class PtrObject extends THREE.Object3D { this.travelAnimation = null this.onActionCompleted() - } if (distance <= stopDistance) { + } + if (distance <= stopDistance) { currentPhase = 'decelerate' } // 运动阶段控制 diff --git a/src/modules/amr/ptr/cl2/Cl2Renderer.ts b/src/modules/amr/ptr/cl2/Cl2Renderer.ts index c1627ee..586ce1e 100644 --- a/src/modules/amr/ptr/cl2/Cl2Renderer.ts +++ b/src/modules/amr/ptr/cl2/Cl2Renderer.ts @@ -1,8 +1,9 @@ import * as THREE from 'three' import BaseRenderer from '@/core/base/BaseRenderer.ts' import Constract from '@/core/Constract.ts' -import Cl23dObject from "./Cl23dObject"; +import Cl23dObject from './Cl23dObject' import type { Object3DLike } from '@/types/ModelTypes.ts' +import type Viewport from '@/core/engine/Viewport.ts' /** * ptr侧叉渲染器 @@ -24,6 +25,39 @@ export default class PtrRenderer extends BaseRenderer { } /** + * 如果某物品要放到 Cl2 上 返回可存放的位置和角度一个 Position 和 Rotation + */ + getStorePlacement(storeItem: ItemJson, bay = 0, level = 0, cell = 0) + : { + position: [number, number, number], rotation: [number, number, number], + getParentObject3D?: (viewport: Viewport, parent: THREE.Object3D) => THREE.Object3D + } { + + return { + position: [0, 0.2, 0], + rotation: [0, 90, 0], + getParentObject3D: this.getArmObject.bind(this) + } + } + + getArmObject(viewport: Viewport, item: ItemJson): THREE.Object3D { + // 获取机械臂对象 + const object = viewport.entityManager.findObjectById(item.dt.storeAt?.item) + if (!object) { + console.warn('PtrRenderer: getArmObject failed, not found Cl2:', item.dt.storeAt?.item) + return + } + + const agv = object as THREE.Group + if (agv.children.length > 1) { + const pillar = agv.children[1] + if (pillar.children.length > 1) { + return pillar.children[1] + } + } + } + + /** * 所有的点,必须使用 storeWidth/storeDepth, 改TF无效 */ override afterCreateOrUpdatePoint(item: ItemJson, option: RendererCudOption, object: THREE.Object3D) { @@ -39,7 +73,6 @@ export default class PtrRenderer extends BaseRenderer { ) } - createLineBasic(start: ItemJson, end: ItemJson, type: LinkType): THREE.Object3D { throw new Error('not allow store line.') } @@ -51,11 +84,6 @@ export default class PtrRenderer extends BaseRenderer { createPoint(item: ItemJson, option?: RendererCudOption): THREE.Object3D { // 创建平面几何体 - if (!item.dt.ptrWidth || !item.dt.ptrDepth) { - system.showErrorDialog('field ptrWidth / ptrDepth is null!') - return null - } - const group = new Cl23dObject(item, this.tempViewport, option) group.name = PtrRenderer.POINT_NAME @@ -64,7 +92,6 @@ export default class PtrRenderer extends BaseRenderer { return group } - updatePoint(item: ItemJson, object: Object3DLike, option?: RendererCudOption): Object3DLike { const group: THREE.Group = object as THREE.Group diff --git a/src/modules/rack/RackRenderer.ts b/src/modules/rack/RackRenderer.ts index 21c9f4f..93e0dc9 100644 --- a/src/modules/rack/RackRenderer.ts +++ b/src/modules/rack/RackRenderer.ts @@ -83,7 +83,7 @@ export default class RackRenderer extends BaseRenderer { localX += bays[bay].bayWidth / 2 // 居中 let localY = 0 - for (let i = 0; i < level; i++) { + for (let i = 0; i <= level; i++) { localY += levelHeights[i] } 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 +} + /** * 服务器状态信息 */ diff --git a/src/types/Model.d.ts b/src/types/Model.d.ts index 17eb8f7..dcdd001 100644 --- a/src/types/Model.d.ts +++ b/src/types/Model.d.ts @@ -3,12 +3,18 @@ */ declare interface Model { /** - * 根据实体 ID 查找模型实体 + * 根据实体 ID 查找数据实体 * @param entityId 实体 ID */ find(entityId: string): ItemJson /** + * 根据实体 ID 查找模型实体 + * @param id + */ + find3D(id: string): any + + /** * 根据实体 ID 获取位置 */ getPositionByEntityId(entityId: string): Vector3IF @@ -40,6 +46,11 @@ declare interface Model { */ createInv(boxType: ContainerT, lpn: string, rack: string, bay: number = 0, level: number = 0, cell: number = 0): void + /** + * 根据 ID 删除库存物品 + * @param itemId 物品ID + */ + deleteInv(itemId: string): void /** * 在指定位置创建一个流动的库存物品 @@ -82,6 +93,7 @@ declare interface Model { */ multiSelectedEntityIds: string[] + } /** diff --git a/src/types/Types.d.ts b/src/types/Types.d.ts index 80f812d..66bd45b 100644 --- a/src/types/Types.d.ts +++ b/src/types/Types.d.ts @@ -63,6 +63,7 @@ interface RendererCudOption { position?: any //THREE.Quaternion rotation?: any + getParentObject3D?: (viewport:any, item: ItemJson) => any } /**