如何在 Forge 中创建多模型加载器和查看器
Posted
技术标签:
【中文标题】如何在 Forge 中创建多模型加载器和查看器【英文标题】:How to create a Multi-Model Loader and Viewer in Forge 【发布时间】:2021-11-25 13:39:51 【问题描述】:我正在尝试构建一个 Forge 多模型查看器,该查看器使用 Nodejs 从存储桶中获取/加载多个模型并将它们显示在查看器中。
我使用加载视图的教程,正在寻找一种解决方法,将存储桶中的所有模型加载到 Forge 查看器。
【问题讨论】:
【参考方案1】:如果我理解正确,您想在视图模型教程的顶部添加多模型支持,对吧?
为此,我们需要调整 ForgeViewer.js、ForgeTree.js 和 index.html
// ForgeViewer.js
/////////////////////////////////////////////////////////////////////
// Copyright (c) Autodesk, Inc. All rights reserved
// Written by Forge Partner Development
//
// Permission to use, copy, modify, and distribute this software in
// object code form for any purpose and without fee is hereby granted,
// provided that the above copyright notice appears in all copies and
// that both that copyright notice and the limited warranty and
// restricted rights notice below appear in all supporting
// documentation.
//
// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS.
// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF
// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC.
// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE
// UNINTERRUPTED OR ERROR FREE.
/////////////////////////////////////////////////////////////////////
var viewer = null;
function launchViewer(models)
if (viewer != null)
viewer.tearDown()
viewer.finish()
viewer = null
$("#forgeViewer").empty();
if (!models || models.length <= 0)
return alert('Empty `models` input');
var options =
env: 'MD20ProdUS',
api: 'D3S',
getAccessToken: getForgeToken
;
if (LMV_VIEWER_VERSION >= '7.48')
options.env = 'AutodeskProduction2';
options.api = 'streamingV2';
Autodesk.Viewing.Initializer(options, () =>
viewer = new Autodesk.Viewing.GuiViewer3D(document.getElementById('forgeViewer'));
//load model one by one in sequence
const util = new MultipleModelUtil(viewer);
viewer.multipleModelUtil = util;
// Use ShareCoordinates alignment instead
// See https://github.com/yiskang/MultipleModelUtil for details
// util.options =
// alignment: MultipleModelAlignmentType.ShareCoordinates
// ;
util.processModels(models);
);
function getForgeToken(callback)
jQuery.ajax(
url: '/api/forge/oauth/token',
success: function (res)
callback(res.access_token, res.expires_in)
);
// ForgeTree.js
/////////////////////////////////////////////////////////////////////
// Copyright (c) Autodesk, Inc. All rights reserved
// Written by Forge Partner Development
//
// Permission to use, copy, modify, and distribute this software in
// object code form for any purpose and without fee is hereby granted,
// provided that the above copyright notice appears in all copies and
// that both that copyright notice and the limited warranty and
// restricted rights notice below appear in all supporting
// documentation.
//
// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS.
// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF
// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC.
// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE
// UNINTERRUPTED OR ERROR FREE.
/////////////////////////////////////////////////////////////////////
$(document).ready(function ()
prepareAppBucketTree();
$('#refreshBuckets').click(function ()
$('#appBuckets').jstree(true).refresh();
);
$('#createNewBucket').click(function ()
createNewBucket();
);
$('#submitTranslation').click(function ()
var treeNode = $('#appBuckets').jstree(true).get_selected(true)[0];
submitTranslation(treeNode);
);
$('#createBucketModal').on('shown.bs.modal', function ()
$("#newBucketKey").focus();
);
$('#CompositeTranslationModal').on('shown.bs.modal', function ()
$("#rootDesignFilename").focus();
);
$('#hiddenUploadField').change(function ()
var node = $('#appBuckets').jstree(true).get_selected(true)[0];
var _this = this;
if (_this.files.length == 0) return;
var file = _this.files[0];
switch (node.type)
case 'bucket':
var formData = new FormData();
formData.append('fileToUpload', file);
formData.append('bucketKey', node.id);
$.ajax(
url: '/api/forge/oss/objects',
data: formData,
processData: false,
contentType: false,
type: 'POST',
success: function (data)
$('#appBuckets').jstree(true).refresh_node(node);
_this.value = '';
);
break;
);
$('#viewModels').click(function ()
let treeInst = $('#appBuckets').jstree(true);
let selectedNodeIds = $('#appBuckets').jstree('get_selected');
let models = [];
for (let i = 0; i < selectedNodeIds.length; i++)
let urn = selectedNodeIds[i];
let node = treeInst.get_node(`$urn_anchor`);
if (!node || (node.type !== 'object'))
continue;
models.push(
name: node.original.text,
urn: `urn:$urn`
);
if (models.length <= 0 || (models.length !== selectedNodeIds.length))
alert('Nothing selected or not all selected nodes are object-typed');
launchViewer(models);
);
);
function createNewBucket()
var bucketKey = $('#newBucketKey').val();
var policyKey = $('#newBucketPolicyKey').val();
jQuery.post(
url: '/api/forge/oss/buckets',
contentType: 'application/json',
data: JSON.stringify( 'bucketKey': bucketKey, 'policyKey': policyKey ),
success: function (res)
$('#appBuckets').jstree(true).refresh();
$('#createBucketModal').modal('toggle');
,
error: function (err)
if (err.status == 409)
alert('Bucket already exists - 409: Duplicated')
console.log(err);
);
function prepareAppBucketTree()
$('#appBuckets').jstree(
'core':
'themes': "icons": true ,
'data':
"url": '/api/forge/oss/buckets',
"dataType": "json",
'multiple': true,
"data": function (node)
return "id": node.id ;
,
'types':
'default':
'icon': 'glyphicon glyphicon-question-sign'
,
'#':
'icon': 'glyphicon glyphicon-cloud'
,
'bucket':
'icon': 'glyphicon glyphicon-folder-open'
,
'object':
'icon': 'glyphicon glyphicon-file'
,
"checkbox":
keep_selected_style: false,
three_state: false,
deselect_all: true,
cascade: 'none'
,
"plugins": ["types", "checkbox", "state", "sort", "contextmenu"],
contextmenu: items: autodeskCustomMenu
).on('loaded.jstree', function ()
$('#appBuckets').jstree('open_all');
$('#viewModels').show();
// ).bind("activate_node.jstree", function (evt, data)
// if (data != null && data.node != null && data.node.type == 'object')
// $("#forgeViewer").empty();
// var urn = data.node.id;
// jQuery.ajax(
// url: '/api/forge/modelderivative/manifest/' + urn,
// success: function (res)
// if (res.progress === 'success' || res.progress === 'complete') launchViewer(urn);
// else $("#forgeViewer").html('The translation job still running: ' + res.progress + '. Please try again in a moment.');
// ,
// error: function (err)
// var msgButton = 'This file is not translated yet! ' +
// '<button class="btn btn-xs btn-info" onclick="translateObject()"><span class="glyphicon glyphicon-eye-open"></span> ' +
// 'Start translation</button>'
// $("#forgeViewer").html(msgButton);
//
// );
//
);
function autodeskCustomMenu(autodeskNode)
var items;
switch (autodeskNode.type)
case "bucket":
items =
uploadFile:
label: "Upload file",
action: function ()
uploadFile();
,
icon: 'glyphicon glyphicon-cloud-upload'
;
break;
case "object":
items =
translateFile:
label: "Translate",
action: function ()
var treeNode = $('#appBuckets').jstree(true).get_selected(true)[0];
translateObject(treeNode);
,
icon: 'glyphicon glyphicon-eye-open'
;
break;
return items;
function uploadFile()
$('#hiddenUploadField').click();
function submitTranslation(node)
$("#forgeViewer").empty();
if (node == null) node = $('#appBuckets').jstree(true).get_selected(true)[0];
var bucketKey = node.parents[0];
var objectKey = node.id;
var rootDesignFilename = $('#rootDesignFilename').val();
var isSvf2 = $('#outputFormat :selected').text() === 'SVF2';
var xAdsForce = ($('#xAdsForce').is(':checked') === true);
var data = 'bucketKey': bucketKey, 'objectName': objectKey, 'isSvf2': (isSvf2 === true), 'xAdsForce': xAdsForce ;
if((rootDesignFilename && rootDesignFilename.trim() && rootDesignFilename.trim().length > 0))
data.rootFilename = rootDesignFilename;
data.compressedUrn = true;
jQuery.post(
url: '/api/forge/modelderivative/jobs',
contentType: 'application/json',
data: JSON.stringify(data),
success: function (res)
$('#CompositeTranslationModal').modal('hide');
$("#forgeViewer").html('Translation started! Please try again in a moment.');
,
);
function translateObject()
$('#CompositeTranslationModal').modal('show');
<!DOCTYPE html>
<html>
<head>
<title>Autodesk Forge Tutorial</title>
<meta charset="utf-8" />
<link rel="shortcut icon" href="https://github.com/Autodesk-Forge/learn.forge.viewmodels/raw/master/img/favicon.ico">
<!-- Common packages: jQuery, Bootstrap, jsTree -->
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/jstree/3.3.7/jstree.min.js"></script>
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css">
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/jstree/3.3.7/themes/default/style.min.css" />
<!-- Autodesk Forge Viewer files -->
<link rel="stylesheet" href="https://developer.api.autodesk.com/modelderivative/v2/viewers/7.*/style.min.css" type="text/css">
<script src="https://developer.api.autodesk.com/modelderivative/v2/viewers/7.*/viewer3D.js"></script>
<script src="https://unpkg.com/@tweenjs/tween.js@18.6.4/dist/tween.umd.js"></script>
<!-- MultipleModelUtil -->
<script src="http://cdn.jsdelivr.net/gh/yiskang/MultipleModelUtil/MultipleModelUtil.js"></script>
<!-- this project files -->
<link href="css/main.css" rel="stylesheet" />
<script src="js/ForgeTree.js"></script>
<script src="js/ForgeViewer.js"></script>
</head>
<body>
<!-- Fixed navbar by Bootstrap: https://getbootstrap.com/examples/navbar-fixed-top/ -->
<nav class="navbar navbar-default navbar-fixed-top">
<div class="container-fluid">
<ul class="nav navbar-nav left">
<li>
<a href="http://developer.autodesk.com" target="_blank">
<img src="//developer.static.autodesk.com/images/logo_forge-2-line.png" >
</a>
</li>
</ul>
</div>
</nav>
<!-- End of navbar -->
<div class="container-fluid fill">
<div class="row fill">
<div class="col-sm-2 fill">
<div class="panel panel-default fill">
<div class="panel-heading" data-toggle="tooltip">
Buckets & Objects
<span id="refreshBuckets" class="glyphicon glyphicon-refresh" style="cursor: pointer"></span>
<span id="viewModels" class="glyphicon glyphicon-eye-open" style="cursor: pointer; display: none" title="View selected models in the Forge Viewer"></span>
<button class="btn btn-xs btn-info" style="float: right" id="showFormCreateBucket" data-toggle="modal" data-target="#createBucketModal">
<span class="glyphicon glyphicon-folder-close"></span> New bucket
</button>
</div>
<div id="appBuckets">
tree here
</div>
</div>
</div>
<div class="col-sm-10 fill">
<div id="forgeViewer"></div>
</div>
</div>
</div>
<form id="uploadFile" method='post' enctype="multipart/form-data">
<input id="hiddenUploadField" type="file" name="theFile" style="visibility:hidden" />
</form>
<!-- Modal Create Bucket -->
<div class="modal fade" id="createBucketModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Cancel">
<span aria-hidden="true">×</span>
</button>
<h4 class="modal-title" id="myModalLabel">Create new bucket</h4>
</div>
<div class="modal-body">
<input type="text" id="newBucketKey" class="form-control"> For demonstration purposes, objects (files)
are NOT automatically translated. After you upload, right click on
the object and select "Translate". Note: Technically your bucket name is required to be globally unique across
the entire platform - to keep things simple with this tutorial your client ID will be prepended by default to
your bucket name and in turn masked by the UI so you only have to make sure your bucket name is unique within
your current Forge app.
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" id="createNewBucket">Go ahead, create the bucket</button>
</div>
</div>
</div>
</div>
<!-- Composite model translation -->
<div class="modal fade" id="CompositeTranslationModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Cancel">
<span aria-hidden="true">×</span>
</button>
<h4 class="modal-title" id="myModalLabel">Composite Translation</h4>
</div>
<div class="modal-body">
<div class="form-horizontal">
<div class="form-group">
<label for="outputFormat" class="col-sm-3 control-label">Output Format</label>
<div class="col-sm-9">
<select id="outputFormat" class="form-control">
<option>SVF</option>
<option selected="selected">SVF2</option>
</select>
</div>
</div>
<div class="form-group" style="margin-bottom: 0;">
<label for="rootDesignFilename" class="col-sm-3 control-label">Root Filename</label>
<div class="col-sm-9">
<input type="text" id="rootDesignFilename" class="form-control" placeholder="Enter the filename of the root design">
</div>
<div class="col-sm-12">
<span class="help-block" style="margin-bottom: 0; padding-left: 20px;">Enter the filename of the root design for the composite model. If the file is not a composite one, please press "Go ahead, submit translation" button directly.</span>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<div class="checkbox pull-left">
<label>
<input type="checkbox" id="xAdsForce"> Replace previous result
</label>
</div>
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" id="submitTranslation">Go ahead, submit translation</button>
</div>
</div>
</div>
</div>
</body>
</html>
以下是我所做的更改:https://github.com/yiskang/forge-viewmodels-nodejs-svf2/commit/5025b846970c2d95909b379f6704a51ca24caffd
这是演示快照:
【讨论】:
当我尝试加载模型时出现此错误 statusCode: 400, statusMessage: 'Bad Request', statusBody: ... arg0:statusCode: 400, statusMessage: 'Bad Request' , statusBody: … 抱歉,信息有限,很难判断。你能分享完整的错误信息和重现它的步骤吗?以上是关于如何在 Forge 中创建多模型加载器和查看器的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 Autodesk Forge 查看器离线查看模型?