14 Dec 2017

Running Forge Viewer headless in Chrome with Puppeteer

Default blog image

    Here is a topic that came up several times recently from various developers using the Forge Viewer and the Model Derivatives API: how to get high quality thumbnails of a specific model?

    The Model Derivatives API doesn't offer you thumbnails bigger than 400x400 pixels and there is no way to obtain a custom thumbnail with different camera orientation, hidden parts and so on... 

    Fortunately there is a workaround and it's pretty straightforward to put in place: it consist in running a webpage with the Forge Viewer through Puppeteer, a Node.js API that lets you run Chrome Browser in headless mode, so no interaction and no client browser needed, all can be automated and performed server side. You are free to run any web page online or local to the node application, use the Puppeteer API to interact with the page and also the Forge Viewer API to potentially set camera and any other interaction with the model that you could perform from a classic page loaded in a client browser.

    I spend a few hours experimenting with Puppeteer and created a quick proof of concept (PoC) that loads a model, waits for the Autodesk.Viewing.GEOMETRY_LOADED_EVENT and adds a class to the div that contains the viewer. On the Puppeteer side you have several, including page.waitForSelector, which can spot DOM modifications and acts accordingly, in our scenario generate a shot and end the test successfully.

    Here is the Puppeteer code I created in order to achieve the workflow describe above:

import puppeteer from 'puppeteer'
import Forge from 'forge-apis'
import 'babel-polyfill'
import path from 'path'

// Fetch Forge token
const getToken = () => {
  
  const scope = [
    'viewables:read'
  ]
  
  const oAuth2TwoLegged = new Forge.AuthClientTwoLegged(
    process.env.FORGE_CLIENT_ID,
    process.env.FORGE_CLIENT_SECRET,
    scope)
  
  return oAuth2TwoLegged.authenticate()
}

// Runs the test
(async () => {
  
  // Using the workaround to run chrome with
  // WebGL enabled
  const browser = await puppeteer.launch({
    headless: false,
    args: [
      '--hide-scrollbars',
      '--mute-audio',
      '--headless'
    ]
  })
  
  try {
    
    // assumes files are in ./test relative directory
    // chrome doesn't support relative path
    const filename = path.resolve(__dirname, './test/viewer.html')
    
    const token = await getToken()
    
    // assumes URN is defined in ENV var
    const urn = process.env.URN
    
    const url = `file://${filename}?accessToken=${token.access_token}&urn=${urn}`
    
    const page = await browser.newPage()
  
  await page.goto(url)
    
    // waits for .geometry-loaded class being added
  await page.mainFrame().waitForSelector(
    '.geometry-loaded', {
      timeout: 30000
    })
    
    // saves screenshot in process.env.IMGPATH
    // or defaults to test.png
  await page.screenshot({
    path: process.env.IMGPATH || 'test.png'
  })
    
    console.log('Test sucessful :)')
    
  } catch (ex) {
    
    console.log('Test failed :(')
    console.log(ex)
    
  } finally {
  
  await browser.close()
  }
})()

    The client side code of the page loading the Viewer is rather classic, I only added an event for GEOMETRY_LOADED and add a .geometry-loaded class to the container div which can be hooked by the Puppeteer code:

initialize({
  
  acccessToken: getQueryParam("acccessToken"),
  env: "AutodeskProduction"
  
}).then(function() {
  
  loadDocument ("urn:" + getQueryParam("urn")).then(function(doc) {
    
    var items = getViewableItems (doc, ["3d", "2d"])
    
    var path = doc.getViewablePath(items[0])
    
    var viewerDiv = document.getElementById("viewer")
    
    var viewer = getQueryParam("showToolbar")
      ? new Autodesk.Viewing.Private.GuiViewer3D(viewerDiv)
      : new Autodesk.Viewing.Viewer3D(viewerDiv)
    
    viewer.addEventListener(
      Autodesk.Viewing.GEOMETRY_LOADED_EVENT,
      function () {
        viewerDiv.classList.add("geometry-loaded")
      })
    
    viewer.start(path)
  })
})

    And that's it! You can find the complete project and how to run it at:

       https://github.com/leefsmp/forge-viewer-headless

    Goes without saying that the approach is not only useful for generating high quality thumbnails and screenshots but can also be used for server-side unit tests and all kind of advanced automated testing ...

 

 

Related Article