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

BlueprintJsUiPlugin.ts 7.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. import {UiConfigRendererBlueprint} from 'uiconfig-blueprint'
  2. import {
  3. Class,
  4. Color,
  5. createDiv,
  6. CustomContextMenu,
  7. downloadBlob,
  8. getOrCall,
  9. IEvent,
  10. IViewerPlugin,
  11. IViewerPluginSync,
  12. Texture,
  13. ThreeViewer, UndoManagerPlugin,
  14. uploadFile,
  15. Vector2,
  16. Vector3,
  17. Vector4, UiObjectConfig, WebGLCubeRenderTarget, WebGLMultipleRenderTargets, WebGLRenderTarget,
  18. } from 'threepipe'
  19. export class BlueprintJsUiPlugin extends UiConfigRendererBlueprint implements IViewerPluginSync {
  20. declare ['constructor']: typeof BlueprintJsUiPlugin
  21. static readonly PluginType = 'BlueprintJsUi'
  22. enabled = true
  23. static CONTAINER_SLOT = 'uiconfigMainPanelSlot'
  24. constructor(container?: HTMLElement) {
  25. super(container ?? document.getElementById(BlueprintJsUiPlugin.CONTAINER_SLOT) ?? document.body, {
  26. autoPostFrame: false,
  27. })
  28. this.THREE = {Color, Vector4, Vector3, Vector2, Texture} as any
  29. // @ts-expect-error required for tpTextureInputComponent so that it doesn't clone it. todo check others as well like object3d etc
  30. Texture.prototype._ui_isPrimitive = true
  31. // @ts-expect-error same
  32. WebGLRenderTarget.prototype._ui_isPrimitive = true
  33. // @ts-expect-error same
  34. WebGLCubeRenderTarget.prototype._ui_isPrimitive = true
  35. // @ts-expect-error same
  36. WebGLMultipleRenderTargets.prototype._ui_isPrimitive = true
  37. }
  38. protected _viewer?: ThreeViewer
  39. private _lastManager?: BlueprintJsUiPlugin['undoManager']
  40. onAdded(viewer: ThreeViewer): void {
  41. this._viewer = viewer
  42. viewer.addEventListener('preRender', this._preRender)
  43. viewer.addEventListener('postRender', this._postRender)
  44. viewer.addEventListener('preFrame', this._preFrame)
  45. viewer.addEventListener('postFrame', this._postFrame)
  46. const undo = viewer.getOrAddPluginSync(UndoManagerPlugin) // yes, manual dependency
  47. const manager = undo?.undoManager
  48. if (manager) {
  49. this._lastManager?.dispose()
  50. this._lastManager = this.undoManager
  51. this.undoManager = manager
  52. if (this._lastManager) Object.assign(manager.presets, this._lastManager.presets)
  53. }
  54. }
  55. onRemove(viewer: ThreeViewer): void {
  56. this._viewer = undefined
  57. viewer.removeEventListener('preRender', this._preRender)
  58. viewer.removeEventListener('postRender', this._postRender)
  59. viewer.removeEventListener('preFrame', this._preFrame)
  60. viewer.removeEventListener('postFrame', this._postFrame)
  61. this.undoManager = this._lastManager
  62. this._lastManager = undefined
  63. this.dispose()
  64. }
  65. dispose() {
  66. this.undoManager?.dispose()
  67. this.unmount()
  68. }
  69. private _plugins: IViewerPlugin[] = []
  70. setupPlugins(...plugins: Class<IViewerPlugin>[]): void {
  71. plugins.forEach(plugin => this.setupPluginUi(plugin))
  72. }
  73. setupPluginUi<T extends IViewerPlugin>(plugin: T|Class<T>): UiObjectConfig | undefined {
  74. const p = (<Class<IViewerPlugin>>plugin).prototype ? this._viewer?.getPlugin<T>(<Class<T>>plugin) : <T>plugin
  75. if (!p) {
  76. console.warn('plugin not found:', plugin)
  77. return undefined
  78. }
  79. this._plugins.push(p)
  80. if (p.uiConfig && p.uiConfig.hidden === undefined) p.uiConfig.hidden = false // todo; this is a hack for now
  81. const ui = p.uiConfig
  82. this.appendChild(ui)
  83. // this._setupPluginSerializationContext(ui, p)
  84. return ui
  85. }
  86. // @ts-expect-error todo: port to blueprint from tweakpane
  87. private _setupPluginSerializationContext(ui: any, p: IViewerPlugin) {
  88. // serialization
  89. if (!(ui?.uiRef && p.toJSON)) return;
  90. (p as any)._defaultState = typeof p.toJSON === 'function' ? p.toJSON() : null
  91. ;(p as any).resetDefaults = async() => {
  92. if (!(p as any)._defaultState) return
  93. await p.fromJSON?.((p as any)._defaultState)
  94. ui.uiRefresh?.(true, 'postFrame')
  95. }
  96. const topBtn = (ui.uiRef as any).controller_.view.element
  97. const opBtn = createDiv({
  98. innerHTML: '&#8942;',
  99. classList: ['pluginOptionsButton'],
  100. elementTag: 'button',
  101. })
  102. opBtn.onclick = (ev) => {
  103. const ops = {} as any
  104. if (typeof p.toJSON === 'function') {
  105. ops['download preset'] = async() => {
  106. if (!this._viewer) return
  107. const json = this._viewer.exportPluginConfig(p)
  108. await downloadBlob(new Blob([JSON.stringify(json, null, 2)], {type: 'application/json'}), 'preset.' + (p.constructor as any).PluginType + '.json')
  109. }
  110. }
  111. if (typeof p.fromJSON === 'function') {
  112. ops['upload preset'] = async() => {
  113. const files = await uploadFile(false, false)
  114. if (files.length === 0) return
  115. const file = files[0]
  116. const text = await file.text()
  117. const json = JSON.parse(text)
  118. await this._viewer?.importPluginConfig(json, p)
  119. ui.uiRefresh?.(true, 'postFrame')
  120. }
  121. if ((p as any)._defaultState) ops['reset defaults'] = () => (p as any).resetDefaults?.()
  122. }
  123. const menu = CustomContextMenu.Create(ops, topBtn.clientWidth - 120, 12)
  124. topBtn.append(menu)
  125. ev.preventDefault()
  126. }
  127. topBtn.appendChild(opBtn)
  128. }
  129. refreshPluginsEnabled() {
  130. this._plugins.forEach(p=>{
  131. const config = p.uiConfig
  132. if (config) {
  133. // const enabled = (p as any).enabled ?? true
  134. // safeSetProperty(config, 'hidden', !enabled, true)
  135. // if (config.expanded)
  136. // safeSetProperty(config, 'expanded', config.expanded && enabled, true)
  137. if (getOrCall(config.hidden) !== true)
  138. config.uiRefresh?.(true, 'postFrame')
  139. else if (config.uiRef) {
  140. config.uiRef.hidden = true
  141. }
  142. }
  143. })
  144. }
  145. /**
  146. * Required for loading files in BPFileComponent
  147. */
  148. get fileLoader() {
  149. return this._viewer
  150. }
  151. private _preRender = () => this.refreshQueue('preRender')
  152. private _postRender = () => this.refreshQueue('postRender')
  153. private _postFrame = (e: IEvent<'postFrame'>) => {
  154. this.dispatchEvent(e)
  155. this.refreshQueue('postFrame')
  156. }
  157. private _preFrame = () => this.refreshQueue('preFrame')
  158. // alert = async(message?: string): Promise<void> =>this._viewer ? this._viewer.dialog.alert(message) : window?.alert(message)
  159. // confirm = async(message?: string): Promise<boolean> =>this._viewer ? this._viewer.dialog.confirm(message) : window?.confirm(message)
  160. // prompt = async(message?: string, _default?: string, cancel = true): Promise<string | null> =>this._viewer ? this._viewer.dialog.prompt(message, _default, cancel) : window?.prompt(message, _default)
  161. }