C#開發BIMFACE系列42 服務端API之圖紙對比

張傳寧發表於2021-10-12
BIMFACE二次開發系列目錄     【已更新最新開發文章,點選檢視詳細】
C#開發BIMFACE系列42 服務端API之圖紙對比

在我的前一篇部落格C#開發BIMFACE系列42 服務端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,用於發起對比,獲取對比狀態、獲取對比結果。請參考我的部落格:

測試程式

C#開發BIMFACE系列42 服務端API之圖紙對比

發起圖紙對比

C#開發BIMFACE系列42 服務端API之圖紙對比

呼叫伺服器端的API獲取對比結果

C#開發BIMFACE系列42 服務端API之圖紙對比

對比差異分為三類:新增、修改、刪除。由於CAD圖紙的展示型別包含 Model 與 Layer 兩種形式,

C#開發BIMFACE系列42 服務端API之圖紙對比

C#開發BIMFACE系列42 服務端API之圖紙對比

差異結果中也是包含兩種展示型別的對比資訊,所以可能有重複的圖元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檔案中包含多個圖框,如下圖

C#開發BIMFACE系列42 服務端API之圖紙對比

那麼當前版本與歷史版本對比完成後,通過上述測試程式,在Web網頁中點選差異項可以自動定位到圖元變化所在位置。是否可以知道差異項來自哪個圖框呢?

答案是肯定的,實現方案參考下一篇部落格《C#開發BIMFACE系列43 服務端API之圖紙拆分》

 

上述測試程式使用了 《BIMFace.SDK.CSharp》開源SDK。歡迎大家下載使用。

C#開發BIMFACE系列42 服務端API之圖紙對比

BIMFACE二次開發系列目錄     【已更新最新開發文章,點選檢視詳細】

相關文章