Skip to content

Instantly share code, notes, and snippets.

@leefsmp
Last active March 22, 2021 22:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save leefsmp/2af8e476221163023bc17424fd35e844 to your computer and use it in GitHub Desktop.
Save leefsmp/2af8e476221163023bc17424fd35e844 to your computer and use it in GitHub Desktop.
Forge Viewer Selection Filter Extension
/////////////////////////////////////////////////////////////////
// SelectionFilter Viewer Extension
// By Philippe Leefsma, Autodesk Inc, April 2017
//
/////////////////////////////////////////////////////////////////
import MultiModelExtensionBase from 'Viewer.MultiModelExtensionBase'
import WidgetContainer from 'WidgetContainer'
import FilterTreeView from './FilterTreeView'
import EventTool from 'Viewer.EventTool'
import Toolkit from 'Viewer.Toolkit'
import { ReactLoader } from 'Loader'
import ReactDOM from 'react-dom'
import Switch from 'Switch'
import Label from 'Label'
import React from 'react'
class SelectionFilterExtension extends MultiModelExtensionBase {
/////////////////////////////////////////////////////////////////
// Class constructor
//
/////////////////////////////////////////////////////////////////
constructor (viewer, options) {
super (viewer, options)
this.onNodeChecked = this.onNodeChecked.bind(this)
this.renderTitle = this.renderTitle.bind(this)
this.eventTool = new EventTool(this.viewer)
this.react = options.react
this.leafNodesMap = {}
}
/////////////////////////////////////////////////////////
// Load callback
//
/////////////////////////////////////////////////////////
load () {
this.react.setState({
models: []
}).then (() => {
this.react.pushRenderExtension(this)
this.models.forEach ((model) => {
if (model.getData().instanceTree) {
this.addModel(model)
}
})
})
this.eventTool.on ('buttondown', () => {
this.mouseDown = true
return false
})
this.eventTool.on ('buttonup', (event) => {
this.mouseDown = false
return false
})
this.eventTool.on ('mousemove', (event) => {
// model.rayIntersect cannot be used in this scenario
// because needs to check for every component
// for intersection
//const raycaster = this.pointerToRaycaster(event)
//const hitTest = this.viewer.model.rayIntersect(
// raycaster, true, dbIds)
if (!this.mouseDown) {
const hitTest = this.viewer.clientToWorld(
event.canvasX,
event.canvasY,
true)
if (hitTest) {
const {guid} = hitTest.model
return !this.leafNodesMap[guid][hitTest.dbId]
}
}
return false
})
this.viewer.loadDynamicExtension(
'Viewing.Extension.ContextMenu').then (
(ctxMenuExtension) => {
ctxMenuExtension.on('buildMenu', (params) => {
const guid = params.model
? params.model.guid
: ''
const dbId = params.dbId
return (!dbId || this.leafNodesMap[guid][dbId])
? params.menu
: []
})
})
console.log('Viewing.Extension.SelectionFilter loaded')
return true
}
/////////////////////////////////////////////////////////
//
//
/////////////////////////////////////////////////////////
get className() {
return 'selection-filter'
}
/////////////////////////////////////////////////////////
// Extension Id
//
/////////////////////////////////////////////////////////
static get ExtensionId () {
return 'Viewing.Extension.SelectionFilter'
}
/////////////////////////////////////////////////////////
// Unload callback
//
/////////////////////////////////////////////////////////
unload () {
console.log('Viewing.Extension.SelectionFilter unloaded')
this.eventTool.deactivate()
super.unload ()
return true
}
/////////////////////////////////////////////////////////
// Adds a new model to the treeview and
// initializes the dbIds Map
/////////////////////////////////////////////////////////
async addModel (model) {
const {models} = this.react.getState()
const guids = models.map((m)=> {return m.guid})
if (guids.includes(model.guid)) {
return
}
this.react.setState({
models: [...models, model]
})
this.leafNodesMap[model.guid] = {}
Toolkit.getLeafNodes (model).then((dbIds) => {
dbIds.forEach((dbId) => {
this.leafNodesMap[model.guid][dbId] = true
})
})
this.eventTool.activate()
}
/////////////////////////////////////////////////////////
// callback invoked by base class when a new model
// is loaded and structure tree is ready to parse
//
/////////////////////////////////////////////////////////
onObjectTreeCreated (event) {
this.addModel (event.model)
}
/////////////////////////////////////////////////////////
// User Selection callback. Checks if the selected dbId
// is selectable or not
//
/////////////////////////////////////////////////////////
onSelection (event) {
if (event.selections.length) {
const selection = event.selections[0]
const dbId = selection.dbIdArray[0]
const model = selection.model
if (this.leafNodesMap[model.guid]) {
if (!this.leafNodesMap[model.guid][dbId]) {
setTimeout(() => {
this.viewer.clearSelection()
}, 300)
this.viewer.clearSelection()
}
}
}
}
/////////////////////////////////////////////////////////
// Model unloaded callback invoked by the base class
//
/////////////////////////////////////////////////////////
onModelUnloaded (event) {
const {models} = this.react.getState()
const model = event.model
this.react.setState({
models: models.filter((m) => {
return (m.guid !== model.guid)
})
})
delete this.leafNodesMap[model.guid]
}
/////////////////////////////////////////////////////////
// Node checked callback invoked by the treeview
// component
/////////////////////////////////////////////////////////
onNodeChecked (event) {
const model = event.model
const node = event.node
Toolkit.getLeafNodes (model, node.id).then(
(dbIds) => {
dbIds.forEach((dbId) => {
if (node.checked) {
const leafNode = event.tree.getNodeById(dbId)
const checked = leafNode
? leafNode.checked
: node.checked
this.leafNodesMap[model.guid][dbId] = checked
} else {
this.leafNodesMap[model.guid][dbId] = false
}
})
})
}
/////////////////////////////////////////////////////////
// UI method to dock/undock the controls from side panel
//
/////////////////////////////////////////////////////////
async setDocking (docked) {
const id = SelectionFilterExtension.ExtensionId
if (docked) {
await this.react.popRenderExtension(id)
this.react.pushViewerPanel(this, {
height: 250,
width: 350
})
} else {
await this.react.popViewerPanel(id)
this.react.pushRenderExtension(this)
}
}
/////////////////////////////////////////////////////////
// Render title using React
//
/////////////////////////////////////////////////////////
renderTitle (docked) {
const spanClass = docked
? 'fa fa-chain-broken'
: 'fa fa-chain'
return (
<div className="title">
<label>
Selection Filter
</label>
<div className="selection-filter-controls">
<button onClick={() => this.setDocking(docked)}
title="Toggle docking mode">
<span className={spanClass}/>
</button>
</div>
</div>
)
}
/////////////////////////////////////////////////////////
// Render controls using React
//
/////////////////////////////////////////////////////////
renderContent () {
const { models } = this.react.getState()
const treeViews = models.map((model) => {
return (
<FilterTreeView onNodeChecked={this.onNodeChecked}
viewer={this.viewer}
key={model.guid}
model={model}/>
)
})
return (
<div className="content">
<ReactLoader show={!models.length}/>
{ treeViews }
</div>
)
}
/////////////////////////////////////////////////////////
// Render all
//
/////////////////////////////////////////////////////////
render (opts) {
return (
<WidgetContainer
renderTitle={() => this.renderTitle(opts.docked)}
showTitle={opts.showTitle}
className={this.className}>
{ this.renderContent () }
</WidgetContainer>
)
}
}
Autodesk.Viewing.theExtensionManager.registerExtension (
SelectionFilterExtension.ExtensionId,
SelectionFilterExtension)
export default 'Viewing.Extension.SelectionFilter'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment