Skip to content

Instantly share code, notes, and snippets.

@leefsmp
Created May 17, 2017 15:48
Show Gist options
  • Save leefsmp/0e77baf619843d3a8273b8997e78fe39 to your computer and use it in GitHub Desktop.
Save leefsmp/0e77baf619843d3a8273b8997e78fe39 to your computer and use it in GitHub Desktop.
/////////////////////////////////////////////////////////
// Viewing.Extension.ModelLoader
// by Philippe Leefsma, April 2017
//
/////////////////////////////////////////////////////////
import MultiModelExtensionBase from 'Viewer.MultiModelExtensionBase'
import ContentEditable from 'react-contenteditable'
import './Viewing.Extension.ModelLoader.scss'
import WidgetContainer from 'WidgetContainer'
import ServiceManager from 'SvcManager'
import { ReactLoader } from 'Loader'
import Toolkit from 'Viewer.Toolkit'
import DOMPurify from 'dompurify'
import ReactDOM from 'react-dom'
import Label from 'Label'
import React from 'react'
import {
DropdownButton,
MenuItem
} from 'react-bootstrap'
class ModelLoaderExtension extends MultiModelExtensionBase {
/////////////////////////////////////////////////////////
// Class constructor
//
/////////////////////////////////////////////////////////
constructor (viewer, options) {
super (viewer, options)
this.renderTitle = this.renderTitle.bind(this)
this.dialogSvc =
ServiceManager.getService('DialogSvc')
this.modelSvc =
ServiceManager.getService('ModelSvc')
this.react = options.react
}
/////////////////////////////////////////////////////////
//
//
/////////////////////////////////////////////////////////
get className() {
return 'model-loader'
}
/////////////////////////////////////////////////////////
// Extension Id
//
/////////////////////////////////////////////////////////
static get ExtensionId() {
return 'Viewing.Extension.ModelLoader'
}
/////////////////////////////////////////////////////////
// Load callback
//
/////////////////////////////////////////////////////////
load () {
if (!this.viewer.model) {
this.viewer.container.classList.add('empty')
}
const models = this.models
const activeModel = models.length
? models[0]
: null
this.firstFileType = activeModel
? this.getFileType(activeModel.urn)
: null
this.react.setState({
activeModel,
models
}).then (() => {
this.react.pushRenderExtension(this)
})
const transformerReactOptions = {
pushRenderExtension: () => {
return Promise.resolve()
},
popRenderExtension: () => {
return Promise.resolve()
}
}
const transformerOptions = Object.assign({}, {
react: transformerReactOptions,
fullTransform : true,
hideControls : true
}, this.options.transformer)
this.viewer.loadDynamicExtension(
'Viewing.Extension.ModelTransformer',
transformerOptions).then((modelTransformer) => {
this.react.setState({
modelTransformer
})
if (activeModel ) {
modelTransformer.setModel(
activeModel)
}
})
console.log('Viewing.Extension.ModelLoader loaded')
return true
}
/////////////////////////////////////////////////////////
// Unload callback
//
/////////////////////////////////////////////////////////
unload () {
console.log('Viewing.Extension.ModelLoader unloaded')
this.react.popViewerPanel(this)
super.unload ()
return true
}
/////////////////////////////////////////////////////////
// Displays model selection popup dialog
//
/////////////////////////////////////////////////////////
showModelDlg () {
this.dialogSvc.setState({
className: 'model-loader-dlg',
title: 'Select Model ...',
showOK: false,
search: '',
content:
<div>
<ReactLoader show={true}/>
</div>,
open: true
})
this.modelSvc.getModels(this.options.database).then(
(models) => {
const dbModelsByName =
_.sortBy(models, (model) => {
return model.name
})
this.dialogSvc.setState({
dbModels: dbModelsByName,
open: true
}, true)
this.setDlgItems (dbModelsByName)
this.batchRequestThumbnails(5)
})
}
/////////////////////////////////////////////////////////
// Get file type by base64 decoding the model URN
//
/////////////////////////////////////////////////////////
getFileType (urn) {
return window.atob(urn).split(".").pop(-1)
}
/////////////////////////////////////////////////////////
// Loads a model based on database info
// For testing purpose also supports
// loading models offline
// See for more details: http://autode.sk/2qsKxx8
//
/////////////////////////////////////////////////////////
loadModel (dbModel) {
return new Promise(async(resolve) => {
const fileType = this.getFileType(dbModel.urn)
const loadOptions = {
placementTransform:
this.buildPlacementTransform(fileType)
}
switch (dbModel.env) {
case 'AutodeskProduction':
const doc = await Toolkit.loadDocument(
dbModel.urn)
const items = Toolkit.getViewableItems(doc)
if (items.length) {
const path = doc.getViewablePath(items[0])
this.viewer.loadModel(path, loadOptions,
(model) => {
model.database = this.options.database
model.dbModelId = dbModel._id
model.name = dbModel.name
model.guid = this.guid()
model.urn = dbModel.urn
resolve (model)
})
}
break
case 'Local':
this.viewer.loadModel(dbModel.path, loadOptions,
(model) => {
model.database = this.options.database
model.dbModelId = dbModel._id
model.name = dbModel.name
model.guid = this.guid()
model.urn = dbModel.urn
resolve (model)
})
break
}
})
}
/////////////////////////////////////////////////////////
// Unload model upon user request
//
/////////////////////////////////////////////////////////
async unloadModel () {
const {activeModel, models} = this.react.getState()
const onClose = async(result) => {
if (result === 'OK') {
const filteredModels = models.filter((model) => {
return model.guid !== activeModel.guid
})
if (!filteredModels.length) {
this.viewer.container.classList.add('empty')
this.firstFileType = null
}
await this.react.setState({
models: filteredModels
})
const nextActiveModel = filteredModels.length
? filteredModels[0]
: null
this.eventSink.emit('model.unloaded', {
model: activeModel
})
this.viewer.impl.unloadModel(activeModel)
await this.setActiveModel(nextActiveModel, {
source: 'model.unloaded'
})
}
this.dialogSvc.off('dialog.close', onClose)
}
const msg = DOMPurify.sanitize(
`Are you sure you want to unload`
+ `<b><br/>${activeModel.name}</b> ?`)
this.dialogSvc.on('dialog.close', onClose)
this.dialogSvc.setState({
className: 'model-loader-unload-dlg',
title: 'Unload Model ...',
content:
<div dangerouslySetInnerHTML={{__html: msg}}>
</div>,
open: true
})
}
/////////////////////////////////////////////////////////
// .rvt and .nwc files are z-oriented, whereas other
// file formats are y-oriented.
// Depending what file type was the initial model,
// we need to adjust the subsequent loaded models
//
/////////////////////////////////////////////////////////
buildPlacementTransform (fileType) {
this.firstFileType = this.firstFileType || fileType
const placementTransform = new THREE.Matrix4()
// those file type have different orientation
// than other, so need to correct it
// upon insertion
const zOriented = ['rvt', 'nwc']
if (zOriented.indexOf(this.firstFileType) > -1) {
if (zOriented.indexOf(fileType) < 0) {
placementTransform.makeRotationX(
90 * Math.PI/180)
}
} else {
if(zOriented.indexOf(fileType) > -1) {
placementTransform.makeRotationX(
-90 * Math.PI/180)
}
}
return placementTransform
}
/////////////////////////////////////////////////////////
// Fit whole model to view
//
/////////////////////////////////////////////////////////
fitModelToView (model) {
const instanceTree = model.getData().instanceTree
if (instanceTree) {
const rootId = instanceTree.getRootId()
this.viewer.fitToView([rootId], model)
}
}
/////////////////////////////////////////////////////////
// ModelBeginLoad event
//
/////////////////////////////////////////////////////////
onModelBeginLoad (event) {
const {models} = this.react.getState()
const model = event.model
this.react.setState({
models: [...models, model]
})
this.setActiveModel (model, {
source: 'model.loaded',
fitToView: true
})
this.firstFileType = this.firstFileType ||
this.getFileType(model.urn)
}
/////////////////////////////////////////////////////////
// ModelRootLoaded event
//
/////////////////////////////////////////////////////////
onModelRootLoaded (event) {
this.viewer.container.classList.remove('empty')
}
/////////////////////////////////////////////////////////
// Model Selected event
//
/////////////////////////////////////////////////////////
onSelection (event) {
if (event.selections && event.selections.length) {
const selection = event.selections[0]
const model = selection.model
this.setActiveModel (model, {
source: 'model.selected'
})
this.eventSink.emit('model.selected', {
model
})
}
}
/////////////////////////////////////////////////////////
// Set model as active
//
/////////////////////////////////////////////////////////
async setActiveModel (model, params = {}) {
const activeGuid = this.viewer.activeModel
? this.viewer.activeModel.guid
: null
this.viewer.activeModel = model
if (params.fitToView) {
this.fitModelToView (model)
}
await this.react.setState({
activeModel: model
})
if (model) {
this.setStructure(model)
if (model.guid !== activeGuid) {
this.eventSink.emit('model.activated', {
source: params.source,
model
})
}
}
}
/////////////////////////////////////////////////////////
// Fixing the model structure browser to show active
// model structure
//
/////////////////////////////////////////////////////////
setStructure (model) {
const instanceTree = model.getData().instanceTree
if (instanceTree && this.viewer.modelstructure) {
this.viewer.modelstructure.setModel(
instanceTree)
}
}
/////////////////////////////////////////////////////////
//
//
/////////////////////////////////////////////////////////
onKeyDown (e) {
if (e.keyCode === 13) {
e.stopPropagation()
e.preventDefault()
}
}
/////////////////////////////////////////////////////////
//
//
/////////////////////////////////////////////////////////
onSearchChanged (e) {
const search = e.target.value.toLowerCase()
this.dialogSvc.setState({
search
}, true)
const state = this.dialogSvc.getState()
const filteredDbModels =
state.dbModels.filter((dbModel) => {
return search.length
? dbModel.name.toLowerCase().indexOf(search) > -1
: true
})
this.setDlgItems (filteredDbModels)
}
/////////////////////////////////////////////////////////
// Load model items in popup selection dialog
//
/////////////////////////////////////////////////////////
setDlgItems (dbModels) {
const modelDlgItems = dbModels.map((dbModel) => {
return (
<div key={dbModel._id} className="model-item"
onClick={() => {
this.loadModel(dbModel).then((model) => {
this.eventSink.emit('model.loaded', {
model
})
})
this.dialogSvc.setState({
open: false
})
}}>
<img className={dbModel.thumbnail ? "":"default-thumbnail"}
src={dbModel.thumbnail ? dbModel.thumbnail : ""}/>
<Label text= {dbModel.name}/>
</div>
)
})
const state = this.dialogSvc.getState()
this.dialogSvc.setState({
content:
<div>
<ReactLoader show={false}/>
<ContentEditable
onChange={(e) => this.onSearchChanged(e)}
onKeyDown={(e) => this.onKeyDown(e)}
data-placeholder="Search ..."
html={state.search}
className="search"
/>
<div className="scroller">
{ modelDlgItems }
</div>
</div>
}, true)
}
/////////////////////////////////////////////////////////
// batch requests thumbnails for models shown in
// popup selection dialog
//
/////////////////////////////////////////////////////////
batchRequestThumbnails (size) {
const state = this.dialogSvc.getState()
const chunks = _.chunk(state.dbModels, size)
chunks.forEach((modelChunk) => {
const modelIds = modelChunk.map((model) => {
return model._id
})
this.modelSvc.getThumbnails(
this.options.database, modelIds).then(
(thumbnails) => {
const dbModels = state.dbModels.map((model) => {
const idx = modelIds.indexOf(model._id)
return (idx < 0
? model
: Object.assign({}, model, {
thumbnail: thumbnails[idx]
}))
})
this.dialogSvc.setState({
dbModels
}, true)
this.setDlgItems (dbModels)
})
})
}
/////////////////////////////////////////////////////////
// Panel docking mode
//
/////////////////////////////////////////////////////////
async setDocking (docked) {
const id = ModelLoaderExtension.ExtensionId
if (docked) {
await this.react.popRenderExtension(id)
await this.react.pushViewerPanel(this, {
height: 250,
width: 350
})
} else {
await this.react.popViewerPanel(id)
this.react.pushRenderExtension(this)
}
}
/////////////////////////////////////////////////////////
// React method - render panel title
//
/////////////////////////////////////////////////////////
renderTitle (docked) {
const spanClass = docked
? 'fa fa-chain-broken'
: 'fa fa-chain'
return (
<div className="title">
<label>
Model Loader
</label>
<div className="model-loader-controls">
<button onClick={() => this.setDocking(docked)}
title="Toggle docking mode">
<span className={spanClass}/>
</button>
</div>
</div>
)
}
/////////////////////////////////////////////////////////
// React method - render panel controls
//
/////////////////////////////////////////////////////////
renderControls () {
const {activeModel, models} = this.react.getState()
const modelItems = models.map((model, idx) => {
return (
<MenuItem eventKey={idx} key={model.guid}
onClick={() => {
this.setActiveModel(model, {
source: 'dropdown',
fitToView: true
})
}}>
{ model.name }
</MenuItem>
)
})
const modelName = activeModel
? activeModel.name
: ''
return (
<div className="controls">
<div className="row">
<DropdownButton
title={"Model: " + modelName}
className="sequence-dropdown"
disabled={!activeModel}
key="sequence-dropdown"
id="sequence-dropdown">
{ modelItems }
</DropdownButton>
<button onClick={() => this.showModelDlg()}
title="Load model">
<span className="fa fa-plus"/>
</button>
<button onClick={() => this.unloadModel()}
disabled={!activeModel}
title="Unload model">
<span className="fa fa-times"/>
</button>
</div>
</div>
)
}
/////////////////////////////////////////////////////////
// React method - render transformer extension UI
//
/////////////////////////////////////////////////////////
renderTransformer () {
const {modelTransformer} = this.react.getState()
return modelTransformer
? modelTransformer.render({showTitle: false})
: <div/>
}
/////////////////////////////////////////////////////////
// React method - render extension UI
//
/////////////////////////////////////////////////////////
render (opts) {
return (
<WidgetContainer
renderTitle={() => this.renderTitle(opts.docked)}
showTitle={opts.showTitle}
className={this.className}>
{ this.renderControls() }
{ this.renderTransformer() }
</WidgetContainer>
)
}
}
Autodesk.Viewing.theExtensionManager.registerExtension(
ModelLoaderExtension.ExtensionId,
ModelLoaderExtension)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment