3 Oct 2017

Enrich Fusion 360 data for the Viewer

When displaying your Fusion 360 model in the Viewer it already has quite a bit of data on the various components. However, it does not contain custom data like e.g. Attributes.

One way to go around that is to store additional data outside the model which will be available from the Viewer as well. One benefit of that is that the extra data will be available from anywhere, even without the model, so can be modified any time.

Since Fusion 360 has an API, and the available programming languages support HTTP requests, therefore we can also reach out to web services to store our data.

I) Our Fusion 360 add-in will implement a simple command that lets the user select a body and assign data to it. This data will be saved on mLab and then can be reached from inside the Viewer client as well.

Just for fun, and to show how you can add custom Python modules to your code, this time I’m going to use PyMongo to connect directly to our database on mLab and store there the data. You can simply copy the source code of PyMongo into your Fusion 360 add-in's folder, then use the following code you make sure that Python will be able to find it:

# Makes sure that additional modules stored inside the
# add-in folder will be found
import os, sys
my_addin_path = os.path.dirname(os.path.realpath(__file__)) 
if not my_addin_path in sys.path:
   sys.path.append(my_addin_path)

A nicer solution would be to hide the database access behind the server code and then talk to the server from the Fusion 360 add-in using HTTP requests.

In order to identify the body that you want to associate data with we need to save:
- the document's Item ID - returned by app.activeDocument.dataFile.id
- version number of the document - returned by app.activeDocument.dataFile.versionNumber
- path inside the Fusion 360 model to the body, e.g. “FirstPart:1+SubComponent:1+Body1” - returned by body.assemblyContext.fullPathName

Now when you open a given item version in the Viewer you can check in your database if there are items for that based on the Item ID and version number. Then when a body gets selected, you can check for data associated with a body that has the same path inside the model.

Here is the source code of the Fusion 360 add-in: https://github.com/adamenagy/fusion-addin-mongodb
Make sure to update the value of the uri variable in it!

II) Our Viewer web app will provide a button in the toolbar that will try to get data from mLab through our server about the selected body. If it finds something then it will simply show it in an alert box.

On the server side we will provide an end point that the web app can call to get the body data. This will connect to mLab and run a query using the shown model’s Item ID, version number, and the selected body’s full path in the model.

We can get the Item ID and version number from the Version ID like so:
Version ID: urn:adsk.wipprod:fs.file:vf.dhFQocFPTdy5brBtQVvuCQ?version=1 
Item ID: urn:adsk.wipprod:dm.lineage:dhFQocFPTdy5brBtQVvuCQ
Version number
: 1

In order to use the Fusion 360 add-in and the web app code you first need to create your mongo database somewhere. In case of creating it on mLab you need to follow these steps after creating an account.

1) Create a new database:

Create new database

2) Add a collection called “mycollection” - you can name it something else too, but then have to change that in both the add-in and the web app code:

Create new collection

3) Add a database user

Create new user

4) Now you just need the connection string that mLab provides and place the user name and password in it:

Use connection string

In case of the Fusion 360 add-in you just have to set the “uri” variables value in FusionData.py to the connection string, and in the web app just create an Environment variable named MLAB_URL and assign the same connection string value to it.

I added the web app code to the Model Derivative API sample: https://github.com/Autodesk-Forge/model.derivative-nodejs-sample 

The relevant parts are listed below.

The server side code (data.management.js):

// Get the item id and version number from the 
// base64 encoded version id
function getIdAndVersion(urn64) {
    var urn = new Buffer(urn64, 'base64').toString("ascii");
    // urn will be something like this:
    // urn:adsk.wipprod:fs.file:vf.dhFQocFPTdy5brBtQVvuCQ?version=1
    urn = urn.replace('urn:adsk.wipprod:fs.file:vf.', '')
    var parts = urn.split('?version=');

    var itemId = parts[0];
    var version = parts[1];

    return { itemId: "urn:adsk.wipprod:dm.lineage:" + itemId, version: parseInt(version) };
}

// Expose an end point through which the client can check if our
// mongo db contains info about the selected body
router.get('/fusionData/:urn/:path', function (req, res) {
    var urn = req.params.urn;
    var path = req.params.path;

    var mongodb = require('mongodb');
    var mongoClient = mongodb.MongoClient;

    // You could also put the connection URL here, but it's nicer to have it
    // in an Environment variable - MLAB_URL
    mongoClient.connect(process.env.MLAB_URL, function(err, db){
        if (err) {
            console.log(err);
            console.log("Failed to connect to MongoDB on mLab");
            res.status(500).end();
        } else {
            mongoClient.db = db; // keep connection
            console.log("Connected to MongoDB on mLab");

            var query = getIdAndVersion(urn);
            query.fullPath = path;

            var coll = db.collection("mycollection");

            coll.find(query).toArray(function(err, results) {
                console.log(results);

                res.json(results);
            });
        }
    });
})

The client side code (scripts.js):

// Get the full path of the selected body
function getFullPath(tree, dbId) {
    var path = [];
    while (dbId) {
        var name = tree.getNodeName(dbId);
        path.unshift(name);
        dbId = tree.getNodeParentId(dbId);
    }

    // We do not care about the top 2 items because it's just the file name
    // and root component name
    path = path.splice(2, path.length - 1)

    return path.join('+');
}

// Adds a button to the Viewer toolbar that can be used
// to check for body specific data in our mongo db
// Call this once the Viewer has been set up
function addFusionButton(viewer) {
    var button = new Autodesk.Viewing.UI.Button('toolbarFusion');
    button.onClick = function (e) {
        var ids = viewer.getSelection();
        if (ids.length === 1) {
            var tree = viewer.model.getInstanceTree();
            var fullPath = getFullPath(tree, ids[0]);
            console.log(fullPath);

            // In our case the server endpoint for this starts with '/dm/fusionData'
            $.ajax ({
                url: '/dm/fusionData/' + viewer.model.loader.svfUrn + '/' + encodeURIComponent(fullPath),
                type: 'GET'
            }).done (function (data) {
                console.log('Retrieved data');
                console.log(data);

                alert(JSON.stringify(data, null, 2));
            }).fail (function (xhr, ajaxOptions, thrownError) {
                alert('Failed to retrieve data') ;
            }) ;
        }
    };
    button.addClass('toolbarFusionButton');
    button.setToolTip('Show Fusion properties');

    // SubToolbar
    var subToolbar = new Autodesk.Viewing.UI.ControlGroup('myFusionAppGroup');
    subToolbar.addControl(button);

    viewer.toolbar.addControl(subToolbar);
}

Here it is in action:

Related Article