定制Autodesk Forge Viewer文档浏览器扩展以支持多模型
问题背景
我正在使用Autodesk Forge Viewer及Autodesk.DocumentBrowser扩展,目标是扩展该文档浏览器的功能以同时支持多模型。目前该扩展仅显示单个模型的图纸和视图,我希望自定义它以展示Viewer中所有已加载模型的图纸和视图。
查阅资料后发现该功能默认未实现,因此尝试基于DocumentBrowser扩展的源码创建自定义扩展。在DocumentBrowser源码中,_hookToModel方法用于设置Autodesk.Viewing.UI.Tree根节点,当前TreeView仅接受单个模型的BubbleNode作为父节点。我尝试创建包含所有模型根节点的父节点,但显示空树,以下是尝试的代码:
尝试代码1:_hookToModel方法
_hookToModel() { var models = this.viewer.getVisibleModels();//获取所有已加载模型的数组 this.rootNodes = models.map(model => { var docNode = model.getDocumentNode(); if (!docNode) return null; this.currNode = docNode.findParentGeom2Dor3D(); let rootNode = docNode.getRootNode(); this.geometries = rootNode.search({ 'type': 'geometry' }); return rootNode; }).filter(node => node !== null); let cache = this.getCache(); if (cache.ui) { this.ui = cache.ui; cache.ui = null; } else { this.ui = new _uiController__WEBPACK_IMPORTED_MODULE_0__.UiController(this.viewer); } this.ui.createUi(this.currNode, this.options, this.rootNodes); // 尝试传递模型根节点以调整树和缩略图 this.ui.setChangeModelHandler(this._changeModel.bind(this)); }
尝试代码2:createUi方法
/** * 创建UI。 * @param {Autodesk.Viewing.BubbleNode} currNode - 加载到Viewer中的节点 * @param {Object} [options] * @param {bool} [options.openDocumentBrowserOnLoad=false] - 扩展加载时是否打开文档浏览器面板。 * @param {bool} [options.showThumbnails=true] - 显示或隐藏文档浏览器中的缩略图,默认显示。 */ createUi(currNode, options, rootNodes) { this.currNode = currNode; this.rootNodes = rootNodes; this._addToolbarButton(); if (!this.panel) { this.panel = new _Panel__WEBPACK_IMPORTED_MODULE_0__.Panel(this.viewer, this.currNode, this.rootNodes);//将rootNodes传递给面板构造函数 this.panel.setChangeModelHandler(this._changeModelFn); this.panel.addVisibilityListener(this._onPanelVisibilityChange); // 显示或隐藏缩略图 if (options && options.showThumbnails !== undefined) { this.panel.setThumbnailVisibility(options.showThumbnails); } if (options && options.openDocumentBrowserOnLoad) { this.panel.toggleVisibility(); } } else { // 同步超链接触发的选择状态变更的UI调整 this.panel.setCurrentNode(currNode); } this._updateButtonState(); }
尝试代码3:_createTreeView方法
_createTreeView() { if (this.myTree) return; var rootNode = new Autodesk.Viewing.BubbleNode("Aggregated-Models", this.rootNodes);//尝试创建父根节点但显示空树 //var rootNode = this.currNode.getRootNode();//仅加载单个模型 var delegate = (0, _TreeViewDelegate__WEBPACK_IMPORTED_MODULE_1__.createTreeViewDelegate)(rootNode, this); var options = { leafClassName: 'docBrowserLeaf', selectedClassName: 'selected-ex' }; var container = this._getTabDiv(TAB_ID_TREE); this.myTree = new Autodesk.Viewing.UI.Tree( delegate, rootNode, container, options ); }
问题现象

问题分析
你的代码主要存在以下几个核心问题:
BubbleNode构造函数使用错误
你试图直接通过new Autodesk.Viewing.BubbleNode("Aggregated-Models", this.rootNodes)将子节点数组传入构造函数,但BubbleNode的构造函数并不支持这种用法,导致聚合根节点没有正确关联子节点,最终树显示为空。TreeViewDelegate不兼容多根结构
原DocumentBrowser的TreeViewDelegate是为单个模型的树结构设计的,它的子节点获取逻辑默认只会处理单个模型根节点的层级,无法识别你创建的聚合根节点下的多个模型节点。currNode赋值逻辑错误
在_hookToModel的循环中,你反复覆盖this.currNode的值,最终它只会保留最后一个模型的节点,这会导致后续UI同步、节点切换等逻辑出现异常。
修正方案
1. 正确创建聚合根节点并添加子节点
修改_createTreeView方法,先创建空的聚合根节点,再手动添加各个模型的根节点作为子节点:
_createTreeView() { if (this.myTree) return; // 创建空的聚合根节点,第二个参数传null表示无父节点 var rootNode = new Autodesk.Viewing.BubbleNode("Aggregated-Models", null); // 遍历所有模型根节点,添加为聚合节点的子节点 this.rootNodes.forEach(modelRoot => { rootNode.addChild(modelRoot); }); var delegate = (0, _TreeViewDelegate__WEBPACK_IMPORTED_MODULE_1__.createTreeViewDelegate)(rootNode, this); var options = { leafClassName: 'docBrowserLeaf', selectedClassName: 'selected-ex' }; var container = this._getTabDiv(TAB_ID_TREE); this.myTree = new Autodesk.Viewing.UI.Tree( delegate, rootNode, container, options ); }
2. 修改TreeViewDelegate的子节点获取逻辑
找到createTreeViewDelegate的实现,调整getChildren方法,让它能正确处理聚合根节点:
// 在TreeViewDelegate的getChildren方法中修改 getChildren(node, callback) { // 判断是否是我们创建的聚合根节点 if (node.name === "Aggregated-Models") { // 直接返回聚合节点的子节点列表 callback(null, node.getChildren()); } else { // 保留原有的单个模型节点的子节点获取逻辑 node.getChildren((err, children) => { // 可以根据需要过滤子节点,比如只保留视图/图纸节点 callback(err, children); }); } }
3. 修正currNode的赋值逻辑
在_hookToModel中,避免循环覆盖this.currNode,改为保存每个模型的geom节点,并设置默认的currNode:
_hookToModel() { var models = this.viewer.getVisibleModels(); this.rootNodes = models.map(model => { var docNode = model.getDocumentNode(); if (!docNode) return null; // 给每个模型对象附加geom节点,方便后续使用 model._geomNode = docNode.findParentGeom2Dor3D(); let rootNode = docNode.getRootNode(); // 合并所有模型的geometry节点 this.geometries = [...(this.geometries || []), ...rootNode.search({ 'type': 'geometry' })]; return rootNode; }).filter(node => node !== null); // 设置默认currNode为第一个模型的geom节点 this.currNode = this.rootNodes.length > 0 ? this.rootNodes[0].getDocumentNode().findParentGeom2Dor3D() : null; let cache = this.getCache(); if (cache.ui) { this.ui = cache.ui; cache.ui = null; } else { this.ui = new _uiController__WEBPACK_IMPORTED_MODULE_0__.UiController(this.viewer); } this.ui.createUi(this.currNode, this.options, this.rootNodes); this.ui.setChangeModelHandler(this._changeModel.bind(this)); }
4. 调整模型切换逻辑
修改_changeModel方法,确保切换节点时能激活对应的模型:
_changeModel(node) { // 获取节点所属的模型 const targetModel = node.getModel(); if (targetModel) { // 激活当前模型 this.viewer.setActiveModel(targetModel); } // 保留原有的节点选择、视图加载逻辑 this.viewer.select(node); // 其他原有逻辑(比如切换视图/图纸)... }
内容的提问来源于stack exchange,提问作者Ahmed Shawky

