Просмотр исходного кода

Rewrite/improve HierarchyUiPlugin.

master
Palash Bansal 1 год назад
Родитель
Сommit
cc0cc8a05f
Аккаунт пользователя с таким Email не найден

+ 2
- 2
plugins/tweakpane-editor/package-lock.json Просмотреть файл

{ {
"name": "@threepipe/plugin-tweakpane-editor", "name": "@threepipe/plugin-tweakpane-editor",
"version": "0.3.4",
"version": "0.4.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@threepipe/plugin-tweakpane-editor", "name": "@threepipe/plugin-tweakpane-editor",
"version": "0.3.4",
"version": "0.4.0",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@threepipe/plugin-tweakpane": "file:./../tweakpane/src/", "@threepipe/plugin-tweakpane": "file:./../tweakpane/src/",

+ 3
- 3
plugins/tweakpane-editor/package.json Просмотреть файл

{ {
"name": "@threepipe/plugin-tweakpane-editor", "name": "@threepipe/plugin-tweakpane-editor",
"description": "Tweakpane Editor Plugin for ThreePipe", "description": "Tweakpane Editor Plugin for ThreePipe",
"version": "0.3.4",
"version": "0.4.0",
"devDependencies": { "devDependencies": {
"tippy.js": "^6.3.7", "tippy.js": "^6.3.7",
"treejs": "git://github.com/repalash/treejs.git#d303016bb74e75725d13e97291ac1d4727985918" "treejs": "git://github.com/repalash/treejs.git#d303016bb74e75725d13e97291ac1d4727985918"
"replace": { "replace": {
"dependencies": {}, "dependencies": {},
"peerDependencies": { "peerDependencies": {
"threepipe": "^0.0.39",
"@threepipe/plugin-tweakpane": "^0.5.3"
"threepipe": "^0.0.42",
"@threepipe/plugin-tweakpane": "^0.5.4"
} }
} }
}, },

+ 174
- 21
plugins/tweakpane-editor/src/HierarchyUiPlugin.ts Просмотреть файл

import {createDiv, createStyles, css, timeout} from 'ts-browser-helpers'
import {createDiv, createStyles, css} from 'ts-browser-helpers'
import Tree from 'treejs' import Tree from 'treejs'
import {AViewerPluginSync, IObject3D, Object3D, ThreeViewer} from 'threepipe'
import {
AViewerPluginSync,
IObject3D,
ISceneEventMap,
Object3D,
PickingPlugin,
ThreeViewer,
type UndoManagerPlugin, Event2,
} from 'threepipe'
import {UiObjectConfig} from 'uiconfig.js' import {UiObjectConfig} from 'uiconfig.js'


export class HierarchyUiPlugin extends AViewerPluginSync { export class HierarchyUiPlugin extends AViewerPluginSync {


toJSON: any = undefined toJSON: any = undefined


treeView: any = undefined
treeView?: Tree = undefined


hierarchyDiv = createDiv({ hierarchyDiv = createDiv({
innerHTML: '', innerHTML: '',
.treejs .treejs-switcher:before { .treejs .treejs-switcher:before {
border-top: 6px solid var(--tp-container-foreground-color, hsl(230, 7%, 75%)) !important; border-top: 6px solid var(--tp-container-foreground-color, hsl(230, 7%, 75%)) !important;
} }
.treejs .treejs-switcher,.treejs-label,.treejs-checkbox {
pointer-events: auto;
}

.treejs .treejs-node {
pointer-events: none;
position: relative;
}

.treejs .treejs-label {
position: absolute;
height: 16px;
line-height: 16px;
text-overflow: ellipsis;
overflow: hidden;
width: calc(100% - 50px);
padding: 1px 4px;
margin: 1px 0px;
border-radius: 2px;
box-sizing: content-box;
}

.treejs .treejs-node-selected .treejs-label {
outline-offset: -1px;
outline: solid 1px #1890ff;
}

.treejs .treejs-node-selected > .treejs-label {
background-color: #067ce9;
color: #eee;
outline: none;
}
`) `)


} }


reset(e?: any) {
reset(e?: Event2<'sceneUpdate', ISceneEventMap, IObject3D>) {
if (e?.source === HierarchyUiPlugin.PluginType) return // for infinite loop if (e?.source === HierarchyUiPlugin.PluginType) return // for infinite loop

// visible changed from outside
if (e && e.object && e.change === 'visible' && this.treeView) {
const nodeId = e.object.uuid
// @ts-expect-error no type
const node = this.treeView.nodesById[nodeId] as TNode
if (node && !!node.status !== e.object.visible) {
this.treeView.setValue(nodeId)
this.treeView.updateLiElements()
}
// this.treeView.values = obj.children.reduce(this._findVisible, [])
// console.log(this.treeView.values)
}

if (!e?.hierarchyChanged) return if (!e?.hierarchyChanged) return
this._needsReset = true this._needsReset = true
} }

protected async _reset() { protected async _reset() {
this._needsReset = false this._needsReset = false


if (!obj) return if (!obj) return


const data = obj.children.reduce(this._buildData, []) const data = obj.children.reduce(this._buildData, [])
const visible = obj.children.reduce(this._findVisible, [])
let firstChange = false let firstChange = false
return new Promise<void>((resolve) => {

return new Promise<void>((resolve, _reject) => {
this.treeView = new Tree(this.hierarchyDiv, { this.treeView = new Tree(this.hierarchyDiv, {
closeDepth: 1, closeDepth: 1,
data, data,
// values: visible, // uuids of visible nodes // values: visible, // uuids of visible nodes
loaded: function() { loaded: function() {
this.values = visible
this.values = []
resolve() resolve()
}, },
onChange: () => { onChange: () => {
firstChange = true firstChange = true
return return
} }
timeout(200).then(() => { // wait for the UI to update
if (this.treeView)
this._setVisible(this.treeView.values)
})
// timeout(200).then(() => { // wait for the UI to update
this._setVisible()
// })
}, },
onItemLabelClick: (item: any) => { onItemLabelClick: (item: any) => {
const obj1 = this._viewer?.scene.modelRoot.getObjectByProperty('uuid', item) const obj1 = this._viewer?.scene.modelRoot.getObjectByProperty('uuid', item)
obj1.dispatchEvent({type: 'select', value: obj1, object: obj1, ui: true}) obj1.dispatchEvent({type: 'select', value: obj1, object: obj1, ui: true})
}, },
}) })
}).then(()=>{
this._refreshVisible()
this._selectedObjectChanged()
}) })
} }


private _refreshVisible() {
const obj = this._viewer?.scene.modelRoot as Object3D
if (!obj) return
const visible = obj.children.reduce(this._findVisible, [])
this.treeView?.emptyNodesCheckStatus()
this.treeView?.treeNodes.map(n=>refreshVisible(n, visible, this.treeView!))
this.treeView?.updateLiElements()
}

onAdded(viewer: ThreeViewer) { onAdded(viewer: ThreeViewer) {
super.onAdded(viewer) super.onAdded(viewer)
this.reset()
viewer.scene.addEventListener('sceneUpdate', this.reset) viewer.scene.addEventListener('sceneUpdate', this.reset)
viewer.addEventListener('postFrame', this._postFrame) viewer.addEventListener('postFrame', this._postFrame)

viewer.forPlugin<PickingPlugin>('PickingPlugin', (pi)=>{
pi.addEventListener('selectedObjectChanged', this._selectedObjectChanged)
}, (pi)=>{
pi.removeEventListener('selectedObjectChanged', this._selectedObjectChanged)
})
viewer.forPlugin<UndoManagerPlugin>('UndoManagerPlugin', (um)=>{
this.undoManager = um.undoManager
}, ()=>{
this.undoManager = undefined
})

this.reset()
}

undoManager?: UndoManagerPlugin['undoManager'] = undefined

private _selectedObjectChanged = ()=>{
const picking = this._viewer?.getPlugin(PickingPlugin)
if (!picking || !this.treeView) return
const liElem = this.treeView.liElementsById as Record<string, HTMLLIElement>
const elems = Object.values(liElem)
for (const li of elems) {
li.classList.remove('treejs-node-selected')
}
const selected = picking.getSelectedObject() as Object3D|undefined
if (selected?.uuid) {
const li = liElem[selected?.uuid]
if (li) {
li.classList.add('treejs-node-selected')
li.scrollIntoView({block: 'nearest', inline: 'nearest'})
}
}
} }


onRemove(viewer: ThreeViewer) { onRemove(viewer: ThreeViewer) {
// todo: remove UI element. // todo: remove UI element.
viewer.scene.removeEventListener('sceneUpdate', this.reset) viewer.scene.removeEventListener('sceneUpdate', this.reset)
viewer.removeEventListener('postFrame', this._postFrame) viewer.removeEventListener('postFrame', this._postFrame)
viewer.getPlugin(PickingPlugin)?.removeEventListener('selectedObjectChanged', this._selectedObjectChanged)
return super.onRemove(viewer) return super.onRemove(viewer)
} }


} }
private _findVisible = (data: any[], obj: Object3D): any[] => { // only leaf. private _findVisible = (data: any[], obj: Object3D): any[] => { // only leaf.
if (!obj.visible) return data if (!obj.visible) return data
if (obj.children.length < 1) data.push(obj.uuid)
else data.push(...obj.children.reduce(this._findVisible, []))
data.push(obj.uuid)
data.push(...obj.children.reduce(this._findVisible, []))
return data return data
} }
private _setVisible = (values: string[]): void => {
private _setVisible = (): void => {
this._viewer?.doOnce('postFrame', () => { this._viewer?.doOnce('postFrame', () => {
const obj = this._viewer?.scene.modelRoot const obj = this._viewer?.scene.modelRoot
if (!obj || values === undefined || values === null) return
const ancestorSet: Set<Object3D> = new Set()
if (!obj || !this.treeView) return
const changeMap: Map<Object3D, [boolean, boolean]> = new Map()
function cAdd(o: Object3D, v: boolean) {
const c = changeMap.get(o)?.[0] ?? o.visible
if (v !== c) changeMap.set(o, [v, o.visible])
}
obj.traverse((o)=>{ obj.traverse((o)=>{
if (o === obj) return if (o === obj) return
o.visible = values.includes(o.uuid)
if (o.visible) o.traverseAncestors(p=>ancestorSet.add(p))
// @ts-expect-error no type
const node = this.treeView.nodesById[o.uuid] as TNode|undefined
if (node) {
cAdd(o, !!node.status)
if (o.visible) o.traverseAncestors(p => cAdd(p, true))
}
}) })
ancestorSet.forEach(o=> o.visible = true)
this._viewer?.scene?.setDirty({refreshScene: true, source: HierarchyUiPlugin.PluginType, updateGround: false})
const cmd = {
redo: (refresh = true)=>{
let changed = false
changeMap.entries().forEach(([o, v])=> {
if (o.visible === v[0]) return
o.visible = v[0]
changed = true
})
if (!changed) return
this._viewer?.scene?.setDirty({refreshScene: true, source: HierarchyUiPlugin.PluginType, updateGround: false})
refresh && this._refreshVisible()
},
undo: ()=>{
let changed = false
changeMap.entries().forEach(([o, v])=> {
if (o.visible === v[1]) return
o.visible = v[1]
changed = true
})
if (!changed) return
this._viewer?.scene?.setDirty({refreshScene: true, source: HierarchyUiPlugin.PluginType, updateGround: false})
this._refreshVisible()
},
}
cmd.redo(false)
this.undoManager?.record(cmd)
}) })
} }
} }

interface TNode {id: string, status: 0|1|2, text: string, children: TNode[]}
// this is required because setValues in Treejs toggles it, not sets it
function refreshVisible(node: TNode, visibles: string[], tree: Tree) {
const v = visibles.includes(node.id)
const last = node.status
if (node.children.length) {
let allVisible = true
for (const child of node.children) {
const res = refreshVisible(child, visibles, tree)
if (!res) allVisible = false
}
node.status = v ? allVisible ? 2 : 1 : 0
} else {
node.status = v ? 2 : 0
}
if (last !== node.status) {
// @ts-expect-error no type
tree.willUpdateNodesById[node.id] = node
}
return node.status
}

Загрузка…
Отмена
Сохранить