22 Jan 2018

Area Planning Tool with Forge Viewer and MySQL

Introduction:

I recently came across a customer wanting to measure Floor spaces in a collaborative way.  The desktop way, was via installing ‘Autodesk Design Review’, then loading the DWG file, perform the area-measurement and markup.  Finally  save and email a DWF file to others in the organization.  If only there was a better way.  Could I move this workflow to the cloud ?  

> To the cloud !

Live demo: https://wallabyway.github.io/area-markup/

(note: click measurement tool before you start)

source code - GitHub: https://github.com/wallabyway/area-markup

With Autodesk’s Forge Viewer, I could view a DWG file in a browser and use the built in ‘measureTool’ and ‘markup Tool’  to create new area-measurements.  I can do this in a collaborative way, if I could only save the markup data from Forge Viewer and serialize it to a mySQL server.  

architecture

From the diagram above, the Forge Viewer (on the left) has APIs that allow me to associate each individual 'blue' markup areas, which I create in the browser, to an ID taken from the mySQL database (on the right, see Markup table IDs).  This keeps things in sync between the browser and database, to doing more powerful database queries like…

- filtering markups based on user login

- markups by floor level 

- markups in an approval process 

- markups associated with a DWG file version

Using Vue.js and the ‘ToDo’ example, I can make a quick UI to control these filters, combined with the Forge Viewer for handling the DWG and markups.  I can turn markup-areas on and off like photoshop ‘layers’ (see tick boxes on the left), and tabulate ‘total area’.   But first, let’s start with 'how to persist markup data' from the forge viewer...

Forge-Viewer + Markup Extensions:

Forge Viewer has two build-in extensions that could do the job, but they are slightly different.  Which do we choose... The 'markup tool' or the 'measurement tool' ? 

The markup tool API, while great for grabbing static SVG, is missing a few key features I needed.   For example, there is no floating label with ‘square feet’ , no way to calculate area, or total area of a group of measurements, no way to hide and show individual markups, drag existing polyline points around or persist the markup behavior state (more on that below).

This sounded like a job for ‘area measure’ tool.  It ticks all the boxes, except for…  persistence/serialization.  There’s no API to save or restore area-measurements and we need that to save it to our mySQL database…. so let’s add it !

JSON and Back Again:

The code below adds a few methods to the 2D ‘measure’ extension so I can serialize and deserialize area-measurements to JSON.  Once I have the JSON, I can easily send it to my database for storage and retrieval.

The two important methods are … getJson() and loadJson()

GitHub/measure/MeasureTool.js

We need to serialize the ‘measurementsList’ object to JSON.  We do this easily with JSON.stringify().

The harder part is loadJson(), which deserializes our JSON string into our Forge Viewer objects.  The main trick is re-instantiating various sub-objects like threejs vector3 and Forge.picking etc.

MeasureTool Extension:

The markup extension conveniently calculates the square foot and provides the information in the 'currentMeasurement.area' json object.  I use it when I click the save 's' key.  

I set a bunch of defaults for this particular use-case, where I need the units to display as 'decimal-ft', no decimal places, and default the measurement tool to 'area' in the code here.

this.setUnits("decimal-ft");
this.setPrecision(0);
this.changeMeasurementType(avem.MEASUREMENT_AREA);

See Philippe's blog post for more details on the options available.

Note: This extension comes pre-build with Viewer v3.3.  I only modify one file, measureTool.js which overwrites the existing code with our custom code (see line75 index.html ), so this technique is 'unsupported'.

Vue.js - In addition to the extension code, my front-end code connects Forge Viewer to Vue.js/bootstrap4 in a minimal way, without a builder like webpack.  Vue.js makes doing ‘To Do’ lists in a very clean way, compared to jQuery. (see Vuejs to do)

To connect Forge Viewer extension to Vue.js, I use two signals - ‘restoreJson’ and ‘deleteMarkup’ via dispatch events. When I un-tick on a markup-item (the list on the left), I send a removeData event to my extension code:   

Vue.js event-dispatch code here:

cardClick: function (item) {
      if (!item.completed) {
        viewer.dispatchEvent(new CustomEvent('removeData', {'detail': item.MarkupID}));
      }

with a corresponding listener in my extension here:

_viewer.addEventListener("removeData", this.onRemoveMeasurementBinded);

With more time, I would like to add ‘refresh’ to avoid a browser refresh when I save new markups (see the video at 1:34mins), and a ‘highlight’ event, to change the color of a markup when I hover over an area-markup list item. I’ll leave that as an exercise to the reader.  

Server Code

I use a minimal server that connects node.js to MySQL providing some simple CRUD endpoints

// Get all markup's from MySQL stored proc 'queryApproval script' given input: approvalid
app.get('/allMarkup', (req, res) => {
    connection.query('CALL queryApproval(?)',[req.query.approvalid], function(err, rows) {
        if (err) throw err;
        res.status(200).send( JSON.stringify(rows[0]) );
    });
});

You can find all the backend javascript code and database build-script and test-script on GitHub.  For debugging, I use localhost.  I also use this neat tunneling service called ngrok, to demo a public version.  I decided to host this on GitHub pages instead of a Heroku deploy, for something new, but it means the demo link above is limited and cannot permanently delete or save new markup.  I hardwired a pre-canned dummyData.json file containing markups.   To make the frontend code point to a live MySQL server, uncomment line 68 in app.js and set the MySQL environment variables using:

export MYSQL_host=xxx.rds.amazonaws.com
export MYSQL_user=xxx
export MYSQL_password=xxx
export MYSQL_database=xxx
node server.js

Now open a browser at localhost:3000.  Here’s a quick video of it in action:

spacePlanner from mb on Vimeo.

Here you will see that ticking the boxes on the left side list, will change the visibility of area-markups.  It also updates the total ‘square foot’ calculation at the bottom of the list.   I then click on an existing area measurement and grab the edges to modify the markup.

I then demonstrate how to draw a new markup and save it.  In the video, I click the ’S’ button (for save), which then saves it to the MySQL table called ‘markups’.  You’ll notice that I hit browser refresh to show the item appear in the Vue.js list.  See my comments above on how to improve this.

Remember to follow @micbeale on Twitter.

..Feel free to add any issues you find to my Github issues repo.

Related Article