15 Jun 2023

Persist Markups Data in Viewer

Markups Persist

It's easy to create markups with few lines of code, but viewer doesn't save those and persists the next time when you load the viewer.

In this blog we'll explain and give the code snippet just to do that.

Working code is written as sample and can be found here: GitHub Sample

After adding the markup in the viewer let's capture the data by listening to events and get the markups data. It's best practise to do any custom action after OBJECT_TREE_CREATED_EVENT is emitted, which is shown in the sample

let markupext = viewer.getExtension('Autodesk.Viewing.MarkupsCore');
let urn = viewer.model.getSeedUrn();

// Fired whenever the drawing tool changes. For example, when the Arrow drawing tool changes into the Rectangle drawing tool.
markupext.addEventListener('EVENT_EDITMODE_CHANGED', function (ev) {
    const editTool = ev.target;
    if (editTool) {
        //Fired when a markup has been created. For example, as soon as the user stops dragging and releases the mouse button to finish drawing an arrow on the screen
        editTool.addEventListener('EVENT_EDITMODE_CREATION_END', function (ev) {
            saveMarkups()
        });
        // Fired when a markup is no longer selected.
        editTool.addEventListener('EVENT_MARKUP_DESELECT', function (ev) {
            saveMarkups()
        });
    }
});

// The selected markup is no longer being modified
markupext.editFrame.addEventListener('EVENT_EDITFRAME_EDITION_END', function (ev) {
    saveMarkups()
});

async function saveMarkups(){
    try {
        let markupsPdata = markupext.generateData();
        const resp = await fetch('/api/markups', { method: 'POST',headers: { "Content-Type": "application/json",}, body: JSON.stringify({ 'urn':urn,'data': markupsPdata }) });
        if (!resp.ok) {
            throw new Error(await resp.text());
        }
    } catch (error) {
            console.log(error)
    }
}

Now we have the markups data as string, you can store it on the server side using the file system, eg in nodejs:

const fs = require('fs')
fs.writeFile(`${req.body.urn}.svg`, req.body.data, (err) => {
    if (err) throw err;
    console.log('SVG written!');
});

Now send the data from the server to the client:

const fsPromises = require('fs').promises;
const svgString = await fsPromises.readFile(`${req.query.urn}.svg`, "utf8");

The last step is to get the SVG string and load the markups. You can convert file/binary into a string on either client or server, depending on your implementation. Let's assume markupsPdata is the SVG in string format on the client side.

// Make sure you load the extensions 'Autodesk.Viewing.MarkupsCore', 'Autodesk.Viewing.MarkupsGui'
let urn = viewer.model.getSeedUrn(); 
async function loadMarkups() {
    try {
        const resp = await fetch(`/api/markups?urn=${urn}`, { method: 'GET'});
        if (!resp.ok) {
            throw new Error(await resp.text());
        }
        const data = await resp.json();
        if (data.markups) {
            markupext.leaveEditMode()
            markupext.show();
            markupext.loadMarkups(data.markups, 'my-custom-layer');
            markupext.enterEditMode('my-custom-layer');
        }
    } catch (error) {
        console.log(error)
    }
}

The last point to be noted is the markups data you get in the form of SVG string can be stored as anything on the server, just store as compressed zip in S3, store as raw data, or as an SVG file, whichever method is convenient.

Since the data is in SVG form, it's a bit verbose and not ideal to store in DB. This is just POC, in your application you'll have multiple markups, you need to create and store markup id, urn and viewer state.

Happy Coding.

 

Related Article