
又是很久没出随笔文章了,一篇文章有时候整理一天,实在是抽不出来时间。
首先,我们要知道消防里的知识,不是简简单单的几个灭火器,烟感报警器这么简单的,消防是自有一套完整体系的,光是消防相关的产业年产值就有几千个亿。而我们普通非专业人士常见的消防设备只是消防产业中的皮毛。
还是闲话少序,切入正题。
前面的课程里,有一篇文章《使用webgl(three.js搭建一个3D智慧园区、3D建筑,3D消防模拟,web版3D,bim管理系统——第四课》是介绍实现消防模拟的。本文以及后续的篇幅将系统的介绍消防可视化方案。
我们是技术类文章,这里只能做一个简单的常见的消防系统定义介绍:
消防给水系统:以水为灭火剂消防扑救火灾的供水系统。由水源、消防给水管网、消防水池、消防水泵及消火栓、自动喷水灭火设施等组成。
消火栓系统:消火栓系统一般都由主泵和备用泵组成。一般按钮启动后,先启动1#泵,1#泵启动失灵,自动转启2#泵,只有当两台泵都不能启动时,控制盘上才显示故障。消防水泵的故障,一般是指水泵电机断电、过载及短路等
自动喷水灭火系统:自动喷水灭火系统由洒水喷头、报警阀组、水流报警装置等组件,以及管道、供水设施组成,并能在发生火灾时喷水的自动灭火系统。
气体灭火系统:气体灭火系统主要用在不适于设置水灭火系统等其他灭火系统的环境中
防烟排烟系统:防排烟系统,都是由送排风管道、管井、防火阀、门开关设备、送、排风机等设备组成
火灾自动报警系统:火灾自动报警系统是由触发装置、火灾报警装置、联动输出装置以及具有其它辅助功能装置组成的
言归正传,我们下面对各系统架构做一些概括性讲解。
1.1、消防给水系统效果展示
其功能实现的主要伪代码如下:
$(function ( {
indexPage = new IndexPage(;
indexPage.init(;
for (var i = 1; i <= 6; i++ {
if (window["System0" + i] {
window["system0" + i] = new window["System0" + i](;
window["system0" + i].init(;
}
}
};
切换系统事件绑定
$(".res .sys".click(function ( { $("#sysMainNav".hide(; $("#sysNav1".show(; $("#sysNav2".show(; var systemindex = $(this.attr("data-systemIndex"; console.log(systemindex; if (window["system0" + systemindex] { window["system0" + systemindex].show(; } };
主功能切换
//功能ui IndexPage.prototype.showSystemMainNav1 = function (nav1 { layer.closeAll(; $("#backToMainNav".nextAll(.remove(; if (nav1 && nav1.length > 0 { var sysNav1_detailhtml = ""; $.each(nav1, function (_index, _obj { sysNav1_detailhtml += '<div id="' + _obj.id + '" class="boxbg ' + _obj.defaultState + '" onclick="if(indexPage.clickme{return false;};indexPage.clickme=true;setTimeout(function({indexPage.clickme=false},500;' + _obj.actionStr + ';">\ <img src= "../img/' + (_obj.defaultState == "noselect" ? "dot_unselect.png" : "dot_selected.png" + '" class="s1icontime" />\ <p class="timeP">' + _obj.name + '</p>\ </div >'; }; $("#backToMainNav".after(sysNav1_detailhtml; } }
1.2、消火栓系统
例如消火栓注重分室外、室内、干式、湿式、水鹤等等
modelBussiness.hideAllPLSystemDevs(200, function ( {
layer.msg("loading...", { time: 1000 };
if (param.greatePosition {
var greatePosition = param.greatePosition[_this.nav1State];
var camera = { x: 0, y: 0, z: 0 };
var target = { x: 0, y: 0, z: 0 };
var abloutModelNames = null;
var abloutModelObjs = null;
if (greatePosition {
camera = greatePosition.camera;
target = greatePosition.target;
abloutModelNames = greatePosition.abloutModelNames;
}
if (abloutModelNames == "All" {
modelBussiness.showAllDevModels(1;
}
else if (abloutModelNames && abloutModelNames.length > 0 {
abloutModelObjs = WT3DObj.commonFunc.findObjectsByNames(abloutModelNames;
if (abloutModelObjs && abloutModelObjs.length > 0 {
$.each(abloutModelObjs, function (_index, _obj {
_obj.visible = true;
};
}
}
WT3DObj.commonFunc.changeCameraPosition(camera, target, 1000, function ( {
if (id == "s02_03_ssshs" || id == "s02_03_gsshs" {
if (_objs {
$.each(_objs, function (_index, _obj {
if (_obj.name == "pldev_t10_1_7" {
WT3DObj.commonFunc.changeObjsOpacity([_obj], 1, 0.2, 50, function ( {
};
}
if (_obj.name == "pldev_t10_1_7_inner" && id == "s02_03_gsshs" {
_obj.visible = false;
}
if (_obj.name == "pldev_t10_1_7_inner" && id == "s02_03_ssshs" {
_obj.visible = true;
}
};
}
}
};
} else {
console.log("数据初始化异常!没有设置最佳位置";
return;
}
};
1.3、自动喷水灭火系统
【系统部件原理动画举例】
//执行动画 递归调用
System02.prototype.doAnimation = function ( {
modelBussiness.showAllPLSystemDevs(50, function ( {
modelBussiness.hideAllBuildModels(;
modelBussiness.opcityDevs(function ( {
modelBussiness.changeDevsToSSMaterial(;
WT3DObj.commonFunc.changeCameraPosition({ x: -34, y: -331, z: -546 }, { x: -412, y: -260, z: 135 }, 1000, function ( {
};
var actions = system02.nav2[2].children;
if (actions && actions.length > 0 {
function doaction(action_nub {
$("#" + actions[action_nub].id.attr("class", "nav2res navsys";
if (action_nub > 0 {
$("#" + actions[action_nub - 1].id.attr("class", "nav2res navsys noseleced";
}
actions[action_nub].action(function ( {
if (actions[action_nub + 1] && actions[action_nub + 1].action {
doaction(action_nub + 1;
}
};
}
doaction(0;
}
};
};
}
var animationChildren=[
{
name: "发生火情",
id: "s01_step01",
greatePosition: {
camera: { x: -1388, y: -781, z: -309 },
target: { x: -2119, y: -1106, z: 873 }
},
actionStr: "",
action: function (callBack {
modelBussiness.addFire(function ( {
setTimeout(function ( {
callBack(;
}, 2000;
};
}
},
{
name: "喷淋动作",
id: "s01_step02",
greatePosition: {
camera: { x: -1388, y: -781, z: -309 },
target: { x: -2119, y: -1106, z: 873 }
},
actionStr: "",
action: function (callBack {
console.log("执行2";
//找到喷淋
var pls = WT3DObj.commonFunc.findObjectsByNames(["pldev_penling_1_1_28", "pldev_penling_1_1_28_sp_1", "pldev_penling_1_1_28_sp_2", "pldev_penling_1_1_28_sp_3"];
WT3DObj.commonFunc.flashObjs(pls, "flashPL", 0xff0000, 10, 200, 0;
setTimeout(function ( {
var pl = pls[0];
pl.children[1].visible = false;
pl.children[3].visible = false;
//喷淋裂开
var pls1 = pls[1];
var p1x = pls1.position.x;
var p1z = pls1.position.z;
var pls2 = pls[2];
var pls3 = pls[3];
pls1.oldpostion = { x: pls1.position.x, y: pls1.position.y, z: pls1.position.z };
new TWEEN.Tween(pls1.position.to({
x: p1x + 100 * (Math.random( < 0.5 ? -5 * Math.random( : 3 * Math.random(,
y: -2000,
z: p1z + 100 * (Math.random( < 0.5 ? -5 * Math.random( : 3 * Math.random(
}, 8000.start(;
new TWEEN.Tween(pls2.position.to({
x: p1x + 100 * (Math.random( < 0.5 ? -5 * Math.random( : 3 * Math.random(,
y: -2000,
z: p1z + 100 * (Math.random( < 0.5 ? -5 * Math.random( : 3 * Math.random(
}, 8000.start(;
new TWEEN.Tween(pls3.position.to({
x: p1x + 100 * (Math.random( < 0.5 ? -5 * Math.random( : 3 * Math.random(,
y: -2000,
z: p1z + 100 * (Math.random( < 0.5 ? -5 * Math.random( : 3 * Math.random(
}, 8000.start(;
//开始喷水
modelBussiness.addWater(
function ( {
setTimeout(function ( {
callBack(;
}, 1000;
}
;
}, 2000;
}
},
......]
1.4、气体灭火系统
该系统也是主要展现系统部件跟工作动画原理
例如如下代码:
var a_messagePanel_1126 = WT3DObj.commonFunc.findObject("a_messagePanel_1126"; a_messagePanel_1126.position.x=1499.804; a_messagePanel_1126.position.y = 474.389; a_messagePanel_1126.position.z = -74.822; moveXH(a_messagePanel_1126, { x: 2008.767, y: 474.389, z: -74.822, t: 0 }, 2000; setTimeout(function ( { moveXH(a_messagePanel_1126, { x: 2007.926, y: 478.648, z: -451.129, t: 2000 }, 2000; setTimeout(function ( { moveXH(a_messagePanel_1126, { x: -657.755, y: 478.648, z: -451.129, t: 2000 }, 2000; WT3DObj.commonFunc.changeCameraPosition({ x: -295.23352627790484, y: 445.15353495337854, z: -140.38158326269246 }, { x: -798.2079071244784, y: 270.3977749099737, z: -511.7589163393606 }, 10, function ( { }; setTimeout(function ( { moveXH(a_messagePanel_1126, { x: -657.755, y: 478.648, z: -303.566, t: 2000 }, 2000; setTimeout(function ( { moveXH(a_messagePanel_1126, { x: -657.755, y: 339.858, z: -303.566, t: 2000 }, 2000; setTimeout(function ( { a_messagePanel_1126.visible = false; moveXH(a_messagePanel_1126, { x: -662.270, y: 315.038, z: -339.977, t: 2000 }, 2000; setTimeout(function ( { a_messagePanel_1126.visible = true; moveXH(a_messagePanel_1126, { x: -662.270, y: 465.038, z: -339.977, t: 2000 }, 2000; setTimeout(function ( { moveXH(a_messagePanel_1126, { x: -662.270, y: 465.038, z: -719.977, t: 2000 }, 2000; setTimeout(function ( { WT3DObj.commonFunc.changeCameraPosition({ x: 1672.1492964146757, y: 437.22502365828495, z: -946.6467804045902 }, { x: 1393.4110369255911, y: 391.9499762093484, z: -843.1414359356829 }, 1000, function ( { }; moveXH(a_messagePanel_1126, { x: 1433, y: 465.038, z: -719.847, t: 2000 }, 2000; setTimeout(function ( { moveXH(a_messagePanel_1126, { x: 1433, y: 465.038, z: -739.847, t: 2000 }, 2000; setTimeout(function ( { moveXH(a_messagePanel_1126, { x: 1429.452, y: 382.109, z: -739.847, t: 2000 }, 2000; setTimeout(function ( { sgbjq2(callBack; }, 2000; }, 2000; }, 2000; }, 2000; }, 2000; }, 2000; }, 2000; }, 2000; }, 2000; }, 2000; }, 2000;
/*
关闭送(排)风机及送(排)风阀门
停止通风和空调系统及关闭该防护区的电动防火阀
关闭防护区的门窗
*/
/*
向驱动气瓶电磁阀发送开启信号,可设≤30s 的延迟喷射
*/
function djsdz(callBack {
var dev = WT3DObj.commonFunc.findObjectsByNames([
"dev_ledFont_1",
][0];
for (var i = 30; i >=0; i-- {
(function (a {
setTimeout(function ( {
if (a <10 {
a = "0"+a;
}
dev.freshData(a;
},1000 * (30 - a;
}(i;
}
setTimeout(function ( {
callBack(;
},31 * 1000;
}
1.5、防烟排烟系统
action: function (callBack {
//剖面图
$.each(system07.allModels, function (_index, _obj {
if (_obj.hasoldPosition {
_obj.position.y = _obj.hasoldPosition.y ;
}
_obj.visible = true;
};
$.each(system07.foreModels, function (_index, _obj {
if (_obj.hasoldPosition {
_obj.position.y = _obj.hasoldPosition.y + 10000;
} else {
_obj.hasoldPosition = { y: _obj.position.y };
_obj.position.y = _obj.hasoldPosition.y + 10000;
}
_obj.visible = false;
};
//发生火情
system07.stepName = "发生火情!";
layer.msg("</br><font style='color:#ffffff;font-size:20px;'>发生火情!</font></br>";
WT3DObj.commonFunc.changeCameraPosition({ x: 6120.260855819656, y: 1344.6126089802272, z: 10903.874792293966 }, { x: 6292.0698848787415, y: 106.7257770257646, z: 4388.448120618271 }, 1000, function ( {
addLines(;
addFire(;
addFYSFK(;
setTimeout(function ( {
WT3DObj.commonFunc.changeCameraPosition({ x: 5416.2974093433795, y: 1500.4126878089987, z: 7749.928368473556 }, { x: 5474.770344242846, y: 899.4381166888209, z: 4216.286318511336 }, 2000, function ( {
callBack(;
};
}, 2000;
};
}
},
System07.prototype.doAnimationFlag = false;
System07.prototype.showNav2Action = function (index, id {
var _this = this;
modelBussiness.removeSingleShowDevs(;
console.log(id;
if ((_this.nav1State == "s07_05" || _this.nav1State == "s07_06" && _this.doAnimationFlag {
layer.log("正在执行动画";
return;
}
$("#" + _this.nav2State.attr("class", "nav2res navsys noseleced";
$("#" + id.attr("class", "nav2res navsys";
_this.nav2State = id;
var param = _this.getNav2Param(index, id;
if (!param {
console.log("数据初始化异常!";
return;
}
layer.closeAll(;
this.playVoice(index, id;
if (param.dataId && id != "s04_02_gsgd" {
modelBussiness.showDevList(param.dataId;
}
function flashFunc( {
if (param && param.flashNames && param.flashNames.length > 0 {
var fnames = [];
$.each(param.flashNames, function (_findex, _fobj {
var _n = _fobj;
if (_fobj.indexOf("_children_" > 0 {
_n = _fobj.split("_children_"[0];
}
fnames.push(_n;
};
_objs = WT3DObj.commonFunc.findObjectsByNames(fnames;
if (param.flashNames[0] == "All" {
_objs = modelBussiness.getAllPLSystemDevs(;
}
setTimeout(function ( {
WT3DObj.commonFunc.flashObjs(_objs, "flashPL", 0x00ff00, 8, 150, 0;
}, 1000;
}
}
flashFunc(;
switch (_this.nav1State {
//全景
case "s07_01":
//剖面
case "s07_02":
//管网
case "s07_04":
case "s07_03":
if (param.greatePosition {
var greatePosition = param.greatePosition[_this.nav1State];
var camera = { x: 0, y: 0, z: 0 };
var target = { x: 0, y: 0, z: 0 };
if (greatePosition {
camera = greatePosition.camera;
target = greatePosition.target;
}
WT3DObj.commonFunc.changeCameraPosition(camera, target, 1000, function ( { };
} else {
console.log("数据初始化异常!没有设置最佳位置";
return;
}
break;
//动画
case "s07_06":
case "s07_05":
{
//param.action(function ( {
// system07.stepName = null;
//};
}
break;
}
1.6、火灾自动报警系统
1.6.1、地上部分排烟动画演练 部分录屏
1.6.2、地下部分自动报警动画演练 部分录屏
function move(id {
var dv = document.getElementById(id;
var x = 0;
var y = 0;
var l = 0;
var t = 0;
var isDown = false;
//鼠标按下事件
dv.onmousedown = function (e {
//获取x坐标和y坐标
x = e.clientX;
y = e.clientY;
//获取左部和顶部的偏移量
l = dv.offsetLeft;
t = dv.offsetTop;
//开关打开
isDown = true;
//设置样式
dv.style.cursor = 'move';
}
//鼠标移动
window.onmousemove = function (e {
if (isDown == false {
return;
}
//获取x和y
var nx = e.clientX;
var ny = e.clientY;
//计算移动后的左偏移量和顶部的偏移量
var nl = nx - (x - l;
var nt = ny - (y - t;
dv.style.left = nl + 'px';
dv.style.top = nt + 'px';
layer.closeAll(;
}
//鼠标抬起事件
dv.onmouseup = function ( {
//开关关闭
isDown = false;
dv.style.cursor = 'default';
}
}
move("ccAnimationgif";
2.1、模型创建
代码模型,更好的节约了带宽与内存资源,提升了用户体验。载入模型,降低了开发门槛,提升了效率,但其没有代码模型灵活可控
代码模型:
[{"show":true,"uuid":"","name":"cube2_6","objType":"cube2","length":200,"width":200,"height":200,"x":0,"y":200,"z":0,"style":{"skinColor":16777215,"skin":{"skin_up":{"skinColor":16777215,"imgurl":"../../img/3dImg/rack_inside.jpg","materialType":"basic","side":1,"opacity":1},"skin_down":{"skinColor":16777215},"skin_fore":{"skinColor":16777215},"skin_behind":{"skinColor":16777215},"skin_left":{"skinColor":16777215},"skin_right":{"skinColor":16777215}}},"showSortNub":6}]
载入模型:
{"name":"twaver_idc_jiazi_7","objType":"objmodel","position":{"x":0,"y":0,"z":0},"scale":{"x":1,"y":1,"z":1},"visible":true,"rotation":[{"direction":"x","degree":0}],"filePath":"../../js/msj3D/sourse/models/jiazi/","mtlFileName":"jiazi.mtl","objFileName":"jiazi.obj","mtlIsPublic":false,"showSortNub":7}
2.2、场景创建
搭建场景时需要考虑业务的逻辑关系,什么时候要用,什么时候需要释放,哪些模型属于一个场景,哪些模型属于逻辑打开的场景
//隐藏所有设备
ModelBussiness.prototype.hideAllDevModels = function (timelong, callBackFunc {
var _this = this;
var devModels = _this.getAllDevModels(;
if (devModels && devModels.length > 0 {
WT3DObj.commonFunc.changeObjsOpacity(devModels, 1, 0.01, timelong ? timelong : 1000, function ( {
$.each(devModels, function (_index, _obj {
_obj.visible = false;
};
WT3DObj.commonFunc.changeObjsOpacity(devModels, 0.9, 1, 1, function ( {
if (callBackFunc {
callBackFunc(;
}
};
};
}
}
ModelBussiness.prototype.showAllDevModels = function (timelong, callBackFunc {
var _this = this;
var devModels = _this.getAllDevModels(;
if (devModels && devModels.length > 0 {
$.each(devModels, function (_index, _obj {
_obj.visible = true;
};
WT3DObj.commonFunc.changeObjsOpacity(devModels, 0.5,1, timelong ? timelong : 1000, function ( {
if (callBackFunc {
callBackFunc(;
}
};
}
}
2.3、场景切换
对于大场景切换,通常采用两种方式,一种时改变路由的方式,一种是释放当前资源,加载新资源
初始化对象:
$(function ( { indexPage = new IndexPage(; indexPage.init(; for (var i = 1; i <= 6; i++ { if (window["System0" + i] { window["system0" + i] = new window["System0" + i](; window["system0" + i].init(; } } };
//系统1:室内外消防给水系统
function System01( {
}
//初始化
System01.prototype.init = function ( {
}
//显示
System01.prototype.show = function ( {
console.log("显示室内外消防给水系统";
this.loadVoice(;
this.initUI(;
modelBussiness.loadDevModels(1, function ( {
modelBussiness.hideAllInsideBoxs(;
};
}
System01.prototype.initUI = function ( {
//主功能
this.nav1 = [
{
defaultState: "",
name: "全景视角",
id: "s01_01",
actionStr: "system01.showNavSystem('s01_01'"
},
{
defaultState: "noselect",
name: "剖面视角",
id: "s01_02",
actionStr: "system01.showNavSystem('s01_02'"
},
{
defaultState: "noselect",
name: "管网视角",
id: "s01_03",
actionStr: "system01.showNavSystem('s01_03'"
},
{
defaultState: "noselect",
name: "系统部件",
id: "s01_04",
actionStr: "system01.showNavSystem('s01_04'"
}
]
//子功能
....
/*
消防水泵
消防供水管道
*/
indexPage.showSystemMainNav1(this.nav1;
this.nav1State = "s01_01";
indexPage.showSystemMainNav2(this.nav2[0];
this.nav2State = "";
}
2.4、动画模拟
例如动画列表配置如下:
{ name: "排烟动画", children: [ { name: "发生火情", id: "s07_step01", greatePosition: { camera: { x: 6120.260855819656, y: 1344.6126089802272, z: 10903.874792293966 }, target: { x: 743.2076296596372, y: 901.5537889282617, z: -467.5814262976444 } }, actionStr: "system07.showNav2Action(2,'s07_step01'", action: function (callBack { //剖面图 $.each(system07.allModels, function (_index, _obj { if (_obj.hasoldPosition { _obj.position.y = _obj.hasoldPosition.y ; } _obj.visible = true; }; $.each(system07.foreModels, function (_index, _obj { if (_obj.hasoldPosition { _obj.position.y = _obj.hasoldPosition.y + 10000; } else { _obj.hasoldPosition = { y: _obj.position.y }; _obj.position.y = _obj.hasoldPosition.y + 10000; } _obj.visible = false; }; //发生火情 //do something... } }, { name: "感烟探测器", id: "s07_step02_1", greatePosition: { camera: { x: -1388, y: -781, z: -309 }, target: { x: -2119, y: -1106, z: 873 } }, actionStr: "system07.showNav2Action(2,'s07_step02_1'", action: function (callBack { //do something... } }, { name: "烟感信号发送", id: "s07_step02_2", greatePosition: { camera: { x: -1388, y: -781, z: -309 }, target: { x: -2119, y: -1106, z: 873 } }, actionStr: "system07.showNav2Action(2,'s07_step02_2'", action: function (callBack { //do something... } }, .... { name: "动画结束", id: "s07_step11", greatePosition: { camera: { x: 1737.3999068753242, y: 2415.4782632988126, z: -333.6159257900231 }, target: { x: 1239.420815260611, y: 1011.0884298164777, z: 2597.9725494779173 } }, actionStr: "system07.showNav2Action(2,'s07_step11'", action: function (callBack { //删除船船 //删除所有ani开头的模型 //停止旋转 //缩小挡烟垂壁 } } ] },
执行动画如下:
//执行动画
System07.prototype.doAnimation = function (index {
var _this = this;
this.doAnimationFlag = true;
var actions = system07.nav2[index].children;
if (actions && actions.length > 0 {
function doaction(action_nub {
$("#" + actions[action_nub].id.attr("class", "nav2res navsys";
if (action_nub > 0 {
$("#" + actions[action_nub - 1].id.attr("class", "nav2res navsys noseleced";
}
actions[action_nub].action(function ( {
if (actions[action_nub + 1] && actions[action_nub + 1].action {
doaction(action_nub + 1;
} else {
_this.doAnimationFlag = false;
}
};
}
doaction(0;
}
}
下节课主要介绍3D实现配电站、变电所
技术交流 1203193731@qq.com
交流微信:
如果你有什么要交流的心得 可邮件我
其它相关文章:
webgl(three.js3D光伏,3D太阳能能源,3D智慧光伏、光伏发电、清洁能源三维可视化解决方案——第十六课
如何用webgl(three.js搭建一个3D库房,3D仓库3D码头,3D集装箱,车辆定位,叉车定位可视化孪生系统——第十五课
webgl(three.js实现室内三维定位,3D定位,3D楼宇bim、实时定位三维可视化解决方案——第十四课(定位升级版)
使用three.js(webgl搭建智慧楼宇、设备检测、数字孪生——第十三课
如何用three.js(webgl搭建3D粮仓、3D仓库、3D物联网设备监控-第十二课
如何用webgl(three.js搭建处理3D隧道、3D桥梁、3D物联网设备、3D高速公路、三维隧道桥梁设备监控-第十一课
如何用three.js实现数字孪生、3D工厂、3D工业园区、智慧制造、智慧工业、智慧工厂-第十课
使用webgl(three.js创建3D机房,3D机房微模块详细介绍(升级版二
如何用webgl(three.js搭建一个3D库房-第一课
如何用webgl(three.js搭建一个3D库房,3D密集架,3D档案室,-第二课
使用webgl(three.js搭建一个3D建筑,3D消防模拟——第三课
使用webgl(three.js搭建一个3D智慧园区、3D建筑,3D消防模拟,web版3D,bim管理系统——第四课
如何用webgl(three.js搭建不规则建筑模型,客流量热力图模拟
使用webgl(three.js搭建一个3D智慧园区、3D建筑,3D消防模拟,web版3D,bim管理系统——第四课(炫酷版一)
编程笔记 » 如何使用webgl(three.js)实现3D消防/3D建筑消防大楼/消防数字孪生/消防可视化解决方案——第十八课(一)