此篇文章原作是 Autodesk ADN Philippe Leefsma,以下以我简称。

这有一个简易的博客用来说明一个我刚加入 https://forge-rcdb.autodesk.io 的一个新功能,他主要是提供一个使用介面让使用者可以在一个文档(Forge Document)里不同的视图(Forge Viewable)间快速的切换。A360 viewer有提供类似的功能,但这并不包含在 Forge Viewer 里头。

在 Forge Document 里。条列 Viewable 清单或是切换视图是非常容易的,你主要是要建立一个使用者介面让使用者去选取他们想观看的内容。如果您不知道要怎么让 Forge Model Derivative 服务帮您转换多个视图,请先看看这篇文章:为什么Revit模型有多个视图,丢到 Forge 转换后确只剩一个? (如何设定多个视图)

接著让我们来看看该怎么实作这个功能(我所有的代码都有用到 ES6、async/await,UI的部份使用了 React):

一、从已转换好的 URN 载入 Document:

/////////////////////////////////////////////////////////
// Load a document from URN
//
/////////////////////////////////////////////////////////
static loadDocument (urn) {

  return new Promise((resolve, reject) => {

    const paramUrn = !urn.startsWith('urn:')
      ? 'urn:' + urn
      : urn

    Autodesk.Viewing.Document.load(paramUrn, (doc) => {

      resolve (doc)

    }, (error) => {

      reject (error)
    })
  })
}

二、从已载入的 Document 物件获取所有 Viewable 清单:

/////////////////////////////////////////////////////////
// Return viewables
//
/////////////////////////////////////////////////////////
static getViewableItems (doc, roles = ['3d', '2d']) {

  const rootItem = doc.getRootItem()

  let items = []

  const roleArray = roles
    ? (Array.isArray(roles) ? roles : [roles])
    : []

  roleArray.forEach((role) => {

    items = [ ...items,
      ...Autodesk.Viewing.Document.getSubItemsWithProperties(
        rootItem, { type: 'geometry', role }, true) ]
  })

  return items
}

三、载入已选中的 Viewable:
首先,我门要把已载入的模型先卸载,下面的样例是假设我们已经载入了一个模型,现在使用者选择了新的 Viewable
要 Forge Viewer 载入。我使用了 viewer.tearDown() 来确保当前载入模型占用的内存可以都被释出。这个样例也支持从 3D 模型切换到 2D 模型,或者是反过来;您可以忽略我代码里使用到的 React 相关的代码。

/////////////////////////////////////////////////////////
// Load the selected viewable
//
/////////////////////////////////////////////////////////
onItemSelected (item) {

  const {activeItem} = this.react.getState()

  if (item.guid !== activeItem.guid) {

    this.viewer.tearDown()

    this.viewer.start()

    const path =
      this.viewerDocument.getViewablePath(item)

    this.viewer.loadModel(path)

    this.react.setState({
      activeItem: item
    })
  }
}

完整的代码如下所示,对这功能有兴趣的朋友们可以通过这个网址 https://forge-rcdb.autodesk.i...,并载入 Viewable Selector 来体验。


/////////////////////////////////////////////////////////
// Viewing.Extension.ViewableSelector
// by Philippe Leefsma, November 2017
//
/////////////////////////////////////////////////////////
import MultiModelExtensionBase from 'Viewer.MultiModelExtensionBase'
import './Viewing.Extension.ViewableSelector.scss'
import WidgetContainer from 'WidgetContainer'
import ReactTooltip from 'react-tooltip'
import ServiceManager from 'SvcManager'
import Toolkit from 'Viewer.Toolkit'
import ReactDOM from 'react-dom'
import Image from 'Image'
import Label from 'Label'
import React from 'react'

class ViewableSelectorExtension extends MultiModelExtensionBase {

  /////////////////////////////////////////////////////////
  // Class constructor
  //
  /////////////////////////////////////////////////////////
  constructor (viewer, options) {

    super (viewer, options)

    this.react = options.react
  }

  /////////////////////////////////////////////////////////
  //
  //
  /////////////////////////////////////////////////////////
  get className() {

    return 'viewable-selector'
  }

  /////////////////////////////////////////////////////////
  // Extension Id
  //
  /////////////////////////////////////////////////////////
  static get ExtensionId() {

    return 'Viewing.Extension.ViewableSelector'
  }

  /////////////////////////////////////////////////////////
  // Load callback
  //
  /////////////////////////////////////////////////////////
  load () {

    this.react.setState({

      activeItem: null,
      items: []

    }).then (async() => {

      const urn = this.options.model.urn

      this.viewerDocument =
        await this.options.loadDocument(urn)

      const items =
        await Toolkit.getViewableItems(
          this.viewerDocument)

      if (items.length > 1) {

        this.createButton()

        await this.react.setState({
          activeItem: items[0],
          items: [items[0], items[1], items[2]]
        })

        if (this.options.showPanel) {

          this.showPanel (true)
        }
      }
    })

    console.log('Viewing.Extension.ViewableSelector loaded')

    return true
  }

  /////////////////////////////////////////////////////////
  // Unload callback
  //
  /////////////////////////////////////////////////////////
  unload () {

    this.react.popViewerPanel(this)

    console.log('Viewing.Extension.ViewableSelector unloaded')

    return true
  }

  /////////////////////////////////////////////////////////
  // Load the selected viewable
  //
  /////////////////////////////////////////////////////////
  onItemSelected (item) {

    const {activeItem} = this.react.getState()

    if (item.guid !== activeItem.guid) {

      this.viewer.tearDown()

      this.viewer.start()

      const path =
        this.viewerDocument.getViewablePath(item)

      this.viewer.loadModel(path)

      this.react.setState({
        activeItem: item
      })
    }
  }

  /////////////////////////////////////////////////////////
  // Create a button to display the panel
  //
  /////////////////////////////////////////////////////////
  createButton () {

    this.button = document.createElement('button')

    this.button.title = 'This model has multiple views ...'

    this.button.className = 'viewable-selector btn'

    this.button.innerHTML = 'Views'

    this.button.onclick = () => {

      this.showPanel(true)
    }

    const span = document.createElement('span')

    span.className = 'fa fa-list-ul'

    this.button.appendChild(span)

    this.viewer.container.appendChild(this.button)
  }

  /////////////////////////////////////////////////////////
  // Show/Hide panel
  //
  /////////////////////////////////////////////////////////
  showPanel (show) {

    if (show) {

      const {items} = this.react.getState()

      this.button.classList.add('active')

      const container = this.viewer.container

      const height = Math.min(
        container.offsetHeight - 110,
        (items.length + 1)  * 78 + 55)

      this.react.pushViewerPanel(this, {
        maxHeight: height,
        draggable: false,
        maxWidth: 500,
        minWidth: 310,
        width: 310,
        top: 30,
        height
      })

    } else {

      this.react.popViewerPanel(this.id).then(() => {

        this.button.classList.remove('active')
      })
    }
  }

  /////////////////////////////////////////////////////////
  // Render React panel content
  //
  /////////////////////////////////////////////////////////
  renderContent () {

    const {activeItem, items} = this.react.getState()

    const urn = this.options.model.urn

    const apiUrl = this.options.apiUrl

    const domItems = items.map((item) => {

      const active = (item.guid === activeItem.guid)
        ? ' active' :''

      const query = `size=400&guid=${item.guid}`

      const src = `${apiUrl}/thumbnails/${urn}?${query}`

      return (
        <div key={item.guid} className={"item" + active}
          onClick={() => this.onItemSelected(item)}>
          <div className="image-container"
            data-for={`thumbnail-${item.guid}`}
            data-tip>
            <Image src={src}/>
          </div>
          <ReactTooltip id={`thumbnail-${item.guid}`}
            className="tooltip-thumbnail"
            delayShow={700}
            effect="solid"
            place="right">
            <div>
              <img src={src} height="200"/>
            </div>
          </ReactTooltip>
          <Label text={item.name}/>
        </div>
      )
    })

    return (
      <div className="items">
        {domItems}
        <div style={{height: '80px'}}/>
      </div>
    )
  }

  /////////////////////////////////////////////////////////
  // Render title
  //
  /////////////////////////////////////////////////////////
  renderTitle () {

    return (
      <div className="title">
        <label>
          Select Viewable
        </label>
        <div className="viewable-selector-controls">
          <button onClick={() => this.showPanel(false)}
            title="Toggle panel">
            <span className="fa fa-times"/>
          </button>
        </div>
      </div>
    )
  }

  /////////////////////////////////////////////////////////
  // Render main
  //
  /////////////////////////////////////////////////////////
  render (opts) {

    return (
      <WidgetContainer
        renderTitle={() => this.renderTitle(opts.docked)}
        showTitle={opts.showTitle}
        className={this.className}>
        {this.renderContent()}
      </WidgetContainer>
    )
  }
}

Autodesk.Viewing.theExtensionManager.registerExtension(
  ViewableSelectorExtension.ExtensionId,
  ViewableSelectorExtension)

康益昇
748 声望103 粉丝