feat: support open 3d viewer in media asset panel (#6703)

## Summary

Add support for previewing 3D assets directly in the Media Asset Panel.

## Changes

- **3D Asset Preview**: Clicking on 3D assets (`.glb`, `.gltf`, etc.) in
the Media Asset Panel now opens the full
  3D viewer

## Screenshots



https://github.com/user-attachments/assets/38808712-acc8-42aa-9f11-8d8bf2387b20

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6703-feat-support-open-3d-viewer-in-media-asset-panel-2ab6d73d3650811dbff9ecb570a0a878)
by [Unito](https://www.unito.io)
This commit is contained in:
Terry Jia
2025-11-14 14:31:58 -05:00
committed by GitHub
parent b23a92b442
commit a9f416233d
4 changed files with 114 additions and 11 deletions

View File

@@ -27,7 +27,11 @@ interface Load3dViewerState {
materialMode: MaterialMode
}
export const useLoad3dViewer = (node: LGraphNode) => {
/**
* @param node Optional node - if provided, viewer works in node mode with apply/restore
* If not provided, viewer works in standalone mode for asset preview
*/
export const useLoad3dViewer = (node?: LGraphNode) => {
const backgroundColor = ref('')
const showGrid = ref(true)
const cameraType = ref<CameraType>('perspective')
@@ -40,6 +44,7 @@ export const useLoad3dViewer = (node: LGraphNode) => {
const materialMode = ref<MaterialMode>('original')
const needApplyChanges = ref(true)
const isPreview = ref(false)
const isStandaloneMode = ref(false)
let load3d: Load3d | null = null
let sourceLoad3d: Load3d | null = null
@@ -166,11 +171,14 @@ export const useLoad3dViewer = (node: LGraphNode) => {
}
})
/**
* Initialize viewer in node mode (with source Load3d)
*/
const initializeViewer = async (
containerRef: HTMLElement,
source: Load3d
) => {
if (!containerRef) return
if (!containerRef || !node) return
sourceLoad3d = source
@@ -263,6 +271,52 @@ export const useLoad3dViewer = (node: LGraphNode) => {
}
}
/**
* Initialize viewer in standalone mode (for asset preview)
*/
const initializeStandaloneViewer = async (
containerRef: HTMLElement,
modelUrl: string
) => {
if (!containerRef) return
try {
isStandaloneMode.value = true
const mockNode = {
widgets: [
{ name: 'width', value: 800 },
{ name: 'height', value: 600 }
],
properties: {},
graph: null,
type: 'AssetPreview'
} as unknown as LGraphNode
load3d = new Load3d(containerRef, {
node: mockNode,
disablePreview: true,
isViewerMode: true
})
await load3d.loadModel(modelUrl)
backgroundColor.value = '#282828'
showGrid.value = true
cameraType.value = 'perspective'
fov.value = 75
lightIntensity.value = 1
backgroundRenderMode.value = 'tiled'
upDirection.value = 'original'
materialMode.value = 'original'
isPreview.value = true
} catch (error) {
console.error('Error initializing standalone 3D viewer:', error)
useToastStore().addAlert('Failed to load 3D model')
}
}
const exportModel = async (format: string) => {
if (!load3d) return
@@ -289,6 +343,8 @@ export const useLoad3dViewer = (node: LGraphNode) => {
}
const restoreInitialState = () => {
if (!node) return
const nodeValue = node
needApplyChanges.value = false
@@ -324,7 +380,7 @@ export const useLoad3dViewer = (node: LGraphNode) => {
}
const applyChanges = async () => {
if (!sourceLoad3d || !load3d) return false
if (!node || !sourceLoad3d || !load3d) return false
const viewerCameraState = load3d.getCameraState()
const nodeValue = node
@@ -378,6 +434,10 @@ export const useLoad3dViewer = (node: LGraphNode) => {
return
}
if (!node) {
return
}
try {
const resourceFolder =
(node.properties['Resource Folder'] as string) || ''
@@ -403,6 +463,10 @@ export const useLoad3dViewer = (node: LGraphNode) => {
return
}
if (!node) {
return
}
try {
const resourceFolder =
(node.properties['Resource Folder'] as string) || ''
@@ -460,9 +524,11 @@ export const useLoad3dViewer = (node: LGraphNode) => {
materialMode,
needApplyChanges,
isPreview,
isStandaloneMode,
// Methods
initializeViewer,
initializeStandaloneViewer,
exportModel,
handleResize,
handleMouseEnter,