Some of you may remember this post, which talks about the ability to export to Navisworks from Revit to bring room information into the Forge viewer. One of the side effects of using this technique is that there’s a bunch of semi-transparent room boundary geometry in the resultant model, which can make navigation a little tricky.
For instance, here’s what happens when I try to select the wall at the end of a corridor (you can’t see the cursor, but you should get the idea – the invisible room geometry gets selected rather than the wall).
To help improve the user experience in Dasher 360, we’ve been working on reducing the impact of this room geometry on the selection mechanism. The first step was to make sure the room geometry doesn’t get selected: very often it will get selected when the user actually wants to select something behind it. The way I worked around this was to check for the “on selection changed” event and then – if a highly transparent solid (which I’m taking to be a room) gets selected – we go ahead and select the next non-transparent object instead. To do this we need to track the cursor coordinates via the “on mouse move” event.
The second thing I ended up doing was to change the material used for room geometry – which has a very specific opacity of 0.09999999 – to be completely invisible. This stops a problem of the unselected room appearing as a haze across the view.
This has all been wrapped up into an extension to make it easier to load and unload, as needed. Here’s the TypeScript code:
/// <reference path='../../../../../typings/lmv-client/lmv-client.d.ts' />
export default class HideRoomsExtension extends Autodesk.Viewing.Extension {
protected _controller: Dasher.IViewerControllerInterface;
protected _dataModel: Dasher.IDataModel;
private _textures: string[] = [];
private _canvasX: number;
private _canvasY: number;
constructor(viewer: Autodesk.Viewing.Private.GuiViewer3D, options: any) {
super(viewer, options);
this._dataModel = options.dataModel;
this._controller = options.controller;
}
load(): boolean {
document.addEventListener('mousemove', this.onMouseMove);
this.viewer.addEventListener(Autodesk.Viewing.SELECTION_CHANGED_EVENT, this.onSelectionChanged);
this.hideTransparentMaterials();
return true;
}
unload(): boolean {
document.removeEventListener('mousemove', this.onMouseMove);
this.viewer.removeEventListener(Autodesk.Viewing.SELECTION_CHANGED_EVENT, this.onSelectionChanged);
this.showTransparentMaterials();
return true;
}
// This isn't ideal: we store the current mouse location as we need
// it to reselect when a room is selected
private onMouseMove = (event: any): void => {
this._canvasX = event.canvasX;
this._canvasY = event.canvasY;
}
private onSelectionChanged = (event: any): void => {
// Check for transparent solids
if (event.dbIdArray.length > 0) {
event.dbIdArray.forEach((id: number) => {
this.viewer.model.getProperties(id, (result: any) => {
if (result.name === 'Solid') {
if (result.properties.findIndex((prop: any) => {
return prop.displayName === 'Transparency' && prop.displayValue > 0.8;
}) > -1) {
// Reselect based on the current mouse position
let res = this.viewer.impl.hitTest(this._canvasX, this._canvasY, true);
if (res && res.dbId && res.model) {
this.viewer.impl.selector.setSelection([res.dbId], res.model);
} else {
this._dataModel.clearViewerSelection();
}
}
}
});
});
}
}
private hideTransparentMaterials(): void {
let store = (this._textures.length === 0);
let mats = this.viewer.impl.matman()._materials;
Object.keys(mats).forEach((p: string) => {
let m = mats[p];
if (m.opacity < 0.1 && m.opacity > 0.099 && m.visible) {
if (store) {
this._textures.push(p);
}
m.visible = false;
m.needsUpdate = true;
}
});
}
private showTransparentMaterials(): void {
if (this._textures) {
let mats = this.viewer.impl.matman()._materials;
this._textures.forEach((p: string) => {
let m = mats[p];
m.visible = true;
m.needsUpdate = true;
});
}
}
}
Here’s the new behaviour, where we see the end wall gets selected rather than one of the phantom rooms.