前言
随着工业物联网和互联网技术的普及和发展, 人工填料的方式已经逐渐被机械设备取代. 工业厂商减小误操作, 提升设备安全以及追求高效率等制造特点对设备的要求愈加高标准, 严要求. 同时机械生产以后还需遵从整个项目流程的规范管理, 如何实行管理与交接也是一大严峻的挑战. 因此, 整个生产流程中还应该制定一套关于管理流程的可视化界面.
在工业过程控制中, 按被控对象的实时数据采集的信息与给定值比较产生的误差的比例, 积分和微分进行控制的控制系统, 简称 PID 控制系统. PID 控制生产环境具有适应性强, 鲁棒性强, 使用方便等特点. 进料系统则涉及到超高压技术, 在流水线系统中广泛应用, 能够实现设备半自动化或自动化送料作业, 解决传统进料方式计量不准, 工作环境污染以及工人劳动强度高等问题, 从而实现高效的流水线加工. 结合 PID 和自动化部署, 可以为电力, 机械, 冶金, 化工, 食品, 纺织等工业或者民用行业供需. 本篇文章通过搭建危险废物进料系统的 2D 场景以及数据界面展示, 帮助我们了解如何使用 HT 实现一个可视化的 PID 控制进料系统.
项目地址预览: 基于 html5 的 PID - 进料系统可视化界面 http://www.hightopo.com/demo/PID-feed-system/
效果预览
整体协作场景
抓斗操作场景
进料场景
代码构建
搭建场景
该文主要实现的是 2D 场景, 我们需要用到拓扑组件的相关 API 搭建基础场景:
- dataModel = new ht.DataModel(); // 数据容器, 用来存取数据节点 Node
- graphView = new ht.graph.GraphView(dataModel); // 拓扑组件
- graphView.addToDOM(); // 将组件添加到 body 中
上述代码添加组件到 body 中所用的是 addToDom 方法, HT 组件一般会嵌入 BorderPane,SplitView 和 TabView 等容器中使用, 而最外层的 HT 组件则需要用户手工将 getView() 返回的底层 div 元素添加到页面的 DOM 元素中, 这里需要注意的是, 当父容器大小变化时, 如果父容器是 BorderPane 和 SplitView 等这些 HT 预定义的容器组件, 则 HT 的容器会自动递归调用孩子组件 invalidate 函数通知更新. 但如果父容器是原生的 HTML 元素, 则 HT 组件无法获知需要更新, 因此最外层的 HT 组件一般需要监听 Windows 的窗口大小变化事件, 调用最外层组件 invalidate 函数进行更新.
为了最外层组件加载填充满窗口的方便性, HT 的所有组件都有 addToDOM 函数, 其实现逻辑如下, 其中 iv 是 invalidate 的简写:
- addToDom = function(){
- var self = this,
- view = self.getView(), // 获取组件的底层 div
- style = view.style;
- document.body.appendChild(view); // 将组件底层 div 添加到 body 中
- style.left = '0'; // 默认所有组件的 position 都设置为 absolute 绝对定位
- style.right = '0';
- style.top = '0';
- style.bottom = '0';
- Windows.addEventListener('resize',function(){ self.iv(); },false); // 窗口改变大小, 调用刷新函数
- }
将视图默认方法重置:
- graphView.setPannable(false); // 禁用通过鼠标拖拽进行平移操作
- graphView.setRectSelectable(false); // 禁用拓扑上进行框选操作
- graphView.setMovableFunc(()=>{false}); // 禁用移动过滤器函数
在 2D 编辑器上创建 2D 图形会生成 JSON 文件, 引入生成场景需要进行反序列化:
- ht.Default.xhrLoad('displays/industry/PID - 进料系统. json',function(text){
- var JSON = ht.Default.parse(text); // 解析为 JSON 对象
- dataModel.deserialize(JSON); // 反序列化为场景
- })
在 HT 中, Data 类型对象构造时内部会自动被赋予一个 id 属性, 可通过 data.getId() 和 data.setId( id ) 获取和设置, Data 对象添加到 DataModel 之后不允许修改 id 值, 可通过 dataModel.getDataById (id ) 快速查找 Data 对象. 但是一般建议 id 属性由 HT 自动分配, 用户业务意义的唯一标示可存在 tag 属性上, 通过 Data#setTag( tag ) 函数允许任意动态改变 tag 值, 通过 DataModel#getDataByTag(tag) 可查找到对应的 Data 对象, 并支持通过 DataModel#removeDataByTag( tag ) 删除 Data 对象. 我们这边通过在 JSON 中设置 Data 对象的 tag 属性, 在代码中通过 dataModel.getDataByTag( tag ) 函数来获取该 Data 对象:
- {
- "c": "ht.Node",
- "i": 407,
- "p": {
- "displayName": "抓手的结",
- "parent": {
- "__i": 403
- },
- "tag": "gripKnot",
- "image": "symbols/symbol factory / 垃圾处理 / 抓手的结. json",
- "position": {
- "x": -569.62125,
- "y": -117.05025
- },
- "width": 50,
- "height": 25
- },
- "s": {
- "select.width": 0
- }
- },
- var gripRightPaw = dataModel.getDataByTag('gripRightPaw');
- var girpLeftPaw = dataModel.getDataByTag('grapLeftPaw');
- var gripKnot = dataModel.getDataByTag('gripKnot');
展开动画
HT 对动画封装了 ht.Default.startAnim 函数, 通过设置 duration 获取动画时长, action 函数里为执行的动画属性, 以及 finishFunc 动画执行后的回调函数, 该案例共置 8 个动画, 包含自驱动以及异步动画. 下面举第八个动画 (循环水流动) 为例来理解 ht 内置动画效果:
- // 循环水流动
- function animation() {
- var lineJson = {};
- var name = '';
- var speed = 20,
- lastTime = Date.now();
- // 循环获取水流 tag, 并设置初始化 shape.Dash.offset 为 0
- for (var i = 1; i <= 9; i++ ) {
- if (i != 8) {
- name = 'line'+i;
- lineJson[name] = 0;
- }
- }
- ht.Default.startAnim({
- duration: 5000,
- action: function () {
- var time = Date.now(),
- deltaTime = (time - lastTime) / 1000;
- for (var tags in lineJson) {
- if (tags.split('e')[1] % 2) {
- lineJson[tags] += deltaTime * speed;
- } else {
- lineJson[tags] -= deltaTime * speed;
- }
- var lines = dataModel.getDataByTag(tags);
- lines.setStyle('shape.dash.offset',lineJson[tags]);
- }
- lastTime = time
- },
- finishFunc: function () {
- animation();
- //TODO... 也可以在这里异步调用下一个动画
- }
- })
- }
该例首先根据已创建的循环水流 (已绑定 tag 标签) 通过 for 循环以及 dataModel. getDataByTag 动态获取 Data 节点, 通过标签名携带的数字判断水流方向, 最终使用 Data.setStyle(可以简写为 Data.s ) 设置虚线部分的偏移距离.
上面的循环水流为例, 如果 lineJson[tags] += value (定值) , 当用户放大视图时图元数量减少, 会多调用几次 anim 中的 action 函数, 流动速度增快, 缩小同理. 因此采用 value = speed * deltaTime 的解决方式, 解决视图在不同缩放 zoom 的情况下播放速度不一致的问题, 具体原理如下:
- //global
- var lastTime = Date.now();
- var distance = 0; // 距离
- var speed = 20; // 速度
- //action
- ht.Default.startAnim({
- duration:5000,
- action:function(){
- var time = Date.now();
- var deltaTime = (time - lastTime) / 1000;
- distance += speed * deltaTime;
- lastTime = time;
- },
- finishFunc:function(){//TODO}
- })
ht 实现动画不仅可以使用 startAnim 来驱动, 也可以采用按调度 addScheduleTask 进行实现, 代码如下:
- dataModel.addScheleTask({
- interval, // 调度间隔
- beforeAction(){}, // 调度开始之前的动作
- action(){}, // 调度任务
- afterAction(){} // 调度结束之后的动作
- })
也可以使用 callLater 进行实现, ht 内置函数封装了非常多关于动画有趣且实操性强的 API , 有兴趣可以进入官网 ( https://www.hightopo.com)进行了解和学习, 也可以线上申请 framework 的试用包. 如果想要了解更多 HT 封装的动画进行操作, 可以参考 https://www.cnblogs.com/xhload3d/p/9222549.html 等其他文章.
可操作
当然, HT 也汲取了订阅 - 发布模式的天然优势, 通过驱动数据更改视图, 更加直观地感受到数据与视图的绑定过程. 以下提供 2 种 HT 提供的可操作界面, 第一种是通过创建面板组件, HT 内部提供了包含 formPane ,borderPane,TablePane 等一系列通用面板组件, 此处我们以 formPane 为例, 首先在 index.HTML 主页面中引入 ht-form.JS , 该文件封装了 formPane 面板的 API , 相关伪代码如下:
- var fp = new ht.widget.FormPane(); // 创建面板对象
- fp.setWidth(200);
- fp.setHeight(100);
- fp.setRowHeight(30); // 面板行高
- fp.setPadding(16);
- fp.getView().className = 'main'; // 节点设置类名后可以直接在 style 中设置属性, 说白了 fp.getView() 就是一个普通的 DOM 节点
- fp.addRow([{ // 通过 addRow 方法添加文本以及进度条等内容
- id:'text',
- element:'Current Speed === 20',
- align:'center'
- }],[0.1]);
- fp.addRow([{
- id:'speed',
- slider:{ // 进度条
- min:0,
- max:100,
- value:20, // 当前进度值
- step:1,
onValueChanged(){ value 改变时触发函数
- var speed = fp.v('speed');
- fp.v('text','Current Speed ===' + speed);
- }
- }
- }],[0.1]);
- document.body.appendChild(fp.getView());
此时, 我们只要把之前定义的 speed 指向 fp.v('speed') , 就可以简单地实现数据视图绑定:
- function animation(fp){
- var lineJson = {};
- var name = '';
- var lastTime = Date.now();
- var speed;
- for (var i = 1; i <= 9; i++ ) {
- if (i != 8) {
- name = 'line'+i;
- lineJson[name] = 0;
- }
- }
- ht.Default.startAnim({
- duration: 5000,
- action: function () {
- speed = fp.v('speed');
- var time = Date.now(),
- deltaTime = (time - lastTime) / 1000;
- for (var tags in lineJson) {
- if (tags.split('e')[1] % 2) {
- lineJson[tags] += deltaTime * speed;
- } else {
- lineJson[tags] -= deltaTime * speed;
- }
- var lines = dataModel.getDataByTag(tags);
- lines.setStyle('shape.dash.offset',lineJson[tags]);
- }
- lastTime = time;
- },
- finishFunc: function () {
- animation(fp);
- }
- })
- }
另一种是通过 HT 的矢量图形库, 矢量图形采用点, 线或多边形的图形描述方式, 解决了 PNG ,jpg 等格式图片在缩放过程中出现失真现象. 创建矢量图形可以通过常规编辑器如 webstorm,webstorm 通过代码编写, 也可以通过 HT-2D 编辑器直接创建图形, 基本上不需要操作代码就可以简单地创建出图形, 有学过 3dmax 或者 CAD 制图的同学对此应该都不陌生. 在编辑器的不断完善下, 内部已经有许多优秀的图标和组件案例, 这边就可以直接引用一些小案例, 首先需要创建一张图纸, 然后直接拉取一个自制图标, 类似 legend 的效果都是绘线画出来的, 更改文字部分就可以直接看到效果了.
关键的还是功能性组件, 图标展示显示界面, 功能性组件支持事件的触发, 首先在控件里面拉取 slider 图标, 然后到组件栏拉取 slider 组件, 设置控件的最大值, 最小值和默认值等一系列参数.
可以得知我们即将改变的值有两个, 一个是 slider , 一个是文本的值, 默认 20, 我们给这两个 Data 对象绑定唯一标签, 分别为 sliderValue 以及 textValue, 先通过进度条的当前值改变文本的值:
- var sliderValue = dataModel.getDataByTag('sliderValue');
- var textValue = dataModel.getDataByTag('textValue');
- sliderValue.a('ht.onChange',function(){ //value 改变触发事件
- textValue.a('textValue',sliderValue.a('ht.value'));
- })
然后 animation 拿到进度条的当前值, 指向 speed:
- function animation(data) {
- var lineJson = {};
- var name = '';
- var lastTime = Date.now();
- var speed;
- for (var i = 1; i <= 9; i++ ) {
- if (i != 8) {
- name = 'line'+i;
- lineJson[name] = 0;
- }
- }
- ht.Default.startAnim({
- duration: 5000,
- action: function () {
- speed = data.a('ht.value');
- var time = Date.now(),
- deltaTime = (time - lastTime) / 1000;
- for (var tags in lineJson) {
- if (tags.split('e')[1] % 2) {
- lineJson[tags] += deltaTime * speed;
- } else {
- lineJson[tags] -= deltaTime * speed;
- }
- var lines = dataModel.getDataByTag(tags);
- lines.setStyle('shape.dash.offset',lineJson[tags]);
- }
- lastTime = time;
- },
- finishFunc: function () {
- animation(data);
- }
- })
- }
当然也可以自定义多个 slider 分别控制不同的动画, 具体如何实现还是全凭需求而定.
不局限于 2D 可视化场景, 与 3D 相关生产环境的可视化场景模拟也有许多案例, 如下:
3D 水泥工厂工艺流程: http://www.hightopo.com/demo/CementFactory/
3D 高炉炼铁工业流程: http://www.hightopo.com/demo/large-screen-puddling/
来源: https://www.cnblogs.com/htdaydayup/p/11525633.html