From 6ce2cc877533873138433bf2cb2a854da45b0837 Mon Sep 17 00:00:00 2001 From: luoyifan Date: Sat, 5 Jul 2025 17:40:35 +0800 Subject: [PATCH] =?UTF-8?q?pallet=20=E6=97=A0=E6=B3=95=E5=87=BA=E7=8E=B0?= =?UTF-8?q?=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/core/ModelUtils.ts | 1 + src/core/base/BaseRenderer.ts | 4 +- src/core/manager/EnvManager.ts | 16 ++--- src/core/script/LCCScript.ts | 7 ++ src/editor/widgets/monitor/MonitorView.vue | 112 +++++++---------------------- src/modules/pallet/PalletRenderer.ts | 43 ++++++----- src/modules/tote/ToteRenderer.ts | 46 +----------- src/types/LCC.d.ts | 45 ++++++------ 8 files changed, 89 insertions(+), 185 deletions(-) diff --git a/src/core/ModelUtils.ts b/src/core/ModelUtils.ts index 3ce1871..8dea151 100644 --- a/src/core/ModelUtils.ts +++ b/src/core/ModelUtils.ts @@ -292,6 +292,7 @@ export function processModel(mesh: THREE.Mesh) { -center.z ) geometry.applyMatrix4(translate) + geometry.computeVertexNormals(); // 可选:验证最终包围盒 //@ts-ignore diff --git a/src/core/base/BaseRenderer.ts b/src/core/base/BaseRenderer.ts index 93bf038..b33f9f6 100644 --- a/src/core/base/BaseRenderer.ts +++ b/src/core/base/BaseRenderer.ts @@ -136,7 +136,7 @@ export default abstract class BaseRenderer { // 由基础类创造一个属于自己的点演示 const point = this.createPointBasic(item, option) const matrix = this.calcMeshMatrix(item, option) - point.applyMatrix4(matrix) + point.setMatrix4(matrix) point.visible = ((typeof item.v !== 'undefined') ? item.v : true) return point @@ -262,7 +262,7 @@ export default abstract class BaseRenderer { // point.manager.syncMeshObject3D(point) // } const matrix = this.calcMeshMatrix(item, option) - point.applyMatrix4(matrix) + point.setMatrix4(matrix) point.visible = ((typeof item.v !== 'undefined') ? item.v : true) return point diff --git a/src/core/manager/EnvManager.ts b/src/core/manager/EnvManager.ts index f2dbd31..03d28ab 100644 --- a/src/core/manager/EnvManager.ts +++ b/src/core/manager/EnvManager.ts @@ -7,7 +7,6 @@ import AmrMessageManager from '@/core/manager/amr/AmrMessageManager' import { AmrMsg } from '@/core/manager/amr/AmrMessageDefine' export default class EnvManager { - private viewport: Viewport private amrMessageManager: AmrMessageManager = new AmrMessageManager() public client: mqtt.MqttClient = null @@ -105,9 +104,10 @@ export default class EnvManager { this.client = null } - this.clearExecutors() - this.clearInv() - this.viewport?.runtimeManager?.clear() + if (window['viewport']) { + const viewport = window['viewport'] as Viewport + viewport?.runtimeManager?.clear() + } } finally { system.clearLoading() @@ -115,14 +115,6 @@ export default class EnvManager { } } - clearInv() { - - } - - clearExecutors() { - - } - /** * 创建运行环境 * @param worldId 世界ID diff --git a/src/core/script/LCCScript.ts b/src/core/script/LCCScript.ts index 3be4c38..e631e03 100644 --- a/src/core/script/LCCScript.ts +++ b/src/core/script/LCCScript.ts @@ -66,6 +66,13 @@ export default class LCCScript implements LCC { }) } + queryDeviceInfoList(): Promise> { + return Request.request.post('/api/workbench/LccController@queryDeviceInfoList', { + projectUUID: worldModel.state.project_uuid, + envId: worldModel.state.runState.currentEnvId + }) + } + // 从后台读取所有车 async loadExecutor(): Promise { const res = await Request.request.post('/api/workbench/LccController@loadExecutor', { diff --git a/src/editor/widgets/monitor/MonitorView.vue b/src/editor/widgets/monitor/MonitorView.vue index a6aa7ee..4bbc398 100644 --- a/src/editor/widgets/monitor/MonitorView.vue +++ b/src/editor/widgets/monitor/MonitorView.vue @@ -52,8 +52,8 @@ p-id="22185" fill="#ffffff">
- -
55%
+ +
{{ getTaskProcessPercent(deviceInfo) }} %
@@ -105,71 +105,31 @@ export default { return { searchKeyword: '', selectedId: '', - /* - 设备列表, 数据展示为: - [ - { - id: 'device-id', - type: 'device-type', - online: true, - x: 0, - y: 0, - z: 0, - logicX: 0, - logicY: 0, - direction: 'up', - orientation: 0, - soc: 100, - mode: 'AMR_FREE_MODE', - taskStatus: 'IDLE', - isBlocked: false, - taskCompleted: 0, - taskTotalCount: 10 - } - ] - */ /** * 设备列表数据 - * @type {Array<{ - id: string, - type: string, - online: boolean, - x: number, - y: number, - z: number, - logicX: number, - logicY: number, - direction: string, - orientation: number, - soc: number, - mode: 'AMR_FREE_MODE' | - 'AMR_INIT_MODE' | - 'AMR_TASK_MODE' | - 'AMR_SINGLE_ACTION_MODE' | - 'AMR_MANUAL_MODE' | - 'AMR_HANDSET_MODE' | - 'AMR_CHARGE_MODE' | - 'AMR_TASK_INTERRUPT_MODE' | - 'AMR_CUSTOMIZE_MODE', - taskStatus: string, - isBlocked: boolean, - taskCompleted: number, - taskTotalCount: number - // 业务任务ID - bizTaskId: string - // 业务任务类型: 移动任务 / 充电任务 / 搬运任务 / 装载任务 / 卸载任务 - bizTaskType: '' | 'MOVE' |'CHARGE' |'CARRY' |'LOAD' |'UNLOAD' - // 比如 WAY_1_1 Rack1/0/0/0 - bizTaskFrom: string - // 比如 WAY_1_1 Rack1/0/0/0 charge1 - bizTaskTo: string - }>} + * @type {Array} */ deviceList: [] } }, + mounted() { + window['MonitorView'] = this + }, + unmounted() { + window['MonitorView'] = null + this.undescribe() + }, methods: { /** + * 获取任务进度百分比 + */ + getTaskProcessPercent(deviceInfo) { + if (deviceInfo == null || deviceInfo.taskTotalCount === 0) { + return 0 + } + return Math.round((deviceInfo.taskCompleted / deviceInfo.taskTotalCount) * 100) + }, + /** * 处理设备存活消息 * @type {(type: BackendTopicType, topic: string, body: { * id: string @@ -207,35 +167,11 @@ export default { }, async subscribe() { this.deviceList = [] - const list = await LCC.loadExecutor() - this.deviceList = _.map(list, executorVo => { - const row = { - online: false - } - - return { - id: executorVo.executor_id, - type: row.type, - online: row.online, - x: row.x, - y: row.y, - z: row.z, - logicX: row.logicX, - logicY: row.logicY, - direction: row.direction, - orientation: row.orientation, - soc: row.soc, - mode: row.mode, - taskStatus: row.taskStatus, - isBlocked: row.isBlocked, - taskCompleted: row.taskCompleted || 0, - taskTotalCount: row.taskTotalCount || 0, - bizTaskId: row.bizTaskId || '', - bizTaskType: row.bizTaskType || '', - bizTaskFrom: row.bizTaskFrom || '', - bizTaskTo: row.bizTaskTo || '' - } - }) + const res = await LCC.queryDeviceInfoList() + if (!res.success) { + return + } + this.deviceList = res.data // 订阅设备状态消息 LCC.subscribe('DeviceAlive', this.onDeviceAliveMessage) diff --git a/src/modules/pallet/PalletRenderer.ts b/src/modules/pallet/PalletRenderer.ts index 35970dc..49ab54e 100644 --- a/src/modules/pallet/PalletRenderer.ts +++ b/src/modules/pallet/PalletRenderer.ts @@ -8,7 +8,7 @@ import { loadGlbModule, loadTexture, processModel } from '@/core/ModelUtils.ts' import InstanceMeshManager from '@/core/manager/InstanceMeshManager.ts' /** - * 货架货位渲染器 + * 托盘渲染器 */ export default class PalletRenderer extends BaseRenderer { static POINT_NAME = 'pallet_point' @@ -24,34 +24,38 @@ export default class PalletRenderer extends BaseRenderer { } palletGeometry: THREE.BufferGeometry - palletMaterial: THREE.Material + palletMaterial: THREE.MeshPhongMaterial + private _isInitialized = false + async init() { + await super.init() - init() { - return Promise.all([ - super.init(), + const [glbGroup, palletTexture] = await Promise.all([ loadGlbModule(MODULE_PALLET_GLB), loadTexture(MODULE_PALLET_TEX) + ]) - ]).then(([_, glbGroup, palletTexture]) => { - const mesh = glbGroup.children[0] as THREE.Mesh + const mesh = glbGroup.children[0] as THREE.Mesh - this.palletGeometry = processModel(mesh) - this.palletMaterial = new THREE.MeshPhongMaterial({ color: 0x2b5d94 }) + this.palletGeometry = processModel(mesh) + this.palletMaterial = new THREE.MeshPhongMaterial({ color: 0x2b5d94 }) - palletTexture.flipY = true - palletTexture.wrapS = THREE.RepeatWrapping - palletTexture.wrapT = THREE.RepeatWrapping - palletTexture.repeat.set(0.5, 0.5) + palletTexture.flipY = true + palletTexture.wrapS = THREE.RepeatWrapping + palletTexture.wrapT = THREE.RepeatWrapping + palletTexture.repeat.set(0.5, 0.5) - //@ts-ignore - this.palletMaterial.color.set(this.defaultUserData.color) - //@ts-ignore - this.palletMaterial.normalMap = palletTexture - }) + this.palletMaterial.color.set(this.defaultUserData.color) + this.palletMaterial.map = palletTexture + this.palletMaterial.needsUpdate = true + + this._isInitialized = true } createPointBasic(item: ItemJson, option?: RendererCudOption): Object3DLike { + if (!this._isInitialized) { + throw new Error('Renderer not initialized') + } return this.pointManager.createByItem(item) } @@ -59,6 +63,9 @@ export default class PalletRenderer extends BaseRenderer { if (!this.tempViewport) { throw new Error('tempViewport is not set.') } + if (!this._isInitialized) { + throw new Error('Renderer not ready') + } return this.tempViewport.getOrCreateMeshManager(this.itemTypeName, () => // 构建 InstanceMesh 代理对象 new InstanceMeshManager(this.itemTypeName, diff --git a/src/modules/tote/ToteRenderer.ts b/src/modules/tote/ToteRenderer.ts index 66b1609..97f27a2 100644 --- a/src/modules/tote/ToteRenderer.ts +++ b/src/modules/tote/ToteRenderer.ts @@ -8,7 +8,7 @@ import { load3DModule, loadByUrl, loadTexture, processModel } from '@/core/Model import InstanceMeshManager from '@/core/manager/InstanceMeshManager.ts' /** - * 货架货位渲染器 + * 周转箱渲染器 */ export default class PalletRenderer extends BaseRenderer { static POINT_NAME = 'pallet_point' @@ -26,50 +26,6 @@ export default class PalletRenderer extends BaseRenderer { toteGeometry: THREE.BufferGeometry toteMaterial: THREE.Material - // - // processModel(mesh: THREE.Mesh) { - // const geometry = mesh.geometry.clone() as THREE.BufferGeometry - // geometry.rotateX(-Math.PI / 2) - // - // // 获取原始包围盒 - // //@ts-ignore - // const box = new THREE.Box3().setFromBufferAttribute(geometry.attributes.position) - // const size = new THREE.Vector3() - // box.getSize(size) - // - // // 计算缩放因子:让整个包围盒变成 1x1x1 的立方体 - // const scaleX = 1 / size.x - // const scaleY = 1 / size.y - // const scaleZ = 1 / size.z - // - // // 缩放几何体到 1x1x1 立方体(保持原点不变) - // geometry.scale(scaleX, scaleY, scaleZ) - // - // // 更新包围盒 - // //@ts-ignore - // const scaledBox = new THREE.Box3().setFromBufferAttribute(geometry.attributes.position) - // const center = new THREE.Vector3() - // scaledBox.getCenter(center) - // - // // 平移:让中心在 (0, height/2, 0),底部贴地 - // const translate = new THREE.Matrix4().makeTranslation( - // -center.x, - // -scaledBox.min.y, // 保证底部贴地 - // -center.z - // ) - // geometry.applyMatrix4(translate) - // - // // 可选:验证最终包围盒 - // //@ts-ignore - // const finalBox = new THREE.Box3().setFromBufferAttribute(geometry.attributes.position) - // const finalSize = new THREE.Vector3() - // finalBox.getSize(finalSize) - // console.log('最终包围盒大小:', finalSize) // 应该是 (1, 1, 1) - // console.log('包围盒底部 Y 值:', finalBox.min.y) // 应该是 0 - // - // return geometry - // } - init() { return Promise.all([ super.init(), diff --git a/src/types/LCC.d.ts b/src/types/LCC.d.ts index 48bce25..1a97399 100644 --- a/src/types/LCC.d.ts +++ b/src/types/LCC.d.ts @@ -46,6 +46,11 @@ declare interface LCC { * @param eventHandler 消息处理函数 */ unsubscribe(topicType: BackendTopicType, eventHandler: BackendMessageHandler); + + /** + * 获取所有设备状态 + */ + queryDeviceInfoList(): Promise> } /** @@ -54,17 +59,32 @@ declare interface LCC { type BackendTopicType = 'ServerState' | 'ClientState' | 'TaskUpdate' | 'InvUpdate' | 'DeviceStatus' | 'DeviceAlive' | 'Logs' | 'Alarm' | 'ScriptUpdate' -type DeviceAliveFn = (type: BackendTopicType, topic: string, body: { +type DeviceAliveFn = (type: BackendTopicType, topic: string, body: DeviceAliveVo) => void + +type DeviceStatusFn = (type: BackendTopicType, topic: string, body: DeviceVo) => void + +/** + * 后端消息回调 + */ +type BackendMessageHandler = (type: BackendTopicType, topic: string, message: any) => void + +type ContainerT = 'pallet' | 'tote' | 'carton' | 'box' + +interface DeviceAliveVo { id: string type: string online: boolean -}) => void +} -type DeviceStatusFn = (type: BackendTopicType, topic: string, body: { +interface DeviceVo { // 设备 ID id: string // 设备类型 type: string + // 是否在线 + isOnline: boolean + // 是否系统托管 + isSystemManaged: boolean // 设备x,y,z坐标 x: number y: number @@ -88,15 +108,7 @@ type DeviceStatusFn = (type: BackendTopicType, topic: string, body: { // AMR_CHARGE_MODE=充电模式; // AMR_TASK_INTERRUPT_MODE=任务被中断模式; // AMR_CUSTOMIZE_MODE=自定义模式; - mode: 'AMR_FREE_MODE' | - 'AMR_INIT_MODE' | - 'AMR_TASK_MODE' | - 'AMR_SINGLE_ACTION_MODE' | - 'AMR_MANUAL_MODE' | - 'AMR_HANDSET_MODE' | - 'AMR_CHARGE_MODE' | - 'AMR_TASK_INTERRUPT_MODE' | - 'AMR_CUSTOMIZE_MODE' + mode: 'AMR_FREE_MODE' | 'AMR_INIT_MODE' | 'AMR_TASK_MODE' | 'AMR_SINGLE_ACTION_MODE' | 'AMR_MANUAL_MODE' | 'AMR_HANDSET_MODE' | 'AMR_CHARGE_MODE' | 'AMR_TASK_INTERRUPT_MODE' | 'AMR_CUSTOMIZE_MODE' // 任务状态 ID: IDLE / PAUSED / EXECUTING taskStatus: string // 是否阻挡 @@ -117,14 +129,7 @@ type DeviceStatusFn = (type: BackendTopicType, topic: string, body: { bizTaskTo: string // 搬运托盘号 bizLpn: string -}) => void - -/** - * 后端消息回调 - */ -type BackendMessageHandler = (type: BackendTopicType, topic: string, message: any) => void - -type ContainerT = 'pallet' | 'tote' | 'carton' | 'box' +} interface InvVo { lpn: string