You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Autodesk Forge Viewer切换至First Object选择模式卡顿问题求助

Autodesk Forge Viewer切换回First Object模式卡顿加载缓慢的解决方案

问题分析

切换至Leaf Object模式时流畅,但切回First Object模式卡顿,核心原因是:

  • First Object模式需要遍历模型层级树,找到点击位置最顶层的父节点,当模型层级复杂或使用AggregatedView加载多模型时,层级遍历的计算量会急剧上升,占用大量CPU资源。
  • Leaf Object模式仅选择最底层几何节点,计算逻辑简单,因此性能表现更好。

现有代码的问题点

  1. 重复绑定事件:loadManifest函数中每次加载模型都会绑定AGGREGATE_SELECTION_CHANGED_EVENT,导致多个重复监听叠加触发,加重性能负担。
  2. 事件绑定错误:viewer.addEventListener(Autodesk.Viewing.SELECTION_CHANGED_EVENT, switchSelectionMode(1))直接执行了函数,而非传递回调,导致模式切换逻辑错误触发。
  3. 模式切换优化不彻底:现有switchSelectionMode仅禁用了请求,未处理选择状态清理和渲染暂停,无法完全缓解卡顿。

解决方案

1. 修复事件绑定

  • 将AGGREGATE_SELECTION_CHANGED_EVENT的监听移至Viewer初始化后,确保仅绑定一次。
  • 修正SELECTION_CHANGED_EVENT的绑定方式,使用箭头函数或bind传递参数。

2. 优化模式切换逻辑

  • 切换前清理当前选择状态,减少层级遍历的计算量。
  • 切换过程中暂停渲染,完成后恢复,避免界面卡顿。

3. 调整加载与配置策略

  • 确保所有模型加载完成后再允许切换选择模式,避免加载过程中资源竞争。

修改后的完整代码

Autodesk.Viewing.Initializer(options, function() {
  const viewerDiv = document.getElementById('forgeViewer');
  const viewer = new Autodesk.Viewing.GuiViewer3D(viewerDiv);
  viewer.start();

  // 仅绑定一次事件
  viewer.addEventListener(Autodesk.Viewing.TOOLBAR_CREATED_EVENT, onToolbarCreated);
  viewer.addEventListener(Autodesk.Viewing.GEOMETRY_LOADED_EVENT, Loadedevent);
  
  // 初始化AggregatedView
  const view = new Autodesk.Viewing.AggregatedView();
  view.init(viewerDiv, options3d);
  const aggregatedViewer = view.viewer;

  // 设置自定义配置
  const customProfileSettings = {
    settings: {
      reverseMouseZoomDir: true,
      reverseHorizontalLookDirection: true,
      selectionMode: 1 // 1 = First Object
    }
  };
  const customProfile = new Autodesk.Viewing.Profile(customProfileSettings);
  viewer.setProfile(customProfile);

  // 修正事件绑定:使用箭头函数传递参数
  viewer.addEventListener(Autodesk.Viewing.SELECTION_CHANGED_EVENT, () => switchSelectionMode(1));

  // 优化Viewer性能设置
  viewer.setQualityLevel(false, false);
  viewer.setGroundShadow(false);
  viewer.setGroundReflection(false);
  viewer.setProgressiveRendering(true);
  viewer.setNavigationLock(true); // 临时锁定导航,切换完成后解锁

  // 加载所有模型,完成后再启用交互
  const tasks = models.map(md => loadManifest(md.urn, aggregatedViewer));
  Promise.all(tasks)
    .then(docs => Promise.resolve(docs.map(doc => {
      const bubbles = doc.getRoot().search({ type: 'geometry', role: '3d' });
      return bubbles[0] || null;
    })))
    .then(bubbles => {
      view.setNodes(bubbles);
      viewer.setNavigationLock(false); // 模型加载完成后解锁导航
      // 绑定聚合选择事件(仅一次)
      aggregatedViewer.addEventListener(Autodesk.Viewing.AGGREGATE_SELECTION_CHANGED_EVENT, handleAggregateSelection);
    });
});

function loadManifest(documentId, viewer) {
  return new Promise((resolve, reject) => {
    const onDocumentLoadSuccess = (doc) => {
      doc.downloadAecModelData(() => resolve(doc));
    };
    Autodesk.Viewing.Document.load(documentId, onDocumentLoadSuccess, reject);
  });
}

// 聚合选择事件处理(仅绑定一次)
function handleAggregateSelection(event) {
  if (!event.selections || event.selections.length !== 1) return;
  const selSet = event.selections[0];
  const dbId = selSet.dbIdArray[0];
  const model = selSet.model;

  model.getProperties(dbId, function(data) {
    if (FromPage === '') {
      if (FromSelection === '') {
        if (Count === 0) {
          let instanceTree = model.getInstanceTree();
          let parentId = instanceTree.getNodeParentId(dbId);
          var objectid = parentId + 1;
          if (objectid === PreviousParentId) {
            Count = 1;
            selectionchangecount = 0;
          } else {
            viewer.select(parentId, model, Autodesk.Viewing.SelectionType.OVERLAYED);
            Count = 1;
            selectionchangecount = 0;
          }
          PreviousParentId = parentId;
        } else {
          itemobject = dbId;
          selectionchangecount++;
          if (selectionchangecount === fileselectioncount) {
            Count = 0;
            selectionchangecount = 0;
          }
        }
      } else {
        selectionchangecount++;
        if (selectionchangecount === filecount) {
          FromSelection = '';
          selectionchangecount = 0;
        }
        itemobject = dbId;
      }
    } else {
      FromPage = '';
      itemobject = dbId;
    }
  });
}

// 优化后的选择模式切换函数
function switchSelectionMode(newMode) {
  if (newMode === viewer.getSelectionMode()) return;
  
  // 清理当前选择状态
  viewer.clearSelection();
  
  // 暂停渲染和请求
  viewer.impl.disableRendering(true);
  viewer.impl.disableRequests(true);
  
  // 切换模式
  viewer.setSelectionMode(newMode);
  
  // 恢复渲染和请求,强制刷新视图
  viewer.impl.disableRequests(false);
  viewer.impl.disableRendering(false);
  viewer.impl.invalidate(true);
}

额外优化建议

  • 如果模型层级固定,可以预缓存所有节点的父节点映射,避免每次选择时重复遍历instanceTree。
  • 对于超大型模型,考虑使用viewer.loadDocumentNode时指定加载层级,只加载必要的层级数据。

内容的提问来源于stack exchange,提问作者E.M Pradeep

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 00:45:57