基于 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. 先行一步,7大技术创新和突破,阿里云把 Serverless 领域的这些难题都给解了

    ​简介: 函数计算 FC 首创 GPU 实例.业内首发实例级别可观测和调试.率先提供端云联调和多环境部署能力.GB 级别镜像启动时间优化至秒级.VPC 网络建连优化至200ms,Serverless ...

  2. IIncrementalGenerator 获取项目默认命名空间

    本文将告诉大家如何在分析器里面获取到项目的默认命名空间 在 Roslyn 分析器里面读取项目的默认命名空间,可以通过读取项目的属性配置实现.通过 IIncrementalGenerator 增量 So ...

  3. 2018-12-26-WPF-开启-ScrollViewer-的触摸滚动

    title author date CreateTime categories WPF 开启 ScrollViewer 的触摸滚动 lindexi 2018-12-26 14:24:26 +0800 ...

  4. keil 中未编译的代码灰色显示

    一.转载文章 转载:KEIL,#ifdef宏定义下失效代码差异性显示 注意keil的版本,太低的版本不具备灰色显示,据我所知在KEIL uVersion V5.31版本以上均可以. 二.使能灰色显示 ...

  5. 实验2 C语言分支与循环基础应用编程 王刚202383310053

    1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<time.h> 4 #define N 5 5 int ma ...

  6. 为什么我反对过度使用TypeScript?

    前言 在2024年, TypeScript肯定算不上什么新鲜的技术. 但是经过长时间的使用, 我认为可以使用, 但是要适度. 类型跟不上业务的变化 我们知道TypeScript的类型定义是业务的体现. ...

  7. ansible(3)--ansible的相关命令行工具

    目录 1 ansible命令详解 2 ansible-doc显示模块帮助信息 3 ansible-playbook 4 ansible-galaxy 5 ansible-console 1 ansib ...

  8. neo4j配置文件neo4j.conf详解

    一.dbms配置 dbms.default_database=neo4j 目录路径 dbms.directories.data=datadbms.directories.plugins=plugins ...

  9. C 语言编程 — 数据类型转换

    目录 文章目录 目录 前文列表 数据类型转换 隐式(自动)类型转换 常用的算术转换 显式(强制)类型转换 前文列表 <程序编译流程与 GCC 编译器> <C 语言编程 - 基本语法& ...

  10. 智能勘探 | AIRIOT智慧油田管理解决方案

      石油勘探和开采地处偏远地区,涉及面广且生产规模大.特殊的作业环境下,使得工作人员作业条件艰苦,仅靠人工值守难度很大,不可避免的遇到一系列硬核挑战: 1.设备维护难度较高: 2.采油厂分布地域广.分 ...