# IFC-viewer for Angular
*Creator: Mads Holten Rasmussen*

The library includes my take on an IFC viewer for Angular based on IFC.js.

## Install to Angular project
The library uses [Angular Material Design](https://material.angular.io/), so please install this package beforehand.

`npm i --save ngx-ifc-viewer`
`npm i --save three web-ifc-three camera-controls three-fatline`
`npm i --save-dev @types/three`

Add to app.module.ts

```typescript
import { IfcViewerModule } from 'ngx-ifc-viewer';

@NgModule({
  imports: [
    ...,
    IfcViewerModule
  ]
})
```

## Use
See examples of use in the [LD-BIM viewer](https://github.com/MadsHolten/ld-bim).

## Install to non-Angular project
`npm i --save ngx-ifc-viewer`

Add to HTML body
```html
<ifc-viewer></ifc-viewer>
```
Copy the files `web-ifc.wasm` and `web-ifc-mt.wasm` from the folder `node_modules/web-ifc/` to a new folder `assets/ifcjs`.
Add the files `IFCWorker.js` and `IFCWorker.js.map` from `node_modules/web-ifc-three/`.

Add some global styling to buttons that will also affect the buttons in the IFC-viewer (`styles.scss`):
```scss
.btn {
    background-color: DodgerBlue;
    border: none;
    color: white;
    padding: 12px 16px;
    font-size: 16px;
    cursor: pointer;
}

/* Darker background on mouse-over */
.btn:hover {
    background-color: RoyalBlue;
}
```

## Use
See examples of use in the [LD-BIM viewer](https://github.com/MadsHolten/ld-bim).

### Viewer
The viewer inherits the size of its parent div, so the example below will give us a full screen viewer.

```html
<div style="width: 100%; height: 100%">
    <ifc-viewer></ifc-viewer>
</div>
```

The viewer per default has a button to upload a model, but it is also possible to do that from outside using the `IFCModelLoaderService`. This service takes a file and returns an observable that first shows the status of the loading process and once finished has the resulting model attached. This model can then be passed to the `IFCViewerService` or other services such as the [Linked Building Data (LBD) toolset](ngx-lbd
). The viewer service can at any time be used to access all the models that have been loaded.

```typescript
import { IFCModelLoaderService, IFCViewerService } from 'ngx-ifc-viewer';
import { IFCModel } from 'web-ifc-three/IFC/components/IFCModel';

@Component({
  selector: 'app-component',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {

  public models: IFCModel[] = [];

  constructor(
    private: _modelLoader: IFCModelLoaderService,
    private _viewer: IFCViewerService
  ){}

  uploadFile(files: FileList){

    this._modelLoader.loadModel(files[0]).subscribe(async (status: LoadingStatus) => {

        // Load finished
        if(status.result != undefined){

          const model = status.result;

          // Add to array of models
          this.models.push(model);

          // Append model to scene
          this._viewer.appendModel(model);

        }

      }, 
      err => console.log(err))

  }
}
```

#### Input
|Name      |Description                                                                        |
|:---------|:----------------------------------------------------------------------------------|
|settings  | An instance of the MenuPanelSettings class which holds all settings for the viewer|

#### Output
* elementClick
* canvasClick

### Menu panel
The content displayed in the menu panel can be limited by specifying so in the viewer settings.

The top menu panel can be extended by placing HTML content between the `<ifc-viewer>`-tags. To display this content in the top menu after a model is loaded in the scene, use the `menuPostLoadContent`-selector as demonstrated below.

```html
<div style="width: 100%; height: 100%">
    <ifc-viewer>

      <!-- MENU PANEL ADDITIONAL BUTTONS -->
      <ng-container menuPostLoadContent>

        <!-- My function -->
        <button class="btn" *ngIf="rdfParsed" (click)="myFunction()" matTooltip="Do something cool">
            <fa-icon [icon]="faUnicorn"></fa-icon>
        </button>

      </ng-container>

    </ifc-viewer>
</div>
```

In the above example we append a font-awesome icon with a [unicorn](https://fontawesome.com/search?q=unicord&s=solid%2Cbrands). This requires the icon to also be loaded from the `@fortawesome/free-solid-svg-icons` package:

```typescript
import { faUnicorn } from '@fortawesome/free-solid-svg-icons';

@Component({
  ...
})
export class AppComponent {
  public faUnicorn = faUnicorn;
  ...
}
```

### Context menu
The content displayed in the context menu on element click can be limited by specifying so in the viewer settings.

The context menu can also be extended with custom items by placing HTML content between the `<ifc-viewer>`-tags. To display this content in the top menu after a model is loaded in the scene, use the `contextMenu`-selector as demonstrated below.

This requires `MatMenuModule` and `MatButtonModule` to be imported in the app module.

```html
<div style="width: 100%; height: 100%">
    <ifc-viewer (elementClick)="lastClicekedElement = $event">

      <!-- CONTEXT MENU ADDITIONAL BUTTONS -->
      <ng-container contextMenu>
        <button mat-menu-item (click)="colorElement()">Color element</button>
      </ng-container>

    </ifc-viewer>
</div>
```

### Coloring
In the context menu example we define a "color element" function. Implementing this functionality is quite simple using the `ColorService`. In the example below we make use of the `lastClickedElement` which is defined by element clicks in the model using the `elementClick`-output of the viewer (se context menu HTML snippet). From this we can access the expressID of the element and feed it to the `colorSubset`-method of the color service. We also provide it with a stringified version of the expressID as the customID. The customID is used by the color service to know that it already exists in the scene, and therefore clicking and coloring the same element again will toggle the color off.

```typescript
import { ColorService, ElementClickEvent } from 'ngx-ifc-viewer';

@Component({
  ...
})
export class AppComponent {
  ...
  public lastClicekedElement?: ElementClickEvent;

  constructor(
    private _color: ColorService
  ){}

  colorElement(){
    if(this.lastClickedElement == undefined) return;
    const expressID = this.lastClickedElement.expressID;
    this._color.colorSubset([expressID], "red", expressID.toString());
  }
}
```

The color service also has a function that applies color to groups of elements. Simply feed it with a nested array of expressIDs (an array per group) and the service will apply colors and return the colors used. The [default color pallette](https://coolors.co/palette/001219-005f73-0a9396-94d2bd-e9d8a6-ee9b00-ca6702-bb3e03-ae2012-9b2226) has 10 colors.

```typescript
import { ColorService, IFCModelLoaderService } from 'ngx-ifc-viewer';

@Component({
  ...
})
export class AppComponent {
  ...
  constructor(
    private _modelLoader: IFCModelLoaderService,
    private _color: ColorService
  ){}

  colorGroups(){
    const wallIDs = await this._modelLoader.ifc.getAllItemsOfType(modelID, IFCWALLSTANDARDCASE, false);
    const windowIDs = await this._modelLoader.ifc.getAllItemsOfType(modelID, IFCWINDOW, false);
    const doorIDs = await this._modelLoader.ifc.getAllItemsOfType(modelID, IFCDOOR, false);
    const colors = await this._color.colorByGroup([wallIDs, windowIDs, doorIDs]);
    console.log(colors);
  }
}
```