Autodesk Forge is now Autodesk Platform Services

12 Sep 2017

Adding custom lines to the Forge Viewer scene

    There are several reasons why you would want to add custom lines to a Viewer scene: representing wireframe geometry, visualizing bounding boxes or any other of visual feedback your app may have to provide to the user...

    There are basically three ways to achieve that, let's take a look what those are, but let's first create a single line with the snippet below:

const geometry = new THREE.Geometry ()

geometry.vertices.push (new THREE.Vector3 ( 0,  0,  0))
geometry.vertices.push (new THREE.Vector3 (10, 10, 10))

var linesMaterial = new THREE.LineBasicMaterial ({
  color: new THREE.Color (0xFF0000),
  transparent: true,
  depthWrite: false,
  depthTest: true,
  linewidth: 10,
  opacity: 1.0
})

var lines = new THREE.Line (geometry,
  linesMaterial,
  THREE.LinePieces)

     I - The first method is to add the lines directly to the scene:

//1 - First method
viewer.impl.scene.add (lines)
viewer.impl.invalidate (true)

    However this approach doesn't seem to reliably work in any browser: it works in Firefox but not in Chrome on my side... so let's move to the next one.

    II - Second method is to add the lines to the sceneAfter

//2 - Second method
viewer.impl.sceneAfter.add (lines)
viewer.impl.invalidate (true)

    Works better across all browsers but as soon as you toggle visibility of a component ("Isolate" or "Hide" from the context menu or programmatically with viewer.hide, viewer.isolate, ... ) all lines added to the scene this way will also disappear. I guess this is a consequence of the shaders and rendering settings used by the viewer.

   III - Third method is to create an overlay scene and add the lines to it:

//3 - Third method
viewer.impl.createOverlayScene (
  'myOverlay', linesMaterial)

viewer.impl.addOverlay (
  'myOverlay', lines)

viewer.impl.invalidate (true)

    This approach works reliably across all browsers and the custom lines will not be affected by visibility settings of the components in the viewer. So based on your needs, you'll have you pick the one that is best suited...

    As an example I created the following Viewing.Extension.BoundingBox which lets you pick a component and visualize its bounding box represented by custom lines in an overlay scene.

    The live demo is available here and the complete code below:

/////////////////////////////////////////////////////////////////
// BoundingBox Viewer Extension
// By Philippe Leefsma, Autodesk Inc, August 2017
//
/////////////////////////////////////////////////////////////////
import MultiModelExtensionBase from 'Viewer.MultiModelExtensionBase'
import Toolkit from 'Viewer.Toolkit'
class BoundingBoxExtension extends MultiModelExtensionBase {
/////////////////////////////////////////////////////////
// Class constructor
//
/////////////////////////////////////////////////////////
constructor (viewer, options) {
super (viewer, options)
this.onContextMenu = this.onContextMenu.bind(this)
this.linesMaterial = this.createMaterial(0x0000FF)
this.lineGroups = []
}
/////////////////////////////////////////////////////////
//
//
/////////////////////////////////////////////////////////
createMaterial (color = 0x000000, opacity = 1.0) {
return new THREE.LineBasicMaterial({
color: new THREE.Color(color),
transparent: true,
depthWrite: false,
depthTest: true,
linewidth: 10,
opacity
})
}
/////////////////////////////////////////////////////////
// Load callback
//
/////////////////////////////////////////////////////////
load () {
this.viewer.loadDynamicExtension(
'Viewing.Extension.ContextMenu').then(
(ctxMenuExtension) => {
ctxMenuExtension.addHandler(
this.onContextMenu)
})
console.log('Viewing.Extension.BoundingBox loaded')
return true
}
/////////////////////////////////////////////////////////
//
//
/////////////////////////////////////////////////////////
get className() {
return 'bounding-box'
}
/////////////////////////////////////////////////////////
// Extension Id
//
/////////////////////////////////////////////////////////
static get ExtensionId () {
return 'Viewing.Extension.BoundingBox'
}
/////////////////////////////////////////////////////////
// Unload callback
//
/////////////////////////////////////////////////////////
unload () {
console.log('Viewing.Extension.BoundingBox unloaded')
super.unload ()
return true
}
/////////////////////////////////////////////////////////
//
//
/////////////////////////////////////////////////////////
onModelRootLoaded () {
this.options.loader.show (false)
this.viewer.impl.createOverlayScene (
'boundingBox',
this.linesMaterial)
}
/////////////////////////////////////////////////////////
//
//
/////////////////////////////////////////////////////////
async onSelection (event) {
if (event.selections.length) {
const selection = event.selections[0]
const model =
this.viewer.activeModel ||
this.viewer.model
this.selectedDbId = selection.dbIdArray[0]
const bbox =
await Toolkit.getWorldBoundingBox(
model, this.selectedDbId)
this.drawBox(bbox)
}
}
/////////////////////////////////////////////////////////
//
//
/////////////////////////////////////////////////////////
drawBox (bbox) {
const geometry = new THREE.Geometry()
const { min, max } = bbox
geometry.vertices.push(new THREE.Vector3(min.x, min.y, min.z))
geometry.vertices.push(new THREE.Vector3(max.x, min.y, min.z))
geometry.vertices.push(new THREE.Vector3(max.x, min.y, min.z))
geometry.vertices.push(new THREE.Vector3(max.x, min.y, max.z))
geometry.vertices.push(new THREE.Vector3(max.x, min.y, max.z))
geometry.vertices.push(new THREE.Vector3(min.x, min.y, max.z))
geometry.vertices.push(new THREE.Vector3(min.x, min.y, max.z))
geometry.vertices.push(new THREE.Vector3(min.x, min.y, min.z))
geometry.vertices.push(new THREE.Vector3(min.x, max.y, max.z))
geometry.vertices.push(new THREE.Vector3(max.x, max.y, max.z))
geometry.vertices.push(new THREE.Vector3(max.x, max.y, max.z))
geometry.vertices.push(new THREE.Vector3(max.x, max.y, min.z))
geometry.vertices.push(new THREE.Vector3(max.x, max.y, min.z))
geometry.vertices.push(new THREE.Vector3(min.x, max.y, min.z))
geometry.vertices.push(new THREE.Vector3(min.x, max.y, min.z))
geometry.vertices.push(new THREE.Vector3(min.x, max.y, max.z))
geometry.vertices.push(new THREE.Vector3(min.x, min.y, min.z))
geometry.vertices.push(new THREE.Vector3(min.x, max.y, min.z))
geometry.vertices.push(new THREE.Vector3(max.x, min.y, min.z))
geometry.vertices.push(new THREE.Vector3(max.x, max.y, min.z))
geometry.vertices.push(new THREE.Vector3(max.x, min.y, max.z))
geometry.vertices.push(new THREE.Vector3(max.x, max.y, max.z))
geometry.vertices.push(new THREE.Vector3(min.x, min.y, max.z))
geometry.vertices.push(new THREE.Vector3(min.x, max.y, max.z))
const lines = new THREE.Line(geometry,
this.linesMaterial,
THREE.LinePieces)
this.lineGroups.push(lines)
this.viewer.impl.addOverlay('boundingBox', lines)
this.viewer.impl.invalidate(
true, true, true)
}
/////////////////////////////////////////////////////////
//
//
/////////////////////////////////////////////////////////
onContextMenu (event) {
const model =
this.viewer.activeModel ||
this.viewer.model
event.menu.forEach((entry) => {
const title = entry.title.toLowerCase()
switch (title) {
case 'isolate':
entry.target = () => {
Toolkit.isolateFull(
this.viewer, this.selectedDbId, model)
}
break
case 'hide selected':
entry.target = () => {
Toolkit.hide(
this.viewer, this.selectedDbId, model)
}
break
case 'show all objects':
entry.target = () => {
Toolkit.isolateFull(
this.viewer, [], model)
this.viewer.fitToView()
}
break
default: break
}
})
const instanceTree = model.getData().instanceTree
const dbId = event.dbId || (instanceTree
? instanceTree.getRootId()
: -1)
if (dbId > -1) {
event.menu.push({
title: 'Clear All BoundingBoxes',
target: () => {
this.lineGroups.forEach((lines) => {
this.viewer.impl.removeOverlay('boundingBox', lines)
})
this.viewer.impl.invalidate(
true, true, true)
this.lineGroups = []
}
})
}
}
}
Autodesk.Viewing.theExtensionManager.registerExtension (
BoundingBoxExtension.ExtensionId,
BoundingBoxExtension)
export default 'Viewing.Extension.BoundingBox'

Related Article