# 批注(Annotation)

在运维或协同的场景中,经常需要对模型或图纸进行批注,及时记录已发现的问题并交给相关负责的人员。

本节我们将实现一个简单的批注编辑功能。

# 教程内容

在运维的场景中,我们将实现以下功能

  • 创建批注工具条
  • 绘制批注
  • 恢复批注

# 批注功能说明

在我们开始实现功能之前,先了解一下BIMFACE中有关批注的一些概念及功能说明。

# 1. 基本概念

批注指的是在当前场景视角下添加“云线”、“箭头”等图元,辅助使用者进行标记的功能,它适用于所有的矢量图纸及三维模型场景。在三维场景中,一旦开启绘制批注,则场景的视角将被固定,直到结束绘制批注。

# 2. 批注样式

BIMFACE中的批注样式设置分为四类,分别为批注类型、线宽、批注线颜色及填充色。

其中,批注类型有7类,分别为:

  • 箭头
  • 矩形云线
  • 云线
  • 矩形
  • 圆形
  • 十字
  • 文字 后续将添加更多的批注类型。

# 批注功能

# 1. 创建批注工具条

为便于用户能够快速开发属于自己的BIM应用,BIMFACE提供了一整套的工具条UI接口。这里,我们直接调用批注的工具条方法。

在script标签中构造函数createAnnotationToolbar,用以创建批注工具条。

// ************************** 批注 **************************
let annotationToolbar = null;
function createAnnotationToolbar() {
  if (!annotationToolbar) {
    // 创建批注工具条的配置
    let config = new Glodon.Bimface.Plugins.Annotation.AnnotationToolbarConfig();
    config.viewer = viewer3D;
    // 创建批注工具条
    annotationToolbar = new Glodon.Bimface.Plugins.Annotation.AnnotationToolbar(config);
    // 注册批注工具条的监听事件
    annotationToolbar.addEventListener(Glodon.Bimface.Plugins.Annotation.AnnotationToolbarEvent.Saved, onAnnotationSaved);
    annotationToolbar.addEventListener(Glodon.Bimface.Plugins.Annotation.AnnotationToolbarEvent.Cancelled, eixtAnnotation);
  }
}

相应的,我们对保存批注、退出批注的函数进行定义:

let annotationState = null;
// 保存批注并退出
function onAnnotationSaved() {
  annotationState = annotationToolbar.getAnnotationManager().getCurrentState();
  eixtAnnotation();
}

// 退出批注
function eixtAnnotation() {
  // 显示主工具条
  app.getToolbar("MainToolbar").show();
  annotationToolbar.getAnnotationManager().exit();
}
# 2. 绘制批注

我们先新增一个“开始绘制批注”的按钮:

<button class="button" id="btnDrawAnnotation" onclick="drawAnnotation()">开始绘制批注</button>

在script标签中构造函数drawAnnotation,通过isDrawAnnotationActivated来判断是否激活了绘制批注的功能。

let isDrawAnnotationActivated = false;
function drawAnnotation() {
  // 创建批注工具条
  createAnnotationToolbar();
  if (!isDrawAnnotationActivated) {
    // 隐藏主工具条
    app.getToolbar("MainToolbar").hide();
    // 显示批注工具条
    annotationToolbar.show();
    // 修改批注的激活状态为true
    isDrawAnnotationActivated = true;
  } 
}

另外,当退出批注时,也要通过做记录,因此对退出批注的函数做一些修改:

function eixtAnnotation() {
  app.getToolbar("MainToolbar").show();
  annotationToolbar.getAnnotationManager().exit();
  // 批注的激活状态为false
  isDrawAnnotationActivated = false;
}

重新加载页面,我们已经可以通过点击“开始绘制批注”按钮来调用现成的批注工具条,进行绘制批注的功能了。

在三维场景中,批注内容需要与相机状态关联,为了不影响其他的模型浏览操作,在退出绘制后,默认不显示批注内容。最后,我们还需要能查看我们已经保存的批注。

# 3. 恢复批注

我们在保存批注的过程中,实际上是将批注状态赋值给了annotationState这一变量。而恢复批注,就是将annotationState再放入到批注管理器内。

我们依旧增加一个“恢复批注”的按钮:

<button class="button" id="btnRestoreAnnotation" onclick="restoreAnnotation()">恢复批注</button>

在script标签中构造函数restoreAnnotation。

function restoreAnnotation() {
  if (annotationState != null) {
    // 恢复批注
    annotationToolbar.getAnnotationManager().setState(annotationState);
  } else {
    window.alert("Please draw an annotation first.");
  }
}

现在,我们已经能够顺利地恢复显示批注了。

批注保存与恢复

更多关于批注的功能可点击此处前往示例DEMO (opens new window)中查看。

# 完整代码

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>BIMFACE model scene</title>
    <style type="text/css">
      * {
        margin: 0;
        padding: 0;
      }
      html, body {
        height: 100%;
      }
      .buttons {
        font-size: 0;
      }
      .button {
        margin: 5px 0 5px 5px;
        width: 90px;
        height: 30px;
        border-radius: 3px;
        border: none;
        background: #11DAB7;
        color: #FFFFFF;
      }
      .main {
        display: flex;
        flex-direction: column;
        overflow: hidden;
        height: 100%;
      }
      #domId {
        flex: 1;
      }
    </style>
  </head>
  <body>
    <div class='main'>
      <div class='buttons'>
        <button class="button" id="btnIsolation" onclick="isolateComponents()">构件隔离</button>
        <button class="button" id="btnZoomToSelection" onclick="zoomToSelectedComponents()">构件定位</button>
        <button class="button" id="btnOverrideColor" onclick="overrideComponents()">构件着色</button>
        <button class="button" id="btnBlinkComponent" onclick="blinkComponents()">构件强调</button>
        <button class="button" id="btnSaveState" onclick="getCurrentState()">保存状态</button>
        <button class="button" id="btnRestoreState" onclick="setState()">恢复状态</button>
        <button class="button" id="btnStartAutoRotate" onclick="startAutoRotate()">开始旋转场景</button>
        <button class="button" id="btnAddKeyFrame" onclick="addKeyFrame()">添加关键帧</button>
        <button class="button" id="btnPlayWalkThrough" onclick="playWalkThrough()">播放路径漫游</button>
        <button class="button" id="btnDrawAnnotation" onclick="drawAnnotation()">开始绘制批注</button>
        <button class="button" id="btnRestoreAnnotation" onclick="restoreAnnotation()">恢复批注</button>
      </div>
      <div id="domId"></div>
    </div>
    <script src="https://static.bimface.com/api/BimfaceSDKLoader/BimfaceSDKLoader@latest-release.js"></script>
    <script>
      let viewToken = '<yourViewToken>';
      // 声明Viewer及App
      let viewer3D;
      let app;
      // 配置JSSDK加载项
      window.onload = function() {
        let loaderConfig = new BimfaceSDKLoaderConfig();
        loaderConfig.viewToken = viewToken;
        BimfaceSDKLoader.load(loaderConfig, successCallback, failureCallback);
      }
      // 加载成功回调函数
      function successCallback(viewMetaData) {
        let dom4Show = document.getElementById('domId');
        // 设置WebApplication3D的配置项
        let webAppConfig = new Glodon.Bimface.Application.WebApplication3DConfig();
        webAppConfig.domElement = dom4Show;
        // 创建WebApplication3D,用以显示模型
        app = new Glodon.Bimface.Application.WebApplication3D(webAppConfig);  
        app.addView(viewToken);
        viewer3D = app.getViewer();
      }
      // 加载失败回调函数
      function failureCallback(error) {
        console.log(error);
      }

      // ************************** 隔离 **************************
      let isIsolationActivated = false;
      function isolateComponents() {
        if (!isIsolationActivated) {
          // 设置隔离选项,指定其他构件为半透明状态
          let makeOthersTranslucent = Glodon.Bimface.Viewer.IsolateOption.MakeOthersTranslucent;
          // 调用viewer3D.method,隔离楼层为"F2"的构件
          viewer3D.getModel().isolateComponentsByObjectData([{"levelName":"F2"}], makeOthersTranslucent);
          // 渲染三维模型
          viewer3D.render(); 
          // 修改按钮的文字内容
          setButtonText("btnIsolation", "取消隔离");
        } else {
          // 清除隔离
          viewer3D.getModel().clearIsolation();
          // 渲染三维模型
          viewer3D.render();
          // 修改按钮的文字内容
          setButtonText("btnIsolation", "构件隔离");
        }
        isIsolationActivated = !isIsolationActivated;
      }

      // ************************** 定位 **************************
      let isZoomToSelectionActivated = false;
      function zoomToSelectedComponents(){
        if (!isZoomToSelectionActivated) {
          // 选中id为"271431"的构件
          viewer3D.getModel().addSelectedComponentsById(["271431"]);
          // 定位到选中的构件
          viewer3D.getModel().zoomToSelectedComponents();
          // 清除构件选中状态
          viewer3D.getModel().clearSelectedComponents();
          setButtonText("btnZoomToSelection", "回到主视角");
        } else {
          // 切换至主视角
          viewer3D.setView(Glodon.Bimface.Viewer.ViewOption.Home);
          setButtonText("btnZoomToSelection", "构件定位");
        }
        isZoomToSelectionActivated = !isZoomToSelectionActivated;
      }

      // ************************** 着色 **************************
      let isOverrideActivated = false;
      function overrideComponents(){
        if (!isOverrideActivated) {
          // 新建color对象,指定关注构件被染色的数值
          let color = new Glodon.Web.Graphics.Color("#11DAB7", 0.5);
          // 对关注构件进行着色
          viewer3D.getModel().overrideComponentsColorById(["389601"], color);
          viewer3D.render();
          setButtonText("btnOverrideColor", "清除着色");
        } else {
          // 清除构件着色
          viewer3D.getModel().clearOverrideColorComponents();
          viewer3D.render();
          setButtonText("btnOverrideColor", "构件着色");
        }
        isOverrideActivated = !isOverrideActivated;
      }

      // ************************** 构件闪烁 **************************
      let isBlinkActivated = false;
      function blinkComponents() {
        if (!isBlinkActivated) {
          let blinkColor = new Glodon.Web.Graphics.Color("#B22222", 0.8);
          // 打开构件强调开关
          viewer3D.enableBlinkComponents(true);
          // 给需要报警的构件添加强调状态
          viewer3D.getModel().addBlinkComponentsById(["389617"]);
          // 设置强调状态下的颜色
          viewer3D.getModel().setBlinkColor(blinkColor);
          // 设置强调状态下的频率
          viewer3D.getModel().setBlinkIntervalTime(500);
          viewer3D.render();
          setButtonText("btnBlinkComponent", "清除强调");
        } else {
          // 清除构件强调
          viewer3D.getModel().clearAllBlinkComponents();
          viewer3D.render();
          setButtonText("btnBlinkComponent", "构件强调");
        }
        isBlinkActivated = !isBlinkActivated;
      }

      // ************************** 状态 **************************
      let state;
      function getCurrentState(){
        // 保存当前模型浏览状态
        state = viewer3D.getCurrentState(); 
      }

      function setState(){
        if (state != null) {
          // 恢复模型浏览状态
          viewer3D.setState(state);
          viewer3D.render();
        } else {
          window.alert("请先保存一个模型浏览状态!");
        }
      }

      // ************************** 旋转场景 **************************
      let isAutoRotateActivated = false;
      function startAutoRotate() {
        if (!isAutoRotateActivated) {
          // 开始场景旋转
          viewer3D.startAutoRotate(5);
          setButtonText("btnStartAutoRotate", "结束旋转场景");
        } else {
          // 结束场景旋转
          viewer3D.stopAutoRotate();
          setButtonText("btnStartAutoRotate", "开始旋转场景");
        }
        isAutoRotateActivated = !isAutoRotateActivated;
      }

      // ************************** 路径漫游 **************************
      let walkThrough = null;
      function createWalkThrough() {
        if (walkThrough == null) {
          // 构造路径漫游配置wtConfig
          let walkThroughConfig = new Glodon.Bimface.Plugins.Walkthrough.WalkthroughConfig();
          // 设置路径漫游配置匹配的viewer对象
          walkThroughConfig.viewer = viewer3D;
          // 构造路径漫游对象
          walkThrough = new Glodon.Bimface.Plugins.Walkthrough.Walkthrough(walkThroughConfig);
        }
      }

      function addKeyFrame() {
        createWalkThrough();
        //添加关键帧
        walkThrough.addKeyFrame(); 
      }

      function playWalkThrough() {
        if (walkThrough != null) {
          // 设置播放时间为5秒
          walkThrough.setWalkthroughTime(5);
          // 设置关键帧事件
          walkThrough.setKeyFrameCallback(kfCallback);
          // 播放路径漫游
          walkThrough.play();
        } else {
          window.alert("Please add keyframes first.");
        }
      }

      function kfCallback(idx) {
        switch (idx) {
          case 0:
            break;
          case 1:
            console.log('Hello, BIM!');
            break;
        }
      }

      // ************************** 批注 **************************
      let isDrawAnnotationActivated = false;
      let annotationToolbar = null;
      let annotationState = null;
      function createAnnotationToolbar() {
        if (!annotationToolbar) {
          // 创建批注工具条的配置
          let config = new Glodon.Bimface.Plugins.Annotation.AnnotationToolbarConfig();
          config.viewer = viewer3D;
          // 创建批注工具条
          annotationToolbar = new Glodon.Bimface.Plugins.Annotation.AnnotationToolbar(config);
          // 注册批注工具条的监听事件
          annotationToolbar.addEventListener(Glodon.Bimface.Plugins.Annotation.AnnotationToolbarEvent.Saved, onAnnotationSaved);
          annotationToolbar.addEventListener(Glodon.Bimface.Plugins.Annotation.AnnotationToolbarEvent.Cancelled, eixtAnnotation);
        }
      }

      // 保存批注并退出
      function onAnnotationSaved() {
        annotationState = annotationToolbar.getAnnotationManager().getCurrentState();
        eixtAnnotation();
      }

      // 退出批注
      function eixtAnnotation() {
        // 显示主工具条
        app.getToolbar("MainToolbar").show();
        annotationToolbar.getAnnotationManager().exit();
        // 批注的激活状态为false
        isDrawAnnotationActivated = false;
      }

      function drawAnnotation() {
        // 创建批注工具条
        createAnnotationToolbar();
        if (!isDrawAnnotationActivated) {
          // 隐藏主工具条
          app.getToolbar("MainToolbar").hide();
          // 显示批注工具条
          annotationToolbar.show();
          // 修改批注的激活状态为true
          isDrawAnnotationActivated = true;
        } 
      }

      function restoreAnnotation() {
        if (annotationState != null) {
          // 恢复批注
          annotationToolbar.getAnnotationManager().setState(annotationState);
        } else {
          window.alert("Please draw an annotation first.");
        }
      }

      // ************************** 按钮文字 **************************
      function setButtonText(btnId, text) {
        let dom = document.getElementById(btnId);
        if (dom != null && dom.nodeName == "BUTTON") {
          dom.innerText = text;
        }
      }
    </script>
  </body>
</html>

恭喜你,你已完成了在运维场景中对模型添加批注的功能。接下来,你可以继续学习在场景中添加二维标签 (opens new window)的功能。