基於 WEB 的 WMS 3D 可視化管理系統

基於 WEB 的 WMS 3D 可視化管理系統
前言
首先介紹一下什麼是WMS。WMS是倉庫管理系統(Warehouse Management System) 的縮寫,倉庫管理系統是經過入庫業務、出庫業務、倉庫調撥、庫存調撥和虛倉管理等功能,對批次管理、物料對應、庫存盤點、質檢管理、虛倉管理和即時庫存管理等功能綜合運用的管理系統,有效控制並跟蹤倉庫業務的物流和成本管理全過程,實現或完善的企業倉儲信息管理。該系統能夠獨立執行庫存操做,也可與其餘系統的單據和憑證等結合使用,可爲企業提供更爲完整企業物流管理流程和財務管理信息。
目前主流的 WMS 倉庫管理系統大都採用了 B/S 模式,但數據可視化技術上仍採用的是傳統圖表顯示方式。本文從數據可視化的角度介紹了一種基於 WEB 的 3D 可視化實現方案,底層基於標準的 HTML5 WebGL 技術,以 3D 的方式顯示倉庫立體場景,包括貨架、貨物、堆垛機、穿梭車、輸送機等。相對於傳統圖表顯示方式,三維的倉庫管理可視化顯示方式,顯得更加直觀和立體化,不管是用戶體驗仍是產品質量都獲得了巨大提高。
 
1、WebGL 介紹以及 3D 引擎的選擇
WebGL(全寫Web Graphics Library)是一種3D繪圖協議,這種繪圖技術標準容許把JavaScript和 OpenGL ES 2.0 結合在一塊兒,經過增長 OpenGL ES 2. 0的一個 JavaScript 綁定,WebGL 能夠爲HTML5 Canvas 提供硬件 3D 加速渲染,這樣Web開發人員就能夠藉助系統顯卡來在瀏覽器裏更流暢地展現3D場景和模型了,還能建立複雜的導航和數據視覺化。顯然,WebGL 技術標準免去了開發網頁專用渲染插件的麻煩,可被用於建立具備複雜3D結構的網站頁面,甚至能夠用來設計 3D 網頁遊戲等等。
因爲 WebGL 是一種偏底層的技術,爲了下降開發難度和節省開發成本,不建議直接基於 WebGL進行開發。目前業內大都採用基於 WebGL 實現的 3D 引擎進行開發。Web 3D 引擎比較多,不少是面向不一樣行業和不一樣的應用場景的,下面咱們介紹幾個常見的且有表明性的 3D 引擎,並選擇一個適合來用來構建 WMS 3D 可視化倉庫管理系統。
1. Three.js
Three.js 是純渲染引擎,並且代碼易讀,容易做爲學習WebGL、3D圖形、3D數學應用的平臺,也能夠作中小型的重表現的Web項目。但若是要作中大型項目,尤爲是多種媒體混雜的或者是遊戲項目VR體驗項目,Three.js必需要配合更多擴展庫才能完成。
 
2. Babylon.js
Babylon.js 是微軟發佈的開源的 Web 3D 引擎。最初設計做爲一個Silverlight遊戲引擎,Babylon.js 的維護傾向於基於 Web 的遊戲開發與碰撞檢測和抗鋸齒等特性。在其官網上能夠看到不少例子: http://www.babylonjs.com/
 
HT for Web 是基於 HTML5標準的企業應用圖形界面一站式解決方案,其包含通用組件、拓撲組件和3D渲染引擎等豐富的圖形界面開發類庫。雖然 HT for Web 是商業軟件但其提供的一站式解決方案能夠極大縮短產品開發週期、減小研發成本、補齊咱們在 Web 圖形界面可視化技術上的短板。
 
咱們選擇的 3D 引擎是 HT for Web,雖然須要必定的受權費,但整體上來看是有價值的,咱們在很短的時間內就能夠開發出一套定製化的 WMS 3D 可視化倉庫管理系統。因爲是商用軟件,對方提供了很好的技術支持,官網有完善的文檔手冊,開發包的使用也很容易上手。
 
2、功能實現
WMS 數據可視化主要包括如下幾部分功能:
1. 狀態管理
用於顯示WMS通信狀態、堆垛機狀態,包括是否故障、通信狀態、故障信息。
 
顯示狀態面板只須要引用 HT 的圖紙文件:
1 const g2d = new ht.graph.GraphView() 2 g2d.setPannable(false) 3 g2d.setRectSelectable(false) 4 g2d.handleScroll = function () {} 5 g2d.setScrollBarVisible(false) 6 
7 ht.Default.xhrLoad('displays/狀態面板.json', function (json) { 8  g2d.dm().deserialize(json) 9 })
2. 任務管理
顯示當前出庫入庫任務列表
 
 
出庫入庫任務列表也能夠用 HT 圖紙進行顯示:
1 const g2d = new ht.graph.GraphView() 2 
3 g2d.setPannable(false) 4 g2d.setRectSelectable(false) 5 g2d.handleScroll = function () {} 6 g2d.setScrollBarVisible(false) 7 ht.Default.xhrLoad('displays/任務列表.json', function (json) { 8  g2d.dm().deserialize(json) 9 })
3. 故障管理
顯示當前的故障信息列表。
故障信息頁面爲 HT 圖紙,代碼實現以下:
1 const g2d = new ht.graph.GraphView() 2 g2d.setPannable(false) 3 g2d.setRectSelectable(false) 4 g2d.handleScroll = function () {} 5 g2d.setScrollBarVisible(false) 6 
7 ht.Default.xhrLoad('displays/故障信息.json', function (json) { 8  g2d.dm().deserialize(json); 9 });

 

 
4. 單機管理
提早信息後WMS實現貨物入庫或出庫。
入庫邏輯和出庫邏輯須要分別實現,整個過程涉及貨物在輸送出上的移動動畫、堆垛機的移動動畫、堆垛機的取貨放貨動畫。
貨物入庫核心代碼:
 1 // 貨物入庫
 2 function goodsIn(code) {  3     var good = dataModel.getDataByTag(code)  4     if (!good) {  5         console.warn('貨物編號不存在:', code)  6         return
 7  }  8     ////////// 入庫口移動至輸入機 //////////////
 9 
10     var row = good.a('row') 11     var col = good.a('col') 12     var floor = good.a('floor') 13 
14     if (col <= colSize / 2) { // 左側
15         let goodP3 = dataModel.getDataByTag('入口1').p3() 16         goodP3[1] = floorBaseElevation 17  good.p3(goodP3) 18     } else { // 右側
19         let goodP3 = dataModel.getDataByTag('入口2').p3() 20         goodP3[1] = floorBaseElevation 21  good.p3(goodP3) 22  } 23     good.s('3d.visible', true) 24     good.setHost(null) 25 
26     if (col <= colSize / 2) { // 左側
27         let refer = dataModel.getDataByTag('LeftFront') 28         moveZTo(good, refer.getY(), null, () => { 29             moveXTo(good, refer.getX(), null, () => { // 左移
30                 // 後移至貨架水平位置
31                 let targetY = null
32                 if (Math.floor(row % 2) === 0) { // 偶數列
33                     targetY = good.a('p3')[2] + 300
34                 } else { 35                     targetY = good.a('p3')[2] 36  } 37                 moveZTo(good, targetY, null, () => { 38                     // 右移至貨架邊緣
39                     moveXTo(good, dataModel.getDataByTag('升降機L' + row + ':底座').getX(), null, () => { 40                         // 離開輸送機移動至貨架
41  goodToShelve(good) 42  }) 43  }) 44  }) 45  }) 46 
47     } else { // 右側
48         let refer = dataModel.getDataByTag('RightFront') 49         moveZTo(good, refer.getY(), null, () => { 50             moveXTo(good, refer.getX(), null, () => { // 右移
51                 // 後移至貨架水平位置
52                 let targetY = null
53                 if (Math.floor(row % 2) === 0) { // 偶數列
54                     targetY = good.a('p3')[2] + 300
55                 } else { 56                     targetY = good.a('p3')[2] 57  } 58                 moveZTo(good, targetY, null, () => { 59                     // 左移至貨架邊緣
60                     moveXTo(good, dataModel.getDataByTag('升降機R' + row + ':底座').getX(), null, () => { 61                         // 離開輸送機移動至貨架
62  goodToShelve(good) 63  }) 64  }) 65  }) 66  }) 67  } 68 }

貨物出庫核心代碼:html

 1 // 貨物出庫
 2 function goodsOut(code) {  3     var good = dataModel.getDataByTag(code)  4     if (!good) {  5         console.warn('貨物編號不存在:', code)  6         return
 7  }  8 
 9     var row = good.a('row') 10     var col = good.a('col') 11     var floor = good.a('floor') 12 
13     let elevatorRow = parseInt((row + 1) / 2) 14     let isLeft = col <= (colSize / 2) 15     let elevator = isLeft ? dataModel.getDataByTag("升降機L" + elevatorRow) : dataModel.getDataByTag("升降機R" + elevatorRow) 16 
17     let elevatorX = elevator.getX() 18     let x = (good.getX() - elevatorX) 19     // 水平移動
20  ht.Default.startAnim({ 21         duration: Math.abs(col - elevator.a('col')) * animationUnit, // 動畫週期毫秒數,默認採用`ht.Default.animDuration`
22         action: function (v, t) { 23             elevator.setX(elevatorX + x * v) 24  }, 25         finishFunc: function () { 26             elevator.a('col', col) 27 
28             // 底座垂直移動
29             let base = dataModel.getDataByTag(elevator.getTag() + ":底座") 30             if (floor > 1) { 31                 baseUp(base, good, floor, true, false) 32             } else { 33                 // 取貨,出貨
34                 startHandAnimation(base, good, floor, true, false) 35  } 36  } 37  }); 38 }

堆垛機上升動畫實現:html5

 1 function elevatorIn(elevator, good) {  2     console.log('elevatorIn')  3     var row = good.a('row')  4     var col = good.a('col')  5     var floor = good.a('floor')  6 
 7     let elevatorX = elevator.getX()  8     let goodP3 = good.a('p3')  9     let x = (goodP3[0] - elevatorX) 10     // 水平移動
11  ht.Default.startAnim({ 12         duration: Math.abs(col - elevator.a('col')) * animationUnit, // 動畫週期毫秒數,默認採用`ht.Default.animDuration`
13         action: function (v, t) { 14             elevator.setX(elevatorX + x * v) 15  }, 16         finishFunc: function () { 17             elevator.a('col', col) 18 
19             // 底座垂直移動
20             let base = dataModel.getDataByTag(elevator.getTag() + ":底座") 21             if (floor > 1) { 22                 baseUp(base, good, floor, false, true) 23             } else { 24                 // 送貨
25                 startHandAnimation(base, good, floor, false, true) 26  } 27  } 28  }); 29 }

堆垛機動畫:json

 1 // 堆垛機出貨
 2 function elevatorOut(elevator, good, goodIn) {  3     console.log('elevatorOut')  4     let elevatorX = elevator.getX()  5     let isLeft = elevator.getTag().startsWith('升降機L')  6     let start = isLeft ? LeftElevatorX : RightElevatorX  7     let xOffset = (start - elevatorX)  8 
 9     let t = isLeft ? Math.abs(elevator.a('col')) : Math.abs(colSize - elevator.a('col') + 1) 10     // 水平移動
11  ht.Default.startAnim({ 12         duration: t * animationUnit, // 動畫週期毫秒數,默認採用`ht.Default.animDuration`
13         action: function (v, t) { 14             elevator.setX(elevatorX + xOffset * v) 15  }, 16         finishFunc: function () { 17             elevator.a('col', isLeft ? 0 : (colSize + 1)) 18             if (!goodIn) { 19                 startHandAnimation(dataModel.getDataByTag(elevator.getTag() + ":底座"), good, 1, false, goodIn) 20  } 21  } 22  }) 23 } 24 
25 // 堆垛機取貨
26 function elevatorIn(elevator, good) { 27     console.log('elevatorIn') 28     var row = good.a('row') 29     var col = good.a('col') 30     var floor = good.a('floor') 31 
32     let elevatorX = elevator.getX() 33     let goodP3 = good.a('p3') 34     let x = (goodP3[0] - elevatorX) 35     // 水平移動
36  ht.Default.startAnim({ 37         duration: Math.abs(col - elevator.a('col')) * animationUnit, // 動畫週期毫秒數,默認採用`ht.Default.animDuration`
38         action: function (v, t) { 39             elevator.setX(elevatorX + x * v) 40  }, 41         finishFunc: function () { 42             elevator.a('col', col) 43 
44             // 底座垂直移動
45             let base = dataModel.getDataByTag(elevator.getTag() + ":底座") 46             if (floor > 1) { 47                 baseUp(base, good, floor, false, true) 48             } else { 49                 // 送貨
50                 startHandAnimation(base, good, floor, false, true) 51  } 52  } 53  }); 54 }

堆垛機底座和抓手動畫:瀏覽器

 1 // 抓手動畫
 2 function startHandAnimation(baseNode, goodNode, floor, pick, goodIn) {  3     console.log('startHandAnimation:', floor, pick, goodIn)  4     let elevator = baseNode.getParent()  5     // 抓手移動的方向
 6     let isBack = goodNode.a('row') === elevator.a('row') * 2
 7     baseNode.eachChild(hand => {  8         var z = hand.getY()  9         // 抓手動畫
10  ht.Default.startAnim({ 11             duration: 4000, // 動畫週期毫秒數,默認採用`ht.Default.animDuration`
12             easing: function (t) { 13                 if (t < 0.5) { 14                     return t * 2
15                 } else { 16                     return (1 - t) * 2
17  } 18  }, 19             action: function (v, t) { 20                 if (t >= 0.5) { 21                     if (pick) { 22  goodNode.setHost(hand) 23                     } else { 24                         goodNode.setHost(null) 25  } 26  } 27                 if (goodIn) { 28                     if (pick) { // 取貨
29                         hand.setY(z + 150 * v) 30                     } else { // 放貨
31                         if (isBack) { 32                             hand.setY(z - 150 * v) 33                         } else { 34                             hand.setY(z + 150 * v) 35  } 36  } 37                 } else { 38                     if (pick) { // 取貨
39                         if (isBack) { 40                             hand.setY(z - 150 * v) 41                         } else { 42                             hand.setY(z + 150 * v) 43  } 44                     } else { // 放貨
45                         hand.setY(z - 150 * v) 46  } 47  } 48  }, 49             finishFunc: function () { 50                 if (baseNode.a('floor') > 1) { 51  baseDown(baseNode, goodNode, floor, pick, goodIn) 52                 } else { 53                     if (elevator.a('col') === 0 || elevator.a('col') === colSize + 1) { 54                         if (goodIn) { // 入庫: 已完成取貨動做, 升降機進入貨架
55  elevatorIn(elevator, goodNode) 56                         } else { // 出庫:已將貨物放置到輸送機
57                             // 移動到小車位置
58  startGoodOutAnimation(goodNode) 59  } 60                     } else { // 將升降機移到貨架外
61  elevatorOut(elevator, goodNode, goodIn) 62  } 63  } 64  } 65  }); 66  }) 67 } 68 
69 // 底座上升
70 function baseUp(baseNode, goodNode, floor, pick, goodIn) { 71     console.log('底座上升:', baseNode.getTag()) 72 
73     var baseElevation = baseNode.getElevation() 74 
75     let goodP3 = goodNode.a('p3') 76     var elevationOffset = (goodP3[1] - baseElevation) 77     // 上升
78  ht.Default.startAnim({ 79         duration: (floor - 1) * animationUnit, 80         action: function (v, t) { 81             baseNode.setElevation(baseElevation + elevationOffset * v) 82  }, 83         finishFunc: function () { 84             baseNode.a('floor', floor) 85  startHandAnimation(baseNode, goodNode, floor, pick, goodIn) 86  } 87  }); 88 }

 

 
5. 主3D場景
以 3D 的方式顯示倉庫立體場景,包括貨架、貨物、堆垛機、穿梭車、輸送機等。支持經常使用視角切換,提供側視、俯視、正視、斜視。當選中某個貨物時。
 
視角切換圖標是基於 HT for Web 交互功能定製的圖標:
 1 const g2d = new ht.graph.GraphView()  2 g2d.setPannable(false)  3 g2d.setRectSelectable(false)  4 g2d.handleScroll = function () {}  5 
 6 ht.Default.xhrLoad('displays/視角切換.json', function (json) {  7  g2d.dm().deserialize(json);  8 });  9 
10 g2d.lookAtFront = function () { 11     eventbus.trigger('g3d.lookAtFront') 12 } 13 g2d.lookAtLean = function () { 14     eventbus.trigger('g3d.lookAtLean') 15 } 16 g2d.lookAtLeft = function () { 17     eventbus.trigger('g3d.lookAtLeft') 18 } 19 g2d.lookAtTop = function () { 20     eventbus.trigger('g3d.lookAtTop') 21 }
  可顯示貨物的詳細信息(托盤號、貨位、批號、物料代碼、物料名稱、單位、數量、備註、堆垛機號、質量狀態):
藉助 HT for Web 的數據驅動模型以及動畫API,能夠很容易地控制貨物出庫出庫動做,並與後臺數據綁定。能夠模擬堆垛機入庫取貨,貨物在輸送機上移動並出庫,貨物通過檢測門入庫等動畫效果。
 
 

在線演示地址:http://www.hightopo.com/demo/wms/index.html學習

相關文章
相關標籤/搜索