zoukankan      html  css  js  c++  java
  • 基于 WEB 的 WMS 3D 可视化管理系统

    基于 WEB 的 WMS 3D 可视化管理系统
    前言
    首先介绍一下什么是WMS。WMS是仓库管理系统(Warehouse Management System) 的缩写,仓库管理系统是通过入库业务、出库业务、仓库调拨、库存调拨和虚仓管理等功能,对批次管理、物料对应、库存盘点、质检管理、虚仓管理和即时库存管理等功能综合运用的管理系统,有效控制并跟踪仓库业务的物流和成本管理全过程,实现或完善的企业仓储信息管理。该系统可以独立执行库存操作,也可与其他系统的单据和凭证等结合使用,可为企业提供更为完整企业物流管理流程和财务管理信息。
    目前主流的 WMS 仓库管理系统大都采用了 B/S 模式,但数据可视化技术上仍采用的是传统图表显示方式。本文从数据可视化的角度介绍了一种基于 WEB 的 3D 可视化实现方案,底层基于标准的 HTML5 WebGL 技术,以 3D 的方式显示仓库立体场景,包括货架、货物、堆垛机、穿梭车、输送机等。相对于传统图表显示方式,三维的仓库管理可视化显示方式,显得更加直观和立体化,无论是用户体验还是产品质量都得到了巨大提升。
     
    一、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 可视化仓库管理系统。由于是商用软件,对方提供了很好的技术支持,官网有完善的文档手册,开发包的使用也很容易上手。
     
    二、功能实现
    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 }

    货物出库核心代码:

     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 }

    堆垛机上升动画实现:

     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 }

    堆垛机动画:

     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

  • 相关阅读:
    有序向量
    无序向量
    设计模式入门
    策略模式
    面向对象相关知识点
    MySQL数据库知识培训
    数据库业务规范
    go最小路径
    go求质数
    CSS3
  • 原文地址:https://www.cnblogs.com/iotopo/p/wms3d.html
Copyright © 2011-2022 走看看