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

基于 WEB 的 WMS 3D 可视化管理系统的更多相关文章

  1. 《基于 Web Service 的学分制教务管理系统的研究与实现》论文笔记(十一)

    标题:基于 Web Service 的学分制教务管理系统的研究与实现 一.基本内容 时间:2014 来源:苏州大学 关键词:: 教务管理系统 学分制 Web Service 二.研究内容 1.教务管理 ...

  2. 基于 HTML5 + WebGL 的 3D 可视化挖掘机

    前言 在工业互联网以及物联网的影响下,人们对于机械的管理,机械的可视化,机械的操作可视化提出了更高的要求.如何在一个系统中完整的显示机械的运行情况,机械的运行轨迹,或者机械的机械动作显得尤为的重要,因 ...

  3. 基于 HTML5 + WebGL 实现 3D 可视化地铁系统

    前言 工业互联网,物联网,可视化等名词在我们现在信息化的大背景下已经是耳熟能详,日常生活的交通,出行,吃穿等可能都可以用信息化的方式来为我们表达,在传统的可视化监控领域,一般都是基于 Web SCAD ...

  4. 基于 H5 + WebGL 实现 3D 可视化地铁系统

    前言 工业互联网,物联网,可视化等名词在我们现在信息化的大背景下已经是耳熟能详,日常生活的交通,出行,吃穿等可能都可以用信息化的方式来为我们表达,在传统的可视化监控领域,一般都是基于 Web SCAD ...

  5. 基于WebGL架构的3D可视化平台ThingJS-搭建设备管理系统

    国内高层建筑不断兴建,它的特点是高度高.层数多.体量大.面积可达几万平方米到几十万平方米.这些建筑都是一个个庞然大物,高高的耸立在地面上,这是它的外观,而随之带来的内部的建筑设备也是大量的.为了提高设 ...

  6. 基于 HTML5 WebGL 的 3D 仓储管理系统

    仓储管理系统(WMS)是一个实时的计算机软件系统,它能够按照运作的业务规则和运算法则,对信息.资源.行为.存货和分销运作进行更完美地管理,使其最大化满足有效产出和精确性的要求.从财务软件.进销存软件C ...

  7. 基于WebGL架构的3D可视化平台—设备管理

    ---恢复内容开始--- 国内高层建筑不断兴建,它的特点是高度高.层数多.体量大.面积可达几万平方米到几十万平方米.这些建筑都是一个个庞然大物,高高的耸立在地面上,这是它的外观,而随之带来的内部的建筑 ...

  8. 基于Web的文件上传管理系统

    一般10M以下的文件上传通过设置Web.Config,再用VS自带的FileUpload控件就可以了,但是如果要上传100M甚至1G的文件就不能这样上传了.我这里分享一下我自己开发的一套大文件上传控件 ...

  9. 基于WebGL架构的3D可视化平台—新风系统演示

    新风系统是根据在密闭的室内一侧用专用设备向室内送新风,再从另一侧由专用设备向室外排出,在室内会形成“新风流动场”,从而满足室内新风换气的需要.实施方案是:采用高风压.大流量风机.依靠机械强力由一侧向室 ...

  10. 基于WebGL架构的3D可视化平台—实现小车行走路线演示

    小车行走路线演示New VS Old 刚接触ThingJS的时候,写的一个小车开进小区的演示,今天又看了教程中有movePath这个方法就重新写了一遍,其中也遇到了一些问题,尤其突出的问题就是小车过弯 ...

随机推荐

  1. 修改python别名

    修改Python别名 发现课程提供的启动python程序的命令均为python3,而我们的环境中只能用python来启动 Python 解释器,可以通过修改python的别名来实现不同名称调用同一个程 ...

  2. LVGL 定时器

    LVGL 8.0 以后好像取消了自定义任务模块,想要使用多线程只能使用系统的线程. 一.定时器结构体 typedef struct _lv_timer_t { uint32_t period; // ...

  3. Educational Codeforces Round 160 (Rated for Div. 2)

    A 直接模拟,注意细节 #include<bits/stdc++.h> #define ll long long using namespace std; ll p[15] = {1}; ...

  4. Mybatis逆向工程的2种方法,一键高效快速生成Pojo、Mapper、XML,摆脱大量重复开发

    一.写在开头 最近一直在更新<Java成长计划>这个专栏,主要是Java全流程学习的一个记录,目前已经更新到Java并发多线程部分,后续会继续更新:而今天准备开设一个全新的专栏 <E ...

  5. 深入了解 Java 字节码

    1.1 什么是字节码? Java 在刚刚诞生之时曾经提出过一个非常著名的口号: "一次编写,到处运行(write once,run anywhere)",这句话充分表达了软件开发人 ...

  6. 使用Elasticsearch在Rails中进行全文本搜索

    使用Elasticsearch在Rails中进行全文本搜索 参考: https://blog.csdn.net/cunjie3951/article/details/106921108

  7. 联想G470安装黑苹果

    macos10136 黑苹果usb无线网卡 1.系统下载: 下面是我自制的带clover 4596版本的u盘镜像: 链接: https://pan.baidu.com/s/1wRdVddwkei7bf ...

  8. Oracle中ALTER TABLE的五种用法(二)

    首发微信公众号:SQL数据库运维 原文链接:https://mp.weixin.qq.com/s?__biz=MzI1NTQyNzg3MQ==&mid=2247485212&idx=1 ...

  9. spring-boot集成Quartz-job存储方式二RAM,改从json配置文件读取job配置

    前面第二种RAM方法已经可以满足单机使用需求了,但是本地调试和服务器应用会有冲突,因此将定时任务保存到本地json配置文件中,这样更灵活. 1.ApplicationInit类 package org ...

  10. vue3:modal组件开发

    项目环境 @vue/cli 4.5.8 最终效果 需求分析 显示/隐藏 点击遮罩层能否关闭 宽度和zIndex自定义 标题栏 -显示标题和关闭按钮 主体 底部 -内置取消和确定功能 前置知识 tele ...