import type Viewport from '@/core/engine/Viewport.ts' import AMR_1 from '../../../lcc-map/jx_test/floor/AMR_1.json' import Decimal from 'decimal.js' export function AmrMapConvertDemo() { const from: any = AMR_1 const result = AmrMapConvert(from) console.log(result) debugger } window['AmrMapConvertDemo'] = AmrMapConvertDemo // AmrMapConvertDemo() export function AmrMapConvert(from: any): Array { const d1000 = new Decimal(1000) const d100 = new Decimal(100) const storeType = new Map() for (const td of from.toolingData) { if (td.categoryId === 1) { // 货架 /* 根据 layersHeight 获取出 "layersHeight": [ { "id": 1, "layerHeight": 1, "clearance": 1079, "layerGoodsAllocationGroup": { "goodsAllocationNum": 2, "goodsAllocation": [ { "isSelected": false, "gaps": [ { "value": 100 }, { "value": 100 }, { "value": 100 } ], "goodsPlacementOffset": 100, "goodsStackId": 1, "id": 98, "localNumber": "1-1", "goodsSlotFeatureType": "Default", "distanceToStation": 990, "goodsSlotFeatureHeight": "", "boxMarkerOffsetToBottom": 0, "goodsSlotTelescopicDistanceToDetectFeature": "", "verticalGroupId": "v-1", "horizontalGroupId": "h-1", "displayNumber": "", "beamHeight": 130, "layerHeight": 1, "clearance": 1079, "width": 1000, "orientation": [], "slideChute": false, "heightOverGoodsSlotWhenPut": null, "heightOverGoodsSlotWhenGet": null, "heightGapBetweenForkAndGoodsWhenGet": null, "heightGapBetweenForkAndGoodsWhenPut": null, "goodsSlotPillarHeight": null, "goodsSlotStatusCheckHeight": "" }, { "isSelected": false, "gaps": [ { "value": 100 }, { "value": 100 }, { "value": 100 } ], "goodsPlacementOffset": 100, "goodsStackId": 1, "id": 98, "localNumber": "1-2", "goodsSlotFeatureType": "Default", "distanceToStation": 990, "goodsSlotFeatureHeight": "", "boxMarkerOffsetToBottom": 0, "goodsSlotTelescopicDistanceToDetectFeature": "", "verticalGroupId": "v-2", "horizontalGroupId": "h-1", "displayNumber": "", "beamHeight": 130, "layerHeight": 1, "clearance": 1079, "width": 1000, "orientation": [], "slideChute": false, "heightOverGoodsSlotWhenPut": null, "heightOverGoodsSlotWhenGet": null, "heightGapBetweenForkAndGoodsWhenGet": null, "heightGapBetweenForkAndGoodsWhenPut": null, "goodsSlotPillarHeight": null, "goodsSlotStatusCheckHeight": "" } ] }, "goodsStackId": 1 }, { "id": 2, "layerHeight": 1210, "layerGoodsAllocationGroup": { "goodsAllocationNum": 2, "goodsAllocation": [ { "isSelected": false, "gaps": [ { "value": 100 }, { "value": 100 }, { "value": 100 } ], "goodsPlacementOffset": 100, "goodsStackId": 1, "id": 98, "localNumber": "2-1", "goodsSlotFeatureType": "Default", "distanceToStation": 990, "goodsSlotFeatureHeight": "", "boxMarkerOffsetToBottom": 0, "goodsSlotTelescopicDistanceToDetectFeature": "", "verticalGroupId": "v-1", "horizontalGroupId": "h-2", "displayNumber": "", "beamHeight": 130, "layerHeight": 1210, "clearance": 2000, "width": 1000, "orientation": [], "slideChute": false, "heightOverGoodsSlotWhenPut": null, "heightOverGoodsSlotWhenGet": null, "heightGapBetweenForkAndGoodsWhenGet": null, "heightGapBetweenForkAndGoodsWhenPut": null, "goodsSlotPillarHeight": null, "goodsSlotStatusCheckHeight": "" }, { "isSelected": false, "gaps": [ { "value": 100 }, { "value": 100 }, { "value": 100 } ], "goodsPlacementOffset": 100, "goodsStackId": 1, "id": 98, "localNumber": "2-2", "goodsSlotFeatureType": "Default", "distanceToStation": 990, "goodsSlotFeatureHeight": "", "boxMarkerOffsetToBottom": 0, "goodsSlotTelescopicDistanceToDetectFeature": "", "verticalGroupId": "v-2", "horizontalGroupId": "h-2", "displayNumber": "", "beamHeight": 130, "layerHeight": 1210, "clearance": 2000, "width": 1000, "orientation": [], "slideChute": false, "heightOverGoodsSlotWhenPut": null, "heightOverGoodsSlotWhenGet": null, "heightGapBetweenForkAndGoodsWhenGet": null, "heightGapBetweenForkAndGoodsWhenPut": null, "goodsSlotPillarHeight": null, "goodsSlotStatusCheckHeight": "" } ] }, "clearance": 2000 } ], */ const rackDepth = new Decimal(td.width).div(d1000).toNumber() const levelHeight = td.layersHeight.map((lh: any) => new Decimal(lh.layerHeight).div(d1000).toNumber()) const levelCount = levelHeight.length // 长度除以货位数 const width = new Decimal(td.length).div(td.goodsAllocationNum).div(d1000).toNumber() storeType.set(td.id, { 't': 'rack', 'v': true, 'tf': [ [0, 0, 0], [0, 0, 0], [width, 1.211, rackDepth] ], 'dt': { 'rackDepth': rackDepth, 'bottomBarHeight': 0.2, 'bottomLinkHeight': 0.2, 'topLinkDistance': 0.2, 'levelCount': levelCount, 'bayCount': 1, 'hideFloor': 0, 'extendColumns': 1, 'columnSpacing': 1, 'bays': [ { 'bayWidth': width, 'levelHeight': levelHeight, 'topHeight': 2 } ], 'center': [], 'in': [], 'out': [], 'rackWidth': width, 'rackHeight': 1.211 } }) } else if (td.categoryId === 2) { // 地堆 storeType.set(td.id, { 't': 'gstore', 'v': true, 'tf': [ [0, 0, 0], [0, 0, 0], [new Decimal(td.width).div(d1000).toNumber(), 0.01, new Decimal(td.length).div(d1000).toNumber()] ], 'dt': { 'in': [], 'out': [], 'center': [], 'strokeWidth': 0.1 } }) } } // ====================== 第一次循环,建立点位 ========================= const amrIdMap = new Map() const lccMap = new Map() let maxX = NaN, maxY = NaN, minX = NaN, minY = NaN for (const amrNode of from.businessMap[0].mapData) { amrIdMap.set(amrNode.id, amrNode) const lccId = _.toString(amrNode.id) // amrNode.logicX + '_' + amrNode.logicY // if (lccId === '105_105') { // debugger // } let insertNode: any = null if (amrNode.type === 'CODE') { if (isStorePos(amrNode)) { // =================== 这是一个存储位 ===================== const rackTypeId = amrNode.attribute[0].rackTypeId insertNode = { 'id': lccId, ..._.cloneDeep(storeType.get(rackTypeId)) } // 货架高度是从 attribute[0].goodsAllocation[].layerHeight 读取的 if (insertNode.t === 'rack') { const levelsInfo = amrNode.attribute[0].attrDetail const levelCount = levelsInfo.length const levelHeight = _.map(levelsInfo, l => new Decimal(l.val).div(d1000).toNumber()) insertNode.dt.levelCount = levelCount insertNode.dt.bays[0].levelHeight = levelHeight } } else { // =================== 这是一个路标 ===================== insertNode = { 'id': lccId, 't': 'way', 'v': true, 'tf': [ [0, 0, 0], [0, 0, 0], [0.25, 0.1, 0.25] ], 'dt': { 'qrCode': amrNode.uniqueName, 'in': [], 'out': [], 'center': [] } } // 旋转站 if (_.findIndex(amrNode.propsValues, v => v === 'CellTypeSpinStation') >= 0 && amrNode.constraints) { // 这是一个旋转站, 读取 constraints 所有的 key const agvRotation = [] for (const key of Object.keys(amrNode.constraints)) { agvRotation.push(key) } if (agvRotation.length > 0) { insertNode.dt.agvRotation = agvRotation } } } // 这是潜伏AGV货位 if (amrNode.type === 'CODE' && amrNode.attribute[0]?.rackTypeId === 3 && amrNode.attribute[0]?.attrValue === 'CellTypeRackLocation') { lccMap.set(lccId + 'C', { 'id': lccId + 'C', 't': 'gstore', 'v': true, 'tf': [ [ new Decimal(amrNode.x).div(d100).toNumber(), 0, new Decimal(amrNode.y).div(d100).toNumber() ], [0, 0, 0], [0.5, 0.01, 0.5] ], 'dt': { 'in': [], 'out': [], 'center': [], 'strokeWidth': 0.05 }, 'logicX': amrNode.logicX, 'logicY': amrNode.logicY }) insertNode.dt.linkStore = [] for (let c of amrNode.direction) { insertNode.dt.linkStore.push({ 'item': lccId + 'C', 'bay': 0, 'level': 0, 'cell': 0, 'direction': convertDirection(c) }) } } } if (insertNode) { // insertNode.originId = amrNode.id // insertNode.name = _.toString(amrNode.id) insertNode.tf[0][0] = new Decimal(amrNode.x).div(d100).toNumber() insertNode.tf[0][2] = new Decimal(amrNode.y).div(d100).toNumber() insertNode.logicX = amrNode.logicX insertNode.logicY = amrNode.logicY if (isNaN(maxX) || insertNode.tf[0][0] > maxX) { maxX = insertNode.tf[0][0] } if (isNaN(maxY) || insertNode.tf[0][2] > maxY) { maxY = insertNode.tf[0][2] } if (isNaN(minX) || insertNode.tf[0][0] < minX) { minX = insertNode.tf[0][0] } if (isNaN(minY) || insertNode.tf[0][2] < minY) { minY = insertNode.tf[0][2] } lccMap.set(lccId, insertNode) } } // ====================== 第二次循环,建立点位关联 ========================= for (const amrNode of from.businessMap[0].mapData) { if (amrNode.type !== 'ROAD') { continue } for (const dg of amrNode.directionGroup) { // 从 dg.startSite 和 dg.endSite 中获取对应的点位 const startNode = amrIdMap.get(dg.startSite) const endNode = amrIdMap.get(dg.endSite) if (!startNode || !endNode) { console.warn('未找到起点或终点节点', dg.startSite, dg.endSite) continue } const startLccId = _.toString(dg.startSite) // startNode.logicX + '_' + startNode.logicY const endLccId = _.toString(dg.endSite) // endNode.logicX + '_' + endNode.logicY if (!lccMap.get(startLccId) || !lccMap.get(endLccId)) { console.warn('未找到起点或终点 LCC 节点', startLccId, endLccId) continue } if (lccMap.get(startLccId).t === 'way' && lccMap.get(endLccId).t === 'way') { // 起点和终点都是路标 lccMap.get(startLccId).dt.in.push(endLccId) lccMap.get(startLccId).dt.out.push(endLccId) lccMap.get(endLccId).dt.in.push(startLccId) lccMap.get(endLccId).dt.out.push(startLccId) } else if (lccMap.get(startLccId).t === 'way' && (lccMap.get(endLccId).t === 'rack' || lccMap.get(endLccId).t === 'gstore')) { // 起点是路标,终点是存储位 if (!lccMap.get(startLccId).dt.linkStore) { lccMap.get(startLccId).dt.linkStore = [] } const direction = convertAmrDirectToLinkStoreDirection(amrNode.direction?.id) if (_.isArray(lccMap.get(endLccId).dt.bays?.[0]?.levelHeight)) { for (let i = 0; i < lccMap.get(endLccId).dt.bays[0].levelHeight.length; i++) { lccMap.get(startLccId).dt.linkStore.push({ 'item': endLccId, 'bay': 0, 'level': i, 'cell': 0, 'direction': direction }) } } else { lccMap.get(startLccId).dt.linkStore.push({ 'item': endLccId, 'bay': 0, 'level': 0, 'cell': 0, 'direction': direction }) } convertLinkStoreDistance(direction, lccMap.get(endLccId)) } else if (lccMap.get(endLccId).t === 'way' && (lccMap.get(endLccId).t === 'rack' || lccMap.get(endLccId).t === 'gstore')) { // 终点是路标,起点是存储位 if (!lccMap.get(endLccId).dt.linkStore) { lccMap.get(endLccId).dt.linkStore = [] } const direction = convertAmrDirectToLinkStoreDirection(amrNode.direction?.id) if (_.isArray(lccMap.get(startLccId).dt.bays?.[0]?.levelHeight)) { for (let i = 0; i < lccMap.get(startLccId).dt.bays[0].levelHeight.length; i++) { lccMap.get(endLccId).dt.linkStore.push({ 'item': startLccId, 'bay': 0, 'level': i, 'cell': 0, 'direction': direction }) } } else { lccMap.get(endLccId).dt.linkStore.push({ 'item': startLccId, 'bay': 0, 'level': 0, 'cell': 0, 'direction': direction }) } convertLinkStoreDistance(direction, lccMap.get(startLccId)) } } } // ====================== 第三次循环,将所有点位移动到画面中心 ========================= const centerX = new Decimal((maxX + minX) / 2).toNumber() const centerY = new Decimal((maxY + minY) / 2).toNumber() for (const entry of lccMap.entries()) { const node = entry[1] if (node.tf) { node.tf[0][0] = new Decimal(node.tf[0][0]).minus(centerX).toNumber() node.tf[0][2] = new Decimal(node.tf[0][2]).minus(centerY).toNumber() } } const planObj: Object = {} for (const entry of lccMap.entries()) { planObj[entry[0]] = entry[1] } // console.log(planObj) // debugger return Object.values(planObj) as Array } // 将 AMR 的方向转换为 LinkStore 的方向 function convertAmrDirectToLinkStoreDirection(direct: number): string { if (direct === 1) { return 'up' } else if (direct === 2) { return 'down' } else if (direct === 3) { return 'right' } else if (direct === 4) { return 'left' } else if (direct === 5) { return 'right' } else { throw new Error('未知的方向: ' + direct) } } // 判断点位是否为存储位 function isStorePos(amrNode: any) { return (_.findIndex(amrNode.propsValues, v => v === 'CellTypePallet') >= 0) } // 根据方向,调整存储位的距离 function convertLinkStoreDistance(direction: string, storeNode: any) { if (direction === 'up') { storeNode.tf[0][2] -= 1.2 } else if (direction === 'down') { storeNode.tf[0][2] += 1.2 } else if (direction === 'left') { storeNode.tf[0][0] -= 1.2 storeNode.tf[1][1] = 90 // 90 度旋转 } else if (direction === 'right') { storeNode.tf[0][0] += 1.2 storeNode.tf[1][1] = 90 // 90 度旋转 } } // 根据方向,调整存储位的距离 function convertDirection(directions: string) { // N=up / S=down / E=right / W=left if (directions === 'N') { return 'up' } if (directions === 'S') { return 'down' } if (directions === 'E') { return 'right' } if (directions === 'W') { return 'left' } throw new Error('convertDirection error:' + directions) }