December 18, 2017

Screenshot automation of 2D Drawings with Forge Viewer and Headless Chrome

    Last week I showed you how to run the Forge Viewer headless in Chrome with Puppeteer and an obvious use case of that is automating the generation of high-quality screenshots of your models. The workflow also applies to 2D drawings, however taking a direct shot of a drawing will generate an image that contains not only the drawing sheet but also the borders, which in some cases may not be desirable:

unclipped screenshot

    In order to strip down our screenshot to the bare sheet, we need to add a bit of extra code. The first step is to find out the extends of the sheet in model space and convert that to screen coordinates:

var bbox = viewer.model.getBoundingBox()

var screenbbox = {
    min: viewer.worldToClient(bbox.min),
    max: viewer.worldToClient(bbox.max)

    We need to pass that information to the Puppeteer code: an easy way to communicate between the client and node code is to use Puppeteer evaluate function. This can execute a function in browser context or retrieve a value from the DOM objects, so I simply set the bounding box result in the document:

      function () {


        var bbox = viewer.model.getBoundingBox()

        document.bbox = {
          min: viewer.worldToClient(bbox.min),
          max: viewer.worldToClient(bbox.max)

    We can then simply access that value from Puppeteer side and use the page.screenshot function, which takes a optional clip argument allowing to clip the image that is being generated:

    options <Object> Options object which might have the following properties:
    // ...
    clip <Object> An object which specifies clipping region of the page. Should have the following fields:
        x <number> x-coordinate of top-left corner of clip area
        y <number> y-coordinate of top-left corner of clip area
        width <number> width of clipping area
        height <number> height of clipping area

    So our node code looks like below: 

const page = await browser.newPage()

await page.goto(url)

const mainFrame = page.mainFrame()

//waits for .geometry-loaded class being added
await mainFrame.waitForSelector(
  '.geometry-loaded', {
    timeout: 30000

const bbox = await mainFrame.evaluate(() => {
  return document.bbox

// ensure min >= 0
bbox.min.x = bbox.min.x > 0 ? bbox.min.x : 0

const clip  = {
  height: bbox.min.y - bbox.max.y - 5, //5 pixels correction 
  width: bbox.max.x - bbox.min.x,
  x: bbox.min.x,
  y: bbox.max.y,

// saves screenshot in process.env.IMGPATH
// or defaults to test.png
await page.screenshot({
  path: process.env.IMGPATH || 'test.png',

    And here is how our drawing screenshot looks after running the test:

Clipped screenshot

    Check out the full project at:


Related Posts