petrbroz / svf-utils

Utilities for working with the SVF file format used by Autodesk Platform Services.
https://petrbroz.github.io/svf-utils/
MIT License
123 stars 53 forks source link
autodesk-platform-services gltf svf

svf-utils

Publish to NPM npm version node npm downloads platforms license

APS & glTF logos

Utilities for converting Autodesk Platform Services SVF file format into glTF 2.0.

Usage

Command line

Unix/macOS

svf-to-gltf <path to local svf> --output-folder <path to output folder>

or

export APS_CLIENT_ID=<client id>
export APS_CLIENT_SECRET=<client secret>
svf-to-gltf <urn> --output-folder <path to output folder>

or

export APS_ACCESS_TOKEN=<access token>
svf-to-gltf <urn> --output-folder <path to output folder>

Windows

svf-to-gltf <path to local svf> --output-folder <path to output folder>

or

set APS_CLIENT_ID=<client id>
set APS_CLIENT_SECRET=<client secret>
svf-to-gltf <urn> --output-folder <path to output folder>

or

set APS_ACCESS_TOKEN=<access token>
svf-to-gltf <urn> --output-folder <path to output folder>

Node.js

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 method, and save the model into glTF using GltfWriter#write: 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 or SvfReader#enumerateGeometries to asynchronously iterate over individual elements:

const { SvfReader } = require('svf-utils');

// ...

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:

const { parseMeshes } = require('svf-utils/lib/svf/meshes');

// ...

for (const mesh of parseMeshes(buffer)) {
    console.log(mesh);
}

For additional examples, see the samples subfolder.

Customization

You can customize the translation by sub-classing the reader and/or the writer class. For example:

Metadata

When converting models from Model Derivative service, 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", and role "Autodesk.CloudPlatform.PropertyDatabase":

    ...
    const pdbDerivatives = manifestHelper.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('./properties.sdb'));
    }
    ...

The structure of the sqlite database, and the way to extract model properties from it is explained in https://github.com/wallabyway/propertyServer/blob/master/pipeline.md. Here's a simple diagram showing the individual tables in the database, and the relationships between them:

Property Database Diagram

And here's an example query listing all objects with "Material" property containing the "Concrete" word:

SELECT _objects_id.id AS dbId, _objects_id.external_id AS externalId, _objects_attr.name AS propName, _objects_val.value AS propValue
FROM _objects_eav
    INNER JOIN _objects_id ON _objects_eav.entity_id = _objects_id.id
    INNER JOIN _objects_attr ON _objects_eav.attribute_id = _objects_attr.id
    INNER JOIN _objects_val ON _objects_eav.value_id = _objects_val.id
WHERE propName = "Material" AND propValue LIKE "%Concrete%"

GLB, Draco, and other post-processing

Following the Unix philosophy, we removed post-processing dependencies from this project, and instead leave it to developers to "pipe" the output of this library to other tools such as https://github.com/CesiumGS/gltf-pipeline or https://github.com/zeux/meshoptimizer. See ./samples/local-svf-to-gltf.sh or ./samples/remote-svf-to-gltf.sh for examples.

Development

If you're using Visual Studio Code, you can use the following "task" and "launch" configurations:

In .vscode/tasks.json:

...
{
    "label": "build",
    "type": "npm",
    "script": "build",
    "problemMatcher": [
        "$tsc"
    ],
    "group": "build",
    "presentation": {
        "echo": true,
        "reveal": "silent",
        "focus": false,
        "panel": "shared",
        "showReuseMessage": false,
        "clear": false
    }
}
...

In .vscode/launch.json:

...
{
    "type": "node",
    "request": "launch",
    "name": "Convert Model Derivative SVF to glTF",
    "program": "${workspaceFolder}/test/remote-svf-to-gltf.js",
    "args": ["<your model urn>", "<path to output folder>"],
    "env": {
        "APS_CLIENT_ID": "<your client id>",
        "APS_CLIENT_SECRET": "<your client secret>"
    },
    "preLaunchTask": "build"
},
{
    "type": "node",
    "request": "launch",
    "name": "Convert Local SVF to glTF",
    "program": "${workspaceFolder}/test/local-svf-to-gltf.js",
    "args": ["<path to svf file>", "<path to output folder>"],
    "preLaunchTask": "build"
}
...

Intermediate Format

The project provides a collection of interfaces for an intermediate 3D format that is meant to be used by all loaders and writers. When implementing a new loader, make sure that its output implements the intermediate format's IScene interface. Similarly, this interface should also be expected as the input to all new writers.