这篇文章是翻译自 Autodesk Forge 博客的 Forge SVF Extractor in Node.js,作者是 Autodesk ADN Philippe 大神,更多细节请参考博客原文。
为了离线使用 Forge Viewer 自 Autodesk Forge 服务器提取 Forge SVF 文档的议题最近在 Autodesk Forge 相关讨论群变的越来越火热。Autodesk ADN Cyrille 大神的 https://extract.autodesk.io 也是近期内小友们讨论最热烈的提取方案,您可以在这个地方看到这个工程的整个代码 https://github.com/cyrillef/e...,整个提取 SVF 文档工程的主要思路被实作于
bubble.js,有兴趣的小友们可以参考参考。
然而当 Philippe 大神尝试将提取 SVF 的思路整合至他自己的应用里时,发现 bubble.js 与原工程的藕合度太高,很难直接拿来使用,所以在研究 bubble.js 几天后,Philippe 大神将里面整个思路提取出来,并使用 ES2015+ 的特性(如 Promise、async等)实作了自个的版本,他的代码如下方所示,想要看更细节的小友们可以参考 forge-rcdb.nodejs 工程:
// // copyright (c) Autodesk,Inc. All rights reserved // // 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. // // Forge Extractor // by Philippe Leefsma (original version by Cyrille Fauvel) // import BaseSvc from './BaseSvc' import archiver from 'archiver' import Forge from 'forge-apis' import request from 'request' import mkdirp from 'mkdirp' import Zip from 'node-zip' import Zlib from 'zlib' import path from 'path' import _ from 'lodash' import fs from 'fs' export default class ExtractorSvc extends BaseSvc { ///////////////////////////////////////////////////////// // // ///////////////////////////////////////////////////////// constructor (config) { super (config) this.derivativesAPI = new Forge.DerivativesApi() } ///////////////////////////////////////////////////////// // // ///////////////////////////////////////////////////////// name () { return 'ExtractorSvc' } ///////////////////////////////////////////////////////// // Create directory async // ///////////////////////////////////////////////////////// mkdirpAsync (dir) { return new Promise((resolve,reject) => { mkdirp(dir,(error) => { return error ? reject (error) : resolve() }) }) } ///////////////////////////////////////////////////////// // download all URN resources to target directory // (unzipped) // ///////////////////////////////////////////////////////// download (getToken,urn,directory) { return new Promise (async (resolve,reject) => { // make sure target dir exists await this.mkdirpAsync (directory) // get token,can be object token or an async // function that returns the token const token = ((typeof getToken == 'function') ? await getToken() : getToken) // get URN top level manifest const manifest = await this.derivativesAPI.getManifest ( urn,{},{autoRefresh:false},token) // harvest derivatives const derivatives = await this.getDerivatives ( getToken,manifest.body) // format derivative resources const nestedDerivatives = derivatives.map((item) => { return item.files.map((file) => { const localPath = path.resolve( directory,item.localPath) return { basePath: item.basePath,guid: item.guid,mime: item.mime,fileName: file,urn: item.urn,localPath } }) }) // flatten resources const derivativesList = _.flattenDeep( nestedDerivatives) // creates async download tasks for each // derivative file const downloadTasks = derivativesList.map( (derivative) => { return new Promise(async(resolve) => { const urn = path.join( derivative.basePath,derivative.fileName) const data = await this.getDerivative( getToken,urn) const filename = path.resolve( derivative.localPath,derivative.fileName) await this.savetodisk(data,filename) resolve(filename) }) }) // wait for all files to be downloaded const files = await Promise.all(downloadTasks) resolve(files) }) } ///////////////////////////////////////////////////////// // Parse top level manifest to collect derivatives // ///////////////////////////////////////////////////////// parseManifest (manifest) { const items = [] const parseNodeRec = (node) => { const roles = [ 'Autodesk.Cloudplatform.DesignDescription','Autodesk.Cloudplatform.PropertyDatabase','Autodesk.Cloudplatform.IndexableContent','leaflet-zip','thumbnail','graphics','preview','raas','pdf','lod',] if (roles.includes(node.role)) { const item = { guid: node.guid,mime: node.mime } const pathInfo = this.getPathInfo(node.urn) items.push (Object.assign({},item,pathInfo)) } if (node.children) { node.children.forEach ((child) => { parseNodeRec (child) }) } } parseNodeRec({ children: manifest.derivatives }) return items } ///////////////////////////////////////////////////////// // Collect derivatives for SVF // ///////////////////////////////////////////////////////// getSVFDerivatives (getToken,item) { return new Promise(async(resolve,reject) => { try { const svfPath = item.urn.slice ( item.basePath.length) const files = [svfPath] const data = await this.getDerivative ( getToken,item.urn) const pack = new Zip (data,{ checkCRC32: true,base64: false }) const manifestData = pack.files['manifest.json'].asNodeBuffer() const manifest = JSON.parse ( manifestData.toString('utf8')) if (manifest.assets) { manifest.assets.forEach((asset) => { // Skip SVF embedded resources if (asset.URI.indexOf('embed:/') === 0) { return } files.push(asset.URI) }) } return resolve( Object.assign({},{ files })) } catch (ex) { reject (ex) } }) } ///////////////////////////////////////////////////////// // Collect derivatives for F2D // ///////////////////////////////////////////////////////// getF2dDerivatives (getToken,reject) => { try { const files = ['manifest.json.gz'] const manifestPath = item.basePath + 'manifest.json.gz' const data = await this.getDerivative ( getToken,manifestPath) const manifestData = Zlib.gunzipSync(data) const manifest = JSON.parse ( manifestData.toString('utf8')) if (manifest.assets) { manifest.assets.forEach((asset) => { // Skip SVF embedded resources if (asset.URI.indexOf('embed:/') === 0) { return } files.push(asset.URI) }) } return resolve( Object.assign({},{ files })) } catch (ex) { reject (ex) } }) } ///////////////////////////////////////////////////////// // Get all derivatives from top level manifest // ///////////////////////////////////////////////////////// getDerivatives (getToken,manifest) { return new Promise(async(resolve,reject) => { const items = this.parseManifest(manifest) const derivativeTasks = items.map((item) => { switch (item.mime) { case 'application/autodesk-svf': return this.getSVFDerivatives( getToken,item) case 'application/autodesk-f2d': return this.getF2dDerivatives( getToken,item) case 'application/autodesk-db': return Promise.resolve( Object.assign({},{ files: [ 'objects_attrs.json.gz','objects_vals.json.gz','objects_offs.json.gz','objects_ids.json.gz','objects_avs.json.gz',item.rootFileName ]})) default: return Promise.resolve( Object.assign({},{ files: [ item.rootFileName ]})) } }) const derivatives = await Promise.all( derivativeTasks) return resolve(derivatives) }) } ///////////////////////////////////////////////////////// // Generate path information from URN // ///////////////////////////////////////////////////////// getPathInfo (encodedURN) { const urn = decodeURIComponent(encodedURN) const rootFileName = urn.slice ( urn.lastIndexOf ('/') + 1) const basePath = urn.slice ( 0,urn.lastIndexOf ('/') + 1) const localPathTmp = basePath.slice ( basePath.indexOf ('/') + 1) const localPath = localPathTmp.replace ( /^output\//,'') return { rootFileName,localPath,basePath,urn } } ///////////////////////////////////////////////////////// // Get derivative data for specific URN // ///////////////////////////////////////////////////////// getDerivative (getToken,urn) { return new Promise(async(resolve,reject) => { const baseUrl = 'https://developer.api.autodesk.com/' const url = baseUrl + `derivativeservice/v2/derivatives/${urn}` const token = ((typeof getToken == 'function') ? await getToken() : getToken) request({ url,method: 'GET',headers: { 'Authorization': 'Bearer ' + token.access_token,'Accept-Encoding': 'gzip,deflate' },encoding: null },(err,response,body) => { if (err) { return reject(err) } if (body && body.errors) { return reject(body.errors) } if ([200,201,202].indexOf( response.statusCode) < 0) { return reject(response) } return resolve(body || {}) }) }) } ///////////////////////////////////////////////////////// // Save data to disk // ///////////////////////////////////////////////////////// savetodisk (data,filename) { return new Promise(async(resolve,reject) => { await this.mkdirpAsync(path.dirname(filename)) const wstream = fs.createWriteStream(filename) const ext = path.extname(filename) wstream.on('finish',() => { resolve() }) if (typeof data === 'object' && ext === '.json') { wstream.write(JSON.stringify(data)) } else { wstream.write(data) } wstream.end() }) } ///////////////////////////////////////////////////////// // Create a zip // ///////////////////////////////////////////////////////// createZip (rootDir,zipfile,zipRoot,files) { return new Promise((resolve,reject) => { try { const output = fs.createWriteStream(zipfile) const archive = archiver('zip') output.on('close',() => { resolve() }) archive.on('error',(err) => { reject(err) }) archive.pipe(output) if (files) { files.forEach((file) => { try { const rs = fs.createReadStream(file) archive.append(rs,{ name: `${zipRoot}/${file.replace(rootDir,'')}` }) } catch(ex){ console.log(ex) } }) } else { archive.bulk([ { expand: false,src: [rootDir + '/*'] }]) } archive.finalize() } catch (ex) { reject(ex) } }) } }
// name of model to download const name = 'MyForgeModel' // URN of model to download const urn = 'dXGhsujdj .... ' // Get Forge service const forgeSvc = ServiceManager.getService( 'ForgeSvc') // getToken async function const getToken = () => forgeSvc.get2LeggedToken() // Get Extractor service const extractorSvc = ServiceManager.getService( 'ExtractorSvc') // target path to download SVF const dir = path.resolve(__dirname,`${name}`) // perform download const files = await extractorSvc.download( getToken,dir) // target zipfile const zipfile = dir + '.zip' // zip all files await extractorSvc.createZip( dir,name,files) // remove downloaded resources directory rmdir(dir)
如果想体验实际操作的小友们可以透过这个位址 https://forge-rcdb.autodesk.i... 上传自个的模型文档,点击Download SVF
即可。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。