# 外部构件(External Object)

BIM模型主要提供的是静态的模型与信息,但在真正的施工、运维场景中,普遍存在需要在既有模型基础上额外添加构件,并对构件进行编辑的需求。这一节我们将在场景中添加一辆汽车,并尝试变动它的位置和方向。

# 教程内容

在已搭建的运维平台上,接下来我们需要

  • 添加车辆构件
  • 改变车辆的方向
  • 让车辆向前移动

# 外部构件说明

# 外部构件应用

# 1. 添加外部构件

我们先创建一个添加外部构件的按钮:

<button class="button" id="btnAddObject" onclick="addExternalObject()">添加外部构件</button>

接下来,我们从构件库中选取一个车辆的3DS对象,添加到场景中。

// 添加外部构件
let isExternalObjectAdded = false;
let extObjMng = null;
let extObjId = null;
function addExternalObject() {
  if (isExternalObjectAdded) {
    return;
  }
  // 构造外部构件管理器
  extObjMng = new Glodon.Bimface.Viewer.ExternalObjectManager(viewer3D);
  let objUrl = "http://static.bimface.com/attach/6db9d5bcf88640f997b23be61e870ee8_%E6%B1%BD%E8%BD%A6.3DS";
  // 将3DS对象添加为外部构件
  extObjMng.loadObject({name:'vehicle',url:{objectUrl:objUrl}}, function() {
    isExternalObjectAdded = true;
    viewer3D.render();
  });
}

现在,我们已经将车辆作为外部构件加载到场景中,但目前还看不到构件,原因在于载入的构件默认位置是场景的坐标原点。因此,我们还需要调整构件的坐标,将构件移动到小楼外的地坪上。

// 获取构件对象ID
extObjId = extObjMng.getObjectIdByName("vehicle");
// 将构件移至初始位置
extObjMng.translate(extObjId, {x: -7500, y: -15000, z: -450});
viewer3D.render();

添加外部构件

# 2. 改变车辆方向

在添加外部构件的过程中,我们已经使用了构件平移的方法,接下来我们要对构件进行旋转操作。缩放等更多方法可参考示例DEMO外部构件编辑 (opens new window)

添加一个旋转构件的按钮。

<button class="button" id="btnRotateObject" onclick="rotateObject()">旋转外部构件</button>

我们让构件每次绕逆时针旋转30°。

// 旋转外部构件
function rotateObject() {
  if (!isExternalObjectAdded) {
    return;
  }
  // 绕逆时针旋转30°
  extObjMng.rotateZ(extObjId, Math.PI / 6);
}

改变车辆方向

# 3. 移动车辆前进

添加一个平移构件的按钮。

<button class="button" id="btnMovebject" onclick="moveObject()">移动外部构件</button>

最后,我们让车辆每次向前移动1米。

function moveObject() {
  if (!isExternalObjectAdded) {
    return;
  }
  // 沿汽车前进方向移动1.0米
  extObjMng.offsetY(extObjId, -1000);
}

移动车辆前进

# 4. 拓展

更多关于构件编辑的方法与应用,可参考以下DEMO内容:

# 完整代码

<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>
            <button class="button" id="btnTagging" onclick="addCustomTag()">开始放置标签</button>
            <button class="button" id="btnAddMarker" onclick="addMarker()">放置三维标签</button>
            <button class="button" id="btnCreateRooms" onclick="createRooms()">创建房间</button>
            <button class="button" id="btnHideRoom" onclick="hideRoom(roomIdList)">隐藏房间</button>
            <button class="button" id="btnGetProperty" onclick="getProperty(roomId)">获取属性</button>
            <button class="button" id="btnAddObject" onclick="addExternalObject()">添加外部构件</button>
            <button class="button" id="btnRotateObject" onclick="rotateObject()">旋转外部构件</button>
            <button class="button" id="btnMovebject" onclick="moveObject()">移动外部构件</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();
            roomManager = viewer3D.getRoomManager();
            // 加载轴网
            viewer3D.getModel().showAxisGridsByFloor("", 311);
        }
        // 加载失败回调函数
        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.");
            }
        }

        // ************************** 自定义标签 **************************
        let isAddCustomTagActivated = false;
        let customItemContainer = null;
        function createCustomItemContainer() {
            if (!customItemContainer) {
                // 创建标签容器配置
                let drawableContainerConfig = new Glodon.Bimface.Plugins.Drawable.DrawableContainerConfig();
                // 设置容器配置匹配的对象
                drawableContainerConfig.viewer = viewer3D;
                // 创建标签容器
                customItemContainer = new Glodon.Bimface.Plugins.Drawable.DrawableContainer(drawableContainerConfig);
            }
        }

        function addCustomItem(object) {
            createCustomItemContainer();
            // 创建CustomItemConfig
            let config = new Glodon.Bimface.Plugins.Drawable.CustomItemConfig();
            let content = document.createElement('div');
            // 自定义样式,支持HTML的任意DOM元素
            // 设置标签的宽度和高度
            content.style.width = '80px';
            content.style.height = '32px';
            // 设置标签样式
            content.style.border = 'solid';
            content.style.borderColor = '#FFFFFF';
            content.style.borderWidth = '2px';
            content.style.borderRadius = '5%';
            content.style.background = '#11DAB7';
            // 设置标签文字内容与样式
            content.innerText = '检查点';
            content.style.color = '#FFFFFF';
            content.style.textAlign = 'center';
            content.style.lineHeight = '32px';
            // 设置自定义标签配置
            config.content = content;
            config.viewer = viewer3D;
            config.worldPosition = object.worldPosition;
            // 创建自定义标签对象
            let customItem = new Glodon.Bimface.Plugins.Drawable.CustomItem(config);
            // 将自定义标签添加至标签容器内
            customItemContainer.addItem(customItem)
        }

        function addCustomTag() {
            if (!isAddCustomTagActivated) {
                // 创建鼠标点击的监听事件
                viewer3D.addEventListener(Glodon.Bimface.Viewer.Viewer3DEvent.MouseClicked, addCustomItem);
                setButtonText("btnTagging", "结束放置标签");
            } else {
                // 移除鼠标点击的监听事件
                viewer3D.removeEventListener(Glodon.Bimface.Viewer.Viewer3DEvent.MouseClicked, addCustomItem);
                setButtonText("btnTagging", "开始放置标签");
            }
            isAddCustomTagActivated = !isAddCustomTagActivated;
        }

        // ************************** 内建三维标签 **************************
        let is3DMarkerPlaced = false;
        function addMarker() {
            if (is3DMarkerPlaced) {
                return;
            }
            // 构造三维标签容器配置markerContainerConfig
            let markerContainerConfig = new Glodon.Bimface.Plugins.Marker3D.Marker3DContainerConfig();
            // 设置markerContainerConfig匹配的viewer对象
            markerContainerConfig.viewer = viewer3D;
            // 构造三维标签容器markerContainer
            let markerContainer = new Glodon.Bimface.Plugins.Marker3D.Marker3DContainer(markerContainerConfig);
            // 构造三维标签配置项
            let markerConfig = new Glodon.Bimface.Plugins.Marker3D.Marker3DConfig();
            // 为标签指定图片路径
            markerConfig.src = "http://static.bimface.com/resources/3DMarker/warner/warner_red.png";
            // 构造点位,并指定为标签的插入点
            let markerPos = { "x": -5743.838548165086, "y": -1667.12605781937, "z": 12923.137945446013 };
            markerConfig.worldPosition = markerPos;
            // 指定标签大小
            markerConfig.size = 60;
            // 构造三维标签
            let marker = new Glodon.Bimface.Plugins.Marker3D.Marker3D(markerConfig);
            // 添加标签的点击事件
            marker.onClick(function () {
                window.alert('Warning!');
            });
            // 将三维标签添加至容器内
            markerContainer.addItem(marker);
            is3DMarkerPlaced = true;
        }

        // ************************** 房间创建与应用 **************************
        // *********************创建房间***************************
        let isRoomCreated = false;

        function createRooms() {
            if (isRoomCreated) {
                return;
            }
            // 获取解析后的房间边界信息,这里要找到名称为“活动室 11”的对象,并创建房间
            viewer3D.getModel().getAreas(function (data) {
                if (data) {
                    for (let i = 0; i < data.length; i++) {
                        if (data[i].rooms) {
                            for (let j = 0; j < data[i].rooms.length; j++) {
                                if (data[i].rooms[j].name == "活动室 11") {
                                    // 获取房间信息和id
                                    let roomInfo = data[i].rooms[j];
                                    roomId = 'room-' + roomInfo.id;
                                    // 通过房间边界创建一个3350mm高的房间,并自定义房间id
                                    let roomConfig = new Glodon.Bimface.Plugins.Rooms.RoomConfig();
                                    roomConfig.viewer = viewer3D;
                                    roomConfig.roomId = roomId;
                                    roomConfig.geometry = {
                                        type: "extrusion",
                                        boundary: roomInfo.boundary,
                                        height: 3350,
                                        unit: "mm"
                                    };
                                    roomConfig.roomColor = new Glodon.Web.Graphics.Color(255, 0, 0, 0.5);
                                    roomConfig.frameColor = new Glodon.Web.Graphics.Color(255, 0, 0, 1);
                                    let room = new Glodon.Bimface.Plugins.Rooms.Room(roomConfig);
                                    roomManager.addRoom(room);
                                    viewer3D.render();
                                    isRoomCreated = true;
                                    roomIdList.push(roomId);
                                }
                            }
                        }
                    }
                }
            });
            // 按用户自定义边界信息和标高生成房间
            let roomConfig2 = new Glodon.Bimface.Plugins.Rooms.RoomConfig();
            roomConfig2.viewer = viewer3D;
            roomConfig2.roomId = 'room2';
            roomConfig2.geometry = {
                type: "extrusion",
                boundary: {
                    "outer": [
                        { "x": "-5500", "y": "0", "z": "0" },
                        { "x": "-2300", "y": "0", "z": "0" },
                        { "x": "-2300", "y": "-5000", "z": "0" },
                        { "x": "-5500", "y": "-5000", "z": "0" }
                    ]
                },
                offset: [0, 3500],
                unit: "mm"
            };
            roomConfig2.roomColor = new Glodon.Web.Graphics.Color(0, 255, 0, 0.5);
            roomConfig2.frameColor = new Glodon.Web.Graphics.Color(0, 255, 0, 1);
            let room2 = new Glodon.Bimface.Plugins.Rooms.Room(roomConfig2);
            roomManager.addRoom(room2);
            roomIdList.push(roomConfig2.roomId)


            let roomConfig3 = new Glodon.Bimface.Plugins.Rooms.RoomConfig();
            roomConfig3.viewer = viewer3D;
            roomConfig3.roomId = 'room3';
            roomConfig3.geometry = {
                type: "extrusion",
                grid: {
                    ids: ['A', 'E', '3', '1/5']
                },
                offset: [0, 3500]
            };
            roomConfig3.roomColor = new Glodon.Web.Graphics.Color(0, 0, 255, 0.5);
            roomConfig3.frameColor = new Glodon.Web.Graphics.Color(0, 0, 255, 1);

            let room3 = new Glodon.Bimface.Plugins.Rooms.Room(roomConfig3);
            roomManager.addRoom(room3);
            roomIdList.push(roomConfig3.roomId)

        }

        //*********************隐藏房间*************************
        isHideRoomActivated = false;
        function hideRoom(roomIdList) {
            // 判断是否已经创建房间
            if (!isRoomCreated) {
                window.alert("Please create a room first!");
                return;
            }
            if (!isHideRoomActivated) {
                // 根据id隐藏房间
                roomManager.hideRoomsById(roomIdList);
                viewer3D.render();
                setButtonText("btnHideRoom", "显示房间");
                isHideRoomActivated = true;
            } else {
                // 根据id显示房间
                roomManager.showRoomsById(roomIdList);
                viewer3D.render();
                setButtonText("btnHideRoom", "隐藏房间");
                isHideRoomActivated = false;
            }
        }

        //******************获取属性***************************
        let roomId = roomIdList[0];
        function getProperty(roomId) {
            // 判断是否已经创建房间
            if (!isRoomCreated) {
                window.alert("Please create a room first!");
                return;
            } else {
                // 获取房间对象
                room = roomManager.getRoomById(roomId);

                // 获取面积属性
                area = room.getArea();
                // 获取房间高度
                height = room.getHeight();

                alert("id:" + roomId + "\n" + "面积:" + area.value + "\n" + "高度:" + height);

            }

        }

        // ************************** 添加外部构件 **************************
        // 添加外部构件
        let isExternalObjectAdded = false;
        let extObjMng = null;
        let extObjId = null;
        function addExternalObject() {
            if (isExternalObjectAdded) {
                return;
            }
            // 构造外部构件管理器
            extObjMng = new Glodon.Bimface.Viewer.ExternalObjectManager(viewer3D);
            let objUrl = "http://static.bimface.com/attach/6db9d5bcf88640f997b23be61e870ee8_%E6%B1%BD%E8%BD%A6.3DS";
            // 将3DS对象添加为外部构件
            extObjMng.loadObject({ name: 'vehicle', url: { objectUrl: objUrl } }, function () {
                isExternalObjectAdded = true;
                // 获取构件对象ID
                extObjId = extObjMng.getObjectIdByName("vehicle");
                // 将构件移至初始位置
                extObjMng.translate(extObjId, { x: -7500, y: -15000, z: -450 });
                viewer3D.render();
            });
        }

        // 旋转外部构件
        function rotateObject() {
            if (!isExternalObjectAdded) {
                return;
            }
            // 绕逆时针旋转30°
            extObjMng.rotateZ(extObjId, Math.PI / 6);
        }

        // 移动外部构件
        function moveObject() {
            if (!isExternalObjectAdded) {
                return;
            }
            // 沿汽车前进方向移动1.0米
            extObjMng.offsetY(extObjId, -1000);
        }

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

</html>