基于 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学习

相关文章
相关标签/搜索