21 Apr 2021

Print sheet from Viewer

The Viewer does not provide Print related functionality. What BIM 360 does as well is it creates a screenshot of the sheet inside the Viewer, then uses an html page showing the image and triggers the window.print() function once it's ready.

There are blog posts on taking screenshots and generating an image of the sheet (even on a server).
Instead of calculating the area of the view that contains the actual sheet, we can use Autodesk.Viewing.ScreenShot.getScreenShotWithBounds() with { fullPage: true, margin: 0 } option. Its onDone callback function will provide the actual width and height, and the blob URL of the generated image. Using those we can adjust our template to create the html page we can print. Then we just have to trigger the window.print() function.

We can also automatically close the additional tab/window once the Print dialog is closed by handling the onafterprint event.

Here is the sample implementing this - see the printSheet() function:

<html>
<head>
    <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1, user-scalable=no" />
    <meta charset="utf-8">

    <!-- The Viewer CSS -->
    <link rel="stylesheet" href="https://developer.api.autodesk.com/modelderivative/v2/viewers/7.*/style.min.css" type="text/css">

    <!-- Developer CSS -->
    <style>
        body {
            margin: 0;
            position: relative;
        }
        
        #MyContainerDiv {
            width: 100vw;
            height: 100vh;
            position: relative;
        }
    </style>
    
    <title>Print Sheet</title>

</head>

<body>

    <!-- The Viewer will be instantiated here -->
    <div id="MyContainerDiv">
        <div id="MyViewerDiv"></div>
    </div>
    
    <!-- The Viewer JS -->
    <script src="https://developer.api.autodesk.com/modelderivative/v2/viewers/7.*/viewer3D.js"></script>

    <!-- jQuery -->
    <script src="https://code.jquery.com/jquery-3.2.1.min.js" integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4=" crossorigin="anonymous"></script>

    <!-- Developer JS -->
    <script>

        // this is the iframe URL that shows up when sharing a model embed on a page
        var embedURLfromA360 = 'https://autodesk3743.autodesk360.com/shares/public/SH919a0QTf3c32634dcf07b66797ba369695?mode=embed' // PDF file

        var viewer

        function getURN(onURNCallback) {
            $.get({
                url: embedURLfromA360.replace('public', 'metadata').replace('mode=embed', ''),
                dataType: 'json',
                success: function (metadata) {
                    if (onURNCallback) {
                        let urn = btoa(metadata.success.body.urn).replace("/", "_").replace("=", "")
                        onURNCallback(urn)
                    }
                }
            })
        }

        function getForgeToken(onTokenCallback) {
            $.post({
                url: embedURLfromA360.replace('public', 'sign').replace('mode=embed', 'oauth2=true'),
                data: '{}',
                success: function (oauth) {
                    if (onTokenCallback)
                        onTokenCallback(oauth.accessToken, oauth.validitySeconds)
                }
            })
        }

        getURN(function (urn) {
            var options = {
                env: 'AutodeskProduction',
                getAccessToken: getForgeToken
            }
            var documentId = 'urn:' + urn
            Autodesk.Viewing.Initializer(options, function onInitialized() {
                Autodesk.Viewing.Document.load(documentId, onDocumentLoadSuccess)
            })
        })

        function onDocumentLoadSuccess(doc) {

            // A document contains references to 3D and 2D viewables.
            var items = doc.getRoot().search({
                'type': 'geometry',
                'role': '2d'
            }, true)
            if (items.length === 0) {
                console.error('Document contains no viewables.')
                return
            }

            var viewerDiv = document.getElementById('MyViewerDiv')
            viewer = new Autodesk.Viewing.GuiViewer3D(viewerDiv)
            viewer.start()
            
            viewer.loadDocumentNode(doc, items[1], {}).then(() => {
                let group = viewer.toolbar.getControl('printToolbar');
                if (!group) {
                    group = new Autodesk.Viewing.UI.ControlGroup('printToolbar');
                    viewer.toolbar.addControl(group);
                }

                // Add a new button to the toolbar group
                button = new Autodesk.Viewing.UI.Button('printButton');
                button.setIcon('adsk-icon-layers');

                button.onClick = () => {
                    printSheet(viewer)
                }

                button.setToolTip('Print Sheet');
                group.addControl(button);
            })

        }

        function printSheet(viewer) {
            let onDone = async (blob, imageWidth, imageHeight) => {
                let res = await fetch("printpage.html")
                let content = await res.text()
                // keep height even to avoid extra empty page in print dialog
                imageHeight = imageHeight % 2 ? imageHeight + 1 : imageHeight
                content = content
                    .replace(new RegExp("%IMG_WIDTH%", "g"), imageWidth)
                    .replace(new RegExp("%IMG_HEIGHT%", "g"), imageHeight)
                    .replace("%IMG_SRC%", blob)

                let w = window.open('', '')
                if (w) {
                    w.document.open()
                    w.document.write(content)
                    w.document.close()
                } else {
                    console.info("Could not open a new window")
                }
            }

            const canvasBounds = viewer.impl.getCanvasBoundingClientRect()
            const originalWidth = canvasBounds.width
            const originalHeight = canvasBounds.height
            let widthInPixels
            let pageWidth = viewer.model.getMetadata('page_dimensions', 'page_width')
            const pageUnits = viewer.model.getMetadata('page_dimensions', 'page_units')

            // no page_units => raster
            if (pageUnits) {
                const widthInInch = Autodesk.Viewing.Private.convertUnits(pageUnits.toLowerCase(), 'in', 1, pageWidth)
                const DPI = 150

                widthInPixels = Math.floor(widthInInch * DPI)
            } else {
                widthInPixels = pageWidth
            }

            const canvasRatio = originalHeight / originalWidth
            const width = widthInPixels 
            const height = Math.floor(widthInPixels * canvasRatio)

            const screenshotOptions = {
                fullPage: true,
                margin: 0,
            }

            Autodesk.Viewing.ScreenShot.getScreenShotWithBounds(viewer, width, height, onDone, screenshotOptions)
        }
    </script>

</body>

</html>

Here is the template it's using (printpage.html):

<html>
  <head>
    <style>
      @page {
        size: %IMG_WIDTH%px %IMG_HEIGHT%px;
        margin: 0;
      }

      @media print {
        html,
        body {
          margin: 0;
        }
      }
    </style>
    <script>
      function onImageLoad() {
        window.print();
      }

      function onAfterPrint() {
        window.close();
      }
    </script>
  </head>
  <body onafterprint="onAfterPrint()">
    <img src="%IMG_SRC%" width="%IMG_WIDTH%px" height="%IMG_HEIGHT%px" onload="onImageLoad()" />
  </body>
</html>

  

 

Related Article