Angular Forge Viewer component

Overview

My blog post on how to get the Autodesk Forge Viewer working in Angular is still quite popular and I receive the odd message form time to time with questions/issues. Wind on a year and there still don’t seem to be any more examples.

So I created an Angular component library to wrap up the Forge Viewer and have published it as an NPM package – it’s available on NPM and the source code is all open-source and available on GitHub.

A couple of things to note:

  • The component library targets Angular 5 – it doesn’t work in anything less, and doesn’t work with Angular 6 yet.
  • The library includes TypeScript typings for most of the Viewer API to make the component nice to work with in conjunction with the Forge Viewer API documentation.
  • The library targets v4.* of the Forge Viewer. Required JavaScript and CSS files are downloaded from Autodesk servers by the component – so nothing needs to be added to the index.html. This also allows your app to lazy load the Forge Viewer resources.
  • The library contains a BasicExtension that is registered with all Viewer Components. The extensions captures basic events – such as item elected, object tree loaded etc. More on this below.

Availability

The initial release is immature and has not be fully exercised on a live project. I have tested it with a blank angular cli project and have a example code on the excellent StackBlitz – https://stackblitz.com/edit/angular-forge-viewer.

How to use

Follow these steps to get the viewer working in your app.

Step 1

Add the ng2-adsk-forge-viewer NPM package to your app – npm install ng2-adsk-forge-viewer --save or yarn add ng2-adsk-forge-viewer

Step 2

Add a element to your component.html:

<adsk-forge-viewer [viewerOptions]="viewerOptions3d"
  (onViewerScriptsLoaded)="setViewerOptions()"
  (onViewingApplicationInitialized)="loadDocument($event)">
</adsk-forge-viewer>

Step 3

There is a specific flow of logic to initialize the viewer:

  1. The viewer is constructed and loads scripts/resources from Autodesk’s servers
  2. The onViewerScriptsLoaded event emits to indicate all viewer resources have been loaded
  3. viewerOptions input can now be set, which triggers the creation of the ViewingApplication (i.e. Autodesk.Viewing.Initializer is called)
    • A helper method getDefaultViewerOptions can be used to get the most basic viewer options
  4. The onViewingApplicationInitialized event is emitted and you can now load a document. The event arguments contain a reference to the viewer which can be used to set the documentId to load. E.g.:
public loadDocument(event: ViewingApplicationInitializedEvent) {
  event.viewerComponent.DocumentId = 'DOCUMENT_URN_GOES_HERE';
}

Step 4

When the document has been loaded the onDocumentChanged event is emitted. This event can be used to define the view to display (by default, the viewer will load the first 3D viewable it can find). An example of displaying a 2D viewable:

component.html:

<adsk-forge-viewer [viewerOptions]="viewerOptions2d"
  (onViewerScriptsLoaded)="setViewerOptions()"
  (onViewingApplicationInitialized)="loadDocument($event)"
  (onDocumentChanged)="documentChanged($event)">
</adsk-forge-viewer>

component.ts:

public documentChanged(event: DocumentChangedEvent) {
  const viewerApp = event.viewingApplication;
  if (!viewerApp.bubble) return;

  // Use viewerApp.bubble to get a list of 2D obecjts
  const viewables = viewerApp.bubble.search({ type: 'geometry', role: '2d' });
  if (viewables && viewables.length > 0) {
    event.viewerComponent.selectItem(viewables[0].data);
  }
}

Extensions

BasicExtension

The viewer component comes with a BasicExtension that it registers against all of its viewers. The basic extension captures a handful of events including:

  • Autodesk.Viewing.FIT_TO_VIEW_EVENT,
  • Autodesk.Viewing.FULLSCREEN_MODE_EVENT,
  • Autodesk.Viewing.GEOMETRY_LOADED_EVENT,
  • Autodesk.Viewing.HIDE_EVENT,
  • Autodesk.Viewing.ISOLATE_EVENT,
  • Autodesk.Viewing.OBJECT_TREE_CREATED_EVENT,
  • Autodesk.Viewing.OBJECT_TREE_UNAVAILABLE_EVENT,
  • Autodesk.Viewing.RESET_EVENT,
  • Autodesk.Viewing.SELECTION_CHANGED_EVENT,
  • Autodesk.Viewing.SHOW_EVENT,

The viewer emits these events and should support most use cases. It’s possible to obtain a reference to the BasicExtension via the viewer’s basicExtension getter. This would allow you to subscript to the extensions events (exposed as an RxJS Observable).

Creating your own extension

The BasicExtension is derived from an abstract Extension that wraps up all the logic to register and unregister extensions with the Forge Viewer. It also contains logic to cast Forge Viewer event arguments to strongly typed TypeScript classes.

Your extension should derive from Extension and have a few basic properties and methods.

export class MyExtension extends Extension {
  // Extension must have a name
  public static extensionName: string = 'MyExtension';

  public load() {
    // Called when Forge Viewer loads your extension
  }

  public unload() {
    // Called when Forge Viewer unloads your extension
  }
}

Most of the methods in the abstract Extension class are protected. So they can be overriden in derived classes if required. For example, the BasicExtension overrides the registerExtension method to take a callback to let the viewer component know when the Extension has been registered.

More to come

This is a bit of an intro blog post – I’d suggest checking out my StackBlitz project for a working example. I’m sure more examples will follow in future posts – and remember this component is open source so feel free to fork and/or submit pull requests.

StackBlitz

Advertisements

Testing Angular components

Introduction

On a new project at work we have been using Angular as the front-end framework. One challenge we’ve had to work through is how to most cost effectively test our components and services. By cost effective, I mean in terms of time required to write useful tests.

At the beginning of the project we were following the Angular tutorials for component testing and the team embarked down the path of the TestBed/Component fixture tests. These are the types of test where the component is setup, it’s dependancies mocked out. Some action performed and output asserted. The problem we found, however, is many of these test end up interacting with the DOM (e.g. selecting elements by id or css class) to replicate user actions – such as clicking a button – or ensure some text is correct on the page. E.g.

it('should display original title', () => {
  fixture.detectChanges();
  // query for the title by CSS element selector
  de = fixture.debugElement.query(By.css('h1'));
  el = de.nativeElement;
  expect(el.textContent).toContain(comp.title);
});

This is a simple example, but we quickly found that following this kind of pattern results in extremely expensive “integration” style tests that are time consuming to write and brittle when things change.

During one sprint we were finding for every hour of development, we were spending 2-3 hours on tests and when tests broke it was mainly due to dependency changes or framework updates after upgrading npm packages – rather than the tests finding a problem. We weren’t happy with this so we decided to have a developer catchup to discuss changes to our testing strategy. We agreed to:

  • Favour more traditional Unit tests over the component test we were doing
  • Find libraries to allow us to easily mock out services and component dependancies
  • Favour Jasmine spies that just check back-end methods/services are called over mocking their methods out.

Unit tests over component tests

There is no one size fits all solution here, but the general rule of thumb is that if the logic can be wrapped up in a function and tested via a unit test do that first. This can be achieved by pushing business logic down to business logic/helper classes.

Services are a good candidate for unit testing, they are classes that can be instantiated so don’t need the TestBed.

describe('Service tests', () => {
  let service: MyService;

  beforeEach(() =&amp;gt; {
    service = new MyService();
  });

  it('should be created', () => {
    expect(service).toBeTruthy();
  });

  it('Adds item to list', () => {
    const testData = {
      id: '1234',
      title: 'Item title',
    } as TestItem;

    // Call public method
    service.addItem(testData);

    expect(service.items).toEqual([testData]);
  });
});

Before each test we create a new service. Granted that the service in this example is simple with no dependencies (we’ll come back to testing services without dependencies that).

We test one of the public methods like any unit test. Notice that we don’t use TestBed – so in our tests we don’t need to use the Inject function to allow the test to use the service so tests are more readable and less brittle if/when dependencies change or Angular update their framework.

Where a service has dependencies, these can be mocked via the constructor. We found that libraries like ts-mockito can help mock out these dependancies by providing a simple no-ops mock. ts-mockito can also mock specific calls on dependancies – replacing Jasmine spies.

import { mock, instance, when, anything, verify } from 'ts-mockito';

describe('Service tests', () => {
  let service: MyService;
  let mockOtherService: MyOtherService;

  beforeEach(() => {
    const mockOtherService = mock(MyOtherServie);
    when(mockOtherService.doWork(anything())).thenReturn(anything());

    service = new MyService(instance(mockOtherService));
  });

  it('Adds item to list', () => {
    const testData = {
      id: '1234',
      title: 'Item title',
    } as TestItem;

    // Call public method
    service.addItem(testData);

    // Should have called dependancy
    verify(mockOtherService.doWork(anything())).called();
  });
});

This example mocks out dependencies using ts-mockito. It creates a mock, overrides doWork. An instance is then passed when constructing the service under test.

ts-mockito can also replace Jasmine spies – notice the use of verify to check whether a method on a dependency has been called correctly.

Components must be constructed via the TestBed, so there will always be an element of configuration the required dependancies. We found Components to be by far the trickiest and brittle to test. So, as said before, if business logic can be hived off to helper classes or services so that more traditional unit testing techniques can be used, this is the easiest solution. If not, try to break components up to limit the number of dependant services and components. A good strategy is to make the parent component responsible for fetching data from a back-end service, and then have child components that have simple inputs and outputs of the data the will show (commonly referred to as smart and dumb components).

Libraries that helped us

ts-mockito

We found this library to be very good for mocking out dependencies. The library is relatively young and so is not without limitations. We found that it mocks out methods, getters and setters on objects well. We had mixed results mocking out properties/fields and static methods – but the developers are continuing to add improvements.

ng2-mock-component

This is a great little library for mocking out angular components – allowing child components to be mocked out with optional template, inputs and outputs.

For a simple component like this:

<h1>Test</h1>
<child-component [exampleinput]="inputValue" (onEvent)="eventHandler()"</child-component>;

We can mock out it’s child component easily in the TestBed:

import { ComponentFixture, TestBed } from '@angular/core/testing';

describe('Component tests', () => {
  let component: MyComponent;
  let fixture: ComponentFixture<MyComponent>;

  beforeEach(async(() => {
    return TestBed.configureTestingModule({
      declarations: [
        MyComponent,
        MockComponent({
          selector: 'child-component',
          inputs: ['exampleinput'],
          outputs: ['onEvent'],
        }),
      ],
    })
    .compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(MyComponent);
    component = fixture.componentInstance;
    component.contentSetId = mockContentSetId;
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });
});

This is much more convenient way of creating mock child components.

Other hints

Testing private methods

This always leads to debates about whether private methods should be tested directly by unit test – or implicitly through public methods whom call upon them. I sit in the camp of test wherever there is value, and sometimes this means we want private methods to have their own unit tests.

One thing to realise is that TypeScript’s private methods aren’t actually private – it’s the compiler that enforces the private keyword NOT JavaScript. So it’s possible to call a private method, we found that the following was the best for testing privates as it still gave some type safety on parameters passed to the function:

describe('private test', () => {
  it('test private method', () => {
    const test = new TestClass();

    const expected = true;
    // Call private method
    const actual = test['privateMethod'](parameter1Value);

    expect(expected).toBe(actual);
  });
});

TypeScript will show compile errors if parameter1Value is the wrong type or is missing. An alternative to this is to (test as any).privateMethod(parameter1Value); but this gives no type safety.

angular async

Angular ships with a number of helper methods for testing – one is called async and helps with asynchronous tests. From what I can gather from the docs and the source, it runs tests in a zone and wraps up a call to Jasmine’s done() function.

The problem, is that because it’s called async it becomes confusing when used with JavaScript’s new async/await. I’m a big fan of async/await– I am comfortable with it as an async pattern from C#, but it also makes nested promise chains much more readable. We found that new developers could be caught out and use the wrong async

Angular async:

it('some test', async(() => {
  // Angular async
});

TypeScript async/await:

it('some test', async (done) => {
  const test = await someMethod();
  done();

My current preference is to avoid Angular’s async method entirely and favour async/await and call Jasmine’s done() callback explicitly. I’ve not yet encountered a situation where async/await and done() don’t work but Angular’s async does.

Getting a job in software development

I have been developing commercial software since graduating from University in 2003. I started NBS in 2005 and in the 12 years I’ve worked there I’ve progressed from Graduate Software Developer to NBS Labs manager. As part of this role, I help recruit graduate software developers on to our Graduate Software Developer Scheme. Each year we look to employ 2-3 graduates. The graduates we employ typically studied at one of our local universities – Newcastle, Northumbria, Sunderland or Teeside.

The cost of attending University has increased significantly since the introduction of tuition fees back in 1998. Students can pay around £9000 a year for their degree. I’m going to be a little controversial and say that what is really surprising to me is that, as a recruiter, I’ve not seen an increase in quality of candidate coming through. Instead, I am seeing graduates who have done a little bit of coding – Java, C# – in year 1 and nothing since.

A core part of our recruitment process is a technical test, which many applicants struggle with. So I thought I’d write a quick blog post to give a bit of an insight into what I look for when recruiting graduates.

Review of CVs

The first step in the recruitment process is to sift through CVs. It’s quite common for graduate CVs to look very similar, after all graduates are at the beginning of their careers and have very little commercial experience.

An important thing to realise when you write your CV is that all the students on your course could potentially be applying for the same job. In addition to this, similar number of students from other local Universities might also be applying. How can you make sure that your CV stands out so you make the short list?

  • You are applying for a software development position, make sure you cover your knowledge and experience of key technologies. Also cover group projects and final year projects – with an overview of what the project was, your role and what technologies were used.
  • If you’ve been on a placement as part of your degree you should have some excellent examples of work done whilst on placement and technologies used etc. In ay ways you already have something to make your CV stand out.
  • Demonstrate your abilities and interest in computing – include hyperlinks to personal websites (written using a full development stack and ideally backed by a data store) to showcase your work, blogs, GitHub repositories.

Practical test

From the CVs, we build a short list of candidates to invite in for the first stage of our interview process – a programming test. Our test is fairly simple and is looking for a demonstration of basic programmings skills such as:

  • Interpreting requirements
  • Coding using a good programming style
  • Reading user input
  • Breaking up a problem in to reusable functions
  • Demonstration of code reuse in a loop
  • Reading a file and analysing data within it
  • And ideally showing some initiative – like writing some Unit tests and handling exceptions

This sounds simple doesn’t it? But we see many graduates struggle with this test event though it probably less difficult than something you will have done in a programming seminar at University.

My advice is always a reminder that many software development job interviews will require the completion of a test. Java, .NET, Python, NodeJs are all free to download and IDE’s like the excellent JetBrains IntelliJ IDEA or Visual Studio offer community editions. There are also loads of coding vlogs on YouTube. Practise, practise, practise the basics before you start applying for jobs.

Interview

The tests are code-reviewed by a mix of senior developers and developers to get feedback. If the candidate has written a good solution they are invited back for a formal interview as the final stage of the interview.

You can find lots of really good advice on the Internet about how to interview well – but my advice is try not to panic and remember that the interview is NOT a test to catch you out. It’s a conversation between you and your potential new employer to discuss your knowledge, skills and passion for a career in software development and for you to decide if the company is the right fit for you.

Turtle Minesweeper

I’ve mentioned a few times on various blog posts that I got my first Mac back in 1999. It was a iMac G3 266Mhz running MacOS 8.6. I loved that computer, it was bullet proof compared to previous Windows PCs I’d owned. One thing I missed when moving to the Mac however was Windows Minesweeper. I played the game quite a lot and couldn’t find a clone that did the Windows version justice.

During my time at College I wrote a number of applications in Visual Basic 6 and was quite comfortable with how it worked. An equivalent development on the Mac was REALbasic (which has recently been taken over by Xojo) which I started using in my spare time whilst studying at University. In the summer of 2001 I set about creating a Minesweeper clone for the Mac.

I recently bought a new MacBook Pro 2016 model and was curious to see if the app still worked in macOS Sierra (it was originally written for Classic Mac OS (8.6-> 9.x) and MacOS 10.1 -> 10.5. I stopped supporting it back in 2006 so wasn’t really expecting much.

Analysis and design

Whilst restoring files to the new MacBook Pro, I found the TurtleMine folder and found all the analysis and design documentation I’d written. At University, a few of my modules were about systems analysis and design using UML. I’d tried to apply what I’d learnt to the Minesweeper app.

I’d written a few use cases:

Screen Shot 2017-04-07 at 11.13.21 am

Example use case for uncovering a square

Created some wireframes:

GUI Designs

Wireframes

Sequence diagrams:

ISD - Uncover a square

Sequence diagram for uncovering a square on the minefield

And state charts:

Statechart - Square

State chart for a square on the minefield

All in MS Excel! I must have been a glutton for punishment. Nowadays, I like to use tools like Pencil for wireframes, and Visual Paradigm for UML diagrams.

Running the game

I opened up the latest release of the source code I could find and tried to double click the Turtle Mine application icon:

Screen Shot 2017-04-07 at 12.59.58 pm

And to my total surprise, the app ran!

Download

I don’t have my Turtle Soft website any more, but thought macOS people might still like to be able to download Turtle Mine. If you’d like a copy, you can download Turtle Mine from the DropBox link below:

https://www.dropbox.com/s/p1k87xe02b7an5o/TurtleMine.zip?dl=0

Autodesk Forge Viewer and Angular2

I’ve been using the Autodesk Forge viewer quite a bit lately to integrate 3D building models within various prototype applications. Until now I had only used the Forge Viewer with plain JavaScript (or a bit of JQuery). I recently tried to integrate the viewer within an Angular 2 application and thought I’d share my solution – as I was unable to find any examples when I did a quick google.

Angular2 (just called Angular) is a rewrite of AngularJS framework. A key difference is that Angular2 moves away from the MVC pattern in favour of Components and the shadow DOM. Although not a requirements, Angular2 recommends the use of TypeScript to help more strongly type JavaScript with a view to help maintainability of large applications. Angular is just JavaScript, so it’s not difficult to integrate external JavaScript libraries with it – you just have to follow particular conventions to get these libraries to work. The solution to integrating the Forge Viewer is very similar to some of the React samples on GitHub.

Step 1

After creating a new Angular app via angular-cli, add the required JS includes to index.html:

<script src="https://developer.api.autodesk.com/viewingservice/v1/viewers/three.min.js?v=v2.13"></script>
<script src="https://developer.api.autodesk.com/viewingservice/v1/viewers/viewer3D.min.js?v=v2.13"></script>

Note that I’m going to use the headless Forge Viewer in this example – so I don’t need to include the Forge Viewer’s CSS.

Step 2

Create a new component using angular-cli:

ng generate component forge-viewer

Add the following to forge-viewer.component.html:

<div #viewerContainer class="viewer">
</div>

This provides a Div for the Forge Viewer to render in to. We need to add a #viewerContainer reference within theDiv so that we can obtain an ElementRef to give the Forge Viewer the DOM element to bind to. Add the following CSS to forge-viewer.component.css:

.viewer {
  position: relative;
  width: 100%;
  height: 450px;
}

Step 3

We’ve done the basic setup, we now need to add the main functionality to forge-viewer.component.ts.

import { Component, ViewChild, OnInit, OnDestroy, ElementRef } from '@angular/core';

// We need to tell TypeScript that Autodesk exists as a variables/object somewhere globally
declare const Autodesk: any;

@Component({
  selector: 'forge-viewer',
  templateUrl: './forge-viewer.component.html',
  styleUrls: ['./forge-viewer.component.scss'],
})
export class ForgeViewerComponent implements OnInit, OnDestroy{
  @ViewChild('viewerContainer') viewerContainer: any;
  private viewer: any;

  constructor(private elementRef: ElementRef) { }

...

There are a couple of lines above that are crucially important. We’ve imported the Autodesk Viewer from Autodesk’s servers – this creates a global Autodesk object. We don’t have any TypeScript typings for this object (ts.d files). At time of writing, there were no definitions on the DefinatelyTyped repository. TypeScript is just a superset of JavaScript, so it’s not a problem that we don’t have a typings file. All we need to do is declare an Autodesk variable:

declare const Autodesk: any;

This tells the TypeScript compiler that somewhere globally there is an object called Autodesk.

Also important is a reference to the Div we want to render the viewer in:

@ViewChild('viewerContainer') viewerContainer: any;

Step 4

We’ll now create an instance of the Forge Viewer – we’ll need to do this once the component has been initialised AND our hosting Div has been rendered in the DOM. We’ll use the ngAfterViewInit lifecycle hook:

ngAfterViewInit() {
  this.launchViewer();
}

private getAccessToken(onSuccess: any) {
  const { access_token, expires_in } = // Your code to get a token
  onSuccess(access_token, expires_in);
}

private launchViewer() {
  if (this.viewer) {
    // Viewer has already been initialised
    return;
  }

  const options = {
    env: 'AutodeskProduction',
    getAccessToken: (onSuccess) => { this.getAccessToken(onSuccess) },
  };

  // For a headless viewer
  this.viewer = new Autodesk.Viewing.Viewer3D(this.viewerContainer.nativeElement, {});
  // For a viewer with UI
  // this.viewer = new Autodesk.Viewing.Private.GuiViewer3D(this.viewerContainer.nativeElement, {});

  Autodesk.Viewing.Initializer(options, () => {
    // Initialise the viewer and load a document
    this.viewer.initialize();
    this.loadDocument();
  });
}

private loadDocument() {
  const urn = `urn:${//document urn}`;

  Autodesk.Viewing.Document.load(urn, (doc) => {
    // Get views that can be displayed in the viewer
    const geometryItems = Autodesk.Viewing.Document.getSubItemsWithProperties(doc.getRootItem(), {type: 'geometry'}, true);

    if (geometryItems.length === 0) {
      return;
    }

    // Example of adding event listeners
    this.viewer.addEventListener(Autodesk.Viewing.GEOMETRY_LOADED_EVENT, this.geometryLoaded);
    this.viewer.addEventListener(Autodesk.Viewing.SELECTION_CHANGED_EVENT, (event) => this.selectionChanged(event));

    // Load view in to the viewer
    this.viewer.load(doc.getViewablePath(geometryItems[0]));
  }, errorMsg => console.error);
}

private geometryLoaded(event: any) {
  const viewer = event.target;

  viewer.removeEventListener(Autodesk.Viewing.GEOMETRY_LOADED_EVENT, this.geometryLoaded);

  // Example - set light preset and fit model to view
  viewer.setLightPreset(8);
  viewer.fitToView();
}

private selectionChanged(event: any) {
  const model = event.model;
  const dbIds = event.dbIdArray;

  // Get properties of object
  this.viewer.getProperties(dbIds[0], (props) => {
    // Do something with properties.
  });
}

ngOnDestroy() {
  // Clean up the viewer when the component is destroyed
  if (this.viewer && this.viewer.running) {
    this.viewer.removeEventListener(Autodesk.Viewing.SELECTION_CHANGED_EVENT, this.selectionChanged);
    this.viewer.tearDown();
    this.viewer.finish();
    this.viewer = null;
  }
}

A lot of the code is very similar to how you’d instantiate the viewer via plain JavaScript. The following line creates a new instance of the viewer in the Div of our component template:

this.viewer = new Autodesk.Viewing.Viewer3D(this.viewerContainer.nativeElement, {});

The reset of the code just loads a document and demonstrates how events can be bound.

Gotchas

Whilst working on this prototype, I encountered one gotcha. I could successfully create an instance of the Viewer and load a model in to it. My application had simple routing – when I navigated away from the route where the viewer was hosts, to another route and then back, the viewer wouldn’t display. It seemed that viewer thought it has already been instantiated so didn’t bother and skipped to loading the model…which didn’t work because there was no instance of the viewer.

My solution to the problem isn’t as elegant as I wanted, but does work:

this.viewer = new Autodesk.Viewing.Viewer3D(this.viewerContainer.nativeElement, {}); // Headless viewer

// Check if the viewer has already been initialised - this isn't the nicest, but we've set the env in our
// options above so we at least know that it was us who did this!
if (!Autodesk.Viewing.Private.env) {
  Autodesk.Viewing.Initializer(options, () => {
    this.viewer.initialize();
      this.loadDocument();
  });
} else {
  // We need to give an initialised viewing application a tick to allow the DOM element to be established before we re-draw
  setTimeout(() => {
    this.viewer.initialize();
    this.loadDocument();
  });
}

The 2nd time out component loads, Autodesk.Viewing.Private.env will already be set (we set it!). So we simply call initialise on the viewer and load the model. This didn’t work first time – but adding a setTimeout gave Angular a tick to sort out DOM binding/it’s update cycle before attempting to load the viewer.

Screenshots

The full forge-viewer.component.ts file

import { Component, ViewChild, OnInit, OnDestroy, ElementRef, Input } from '@angular/core';

// We need to tell TypeScript that Autodesk exists as a variables/object somewhere globally
declare const Autodesk: any;

@Component({
  selector: 'forge-viewer',
  templateUrl: './forge-viewer.component.html',
  styleUrls: ['./forge-viewer.component.scss'],
})
export class ForgeViewerComponent implements OnInit, OnDestroy {
  private selectedSection: any = null;
  @ViewChild('viewerContainer') viewerContainer: any;
  private viewer: any;

  constructor(private elementRef: ElementRef) { }

  ngOnInit() {
  }

  ngAfterViewInit() { 
    this.launchViewer();
  }

  ngOnDestroy() {
    if (this.viewer && this.viewer.running) {
      this.viewer.removeEventListener(Autodesk.Viewing.SELECTION_CHANGED_EVENT, this.selectionChanged);
      this.viewer.tearDown();
      this.viewer.finish();
      this.viewer = null;
    }
  }

  private launchViewer() {
    if (this.viewer) {
      return;
    }

    const options = {
      env: 'AutodeskProduction',
      getAccessToken: (onSuccess) => { this.getAccessToken(onSuccess) },
    };

    this.viewer = new Autodesk.Viewing.Viewer3D(this.viewerContainer.nativeElement, {}); // Headless viewer
 
    // Check if the viewer has already been initialised - this isn't the nicest, but we've set the env in our
    // options above so we at least know that it was us who did this!
    if (!Autodesk.Viewing.Private.env) {
      Autodesk.Viewing.Initializer(options, () => {
        this.viewer.initialize();
        this.loadDocument();
      });
    } else {
      // We need to give an initialised viewing application a tick to allow the DOM element to be established before we re-draw
      setTimeout(() => {
        this.viewer.initialize();
        this.loadDocument();
      });
    }
  }

  private loadDocument() {
    const urn = `urn:${// model urn}`;

    Autodesk.Viewing.Document.load(urn, (doc) => {
      const geometryItems = Autodesk.Viewing.Document.getSubItemsWithProperties(doc.getRootItem(), {type: 'geometry'}, true);

      if (geometryItems.length === 0) {
        return;
      }

      this.viewer.addEventListener(Autodesk.Viewing.GEOMETRY_LOADED_EVENT, this.geometryLoaded);
      this.viewer.addEventListener(Autodesk.Viewing.SELECTION_CHANGED_EVENT, (event) => this.selectionChanged(event));

      this.viewer.load(doc.getViewablePath(geometryItems[0]));
    }, errorMsg => console.error);
  }

  private geometryLoaded(event: any) {
    const viewer = event.target;

    viewer.removeEventListener(Autodesk.Viewing.GEOMETRY_LOADED_EVENT, this.geometryLoaded);
    viewer.setLightPreset(8);
    viewer.fitToView();
    // viewer.setQualityLevel(false, true); // Getting rid of Ambientshadows to false to avoid blackscreen problem in Viewer.
  }

  private selectionChanged(event: any) {
    const model = event.model;
    const dbIds = event.dbIdArray;

    // Get properties of object
    this.viewer.getProperties(dbIds[0], (props) => {
       // Do something with properties
    });
  }

  private getAccessToken(onSuccess: any) {
    const { access_token, expires_in } = // get token
    onSuccess(access_token, expires_in);
  }
}

Sequelize

It’s a new year and we’ve started some new projects at work. Over the next few months I’m working on a project to push our specification products on using newer technologies. Traditionally, I’ve worked mostly with a Microsoft Stack – SQL Server and .NET (EntityFramework, WinForms or  WebAPI and ASP.NET). However, a hobby of mine (and part of my role at work) is to keep tabs on latest technologies. I’ve been following the various emerging JavaScript frameworks closely over the last few years – EmberJS, Angular, VueJS, NodeJS, Express (I’ve not looked at ReactJS yet but mean to). One thing I tell everyone who will listen is to bookmark the ThoughWorks technology radar.

For the new project, I want to use JavaScript only – Angular2 on the front end and, NodeJS/Express on the back-end. The main motivation is one of cost and scalability – JavaScript runs on pretty much anything, the ecosystem is full of open source solutions and the stack is now fairly mature (with successful production usage of many of the frameworks). I considered .NET Core but from a previous prototype, the toolset isn’t mature enough yet (maybe it will be when the next version of VisualStudio is released). I also have to admit, I found the whole .NET Core experience quite frustrating during that prototype with tools being marked as RC1 (DNC, DNX etc) only to be totally re-written in RC2 (dotnet cli). Good reason, but changes were so fundamental and should have gone back to a beta/preview status.

The first area I started looking at was the backend data model, API and database. It was during reviewing GraphQL that I happened upon an excellent video by Lee Benson where he showed implementing a GraphQL API backed by a database that used Sequelize as the data access component. As mentioned, I’m used to EntityFramework so I’m familiar with ORMs – I’ve just never used an ORM written in JavaScript!

This blog post will cover a very simple example of creating a NodeJS app and Sequalize model that backs a Postgres database.

Step1

Our first step is to create a new node app and add the necessary dependancies.

$ npm init
$ npm install sequelize -save

# Package for Postgres support
$ npm install pg -save

Step2

We’re going to create a very simple model to store Uniclass 2015 in a database. We will model this as 2 tables:

erd

Simple Entity-Relationship-Diagram

The classification table will store the name of the classification; the classificationItems table will store all of the entries in Uniclass 2015. ClassificationItems will be a self-referencing table so that we can model Uniclass 2015 as a tree.

Step3

We’re going to use Atom, a fantastic text editor, to write our JavaScript. First, we need to create a new .js file to add our database model to. We’ll call this new file “db.js”.

First off, we need to import the sequelize library and create our database connection

const Sequelize = require('sequelize');

const Conn = new Sequelize(
 'classification',
 'postgres',
 'postgres',
 {
 dialect: 'postgres',
 host: 'localhost',
 }
);

Sequelize supports a number of different databases – MySQL, MariaDb, SQlite, Postgres and MS SQL Server. In this example, we’re using the Postgres provider.

Next we define our two models:

const Classification = Conn.define('classification', {
  title: {
    type: Sequelize.STRING,
    allowNull: false,
    comment: 'Classification system name'
  },
  publisher: {
    type: Sequelize.STRING,
    allowNull: true,
    description: 'The author of the classification system'
  },
});

const ClassificationItem = Conn.define('classificationItem', {
  notation: {
    type: Sequelize.STRING,
    allowNull: false,
    comment: 'Notation of the Classification'
  },
  title: {
    type: Sequelize.STRING,
    allowNull: false,
    comment: 'Title of the Classification Item'
   }
});

We use the connection to define each table. We then define the fields within that table (in or example we allow Sequalize to generate an id field and manage the primary keys).

As you’d expect, Sequelize supports a number of field data types – strings, blobs, numbers etc. In our simple example, we’ll just use strings.

Each of our fields requires a value – so we use the allowNull property to enforce that values are required. Sequelize has a wealth of other validators to check whether fields are email addresses, credit card numbers etc.

Once we have our models, we have to define the relationships between them so that Sequelize can manage our many-to-one relationships.

Classification.hasMany(ClassificationItem);
ClassificationItem.belongsTo(Classification);
ClassificationItem.hasMany(ClassificationItem, { foreignKey: 'parentId' });
ClassificationItem.belongsTo(ClassificationItem, {as: 'parent'});

We use the hasMany relationship to tell Sequelize that both Classification and ClassificationItem have many children. Sequelize automatically adds a foreign key to the child relationship and provides convenience methods to add models to the child relationship.

The belongsTo relationship allows child models to get their parent object. This provides us with a convenience method to get our parent object if we need it in our application. Sequelize allows us to control the name of the foreign key. As mentioned above, ClassificationItem is a self-referencing table to help us model the classification system as a tree. Rather than ‘classificationItemId’ being the foreign key to the parent item, I’d prefer parentId to be used instead. This would give us a getParent() method too which reads better. We achieve this by specifying the foreignKey on one side of the relationship and { as: ‘parent’ } against the other side.

Step4

Next we get Sequelize to create the database tables and were write a bit of code to seed the database with some test data:

Conn.sync({force: true}).then(() => {
    return Classification.create({
    title: 'Uniclass 2015',
    publisher: 'NBS'
  });
 }).then((classification) => {
   return classification.createClassificationItem({
     notation: 'Ss',
     title: 'Systems'
   }).then((classificationItem) => {
     return classificationItem.createClassificationItem({
       notation: 'Ss_15',
       title: 'Earthworks systems',
       classificationId: classification.id,
       //parentId: classificationItem.id
     })
   }).then((classificationItem) => {
     return classificationItem.createClassificationItem({
       notation: 'Ss_15_10',
       title: 'Groundworks and earthworks systems',
       classificationId: classification.id
     });
   }).then((classificationItem) => {
     return classificationItem.createClassificationItem({
       notation: 'Ss_15_10_30',
       title: 'Excavating and filling systems',
       classificationId: classification.id
     });
   }).then((classificationItem) => {
     classificationItem.createClassificationItem({
       notation: 'Ss_15_10_30_25',
       title: 'Earthworks excavating systems',
       classificationId: classification.id
     });

     classificationItem.createClassificationItem({
       notation: 'Ss_15_10_30_27',
       title: 'Earthworks filling systems',
       classificationId: classification.id
     });
   });
 });

The sync command creates the database tables – by specifying { force: true }, Sequelize will drop any existing tables and re-create them. This is ideal for development environments but obviously NOT production!

The rest of the code creates a classification object and several classification items. Notice that I use the createClassificationItem method so that parent id’s are set automatically when inserting child records.

The resulting database looks like this:

Step 5

Now we have a model and some data, we can perform a few queries.

1. Get root level classification items:

Classification.findOne({
  where: {
   title: 'Uniclass 2015'
  }
}).then((result) => {
  return result.getClassificationItems({
    where: {
      parentId: null
    }
  })
}).then((result) => {
  result.map((item) => {
    const {notation, title} = item;
    console.log(`${notation} ${title}`);
  });
});

Output:

Ss Systems

2. Get classification items (and their children) with a particular notation:

ClassificationItem.findAll({
  where: {
    notation: {
      $like: 'Ss_15_10_30%'
    }
  }
}).then((results) => {
  results.map((item) => {
    const {notation, title} = item;
    console.log(`${notation} ${title}`);
  })
});

Output

Ss_15_10_30 Excavating and filling systems
Ss_15_10_30_25 Earthworks excavating systems
Ss_15_10_30_27 Earthworks filling systems

3. Get a classification items’s parent:

ClassificationItem.findOne({
  where: {
   id: 6
  }
}).then((result) => {
  const {notation, title} = result;
  console.log(`Child: ${notation} ${title}`);
  return result.getParent();
}).then((parent) => {
  const {notation, title} = parent;
  console.log(`Parent: ${notation} ${title}`);
});

Output

Ss_15_10_30_27 Earthworks filling systems
Ss_15_10_30 Excavating and filling systems

That was a quick whistle stop tour of some of the basic features of Sequelize. At the moment I’m really impressed with it. The only thing that takes a bit of getting used to is working with all the promises. Promises are really powerful, but you need to think about the structure of your code to prevent lots of nested then’s.

More Autodesk Forge

Back in August, I blogged about attending the Autodesk Forge DevCon is San Francisco. This month I’m again extremely fortunate and am attending Autodesk University in Las Vegas with work.

Since my previous blog, I’ve been busy on a proof of concept that marries our NBS Create specification product and the Autodesk Forge Viewer. There will be more to follow in the coming months, but for now I just wanted to capture a few features I implemented incase they are useful to anyone else.

1. Creating an extension that captures object selection

The application I’m prototyping needs to extract data from the model when an object is clicked. The Forge Viewer api documentation covers how to create and register an extension to get selection events etc. Adding functionality as an extension, is the recommended approach for adding custom functionality to the viewer.

The data my application needs from the viewer can only be obtained when the viewer has fully loaded the model’s geometry and object tree. So we have to be sure we subscribe to the appropriate events.

Create and register the extension

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

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

Autodesk.Viewing.theExtensionManager.registerExtension('NBSExtension', NBSExtension);

Subscribe and handle the events

My extension needs to handle the SELECTION_CHANGED_EVENT, GEOMETRY_LOADED_EVENT and OBJECT_TREE_CREATED_EVENT. The events are bound on the extensions “load” method.

NBSExtension.prototype.load = function () {
  console.log('NBSExtension is loaded!');

  this.onSelectionBinded = this.onSelectionEvent.bind(this);
  this.viewer.addEventListener(Autodesk.Viewing.SELECTION_CHANGED_EVENT, this.onSelectionBinded);

  this.onGeometryLoadedBinded = this.onGeometryLoadedEvent.bind(this);
  this.viewer.addEventListener(Autodesk.Viewing.GEOMETRY_LOADED_EVENT, this.onGeometryLoadedBinded);

  this.onObjectTreeCreatedBinded = this.onObjectTreeCreatedEvent.bind(this);
  this.viewer.addEventListener(Autodesk.Viewing.OBJECT_TREE_CREATED_EVENT, this.onObjectTreeCreatedBinded);

  return true;
};

A well behaved extension should also clean up after it’s unloaded.

NBSExtension.prototype.unload = function () {
  console.log('NBSExtension is now unloaded!');

  this.viewer.removeEventListener(Autodesk.Viewing.SELECTION_CHANGED_EVENT, this.onSelectionBinded);
  this.onSelectionBinded = null;

  this.viewer.removeEventListener(Autodesk.Viewing.GEOMETRY_LOADED_EVENT, this.onGeometryLoadedBinded);
  this.onGeometryLoadedBinded = null;

  this.viewer.removeEventListener(Autodesk.Viewing.OBJECT_TREE_CREATED_EVENT, this.onObjectTreeCreatedBinded);
  this.onObjectTreeCreatedBinded = null;

  return true;
};

When the events fire, the following functions are called to allow us to handle the event however we want:

// Event handler for Autodesk.Viewing.SELECTION_CHANGED_EVENT
NBSExtension.prototype.onSelectionEvent = function (event) {
  var currSelection = this.viewer.getSelection();

  // Do more work with current selection
}

// Event handler for Autodesk.Viewing.GEOMETRY_LOADED_EVENT
NBSExtension.prototype.onGeometryLoadedEvent = function (event) {
 
};

// Event handler for Autodesk.Viewing.OBJECT_TREE_CREATED_EVENT
NBSExtension.prototype.onObjectTreeCreatedEvent = function (event) {

};

2. Get object properties

Once we have the selected item, we can call getProperties on the viewer to get an array of all of the property key/value pairs for that object.

var currSelection = this.viewer.getSelection();

// Do more work with current selection
var dbId = currSelection[0];

this.viewer.getProperties(dbId, function (data) {
  // Find the property NBSReference 
  var nbsRef = _.find(data.properties, function (item) {
    return (item.displayName === 'NBSReference');
  });

  // If we have found NBSReference, get the display value
  if (nbsRef && nbsRef.displayValue) {
    console.log('NBS Reference found: ' + nbsRef.displayValue);
  }
}, function () {
  console.log('Error getting properties');
});

The call to this.viewer.getSelection() returns an array of dbId’s (Database ID’s). Each Id can be passed to the getProperties function to get the properties for that dbId. My extension then looks through the array of properties for an “NBSReference” property which can be used to display the associated specification for that object.

Notice that I use Underscore.js’s _.find() function to search the array of properties. I opted for this as I found IE11 didn’t support Javascript’s native Array.prototype.find(). I like the readability of the function and Underscore.js provides the necessary polyfill for IE11.

3. Getting area and volume information

Once the geometry is loaded from the model and the internal object tree create, it’s possible to query the properties in the model that relate to area and volume. For my prototype, I wanted to sum the area and volume of types of a objects the user has selected in the model.

In order to do this, I needed to:

  1. Get the dbId of the selection item
  2. Find that dbID in the object tree
  3. Move to the object’s parent and get all of it’s children (in other words, get the siblings of the selected item)
  4. Sum the area and volume properties of the children

The first step is to build our own representation of the model tree in memory (this must effectively be how the Forge viewer displays the model tree). My code is based on this blog post by Philippe Leefsma.

var viewer = viewerApp.getCurrentViewer();
var model = viewer.model;

if (!modelTree && model.getData().instanceTree) {
  modelTree = buildModelTree(viewer.model);
}

var buildModelTree = function (model) {
  // builds model tree recursively
  function _buildModelTreeRec(node) {
    instanceTree.enumNodeChildren(node.dbId, function (childId) {
      node.children = node.children || [];

      var childNode = {
        dbId: childId,
        name: instanceTree.getNodeName(childId)
      }

      node.children.push(childNode);
      _buildModelTreeRec(childNode);
    });
  }

  // get model instance tree and root component
  var instanceTree = model.getData().instanceTree;
  var rootId = instanceTree.getRootId();
  var rootNode = {
    dbId: rootId,
    name: instanceTree.getNodeName(rootId)
  }
 
  _buildModelTreeRec(rootNode);

  return rootNode;
};

This gives us a representation of the model tree. Once we’ve located all of the siblings, we can use the dbId of each sibling to get it’s area and volume properties.

The code I wrote was based on this sample, originally written be Jim Awe I have to admit, my code is a little bit messy. There are a lot of asynchronous operations going on, which use quite a few callbacks and you do end up close to a pyramid of doom. The code was good for my needs, but I think if I was doing anything more complicated I’d look in to using Promises to tidy the code up a bit.

function _getReportData(items, callback) {
  var results = { "areaSum": 0.0, "areaSumLabel": "", "areaProps": [], "volumeSum": 0.0, "volumeSumLabel": "", volumeProps: [], "instanceCount": 0, "friendlyNotationWithSuffix": friendlyNotationWithSuffix.trim() };

  var viewer = viewerApp.getCurrentViewer();
  var nodes = items;

  nodes.forEach(function (dbId, nodeIndex, nodeArray) {
    // Find node 
    var leafNodes = getLeafNodes(dbId, modelTree);
    if (!leafNodes) return;
    results.instanceCount += leafNodes.length;

    leafNodes.forEach(function (node, leafNodeIndex, leafNodeArray) {
      viewer.getProperties(node.dbId, function (propObj) {
        for (var i = 0; i < propObj.properties.length; ++i) {
          var prop = propObj.properties[i];
          var propValue;
          var propFormat;

          if (prop.displayName === "Area") {
            propValue = parseFloat(prop.displayValue);

            results.areaSum += propValue;
            results.areaSumLabel = Autodesk.Viewing.Private.formatValueWithUnits(results.areaSum.toFixed(2), prop.units, prop.type);

            propFormat = Autodesk.Viewing.Private.formatValueWithUnits(prop.displayValue, prop.units, prop.type);
            results.areaProps.push({ "dbId": dbId, "val": propValue, "label": propFormat, "units": prop.units });
          } else if (prop.displayName === "Volume") {
            propValue = parseFloat(prop.displayValue);

            results.volumeSum += propValue;
            results.volumeSumLabel = Autodesk.Viewing.Private.formatValueWithUnits(results.volumeSum.toFixed(2), prop.units, prop.type);

            propFormat = Autodesk.Viewing.Private.formatValueWithUnits(prop.displayValue, prop.units, prop.type);
            results.volumeProps.push({ "dbId": dbId, "val": propValue, "label": propFormat, "units": prop.units });
          }
        };

        // Callback when we've processed everything
        if (callback && nodeIndex === nodeArray.length - 1 && leafNodeIndex === leafNodeArray.length - 1) {
          callback(results);
        }
      });
    });
  });
}

var getLeafNodes = function (parentNodeDbId, parentNode) {
  var result = null;

  function _getLeafNodesRec(parentNodeDbId, node) {
    // Have we found the node we're looking for?
    if (node.dbId === parentNodeDbId) {
      // We return the children (or the node itself if there are no children)
      result = node.children || [node];
    } else {
      if (node.children) {
        node.children.forEach(function (childNode, index, array) {
          if (result) return;
          _getLeafNodesRec(parentNodeDbId, childNode);
        });
      }
    }
 }

 _getLeafNodesRec(parentNodeDbId, parentNode);
 return result;
};

A couple of things to call out from the above code – The function getLeafNodes is used to get the siblings of the selected item. And the Autodesk Forge viewer has a method to nicely format volumes and areas with the appropriate units:

Autodesk.Viewing.Private.formatValueWithUnits(prop.displayValue, prop.units, prop.type);

I couldn’t actually find this documented in the API though – it was only in the samples on GitHub. But it’s a nice way of getting a nicely formatted string of values with the appropriate units.

This has been another fairly lengthy blog post – so it deserves a few screenshots of the functionality that has been implemented:

And a big shout out to Kirsty Hudson for her awesome UX work!