15 Mar 2021

Drag and Drop in Design Automation for Inventor sample

I wrote about how to Drag and Drop models into the Viewer, but how could it be done in case of the Design Automation for Inventor sample?

One thing you'll need to find out is how to access the models: either the ones stored on the Design Automation sample's server, or the ones stored in the OSS bucket used by the sample, where the SVF is generated by the Model Derivative API. The below steps will help with both.

First let's add a panel to the UI where we can show thumbnails of our models, and let the user drag and drop from. In the WebApplication >> ClientApp >> src >> components folder, add a new file called modelLoadContainer.js with the following content:

import React, { Component } from 'react'
import './modelLoadContainer.css';
import * as dragDrop from './dragDropEventHandlers.js';

export default class ModelLoadContainer extends Component {
    render() {
        return (
            <div className="modelLoadContainer">
                <img
                    onDragStart={dragDrop.onDragStart}
                    src="/models_car.png"
                    urn="urn:dXJuOmFkc2sub2JqZWN0czpvcy5vYmplY3Q6cHJvamVjdHMtemZpLWI3NDc0Y2ZjMDEwMWMxMjNmMTJiNDhmZC9pTG9naWNGb3JtVGVzdC5pcHQ"
                />
                <img 
                    onDragStart={dragDrop.onDragStart}
                    src="/models_wheel.png"
                    urn="/data/978ED0F470D04A48AED4C1FA89ED4E124BF91684/Wheel/4AA37CDB526453C8081256449534CFF91B37C4B5/SVF/bubble.json"
                />
            </div>
        )
    }
}

Note: the urns might be different in your instance of the sample. You can double-check them the following way.
If the model is one of the projects managed by the sample (like the Wheel or Wrench packages) then once it's loaded you can check the path in the browser's developer console and just add "/bubble.json" after the "/SVF/" part at the end:

Path of managed project  
In case of a file stored on OSS, one easy way to find out the urn you need to provide is to load the file in the Buckets Tools web app, which will print the loaded model's urn in the developer tools window:

Path of OSS project

Don't forget to prefix the value you are providing with "urn:" - see example in above code.

We also need to add modelLoadContainer.css to provide styling:

.modelLoadContainer{
    display: flex;
    flex-flow: column;
    min-width: 200px;
    width: 10%;
    background-color: white;
}

Now we have to reference our component from the tabsContainer.js file so that it will show up in the UI. Under the various import statements add this:

import ModelLoadContainer from './modelLoadContainer';

Then right under <ForgeView /> add this:

<ModelLoadContainer />

To make the viewer accessible from our new panel, let's store the viewer instance under the window object. In forgeView.js file, after the line

this.viewer = new Autodesk.Viewing.GuiViewer3D(container);

add the following:

window.viewer = this.viewer;

Then add the drag and drop event handlers to the viewer component, the one with id="ForgeViewer" 

<div className="viewer" id="ForgeViewer" onDragOver={dragDrop.onDragOver} onDrop={dragDrop.onDrop}>

At the bottom of the import section add this as well:

import * as dragDrop from './dragDropEventHandlers.js';

We still need to provide the code that will help with the drag and drop process and load the model. So let's add another file in the components folder called dragDropEventHandlers.js with the following content:

let viewer = null;
let mainModel = null;
let draggedModel = null;
let draggedModelUrn = null;
let Autodesk = null;

export function onDragStart(event) {
    event.dataTransfer.effectAllowed = 'copy';

    let img = event.target;
    draggedModelUrn = img.getAttribute("urn");

    Autodesk = window.Autodesk;
    viewer = window.viewer;
    mainModel = viewer.model;
}

// Load model
const ModelState = {
    unloaded: 0,
    loading: 1,
    loaded: 2,
};
let modelState = ModelState.unloaded;

function loadDocument(documentId) {
    Autodesk.Viewing.Document.load(documentId, (doc) => {
        let items = doc.getRoot().search(
            {
                type: "geometry",
                role: "3d",
            },
            true
        );
        if (items.length === 0) {
            console.error("Document contains no viewables.");
            return;
        }

        viewer
            .loadDocumentNode(doc, items[0], {
                keepCurrentModels: true
            })
            .then(function (model) {
                draggedModel = model;
                modelState = ModelState.loaded;
            });
    });
} 

export function onDragOver(event) {
    event.preventDefault();
   
    switch (modelState) {
        case ModelState.unloaded: {
            modelState = ModelState.loading;

            // Check if it's a urn pointing at a file from OSS
            if (draggedModelUrn.startsWith("urn:")) {
                fetch("/api/viewables/token")
                    .then(response => response.text())
                    .then(token => {
                        Autodesk.Viewing.endpoint.setEndpointAndApi("https://developer.api.autodesk.com", 'modelDerivativeV2');
                        Autodesk.Viewing.endpoint.HTTP_REQUEST_HEADERS["Authorization"] = "Bearer " + token;
                        loadDocument(draggedModelUrn);
                    })
            } else {
                Autodesk.Viewing.endpoint.setEndpointAndApi("", 'modelDerivativeV2');
                delete Autodesk.Viewing.endpoint.HTTP_REQUEST_HEADERS["Authorization"];

                loadDocument(draggedModelUrn);
            }
            
            break;
        }

        case ModelState.loaded: {
            var boxRectangle = event.target.getBoundingClientRect();

            let x = event.clientX - boxRectangle.left;
            let y = event.clientY - boxRectangle.top;

            let res = viewer.impl.hitTest(
                x,
                y,
                true,
                null,
                [mainModel.getModelId()]
            );
            let pt = null;

            if (res) {
                pt = res.intersectPoint;
            } else {
                pt = viewer.impl.intersectGround(x, y);
            }

            let tr = draggedModel.getPlacementTransform();
            tr.elements[12] = pt.x;
            tr.elements[13] = pt.y;
            tr.elements[14] = pt.z;
            draggedModel.setPlacementTransform(tr);
            viewer.impl.invalidate(true, true, true);

            break;
        }
    }
}

export function onDrop(event) {
    event.preventDefault();
    modelState = ModelState.unloaded;
}

In order to load the SVF of a file stored in OSS (without creating a proxy), we need to have an access token with viewables:read scope. So far the sample code did not need any access token on the client side, because the SVF content was always provided directly from its own server (as done here too), instead of relying on Model Derivative service.

The code above assumes that we have an endpoint that provides such an access token - see fetch("/api/viewables/token")
So let's add it. 

We can create a file called ViewablesController.cs in the WebApplication >> Controllers folder with this content:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Autodesk.Forge;
using Autodesk.Forge.Core;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;

namespace WebApplication.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class ViewablesController : ControllerBase
    {
        private readonly ForgeConfiguration _configuration;
        public ViewablesController(IOptions<ForgeConfiguration> configuration)
        {
            _configuration = configuration.Value;
        }

        [HttpGet("token")]
        public async Task<string> ListAsync()
        {
            var twoLeggedApi = new TwoLeggedApi();
            Autodesk.Forge.Client.ApiResponse<dynamic> response = 
                await twoLeggedApi.AuthenticateAsyncWithHttpInfo(
                    _configuration.ClientId, 
                    _configuration.ClientSecret, 
                    oAuthConstants.CLIENT_CREDENTIALS, 
                    new Scope[] { Scope.ViewablesRead });

            // The JSON response from the oAuth server is the Data variable 
            // and has already been parsed into a DynamicDictionary object.
            dynamic bearer = response.Data;
            return bearer.access_token;
        }
    }
}

The last thing to do is provide the images referenced in modelLoadContainer.js, so place these files in the WebApplication >> ClientApp >> public folder:

Wheel Car

 

 

Related Article