threepipe
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

HierarchyUiPlugin.ts 4.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. import {createDiv, createStyles, css, timeout} from 'ts-browser-helpers'
  2. import Tree from 'treejs'
  3. import {AViewerPluginSync, IObject3D, Object3D, ThreeViewer} from 'threepipe'
  4. import {UiObjectConfig} from 'uiconfig.js'
  5. export class HierarchyUiPlugin extends AViewerPluginSync<''> {
  6. enabled = true
  7. public static readonly PluginType = 'HierarchyUiPlugin'
  8. toJSON: any = undefined
  9. treeView: any = undefined
  10. hierarchyDiv = createDiv({
  11. innerHTML: '',
  12. id: 'tpHierarchyContainer',
  13. addToBody: false,
  14. })
  15. constructor(enabled = true) {
  16. super()
  17. this.enabled = enabled
  18. this.reset = this.reset.bind(this)
  19. this._postFrame = this._postFrame.bind(this)
  20. this.uiConfig.domChildren = [this.hierarchyDiv]
  21. createStyles(css`
  22. #tpHierarchyContainer{
  23. width: 100%;
  24. height: auto;
  25. background-color: transparent;
  26. color: var(--tp-container-foreground-color, hsl(230, 7%, 75%));
  27. margin-top: 0;
  28. }
  29. .treejs .treejs-switcher:before {
  30. border-top: 6px solid var(--tp-container-foreground-color, hsl(230, 7%, 75%)) !important;
  31. }
  32. `)
  33. }
  34. reset(e?: any) {
  35. if (e?.fromHierarchyPlugin) return // for infinite loop
  36. if (!e?.hierarchyChanged) return
  37. this._needsReset = true
  38. }
  39. protected async _reset() {
  40. this._needsReset = false
  41. while (this.hierarchyDiv.firstChild) this.hierarchyDiv.firstChild.remove()
  42. const obj = this._viewer?.scene.modelRoot
  43. if (!obj) return
  44. const data = obj.children.reduce(this._buildData, [])
  45. const visible = obj.children.reduce(this._findVisible, [])
  46. let firstChange = false
  47. return new Promise<void>((resolve) => {
  48. this.treeView = new Tree(this.hierarchyDiv, {
  49. closeDepth: 1,
  50. data,
  51. // values: visible, // uuids of visible nodes
  52. loaded: function() {
  53. this.values = visible
  54. resolve()
  55. },
  56. onChange: () => {
  57. if (!firstChange) { // first time called when loaded
  58. firstChange = true
  59. return
  60. }
  61. timeout(200).then(() => { // wait for the UI to update
  62. if (this.treeView)
  63. this._setVisible(this.treeView.values)
  64. })
  65. },
  66. onItemLabelClick: (item: any) => {
  67. const obj1 = this._viewer?.scene.modelRoot.getObjectByProperty('uuid', item)
  68. if (!obj1 || !obj.visible) return
  69. obj1.dispatchEvent({type: 'select', value: obj1, ui: true})
  70. },
  71. })
  72. })
  73. }
  74. onAdded(viewer: ThreeViewer) {
  75. super.onAdded(viewer)
  76. this.reset()
  77. viewer.scene.addEventListener('sceneUpdate', this.reset)
  78. viewer.addEventListener('postFrame', this._postFrame)
  79. }
  80. onRemove(viewer: ThreeViewer) {
  81. // todo: remove UI element.
  82. viewer.scene.removeEventListener('sceneUpdate', this.reset)
  83. viewer.removeEventListener('postFrame', this._postFrame)
  84. return super.onRemove(viewer)
  85. }
  86. protected _needsReset = false
  87. protected _postFrame() {
  88. if (this._needsReset) this._reset()
  89. }
  90. dispose() {
  91. // todo destroy UI element.
  92. }
  93. uiConfig: UiObjectConfig = {
  94. type: 'folder',
  95. label: 'Hierarchy',
  96. children: [],
  97. }
  98. private _buildData = (data: any[], obj: IObject3D): any[] => {
  99. data.push({
  100. text: obj.name || 'unnamed',
  101. id: obj.uuid,
  102. children: obj.children.reduce(this._buildData, []),
  103. })
  104. return data
  105. }
  106. private _findVisible = (data: any[], obj: Object3D): any[] => { // only leaf.
  107. if (!obj.visible) return data
  108. if (obj.children.length < 1) data.push(obj.uuid)
  109. else data.push(...obj.children.reduce(this._findVisible, []))
  110. return data
  111. }
  112. private _setVisible = (values: string[]): void => {
  113. this._viewer?.doOnce('postFrame', () => {
  114. const obj = this._viewer?.scene.modelRoot
  115. if (!obj || values === undefined || values === null) return
  116. const ancestorSet: Set<Object3D> = new Set()
  117. obj.traverse((o)=>{
  118. if (o === obj) return
  119. o.visible = values.includes(o.uuid)
  120. if (o.visible) o.traverseAncestors(p=>ancestorSet.add(p))
  121. })
  122. ancestorSet.forEach(o=> o.visible = true)
  123. this._viewer?.scene?.setDirty({refreshScene: true, fromHierarchyPlugin: true, updateGround: false})
  124. })
  125. }
  126. }