Pārlūkot izejas kodu

Add material selection mode in picker

master
Palash Bansal pirms 11 mēnešiem
vecāks
revīzija
211d62be34
Revīzijas autora e-pasta adrese nav piesaistīta nevienam kontam

+ 2
- 2
plugins/assimpjs/src/AssimpJsPlugin.ts Parādīt failu

@@ -110,8 +110,8 @@ export class AssimpJsPlugin extends AViewerPluginSync {
return
}
const initing = this.init()
const selected = this.exportSelected ? this._viewer.getPlugin(PickingPlugin)?.getSelectedObject() : undefined
object = object || selected || this._viewer.scene.modelRoot
const selected = this.exportSelected ? this._viewer.getPlugin(PickingPlugin)?.getSelectedObject<IObject3D>() : undefined
object = object || (selected?.isObject3D ? selected : this._viewer.scene.modelRoot)

// export to glb
const blob = await this._viewer.export(object, options)

+ 11
- 2
src/plugins/configurator/MaterialConfiguratorBasePlugin.ts Parādīt failu

@@ -2,7 +2,7 @@ import {AViewerPluginSync, ThreeViewer} from '../../viewer'
import {PickingPlugin} from '../interaction/PickingPlugin'
import {imageBitmapToBase64, makeColorSvgCircle, serialize} from 'ts-browser-helpers'
import {UiObjectConfig} from 'uiconfig.js'
import {IMaterial, PhysicalMaterial} from '../../core'
import {IMaterial, IObject3D, PhysicalMaterial} from '../../core'
import {MaterialPreviewGenerator} from '../../three'
import {Color} from 'three'

@@ -160,7 +160,16 @@ export class MaterialConfiguratorBasePlugin extends AViewerPluginSync {
@serialize()
variations: MaterialVariations[] = []

private _selectedMaterial = () => (this._picking?.getSelectedObject()?.material || undefined) as IMaterial | undefined
private _selectedMaterial = () => {
const selected = this._picking?.getSelectedObject()
if (!selected) return undefined
if ((selected as IMaterial).isMaterial) return selected as IMaterial
else {
const mat = ((selected as IObject3D)?.material || undefined) as IMaterial | undefined
if (Array.isArray(mat)) return mat[0]
return mat
}
}
uiConfig: UiObjectConfig = {
label: 'Material Configurator',
type: 'folder',

+ 3
- 2
src/plugins/configurator/SwitchNodeBasePlugin.ts Parādīt failu

@@ -4,6 +4,7 @@ import {PickingPlugin} from '../interaction/PickingPlugin'
import {UiObjectConfig} from 'uiconfig.js'
import {serialize} from 'ts-browser-helpers'
import {snapObject} from '../../three'
import {IObject3D} from '../../core'

/**
* Switch Node Plugin (Base)
@@ -131,8 +132,8 @@ export class SwitchNodeBasePlugin extends AViewerPluginSync {
@serialize() variations: ObjectSwitchNode[] = []

protected _selectedSwitchNode = (): Object3D | undefined => {
const obj = this._picking?.getSelectedObject() // (?.material || undefined) as IMaterial | undefined
if (!obj) return undefined
const obj = this._picking?.getSelectedObject<IObject3D>() // (?.material || undefined) as IMaterial | undefined
if (!obj?.isObject3D) return undefined
const nodes = this.variations.map(v => v.name)
let found: Object3D | undefined = undefined
obj.traverseAncestors(a => {

+ 3
- 2
src/plugins/export/AssetExporterPlugin.ts Parādīt failu

@@ -77,11 +77,12 @@ export class AssetExporterPlugin extends AViewerPluginSync {
async exportSelected(options?: ExportAssetOptions, download = true) {
const selected = this._viewer?.getPlugin<PickingPlugin>('PickingPlugin')?.getSelectedObject() as any
if (!selected) {
alert('Nothing selected')
this._viewer?.dialog.alert('Export Selected: Nothing selected')
return
}
const name = selected.name || 'selected'
const blob = await this._viewer!.assetManager.exporter.exportObject(selected, options ?? this.exportOptions)
if (blob && download) await this._viewer?.exportBlob(blob, 'object.' + blob.ext)
if (blob && download) await this._viewer?.exportBlob(blob, name + '.' + blob.ext)
return blob
}


+ 1
- 1
src/plugins/export/CanvasSnapshotPlugin.ts Parādīt failu

@@ -170,7 +170,7 @@ export class CanvasSnapshotPlugin extends AViewerPluginSync {
/**
* Only for {@link downloadSnapshot} and functions using that
*/
@uiConfig()
@uiConfig(undefined, {label: 'Options'})
@serialize()
defaultOptions: CanvasSnapshotPluginOptions = {
waitForProgressive: true,

+ 6
- 5
src/plugins/extras/SimplifyModifierPlugin.ts Parādīt failu

@@ -61,7 +61,8 @@ export abstract class SimplifyModifierPlugin extends AViewerPluginSync {

simplifyGeometries(geometry?: ValOrArr<IGeometry>, options?: SimplifyOptions) {
if (!geometry) {
const selected = this._pickingPlugin?.getSelectedObject()
const selected = this._pickingPlugin?.getSelectedObject<IObject3D>()
if (!selected?.isObject3D) return
const geom: IGeometry[] = []
selected?.traverse((o) => {
if (o.geometry && !geom.includes(o.geometry)) geom.push(o.geometry)
@@ -84,7 +85,7 @@ export abstract class SimplifyModifierPlugin extends AViewerPluginSync {
disposeOnReplace = false,
}: SimplifyOptions = {}): IGeometry|undefined {
if (!geometry) {
const selected = this._pickingPlugin?.getSelectedObject()
const selected = this._pickingPlugin?.getSelectedObject<IObject3D>()
geometry = selected?.geometry
if (!geometry) return undefined
}
@@ -184,9 +185,9 @@ export abstract class SimplifyModifierPlugin extends AViewerPluginSync {
return
}
}
const selected = this._pickingPlugin?.getSelectedObject()
if (!selected) {
await this._viewer.dialog.alert('Simplify: Nothing Selected')
const selected = this._pickingPlugin?.getSelectedObject<IObject3D>()
if (!selected?.isObject3D) {
await this._viewer.dialog.alert('Simplify: No Object Selected')
return
}
let doAll = false

+ 83
- 49
src/plugins/interaction/PickingPlugin.ts Parādīt failu

@@ -1,7 +1,7 @@
import {EventListener2, Object3D} from 'three'
import {Class, onChange, serialize} from 'ts-browser-helpers'
import {AViewerPluginEventMap, AViewerPluginSync, ThreeViewer} from '../../viewer'
import {BoxSelectionWidget, ObjectPicker, SelectionWidget} from '../../three'
import {bindToValue, BoxSelectionWidget, ObjectPicker, SelectionWidget} from '../../three'
import {IMaterial, IObject3D, IScene, ISceneEventMap} from '../../core'
import {UiObjectConfig} from 'uiconfig.js'
import {FrameFadePlugin} from '../pipeline/FrameFadePlugin'
@@ -9,10 +9,7 @@ import {type UndoManagerPlugin} from './UndoManagerPlugin'
import {ObjectPickerEventMap} from '../../three/utils/ObjectPicker'
import {CameraViewPlugin} from '../animation/CameraViewPlugin'

export interface PickingPluginEventMap extends AViewerPluginEventMap{
selectedObjectChanged: {object: IObject3D|null}
hoverObjectChanged: {object: IObject3D|null}
hitObject: {intersects: {selectedObject: IObject3D|null}}
export interface PickingPluginEventMap extends AViewerPluginEventMap, ObjectPickerEventMap{
}

export class PickingPlugin extends AViewerPluginSync<PickingPluginEventMap> {
@@ -41,6 +38,9 @@ export class PickingPlugin extends AViewerPluginSync<PickingPluginEventMap> {
this.uiConfig && this.uiConfig.uiRefresh?.()
}

@bindToValue({obj: '_picker', key: 'selectionMode'})
selectionMode: 'object' | 'material' = 'object'

@serialize()
autoFocus

@@ -54,10 +54,11 @@ export class PickingPlugin extends AViewerPluginSync<PickingPluginEventMap> {
widgetEnabled = true

protected _widgetEnabledChange() {
if (this.widgetEnabled && this._picker?.selectedObject)
this._widget?.attach(this._picker.selectedObject)
if (!this._widget) return
if (this.widgetEnabled && (this._picker?.selectedObject as IObject3D)?.isObject3D)
this._widget.attach(this._picker!.selectedObject as IObject3D)
else
this._widget?.detach()
this._widget.detach()
this.uiConfig?.uiRefresh?.(true)
}

@@ -81,12 +82,12 @@ export class PickingPlugin extends AViewerPluginSync<PickingPluginEventMap> {
this.dispatchEvent = this.dispatchEvent.bind(this)
}

getSelectedObject<T extends IObject3D = IObject3D>(): T|undefined {
getSelectedObject<T extends IObject3D|IMaterial = IObject3D|IMaterial>(): T|undefined {
if (this.isDisabled()) return
return this._picker?.selectedObject as T || undefined
}

setSelectedObject(object: IObject3D|undefined, focusCamera = false, trackUndo = true) { // todo: listen to object disposed
setSelectedObject(object: IObject3D|IMaterial|undefined, focusCamera = false, trackUndo = true) { // todo: listen to object disposed
const disabled = this.isDisabled()
if (disabled && !object) return
if (!this._picker) return
@@ -94,7 +95,7 @@ export class PickingPlugin extends AViewerPluginSync<PickingPluginEventMap> {
this.autoFocus = false
this._picker.setSelected(object || null, trackUndo)
this.autoFocus = t
if (!disabled && object && (t || focusCamera)) this.focusObject(object)
if (!disabled && object && this.selectionMode === 'object' && (t || focusCamera)) this.focusObject(object as IObject3D)
}

onAdded(viewer: ThreeViewer): void {
@@ -121,6 +122,7 @@ export class PickingPlugin extends AViewerPluginSync<PickingPluginEventMap> {
this._picker.addEventListener('selectedObjectChanged', this._selectedObjectChanged)
this._picker.addEventListener('hoverObjectChanged', this._hoverObjectChanged)
this._picker.addEventListener('hitObject', this._onObjectHit)
this._picker.addEventListener('selectionModeChanged', this._selectionModeChanged)

// on material drop on selected object
// viewer.scene.addEventListener('addSceneObject', async(e) => {
@@ -172,6 +174,7 @@ export class PickingPlugin extends AViewerPluginSync<PickingPluginEventMap> {
this._picker.removeEventListener('selectedObjectChanged', this._selectedObjectChanged)
this._picker.removeEventListener('hoverObjectChanged', this._hoverObjectChanged)
this._picker.removeEventListener('hitObject', this._onObjectHit)
this._picker.removeEventListener('selectionModeChanged', this._selectionModeChanged)
this._picker.dispose()
this._picker.undoManager = undefined // because setting above
this._picker = undefined
@@ -197,11 +200,13 @@ export class PickingPlugin extends AViewerPluginSync<PickingPluginEventMap> {
}

private _checkSelectedInScene() {
if (this.isDisabled()) return
if (this.isDisabled() || !this._viewer) return
const s = this.getSelectedObject()
if (!s || !(s as IObject3D).isObject3D) return // ignoring checking for materials in scene
let inScene = false
s?.traverseAncestors((o) => {
if (o === this._viewer?.scene) inScene = true
;(s as IObject3D).traverseAncestors((o) => {
if (inScene || o !== this._viewer!.scene) return
inScene = true
})
if (!inScene) this.setSelectedObject(undefined, false, false)
}
@@ -218,7 +223,7 @@ export class PickingPlugin extends AViewerPluginSync<PickingPluginEventMap> {

private _onObjectSelectEvent: EventListener2<'select', ISceneEventMap, IScene> = (e)=>{
if (e.source === PickingPlugin.PluginType) return
if (e.object === undefined && e.value === undefined) console.error('e.object or e.value must be set for picking, can be null to unselect')
if (e.object === undefined && e.value === undefined) console.error('PickingPlugin - Error handling object/material `select` event `e.object` or `e.value` must be set for picking, `value` can be null to unselect')
else this.setSelectedObject(e.object || e.value, this.autoFocus || e.focusCamera, true)
}

@@ -240,38 +245,54 @@ export class PickingPlugin extends AViewerPluginSync<PickingPluginEventMap> {
const ui = this.uiConfig
ui.children = [...this._uiConfigChildren]
if (selected) {
ui.children.push(
{
type: 'button',
label: 'Focus',
value: ()=>{
// const selected = this.getSelectedObject()
if (selected.assetType && selected.parentRoot) // todo also check if acceptChildEvents is set on some parent?
selected.dispatchEvent({type: 'select', ui: true, object: selected, bubbleToParent: true, focusCamera: true})
else
this.setSelectedObject(selected, true)
},
},
{
type: 'button',
label: 'Select Parent',
hidden: ()=>!selected.parent,
value: ()=>{
const parent = selected.parent
if (parent) {
if (parent.assetType && parent.parentRoot) // todo also check if acceptChildEvents is set on some parent?
parent.dispatchEvent({type: 'select', ui: true, bubbleToParent: true, object: parent})
if ((selected as IObject3D).isObject3D) {
const obj = (selected as IObject3D)
ui.children.push(
{
type: 'button',
label: 'Focus',
value: () => {
if (!obj.isObject3D) return
// const selected = this.getSelectedObject()
if (selected.assetType && obj.parentRoot) // todo also check if acceptChildEvents is set on some parent?
obj.dispatchEvent({
type: 'select',
ui: true,
object: obj,
bubbleToParent: true,
focusCamera: true,
})
else
this.setSelectedObject(parent, false)
}
this.setSelectedObject(obj, true)
},
},
},
)
{
type: 'button',
label: 'Select Parent',
hidden: () => !obj.parent,
value: () => {
if (!obj.isObject3D) return
const parent = obj.parent
if (parent) {
if (parent.assetType && parent.parentRoot) // todo also check if acceptChildEvents is set on some parent?
parent.dispatchEvent({
type: 'select',
ui: true,
bubbleToParent: true,
object: parent,
})
else
this.setSelectedObject(parent, false)
}
},
},
)
}
let c = selected.uiConfig
if (c) ui.children.push(c)
else {
// check materials
const mats = selected.materials ?? [selected.material as IMaterial]
const mats = (selected as IObject3D).materials ?? [(selected as IObject3D).material as IMaterial]
for (const m of mats) {
c = m?.uiConfig
if (c) ui.children.push(c)
@@ -284,7 +305,7 @@ export class PickingPlugin extends AViewerPluginSync<PickingPluginEventMap> {

const widget = this._widget
if (widget && this.widgetEnabled) {
if (selected) widget.attach(selected)
if ((selected as IObject3D)?.isObject3D) widget.attach((selected as IObject3D))
else widget.detach()
}

@@ -292,20 +313,21 @@ export class PickingPlugin extends AViewerPluginSync<PickingPluginEventMap> {

this._viewer.setDirty()

if (this.autoFocus) {
if (this.autoFocus && this.selectionMode === 'object') {
// this._viewer.resetCamera({rootObject: selected, centerOffset: new Vector3(4, 4, 4)})
this.focusObject(selected)
this.focusObject(selected as IObject3D | undefined)
}

}

private _hoverObjectChanged = (e: any) => {
if (!this._viewer) return
this.dispatchEvent(e)
const selected = this._picker?.hoverObject || undefined

const widget = this._hoverWidget
if (widget && this.widgetEnabled) {
if (selected) widget.attach(selected)
if ((selected as IObject3D)?.isObject3D) widget.attach((selected as IObject3D))
else widget.detach()
}

@@ -313,9 +335,9 @@ export class PickingPlugin extends AViewerPluginSync<PickingPluginEventMap> {

this._viewer?.setDirty()

if (this.autoFocusHover) {
if (this.autoFocusHover && this.selectionMode === 'object') {
// this._viewer?.resetCamera({rootObject: selected, centerOffset: new Vector3(4, 4, 4)})
this.focusObject(selected)
this.focusObject(selected as IObject3D | undefined)
}


@@ -329,9 +351,15 @@ export class PickingPlugin extends AViewerPluginSync<PickingPluginEventMap> {
}
this.dispatchEvent(e)
}
private _selectionModeChanged = (e: any)=>{
if (!this._viewer) return
this.dispatchEvent(e)
if (this.isDisabled()) return
this.uiConfig?.uiRefresh?.(true, 'postFrame', 1)
}

public async focusObject(selected?: Object3D) {
this._viewer?.fitToView(selected, 1.25, 1000, 'easeOut')
public async focusObject(selected?: Object3D|null) {
this._viewer?.fitToView(selected ?? undefined, 1.25, 1000, 'easeOut')
}

private _uiConfigChildren: UiObjectConfig[] = [
@@ -346,6 +374,12 @@ export class PickingPlugin extends AViewerPluginSync<PickingPluginEventMap> {
property: [this, 'hoverEnabled'],
onChange: ()=>this.uiConfig.uiRefresh?.(true), // for autoFocusHover
},
// {
// label: 'Selection Mode',
// type: 'dropdown',
// children: ['object', 'material'].map(v=>({label: v, value: v})),
// onChange: ()=>this.uiConfig.uiRefresh?.(true),
// },
{
label: 'Auto Focus',
type: 'checkbox',

+ 2
- 2
src/plugins/interaction/TransformControlsPlugin.ts Parādīt failu

@@ -4,7 +4,7 @@ import {OrbitControls3, TransformControls2} from '../../three'
import {PickingPlugin} from './PickingPlugin'
import {JSUndoManager, onChange} from 'ts-browser-helpers'
import {TransformControls} from '../../three/controls/TransformControls'
import {UnlitLineMaterial, UnlitMaterial} from '../../core'
import {IObject3D, UnlitLineMaterial, UnlitMaterial} from '../../core'
import {Euler, Object3D, Vector3} from 'three'
import type {UndoManagerPlugin} from './UndoManagerPlugin'

@@ -29,7 +29,7 @@ export class TransformControlsPlugin extends AViewerPluginSync {
this._pickingWidgetDisabled = false
}
if (this.transformControls) {
if (enabled && picking.getSelectedObject()) this.transformControls.attach(picking.getSelectedObject()!)
if (enabled && picking.getSelectedObject<IObject3D>()?.isObject3D) this.transformControls.attach(picking.getSelectedObject<IObject3D>()!)
else this.transformControls.detach()
}
this._viewer.setDirty()

+ 46
- 18
src/three/utils/ObjectPicker.ts Parādīt failu

@@ -1,17 +1,21 @@
import {EventDispatcher, Intersection, Raycaster, Vector2} from 'three'
import {JSUndoManager, now} from 'ts-browser-helpers'
import {ICamera, IObject3D} from '../../core'
import {JSUndoManager, now, onChangeDispatchEvent} from 'ts-browser-helpers'
import {ICamera, IMaterial, IObject3D} from '../../core'

export interface ObjectPickerEventMap{
hoverObjectChanged: {object: IObject3D | null}
selectedObjectChanged: {object: IObject3D | null}
hoverObjectChanged: {object: IObject3D | null, material: IMaterial | null, value: IObject3D | IMaterial | null},
selectedObjectChanged: {object: IObject3D | null, material: IMaterial | null, value: IObject3D | IMaterial | null},
hitObject: {time: number, intersects: {selectedObject: IObject3D | null, intersect: Intersection<IObject3D> | null, intersects: Intersection<IObject3D>[]}}
selectionModeChanged: {detail: {key: 'selectionMode', value: 'object' | 'material', oldValue: 'object' | 'material'}}
}

export class ObjectPicker extends EventDispatcher<ObjectPickerEventMap> {
private _firstHit: IObject3D | undefined

hoverEnabled = false
@onChangeDispatchEvent('selectionModeChanged')
selectionMode: 'object' | 'material' = 'object'

/**
* Time threshold for a pointer click event
*/
@@ -31,8 +35,8 @@ export class ObjectPicker extends EventDispatcher<ObjectPickerEventMap> {
public selectionCondition: (o: IObject3D) => boolean
public raycaster: Raycaster
public mouse: Vector2
private _selected: IObject3D[]
private _hovering: IObject3D[]
private _selected: IObject3D[] | IMaterial[]
private _hovering: IObject3D[] | IMaterial[]
public cursorStyles: {default: string; down: string}
public domElement: HTMLElement
constructor(root: IObject3D, domElement: HTMLElement, camera: ICamera, selectionCondition?: (o:IObject3D)=>boolean) {
@@ -74,7 +78,7 @@ export class ObjectPicker extends EventDispatcher<ObjectPickerEventMap> {
}

dispose() {
this.selectedObject = null
this.setSelected(null)
this.hoverObject = null

this.domElement.removeEventListener('pointermove', this._onPointerMove)
@@ -94,33 +98,52 @@ export class ObjectPicker extends EventDispatcher<ObjectPickerEventMap> {
this._camera = value
}

get selectedObject(): IObject3D | null {
get selectedObject(): IObject3D | IMaterial | null {
return this._selected.length > 0 ? this._selected[0] : null
}

set selectedObject(object) {
this.setSelected(object)
}
// set selectedObject(object) {
// this.setSelected(object)
// }

setSelected(object: IObject3D|null, record = true) {
setSelected(object: IObject3D | IMaterial | null, record = true) {
if ((object as IObject3D)?.isObject3D && this.selectionMode === 'material' ||
(object as IMaterial)?.isMaterial && this.selectionMode === 'object') {
this.selectionMode = (object as IMaterial)?.isMaterial ? 'material' : 'object'
}
if (!this._selected.length && !object || this._selected.length === 1 && this._selected[0] === object) return
const current = [...this._selected]
this._selected = object ? Array.isArray(object) ? [...object] : [object] : []
this.dispatchEvent({type: 'selectedObjectChanged', object: this.selectedObject})

const obj = this.selectedObject
this.dispatchEvent({
type: 'selectedObjectChanged',
object: (obj as IObject3D)?.isObject3D ? (obj as IObject3D) : null,
material: (obj as IMaterial)?.isMaterial ? (obj as IMaterial) : null,
value: obj,
})

record && this.undoManager?.record({
undo: () => this.setSelected(current.length ? current[0] : null, false),
redo: () => this.setSelected(object, false),
})
}

get hoverObject(): IObject3D | null {
get hoverObject(): IObject3D | IMaterial | null {
return this._hovering.length > 0 ? this._hovering[0] : null
}

set hoverObject(object: IObject3D | IObject3D[] | null) {
set hoverObject(object: IObject3D | IObject3D[] | IMaterial | IMaterial[] | null) {
if (!this._hovering.length && !object || this._hovering.length === 1 && this._hovering[0] === object) return
this._hovering = object ? Array.isArray(object) ? [...object] : [object] : []
this.dispatchEvent({type: 'hoverObjectChanged', object: this.hoverObject})
this._hovering = (object ? Array.isArray(object) ? [...object] : [object] : []) as (IObject3D[] | IMaterial[])

const obj = this.hoverObject
this.dispatchEvent({
type: 'hoverObjectChanged',
object: (obj as IObject3D)?.isObject3D ? (obj as IObject3D) : null,
material: (obj as IMaterial)?.isMaterial ? (obj as IMaterial) : null,
value: obj,
})
}

get time() {
@@ -202,7 +225,12 @@ export class ObjectPicker extends EventDispatcher<ObjectPickerEventMap> {
const intersects = this.checkIntersection()
if (intersects) this.dispatchEvent({type: 'hitObject', time: this._mouseUpTime, intersects})
else this.dispatchEvent({type: 'hitObject', time: this._mouseUpTime, intersects: {selectedObject: null, intersect: null, intersects: []}})
this.selectedObject = intersects?.selectedObject || null

let obj: IObject3D|IMaterial|null = intersects?.selectedObject || null
if (this.selectionMode === 'material' && obj && obj.material) {
obj = Array.isArray(obj.material) ? obj.material[0] : obj.material
}
this.setSelected(obj)
}

checkIntersection() {

Notiek ielāde…
Atcelt
Saglabāt