27 Apr 2017

Comparing versions with Viewer



There is a cool feature on BIM 360 Team to compare versions of a file, but what if the file is hosted somewhere else, say Google Drive?

We do have a sample on how show Google Drive files with Viewer: just download from Google Drive to your server, upload to an OSS Bucket, translate and show. 

Now let's modify this sample to show 2 models, say model A and model B, which are 2 versions of the same project. For Revit files, each dbId in the Viewer is linked to an External ID property, which is the Revit unique GUID of the element. That's all we need: all external IDs on model A and NOT on model B were removed, the same way, all external IDs on model B and NOT on model A were added. That's easy. To identify what was changed, for each external ID on both models let's compare all properties if any value changed, then we have modified elements. Done. Now just color them as green (added), red (removed) and orange (modified), and ghost everything else. The full source (forked) is available here

When you run it, note the console showing what was added, removed and modified. One can improve this to show a list of changes in a better way, right?

Most of the work is performed by the extension below, but you need to keep capture both models and load the extension with it, as shown here (the other of A and B are important!).

function VersionChanges(viewer, options) {
  Autodesk.Viewing.Extension.call(this, viewer, options);

VersionChanges.prototype = Object.create(Autodesk.Viewing.Extension.prototype);
VersionChanges.prototype.constructor = VersionChanges;

VersionChanges.prototype.load = function () {
  var modelA = this.options.modelA;
  var modelB = this.options.modelB;

  var removed = {};
  var added = {};
  var modified = {};

  var red = new THREE.Vector4(1, 0, 0, 1);
  var green = new THREE.Vector4(0, 0.5, 0, 1);
  var orange = new THREE.Vector4(1, 0.6, 0.2, 1);

  viewer.addEventListener(Autodesk.Viewing.OBJECT_TREE_CREATED_EVENT, function () {
    listElements(function (listA, listB) {
      // make all elements as ghost

      for (var extIdA in listA) {
        if (listB[extIdA] != null) continue;
        var dbIdA = listA[extIdA].dbId;
        removed[extIdA] = dbIdA;
        console.log('Removed dbId: ' + dbIdA);
        viewer.impl.visibilityManager.show(dbIdA, modelA);
        viewer.setThemingColor(dbIdA, red, modelA);

      for (var extIdB in listB) {
        if (listA[extIdB] != null) continue;
        var dbIdB = listB[extIdB].dbId
        added[extIdB] = dbIdB;
        console.log('Added dbId: ' + dbIdB);
        viewer.impl.visibilityManager.show(dbIdB, modelB);
        viewer.setThemingColor(dbIdB, green, modelB);

      for (var extId in listA) {
        if (typeof listB[extId] === 'undefined') continue; // removed dbId

        var dbId = listA[extId].dbId; // should be the same as listB[extId]
        var propsA = listA[extId].properties;
        var propsB = listB[extId].properties;

        for (var i = 0; i < propsA.length; i++) {
          if (propsB[i] == null) continue;
          if (propsA[i].displayCategory.indexOf('__')==0) continue; // internal properties
          if (propsA[i].displayValue != propsB[i].displayValue) {
            console.log('Property ' + dbId + ': ' + propsA[i].displayName + ' changed from '
              + propsA[i].displayValue + ' to ' + propsB[i].displayValue);
            modified[extId] = dbId;

        if (typeof modified[extId] != 'undefined') {
          console.log('Modified dbId: ' + dbId);
          // color on both models
          //viewer.impl.visibilityManager.show(dbId, modelA);
          //viewer.impl.visibilityManager.show(dbId, modelB);
          viewer.setThemingColor(dbId, orange, modelA);
          viewer.setThemingColor(dbId, orange, modelB);

  function listElements(callback) {
    getAllLeafComponents(modelA, function (modelAdbIds) {
      getAllLeafComponents(modelB, function (modelBdbIds) {
        // this count will help wait until getProperties end all callbacks
        var count = modelAdbIds.length + modelBdbIds.length;

        var modelAExtIds = {};
        modelAdbIds.forEach(function (modelAdbId) {
          modelA.getProperties(modelAdbId, function (modelAProperty) {
            modelAExtIds[modelAProperty.externalId] = {'dbId': modelAdbId, 'properties': modelAProperty.properties};
            if (count == 0) callback(modelAExtIds, modelBExtIds);

        var modelBExtIds = {};
        modelBdbIds.forEach(function (modelBdbId) {
          modelB.getProperties(modelBdbId, function (modelBProperty) {
            modelBExtIds[modelBProperty.externalId] = {'dbId': modelBdbId, 'properties': modelBProperty.properties};
            if (count == 0) callback(modelAExtIds, modelBExtIds);

  function getAllLeafComponents(model, callback) {
    var components = [];

    function getLeafComponentsRec(tree, parentId) {
      if (tree.getChildCount(parentId) > 0) {
        tree.enumNodeChildren(parentId, function (childId) {
          getLeafComponentsRec(tree, childId);
      return components;

    var instanceTree = model.getInstanceTree();
    var allLeafComponents = getLeafComponentsRec(instanceTree, instanceTree.nodeAccess.rootId);

  return true;

VersionChanges.prototype.unload = function () {
  return true;

Autodesk.Viewing.theExtensionManager.registerExtension('Autodesk.Forge.Samples.VersionChanges', VersionChanges);


Related Article

Posted By

Augusto Goncalves

Developer Advocate at Autodesk since 2008, working with both desktop and web/cloud apps using top technologies, like C#, JavaScript, NodeJS and any other that can solve problems and improve workflows. See my samples on Github and follow me on Twitter for updates.