I'm wrapping "web-ifc-viewer" in an angular application. I've some troubles to hide and show components inside the IFC.
I've started from this example but I need to build a generic BIM viewer, so I can't define any category to prior.
import {AfterViewInit, Component, ElementRef, Input, OnDestroy, OnInit, ViewChild} from '@angular/core';
import {IfcViewerAPI} from "web-ifc-viewer";
import {Subject, takeUntil} from "rxjs";
import {AppThemes, BimViewerService} from "./bim-viewer.service";
@Component({
selector: 'almaviva-bim-viewer',
templateUrl: './bim-viewer.template.html',
styleUrls: ['./bim-viewer.component.scss']
})
export class BimViewerComponent implements AfterViewInit {
@ViewChild('viewerContainer')
container!: ElementRef;
viewer?: IfcViewerAPI;
model: any;
ifcElement: any;
loadingValue: number = 0;
constructor() {
}
ngAfterViewInit(): void {
if (this.container) {
this.viewer = new IfcViewerAPI({container: this.container.nativeElement});
this.loadIfc('/assets/sample.ifc');
}
}
private async loadIfc(url: string) {
try {
if (this.viewer) {
await this.viewer.IFC.loader.ifcManager.useWebWorkers(true, '/assets/IFCWorker.js');
await this.viewer.IFC.setWasmPath("wasm/");
this.viewer.axes.setAxes(1000);
this.viewer.grid.setGrid(1000);
await this.viewer.IFC.loader.ifcManager.applyWebIfcConfig({
USE_FAST_BOOLS: true,
COORDINATE_TO_ORIGIN: true
});
this.viewer.IFC.loader.ifcManager.setOnProgress(
(event) => {
this.loadingValue = Math.floor((event.loaded * 100) / event.total);
}
)
this.model = await this.viewer.IFC.loadIfcUrl(url);
const project = await this.viewer.IFC.getSpatialStructure(this.model.modelID, true);
this.ifcElement = project.children[0];
await this.viewer.shadowDropper.renderShadow(this.model.modelID);
}
} catch (e) {
console.log(e);
}
}
async toggleLayer(event: Event, layer: any) {
const subset = this.viewer?.IFC.loader.ifcManager.createSubset({
modelID: this.model.modelID,
ids: [layer.expressID],
removePrevious: true,
customID: `${layer.expressID}-custom-id`
});
if (subset) {
this.viewer?.context.getScene().remove(subset);
}
}
}
When I toggle the layer (toggleLayer()
) I receive an object from the subset like this
This is my html
<div>
<mat-sidenav-container>
<mat-sidenav mode="side" opened>
<mat-toolbar>
<span>
BIM
</span>
</mat-toolbar>
<mat-progress-bar mode="determinate" [value]="loadingValue"></mat-progress-bar>
<mat-list role="list" *ngIf="!ifcElement">
<mat-list-item role="listitem">
Caricamento IFC in corso...
</mat-list-item>
</mat-list>
<mat-accordion *ngIf="ifcElement">
<mat-expansion-panel *ngFor="let arch of ifcElement?.children || []">
<mat-expansion-panel-header>
<mat-panel-title>
{{arch.Name?.value || arch.LongName?.value || 'Architettura'}}
</mat-panel-title>
</mat-expansion-panel-header>
<mat-list role="list">
<mat-list-item role="listitem" *ngFor="let layer of arch.children">
<mat-checkbox (click)="toggleLayer($event, layer)">
{{layer.Name?.value || layer.LongName?.value || 'N/A'}}
</mat-checkbox>
</mat-list-item>
</mat-list>
</mat-expansion-panel>
</mat-accordion>
</mat-sidenav>
<mat-sidenav-content>
<div id="viewer-container" #viewerContainer></div>
<div class="loading-spinner-wrapper" *ngIf="loadingValue!==100">
<mat-spinner mode="indeterminate" diameter="35"></mat-spinner>
</div>
</mat-sidenav-content>
</mat-sidenav-container>
</div>
Here the final result in the browser
The issue it's that nothing happens when I toggle the layer. And the subset log looks always the same.
When you load an IFC model, it also gets added to the scene. You need to remove the original model from the scene.
Instead of creating a subset every time the user toggles a layer, you need to create subsets for each layer in the beginning, and just add or remove the subset from the scene when the user toggles a layer. You can find a tutorial about that here, the full example here and the working app here.
The structure of your logic should be similar to the following snippet. This has been extracted from the minimal example linked above, so probably you need to adapt it a bit to you project:
import {
IFCWALLSTANDARDCASE,
IFCSLAB,
IFCDOOR,
IFCWINDOW,
IFCFURNISHINGELEMENT,
IFCMEMBER,
IFCPLATE,
} from 'web-ifc';
//Sets up the IFC loading
const ifcModels = [];
const ifcLoader = new IFCLoader();
ifcLoader.ifcManager.setWasmPath('../../../');
ifcLoader.load('../../../IFC/01.ifc', async (ifcModel) => {
ifcModels.push(ifcModel);
await setupAllCategories();
});
// Sets up optimized picking
ifcLoader.ifcManager.setupThreeMeshBVH(
computeBoundsTree,
disposeBoundsTree,
acceleratedRaycast);
// List of categories names
const categories = {
IFCWALLSTANDARDCASE,
IFCSLAB,
IFCFURNISHINGELEMENT,
IFCDOOR,
IFCWINDOW,
IFCPLATE,
IFCMEMBER,
};
// Gets the name of a category
function getName(category) {
const names = Object.keys(categories);
return names.find(name => categories[name] === category);
}
// Gets all the items of a category
async function getAll(category) {
return ifcLoader.ifcManager.getAllItemsOfType(0, category, false);
}
// Creates a new subset containing all elements of a category
async function newSubsetOfType(category) {
const ids = await getAll(category);
return ifcLoader.ifcManager.createSubset({
modelID: 0,
scene,
ids,
removePrevious: true,
customID: category.toString(),
});
}
// Stores the created subsets
const subsets = {};
async function setupAllCategories() {
const allCategories = Object.values(categories);
for (let i = 0; i < allCategories.length; i++) {
const category = allCategories[i];
await setupCategory(category);
}
}
// Creates a new subset and configures the checkbox
async function setupCategory(category) {
subsets[category] = await newSubsetOfType(category);
setupCheckBox(category);
}
// Sets up the checkbox event to hide / show elements
function setupCheckBox(category) {
const name = getName(category);
const checkBox = document.getElementById(name);
checkBox.addEventListener('change', (event) => {
const checked = event.target.checked;
const subset = subsets[category];
if (checked) scene.add(subset);
else subset.removeFromParent();
});
}