Autodesk DataViz API多元素Sprite添加及GUID转dbId高效方案咨询
解决DataViz API批量添加Sprite时GUID转dbId的性能问题
问题背景
需要通过Autodesk DataViz API在多个元素的包围盒中心添加Sprite。现有流程是用Revit插件将Revit几何合并为单个项,想在Viewer中为这些项添加Sprite,但遇到两个核心问题:
- DataViz API不支持直接传入多dbId
- 使用
getExternalIdMapping处理大量GUID转dbId时性能极差,循环卡顿甚至无法完成
当前基于dbId的Sprite添加函数运行正常,但替换为GUID转dbId后出现严重性能问题,需要高效的GUID转dbId方案或直接使用GUID的可行方法。
解决方案
1. 一次性获取全局GUID-dbId映射表(最优方案)
不要循环调用getExternalIdMapping,而是一次性获取整个模型的GUID到dbId的映射,之后直接通过查表实现O(1)时间复杂度的转换,彻底解决性能问题。
代码示例:
// 一次性获取全局映射,模型加载完成后执行一次即可 const getGuidToDbIdMap = async (model) => { const mapping = await model.getExternalIdMapping(); // 反转映射结构,让GUID作为键,dbId作为值 return Object.fromEntries(Object.entries(mapping).map(([dbId, guid]) => [guid, dbId])); }; // 使用示例 const guidToDbIdMap = await getGuidToDbIdMap(model); // 批量转换GUID列表到dbId const dbIds = yourGuidList.map(guid => guidToDbIdMap[guid]);
2. 直接通过GUID获取元素包围盒(绕过dbId转换)
如果不想做dbId转换,可以直接通过GUID获取元素的instanceTree节点,再计算包围盒:
const getBboxByGuid = async (model, guid) => { const node = await model.getObjectByExternalIdAsync(guid); if (!node) return null; const dbId = node.dbId; const instanceTree = model.getData().instanceTree; const frags = model.getFragmentList(); const bbox = new THREE.Box3(); const { role } = model.getDocumentNode().data; // 遍历所有fragment计算包围盒 return new Promise((resolve) => { instanceTree.enumNodeFragments(dbId, (fragId) => { if (role === '3d') { frags.getWorldBounds(fragId, bbox); } else if (role === '2d') { const boundsCallback = new Autodesk.Viewing.Private.BoundsCallback(bbox); const mesh = frags.getVizmesh(fragId); const vbr = new Autodesk.Viewing.Private.VertexBufferReader(mesh.geometry, window.NOP_VIEWER.impl.use2dInstancing); vbr.enumGeomsForObject(dbId, boundsCallback); } }, true); // 第三个参数true表示递归遍历子节点 resolve(bbox); }); };
3. 优化现有代码的性能瓶颈
你的现有代码存在几个性能隐患,优化后可以进一步提升效率:
- 避免同步循环中嵌套异步回调:
enumNodeFragments是同步方法,内部用async会导致Promise无法正确等待,改用Promise.all批量处理 - 按需创建SpriteViewable:不需要提前创建所有状态的SpriteViewable,只创建当前需要的即可
- 缓存重复获取的对象:比如
model.getData().instanceTree、model.getFragmentList()提前缓存,避免循环内重复调用
优化后的代码示例:
const findViewables = async (model, elements) => { const DataVizCore = Autodesk.DataVisualization.Core; const viewableType = DataVizCore.ViewableType.SPRITE; const spriteColor = new THREE.Color(0xffffff); // 预定义样式映射 const styleMap = { default: new DataVizCore.ViewableStyle(viewableType, spriteColor, spriteNfDefault), success: new DataVizCore.ViewableStyle(viewableType, spriteColor, spriteNfSuccess), warning: new DataVizCore.ViewableStyle(viewableType, spriteColor, spriteNfWarning), informative: new DataVizCore.ViewableStyle(viewableType, spriteColor, spriteNfInformative), danger: new DataVizCore.ViewableStyle(viewableType, spriteColor, spriteNfDanger) }; const viewableData = new DataVizCore.ViewableData(); viewableData.spriteSize = 40; // 缓存常用对象,减少重复调用 const instanceTree = model.getData()?.instanceTree; const frags = model.getFragmentList(); const { role } = model.getDocumentNode().data; // 去重处理 const uniqueElements = elements.filter((value, index, self) => self.findIndex(v => v.nfNumber === value.nfNumber) === index ); // 批量异步处理所有元素 await Promise.all(uniqueElements.map(async (el) => { let dbId = el.dbId; // 如果传入的是GUID,用全局映射表转换 if (!dbId && el.guid) { dbId = guidToDbIdMap[el.guid]; if (!dbId) return; } const bbox = new THREE.Box3(); // 遍历fragment计算包围盒 await new Promise((resolve) => { instanceTree.enumNodeFragments(dbId, (fragId) => { if (role === '2d') { const boundsCallback = new Autodesk.Viewing.Private.BoundsCallback(bbox); const mesh = frags.getVizmesh(fragId); const vbr = new Autodesk.Viewing.Private.VertexBufferReader(mesh.geometry, window.NOP_VIEWER.impl.use2dInstancing); vbr.enumGeomsForObject(dbId, boundsCallback); } else if (role === '3d') { frags.getWorldBounds(fragId, bbox); } }, true); resolve(); }); const center = bbox.getCenter(); let styleKey = 'default'; // 确定当前元素对应的样式 if (el.value === 'accepted') styleKey = 'success'; else if (el.value === 'declined') styleKey = 'danger'; else if (el.value === 'sent') styleKey = 'informative'; else if (['submitted', 'provided'].includes(el.value)) styleKey = 'warning'; // 只创建需要的SpriteViewable并添加 const viewable = new DataVizCore.SpriteViewable(center, styleMap[styleKey], dbId); viewableData.addViewable(viewable); })); await viewableData.finish(); return viewableData; };
关键说明
- 全局映射表只需在模型加载完成后获取一次,后续所有转换直接查表,性能提升显著
- 处理大量元素时优先用
Promise.all做批量异步处理,避免同步循环阻塞主线程 - 尽量缓存Viewer内部API返回的对象,减少重复调用带来的性能开销
内容的提问来源于stack exchange,提问作者Evhenii
相关产品推荐
相关产品推荐

