C#开发BIMFACE系列42 服务端API之图纸对比

BIMFACE二次开发系列目录     【已更新最新开发文章,点击查看详细】

在我的前一篇博客C#开发BIMFACE系列41 服务端API之模型对比中详细介绍了BIMFACE服务端接口模型对比的功能。 BIMFACE官方文档提供的三维模型对比接口同样也适用于二维CAD图纸对比。下图中是官方提供的对比示例程序。

其中新增的图元使用绿色标记、修改的图元使用黄色标记、删除的图元使用红色标记。

下面介绍BIMFACE图纸对比功能的原理与实现。

图纸对比可以对两个图纸文件进行差异性分析,确定两个图纸文件之间构件的几何和属性差异,包括增加的图元构件、删除的图元和修改的图元。

特别说明:图纸对比是在BIMFACE云端进行的,通常需要5~10分钟。当模型对比完成后,BIMFACE能通知对比结果。

前置条件
  • 您需要将修改前和修改后的图纸上传到云端并转换成功以后才能发起图纸对比;
  • 目前支持.dwg、.dwf单文件的图纸对比。
基本步骤
  1. 通过服务端API发起图纸对比(对比前后模型文件的fileId);
  2. 等待云端对比任务执行;
  3. 对比完成后,在网页端通过调用JavaScript API实现差异图纸的显示;
  4. 除了显示差异图纸,还需要调用服务端API获取对比结果(包括新增、删除、修改的图元列表)。
对比流程

  图纸文件经过云端转换后,生成了BIMFACE定义的数据包。因此,要对比两个图纸文件,实际上需要对比两个文件的数据包。如下图所示,文件B是文件A修改后的版本,对比完成之后,其结果包括两个部分:

  • 几何差异;
  • 变更构件及属性。

 

BIMFACE提供了服务端API,用于发起对比,获取对比状态、获取对比结果。请参考我的博客:

测试程序

发起图纸对比

调用服务器端的API获取对比结果

对比差异分为三类:新增、修改、删除。由于CAD图纸的展示类型包含 Model 与 Layer 两种形式,

差异结果中也是包含两种展示类型的对比信息,所以可能有重复的图元ID,需要手动过滤。

返回结果对应的实体类如下

 1 /// <summary>
 2     /// 模型对比差异类
 3     /// </summary>
 4     public class ModelCompareDiff
 5     {
 6         /// <summary>
 7         ///  对比差异构件所属类别ID。样例 : "-2001320"
 8         /// </summary>
 9         [JsonProperty("categoryId", NullValueHandling = NullValueHandling.Ignore)]
10         public string CategoryId { get; set; }
11 
12         /// <summary>
13         ///  对比差异构件所属类别名称。样例 : "framework"
14         /// </summary>
15         [JsonProperty("categoryName", NullValueHandling = NullValueHandling.Ignore)]
16         public string CategoryName { get; set; }
17 
18         /// <summary>
19         ///  对比构件差异类型:NEW、DELETE、CHANGE
20         /// </summary>
21         [JsonProperty("diffType", NullValueHandling = NullValueHandling.Ignore)]
22         public string DiffType { get; set; }
23 
24         /// <summary>
25         ///   对比差异构件ID。样例 : "296524"
26         /// </summary>
27         [JsonProperty("elementId", NullValueHandling = NullValueHandling.Ignore)]
28         public string ElementId { get; set; }
29 
30         /// <summary>
31         ///  对比差异构件名称
32         /// </summary>
33         [JsonProperty("elementName", NullValueHandling = NullValueHandling.Ignore)]
34         public string ElementName { get; set; }
35 
36         /// <summary>
37         ///  对比差异构件的族名称。样例 : "framework 1"
38         /// </summary>
39         [JsonProperty("family", NullValueHandling = NullValueHandling.Ignore)]
40         public string Family { get; set; }
41 
42         /// <summary>
43         ///  对比差异构件来源构件ID。样例 : "0213154515478"
44         /// </summary>
45         [JsonProperty("id", NullValueHandling = NullValueHandling.Ignore)]
46         public string Id { get; set; }
47 
48         /// <summary>
49         ///  对比差异构件变更文件ID,即(当前)变更后的文件ID。样例 : "1136893002033344"
50         /// </summary>
51         [JsonProperty("followingFileId", NullValueHandling = NullValueHandling.Ignore)]
52         public string FollowingFileId { get; set; }
53 
54         /// <summary>
55         /// 对比差异构件来源文件ID,即 (历史)变更前的文件ID。样例 : "0213154515478"
56         /// </summary>
57         [JsonProperty("previousFileId", NullValueHandling = NullValueHandling.Ignore)]
58         public string PreviousFileId { get; set; }
59 
60         /// <summary>
61         ///  对比差异构件所属专业。样例 : "civil"
62         /// </summary>
63         [JsonProperty("specialty", NullValueHandling = NullValueHandling.Ignore)]
64         public string Specialty { get; set; }
65

对比结果如下

 1 {
 2     "code": "success",
 3     "message": null,
 4     "data": {
 5         "data": [{
 6                 "diffType": "NEW",
 7                 "id": "1946876",
 8                 "layer": "D1",
 9                 "sheetId": "0",
10                 "sheetName": "Model",
11                 "type": "Model"
12             }, {
13                 "diffType": "NEW",
14                 "id": "1946877",
15                 "layer": "D1",
16                 "sheetId": "0",
17                 "sheetName": "Model",
18                 "type": "Model"
19             }, {
20                 "diffType": "NEW",
21                 "id": "1946878",
22                 "layer": "D1",
23                 "sheetId": "0",
24                 "sheetName": "Model",
25                 "type": "Model"
26             }, {
27                 "diffType": "CHANGE",
28                 "id": "40539",
29                 "layer": "0",
30                 "sheetId": "0",
31                 "sheetName": "Model",
32                 "type": "Model"
33             }, {
34                 "diffType": "CHANGE",
35                 "id": "40541",
36                 "layer": "0",
37                 "sheetId": "0",
38                 "sheetName": "Model",
39                 "type": "Model"
40             }, {
41                 "diffType": "CHANGE",
42                 "id": "40542",
43                 "layer": "0",
44                 "sheetId": "0",
45                 "sheetName": "Model",
46                 "type": "Model"
47             }, {
48                 "diffType": "CHANGE",
49                 "id": "22243",
50                 "layer": "AXIS",
51                 "sheetId": "0",
52                 "sheetName": "Model",
53                 "type": "Model"
54             }
55         ],
56         "page": 1,
57         "total": 7
58     }
59 }

网页中使用JS来实现图纸展示与差异对比效果,以及点击异动图元后自动定位到构件所在的视角。官网示例请参考 https://bimface.com/developer-jsdemo#988

官网的对比展示效果是将2张图纸进行叠加对比显示的,下面介绍另一种对比展示方式,2张图纸分别展示,左侧展示当前版本图纸,右侧展示历史版本图纸。

点击新增图元项,自动定位(绿色标记)

点击修改图元项,自动定位(黄色标记)

点击删除图元项,自动定位(红色标记)

 

布局如下

<body>
    <div class="nav"><a class="lg"><b>xxxx图纸.dwg</b></a></div>
    <div id="container">
        <div class='latest'>
            <!--<div class='title'>
                <span>当前轮次(<b>当前版本</b>)</span>
            </div>-->
        </div>

        <div class='prev'>
            <!--<div class='title'>
                <span>上一轮次(<b>历史版本</b>)</span>
            </div>-->
        </div>

        <div class="list">
            <h3>差异列表(<span>0</span>)</h3>
            <div class="detail">
                <ul class="bf-collapse add">
                    <span class="bf-icon"></span>
                    <span>新增图元(<b>0</b>)</span>
                    <div class="items"></div>
                </ul>

                <ul class="bf-collapse edit">
                    <span class="bf-icon"></span>
                    <span>修改图元(<b>0</b>)</span>
                    <div class="items"></div>
                </ul>

                <ul class="bf-collapse deletes">
                    <span class="bf-icon"></span>
                    <span>删除图元(<b>0</b>)</span>
                    <div class="items"></div>
                </ul>
            </div>
        </div>
    </div>
</body>

脚本实现图纸加载展示

 1  $(document).ready(function () {
 2             document.querySelector('.nav .lg b').innerHTML = sclc_desc + "【" + tzFileName1 + "】" + " 对比 【" + tzFileName2 + "】";
 3 
 4             var success = getViewTokens(compareId);
 5             if (!success) {
 6                 return;
 7             }
 8 
 9             prev = previousFileViewToken;
10             latest = followingFileViewToken;
11             compare = compareViewToken;
12 
13             var bimfaceLoaderConfig = new BimfaceSDKLoaderConfig();
14             bimfaceLoaderConfig.viewToken = latest;
15             BimfaceSDKLoader.load(bimfaceLoaderConfig, onSDKLoadSucceeded, onSDKLoadFailed);
16         });
17 
18         function onSDKLoadSucceeded(viewMetaData) {
19             if (viewMetaData.viewType == "drawingView") {
20                 // 加载修改后图纸
21                 var webAppConfig = new Glodon.Bimface.Application.WebApplicationDrawingConfig();
22                 webAppConfig.domElement = document.querySelector('.latest');
23                 latest = new Glodon.Bimface.Application.WebApplicationDrawing(webAppConfig);
24                 latest.load(viewMetaData.viewToken);
25 
26                 // 加载修改前图纸
27                 latest.getViewer().getViewMetaData(prev,
28                     function (viewMetaData) {
29                         var webAppConfig = new Glodon.Bimface.Application.WebApplicationDrawingConfig();
30                         webAppConfig.domElement = document.querySelector('.prev');
31                         prev = new Glodon.Bimface.Viewer.ViewerDrawing(webAppConfig);
32                         prev.load(viewMetaData.viewToken);
33                         prev.addEventListener('Loaded', correspond);
34                     });
35 
36                 $.ajax({
37                     url: "Handlers/GetBIMCompareResultFromDBHandler.ashx",
38                     data: { compareId: compareId, modelType: '2D' },
39                     dataType: "json",
40                     type: "GET",
41                     async: false, //同步。函数有返回值,必修设置为同步执行
42                     success: function (data) {
43                         if (data.code == true) {
44                             var add = '', edit = '', deletes = '';
45                             if (data.news) {
46                                 data.news.map((item, i) => {
47                                     add += `<li class='add-item'>${item.elementId}</li>`;
48                                 });
49                                 document.querySelector('.add .items').innerHTML = add;
50                                 document.querySelector('.add b').innerHTML = data.news.length;
51                             }
52                             if (data.changes) {
53                                 data.changes.map((item, i) => {
54                                     edit += `<li class='modify-item'>${item.elementId}</li>`;
55                                 });
56                                 document.querySelector('.edit .items').innerHTML = edit;
57                                 document.querySelector('.edit b').innerHTML = data.changes.length;
58                             }
59                             if (data.deletes) {
60                                 data.deletes.map((item, i) => {
61                                     deletes += `<li class='delete-item'>${item.elementId}</li>`;
62                                 });
63                                 document.querySelector('.deletes .items').innerHTML = deletes;
64                                 document.querySelector('.deletes b').innerHTML = data.deletes.length;
65                             }
66                             document.querySelector('.list h3 span').innerHTML =
67                                 (data.deletes ? data.deletes.length * 1 : 0) +
68                                 (data.changes ? data.changes.length * 1 : 0) +
69                                 (data.news ? data.news.length * 1 : 0);
70                         } else {
71                             $.messager.alert('提示', data.message, 'warning');
72                         }
73                     },
74                     error: function (e) {
75                         $.messager.alert('提示', e, 'error');
76                     }
77                 });
78             } else {
79                 $.messager.alert('提示', '对比的文件不是二维图纸。', 'warning');
80             }
81         };
82 
83         function onSDKLoadFailed(error) {
84             alert("图纸加载失败。");
85         };

脚本实现差异项点击事件

  1 // 同步新旧图纸的平移和旋转操作
  2         function correspond() {
  3             prevViewer = prev.getViewer();
  4             latestViewer = latest.getViewer();
  5             var state;
  6             bindEvent();
  7             (latestViewer.getViewer()).onViewChanges = function () {
  8                 if (latestViewer.getCurrentState() == state) {
  9                     return;
 10                 }
 11                 state = latestViewer.getCurrentState();
 12                 prev.setState(state);
 13             }
 14 
 15             setTimeout(function () {
 16                 prevViewer.onViewChanges = function () {
 17                     if (prev.getCurrentState() == state) {
 18                         return;
 19                     }
 20                     state = prev.getCurrentState();
 21                     latestViewer.getViewer().setState(state);
 22                 }
 23             },
 24                 10);
 25 
 26             // 同步新旧图纸的HOVER事件和CLICK事件
 27             //let ViewerEvent = Glodon.Bimface.Viewer.ViewerDrawingEvent;
 28             var ViewerEvent = Glodon.Bimface.Viewer.ViewerDrawingEvent;
 29 
 30             latestViewer.addEventListener(ViewerEvent.ComponentsSelectionChanged,
 31                 function (data) {
 32                     prev.clearSelection();
 33                     prev.selectByIds(data);
 34                 });
 35 
 36             prev.addEventListener(ViewerEvent.ComponentsSelectionChanged,
 37                 function (data) {
 38                     latestViewer.getViewer().clearSelection();
 39                     latestViewer.selectByIds(data);
 40                 });
 41 
 42             latestViewer.addEventListener(ViewerEvent.Hover,
 43                 function (data) {
 44                     prev.clearHighlight();
 45                     data.objectId && prev.highlightById(data.objectId);
 46                     console.log(data.objectId);
 47                 });
 48 
 49             prev.addEventListener(ViewerEvent.Hover,
 50                 function (data) {
 51                     latestViewer.getViewer().clearHighlight();
 52                     data.objectId && latestViewer.getViewer().highlightById(data.objectId);
 53                 });
 54         }
 55 
 56         function bindEvent() {
 57             var red = new Glodon.Web.Graphics.Color("#FF0000", 0.8);
 58             var yellow = new Glodon.Web.Graphics.Color("#FFF68F", 0.8);
 59             var blue = new Glodon.Web.Graphics.Color("#32CD99", 0.8);
 60             // 设置差异列表的交互
 61             // 获取文档中 class="detail" 的第一个元素: 差异列表内容的div
 62             var dom = document.querySelector('.detail');
 63 
 64             // 差异列表的点击事件
 65             // e 为MouseEvent事件,其target为点击到的html元素
 66             dom.addEventListener('click',
 67                 function (e) {
 68                     console.log(e);
 69                     var target = e.target;
 70                     tagName = target.tagName;
 71                     // 通过点击对象的种类,决定交互
 72                     if (tagName == 'SPAN') {
 73                         // 如果是span,则展开/收起列表
 74                         target.parentElement.toggleClass('bf-collapse');
 75                     } else if (tagName == 'LI') {
 76                         // 如果是li,则绘制矩形框
 77                         // 获取点击的数值,对应图元的id
 78                         var id = target.innerText;
 79 
 80                         // 清除上一步的选中效果和boundingBox
 81                         latest.getViewer().clearSelection();
 82                         latest.getViewer().clearElementBox();
 83                         prev.clearElementBox();
 84                         prev.clearSelection();
 85 
 86                         switch (target.className) {
 87                             // 新增图元
 88                             case "add-item":
 89                                 // 设置矩形框的样式-蓝色&云线
 90                                 prev.setElementBoxColor(blue);
 91                                 prev.setElementBoxStyle("CloudRect");
 92                                 latest.getViewer().setElementBoxColor(blue);
 93                                 latest.getViewer().setElementBoxStyle("CloudRect");
 94 
 95                                 // 定位
 96                                 latest.getViewer().zoomToObject(id);
 97 
 98                                 // 绘制矩形框
 99                                 var BBox = latest.getViewer().getObjectBoundingBox(parseInt(id));
100                                 prev.showElementBoxByBBox(BBox, 1);
101                                 console.log(BBox);
102                                 latest.getViewer().showElementBoxByBBox(BBox, 1);
103                                 break;
104 
105                             // 被修改图元
106                             case "modify-item":
107                                 // 设置矩形框的样式-黄色&云线
108                                 prev.setElementBoxColor(yellow);
109                                 prev.setElementBoxStyle("CloudRect");
110                                 latest.getViewer().setElementBoxColor(yellow);
111                                 latest.getViewer().setElementBoxStyle("CloudRect");
112 
113                                 // 定位
114                                 prev.zoomToObject(id);
115 
116                                 // 绘制矩形框
117                                 var BBox = prev.getViewer().getObjectBoundingBox(parseInt(id));
118                                 prev.showElementBoxByBBox(BBox, 1);
119                                 latest.getViewer().showElementBoxByBBox(BBox, 1);
120                                 break;
121 
122                             // 被删除图元
123                             case "delete-item":
124                                 // 设置矩形框的样式-红色&云线
125                                 prev.setElementBoxColor(red);
126                                 prev.setElementBoxStyle("CloudRect");
127                                 latest.getViewer().setElementBoxColor(red);
128                                 latest.getViewer().setElementBoxStyle("CloudRect");
129 
130                                 // 定位
131                                 prev.zoomToObject(id);
132 
133                                 // 绘制矩形框
134                                 var BBox = prev.getViewer().getObjectBoundingBox(parseInt(id));
135                                 prev.showElementBoxByBBox(BBox, 1);
136                                 latest.getViewer().showElementBoxByBBox(BBox, 1);
137                         }
138                     }
139                 });
140 
141             // 设置layout切换同步
142             var layout = document.querySelector('.bf-family .bf-sub-toolbar');
143             layout.addEventListener('click',
144                 function (e) {
145                     var target = e.target, tagName = target.tagName, name, views;
146                     if (tagName == 'SPAN') {
147                         name = target.innerText;
148                     } else if (tagName == 'DIV') {
149                         name = target.getAttribute('title');
150                     }
151                     views = prev.getViews();
152                     views.map((item, i) => {
153                         if (item.name == name) {
154                             prev.showViewById(item.id);
155                         }
156                     });
157                 });
158 
159             // 显示效果同步
160             var state = { showLineWidth: true, mode: '普通模式', layout: 'model' }
161             setInterval(() => {
162                 var lineWidth = latest.getViewer().getViewer().viewer.ShowLineWidth;
163                 var container = document.querySelectorAll('.bf-drawing-container ');
164                 if (lineWidth != state.showLineWidth) {
165                     state.showLineWidth = !state.showLineWidth;
166                     prev.showLineWidth(state.showLineWidth);
167                 }
168                 if (document.querySelector('input[mode=普通模式]') &&
169                     document.querySelector('input[mode=普通模式]').checked &&
170                     (state.mode != '普通模式')) {
171                     state.mode = '普通模式';
172 
173                     prev.setPrintMode('Normal');
174                     container[1].style.background = 'rgb(50,50,55)';
175 
176                 } else if (document.querySelector('input[mode=白底模式]') &&
177                     document.querySelector('input[mode=白底模式]').checked &&
178                     (state.mode != '白底模式')) {
179                     state.mode = '白底模式';
180                     prev.setPrintMode('White');
181                     container[1].style.background = 'rgb(255,255,255)';
182                 } else if (document.querySelector('input[mode=黑白模式]') &&
183                     document.querySelector('input[mode=黑白模式]').checked &&
184                     (state.mode != '黑白模式')) {
185                     state.mode = '黑白模式';
186                     prev.setPrintMode('Black');
187                     container[1].style.background = 'rgb(255,255,255)';
188                 }
189             },
190                 1000);
191 
192             // 图层列表显示同步
193             var watch = function () {
194                 var layers = document.querySelector('.layers-panel');
195                 if (layers) {
196                     layers.addEventListener('click',
197                         function (e) {
198                             var data = latest.getViewer().getViewer().getLayers(),
199                                 obj = {},
200                                 arr = [],
201                                 prevState = prev.getLayers();
202                             data.map(function (item, index) {
203                                 obj[item.id] = item;
204                             });
205                             prevState.map(function (item, index) {
206                                 if (obj[item.id]) {
207                                     arr.push(obj[item.id]);
208                                 } else {
209                                     arr.push(item);
210                                 }
211                             });
212                             prev.getViewer().changeLayers(arr);
213                             prev.getViewer().update();
214                         });
215                 } else {
216                     setTimeout(watch, 1000);
217                 }
218             }
219             watch();
220         }
问题思考 ???

官方提供的示例中,对比的2个.dwg文件中,每个文件中仅包含一张图纸,即一个图框。在常规业务场景下,一个.dwg文件中包含多个图框,如下图

当前版本与历史版本对比完成后,通过上述测试程序,在Web网页中点击差异项可以自动定位到图元变化所在位置。是否可以知道差异项来自哪个图框呢?

答案是肯定的,实现方案参考下面两篇博客《C#开发BIMFACE系列43 服务端API之图纸拆分》《C#开发BIMFACE系列44 服务端API之计算图纸对比差异项来源自哪个图框》

 

上述测试程序使用了 《BIMFace.SDK.CSharp》开源SDK。欢迎大家下载使用。

BIMFACE二次开发系列目录     【已更新最新开发文章,点击查看详细】
posted @ 2021-10-12 08:54  张传宁  阅读(713)  评论(0编辑  收藏  举报
页脚 HTML 代码