1430 lines
		
	
	
		
			42 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			1430 lines
		
	
	
		
			42 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
let fragProxy;
 | 
						||
var targetFloorZ;
 | 
						||
var elevatorSpeed;
 | 
						||
var selector = "#forgeViewer";
 | 
						||
var myDataList;
 | 
						||
var lightDataList;
 | 
						||
var lightList = []; //燈光清單
 | 
						||
var levels; //剖面用
 | 
						||
var lowerIdx = 0; //剖面的下方樓層
 | 
						||
var upperIdx = 0; //剖面的上方樓層
 | 
						||
 | 
						||
function getAllLeafComponents(viewer, callback) {
 | 
						||
  var cbCount = 0;
 | 
						||
  var tree;
 | 
						||
  var jsData = [];
 | 
						||
  if(typeof viewer == 'undefined'){
 | 
						||
	return;
 | 
						||
  }
 | 
						||
  function getLeafComponentsRec(current, parent) {
 | 
						||
    cbCount++;
 | 
						||
    if (tree.getChildCount(current) != 0) {
 | 
						||
      tree.enumNodeChildren(
 | 
						||
        current,
 | 
						||
        function (children) {
 | 
						||
          getLeafComponentsRec(children, current);
 | 
						||
        },
 | 
						||
        false
 | 
						||
      );
 | 
						||
    }
 | 
						||
    var nodeName = viewer.model.getInstanceTree().getNodeName(current);
 | 
						||
    jsData.push({ id: current, parent: parent, text: nodeName });
 | 
						||
 | 
						||
    if (--cbCount == 0) callback(jsData);
 | 
						||
  }
 | 
						||
  viewer.getObjectTree(function (objectTree) {
 | 
						||
    tree = objectTree;
 | 
						||
    var rootId = tree.getRootId();
 | 
						||
    var nodeName = viewer.model.getInstanceTree().getNodeName(rootId);
 | 
						||
    jsData.push({ id: rootId, parent: "#", text: nodeName });
 | 
						||
    var allLeafComponents = getLeafComponentsRec(rootId, "#");
 | 
						||
  });
 | 
						||
}
 | 
						||
 | 
						||
class elevator3D {
 | 
						||
  constructor(option = {}) {
 | 
						||
    this.ele = option.element;
 | 
						||
    viewer = option.viewer;
 | 
						||
    this.nodeId = option.nodeId;
 | 
						||
    this.speed = option.speed ?? 0.07;
 | 
						||
    this.tagValue = option.tagValue ?? "";
 | 
						||
    this.fragProxys = [];
 | 
						||
    this.fragProxy = null;
 | 
						||
    this.initCallback = option.inited ?? null;
 | 
						||
    this.movStatus = 0;
 | 
						||
    this.targetFloorZ = 0;
 | 
						||
    this.floorHeight = option.floorHeight ?? [{}];
 | 
						||
    this.sensorObjs = option.sensorObjs ?? null;
 | 
						||
    this.init();
 | 
						||
  }
 | 
						||
 | 
						||
  // 定義遍歷模型中所有片段的異步函數
 | 
						||
  enumFragments = function (nodeId) {
 | 
						||
    return new Promise((resolve, reject) => {
 | 
						||
      // 定義結果陣列
 | 
						||
      let result = [];
 | 
						||
      // 使用 InstanceTree.enumNodeFragments 遍歷模型中的所有片段
 | 
						||
      viewer?.model?.getData().instanceTree.enumNodeFragments(
 | 
						||
        nodeId,
 | 
						||
        (fragId) => {
 | 
						||
          // 將遍歷到的片段 ID 添加到結果陣列中
 | 
						||
          result.push(fragId);
 | 
						||
        },
 | 
						||
        (nodeId) => {
 | 
						||
          // 如果遍歷到的節點是葉節點,则返回 true,表示繼續遍歷該節點的片段
 | 
						||
          return model.getData().instanceTree.isLeaf(nodeId);
 | 
						||
        }
 | 
						||
      );
 | 
						||
      // 将结果陣列作為参数调用 resolve 函数
 | 
						||
      resolve(result);
 | 
						||
    });
 | 
						||
  };
 | 
						||
 | 
						||
  // 設置樹狀結構中片段
 | 
						||
  setTreeFrag = async function (callback) {
 | 
						||
    let tree = viewer?.model?.getData().instanceTree;
 | 
						||
    if (!tree) {
 | 
						||
      return;
 | 
						||
    }
 | 
						||
 | 
						||
    let nodeId = this.nodeId;
 | 
						||
 | 
						||
    if (nodeId) {
 | 
						||
      // 刪除 fragmentProxys 中與當前 nodeId 相同的項目
 | 
						||
      this.fragProxys
 | 
						||
        .filter((x) => x.nodeId == nodeId)
 | 
						||
        .forEach((x) => {
 | 
						||
          let idx = this.fragProxys.indexOf(x);
 | 
						||
          this.fragProxys.splice(idx, 1);
 | 
						||
        });
 | 
						||
 | 
						||
      // 遍歷當前 nodeId 中的所有片段
 | 
						||
      let fragments = await this.enumFragments(nodeId);
 | 
						||
 | 
						||
      fragments.forEach((frag) => {
 | 
						||
        // 取得當前片段的 FragmentProxy 物件
 | 
						||
        let fragProxy = viewer.impl.getFragmentProxy(viewer.model, frag);
 | 
						||
        // 將當前片段的信息添加到 fragProxys 陣列中
 | 
						||
        this.fragProxys.push({ nodeId: nodeId, fragId: frag, frag: fragProxy });
 | 
						||
        // 取得當前片段的動畫變換矩陣
 | 
						||
        fragProxy.getAnimTransform();
 | 
						||
        // 定義當前片段的位置向量
 | 
						||
        let fragPosition = new THREE.Vector3(0, 0, 0); // 一樓0 二樓15 三樓 26
 | 
						||
        // 設置當前片段的位置
 | 
						||
        fragProxy.position = fragPosition;
 | 
						||
 | 
						||
        // 更新當前片段的動畫變換矩陣
 | 
						||
        fragProxy.updateAnimTransform();
 | 
						||
      });
 | 
						||
    }
 | 
						||
    // 通知檢視器更新場景
 | 
						||
    viewer.impl.sceneUpdated(true);
 | 
						||
 | 
						||
    if (!$(this.ele)[0]) {
 | 
						||
      callback ? callback() : "";
 | 
						||
      return;
 | 
						||
    }
 | 
						||
 | 
						||
    if (typeof $(this.ele)[0]._elevator3D == "undefined") {
 | 
						||
      $(this.ele)[0]._elevator3D = [];
 | 
						||
    }
 | 
						||
 | 
						||
    $(this.ele)[0]._elevator3D.push({ nodeId: nodeId, obj: this });
 | 
						||
 | 
						||
    this.initCallback ? this.initCallback() : "";
 | 
						||
    callback ? callback() : "";
 | 
						||
  };
 | 
						||
 | 
						||
  init = function (callback = null) {
 | 
						||
    this.setTreeFrag(callback);
 | 
						||
  };
 | 
						||
 | 
						||
  setElevatorFloor = function (floor) {
 | 
						||
    this.targetFloorZ =
 | 
						||
      this.floorHeight.filter((x) => x.floor == floor)[0]?.height ?? 0;
 | 
						||
  };
 | 
						||
 | 
						||
  setElevatorSpeed = function (speed) {
 | 
						||
    //0.01 ~ 1
 | 
						||
    this.speed = speed;
 | 
						||
  };
 | 
						||
 | 
						||
  // 電梯移動
 | 
						||
  movElevator = async function () {
 | 
						||
    let nodeId = this.nodeId;
 | 
						||
    let fragProxyZ = 0; // 定義 fragProxyZ 變量,用於存儲當前片段的 z 坐標
 | 
						||
 | 
						||
    // 取得當前 nodeId 的片段代理對象
 | 
						||
    let fragProxy = this.fragProxys.filter((x) => x.nodeId == nodeId)[0]?.frag;
 | 
						||
    if (!fragProxy) {
 | 
						||
      return;
 | 
						||
    }
 | 
						||
 | 
						||
    changeColor(nodeId);
 | 
						||
 | 
						||
    if (
 | 
						||
      (this.movStatus == 2 &&
 | 
						||
        fragProxy.position.z.roundDecimal(2) <
 | 
						||
        this.targetFloorZ.roundDecimal(2)) ||
 | 
						||
      (this.movStatus == 1 &&
 | 
						||
        fragProxy.position.z.roundDecimal(2) >
 | 
						||
        this.targetFloorZ.roundDecimal(2)) ||
 | 
						||
      fragProxy.position.z.roundDecimal(2) == this.targetFloorZ.roundDecimal(2)
 | 
						||
    ) {
 | 
						||
      stoped(this);
 | 
						||
    }
 | 
						||
 | 
						||
    if (
 | 
						||
      fragProxy.position.z.roundDecimal(2) > this.targetFloorZ.roundDecimal(2)
 | 
						||
    ) {
 | 
						||
      this.movStatus = 2;
 | 
						||
    } else if (
 | 
						||
      fragProxy.position.z.roundDecimal(2) < this.targetFloorZ.roundDecimal(2)
 | 
						||
    ) {
 | 
						||
      this.movStatus = 1;
 | 
						||
    }
 | 
						||
 | 
						||
    // 遍歷當前 nodeId 中的所有片段
 | 
						||
    let fragments = await this.enumFragments(nodeId);
 | 
						||
 | 
						||
    fragments.forEach((frag) => {
 | 
						||
      let fragProxy = viewer.impl.getFragmentProxy(viewer.model, frag);
 | 
						||
      fragProxy.getAnimTransform();
 | 
						||
 | 
						||
      if (this.movStatus == 2) {
 | 
						||
        fragProxy.position.z -= this.speed;
 | 
						||
      } else if (this.movStatus == 1) {
 | 
						||
        fragProxy.position.z += this.speed;
 | 
						||
      }
 | 
						||
      let tarFrag = this.fragProxys.filter(
 | 
						||
        (x) => x.nodeId == nodeId && x.fragId == frag
 | 
						||
      )[0];
 | 
						||
      if (tarFrag) {
 | 
						||
        tarFrag.frag.position.z = fragProxy.position.z;
 | 
						||
      }
 | 
						||
      fragProxyZ = fragProxy.position.z;
 | 
						||
 | 
						||
      // 改變感測器熱點位置
 | 
						||
      this.sensorObjs && this.sensorObjs[0]?.changePos(nodeId + 2, fragProxyZ);
 | 
						||
      // if(nodeId === 15200) {
 | 
						||
      // }
 | 
						||
      fragProxy.updateAnimTransform();
 | 
						||
    });
 | 
						||
 | 
						||
    viewer.impl.sceneUpdated(true);
 | 
						||
 | 
						||
    let movElevator = $(this.ele)[0]
 | 
						||
      ._elevator3D.filter((x) => x.nodeId == this.nodeId)[0]
 | 
						||
      ?.obj.movElevator.bind(this);
 | 
						||
 | 
						||
    if (this.movStatus == 2) {
 | 
						||
      if (fragProxyZ >= this.targetFloorZ) {
 | 
						||
        requestAnimationFrame(() => {
 | 
						||
          movElevator();
 | 
						||
        });
 | 
						||
      } else {
 | 
						||
        stoped(this);
 | 
						||
      }
 | 
						||
    } else if (this.movStatus == 1) {
 | 
						||
      if (fragProxyZ <= this.targetFloorZ) {
 | 
						||
        requestAnimationFrame(() => {
 | 
						||
          movElevator();
 | 
						||
        });
 | 
						||
      } else {
 | 
						||
        stoped(this);
 | 
						||
      }
 | 
						||
    } else {
 | 
						||
      stoped(this);
 | 
						||
    }
 | 
						||
 | 
						||
    function stoped(obj) {
 | 
						||
      obj.movStatus = 0;
 | 
						||
      hideColor(nodeId);
 | 
						||
      return;
 | 
						||
    }
 | 
						||
  };
 | 
						||
}
 | 
						||
 | 
						||
// 輔助函數,使用 Promise 封裝 viewer.getProperties 函數
 | 
						||
function viewerGetProperties(dbIds, attributeName = null) {
 | 
						||
  // 在這裡,我們使用 viewer.getProperties 函數的成功回調函數作為 resolve 函數,
 | 
						||
  // 並使用 viewer.getProperties 函數的失敗回調函數作為 resolve 函數的參數
 | 
						||
  // 這樣,當 viewer.getProperties 函數成功時,Promise 會傳回 properties 物件;
 | 
						||
  // 當 viewer.getProperties 函數失敗時,Promise 會傳回 null。
 | 
						||
  return new Promise((resolve, reject) => {
 | 
						||
    let option = {};
 | 
						||
    if (attributeName != null) {
 | 
						||
      option.propFilter = [attributeName]; // 限制只返回指定的屬性
 | 
						||
    }
 | 
						||
    viewer.model.getBulkProperties2(
 | 
						||
      dbIds,
 | 
						||
      option, // 取得指定元素的屬性信息
 | 
						||
      function (elements) {
 | 
						||
        resolve(elements); // 成功時傳回 elements 物件
 | 
						||
      }
 | 
						||
    );
 | 
						||
  });
 | 
						||
}
 | 
						||
 | 
						||
// 輔助函數,使用 Promise 封裝 viewer.search 函數
 | 
						||
async function getNodeIdBySearch(text) {
 | 
						||
  return new Promise((resolve, reject) => {
 | 
						||
    viewer.search(
 | 
						||
      text,
 | 
						||
      (e) => {
 | 
						||
        // 在 3D 模型中搜索具有指定文本的元素
 | 
						||
        resolve(e); // 成功時傳回搜索結果
 | 
						||
      },
 | 
						||
      (e) => {
 | 
						||
        resolve(null); // 失敗時傳回 null
 | 
						||
      }
 | 
						||
    );
 | 
						||
  });
 | 
						||
}
 | 
						||
 | 
						||
// 主函數 - 透過 model 全部 node 取得特定 nodeId
 | 
						||
async function getNodeIdByDbIds(checkValue = [], callback = null) {
 | 
						||
  let evelMap = new Map();
 | 
						||
  let hasElement = false; // 設置是否有 【tag_id】的node
 | 
						||
  let targetNodeIds = await getNodeIdBySearch(checkValue);
 | 
						||
  let elements = await viewerGetProperties(targetNodeIds, "【tag_id】");
 | 
						||
 | 
						||
  // 從 elements 中篩選出包含 【tag_id】 屬性的元素
 | 
						||
  elements = elements.filter(
 | 
						||
    (x) =>
 | 
						||
      x.properties.findIndex(
 | 
						||
        (y) =>
 | 
						||
          y.displayName == "【tag_id】" &&
 | 
						||
          checkValue.indexOf(y.displayValue) != -1
 | 
						||
      ) != -1
 | 
						||
  );
 | 
						||
 | 
						||
  if (elements) {
 | 
						||
    hasElement = true;
 | 
						||
  }
 | 
						||
 | 
						||
  for (var e of elements) {
 | 
						||
    let name = e.properties[0].displayValue; // 獲取元素的 【tag_id】 屬性
 | 
						||
    let chiElements = null;
 | 
						||
 | 
						||
    // 獲取 node 元素的子元素信息
 | 
						||
    targetNodeIds = await viewerGetProperties([e.dbId], "child");
 | 
						||
    // 二次篩選
 | 
						||
    chiElements = targetNodeIds.filter(
 | 
						||
      (x) => x.properties.findIndex((y) => y.displayName == "child") != -1
 | 
						||
    );
 | 
						||
 | 
						||
    // 獲取子元素的 nodeId
 | 
						||
    targetNodeIds = chiElements.map((x) => x.properties[0].displayValue);
 | 
						||
    // 獲取子元素的屬性信息
 | 
						||
    chiElements = await viewerGetProperties(targetNodeIds);
 | 
						||
 | 
						||
    // 將 node 元素的 【tag_id】 屬性和子元素的 nodeId 加入 Map 中
 | 
						||
    evelMap.set(name, chiElements[0]?.dbId);
 | 
						||
  }
 | 
						||
 | 
						||
  if (!hasElement) {
 | 
						||
    callback ? callback([]) : "";
 | 
						||
    return [];
 | 
						||
  }
 | 
						||
 | 
						||
  callback ? callback(evelMap) : "";
 | 
						||
  return evelMap;
 | 
						||
}
 | 
						||
 | 
						||
function setElevatorFloor(floor) {
 | 
						||
  if (floor == 0) targetFloorZ = 0;
 | 
						||
  else if (floor == 1) targetFloorZ = 15;
 | 
						||
  else if (floor == 2) targetFloorZ = 26;
 | 
						||
}
 | 
						||
 | 
						||
function setElevatorSpeed(speed) {
 | 
						||
  //0.01 ~ 1
 | 
						||
  elevatorSpeed = speed;
 | 
						||
}
 | 
						||
 | 
						||
function movElevator() {
 | 
						||
  let tree = viewer.model?.getData().instanceTree;
 | 
						||
  let nodeId = 10952;
 | 
						||
  let fragProxyZ = 0;
 | 
						||
  var movStatus = 0; // 0=no 1=up 2=down
 | 
						||
 | 
						||
  if (fragProxy.position.z > targetFloorZ) {
 | 
						||
    movStatus = 2;
 | 
						||
  } else if (fragProxy.position.z < targetFloorZ) {
 | 
						||
    movStatus = 1;
 | 
						||
  }
 | 
						||
 | 
						||
  if (movStatus == 0) {
 | 
						||
    return;
 | 
						||
  }
 | 
						||
 | 
						||
  tree.enumNodeFragments(nodeId, function (frag) {
 | 
						||
    fragProxy = viewer.impl.getFragmentProxy(viewer.model, frag);
 | 
						||
    fragProxy.getAnimTransform();
 | 
						||
    //let fragPosition = new THREE.Vector3(0, 0, 15);// 一樓0 二樓15 三樓 26
 | 
						||
    if (movStatus == 2) {
 | 
						||
      fragProxy.position.z -= elevatorSpeed;
 | 
						||
    } else if (movStatus == 1) {
 | 
						||
      fragProxy.position.z += elevatorSpeed;
 | 
						||
    }
 | 
						||
 | 
						||
    fragProxyZ = fragProxy.position.z;
 | 
						||
    fragProxy.updateAnimTransform();
 | 
						||
  });
 | 
						||
  viewer.impl.sceneUpdated(true);
 | 
						||
 | 
						||
  if (movStatus == 2) {
 | 
						||
    if (fragProxyZ >= targetFloorZ) {
 | 
						||
      requestAnimationFrame(movElevator);
 | 
						||
    }
 | 
						||
  } else if (movStatus == 1) {
 | 
						||
    if (fragProxyZ <= targetFloorZ) {
 | 
						||
      requestAnimationFrame(movElevator);
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  //let fragPosition = new THREE.Vector3(position);// 一樓0 二樓15 三樓 26
 | 
						||
 | 
						||
  //fragProxy.position = fragPosition;
 | 
						||
 | 
						||
  //fragProxy.updateAnimTransform();
 | 
						||
 | 
						||
  //viewer.impl.sceneUpdated(true);
 | 
						||
}
 | 
						||
 | 
						||
function getAllDbIds(viewer) {
 | 
						||
  var instanceTree = viewer.model?.getData().instanceTree;
 | 
						||
 | 
						||
  var allDbIdsStr = Object.keys(instanceTree.nodeAccess.dbIdToIndex);
 | 
						||
 | 
						||
  return allDbIdsStr.map(function (id) {
 | 
						||
    return parseInt(id);
 | 
						||
  });
 | 
						||
}
 | 
						||
 | 
						||
//------------------ 熱圖 -------------------------------
 | 
						||
class ADHeatMaps {
 | 
						||
  constructor(option = {}) {
 | 
						||
    this.devices = option.devices ?? [];
 | 
						||
    this.checkNodeString = "【ROOM】";
 | 
						||
    this.tempVals = [];
 | 
						||
    this.roomDbIds = []; //房間 dbId
 | 
						||
    this.model = null;
 | 
						||
    this.dataVizExtn = null;
 | 
						||
    this.shadingData = null;
 | 
						||
    this.onComplete = option.onComplete ?? null;
 | 
						||
    this.init();
 | 
						||
  }
 | 
						||
 | 
						||
  init = async function () {
 | 
						||
    // 儲存 Viewer 的模型
 | 
						||
    this.model = viewer.model;
 | 
						||
    this.addHeatMaps();
 | 
						||
  };
 | 
						||
 | 
						||
  async addHeatMaps() {
 | 
						||
    // 載入 Autodesk Viewer 的資料視覺化擴充功能
 | 
						||
    const dataVizExtn = await viewer.loadExtension(
 | 
						||
      "Autodesk.DataVisualization"
 | 
						||
    );
 | 
						||
    const {
 | 
						||
      SurfaceShadingData,
 | 
						||
      SurfaceShadingPoint,
 | 
						||
      SurfaceShadingNode,
 | 
						||
      SurfaceShadingGroup,
 | 
						||
    } = Autodesk.DataVisualization.Core;
 | 
						||
    // test
 | 
						||
    const shadingGroup = new SurfaceShadingGroup("iot-heatmap");
 | 
						||
    const rooms = new Map();
 | 
						||
 | 
						||
    for (const { id, roomDbId, forge_dbid, position, sensorTypes } of this
 | 
						||
      .devices) {
 | 
						||
      if (!id || roomDbId == -1) {
 | 
						||
        continue;
 | 
						||
      }
 | 
						||
 | 
						||
      if (!rooms.has(roomDbId)) {
 | 
						||
        const room = new SurfaceShadingNode(id, roomDbId);
 | 
						||
        shadingGroup.addChild(room);
 | 
						||
        rooms.set(roomDbId, room);
 | 
						||
      }
 | 
						||
      let room = rooms.get(roomDbId);
 | 
						||
      room.addPoint(new SurfaceShadingPoint(id, position, sensorTypes));
 | 
						||
    }
 | 
						||
 | 
						||
    const shadingData = new SurfaceShadingData();
 | 
						||
    shadingData.addChild(shadingGroup);
 | 
						||
 | 
						||
    shadingData.initialize(this.model);
 | 
						||
    this.shadingData = shadingData;
 | 
						||
 | 
						||
    if (pageAct.sysSubTag == "L1") {
 | 
						||
      await dataVizExtn.setupSurfaceShading(this.model, shadingData, {
 | 
						||
        type: "PlanarHeatmap",
 | 
						||
        placementPosition: 0.7,
 | 
						||
        confidence: 240.0,
 | 
						||
      });
 | 
						||
      dataVizExtn.registerSurfaceShadingColors(
 | 
						||
        "temperature",
 | 
						||
        // [0x000000, 0xffd524]
 | 
						||
        [0xff0000, 0x0000ff]
 | 
						||
      );
 | 
						||
    } else {
 | 
						||
      await dataVizExtn.setupSurfaceShading(this.model, shadingData);
 | 
						||
      dataVizExtn.registerSurfaceShadingColors(
 | 
						||
        "temperature",
 | 
						||
        [0x0000ff, 0x00ff00, 0xffff00, 0xff0000]
 | 
						||
      );
 | 
						||
    }
 | 
						||
    dataVizExtn.renderSurfaceShading(
 | 
						||
      "iot-heatmap",
 | 
						||
      "temperature",
 | 
						||
      this.getSensorValue.bind(this)
 | 
						||
    );
 | 
						||
 | 
						||
    this.onComplete ? this.onComplete() : "";
 | 
						||
  }
 | 
						||
 | 
						||
  getSensorValue = function (device, sensorType) {
 | 
						||
    let dev = this.devices.filter((x) => x.id == device.id)[0];
 | 
						||
    return dev._temp / 40;
 | 
						||
  };
 | 
						||
 | 
						||
  // 改變溫度
 | 
						||
  changeTemp = async function (devId, temp) {
 | 
						||
    const dataVizExtn = await viewer.loadExtension(
 | 
						||
      "Autodesk.DataVisualization"
 | 
						||
    );
 | 
						||
    //   console.log("changeTemp",devId, temp)
 | 
						||
    this.tempVal = temp;
 | 
						||
    // 透過 device id 取得 roomDbId
 | 
						||
    this.devices.forEach((dev) => {
 | 
						||
      if (devId == dev.id) {
 | 
						||
        dev._temp = temp;
 | 
						||
      }
 | 
						||
    });
 | 
						||
 | 
						||
    if (!this.shadingData) {
 | 
						||
      await this.addHeatMaps();
 | 
						||
      dataVizExtn.renderSurfaceShading(
 | 
						||
        "iot-heatmap",
 | 
						||
        "temperature",
 | 
						||
        this.getSensorValue.bind(this)
 | 
						||
      );
 | 
						||
    } else {
 | 
						||
      dataVizExtn.updateSurfaceShading(this.getSensorValue.bind(this));
 | 
						||
    }
 | 
						||
    // $.each(this.roomDbIds, async (idx, rDbid) => {
 | 
						||
    //     this.dataVizExtn.renderSurfaceShading("RoomPanel" + rDbid, "temperature", this.getSensorValue.bind(this));
 | 
						||
    // })
 | 
						||
 | 
						||
    //if (rDbid != null) {
 | 
						||
    //    // 取得新的溫度值
 | 
						||
    //    let getSensorValue = (device, sensorType) => {
 | 
						||
    //        return this.tempVal / 40;
 | 
						||
    //    }
 | 
						||
    //    // 對 "Room Panel" 做表面顏色的渲染,並使用新的溫度值
 | 
						||
    //    this.dataVizExtn.renderSurfaceShading("RoomPanel" + rDbid, "temperature", getSensorValue);
 | 
						||
    //}
 | 
						||
  };
 | 
						||
 | 
						||
  // 改變設備顏色
 | 
						||
  changeErrorDevice() {
 | 
						||
    const ErrorDeviceList = new ErrorDevice({
 | 
						||
      errorDevices: alarmDbIdList.map((d) => ({
 | 
						||
        ...d,
 | 
						||
        roomDbId: !isNaN(parseInt(x.room_dbid)) ? parseInt(x.room_dbid) : -1,
 | 
						||
        id: d.device_number,
 | 
						||
        position: isJSON(d.device_coordinate_3d)
 | 
						||
          ? JSON.parse(d.device_coordinate_3d)
 | 
						||
          : {}, // x: 0, y: 25, z: -2.5      (3.35, -4.81, 12.88
 | 
						||
        sensorTypes: ["error", "temperature", "humidity", "CO2"],
 | 
						||
        forge_dbid: parseInt(d.forge_dbid),
 | 
						||
      })),
 | 
						||
    });
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
//全部物件 透明度: 輸入0:透明;輸入1:不透明
 | 
						||
function setTransparentBuilding(transparent, filDbids = []) {
 | 
						||
  for (var i = 0; i < allDbIdsStr.length; i++) {
 | 
						||
    setTransparency(parseInt(allDbIdsStr[i]), transparent);
 | 
						||
  }
 | 
						||
 | 
						||
  for (var i = 0; i < filDbids.length; i++) {
 | 
						||
    setTransparency(parseInt(filDbids[i]), transparent == 0 ? 1 : 0);
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
//設定模型 透明度
 | 
						||
function setTransparency(nodeId, opacity) {
 | 
						||
  var model = viewer.model;
 | 
						||
  var fragList = viewer.model.getFragmentList();
 | 
						||
  var fragIds = [];
 | 
						||
 | 
						||
  model.getData().instanceTree.enumNodeFragments(
 | 
						||
    nodeId,
 | 
						||
    (fragId) => {
 | 
						||
      // 將遍歷到的片段 ID 添加到結果陣列中
 | 
						||
      fragIds.push(fragId);
 | 
						||
    },
 | 
						||
    (nodeId) => {
 | 
						||
      // 如果遍歷到的節點是葉節點,则返回 true,表示繼續遍歷該節點的片段
 | 
						||
      return model.getData().instanceTree.isLeaf(nodeId);
 | 
						||
    }
 | 
						||
  );
 | 
						||
 | 
						||
  fragIds.forEach((fragId) => {
 | 
						||
    //获取材质
 | 
						||
    var material = fragList.getMaterial(fragId);
 | 
						||
 | 
						||
    if (material) {
 | 
						||
      //设置透明度
 | 
						||
      material.opacity = opacity; //0.5;
 | 
						||
      material.transparent = true;
 | 
						||
      //标记更新
 | 
						||
      material.needsUpdate = true;
 | 
						||
    }
 | 
						||
  });
 | 
						||
 | 
						||
  //更新viewer
 | 
						||
  viewer.impl.invalidate(true, true, true);
 | 
						||
}
 | 
						||
 | 
						||
function changeColor(nodeId, color = [0, 255, 0, 1]) {
 | 
						||
  //電梯變綠色
 | 
						||
  let model = viewer.model;
 | 
						||
  let fragList = viewer.model.getFragmentList();
 | 
						||
  let instanceTree = viewer.model?.getData().instanceTree;
 | 
						||
  color = color.map((x, i) => (i < 3 ? x / 255 : x));
 | 
						||
  color = new THREE.Vector4().fromArray(color);
 | 
						||
  model.getData().instanceTree.enumNodeFragments(
 | 
						||
    nodeId,
 | 
						||
    (fragId) => {
 | 
						||
      // 將遍歷到的片段 ID 添加到結果陣列中
 | 
						||
      let material = fragList.getMaterial(fragId);
 | 
						||
 | 
						||
      if (material) {
 | 
						||
        //设置透明度
 | 
						||
        material.opacity = 1; //0.5;
 | 
						||
        material.transparent = true;
 | 
						||
        //标记更新
 | 
						||
        material.needsUpdate = true;
 | 
						||
      }
 | 
						||
    },
 | 
						||
    (nodeId) => {
 | 
						||
      // 如果遍歷到的節點是葉節點,则返回 true,表示繼續遍歷該節點的片段
 | 
						||
      return model.getData().instanceTree.isLeaf(nodeId);
 | 
						||
    }
 | 
						||
  );
 | 
						||
  instanceTree.enumNodeChildren(
 | 
						||
    nodeId,
 | 
						||
    function (chiNodeId) {
 | 
						||
      viewer.setThemingColor(chiNodeId, color);
 | 
						||
    },
 | 
						||
    (chiNodeId) => {
 | 
						||
      // 如果遍歷到的節點是葉節點,则返回 true,表示繼續遍歷該節點的片段
 | 
						||
      return model.getData().instanceTree.isLeaf(chiNodeId);
 | 
						||
    }
 | 
						||
  );
 | 
						||
}
 | 
						||
 | 
						||
function hideColor(nodeId) {
 | 
						||
  //顏色改成透明
 | 
						||
  let instanceTree = viewer.model?.getData().instanceTree;
 | 
						||
  instanceTree.enumNodeChildren(
 | 
						||
    nodeId,
 | 
						||
    function (chiNodeId) {
 | 
						||
      var color = new THREE.Vector4(0, 1, 0, 0);
 | 
						||
      viewer.setThemingColor(chiNodeId, color);
 | 
						||
    },
 | 
						||
    true
 | 
						||
  );
 | 
						||
}
 | 
						||
 | 
						||
//紀錄燈具座標
 | 
						||
async function getLightData(data) {
 | 
						||
  lightList = [];
 | 
						||
  lightDataList = data;
 | 
						||
}
 | 
						||
 | 
						||
async function testNewLight(dataList) {
 | 
						||
  // changeColorForHotspot;
 | 
						||
}
 | 
						||
 | 
						||
//------------------- 加入熱點 -----------------
 | 
						||
async function addHotPoint(data) {
 | 
						||
  // console.log("addHotPoint", data)
 | 
						||
  if (!pageAct.sysSubTag && !pageAct.sysMainTag) {
 | 
						||
    return;
 | 
						||
  }
 | 
						||
  var viewer = data.target ? data.target : data;
 | 
						||
  const dataVizExtn = await viewer.loadExtension("Autodesk.DataVisualization");
 | 
						||
  const DataVizCore = Autodesk.DataVisualization.Core;
 | 
						||
  const viewableType = Autodesk.DataVisualization.Core.ViewableType.SPRITE; //DataVizCore.ViewableType.SPRITE;
 | 
						||
  let spriteColor = null;
 | 
						||
  let url = "";
 | 
						||
  const dbIdStart = 10;
 | 
						||
  const dbIdEnd = 19;
 | 
						||
  spriteColor = new THREE.Color(0xffffff);
 | 
						||
  url = "/file/img/forge/sensor_circle.svg";
 | 
						||
  if (location.href.indexOf("localhost:5966") != -1) {
 | 
						||
    url = "/img/forge/sensor_circle.svg";
 | 
						||
  }
 | 
						||
 | 
						||
  const style = new DataVizCore.ViewableStyle(viewableType, spriteColor, url);
 | 
						||
 | 
						||
  if (
 | 
						||
    lightDataList != undefined &&
 | 
						||
    lightDataList != null &&
 | 
						||
    lightDataList.length > 0
 | 
						||
  ) {
 | 
						||
    testNewLight(lightDataList);
 | 
						||
  }
 | 
						||
 | 
						||
  //熱點 點擊事件註冊
 | 
						||
  viewer.addEventListener(DataVizCore.MOUSE_CLICK, onSpriteClicked); // SPRITE_SELECTED
 | 
						||
  // viewer.addEventListener(DataVizCore.MOUSE_CLICK, onSpriteClickedOut);
 | 
						||
  //viewer.addEventListener(
 | 
						||
  //    Autodesk.Viewing.SELECTION_CHANGED_EVENT,
 | 
						||
  //    onSelectionChange
 | 
						||
  //);
 | 
						||
 | 
						||
  const viewableData = new DataVizCore.ViewableData();
 | 
						||
  viewableData.spriteSize = 24; // Sprites as points of size 24 x 24 pixels
 | 
						||
  myDataList.forEach((myData, index) => {
 | 
						||
    const dbId = 10 + index;
 | 
						||
    const myPosition = myData.position;
 | 
						||
    const viewable = new DataVizCore.SpriteViewable(myPosition, style, dbId);
 | 
						||
    myData._dbId = dbId;
 | 
						||
    viewableData.addViewable(viewable);
 | 
						||
  });
 | 
						||
  await viewableData.finish();
 | 
						||
  dataVizExtn.addViewables(viewableData);
 | 
						||
  // console.log(dataVizExtn)
 | 
						||
  $(selector).trigger("autodesk:complete:sprite", { myDataList });
 | 
						||
 | 
						||
  //---------------- 熱點點擊事件 --------------------
 | 
						||
  function onSpriteClicked(event) {
 | 
						||
    event.hasStopped = true;
 | 
						||
    if (event != undefined && event != null) {
 | 
						||
      if (event.dbId >= dbIdStart) {
 | 
						||
        //event.dbId > 0 && event.dbId < 19
 | 
						||
        // console.log(`Sprite clicked: ${event.dbId}`);
 | 
						||
 | 
						||
 | 
						||
        for (let i = dbIdStart; i <= myDataList.length + 10; i++) {
 | 
						||
          !alarmDbIdList.some(({ spriteDbid }) => spriteDbid == i) &&
 | 
						||
            pageAct.sysSubTag !== "L1" &&
 | 
						||
            changeColorForHotspot(i);
 | 
						||
          changeScaleForHotspot(i, true);
 | 
						||
        }
 | 
						||
 | 
						||
        let myData = myDataList.filter((x) => x._dbId == event.dbId)[0];
 | 
						||
        if (myData && pageAct.sysSubTag == "L1" && pageAct.floTag == null) {
 | 
						||
          getLTNode(myData);
 | 
						||
        }
 | 
						||
        if (
 | 
						||
          lightList != undefined &&
 | 
						||
          lightList != null &&
 | 
						||
          lightList.length > 0
 | 
						||
        ) {
 | 
						||
          //setLightOpenOrClose(false, myData.device_guid);//關燈測試
 | 
						||
          //setLightValues(myData.device_guid, 20, 0x00ff00);//更改燈光顏色和強度的測試
 | 
						||
          moveViewToDevice(myData.forge_dbid); //移動視角至該設備
 | 
						||
        }
 | 
						||
        $(selector).trigger("autodesk:click:sprite", { event, myData });
 | 
						||
      } else {
 | 
						||
        !alarmDbIdList.some(({ spriteDbid }) => spriteDbid == event.dbId) &&
 | 
						||
          pageAct.sysSubTag !== "L1" &&
 | 
						||
          changeColorForHotspot(event.dbId, null);
 | 
						||
        changeScaleForHotspot(event.dbId, false);
 | 
						||
        $(selector).trigger("autodesk:clickOut:sprite", { event });
 | 
						||
      }
 | 
						||
 | 
						||
      if (event.clickInfo != null) {
 | 
						||
        //document.getElementById('deviceName').innerHTML = viewer.model.getInstanceTree().getNodeName(event.clickInfo.dbId);
 | 
						||
        //document.getElementById('deviceDbid').innerHTML = event.clickInfo.dbId;
 | 
						||
        //document.getElementById('devicePosition').innerHTML = "(" + (event.clickInfo.point.x).toFixed(2) + ", " + (event.clickInfo.point.y).toFixed(2) + ", " + (event.clickInfo.point.z).toFixed(2) + ")";
 | 
						||
        // console.log(`event>>  dbId: ${event.clickInfo.dbId}, id: ${event.clickInfo.object.id}, position.x: ${event.clickInfo.point.x}, y: ${event.clickInfo.point.y}, z: ${event.clickInfo.point.z}, name: ${viewer.model.getInstanceTree().getNodeName(event.clickInfo.dbId)}`);
 | 
						||
      }
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  //   function onSpriteClickedOut(event){
 | 
						||
  //     event.hasStopped = true;
 | 
						||
  //     if (event != undefined && event != null) {
 | 
						||
  //         if (event.dbId >= dbIdStart) {
 | 
						||
  //           for (let i = dbIdStart; i <= myDataList.length + 10; i++) {
 | 
						||
  //             changeColorForHotspot(event.dbId);
 | 
						||
  //             changeScaleForHotspot(event.dbId, false);
 | 
						||
  //           }
 | 
						||
  //           let myData = myDataList.filter(x => x._dbId == event.dbId)[0];
 | 
						||
  //             debugger
 | 
						||
  //             $(selector).trigger("autodesk:clickOut:sprite", { event });
 | 
						||
  //         }
 | 
						||
  //     }
 | 
						||
  // }
 | 
						||
 | 
						||
  //function onSelectionChange(event) {
 | 
						||
  //    if (event != undefined && event != null) {
 | 
						||
  //        const dbIds = event.dbIdArray;
 | 
						||
 | 
						||
  //        if (dbIds.length > 0) {
 | 
						||
  //            // 處理已選取元件的邏輯
 | 
						||
  //            $(selector).trigger("autodesk:click:sprite", event);
 | 
						||
  //            console.log(`------ name: ${viewer.model.getInstanceTree().getNodeName(dbIds)} , dbId: ${dbIds}`);//, id: ${event.clickInfo.object.id}, position.x: ${event.clickInfo.point.x}, y: ${event.clickInfo.point.y}, z: ${event.clickInfo.point.z}
 | 
						||
  //        } else {
 | 
						||
  //            // 處理沒有選取元件的邏輯
 | 
						||
  //            $(selector).trigger("autodesk:click:sprite", event);
 | 
						||
  //            console.log("no item");
 | 
						||
  //        }
 | 
						||
  //    }
 | 
						||
  //}
 | 
						||
}
 | 
						||
 | 
						||
//hex -> rgb
 | 
						||
function hexToRgb(hex) {
 | 
						||
  return (
 | 
						||
    "rgb(" +
 | 
						||
    parseInt("0x" + hex.slice(1, 3)) +
 | 
						||
    "," +
 | 
						||
    parseInt("0x" + hex.slice(3, 5)) +
 | 
						||
    "," +
 | 
						||
    parseInt("0x" + hex.slice(5, 7)) +
 | 
						||
    ")"
 | 
						||
  );
 | 
						||
}
 | 
						||
 | 
						||
// 熱點 更換顏色
 | 
						||
async function changeColorForHotspot(dbId, type = null, OnOff = false) {
 | 
						||
  if(typeof viewer === "undefined"){
 | 
						||
    return;
 | 
						||
  }
 | 
						||
  const dataVizExtn = await viewer.loadExtension("Autodesk.DataVisualization");
 | 
						||
  let spriteColorFocus = new THREE.Color(0xffffff);
 | 
						||
 | 
						||
  spriteColorFocus = OnOff
 | 
						||
    ? pageAct.sysSubObj.device_normal_color
 | 
						||
      ? new THREE.Color(hexToRgb(pageAct.sysSubObj.device_normal_color))
 | 
						||
      : new THREE.Color(0xffffff)
 | 
						||
    : pageAct.sysSubObj.device_close_color
 | 
						||
      ? new THREE.Color(hexToRgb(pageAct.sysSubObj.device_close_color))
 | 
						||
      : new THREE.Color(0xffffff);
 | 
						||
 | 
						||
  if (type == "focus") {
 | 
						||
    spriteColorFocus = new THREE.Color(0x00ffe1);
 | 
						||
  } else if (type == "error") {
 | 
						||
    spriteColorFocus = new THREE.Color(
 | 
						||
      hexToRgb(pageAct.sysSubObj.device_error_color)
 | 
						||
    );
 | 
						||
    spriteErrIcon = baseForgeApiUrl + "/file/img/forge/sensor_circle.svg";
 | 
						||
    if (location.href.indexOf("localhost:5966") != -1) {
 | 
						||
      spriteErrIcon = baseForgeApiUrl + "/img/forge/sensor_circle.svg";
 | 
						||
    }
 | 
						||
  }
 | 
						||
  const viewablesToUpdate = dbId;
 | 
						||
  dataVizExtn.invalidateViewables(viewablesToUpdate, (viewable) => {
 | 
						||
    return {
 | 
						||
      url: "/file/img/forge/sensor_circle.svg",
 | 
						||
      color: spriteColorFocus,
 | 
						||
    };
 | 
						||
  });
 | 
						||
}
 | 
						||
 | 
						||
// 更換 icon
 | 
						||
async function changeScaleForHotspot(dbId, type = true) {
 | 
						||
  if(typeof viewer == 'undefined'){
 | 
						||
	  return;
 | 
						||
  }
 | 
						||
  const dataVizExtn = await viewer.loadExtension("Autodesk.DataVisualization");
 | 
						||
  let spriteErrIcon = baseForgeApiUrl + "/file/img/forge/sensor_circle.svg";
 | 
						||
  if (location.href.indexOf("localhost:5966") != -1) {
 | 
						||
    spriteErrIcon = baseForgeApiUrl + "/img/forge/hotspot.svg";
 | 
						||
  }
 | 
						||
  const viewablesToUpdate = dbId;
 | 
						||
  dataVizExtn.invalidateViewables(viewablesToUpdate, (viewable) => {
 | 
						||
    return {
 | 
						||
      url: spriteErrIcon,
 | 
						||
    };
 | 
						||
  });
 | 
						||
}
 | 
						||
 | 
						||
// 熱點 更換大小
 | 
						||
async function changeScaleForHotspot(dbId, type = true) {
 | 
						||
	if(typeof viewer == 'undefined'){
 | 
						||
	  return;
 | 
						||
  }
 | 
						||
  const dataVizExtn = await viewer.loadExtension("Autodesk.DataVisualization");
 | 
						||
  let scale = 1;
 | 
						||
  if (type) {
 | 
						||
    scale = 1.5;
 | 
						||
  }
 | 
						||
  const viewablesToUpdate = dbId;
 | 
						||
  dataVizExtn.invalidateViewables(viewablesToUpdate, (viewable) => {
 | 
						||
    return {
 | 
						||
      scale: scale, // Restore the viewable size
 | 
						||
      //url: "https://.../circle.svg",
 | 
						||
    };
 | 
						||
  });
 | 
						||
}
 | 
						||
 | 
						||
//------------------- end --------------
 | 
						||
 | 
						||
//------------------ 熱圖 -------------------------------
 | 
						||
async function loadHeatmaps(model) {
 | 
						||
  //console.log("熱圖 loadHeat")
 | 
						||
  const dataVizExtn = await viewer.loadExtension("Autodesk.DataVisualization");
 | 
						||
 | 
						||
  //取三個空調設備的位置打點
 | 
						||
  const devices = [
 | 
						||
    {
 | 
						||
      id: "Oficina 5",
 | 
						||
      //name: "Oficina-",
 | 
						||
      position: { x: 6.98, y: -19.0, z: 16.86 }, // x: 0, y: 25, z: -2.5      (-4.93, -20.61, 16.86)
 | 
						||
      sensorTypes: ["temperature", "humidity"],
 | 
						||
    },
 | 
						||
    {
 | 
						||
      id: "Oficina 4",
 | 
						||
      //name: "Oficina-",
 | 
						||
      position: { x: 35.85, y: -2.24, z: 16.86 }, // x: 0, y: 25.03, z: -2.52  (23.94, -3.85, 16.86)
 | 
						||
      sensorTypes: ["temperature", "humidity"],
 | 
						||
    },
 | 
						||
    {
 | 
						||
      id: "Oficina 3",
 | 
						||
      //name: "Oficina-",
 | 
						||
      position: { x: 6.98, y: -2.24, z: 16.86 }, // x: 0, y: 25.03, z: -2.52   (-4.93, -3.85, 16.86)
 | 
						||
      sensorTypes: ["temperature", "humidity"],
 | 
						||
    },
 | 
						||
  ];
 | 
						||
  //冷氣N5: (-4.93, -20.61, 16.86), N4: (23.94, -3.85, 16.86), N3: (-4.93, -3.85, 16.86)
 | 
						||
 | 
						||
  // Initialize sensor values
 | 
						||
  let sensorVals = [];
 | 
						||
  for (let i = 0; i < devices.length; i++) {
 | 
						||
    sensorVals[i] = Math.random();
 | 
						||
  }
 | 
						||
 | 
						||
  const roomDbIds = [];
 | 
						||
  roomDbIds.push(7567);
 | 
						||
 | 
						||
  const { SurfaceShadingData, SurfaceShadingPoint, SurfaceShadingNode } =
 | 
						||
    Autodesk.DataVisualization.Core;
 | 
						||
 | 
						||
  const shadingNode = new SurfaceShadingNode("Room Panel", roomDbIds);
 | 
						||
 | 
						||
  devices.forEach((device) => {
 | 
						||
    const shadingPoint = new SurfaceShadingPoint(
 | 
						||
      device.id,
 | 
						||
      device.position,
 | 
						||
      device.sensorTypes
 | 
						||
    );
 | 
						||
    shadingNode.addPoint(shadingPoint);
 | 
						||
  });
 | 
						||
 | 
						||
  const heatmapData = new SurfaceShadingData();
 | 
						||
  heatmapData.addChild(shadingNode);
 | 
						||
  heatmapData.initialize(model);
 | 
						||
 | 
						||
  // Setup surface shading
 | 
						||
  await dataVizExtn.setupSurfaceShading(model, heatmapData);
 | 
						||
 | 
						||
  dataVizExtn.registerSurfaceShadingColors("temperature", [0xff0000, 0x0000ff]);
 | 
						||
 | 
						||
  function getSensorValue(device, sensorType) {
 | 
						||
    return sensorVals[parseInt(device.id.slice(-1)) - 1];
 | 
						||
  }
 | 
						||
 | 
						||
  dataVizExtn.renderSurfaceShading("Room Panel", "temperature", getSensorValue);
 | 
						||
 | 
						||
  setInterval(() => {
 | 
						||
    // Modify sensor values.
 | 
						||
    for (let i = 0; i < devices.length; i++) {
 | 
						||
      sensorVals[i] = Math.random();
 | 
						||
    }
 | 
						||
    dataVizExtn.updateSurfaceShading(getSensorValue);
 | 
						||
  }, 2000);
 | 
						||
}
 | 
						||
//------------------ end --------------------------------
 | 
						||
async function loadHeatmapsForFloor(model) {
 | 
						||
  const dataVizExtn = await viewer.loadExtension("Autodesk.DataVisualization");
 | 
						||
 | 
						||
  //x: -17.33, y: 51.03, z: -2.52
 | 
						||
  const devices = [
 | 
						||
    {
 | 
						||
      id: "Oficina 5",
 | 
						||
      //name: "Oficina-",
 | 
						||
      position: { x: 6.98, y: -19.0, z: 16.86 }, // x: 0, y: 25, z: -2.5      (-4.93, -20.61, 16.86)
 | 
						||
      sensorTypes: ["temperature", "humidity"],
 | 
						||
    },
 | 
						||
    {
 | 
						||
      id: "Oficina 4",
 | 
						||
      //name: "Oficina-",
 | 
						||
      position: { x: 35.85, y: -2.24, z: 16.86 }, // x: 0, y: 25.03, z: -2.52  (23.94, -3.85, 16.86)
 | 
						||
      sensorTypes: ["temperature", "humidity"],
 | 
						||
    },
 | 
						||
    {
 | 
						||
      id: "Oficina 3",
 | 
						||
      //name: "Oficina-",
 | 
						||
      position: { x: 6.98, y: -2.24, z: 16.86 }, // x: 0, y: 25.03, z: -2.52   (-4.93, -3.85, 16.86)
 | 
						||
      sensorTypes: ["temperature", "humidity"],
 | 
						||
    },
 | 
						||
  ];
 | 
						||
 | 
						||
  // Initialize sensor values
 | 
						||
  let sensorVals = [];
 | 
						||
  for (let i = 0; i < devices.length; i++) {
 | 
						||
    sensorVals[i] = Math.random();
 | 
						||
  }
 | 
						||
 | 
						||
  const roomDbIds = [];
 | 
						||
 | 
						||
  roomDbIds.push(7567);
 | 
						||
 | 
						||
  const { SurfaceShadingData, SurfaceShadingPoint, SurfaceShadingNode } =
 | 
						||
    Autodesk.DataVisualization.Core;
 | 
						||
 | 
						||
  const shadingNode = new SurfaceShadingNode("Room Panel", roomDbIds);
 | 
						||
 | 
						||
  devices.forEach((device) => {
 | 
						||
    const shadingPoint = new SurfaceShadingPoint(
 | 
						||
      device.id,
 | 
						||
      device.position,
 | 
						||
      device.sensorTypes
 | 
						||
    );
 | 
						||
    shadingNode.addPoint(shadingPoint);
 | 
						||
  });
 | 
						||
 | 
						||
  const heatmapData = new SurfaceShadingData();
 | 
						||
  heatmapData.addChild(shadingNode);
 | 
						||
  heatmapData.initialize(model);
 | 
						||
 | 
						||
  // Setup surface shading
 | 
						||
  await dataVizExtn.setupSurfaceShading(model, heatmapData);
 | 
						||
 | 
						||
  //dataVizExtn.registerSurfaceShadingColors("co2", [0x00ff00, 0xff0000]);
 | 
						||
  dataVizExtn.registerSurfaceShadingColors("temperature", [0xff0000, 0x0000ff]);
 | 
						||
 | 
						||
  function getSensorValue(device, sensorType) {
 | 
						||
    return sensorVals[parseInt(device.id.slice(-1)) - 1];
 | 
						||
  }
 | 
						||
 | 
						||
  dataVizExtn.renderSurfaceShading("Room Panel", "temperature", getSensorValue);
 | 
						||
 | 
						||
  setInterval(() => {
 | 
						||
    // Modify sensor values.
 | 
						||
    for (let i = 0; i < devices.length; i++) {
 | 
						||
      sensorVals[i] = Math.random();
 | 
						||
    }
 | 
						||
    dataVizExtn.updateSurfaceShading(getSensorValue);
 | 
						||
  }, 2000);
 | 
						||
}
 | 
						||
 | 
						||
//
 | 
						||
async function loadHeatmap() {
 | 
						||
  const model = viewer.model;
 | 
						||
  loadHeatmaps(model);
 | 
						||
}
 | 
						||
 | 
						||
async function loadHeatmapForFloor() {
 | 
						||
  const model = viewer.model;
 | 
						||
  loadHeatmapsForFloor(model);
 | 
						||
}
 | 
						||
 | 
						||
//------------ 剖面 ----------------------
 | 
						||
 | 
						||
function findLevels(model) {
 | 
						||
  return new Promise((resolve, reject) => {
 | 
						||
    model.search(
 | 
						||
      "layer",
 | 
						||
      (nodeIds) => {
 | 
						||
        let levels = [];
 | 
						||
        const tree = viewer.model.getInstanceTree();
 | 
						||
        for (let i = 0; i < nodeIds.length; i++) {
 | 
						||
          const dbId = nodeIds[i];
 | 
						||
          const name = tree.getNodeName(dbId);
 | 
						||
          if (!name || name.includes("<沒有層級>")) continue;
 | 
						||
          levels.push({
 | 
						||
            guid: dbId,
 | 
						||
            name,
 | 
						||
            dbId,
 | 
						||
            extension: {
 | 
						||
              buildingStory: true,
 | 
						||
              structure: false,
 | 
						||
              computationHeight: 0,
 | 
						||
              groundPlane: false,
 | 
						||
              hasAssociatedViewPlans: false,
 | 
						||
            },
 | 
						||
          });
 | 
						||
        }
 | 
						||
        resolve(levels);
 | 
						||
      },
 | 
						||
      (e) => {
 | 
						||
        reject(e);
 | 
						||
      }
 | 
						||
    );
 | 
						||
  });
 | 
						||
}
 | 
						||
 | 
						||
async function getRemoteLevels() {
 | 
						||
  let aecData = await Autodesk.Viewing.Document.getAecModelData(
 | 
						||
    viewer.model.getDocumentNode()
 | 
						||
  );
 | 
						||
 | 
						||
  let levels;
 | 
						||
  if (!aecData || !aecData.levels) {
 | 
						||
    const levelExt = await viewer.loadExtension("Autodesk.AEC.LevelsExtension");
 | 
						||
    levelExt.setAecModelData(undefined, viewer.model); //!<<< Clear before reset
 | 
						||
    levels = await findLevels(viewer.model);
 | 
						||
    aecdata = Autodesk.AEC.AecModelData.computeLevels(levels, viewer.model); //!<<< Rebuild aec model data
 | 
						||
  } else {
 | 
						||
    levels = aecData.levels;
 | 
						||
  }
 | 
						||
  levels.sort((a, b) => b.elevation - a.elevation);
 | 
						||
  return levels;
 | 
						||
}
 | 
						||
 | 
						||
async function getLevelsData(lowerFloor, upperFloor, callback = null) {
 | 
						||
  // 樓層正規化 取得樓層
 | 
						||
  const floorRegex = /[\d|\w]+F/gim;
 | 
						||
  const floorRegex2 = /^FL[\d|\w]+/gim;
 | 
						||
  const data = await getRemoteLevels();
 | 
						||
  for (var i = 0; i < data.length; i++) {
 | 
						||
    let name =
 | 
						||
      data[i].name?.match(floorRegex) || data[i].name?.match(floorRegex2);
 | 
						||
    if (
 | 
						||
      name &&
 | 
						||
      (name[0] == lowerFloor || name[0].split("L")[1] + "F" == lowerFloor)
 | 
						||
    ) {
 | 
						||
      lowerIdx = i;
 | 
						||
    }
 | 
						||
    if (
 | 
						||
      name &&
 | 
						||
      (name[0] == upperFloor || name[0].split("L")[1] + "F" == upperFloor)
 | 
						||
    ) {
 | 
						||
      if (i > upperIdx && lowerFloor == upperFloor) {
 | 
						||
      } else {
 | 
						||
        upperIdx = i;
 | 
						||
      }
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  this.levels = data;
 | 
						||
  profile(callback);
 | 
						||
}
 | 
						||
 | 
						||
function getCutPlaneParam(idx, n) {
 | 
						||
  if (idx < 0 || !n) return;
 | 
						||
 | 
						||
  const level = this.levels[idx];
 | 
						||
  if (!level) return;
 | 
						||
 | 
						||
  const model = viewer.model;
 | 
						||
  const globalOffset = model.getData().globalOffset;
 | 
						||
  const units = model.getUnitString();
 | 
						||
  const elevRaw = Autodesk.Viewing.Private.convertUnits(
 | 
						||
    "ft",
 | 
						||
    units,
 | 
						||
    1,
 | 
						||
    level.elevation
 | 
						||
  );
 | 
						||
  let d = elevRaw - globalOffset.z - 0.5;
 | 
						||
  if (n == 1) d = -1 * d;
 | 
						||
 | 
						||
  return new THREE.Vector4(0, 0, n, d);
 | 
						||
}
 | 
						||
 | 
						||
function profile(callback = null) {
 | 
						||
  //const upperIdx = 6;
 | 
						||
  const upperCutPlaneParam = this.getCutPlaneParam(upperIdx, 1);
 | 
						||
  //const lowerIdx = 7;
 | 
						||
  const lowerCutPlaneParam = this.getCutPlaneParam(lowerIdx, -1);
 | 
						||
  viewer.setCutPlanes([upperCutPlaneParam, lowerCutPlaneParam]);
 | 
						||
  callback ? callback() : "";
 | 
						||
}
 | 
						||
//----------------- end -----------------------------------------------
 | 
						||
//新增燈光
 | 
						||
async function newLight(lightPosition) {
 | 
						||
  //聚光燈
 | 
						||
  var spotLight = new THREE.SpotLight(0xe1cf18, 0, 20, 0.6, 0.5, 10);
 | 
						||
  spotLight.position.set(lightPosition.x, lightPosition.y, lightPosition.z);
 | 
						||
  // console.log(lightPosition.x, lightPosition.y, lightPosition.z)
 | 
						||
  spotLight.castShadow = false;
 | 
						||
  spotLight.visible = true;
 | 
						||
  spotLight.target.position.set(
 | 
						||
    lightPosition.x,
 | 
						||
    lightPosition.y,
 | 
						||
    lightPosition.z - 20
 | 
						||
  );
 | 
						||
  viewer.scene.add(spotLight.target);
 | 
						||
  viewer.scene.add(spotLight);
 | 
						||
  viewer.impl.sceneUpdated(true);
 | 
						||
 | 
						||
  return spotLight;
 | 
						||
}
 | 
						||
 | 
						||
//調整燈光 強度、顏色
 | 
						||
async function setLightValues(deviceGuid, intensity, color) {
 | 
						||
  const light = lightList.find(({ device_guid }) => device_guid === deviceGuid);
 | 
						||
 | 
						||
  if (light) {
 | 
						||
    light.lightObject.intensity = intensity;
 | 
						||
    var tempcolor = new THREE.Color().setHex(color);
 | 
						||
    light.lightObject.color = tempcolor;
 | 
						||
  }
 | 
						||
  // for (var i = 0; i < lightList.length; i++) {
 | 
						||
  //   if (lightList[i].device_guid == deviceGuid) {
 | 
						||
  //     lightList[i].lightObject.intensity = intensity;
 | 
						||
 | 
						||
  //     var tempcolor = new THREE.Color().setHex(color);
 | 
						||
  //     lightList[i].lightObject.color = tempcolor;
 | 
						||
  //   }
 | 
						||
  // }
 | 
						||
  viewer.impl.sceneUpdated(true);
 | 
						||
}
 | 
						||
 | 
						||
//燈光開關
 | 
						||
async function setLightOpenOrClose(value, deviceGuid) {
 | 
						||
  for (var i = 0; i < lightList.length; i++) {
 | 
						||
    if (lightList[i].device_guid == deviceGuid) {
 | 
						||
      lightList[i].lightObject.visible = value;
 | 
						||
    }
 | 
						||
  }
 | 
						||
  viewer.impl.sceneUpdated(true);
 | 
						||
}
 | 
						||
 | 
						||
//透過nodeId,更改物件顏色或顯示與否;color請填寫THREE.Vector4
 | 
						||
function changeColorTransparency(nodeId, color) {
 | 
						||
  //變綠色
 | 
						||
  //var color = new THREE.Vector4(0, 1, 0, 1);//綠色;前三個代表r、g、b; 亦可填入255/255
 | 
						||
  //var color = new THREE.Vector4(0, 1, 0, 0);//不顯示顏色;最後的參數為透明度
 | 
						||
  viewer.setThemingColor(nodeId, color);
 | 
						||
}
 | 
						||
 | 
						||
//隱藏全物件
 | 
						||
function hideAllObjects(filDbids = []) {
 | 
						||
  //viewer.hide(4);//只針對一個物件(dbid為4)做隱藏
 | 
						||
  for (var i = 0; i < allDbIdsStr.length; i++) {
 | 
						||
    viewer.hide(parseInt(allDbIdsStr[i]));
 | 
						||
  }
 | 
						||
 | 
						||
  for (var i = 0; i < filDbids.length; i++) {
 | 
						||
    viewer.show(parseInt(filDbids[i]), viewer.model);
 | 
						||
  }
 | 
						||
}
 | 
						||
//顯示全物件
 | 
						||
function showAllObjects() {
 | 
						||
  //viewer.show(4); //只針對一個物件(dbid為4)做顯示
 | 
						||
  for (var i = 0; i < allDbIdsStr.length; i++) {
 | 
						||
    viewer.show(parseInt(allDbIdsStr[i]));
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
function showHeat(selector) {
 | 
						||
  const labels = [
 | 
						||
    `${(10).toFixed(2)}${"°C"}`,
 | 
						||
    `${(40 / 2).toFixed(2)}${"°C"}`,
 | 
						||
    `${(30).toFixed(2)}${"°C"}`,
 | 
						||
  ];
 | 
						||
  const colorStops = ["blue", "green", "yellow", "red"];
 | 
						||
  createHeatmapRect(labels, colorStops, selector);
 | 
						||
}
 | 
						||
 | 
						||
function showHeatCO2(selector) {
 | 
						||
  const labels = [
 | 
						||
    `${(400).toFixed(2)}`,
 | 
						||
    `${(1600 / 2 + 400).toFixed(2)}`,
 | 
						||
    `${(2000).toFixed(2)}`,
 | 
						||
  ];
 | 
						||
  const colorStops = ["blue", "green", "yellow", "red"];
 | 
						||
  createHeatmapRect(labels, colorStops, selector);
 | 
						||
}
 | 
						||
 | 
						||
function createHeatmapRect(labels, colorStops, selector) {
 | 
						||
  if (!$(selector)[0]) {
 | 
						||
    return;
 | 
						||
  }
 | 
						||
  const context = $(selector)[0].getContext("2d");
 | 
						||
  let i, len;
 | 
						||
  context.clearRect(0, 0, 200, 50);
 | 
						||
  context.fillStyle = "back"; //white
 | 
						||
  for (i = 0, len = labels.length; i < len; i++) {
 | 
						||
    let x = 10 + (180 * i) / (len - 1);
 | 
						||
    if (i === len - 1) {
 | 
						||
      x -= context.measureText(labels[i]).width;
 | 
						||
    } else if (i > 0) {
 | 
						||
      x -= 0.5 * context.measureText(labels[i]).width;
 | 
						||
    }
 | 
						||
    context.fillText(labels[i], x, 10);
 | 
						||
  }
 | 
						||
  const gradient = context.createLinearGradient(0, 0, 200, 0);
 | 
						||
  for (i = 0, len = colorStops.length; i < len; i++) {
 | 
						||
    gradient.addColorStop(i / (len - 1), colorStops[i]);
 | 
						||
  }
 | 
						||
  context.fillStyle = gradient;
 | 
						||
  context.fillRect(10, 20, 200, 25);
 | 
						||
}
 | 
						||
 | 
						||
//======================== 外部呼叫function ===========================
 | 
						||
//紀錄熱點座標
 | 
						||
function getHopspotPoint(data) {
 | 
						||
  myDataList = data;
 | 
						||
}
 | 
						||
 | 
						||
//呼叫載入熱圖
 | 
						||
async function toLoadHeatmap(roomArr) {
 | 
						||
	if(typeof viewer == 'undefined'){
 | 
						||
		return;
 | 
						||
	}
 | 
						||
  const model = viewer.model;
 | 
						||
  loadHeatmaps(model, roomArr);
 | 
						||
}
 | 
						||
 | 
						||
function setShadowShow(type = false) {
 | 
						||
	if(typeof viewer == 'undefined'){
 | 
						||
		return;
 | 
						||
	}
 | 
						||
  viewer.setGroundShadow(type);
 | 
						||
  viewer.impl.sceneUpdated(true);
 | 
						||
}
 | 
						||
//============================= end ===================================
 | 
						||
 | 
						||
function moveViewToDevice(letter) {
 | 
						||
	if(typeof viewer == 'undefined'){
 | 
						||
		return;
 | 
						||
	}
 | 
						||
  if (letter != "") {
 | 
						||
    viewer.clearSelection();
 | 
						||
    viewer.select(letter);
 | 
						||
    viewer.fitToView([letter]);
 | 
						||
  } else {
 | 
						||
    viewer.clearSelection();
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
class ErrorDevice {
 | 
						||
  constructor(options) {
 | 
						||
    this.errorDevices = options.errorDevices ?? [];
 | 
						||
    this.model = null;
 | 
						||
    this.#init();
 | 
						||
  }
 | 
						||
 | 
						||
  #init = async function () {
 | 
						||
    this.model = viewer.model;
 | 
						||
    // 載入 Autodesk Viewer 的資料視覺化擴充功能
 | 
						||
    const dataVizExtn = await viewer.loadExtension(
 | 
						||
      "Autodesk.DataVisualization"
 | 
						||
    );
 | 
						||
    // dataVizExtn.removeSurfaceShading(this.model);
 | 
						||
    // 儲存 Viewer 的模型
 | 
						||
    this.addHeatMaps();
 | 
						||
  };
 | 
						||
 | 
						||
  async addHeatMaps() {
 | 
						||
    const dataVizExtn = await viewer.loadExtension(
 | 
						||
      "Autodesk.DataVisualization"
 | 
						||
    );
 | 
						||
    const {
 | 
						||
      SurfaceShadingData,
 | 
						||
      SurfaceShadingPoint,
 | 
						||
      SurfaceShadingNode,
 | 
						||
      SurfaceShadingGroup,
 | 
						||
    } = Autodesk.DataVisualization.Core;
 | 
						||
    // test
 | 
						||
    const shadingGroup = new SurfaceShadingGroup("device-error-heatmap");
 | 
						||
    const rooms = new Map();
 | 
						||
    for (const { id, forge_dbid, roomDbId, position, sensorTypes } of this
 | 
						||
      .errorDevices) {
 | 
						||
      if (!id || forge_dbid == -1 || roomDbId == -1) {
 | 
						||
        continue;
 | 
						||
      }
 | 
						||
 | 
						||
      if (!rooms.has(forge_dbid) || !rooms.has(roomDbId)) {
 | 
						||
        const room = new SurfaceShadingNode(`error_${id}`, forge_dbid);
 | 
						||
        shadingGroup.addChild(room);
 | 
						||
        rooms.set(forge_dbid, room);
 | 
						||
      }
 | 
						||
      const room = rooms.get(forge_dbid);
 | 
						||
      room.addPoint(
 | 
						||
        new SurfaceShadingPoint(`error_${id}`, position, sensorTypes)
 | 
						||
      );
 | 
						||
    }
 | 
						||
    const shadingData = new SurfaceShadingData();
 | 
						||
    shadingData.addChild(shadingGroup);
 | 
						||
 | 
						||
    shadingData.initialize(this.model);
 | 
						||
 | 
						||
    await dataVizExtn.setupSurfaceShading(this.model, shadingData);
 | 
						||
    dataVizExtn.registerSurfaceShadingColors("error", [0xff0000, 0xff0000]);
 | 
						||
 | 
						||
    dataVizExtn.renderSurfaceShading(
 | 
						||
      "device-error-heatmap",
 | 
						||
      "error",
 | 
						||
      this.getSensorValue.bind(this)
 | 
						||
    );
 | 
						||
  }
 | 
						||
 | 
						||
  getSensorValue = function (device, sensorType) {
 | 
						||
    let dev = this.errorDevices.filter((x) => `error_${x.id}` == device.id)[0];
 | 
						||
    if (sensorType === "error") {
 | 
						||
      return 0;
 | 
						||
    }
 | 
						||
  };
 | 
						||
}
 | 
						||
// =================================燈光用===============================================
 | 
						||
async function getLTNode(device) {
 | 
						||
  // 處理 node 的資料
 | 
						||
  const nodes = device.device_nodes?.map((n) => ({
 | 
						||
    ...n,
 | 
						||
    position: isJSON(n.device_node_coordinate_3D)
 | 
						||
      ? JSON.parse(n.device_node_coordinate_3D)
 | 
						||
      : {}, // x: 0, y: 25, z: -2.5      (3.35, -4.81, 12.88
 | 
						||
    sensorTypes: ["TrafficDensity"],
 | 
						||
    forgeDbId: parseInt(n.forge_dbid),
 | 
						||
    dbId: parseInt(n.forge_dbid),
 | 
						||
  }));
 | 
						||
 | 
						||
  // 載入 Autodesk Viewer 的資料視覺化擴充功能
 | 
						||
  const dataVizExtn = await viewer.loadExtension("Autodesk.DataVisualization");
 | 
						||
 | 
						||
  const {
 | 
						||
    SurfaceShadingGroup,
 | 
						||
    SurfaceShadingData,
 | 
						||
    SurfaceShadingPoint,
 | 
						||
    SurfaceShadingNode,
 | 
						||
  } = Autodesk.DataVisualization.Core;
 | 
						||
 | 
						||
  dataVizExtn.removeSurfaceShading(viewer.model);
 | 
						||
 | 
						||
  // Group to select both north and southbound
 | 
						||
  const shadingGroup = new SurfaceShadingGroup("LT");
 | 
						||
 | 
						||
  nodes.forEach(({ position, forgeDbId, sensorTypes, device_number }) => {
 | 
						||
    const LTGroup = new SurfaceShadingNode("LTGroup" + forgeDbId, forgeDbId);
 | 
						||
    LTGroup.addPoint(
 | 
						||
      new SurfaceShadingPoint(device_number, position, sensorTypes)
 | 
						||
    );
 | 
						||
    shadingGroup.addChild(LTGroup);
 | 
						||
  });
 | 
						||
 | 
						||
  const heatmapData = new SurfaceShadingData();
 | 
						||
  heatmapData.addChild(shadingGroup);
 | 
						||
 | 
						||
  // Initialize with model loaded from APS
 | 
						||
  heatmapData.initialize(viewer.model);
 | 
						||
  await dataVizExtn.setupSurfaceShading(viewer.model, heatmapData);
 | 
						||
  const sensorColors = [0x0080FF, 0x0080FF];
 | 
						||
 | 
						||
  // Set heatmap colors for temperature
 | 
						||
  const sensorType = "TrafficDensity";
 | 
						||
  dataVizExtn.registerSurfaceShadingColors(sensorType, sensorColors);
 | 
						||
  dataVizExtn.renderSurfaceShading("LT", "TrafficDensity", getLTSensorValue);
 | 
						||
  console.log("dataVizExtn", dataVizExtn);
 | 
						||
}
 | 
						||
 | 
						||
// Function that provides a [0,1] value for the Heatmap
 | 
						||
function getLTSensorValue(surfaceShadingPoint, sensorType) {
 | 
						||
  return 1;
 | 
						||
}
 |