From 77ff2a0c5d76e061c73399ecde359c25b94d094b Mon Sep 17 00:00:00 2001 From: Petr Broz Date: Tue, 9 Apr 2024 14:46:28 +0200 Subject: [PATCH] Updated branding & moved to official APS SDK. --- CHANGELOG.md | 9 + LICENSE.md | 2 +- README.md | 133 +++----- bin/{forge-convert.js => svf-to-gltf.js} | 62 ++-- logo.png | Bin 29869 -> 40419 bytes package.json | 22 +- samples/Dockerfile | 4 +- samples/custom-gltf-attribute.js | 34 +- samples/download-svf.js | 11 +- samples/filter-by-area.js | 20 +- samples/local-svf-props.js | 1 - samples/local-svf-to-gltf.js | 13 +- samples/local-svf-to-gltf.sh | 6 +- samples/remote-svf-props.js | 22 +- samples/remote-svf-to-gltf.js | 43 +-- samples/remote-svf-to-gltf.sh | 14 +- samples/serialize-msgpack.js | 18 +- samples/shared.js | 46 +++ src/common/authentication-provider.ts | 30 ++ src/f2d/downloader.ts | 53 ++- src/gltf/writer.ts | 4 +- src/index.ts | 2 +- src/svf/downloader.ts | 50 ++- src/svf/reader.ts | 70 ++-- test/remote-svf-to-gltf.sh | 6 +- yarn.lock | 393 +++++++++++++++++------ 26 files changed, 692 insertions(+), 376 deletions(-) rename bin/{forge-convert.js => svf-to-gltf.js} (55%) create mode 100644 samples/shared.js create mode 100644 src/common/authentication-provider.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 94417f2..a23513e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [5.0.0] - 2024-04-09 + +- Modified + - **[BREAKING CHANGE]** Library has been renamed from `forge-convert-utils` to `svf-utils` + - **[BREAKING CHANGE]** SVF readers and downloaders now expect an `IAuthenticationProvider` interface + for specifying how the requests to the Model Derivative service will be authenticated + - Changed branding from Forge to APS everywhere + - Migrated to the official APS SDKs + ## [4.0.5] - 2023-09-29 - Added diff --git a/LICENSE.md b/LICENSE.md index eef99aa..b21c931 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2022 Petr Broz +Copyright (c) 2024 Autodesk Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 20b8e4a..f7b330d 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,27 @@ -# forge-convert-utils +# svf-utils -![Publish to NPM](https://github.com/petrbroz/forge-convert-utils/workflows/Publish%20to%20NPM/badge.svg) -[![npm version](https://badge.fury.io/js/forge-convert-utils.svg)](https://badge.fury.io/js/forge-convert-utils) -![node](https://img.shields.io/node/v/forge-convert-utils.svg) -![npm downloads](https://img.shields.io/npm/dw/forge-convert-utils.svg) +![Publish to NPM](https://github.com/petrbroz/svf-utils/workflows/Publish%20to%20NPM/badge.svg) +[![npm version](https://badge.fury.io/js/svf-utils.svg)](https://badge.fury.io/js/svf-utils) +![node](https://img.shields.io/node/v/svf-utils.svg) +![npm downloads](https://img.shields.io/npm/dw/svf-utils.svg) ![platforms](https://img.shields.io/badge/platform-windows%20%7C%20osx%20%7C%20linux-lightgray.svg) [![license](https://img.shields.io/badge/license-MIT-blue.svg)](http://opensource.org/licenses/MIT) -![Forge & glTF logos](./logo.png) +![APS & glTF logos](./logo.png) -Utilities for converting [Autodesk Forge](https://forge.autodesk.com) SVF file format into +Utilities for converting [Autodesk Platform Services](https://aps.autodesk.com) SVF file format into [glTF 2.0](https://github.com/KhronosGroup/glTF/tree/master/specification/2.0). -> Check out [forge-convert-sqlite](https://github.com/petrbroz/forge-convert-sqlite) with an experimental -> serialization/deserialization of glTF to/from sqlite. - ## Usage ### Command line -- install the package: `npm install --global forge-convert-utils` -- run the `forge-convert` command without parameters for usage info +- install the package: `npm install --global svf-utils` +- run the `svf-to-gltf` command without parameters for usage info - run the command with a path to a local SVF file - run the command with a Model Derivative URN (and optionally viewable GUID) - - to access Forge you must also specify credentials (`FORGE_CLIENT_ID` and `FORGE_CLIENT_SECRET`) - or an authentication token (`FORGE_ACCESS_TOKEN`) as env. variables + - to access APS you must also specify credentials (`APS_CLIENT_ID` and `APS_CLIENT_SECRET`) + or an authentication token (`APS_ACCESS_TOKEN`) as env. variables - this will also download the property database in sqlite format - optionally, use any combination of the following command line args: - `--output-folder ` to change output folder (by default '.') @@ -38,43 +35,43 @@ Utilities for converting [Autodesk Forge](https://forge.autodesk.com) SVF file f #### Unix/macOS ``` -forge-convert --output-folder +svf-to-gltf --output-folder ``` or ``` -export FORGE_CLIENT_ID= -export FORGE_CLIENT_SECRET= -forge-convert --output-folder +export APS_CLIENT_ID= +export APS_CLIENT_SECRET= +svf-to-gltf --output-folder ``` or ``` -export FORGE_ACCESS_TOKEN=> -forge-convert --output-folder +export APS_ACCESS_TOKEN= +svf-to-gltf --output-folder ``` #### Windows ``` -forge-convert --output-folder +svf-to-gltf --output-folder ``` or ``` -set FORGE_CLIENT_ID= -set FORGE_CLIENT_SECRET= -forge-convert --output-folder +set APS_CLIENT_ID= +set APS_CLIENT_SECRET= +svf-to-gltf --output-folder ``` or ``` -set FORGE_ACCESS_TOKEN= -forge-convert --output-folder +set APS_ACCESS_TOKEN= +svf-to-gltf --output-folder ``` ### Node.js @@ -82,81 +79,41 @@ forge-convert --output-folder The library can be used at different levels of granularity. The easiest way to convert an SVF file is to read the entire model into memory -using [SvfReader#read](https://petrbroz.github.io/forge-convert-utils/docs/classes/_svf_reader_.reader.html#read) -method, and save the model into glTF using [GltfWriter#write](https://petrbroz.github.io/forge-convert-utils/docs/classes/_gltf_writer_.writer.html#write): - -```js -const path = require('path'); -const { ModelDerivativeClient, ManifestHelper } = require('forge-server-utils'); -const { SvfReader, GltfWriter } = require('forge-convert-utils'); - -const { FORGE_CLIENT_ID, FORGE_CLIENT_SECRET } = process.env; - -async function run(urn, outputDir) { - const auth = { client_id: FORGE_CLIENT_ID, client_secret: FORGE_CLIENT_SECRET }; - const modelDerivativeClient = new ModelDerivativeClient(auth); - const manifestHelper = new ManifestHelper(await modelDerivativeClient.getManifest(urn)); - const derivatives = manifestHelper.search({ type: 'resource', role: 'graphics' }); - const readerOptions = { - log: console.log - }; - const writerOptions = { - deduplicate: true, - skipUnusedUvs: true, - center: true, - log: console.log, - filter: (dbid) => (dbid >= 100 && dbid <= 200) // only output objects with dbIDs between 100 and 200 - }; - const writer = new GltfWriter(writerOptions); - for (const derivative of derivatives.filter(d => d.mime === 'application/autodesk-svf')) { - const reader = await SvfReader.FromDerivativeService(urn, derivative.guid, auth); - const scene = await reader.read(readerOptions); - await writer.write(scene, path.join(outputDir, derivative.guid)); - } -} - -run('your model urn', 'path/to/output/folder'); -``` +using [SvfReader#read](https://petrbroz.github.io/svf-utils/docs/classes/_svf_reader_.reader.html#read) +method, and save the model into glTF using [GltfWriter#write](https://petrbroz.github.io/svf-utils/docs/classes/_gltf_writer_.writer.html#write): +[samples/remote-svf-to-gltf.js](./samples/remote-svf-to-gltf.js). If you don't want to read the entire model into memory (for example, when distributing the parsing of an SVF over multiple servers), you can use methods like -[SvfReader#enumerateFragments](https://petrbroz.github.io/forge-convert-utils/docs/classes/_svf_reader_.reader.html#enumeratefragments) -or [SvfReader#enumerateGeometries](https://petrbroz.github.io/forge-convert-utils/docs/classes/_svf_reader_.reader.html#enumerategeometries) +[SvfReader#enumerateFragments](https://petrbroz.github.io/svf-utils/docs/classes/_svf_reader_.reader.html#enumeratefragments) +or [SvfReader#enumerateGeometries](https://petrbroz.github.io/svf-utils/docs/classes/_svf_reader_.reader.html#enumerategeometries) to _asynchronously_ iterate over individual elements: ```js -const { ModelDerivativeClient, ManifestHelper } = require('forge-server-utils'); -const { SvfReader } = require('forge-convert-utils'); - -const { FORGE_CLIENT_ID, FORGE_CLIENT_SECRET } = process.env; - -async function run (urn) { - const auth = { client_id: FORGE_CLIENT_ID, client_secret: FORGE_CLIENT_SECRET }; - const modelDerivativeClient = new ModelDerivativeClient(auth); - const manifestHelper = new ManifestHelper(await modelDerivativeClient.getManifest(urn)); - const derivatives = manifestHelper.search({ type: 'resource', role: 'graphics' }); - for (const derivative of derivatives.filter(d => d.mime === 'application/autodesk-svf')) { - const reader = await SvfReader.FromDerivativeService(urn, derivative.guid, auth); - for await (const fragment of reader.enumerateFragments()) { - console.log(fragment); - } - } -} +const { SvfReader } = require('svf-utils'); + +// ... -run('your model urn'); +const reader = await SvfReader.FromDerivativeService(urn, guid, authProvider); +for await (const fragment of reader.enumerateFragments()) { + console.log(fragment); +} ``` And finally, if you already have the individual SVF assets in memory, you can parse the binary data -directly using _synchronous_ iterators like [parseMeshes](https://petrbroz.github.io/forge-convert-utils/docs/modules/_svf_meshes_.html#parsemeshes): +directly using _synchronous_ iterators like [parseMeshes](https://petrbroz.github.io/svf-utils/docs/modules/_svf_meshes_.html#parsemeshes): ```js -const { parseMeshes } = require('forge-convert-utils/lib/svf/meshes'); +const { parseMeshes } = require('svf-utils/lib/svf/meshes'); + +// ... + for (const mesh of parseMeshes(buffer)) { console.log(mesh); } ``` -> For additional examples, see the [test](./test) subfolder. +> For additional examples, see the [samples](./samples) subfolder. ### Customization @@ -167,7 +124,7 @@ You can customize the translation by sub-classing the reader and/or the writer c ### Metadata -When converting models from [Model Derivative service](https://forge.autodesk.com/en/docs/model-derivative/v2), +When converting models from [Model Derivative service](https://aps.autodesk.com/en/docs/model-derivative/v2), you can retrieve the model's properties and metadata in form of a sqlite database. The command line tool downloads this database automatically as _properties.sqlite_ file directly in your output folder. If you're using this library in your own Node.js code, you can find the database in the manifest by looking for an asset with type "resource", @@ -213,7 +170,7 @@ See [./samples/local-svf-to-gltf.sh](./samples/local-svf-to-gltf.sh) or - clone the repository - install dependencies: `yarn install` - build the library (transpile TypeScript): `yarn run build` -- run samples in the _test_ subfolder, for example: `FORGE_CLIENT_ID= FORGE_CLIENT_SECRET= node test/remote-svf-to-gltf.js ` +- run samples in the _test_ subfolder, for example: `APS_CLIENT_ID= APS_CLIENT_SECRET= node test/remote-svf-to-gltf.js ` If you're using [Visual Studio Code](https://code.visualstudio.com), you can use the following "task" and "launch" configurations: @@ -252,8 +209,8 @@ In _.vscode/launch.json_: "program": "${workspaceFolder}/test/remote-svf-to-gltf.js", "args": ["", ""], "env": { - "FORGE_CLIENT_ID": "", - "FORGE_CLIENT_SECRET": "" + "APS_CLIENT_ID": "", + "APS_CLIENT_SECRET": "" }, "preLaunchTask": "build" }, diff --git a/bin/forge-convert.js b/bin/svf-to-gltf.js similarity index 55% rename from bin/forge-convert.js rename to bin/svf-to-gltf.js index df83061..5a492b1 100755 --- a/bin/forge-convert.js +++ b/bin/svf-to-gltf.js @@ -2,22 +2,22 @@ const program = require('commander'); const path = require('path'); -const fse = require('fs-extra'); -const { ModelDerivativeClient, ManifestHelper } = require('forge-server-utils'); +const { SdkManagerBuilder } = require('@aps_sdk/autodesk-sdkmanager'); +const { ModelDerivativeClient} = require('@aps_sdk/model-derivative'); +const { Scopes } = require('@aps_sdk/authentication'); +const { SvfReader, GltfWriter, BasicAuthenticationProvider, TwoLeggedAuthenticationProvider } = require('../lib'); -const { SvfReader, GltfWriter } = require('..'); - -const { FORGE_CLIENT_ID, FORGE_CLIENT_SECRET, FORGE_ACCESS_TOKEN } = process.env; -let auth = null; -if (FORGE_ACCESS_TOKEN) { - auth = { token: FORGE_ACCESS_TOKEN }; -} else if (FORGE_CLIENT_ID && FORGE_CLIENT_SECRET) { - auth = { client_id: FORGE_CLIENT_ID, client_secret: FORGE_CLIENT_SECRET }; +const { APS_CLIENT_ID, APS_CLIENT_SECRET, APS_ACCESS_TOKEN } = process.env; +let authenticationProvider = null; +if (APS_ACCESS_TOKEN) { + authenticationProvider = new BasicAuthenticationProvider(APS_ACCESS_TOKEN); +} else if (APS_CLIENT_ID && APS_CLIENT_SECRET) { + authenticationProvider = new TwoLeggedAuthenticationProvider(APS_CLIENT_ID, APS_CLIENT_SECRET); } async function convertRemote(urn, guid, outputFolder, options) { console.log(`Converting urn ${urn}, guid ${guid}`); - const reader = await SvfReader.FromDerivativeService(urn, guid, auth); + const reader = await SvfReader.FromDerivativeService(urn, guid, authenticationProvider); const scene = await reader.read({ log: console.log }); const writer = new GltfWriter(options); await writer.write(scene, path.join(outputFolder, guid)); @@ -59,31 +59,43 @@ program } else { // ID is the Model Derivative URN // Convert input guid or all guids - if (!auth) { - console.warn('Missing environment variables for Autodesk Forge authentication.'); - console.warn('Provide FORGE_CLIENT_ID and FORGE_CLIENT_SECRET, or FORGE_ACCESS_TOKEN.'); + if (!authenticationProvider) { + console.warn('Missing environment variables for APS authentication.'); + console.warn('Provide APS_CLIENT_ID and APS_CLIENT_SECRET, or APS_ACCESS_TOKEN.'); return; } const urn = id; - const client = new ModelDerivativeClient(auth); - const helper = new ManifestHelper(await client.getManifest(urn)); const folder = path.join(program.outputFolder, urn); if (guid) { await convertRemote(urn, guid, folder, options); } else { - const derivatives = helper.search({ type: 'resource', role: 'graphics' }); - for (const derivative of derivatives.filter(d => d.mime === 'application/autodesk-svf')) { + const sdkManager = SdkManagerBuilder.create().build(); + const modelDerivativeClient = new ModelDerivativeClient(sdkManager); + const accessToken = await authenticationProvider.getToken([Scopes.ViewablesRead]); + const manifest = await modelDerivativeClient.getManifest(accessToken, urn); + const derivatives = []; + function traverse(derivative) { + if (derivative.type === 'resource' && derivative.role === 'graphics' && derivative.mime === 'application/autodesk-svf') { + derivatives.push(derivative); + } + if (derivative.children) { + for (const child of derivative.children) { + traverse(child); + } + } + } + for (const derivative of manifest.derivatives) { + if (derivative.children) { + for (const child of derivative.children) { + traverse(child); + } + } + } + for (const derivative of derivatives) { await convertRemote(urn, derivative.guid, folder, options); } } - - // Store the property database within the subfolder (it is shared by all viewables) - const pdbDerivatives = helper.search({ type: 'resource', role: 'Autodesk.CloudPlatform.PropertyDatabase' }); - if (pdbDerivatives.length > 0) { - const databaseStream = client.getDerivativeChunked(urn, pdbDerivatives[0].urn, 1 << 20); - databaseStream.pipe(fse.createWriteStream(path.join(folder, 'properties.sqlite'))); - } } } catch (err) { console.error(err); diff --git a/logo.png b/logo.png index 740747db612c4771984f23cf5f4db8502c5ff2fe..46ed6624efb4094cd6fc0f63874d919d73e6c367 100644 GIT binary patch literal 40419 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 me&KT~jB8qUTpf3Am&S7~m7=GX{{I7GR7lbQ literal 29869 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.15.0" @@ -16,19 +16,18 @@ "author": "Petr Broz ", "license": "MIT", "keywords": [ - "autodesk", - "forge", + "autodesk-platform-services", "gltf", "typescript" ], "repository": { "type": "git", - "url": "git+https://github.com/petrbroz/forge-convert-utils.git" + "url": "git+https://github.com/petrbroz/svf-utils.git" }, "bugs": { - "url": "https://github.com/petrbroz/forge-convert-utils/issues" + "url": "https://github.com/petrbroz/svf-utils/issues" }, - "homepage": "https://github.com/petrbroz/forge-convert-utils#readme", + "homepage": "https://github.com/petrbroz/svf-utils#readme", "devDependencies": { "@types/adm-zip": "^0.4.32", "@types/fs-extra": "^9.0.13", @@ -37,9 +36,12 @@ "typescript": "^4.5.5" }, "dependencies": { + "@aps_sdk/authentication": "^0.1.0-beta.1", + "@aps_sdk/autodesk-sdkmanager": "^0.0.7-beta.1", + "@aps_sdk/model-derivative": "^0.1.0-beta.1", "adm-zip": "^0.5.9", + "axios": "^1.6.8", "commander": "^3.0.2", - "forge-server-utils": "^8.3.5", "fs-extra": "^10.0.0" } } diff --git a/samples/Dockerfile b/samples/Dockerfile index 07c86d2..5c3ca58 100644 --- a/samples/Dockerfile +++ b/samples/Dockerfile @@ -1,7 +1,7 @@ FROM node:lts LABEL maintainer="petr.broz@autodesk.com" -LABEL description="Docker image for experimenting with forge-convert-utils and other glTF tools." +LABEL description="Docker image for experimenting with svf-utils and other glTF tools." # Prepare gltfpack RUN wget https://github.com/zeux/meshoptimizer/releases/download/v0.13/gltfpack-0.13-ubuntu.zip -O /tmp/gltfpack.zip @@ -11,7 +11,7 @@ RUN rm /tmp/gltfpack.zip # Install Node.js dependencies RUN npm install --global gltf-pipeline@^2.0.0 -RUN npm install --global forge-convert-utils@^2.0.0 +RUN npm install --global svf-utils@^5.0.0 # Add test scripts ADD *.sh *.js /tmp/ diff --git a/samples/custom-gltf-attribute.js b/samples/custom-gltf-attribute.js index 9e70d06..4b83015 100644 --- a/samples/custom-gltf-attribute.js +++ b/samples/custom-gltf-attribute.js @@ -2,14 +2,14 @@ * Example: converting an SVF from Model Derivative service into glTF, * embedding object IDs into the COLOR_0 mesh channel. * Usage: - * export FORGE_CLIENT_ID= - * export FORGE_CLIENT_SECRET= + * export APS_CLIENT_ID= + * export APS_CLIENT_SECRET= * node custom-gltf-attribute.js */ const path = require('path'); -const { ModelDerivativeClient, ManifestHelper } = require('forge-server-utils'); -const { SvfReader, GltfWriter } = require('..'); +const { getSvfDerivatives } = require('./shared.js'); +const { SvfReader, GltfWriter, TwoLeggedAuthenticationProvider } = require('..'); /* * Customized glTF writer, outputting meshes with an additional _CUSTOM_INDEX @@ -60,24 +60,20 @@ class CustomGltfWriter extends GltfWriter { } } -const { FORGE_CLIENT_ID, FORGE_CLIENT_SECRET } = process.env; +const { APS_CLIENT_ID, APS_CLIENT_SECRET } = process.env; async function run(urn, outputDir) { - const DefaultOptions = { - deduplicate: false, - skipUnusedUvs: false, - center: true, - log: console.log - }; - try { - const auth = { client_id: FORGE_CLIENT_ID, client_secret: FORGE_CLIENT_SECRET }; - const modelDerivativeClient = new ModelDerivativeClient(auth); - const helper = new ManifestHelper(await modelDerivativeClient.getManifest(urn)); - const derivatives = helper.search({ type: 'resource', role: 'graphics' }); - const writer = new CustomGltfWriter(Object.assign({}, DefaultOptions)); - for (const derivative of derivatives.filter(d => d.mime === 'application/autodesk-svf')) { - const reader = await SvfReader.FromDerivativeService(urn, derivative.guid, auth); + const derivatives = await getSvfDerivatives(urn, APS_CLIENT_ID, APS_CLIENT_SECRET); + const authenticationProvider = new TwoLeggedAuthenticationProvider(APS_CLIENT_ID, APS_CLIENT_SECRET); + const writer = new CustomGltfWriter({ + deduplicate: false, + skipUnusedUvs: false, + center: true, + log: console.log + }); + for (const derivative of derivatives) { + const reader = await SvfReader.FromDerivativeService(urn, derivative.guid, authenticationProvider); const scene = await reader.read({ log: console.log }); await writer.write(scene, path.join(outputDir, derivative.guid)); } diff --git a/samples/download-svf.js b/samples/download-svf.js index d9cb37c..3e94f52 100644 --- a/samples/download-svf.js +++ b/samples/download-svf.js @@ -1,16 +1,17 @@ /* * Example: downloading SVF assets for all viewables in a Model Derivative URN. * Usage: - * export FORGE_CLIENT_ID= - * export FORGE_CLIENT_SECRET= + * export APS_CLIENT_ID= + * export APS_CLIENT_SECRET= * node download-svf.js */ -const { SvfDownloader } = require('..'); -const { FORGE_CLIENT_ID, FORGE_CLIENT_SECRET } = process.env; +const { SvfDownloader, TwoLeggedAuthenticationProvider } = require('..'); +const { APS_CLIENT_ID, APS_CLIENT_SECRET } = process.env; async function run(urn, outputDir = '.') { - const downloader = new SvfDownloader({ client_id: FORGE_CLIENT_ID, client_secret: FORGE_CLIENT_SECRET }); + const authenticationProvider = new TwoLeggedAuthenticationProvider(APS_CLIENT_ID, APS_CLIENT_SECRET); + const downloader = new SvfDownloader(authenticationProvider); const download = downloader.download(urn, { outputDir, log: console.log }); await download.ready; } diff --git a/samples/filter-by-area.js b/samples/filter-by-area.js index 2534288..5d8599c 100644 --- a/samples/filter-by-area.js +++ b/samples/filter-by-area.js @@ -2,15 +2,15 @@ * Example: converting a subset of SVF (based on a specified area) into glTF. * Usage: * npm install --save gl-matrix - * export FORGE_CLIENT_ID= - * export FORGE_CLIENT_SECRET= + * export APS_CLIENT_ID= + * export APS_CLIENT_SECRET= * node filter-by-area.js */ const path = require('path'); -const { ModelDerivativeClient, ManifestHelper } = require('forge-server-utils'); -const { SvfReader, GltfWriter } = require('../lib'); const { mat4, vec3 } = require('gl-matrix'); +const { getSvfDerivatives } = require('./shared.js'); +const { SvfReader, GltfWriter, TwoLeggedAuthenticationProvider } = require('..'); /* * Customized glTF writer, only outputting meshes completely contained in a specified area. @@ -96,7 +96,7 @@ class AreaFilteredGltfWriter extends GltfWriter { } } -const { FORGE_CLIENT_ID, FORGE_CLIENT_SECRET } = process.env; +const { APS_CLIENT_ID, APS_CLIENT_SECRET } = process.env; async function run(urn, outputDir) { const DefaultOptions = { @@ -107,13 +107,11 @@ async function run(urn, outputDir) { }; try { - const auth = { client_id: FORGE_CLIENT_ID, client_secret: FORGE_CLIENT_SECRET }; - const modelDerivativeClient = new ModelDerivativeClient(auth); - const helper = new ManifestHelper(await modelDerivativeClient.getManifest(urn)); - const derivatives = helper.search({ type: 'resource', role: 'graphics' }); + const derivatives = await getSvfDerivatives(urn, APS_CLIENT_ID, APS_CLIENT_SECRET); + const authenticationProvider = new TwoLeggedAuthenticationProvider(APS_CLIENT_ID, APS_CLIENT_SECRET); const writer = new AreaFilteredGltfWriter(Object.assign({}, DefaultOptions), [-25.0, -25.0, -25.0], [25.0, 25.0, 25.0]); - for (const derivative of derivatives.filter(d => d.mime === 'application/autodesk-svf')) { - const reader = await SvfReader.FromDerivativeService(urn, derivative.guid, auth); + for (const derivative of derivatives) { + const reader = await SvfReader.FromDerivativeService(urn, derivative.guid, authenticationProvider); const scene = await reader.read({ log: console.log }); await writer.write(scene, path.join(outputDir, derivative.guid)); } diff --git a/samples/local-svf-props.js b/samples/local-svf-props.js index 2ce1bc5..96cfc05 100644 --- a/samples/local-svf-props.js +++ b/samples/local-svf-props.js @@ -15,7 +15,6 @@ function run(dir) { const attrs = fs.readFileSync(path.join(dir, 'objects_attrs.json.gz')); const vals = fs.readFileSync(path.join(dir, 'objects_vals.json.gz')); const db = new PropDbReader(ids, offs, avs, attrs, vals); - const numObjects = offs.length - 1; for (let dbid = 1; dbid < numObjects; dbid++) { console.log(`Properties of #${dbid}`); diff --git a/samples/local-svf-to-gltf.js b/samples/local-svf-to-gltf.js index 13d30d1..bba5e3d 100644 --- a/samples/local-svf-to-gltf.js +++ b/samples/local-svf-to-gltf.js @@ -7,21 +7,14 @@ const path = require('path'); const { SvfReader, GltfWriter } = require('..'); -async function run (filepath, outputDir) { - const defaultOptions = { - deduplicate: false, - skipUnusedUvs: false, - center: true, - log: console.log - }; - +async function run(filepath, outputDir) { try { const reader = await SvfReader.FromFileSystem(filepath); const scene = await reader.read(); let writer; - writer = new GltfWriter(Object.assign({}, defaultOptions)); + writer = new GltfWriter({ deduplicate: false, skipUnusedUvs: false, center: true, log: console.log }); await writer.write(scene, path.join(outputDir, 'gltf-raw')); - writer = new GltfWriter(Object.assign({}, defaultOptions, { deduplicate: true, skipUnusedUvs: true })); + writer = new GltfWriter({ deduplicate: true, skipUnusedUvs: true, center: true, log: console.log }); await writer.write(scene, path.join(outputDir, 'gltf-dedup')); } catch(err) { console.error(err); diff --git a/samples/local-svf-to-gltf.sh b/samples/local-svf-to-gltf.sh index 303c93f..ae25b61 100755 --- a/samples/local-svf-to-gltf.sh +++ b/samples/local-svf-to-gltf.sh @@ -7,10 +7,10 @@ # ./local-svf-to-gltf.sh # Install dependencies -npm install --global forge-convert-utils gltf-pipeline +npm install --global svf-utils gltf-pipeline -# Convert SVF to glTF with [forge-convert-utils](https://github.com/petrbroz/forge-convert-utils) -forge-convert $1 --output-folder $2/gltf --deduplicate --skip-unused-uvs --ignore-lines --ignore-points +# Convert SVF to glTF with [svf-utils](https://github.com/petrbroz/svf-utils) +svf-to-gltf $1 --output-folder $2/gltf --deduplicate --skip-unused-uvs --ignore-lines --ignore-points # Validate glTF using [gltf-validator](https://github.com/KhronosGroup/glTF-Validator), if available if [ -x "$(command -v gltf_validator)" ]; then diff --git a/samples/remote-svf-props.js b/samples/remote-svf-props.js index dd6a000..2a2fd1a 100644 --- a/samples/remote-svf-props.js +++ b/samples/remote-svf-props.js @@ -1,23 +1,21 @@ /* * Example: parsing object properties for all viewables in a Model Derivative URN. * Usage: - * export FORGE_CLIENT_ID= - * export FORGE_CLIENT_SECRET= + * export APS_CLIENT_ID= + * export APS_CLIENT_SECRET= * node remote-svf-props.js */ -const { ModelDerivativeClient, ManifestHelper } = require('forge-server-utils'); -const { SvfReader } = require('..'); +const { getSvfDerivatives } = require('./shared.js'); +const { SvfReader, TwoLeggedAuthenticationProvider } = require('..'); -const { FORGE_CLIENT_ID, FORGE_CLIENT_SECRET } = process.env; +const { APS_CLIENT_ID, APS_CLIENT_SECRET } = process.env; -async function run (urn) { - const auth = { client_id: FORGE_CLIENT_ID, client_secret: FORGE_CLIENT_SECRET }; - const modelDerivativeClient = new ModelDerivativeClient(auth); - const helper = new ManifestHelper(await modelDerivativeClient.getManifest(urn)); - const derivatives = helper.search({ type: 'resource', role: 'graphics' }); - for (const derivative of derivatives.filter(d => d.mime === 'application/autodesk-svf')) { - const reader = await SvfReader.FromDerivativeService(urn, derivative.guid, auth); +async function run(urn) { + const derivatives = await getSvfDerivatives(urn, APS_CLIENT_ID, APS_CLIENT_SECRET); + const authenticationProvider = new TwoLeggedAuthenticationProvider(APS_CLIENT_ID, APS_CLIENT_SECRET); + for (const derivative of derivatives) { + const reader = await SvfReader.FromDerivativeService(urn, derivative.guid, authenticationProvider); const propdb = await reader.getPropertyDb(); const props = propdb.getProperties(1); for (const name of Object.keys(props)) { diff --git a/samples/remote-svf-to-gltf.js b/samples/remote-svf-to-gltf.js index 43b620a..f5c0ca1 100644 --- a/samples/remote-svf-to-gltf.js +++ b/samples/remote-svf-to-gltf.js @@ -1,46 +1,29 @@ /* - * Example: converting an SVF (incl. property database) from Model Derivative service. + * Example: converting an SVF from Model Derivative service. * Usage: - * export FORGE_CLIENT_ID= - * export FORGE_CLIENT_SECRET= + * export APS_CLIENT_ID= + * export APS_CLIENT_SECRET= * node remote-svf-to-gltf.js */ const path = require('path'); -const fs = require('fs'); -const { ModelDerivativeClient, ManifestHelper } = require('forge-server-utils'); -const { SvfReader, GltfWriter } = require('..'); +const { getSvfDerivatives } = require('./shared.js'); +const { SvfReader, GltfWriter, TwoLeggedAuthenticationProvider } = require('..'); -const { FORGE_CLIENT_ID, FORGE_CLIENT_SECRET } = process.env; - -async function run (urn, outputDir) { - const defaultOptions = { - deduplicate: false, - skipUnusedUvs: false, - center: true, - log: console.log - }; +const { APS_CLIENT_ID, APS_CLIENT_SECRET } = process.env; +async function run(urn, outputDir) { try { - const auth = { client_id: FORGE_CLIENT_ID, client_secret: FORGE_CLIENT_SECRET }; - const modelDerivativeClient = new ModelDerivativeClient(auth); - const helper = new ManifestHelper(await modelDerivativeClient.getManifest(urn)); - const derivatives = helper.search({ type: 'resource', role: 'graphics' }); - const writer0 = new GltfWriter(Object.assign({}, defaultOptions)); - const writer1 = new GltfWriter(Object.assign({}, defaultOptions, { deduplicate: true, skipUnusedUvs: true })); - // Convert individual 3D viewables into glTFs - for (const derivative of derivatives.filter(d => d.mime === 'application/autodesk-svf')) { - const reader = await SvfReader.FromDerivativeService(urn, derivative.guid, auth); + const derivatives = await getSvfDerivatives(urn, APS_CLIENT_ID, APS_CLIENT_SECRET); + const writer0 = new GltfWriter({ deduplicate: false, skipUnusedUvs: false, center: true, log: console.log }); + const writer1 = new GltfWriter({ deduplicate: true, skipUnusedUvs: true, center: true, log: console.log }); + const authenticationProvider = new TwoLeggedAuthenticationProvider(APS_CLIENT_ID, APS_CLIENT_SECRET); + for (const derivative of derivatives) { + const reader = await SvfReader.FromDerivativeService(urn, derivative.guid, authenticationProvider); const scene = await reader.read({ log: console.log }); await writer0.write(scene, path.join(outputDir, derivative.guid, 'gltf-raw')); await writer1.write(scene, path.join(outputDir, derivative.guid, 'gltf-dedup')); } - // Also download property database in sqlite form - const pdbDerivatives = helper.search({ type: 'resource', role: 'Autodesk.CloudPlatform.PropertyDatabase' }); - if (pdbDerivatives.length > 0) { - const databaseStream = modelDerivativeClient.getDerivativeChunked(urn, pdbDerivatives[0].urn, 1 << 20); - databaseStream.pipe(fs.createWriteStream(path.join(outputDir, 'properties.sqlite'))); - } } catch(err) { console.error(err); process.exit(1); diff --git a/samples/remote-svf-to-gltf.sh b/samples/remote-svf-to-gltf.sh index 1774c43..75fec04 100755 --- a/samples/remote-svf-to-gltf.sh +++ b/samples/remote-svf-to-gltf.sh @@ -3,20 +3,20 @@ # Script converting an SVF (without property database) from Model Derivative service # to glTF, and post-processing it using various 3rd party tools. # -# Usage example with Forge credentials: -# export FORGE_CLIENT_ID= -# export FORGE_CLIENT_SECRET= +# Usage example with APS credentials: +# export APS_CLIENT_ID= +# export APS_CLIENT_SECRET= # ./remote-svf-to-gltf.sh # # Usage example with an existing token: -# export FORGE_ACCESS_TOKEN= +# export APS_ACCESS_TOKEN= # ./remote-svf-to-gltf.sh # Install dependencies -npm install --global forge-convert-utils gltf-pipeline +npm install --global svf-utils gltf-pipeline -# Convert SVF to glTF with [forge-convert-utils](https://github.com/petrbroz/forge-convert-utils) -forge-convert $1 --output-folder $2/gltf --deduplicate --skip-unused-uvs --ignore-lines --ignore-points +# Convert SVF to glTF with [svf-utils](https://github.com/petrbroz/svf-utils) +svf-to-gltf $1 --output-folder $2/gltf --deduplicate --skip-unused-uvs --ignore-lines --ignore-points # Iterate over glTFs generated for all viewables (in / subfolders) for gltf in $(find $2/gltf -name "output.gltf"); do diff --git a/samples/serialize-msgpack.js b/samples/serialize-msgpack.js index 30d2e18..0708609 100644 --- a/samples/serialize-msgpack.js +++ b/samples/serialize-msgpack.js @@ -3,8 +3,8 @@ const fs = require('fs'); const path = require('path'); const { pack } = require('msgpackr'); -const { ModelDerivativeClient, ManifestHelper } = require('forge-server-utils'); -const { SvfReader, GltfWriter } = require('..'); +const { getSvfDerivatives } = require('./shared.js'); +const { SvfReader, GltfWriter, TwoLeggedAuthenticationProvider } = require('..'); class MsgpackGltfWriter extends GltfWriter { serializeManifest(manifest, outputPath) { @@ -13,17 +13,15 @@ class MsgpackGltfWriter extends GltfWriter { } } -const { FORGE_CLIENT_ID, FORGE_CLIENT_SECRET } = process.env; +const { APS_CLIENT_ID, APS_CLIENT_SECRET } = process.env; -async function run (urn, outputDir) { +async function run(urn, outputDir) { try { - const auth = { client_id: FORGE_CLIENT_ID, client_secret: FORGE_CLIENT_SECRET }; - const modelDerivativeClient = new ModelDerivativeClient(auth); - const helper = new ManifestHelper(await modelDerivativeClient.getManifest(urn)); - const derivatives = helper.search({ type: 'resource', role: 'graphics' }); + const derivatives = await getSvfDerivatives(urn, APS_CLIENT_ID, APS_CLIENT_SECRET); + const authenticationProvider = new TwoLeggedAuthenticationProvider(APS_CLIENT_ID, APS_CLIENT_SECRET); const writer = new MsgpackGltfWriter({ deduplicate: true, center: true, log: console.log }); - for (const derivative of derivatives.filter(d => d.mime === 'application/autodesk-svf')) { - const reader = await SvfReader.FromDerivativeService(urn, derivative.guid, auth); + for (const derivative of derivatives) { + const reader = await SvfReader.FromDerivativeService(urn, derivative.guid, authenticationProvider); const scene = await reader.read({ log: console.log }); await writer.write(scene, path.join(outputDir, derivative.guid)); } diff --git a/samples/shared.js b/samples/shared.js new file mode 100644 index 0000000..91e3d8e --- /dev/null +++ b/samples/shared.js @@ -0,0 +1,46 @@ +const axios = require('axios').default; +const { SdkManagerBuilder } = require('@aps_sdk/autodesk-sdkmanager'); +const { AuthenticationClient, Scopes } = require('@aps_sdk/authentication'); +const { ModelDerivativeClient } = require('@aps_sdk/model-derivative'); + +async function downloadDerivative(urn, derivativeUrn, clientId, clientSecret) { + const sdkManager = SdkManagerBuilder.create().build(); + const authenticationClient = new AuthenticationClient(sdkManager); + const modelDerivativeClient = new ModelDerivativeClient(sdkManager); + const credentials = await authenticationClient.getTwoLeggedToken(clientId, clientSecret, [Scopes.ViewablesRead]); + const downloadInfo = await modelDerivativeClient.getDerivativeUrl(credentials.access_token, derivativeUrn, urn); + const response = await axios.get(downloadInfo.url, { responseType: 'arraybuffer', decompress: false }); + return response.data; +} + +async function getSvfDerivatives(urn, clientId, clientSecret) { + const sdkManager = SdkManagerBuilder.create().build(); + const authenticationClient = new AuthenticationClient(sdkManager); + const modelDerivativeClient = new ModelDerivativeClient(sdkManager); + const credentials = await authenticationClient.getTwoLeggedToken(clientId, clientSecret, [Scopes.ViewablesRead]); + const manifest = await modelDerivativeClient.getManifest(credentials.access_token, urn); + const derivatives = []; + function traverse(derivative) { + if (derivative.type === 'resource' && derivative.role === 'graphics' && derivative.mime === 'application/autodesk-svf') { + derivatives.push(derivative); + } + if (derivative.children) { + for (const child of derivative.children) { + traverse(child); + } + } + } + for (const derivative of manifest.derivatives) { + if (derivative.children) { + for (const child of derivative.children) { + traverse(child); + } + } + } + return derivatives; +} + +module.exports = { + downloadDerivative, + getSvfDerivatives +}; \ No newline at end of file diff --git a/src/common/authentication-provider.ts b/src/common/authentication-provider.ts new file mode 100644 index 0000000..b381cb6 --- /dev/null +++ b/src/common/authentication-provider.ts @@ -0,0 +1,30 @@ +import { AuthenticationClient, ResponseType, Scopes } from "@aps_sdk/authentication"; +import { SdkManager, SdkManagerBuilder } from "@aps_sdk/autodesk-sdkmanager"; + +export interface IAuthenticationProvider { + getToken(scopes: Scopes[]): Promise; +} + +export class BasicAuthenticationProvider implements IAuthenticationProvider { + constructor(protected accessToken: string) {} + + async getToken(scopes: Scopes[]): Promise { + // TODO: check if the hard-coded token has all the needed scopes + return this.accessToken; + } +} + +export class TwoLeggedAuthenticationProvider implements IAuthenticationProvider { + protected sdkManager: SdkManager; + protected authenticationClient: AuthenticationClient; + + constructor(protected clientId: string, protected clientSecret: string) { + this.sdkManager = SdkManagerBuilder.create().build(); + this.authenticationClient = new AuthenticationClient(this.sdkManager); + } + + async getToken(scopes: Scopes[]): Promise { + const credentials = await this.authenticationClient.getTwoLeggedToken(this.clientId, this.clientSecret, scopes); + return credentials.access_token as string; + } +} \ No newline at end of file diff --git a/src/f2d/downloader.ts b/src/f2d/downloader.ts index 4c975e4..9e392d3 100644 --- a/src/f2d/downloader.ts +++ b/src/f2d/downloader.ts @@ -1,8 +1,11 @@ import * as path from 'path'; import * as zlib from 'zlib'; import * as fse from 'fs-extra'; -import { ModelDerivativeClient, ManifestHelper, IDerivativeResourceChild } from 'forge-server-utils'; -import { IAuthOptions } from 'forge-server-utils/dist/common'; +import axios from 'axios'; +import { ManifestDerivativesChildren, ModelDerivativeClient } from '@aps_sdk/model-derivative'; +import { SdkManager, SdkManagerBuilder } from '@aps_sdk/autodesk-sdkmanager'; +import { IAuthenticationProvider } from '../common/authentication-provider'; +import { Scopes } from '@aps_sdk/authentication'; export interface IDownloadOptions { outputDir?: string; @@ -23,10 +26,12 @@ interface IDownloadContext { } export class Downloader { + protected sdkManager: SdkManager; protected modelDerivativeClient: ModelDerivativeClient; - constructor(protected auth: IAuthOptions) { - this.modelDerivativeClient = new ModelDerivativeClient(this.auth); + constructor(protected authenticationProvider: IAuthenticationProvider) { + this.sdkManager = SdkManagerBuilder.create().build(); + this.modelDerivativeClient = new ModelDerivativeClient(this.sdkManager); } download(urn: string, options?: IDownloadOptions): IDownloadTask { @@ -42,12 +47,37 @@ export class Downloader { }; } + private async _downloadDerivative(urn: string, derivativeUrn: string) { + const accessToken = await this.authenticationProvider.getToken([Scopes.ViewablesRead]); + const downloadInfo = await this.modelDerivativeClient.getDerivativeUrl(accessToken, derivativeUrn, urn); + const response = await axios.get(downloadInfo.url as string, { responseType: 'arraybuffer', decompress: false }); + return response.data; + } + private async _download(urn: string, context: IDownloadContext): Promise { context.log(`Downloading derivative ${urn}`); - const helper = new ManifestHelper(await this.modelDerivativeClient.getManifest(urn)); - const derivatives = helper.search({ type: 'resource', role: 'graphics' }) as IDerivativeResourceChild[]; + const accessToken = await this.authenticationProvider.getToken([Scopes.ViewablesRead]); + const manifest = await this.modelDerivativeClient.getManifest(accessToken, urn); + let derivatives: ManifestDerivativesChildren[] = []; + function collectDerivatives(derivative: ManifestDerivativesChildren) { + if (derivative.type === 'resource' && derivative.role === 'graphics' && (derivative as any).mime === 'application/autodesk-f2d') { + derivatives.push(derivative); + } + if (derivative.children) { + for (const child of derivative.children) { + collectDerivatives(child); + } + } + } + for (const derivative of manifest.derivatives) { + if (derivative.children) { + for (const child of derivative.children) { + collectDerivatives(child); + } + } + } const urnDir = path.join(context.outputDir, urn); - for (const derivative of derivatives.filter(d => d.mime === 'application/autodesk-f2d')) { + for (const derivative of derivatives) { if (context.cancelled) { return; } @@ -55,9 +85,10 @@ export class Downloader { context.log(`Downloading viewable ${guid}`); const guidDir = path.join(urnDir, guid); fse.ensureDirSync(guidDir); - const baseUrn = derivative.urn.substr(0, derivative.urn.lastIndexOf('/')); - const manifestGzip = await this.modelDerivativeClient.getDerivative(urn, baseUrn + '/manifest.json.gz'); - fse.writeFileSync(path.join(guidDir, 'manifest.json.gz'), new Uint8Array(manifestGzip)); + const derivativeUrn = (derivative as any).urn; + const baseUrn = derivativeUrn.substr(0, derivativeUrn.lastIndexOf('/')); + const manifestGzip = await this._downloadDerivative(urn, baseUrn + '/manifest.json.gz'); + fse.writeFileSync(path.join(guidDir, 'manifest.json.gz'), new Uint8Array(manifestGzip as Buffer)); const manifestGunzip = zlib.gunzipSync(manifestGzip); const manifest = JSON.parse(manifestGunzip.toString()); for (const asset of manifest.assets) { @@ -66,7 +97,7 @@ export class Downloader { } context.log(`Downloading asset ${asset.URI}`); try { - const assetData = await this.modelDerivativeClient.getDerivative(urn, baseUrn + '/' + asset.URI); + const assetData = await this._downloadDerivative(urn, baseUrn + '/' + asset.URI); fse.writeFileSync(path.join(guidDir, asset.URI), new Uint8Array(assetData)); } catch (err) { if (context.failOnMissingAssets) { diff --git a/src/gltf/writer.ts b/src/gltf/writer.ts index 509e8c7..76194f3 100644 --- a/src/gltf/writer.ts +++ b/src/gltf/writer.ts @@ -129,8 +129,8 @@ export class Writer { this.manifest = { asset: { version: '2.0', - generator: 'forge-convert-utils', - copyright: '2019 (c) Autodesk' + generator: 'svf-utils', + copyright: '2024 (c) Autodesk' }, extensionsUsed: [ "KHR_texture_transform" diff --git a/src/index.ts b/src/index.ts index f152c3f..3be34dd 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,4 +2,4 @@ export { Reader as SvfReader } from './svf/reader'; export { Downloader as SvfDownloader } from './svf/downloader'; export { Downloader as F2dDownloader } from './f2d/downloader'; export { Writer as GltfWriter } from './gltf/writer'; -export { Region } from 'forge-server-utils/dist/common'; +export { IAuthenticationProvider, BasicAuthenticationProvider, TwoLeggedAuthenticationProvider } from './common/authentication-provider'; \ No newline at end of file diff --git a/src/svf/downloader.ts b/src/svf/downloader.ts index 34f7649..e4bd550 100644 --- a/src/svf/downloader.ts +++ b/src/svf/downloader.ts @@ -1,8 +1,11 @@ import * as path from 'path'; import * as fse from 'fs-extra'; -import { ModelDerivativeClient, ManifestHelper, IDerivativeResourceChild } from 'forge-server-utils'; -import { IAuthOptions, Region } from 'forge-server-utils/dist/common'; +import axios from 'axios'; import { SvfReader } from '..'; +import { SdkManager, SdkManagerBuilder } from '@aps_sdk/autodesk-sdkmanager'; +import { IAuthenticationProvider } from '../common/authentication-provider'; +import { ManifestDerivativesChildren, ModelDerivativeClient } from '@aps_sdk/model-derivative'; +import { Scopes } from '@aps_sdk/authentication'; export interface IDownloadOptions { outputDir?: string; @@ -23,10 +26,12 @@ interface IDownloadContext { } export class Downloader { + protected sdkManager: SdkManager; protected modelDerivativeClient: ModelDerivativeClient; - constructor(protected auth: IAuthOptions, host?: string, region?: Region) { - this.modelDerivativeClient = new ModelDerivativeClient(this.auth, host, region); + constructor(protected authenticationProvider: IAuthenticationProvider, host?: string, region?: string) { + this.sdkManager = SdkManagerBuilder.create().build(); + this.modelDerivativeClient = new ModelDerivativeClient(this.sdkManager); } download(urn: string, options?: IDownloadOptions): IDownloadTask { @@ -42,12 +47,39 @@ export class Downloader { }; } + private async _downloadDerivative(urn: string, derivativeUrn: string) { + const accessToken = await this.authenticationProvider.getToken([Scopes.ViewablesRead]); + const downloadInfo = await this.modelDerivativeClient.getDerivativeUrl(accessToken, derivativeUrn, urn); + const response = await axios.get(downloadInfo.url as string, { responseType: 'arraybuffer', decompress: false }); + return response.data; + } + private async _download(urn: string, context: IDownloadContext): Promise { context.log(`Downloading derivative ${urn}`); - const helper = new ManifestHelper(await this.modelDerivativeClient.getManifest(urn)); - const derivatives = helper.search({ type: 'resource', role: 'graphics' }) as IDerivativeResourceChild[]; + const accessToken = await this.authenticationProvider.getToken([Scopes.ViewablesRead]); + const manifest = await this.modelDerivativeClient.getManifest(accessToken, urn); const urnDir = path.join(context.outputDir || '.', urn); - for (const derivative of derivatives.filter(d => d.mime === 'application/autodesk-svf')) { + + const derivatives: ManifestDerivativesChildren[] = []; + function collectDerivatives(derivative: ManifestDerivativesChildren) { + if (derivative.type === 'resource' && derivative.role === 'graphics' && (derivative as any).mime === 'application/autodesk-svf') { + derivatives.push(derivative); + } + if (derivative.children) { + for (const child of derivative.children) { + collectDerivatives(child); + } + } + } + for (const derivative of manifest.derivatives) { + if (derivative.children) { + for (const child of derivative.children) { + collectDerivatives(child); + } + } + } + + for (const derivative of derivatives) { if (context.cancelled) { return; } @@ -55,9 +87,9 @@ export class Downloader { context.log(`Downloading viewable ${guid}`); const guidDir = path.join(urnDir, guid); fse.ensureDirSync(guidDir); - const svf = await this.modelDerivativeClient.getDerivative(urn, encodeURI(derivative.urn)); + const svf = await this._downloadDerivative(urn, encodeURI((derivative as any).urn)); fse.writeFileSync(path.join(guidDir, 'output.svf'), new Uint8Array(svf)); - const reader = await SvfReader.FromDerivativeService(urn, guid, this.auth); + const reader = await SvfReader.FromDerivativeService(urn, guid, this.authenticationProvider); const manifest = await reader.getManifest(); for (const asset of manifest.assets) { if (context.cancelled) { diff --git a/src/svf/reader.ts b/src/svf/reader.ts index 7a9195e..e3118a3 100644 --- a/src/svf/reader.ts +++ b/src/svf/reader.ts @@ -1,10 +1,11 @@ import * as path from 'path'; import * as fse from 'fs-extra'; import Zip from 'adm-zip'; +import axios from 'axios'; import { isNullOrUndefined } from 'util'; - -import { ModelDerivativeClient, ManifestHelper, IDerivativeResourceChild } from 'forge-server-utils'; -import { IAuthOptions, Region } from 'forge-server-utils/dist/common'; +import { SdkManagerBuilder } from '@aps_sdk/autodesk-sdkmanager'; +import { ManifestDerivativesChildren, ModelDerivativeClient } from '@aps_sdk/model-derivative'; +import { Scopes } from '@aps_sdk/authentication'; import { PropDbReader } from '../common/propdb-reader'; import { parseFragments } from './fragments'; import { parseGeometries } from './geometries'; @@ -12,6 +13,7 @@ import { parseMaterials } from './materials'; import { parseMeshes } from './meshes'; import * as SVF from './schema'; import * as IMF from '../common/intermediate-format'; +import { IAuthenticationProvider } from '../common/authentication-provider'; /** * Entire content of SVF and its assets loaded in memory. @@ -168,13 +170,13 @@ export interface IReaderOptions { * individual SVF objects using methods like {@link readFragments} or {@link enumerateGeometries}. * * @example - * const auth = { client_id: 'forge client id', client_secret: 'forge client secreet' }; - * const reader = await Reader.FromDerivativeService('model urn', 'viewable guid', auth); + * const authProvider = new TwoLeggedAuthenticationProvider(APS_CLIENT_ID, APS_CLIENT_SECRET); + * const reader = await Reader.FromDerivativeService(MODEL_URN, VIEWABLE_GUID, authProvider); * const scene = await reader.read(); // Read entire scene into an intermediate, in-memory representation * console.log(scene); * * @example - * const reader = await Reader.FromFileSystem('path/to/svf'); + * const reader = await Reader.FromFileSystem('path/to/output.svf'); * // Enumerate fragments (without building a list of all of them) * for await (const fragment of reader.enumerateFragments()) { * console.log(fragment); @@ -198,30 +200,56 @@ export class Reader { } /** - * Instantiates new reader for an SVF in Forge Model Derivative service. + * Instantiates new reader for an SVF in APS Model Derivative service. * @async - * @param {string} urn Forge model URN. - * @param {string} guid Forge viewable GUID. The viewable(s) can be found in the manifest + * @param {string} urn APS model URN. + * @param {string} guid APS viewable GUID. The viewable(s) can be found in the manifest * with type: 'resource', role: 'graphics', and mime: 'application/autodesk-svf'. - * @param {IAuthOptions} auth Credentials or access token for accessing the Model Derivative service. - * @param {string} host Optional host URL to be used by all Forge calls. - * @param {Region} region Optional region to be used by all Forge calls. + * @param {IAuthenticationProvider} authenticationProvider Authentication provider for accessing the Model Derivative service. + * @param {string} host Optional host URL to be used by all APS calls. + * @param {string} region Optional region to be used by all APS calls. * @returns {Promise} Reader for the provided SVF. */ - static async FromDerivativeService(urn: string, guid: string, auth: IAuthOptions, host?: string, region?: Region): Promise { + static async FromDerivativeService(urn: string, guid: string, authenticationProvider: IAuthenticationProvider, host?: string, region?: string): Promise { urn = urn.replace(/=/g, ''); - const modelDerivativeClient = new ModelDerivativeClient(auth, host, region); - const helper = new ManifestHelper(await modelDerivativeClient.getManifest(urn)); - const resources = helper.search({ type: 'resource', role: 'graphics', guid }); - if (resources.length === 0) { + const sdkManager = SdkManagerBuilder.create().build(); + const modelDerivativeClient = new ModelDerivativeClient(sdkManager); + const accessToken = await authenticationProvider.getToken([Scopes.ViewablesRead]); + const manifest = await modelDerivativeClient.getManifest(accessToken, urn); + let foundDerivative: ManifestDerivativesChildren | null = null; + function findDerivative(derivative: ManifestDerivativesChildren) { + if (derivative.type === 'resource' && derivative.role === 'graphics' && derivative.guid === guid) { + foundDerivative = derivative; + } + if (derivative.children) { + for (const child of derivative.children) { + findDerivative(child); + } + } + } + for (const derivative of manifest.derivatives) { + if (derivative.children) { + for (const child of derivative.children) { + findDerivative(child); + } + } + } + if (!foundDerivative) { throw new Error(`Viewable '${guid}' not found.`); } - const svfUrn = (resources[0] as IDerivativeResourceChild).urn; - const svf = await modelDerivativeClient.getDerivative(urn, encodeURI(svfUrn)) as Buffer; + + async function downloadDerivative(urn: string, derivativeUrn: string) { + const accessToken = await authenticationProvider.getToken([Scopes.ViewablesRead]); + const downloadInfo = await modelDerivativeClient.getDerivativeUrl(accessToken, derivativeUrn, urn); + const response = await axios.get(downloadInfo.url as string, { responseType: 'arraybuffer', decompress: false }); + return response.data; + } + + const svfUrn = (foundDerivative as any).urn; + const svf = await downloadDerivative(urn, encodeURI(svfUrn)) as Buffer; const baseUri = svfUrn.substr(0, svfUrn.lastIndexOf('/')); const resolve = async (uri: string) => { - const encodedUri = encodeURI(baseUri + '/' + uri); - const buffer = await modelDerivativeClient.getDerivative(urn, encodedUri) as Buffer; + const buffer = await downloadDerivative(urn, encodeURI(path.join(baseUri, uri))); return buffer; }; return new Reader(svf, resolve); diff --git a/test/remote-svf-to-gltf.sh b/test/remote-svf-to-gltf.sh index 3d93031..5f563b3 100755 --- a/test/remote-svf-to-gltf.sh +++ b/test/remote-svf-to-gltf.sh @@ -2,12 +2,12 @@ # Basic SVF-to-glTF conversion test. # Usage: -# export FORGE_CLIENT_ID= -# export FORGE_CLIENT_SECRET= +# export APS_CLIENT_ID= +# export APS_CLIENT_SECRET= # ./remote-svf-to-gltf.sh # Convert SVF to glTF -node ./bin/forge-convert.js $1 --output-folder $2/gltf --deduplicate --skip-unused-uvs --ignore-lines --ignore-points +node ./bin/svf-to-gltf.js $1 --output-folder $2/gltf --deduplicate --skip-unused-uvs --ignore-lines --ignore-points # Iterate over glTFs generated for all viewables (in / subfolders) for gltf in $(find $2/gltf -name "output.gltf"); do diff --git a/yarn.lock b/yarn.lock index 59a731f..74f51b9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,135 +2,222 @@ # yarn lockfile v1 +"@aps_sdk/authentication@^0.1.0-beta.1": + version "0.1.0-beta.1" + resolved "https://registry.npmjs.org/@aps_sdk/authentication/-/authentication-0.1.0-beta.1.tgz" + integrity sha512-b09vBx19HqixLeBrMZWqUdqyINAJNK9Ed0PL+qVVHunTGJ+6TxXwM8omjiHPF6a3ksgy4qAsjwjyWMn70784sg== + dependencies: + "@aps_sdk/autodesk-sdkmanager" "^0.0.7-beta.1" + "@types/node" "^20.9.0" + axios "^1.6.1" + +"@aps_sdk/autodesk-sdkmanager@^0.0.7-beta.1": + version "0.0.7-beta.1" + resolved "https://registry.npmjs.org/@aps_sdk/autodesk-sdkmanager/-/autodesk-sdkmanager-0.0.7-beta.1.tgz" + integrity sha512-RqHLuyUb80j+zo2hmvxzg9ievKBieM5bqYf8f2iVMFo41iB6c9PYWxAmWpq4v1cXlj2/VqTGCRxUFaTjA6q4SQ== + dependencies: + axios "^1.4.0" + cockatiel "^3.1.1" + winston "^3.9.0" + +"@aps_sdk/model-derivative@^0.1.0-beta.1": + version "0.1.0-beta.1" + resolved "https://registry.npmjs.org/@aps_sdk/model-derivative/-/model-derivative-0.1.0-beta.1.tgz" + integrity sha512-8b1nlcPw/cDJRLpmp5aYktzANf0JzjAssC8+epSM7pCQnnT5B3gl/06UN0RK43P5fSdWE8EGxsOztCG5mZY71A== + dependencies: + "@aps_sdk/autodesk-sdkmanager" "^0.0.7-beta.1" + "@types/node" "^20.9.0" + axios "^1.6.1" + +"@colors/colors@1.6.0", "@colors/colors@^1.6.0": + version "1.6.0" + resolved "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz" + integrity sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA== + +"@dabh/diagnostics@^2.0.2": + version "2.0.3" + resolved "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz" + integrity sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA== + dependencies: + colorspace "1.1.x" + enabled "2.0.x" + kuler "^2.0.0" + "@types/adm-zip@^0.4.32": version "0.4.34" - resolved "https://registry.yarnpkg.com/@types/adm-zip/-/adm-zip-0.4.34.tgz#62ac859eb2af6024362a1b3e43527ab79e0c624e" + resolved "https://registry.npmjs.org/@types/adm-zip/-/adm-zip-0.4.34.tgz" integrity sha512-8ToYLLAYhkRfcmmljrKi22gT2pqu7hGMDtORP1emwIEGmgUTZOsaDjzWFzW5N2frcFRz/50CWt4zA1CxJ73pmQ== dependencies: "@types/node" "*" -"@types/fs-extra@^8.0.0": - version "8.1.3" - resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-8.1.3.tgz#4807768c0b0a5a5f4746d8fde2f7ab0137076eea" - integrity sha512-7IdV01N0u/CaVO0fuY1YmEg14HQN3+EW8mpNgg6NEfxEl/lzCa5OxlBu3iFsCAdamnYOcTQ7oEi43Xc/67Rgzw== - dependencies: - "@types/node" "*" - "@types/fs-extra@^9.0.13": version "9.0.13" - resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-9.0.13.tgz#7594fbae04fe7f1918ce8b3d213f74ff44ac1f45" + resolved "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz" integrity sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA== dependencies: "@types/node" "*" -"@types/node@*": - version "20.7.1" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.7.1.tgz#06d732ead0bd5ad978ef0ea9cbdeb24dc8717514" - integrity sha512-LT+OIXpp2kj4E2S/p91BMe+VgGX2+lfO+XTpfXhh+bCk2LkQtHZSub8ewFBMGP5ClysPjTDFa4sMI8Q3n4T0wg== +"@types/node@*", "@types/node@^20.9.0": + version "20.12.6" + resolved "https://registry.npmjs.org/@types/node/-/node-20.12.6.tgz" + integrity sha512-3KurE8taB8GCvZBPngVbp0lk5CKi8M9f9k1rsADh0Evdz5SzJ+Q+Hx9uHoFGsLnLnd1xmkDQr2hVhlA0Mn0lKQ== + dependencies: + undici-types "~5.26.4" -adm-zip@^0.4.13: - version "0.4.16" - resolved "https://registry.yarnpkg.com/adm-zip/-/adm-zip-0.4.16.tgz#cf4c508fdffab02c269cbc7f471a875f05570365" - integrity sha512-TFi4HBKSGfIKsK5YCkKaaFG2m4PEDyViZmEwof3MTIgzimHLto6muaHVpbrljdIvIrFZzEq/p4nafOeLcYegrg== +"@types/triple-beam@^1.3.2": + version "1.3.5" + resolved "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz" + integrity sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw== adm-zip@^0.5.9: version "0.5.10" - resolved "https://registry.yarnpkg.com/adm-zip/-/adm-zip-0.5.10.tgz#4a51d5ab544b1f5ce51e1b9043139b639afff45b" + resolved "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.10.tgz" integrity sha512-x0HvcHqVJNTPk/Bw8JbLWlWoo6Wwnsug0fnYYro1HBrjxZ3G7/AZk7Ahv8JwDe1uIcz8eBqvu86FuF1POiG7vQ== +async@^3.2.3: + version "3.2.5" + resolved "https://registry.npmjs.org/async/-/async-3.2.5.tgz" + integrity sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg== + asynckit@^0.4.0: version "0.4.0" - resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== -axios@^0.21.1: - version "0.21.4" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575" - integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg== +axios@^1.4.0, axios@^1.6.1: + version "1.6.8" + resolved "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz" + integrity sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ== dependencies: - follow-redirects "^1.14.0" + follow-redirects "^1.15.6" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + +axios@^1.6.8: + version "1.6.8" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.8.tgz#66d294951f5d988a00e87a0ffb955316a619ea66" + integrity sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ== + dependencies: + follow-redirects "^1.15.6" + form-data "^4.0.0" + proxy-from-env "^1.1.0" balanced-match@^1.0.0: version "1.0.2" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== brace-expansion@^2.0.1: version "2.0.1" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz" integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== dependencies: balanced-match "^1.0.0" -combined-stream@^1.0.6: +cockatiel@^3.1.1: + version "3.1.2" + resolved "https://registry.npmjs.org/cockatiel/-/cockatiel-3.1.2.tgz" + integrity sha512-5yARKww0dWyWg2/3xZeXgoxjHLwpVqFptj9Zy7qioJ6+/L0ARM184sgMUrQDjxw7ePJWlGhV998mKhzrxT0/Kg== + +color-convert@^1.9.3: + version "1.9.3" + resolved "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-name@1.1.3, color-name@^1.0.0: + version "1.1.3" + resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz" + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== + +color-string@^1.6.0: + version "1.9.1" + resolved "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz" + integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg== + dependencies: + color-name "^1.0.0" + simple-swizzle "^0.2.2" + +color@^3.1.3: + version "3.2.1" + resolved "https://registry.npmjs.org/color/-/color-3.2.1.tgz" + integrity sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA== + dependencies: + color-convert "^1.9.3" + color-string "^1.6.0" + +colorspace@1.1.x: + version "1.1.4" + resolved "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz" + integrity sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w== + dependencies: + color "^3.1.3" + text-hex "1.0.x" + +combined-stream@^1.0.8: version "1.0.8" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz" integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== dependencies: delayed-stream "~1.0.0" commander@^3.0.2: version "3.0.2" - resolved "https://registry.yarnpkg.com/commander/-/commander-3.0.2.tgz#6837c3fb677ad9933d1cfba42dd14d5117d6b39e" + resolved "https://registry.npmjs.org/commander/-/commander-3.0.2.tgz" integrity sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow== delayed-stream@~1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== -follow-redirects@^1.14.0: - version "1.15.3" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.3.tgz#fe2f3ef2690afce7e82ed0b44db08165b207123a" - integrity sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q== +enabled@2.0.x: + version "2.0.0" + resolved "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz" + integrity sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ== + +fecha@^4.2.0: + version "4.2.3" + resolved "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz" + integrity sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw== -forge-server-utils@^8.3.5: - version "8.4.0" - resolved "https://registry.yarnpkg.com/forge-server-utils/-/forge-server-utils-8.4.0.tgz#c4e8396677e646f36ac6a25046db7ce277c8e5b9" - integrity sha512-U1QS1spIGxjbZiDgfsFbSaeamrNnu2zvIDMoqO6yNKtfXuQAVQIkziCxBFvkFOIgCFuokoUnOlZQlCDsdWzETQ== - dependencies: - "@types/adm-zip" "^0.4.32" - "@types/fs-extra" "^8.0.0" - adm-zip "^0.4.13" - axios "^0.21.1" - form-data "^2.5.1" - fs-extra "^8.1.0" +fn.name@1.x.x: + version "1.1.0" + resolved "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz" + integrity sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw== -form-data@^2.5.1: - version "2.5.1" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4" - integrity sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA== +follow-redirects@^1.15.6: + version "1.15.6" + resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz" + integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== + +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== dependencies: asynckit "^0.4.0" - combined-stream "^1.0.6" + combined-stream "^1.0.8" mime-types "^2.1.12" fs-extra@^10.0.0: version "10.1.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf" + resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz" integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ== dependencies: graceful-fs "^4.2.0" jsonfile "^6.0.1" universalify "^2.0.0" -fs-extra@^8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" - integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== - dependencies: - graceful-fs "^4.2.0" - jsonfile "^4.0.0" - universalify "^0.1.0" - fs.realpath@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== glob@^8.0.3: version "8.1.0" - resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e" + resolved "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz" integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== dependencies: fs.realpath "^1.0.0" @@ -141,96 +228,181 @@ glob@^8.0.3: gltf-validator@^2.0.0-dev.3.2: version "2.0.0-dev.3.9" - resolved "https://registry.yarnpkg.com/gltf-validator/-/gltf-validator-2.0.0-dev.3.9.tgz#831cd4d95ce36bc8a2cc176b739c927012119e98" + resolved "https://registry.npmjs.org/gltf-validator/-/gltf-validator-2.0.0-dev.3.9.tgz" integrity sha512-9nPcAgYJwT6sbml7S3/tC+N/BkqTUSL1u8GcmUQLuwToLR0ZH8CF3i/BhVqDwlg7OmKS2GGjjEcnU/oMMeIQUQ== graceful-fs@^4.1.6, graceful-fs@^4.2.0: version "4.2.11" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== inflight@^1.0.4: version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + resolved "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz" integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== dependencies: once "^1.3.0" wrappy "1" -inherits@2: +inherits@2, inherits@^2.0.3: version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== +is-arrayish@^0.3.1: + version "0.3.2" + resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz" + integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== + +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + jsonc-parser@^3.0.0: version "3.2.0" - resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.2.0.tgz#31ff3f4c2b9793f89c67212627c51c6394f88e76" + resolved "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz" integrity sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w== -jsonfile@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" - integrity sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg== - optionalDependencies: - graceful-fs "^4.1.6" - jsonfile@^6.0.1: version "6.1.0" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" + resolved "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz" integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== dependencies: universalify "^2.0.0" optionalDependencies: graceful-fs "^4.1.6" +kuler@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz" + integrity sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A== + +logform@^2.3.2, logform@^2.4.0: + version "2.6.0" + resolved "https://registry.npmjs.org/logform/-/logform-2.6.0.tgz" + integrity sha512-1ulHeNPp6k/LD8H91o7VYFBng5i1BDE7HoKxVbZiGFidS1Rj65qcywLxX+pVfAPoQJEjRdvKcusKwOupHCVOVQ== + dependencies: + "@colors/colors" "1.6.0" + "@types/triple-beam" "^1.3.2" + fecha "^4.2.0" + ms "^2.1.1" + safe-stable-stringify "^2.3.1" + triple-beam "^1.3.0" + lunr@^2.3.9: version "2.3.9" - resolved "https://registry.yarnpkg.com/lunr/-/lunr-2.3.9.tgz#18b123142832337dd6e964df1a5a7707b25d35e1" + resolved "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz" integrity sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow== marked@^4.0.16: version "4.3.0" - resolved "https://registry.yarnpkg.com/marked/-/marked-4.3.0.tgz#796362821b019f734054582038b116481b456cf3" + resolved "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz" integrity sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A== mime-db@1.52.0: version "1.52.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== mime-types@^2.1.12: version "2.1.35" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== dependencies: mime-db "1.52.0" minimatch@^5.0.1, minimatch@^5.1.0: version "5.1.6" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz" integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== dependencies: brace-expansion "^2.0.1" +ms@^2.1.1: + version "2.1.3" + resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + once@^1.3.0: version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz" integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== dependencies: wrappy "1" +one-time@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz" + integrity sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g== + dependencies: + fn.name "1.x.x" + +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + +readable-stream@^3.4.0, readable-stream@^3.6.0: + version "3.6.2" + resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz" + integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +safe-stable-stringify@^2.3.1: + version "2.4.3" + resolved "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz" + integrity sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g== + shiki@^0.10.1: version "0.10.1" - resolved "https://registry.yarnpkg.com/shiki/-/shiki-0.10.1.tgz#6f9a16205a823b56c072d0f1a0bcd0f2646bef14" + resolved "https://registry.npmjs.org/shiki/-/shiki-0.10.1.tgz" integrity sha512-VsY7QJVzU51j5o1+DguUd+6vmCmZ5v/6gYu4vyYAhzjuNQU6P/vmSy4uQaOhvje031qQMiW0d2BwgMH52vqMng== dependencies: jsonc-parser "^3.0.0" vscode-oniguruma "^1.6.1" vscode-textmate "5.2.0" +simple-swizzle@^0.2.2: + version "0.2.2" + resolved "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz" + integrity sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg== + dependencies: + is-arrayish "^0.3.1" + +stack-trace@0.0.x: + version "0.0.10" + resolved "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz" + integrity sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg== + +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +text-hex@1.0.x: + version "1.0.0" + resolved "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz" + integrity sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg== + +triple-beam@^1.3.0: + version "1.4.1" + resolved "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz" + integrity sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg== + typedoc@^0.22.11: version "0.22.18" - resolved "https://registry.yarnpkg.com/typedoc/-/typedoc-0.22.18.tgz#1d000c33b66b88fd8cdfea14a26113a83b7e6591" + resolved "https://registry.npmjs.org/typedoc/-/typedoc-0.22.18.tgz" integrity sha512-NK9RlLhRUGMvc6Rw5USEYgT4DVAUFk7IF7Q6MYfpJ88KnTZP7EneEa4RcP+tX1auAcz7QT1Iy0bUSZBYYHdoyA== dependencies: glob "^8.0.3" @@ -240,31 +412,62 @@ typedoc@^0.22.11: shiki "^0.10.1" typescript@^4.5.5: - version "4.9.5" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" - integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== + version "4.7.4" + resolved "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz" + integrity sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ== -universalify@^0.1.0: - version "0.1.2" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" - integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== universalify@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" + resolved "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz" integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== +util-deprecate@^1.0.1: + version "1.0.2" + resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + vscode-oniguruma@^1.6.1: version "1.7.0" - resolved "https://registry.yarnpkg.com/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz#439bfad8fe71abd7798338d1cd3dc53a8beea94b" + resolved "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz" integrity sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA== vscode-textmate@5.2.0: version "5.2.0" - resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-5.2.0.tgz#01f01760a391e8222fe4f33fbccbd1ad71aed74e" + resolved "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-5.2.0.tgz" integrity sha512-Uw5ooOQxRASHgu6C7GVvUxisKXfSgW4oFlO+aa+PAkgmH89O3CXxEEzNRNtHSqtXFTl0nAC1uYj0GMSH27uwtQ== +winston-transport@^4.7.0: + version "4.7.0" + resolved "https://registry.npmjs.org/winston-transport/-/winston-transport-4.7.0.tgz" + integrity sha512-ajBj65K5I7denzer2IYW6+2bNIVqLGDHqDw3Ow8Ohh+vdW+rv4MZ6eiDvHoKhfJFZ2auyN8byXieDDJ96ViONg== + dependencies: + logform "^2.3.2" + readable-stream "^3.6.0" + triple-beam "^1.3.0" + +winston@^3.9.0: + version "3.13.0" + resolved "https://registry.npmjs.org/winston/-/winston-3.13.0.tgz" + integrity sha512-rwidmA1w3SE4j0E5MuIufFhyJPBDG7Nu71RkZor1p2+qHvJSZ9GYDA81AyleQcZbh/+V6HjeBdfnTZJm9rSeQQ== + dependencies: + "@colors/colors" "^1.6.0" + "@dabh/diagnostics" "^2.0.2" + async "^3.2.3" + is-stream "^2.0.0" + logform "^2.4.0" + one-time "^1.0.0" + readable-stream "^3.4.0" + safe-stable-stringify "^2.3.1" + stack-trace "0.0.x" + triple-beam "^1.3.0" + winston-transport "^4.7.0" + wrappy@1: version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==