mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-30 11:11:53 +00:00
[refactor] remove node as dependency in 3d node (#6707)
## Summary This PR refactors the Load3d 3D rendering system to remove its direct dependency on LGraphNode, making it a more decoupled and reusable component. The core rendering engine is now framework-agnostic and can be used in any context, not just within LiteGraph nodes. ## Changes 1. Decoupled Load3d from LGraphNode - Before: Load3d directly accessed node.widgets and node.properties - After: Load3d accepts optional parameters and callbacks, delegating node integration to the calling code 2. Event-Driven State Management - Removed internal storage from Load3d core components - Camera, controls, and view helper managers now emit cameraChanged events instead of directly storing state - External code (e.g., useLoad3d) listens to events and handles persistence to node.properties 3. Reactive Dimension Updates - Introduced getDimensions callback to support reactive dimension updates - Fixes the issue where dimension changes in vueNodes mode required a refresh - The callback is invoked on every render to get fresh width/height values 4. Improved Configuration System - Load3DConfiguration now accepts properties: Dictionary<NodeProperty | undefined> instead of custom storage interface - Uses official LiteGraph type definitions (Dictionary, NodeProperty) - More semantic parameter naming: storage → properties ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-6707-refactor-remove-node-as-dependency-in-3d-node-2ab6d73d365081ffac1cdce354781ce8) by [Unito](https://www.unito.io)
This commit is contained in:
@@ -7,6 +7,7 @@ import Load3dUtils from '@/extensions/core/load3d/Load3dUtils'
|
|||||||
import type {
|
import type {
|
||||||
AnimationItem,
|
AnimationItem,
|
||||||
CameraConfig,
|
CameraConfig,
|
||||||
|
CameraState,
|
||||||
CameraType,
|
CameraType,
|
||||||
LightConfig,
|
LightConfig,
|
||||||
MaterialMode,
|
MaterialMode,
|
||||||
@@ -16,8 +17,10 @@ import type {
|
|||||||
} from '@/extensions/core/load3d/interfaces'
|
} from '@/extensions/core/load3d/interfaces'
|
||||||
import { t } from '@/i18n'
|
import { t } from '@/i18n'
|
||||||
import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode'
|
import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode'
|
||||||
|
import { LiteGraph } from '@/lib/litegraph/src/litegraph'
|
||||||
import { useToastStore } from '@/platform/updates/common/toastStore'
|
import { useToastStore } from '@/platform/updates/common/toastStore'
|
||||||
import { api } from '@/scripts/api'
|
import { api } from '@/scripts/api'
|
||||||
|
import { app } from '@/scripts/app'
|
||||||
import { useLoad3dService } from '@/services/load3dService'
|
import { useLoad3dService } from '@/services/load3dService'
|
||||||
|
|
||||||
type Load3dReadyCallback = (load3d: Load3d) => void
|
type Load3dReadyCallback = (load3d: Load3d) => void
|
||||||
@@ -68,10 +71,6 @@ export const useLoad3d = (nodeOrRef: MaybeRef<LGraphNode | null>) => {
|
|||||||
const node = rawNode as LGraphNode
|
const node = rawNode as LGraphNode
|
||||||
|
|
||||||
try {
|
try {
|
||||||
load3d = new Load3d(containerRef, {
|
|
||||||
node
|
|
||||||
})
|
|
||||||
|
|
||||||
const widthWidget = node.widgets?.find((w) => w.name === 'width')
|
const widthWidget = node.widgets?.find((w) => w.name === 'width')
|
||||||
const heightWidget = node.widgets?.find((w) => w.name === 'height')
|
const heightWidget = node.widgets?.find((w) => w.name === 'height')
|
||||||
|
|
||||||
@@ -79,6 +78,27 @@ export const useLoad3d = (nodeOrRef: MaybeRef<LGraphNode | null>) => {
|
|||||||
isPreview.value = true
|
isPreview.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
load3d = new Load3d(containerRef, {
|
||||||
|
width: widthWidget?.value as number | undefined,
|
||||||
|
height: heightWidget?.value as number | undefined,
|
||||||
|
// Provide dynamic dimension getter for reactive updates
|
||||||
|
getDimensions:
|
||||||
|
widthWidget && heightWidget
|
||||||
|
? () => ({
|
||||||
|
width: widthWidget.value as number,
|
||||||
|
height: heightWidget.value as number
|
||||||
|
})
|
||||||
|
: undefined,
|
||||||
|
onContextMenu: (event) => {
|
||||||
|
const menuOptions = app.canvas.getNodeMenuOptions(node)
|
||||||
|
new LiteGraph.ContextMenu(menuOptions, {
|
||||||
|
event,
|
||||||
|
title: node.type,
|
||||||
|
extra: node
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
await restoreConfigurationsFromNode(node)
|
await restoreConfigurationsFromNode(node)
|
||||||
|
|
||||||
node.onMouseEnter = function () {
|
node.onMouseEnter = function () {
|
||||||
@@ -487,8 +507,26 @@ export const useLoad3d = (nodeOrRef: MaybeRef<LGraphNode | null>) => {
|
|||||||
hasRecording.value = recordingDuration.value > 0
|
hasRecording.value = recordingDuration.value > 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
animationListChange: (newValue: any) => {
|
animationListChange: (newValue: AnimationItem[]) => {
|
||||||
animations.value = newValue
|
animations.value = newValue
|
||||||
|
},
|
||||||
|
cameraChanged: (cameraState: CameraState) => {
|
||||||
|
const rawNode = toRaw(nodeRef.value)
|
||||||
|
if (rawNode) {
|
||||||
|
const node = rawNode as LGraphNode
|
||||||
|
if (!node.properties) node.properties = {}
|
||||||
|
const cameraConfigProp = node.properties['Camera Config']
|
||||||
|
|
||||||
|
if (cameraConfigProp) {
|
||||||
|
;(cameraConfigProp as CameraConfig).state = cameraState
|
||||||
|
} else {
|
||||||
|
node.properties['Camera Config'] = {
|
||||||
|
cameraType: cameraConfig.value.cameraType,
|
||||||
|
fov: cameraConfig.value.fov,
|
||||||
|
state: cameraState
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import Load3d from '@/extensions/core/load3d/Load3d'
|
|||||||
import Load3dUtils from '@/extensions/core/load3d/Load3dUtils'
|
import Load3dUtils from '@/extensions/core/load3d/Load3dUtils'
|
||||||
import type {
|
import type {
|
||||||
BackgroundRenderModeType,
|
BackgroundRenderModeType,
|
||||||
|
CameraState,
|
||||||
CameraType,
|
CameraType,
|
||||||
MaterialMode,
|
MaterialMode,
|
||||||
UpDirection
|
UpDirection
|
||||||
@@ -20,7 +21,7 @@ interface Load3dViewerState {
|
|||||||
cameraType: CameraType
|
cameraType: CameraType
|
||||||
fov: number
|
fov: number
|
||||||
lightIntensity: number
|
lightIntensity: number
|
||||||
cameraState: any
|
cameraState: CameraState | null
|
||||||
backgroundImage: string
|
backgroundImage: string
|
||||||
backgroundRenderMode: BackgroundRenderModeType
|
backgroundRenderMode: BackgroundRenderModeType
|
||||||
upDirection: UpDirection
|
upDirection: UpDirection
|
||||||
@@ -183,9 +184,19 @@ export const useLoad3dViewer = (node?: LGraphNode) => {
|
|||||||
sourceLoad3d = source
|
sourceLoad3d = source
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const width = node.widgets?.find((w) => w.name === 'width')
|
||||||
|
const height = node.widgets?.find((w) => w.name === 'height')
|
||||||
|
|
||||||
load3d = new Load3d(containerRef, {
|
load3d = new Load3d(containerRef, {
|
||||||
node: node,
|
width: width ? (toRaw(width).value as number) : undefined,
|
||||||
disablePreview: true,
|
height: height ? (toRaw(height).value as number) : undefined,
|
||||||
|
getDimensions:
|
||||||
|
width && height
|
||||||
|
? () => ({
|
||||||
|
width: width.value as number,
|
||||||
|
height: height.value as number
|
||||||
|
})
|
||||||
|
: undefined,
|
||||||
isViewerMode: true
|
isViewerMode: true
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -253,16 +264,6 @@ export const useLoad3dViewer = (node?: LGraphNode) => {
|
|||||||
upDirection: upDirection.value,
|
upDirection: upDirection.value,
|
||||||
materialMode: materialMode.value
|
materialMode: materialMode.value
|
||||||
}
|
}
|
||||||
|
|
||||||
const width = node.widgets?.find((w) => w.name === 'width')
|
|
||||||
const height = node.widgets?.find((w) => w.name === 'height')
|
|
||||||
|
|
||||||
if (width && height) {
|
|
||||||
load3d.setTargetSize(
|
|
||||||
toRaw(width).value as number,
|
|
||||||
toRaw(height).value as number
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error initializing Load3d viewer:', error)
|
console.error('Error initializing Load3d viewer:', error)
|
||||||
useToastStore().addAlert(
|
useToastStore().addAlert(
|
||||||
@@ -283,19 +284,9 @@ export const useLoad3dViewer = (node?: LGraphNode) => {
|
|||||||
try {
|
try {
|
||||||
isStandaloneMode.value = true
|
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, {
|
load3d = new Load3d(containerRef, {
|
||||||
node: mockNode,
|
width: 800,
|
||||||
disablePreview: true,
|
height: 600,
|
||||||
isViewerMode: true
|
isViewerMode: true
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -317,7 +317,7 @@ useExtensionService().registerExtension({
|
|||||||
const cameraConfig = node.properties['Camera Config'] as any
|
const cameraConfig = node.properties['Camera Config'] as any
|
||||||
const cameraState = cameraConfig?.state
|
const cameraState = cameraConfig?.state
|
||||||
|
|
||||||
const config = new Load3DConfiguration(load3d)
|
const config = new Load3DConfiguration(load3d, node.properties)
|
||||||
|
|
||||||
const modelWidget = node.widgets?.find((w) => w.name === 'model_file')
|
const modelWidget = node.widgets?.find((w) => w.name === 'model_file')
|
||||||
const width = node.widgets?.find((w) => w.name === 'width')
|
const width = node.widgets?.find((w) => w.name === 'width')
|
||||||
@@ -444,7 +444,7 @@ useExtensionService().registerExtension({
|
|||||||
const onExecuted = node.onExecuted
|
const onExecuted = node.onExecuted
|
||||||
|
|
||||||
useLoad3d(node).waitForLoad3d((load3d) => {
|
useLoad3d(node).waitForLoad3d((load3d) => {
|
||||||
const config = new Load3DConfiguration(load3d)
|
const config = new Load3DConfiguration(load3d, node.properties)
|
||||||
|
|
||||||
const modelWidget = node.widgets?.find((w) => w.name === 'model_file')
|
const modelWidget = node.widgets?.find((w) => w.name === 'model_file')
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,7 @@ import {
|
|||||||
type CameraManagerInterface,
|
type CameraManagerInterface,
|
||||||
type CameraState,
|
type CameraState,
|
||||||
type CameraType,
|
type CameraType,
|
||||||
type EventManagerInterface,
|
type EventManagerInterface
|
||||||
type NodeStorageInterface
|
|
||||||
} from './interfaces'
|
} from './interfaces'
|
||||||
|
|
||||||
export class CameraManager implements CameraManagerInterface {
|
export class CameraManager implements CameraManagerInterface {
|
||||||
@@ -17,7 +16,6 @@ export class CameraManager implements CameraManagerInterface {
|
|||||||
// @ts-expect-error unused variable
|
// @ts-expect-error unused variable
|
||||||
private renderer: THREE.WebGLRenderer
|
private renderer: THREE.WebGLRenderer
|
||||||
private eventManager: EventManagerInterface
|
private eventManager: EventManagerInterface
|
||||||
private nodeStorage: NodeStorageInterface
|
|
||||||
|
|
||||||
private controls: OrbitControls | null = null
|
private controls: OrbitControls | null = null
|
||||||
|
|
||||||
@@ -45,12 +43,10 @@ export class CameraManager implements CameraManagerInterface {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
renderer: THREE.WebGLRenderer,
|
renderer: THREE.WebGLRenderer,
|
||||||
eventManager: EventManagerInterface,
|
eventManager: EventManagerInterface
|
||||||
nodeStorage: NodeStorageInterface
|
|
||||||
) {
|
) {
|
||||||
this.renderer = renderer
|
this.renderer = renderer
|
||||||
this.eventManager = eventManager
|
this.eventManager = eventManager
|
||||||
this.nodeStorage = nodeStorage
|
|
||||||
|
|
||||||
this.perspectiveCamera = new THREE.PerspectiveCamera(
|
this.perspectiveCamera = new THREE.PerspectiveCamera(
|
||||||
this.DEFAULT_PERSPECTIVE_CAMERA.fov,
|
this.DEFAULT_PERSPECTIVE_CAMERA.fov,
|
||||||
@@ -82,17 +78,7 @@ export class CameraManager implements CameraManagerInterface {
|
|||||||
|
|
||||||
if (this.controls) {
|
if (this.controls) {
|
||||||
this.controls.addEventListener('end', () => {
|
this.controls.addEventListener('end', () => {
|
||||||
const cameraState = this.getCameraState()
|
this.eventManager.emitEvent('cameraChanged', this.getCameraState())
|
||||||
|
|
||||||
const cameraConfig = this.nodeStorage.loadNodeProperty(
|
|
||||||
'Camera Config',
|
|
||||||
{
|
|
||||||
cameraType: this.getCurrentCameraType(),
|
|
||||||
fov: this.perspectiveCamera.fov
|
|
||||||
}
|
|
||||||
)
|
|
||||||
cameraConfig.state = cameraState
|
|
||||||
this.nodeStorage.storeNodeProperty('Camera Config', cameraConfig)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,25 +3,20 @@ import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
type ControlsManagerInterface,
|
type ControlsManagerInterface,
|
||||||
type EventManagerInterface,
|
type EventManagerInterface
|
||||||
type NodeStorageInterface
|
|
||||||
} from './interfaces'
|
} from './interfaces'
|
||||||
|
|
||||||
export class ControlsManager implements ControlsManagerInterface {
|
export class ControlsManager implements ControlsManagerInterface {
|
||||||
controls: OrbitControls
|
controls: OrbitControls
|
||||||
// @ts-expect-error unused variable
|
|
||||||
private eventManager: EventManagerInterface
|
private eventManager: EventManagerInterface
|
||||||
private nodeStorage: NodeStorageInterface
|
|
||||||
private camera: THREE.Camera
|
private camera: THREE.Camera
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
renderer: THREE.WebGLRenderer,
|
renderer: THREE.WebGLRenderer,
|
||||||
camera: THREE.Camera,
|
camera: THREE.Camera,
|
||||||
eventManager: EventManagerInterface,
|
eventManager: EventManagerInterface
|
||||||
nodeStorage: NodeStorageInterface
|
|
||||||
) {
|
) {
|
||||||
this.eventManager = eventManager
|
this.eventManager = eventManager
|
||||||
this.nodeStorage = nodeStorage
|
|
||||||
this.camera = camera
|
this.camera = camera
|
||||||
|
|
||||||
const container = renderer.domElement.parentElement || renderer.domElement
|
const container = renderer.domElement.parentElement || renderer.domElement
|
||||||
@@ -44,15 +39,7 @@ export class ControlsManager implements ControlsManagerInterface {
|
|||||||
: 'orthographic'
|
: 'orthographic'
|
||||||
}
|
}
|
||||||
|
|
||||||
const cameraConfig = this.nodeStorage.loadNodeProperty('Camera Config', {
|
this.eventManager.emitEvent('cameraChanged', cameraState)
|
||||||
cameraType: cameraState.cameraType,
|
|
||||||
fov:
|
|
||||||
this.camera instanceof THREE.PerspectiveCamera
|
|
||||||
? (this.camera as THREE.PerspectiveCamera).fov
|
|
||||||
: 75
|
|
||||||
})
|
|
||||||
cameraConfig.state = cameraState
|
|
||||||
this.nodeStorage.storeNodeProperty('Camera Config', cameraConfig)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,13 @@ import Load3d from '@/extensions/core/load3d/Load3d'
|
|||||||
import Load3dUtils from '@/extensions/core/load3d/Load3dUtils'
|
import Load3dUtils from '@/extensions/core/load3d/Load3dUtils'
|
||||||
import type {
|
import type {
|
||||||
CameraConfig,
|
CameraConfig,
|
||||||
|
CameraState,
|
||||||
LightConfig,
|
LightConfig,
|
||||||
ModelConfig,
|
ModelConfig,
|
||||||
SceneConfig
|
SceneConfig
|
||||||
} from '@/extensions/core/load3d/interfaces'
|
} from '@/extensions/core/load3d/interfaces'
|
||||||
|
import type { Dictionary } from '@/lib/litegraph/src/interfaces'
|
||||||
|
import type { NodeProperty } from '@/lib/litegraph/src/LGraphNode'
|
||||||
import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets'
|
import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets'
|
||||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||||
import { api } from '@/scripts/api'
|
import { api } from '@/scripts/api'
|
||||||
@@ -13,14 +16,17 @@ import { api } from '@/scripts/api'
|
|||||||
type Load3DConfigurationSettings = {
|
type Load3DConfigurationSettings = {
|
||||||
loadFolder: string
|
loadFolder: string
|
||||||
modelWidget: IBaseWidget
|
modelWidget: IBaseWidget
|
||||||
cameraState?: any
|
cameraState?: CameraState
|
||||||
width?: IBaseWidget
|
width?: IBaseWidget
|
||||||
height?: IBaseWidget
|
height?: IBaseWidget
|
||||||
bgImagePath?: string
|
bgImagePath?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
class Load3DConfiguration {
|
class Load3DConfiguration {
|
||||||
constructor(private load3d: Load3d) {}
|
constructor(
|
||||||
|
private load3d: Load3d,
|
||||||
|
private properties?: Dictionary<NodeProperty | undefined>
|
||||||
|
) {}
|
||||||
|
|
||||||
configureForSaveMesh(loadFolder: 'input' | 'output', filePath: string) {
|
configureForSaveMesh(loadFolder: 'input' | 'output', filePath: string) {
|
||||||
this.setupModelHandlingForSaveMesh(filePath, loadFolder)
|
this.setupModelHandlingForSaveMesh(filePath, loadFolder)
|
||||||
@@ -62,7 +68,7 @@ class Load3DConfiguration {
|
|||||||
private setupModelHandling(
|
private setupModelHandling(
|
||||||
modelWidget: IBaseWidget,
|
modelWidget: IBaseWidget,
|
||||||
loadFolder: string,
|
loadFolder: string,
|
||||||
cameraState?: any
|
cameraState?: CameraState
|
||||||
) {
|
) {
|
||||||
const onModelWidgetUpdate = this.createModelUpdateHandler(
|
const onModelWidgetUpdate = this.createModelUpdateHandler(
|
||||||
loadFolder,
|
loadFolder,
|
||||||
@@ -110,48 +116,48 @@ class Load3DConfiguration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private loadSceneConfig(): SceneConfig {
|
private loadSceneConfig(): SceneConfig {
|
||||||
const defaultConfig: SceneConfig = {
|
if (this.properties && 'Scene Config' in this.properties) {
|
||||||
|
return this.properties['Scene Config'] as SceneConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
showGrid: useSettingStore().get('Comfy.Load3D.ShowGrid'),
|
showGrid: useSettingStore().get('Comfy.Load3D.ShowGrid'),
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
'#' + useSettingStore().get('Comfy.Load3D.BackgroundColor'),
|
'#' + useSettingStore().get('Comfy.Load3D.BackgroundColor'),
|
||||||
backgroundImage: ''
|
backgroundImage: ''
|
||||||
}
|
} as SceneConfig
|
||||||
|
|
||||||
const config = this.load3d.loadNodeProperty('Scene Config', defaultConfig)
|
|
||||||
this.load3d.node.properties['Scene Config'] = config
|
|
||||||
return config
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private loadCameraConfig(): CameraConfig {
|
private loadCameraConfig(): CameraConfig {
|
||||||
const defaultConfig: CameraConfig = {
|
if (this.properties && 'Camera Config' in this.properties) {
|
||||||
cameraType: useSettingStore().get('Comfy.Load3D.CameraType'),
|
return this.properties['Camera Config'] as CameraConfig
|
||||||
fov: 35
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const config = this.load3d.loadNodeProperty('Camera Config', defaultConfig)
|
return {
|
||||||
this.load3d.node.properties['Camera Config'] = config
|
cameraType: useSettingStore().get('Comfy.Load3D.CameraType'),
|
||||||
return config
|
fov: 35
|
||||||
|
} as CameraConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
private loadLightConfig(): LightConfig {
|
private loadLightConfig(): LightConfig {
|
||||||
const defaultConfig: LightConfig = {
|
if (this.properties && 'Light Config' in this.properties) {
|
||||||
intensity: useSettingStore().get('Comfy.Load3D.LightIntensity')
|
return this.properties['Light Config'] as LightConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
const config = this.load3d.loadNodeProperty('Light Config', defaultConfig)
|
return {
|
||||||
this.load3d.node.properties['Light Config'] = config
|
intensity: useSettingStore().get('Comfy.Load3D.LightIntensity')
|
||||||
return config
|
} as LightConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
private loadModelConfig(): ModelConfig {
|
private loadModelConfig(): ModelConfig {
|
||||||
const defaultConfig: ModelConfig = {
|
if (this.properties && 'Model Config' in this.properties) {
|
||||||
upDirection: 'original',
|
return this.properties['Model Config'] as ModelConfig
|
||||||
materialMode: 'original'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const config = this.load3d.loadNodeProperty('Model Config', defaultConfig)
|
return {
|
||||||
this.load3d.node.properties['Model Config'] = config
|
upDirection: 'original',
|
||||||
return config
|
materialMode: 'original'
|
||||||
|
} as ModelConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
private applySceneConfig(config: SceneConfig, bgImagePath?: string) {
|
private applySceneConfig(config: SceneConfig, bgImagePath?: string) {
|
||||||
@@ -188,7 +194,10 @@ class Load3DConfiguration {
|
|||||||
this.load3d.setMaterialMode(config.materialMode)
|
this.load3d.setMaterialMode(config.materialMode)
|
||||||
}
|
}
|
||||||
|
|
||||||
private createModelUpdateHandler(loadFolder: string, cameraState?: any) {
|
private createModelUpdateHandler(
|
||||||
|
loadFolder: string,
|
||||||
|
cameraState?: CameraState
|
||||||
|
) {
|
||||||
let isFirstLoad = true
|
let isFirstLoad = true
|
||||||
return async (value: string | number | boolean | object) => {
|
return async (value: string | number | boolean | object) => {
|
||||||
if (!value) return
|
if (!value) return
|
||||||
@@ -209,7 +218,7 @@ class Load3DConfiguration {
|
|||||||
const modelConfig = this.loadModelConfig()
|
const modelConfig = this.loadModelConfig()
|
||||||
this.applyModelConfig(modelConfig)
|
this.applyModelConfig(modelConfig)
|
||||||
|
|
||||||
if (isFirstLoad && cameraState && typeof cameraState === 'object') {
|
if (isFirstLoad && cameraState) {
|
||||||
try {
|
try {
|
||||||
this.load3d.setCameraState(cameraState)
|
this.load3d.setCameraState(cameraState)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -230,8 +239,8 @@ class Load3DConfiguration {
|
|||||||
const subfolderParts = pathParts.slice(1, -1)
|
const subfolderParts = pathParts.slice(1, -1)
|
||||||
const subfolder = subfolderParts.join('/')
|
const subfolder = subfolderParts.join('/')
|
||||||
|
|
||||||
if (subfolder) {
|
if (subfolder && this.properties) {
|
||||||
this.load3d.node.properties['Resource Folder'] = subfolder
|
this.properties['Resource Folder'] = subfolder
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import * as THREE from 'three'
|
import * as THREE from 'three'
|
||||||
|
|
||||||
import { LGraphNode, LiteGraph } from '@/lib/litegraph/src/litegraph'
|
|
||||||
|
|
||||||
import { AnimationManager } from './AnimationManager'
|
import { AnimationManager } from './AnimationManager'
|
||||||
import { CameraManager } from './CameraManager'
|
import { CameraManager } from './CameraManager'
|
||||||
import { ControlsManager } from './ControlsManager'
|
import { ControlsManager } from './ControlsManager'
|
||||||
@@ -9,7 +7,6 @@ import { EventManager } from './EventManager'
|
|||||||
import { LightingManager } from './LightingManager'
|
import { LightingManager } from './LightingManager'
|
||||||
import { LoaderManager } from './LoaderManager'
|
import { LoaderManager } from './LoaderManager'
|
||||||
import { ModelExporter } from './ModelExporter'
|
import { ModelExporter } from './ModelExporter'
|
||||||
import { NodeStorage } from './NodeStorage'
|
|
||||||
import { RecordingManager } from './RecordingManager'
|
import { RecordingManager } from './RecordingManager'
|
||||||
import { SceneManager } from './SceneManager'
|
import { SceneManager } from './SceneManager'
|
||||||
import { SceneModelManager } from './SceneModelManager'
|
import { SceneModelManager } from './SceneModelManager'
|
||||||
@@ -21,17 +18,16 @@ import {
|
|||||||
type MaterialMode,
|
type MaterialMode,
|
||||||
type UpDirection
|
type UpDirection
|
||||||
} from './interfaces'
|
} from './interfaces'
|
||||||
import { app } from '@/scripts/app'
|
|
||||||
|
|
||||||
class Load3d {
|
class Load3d {
|
||||||
renderer: THREE.WebGLRenderer
|
renderer: THREE.WebGLRenderer
|
||||||
protected clock: THREE.Clock
|
protected clock: THREE.Clock
|
||||||
protected animationFrameId: number | null = null
|
protected animationFrameId: number | null = null
|
||||||
node: LGraphNode
|
|
||||||
private loadingPromise: Promise<void> | null = null
|
private loadingPromise: Promise<void> | null = null
|
||||||
|
private onContextMenuCallback?: (event: MouseEvent) => void
|
||||||
|
private getDimensionsCallback?: () => { width: number; height: number } | null
|
||||||
|
|
||||||
eventManager: EventManager
|
eventManager: EventManager
|
||||||
nodeStorage: NodeStorage
|
|
||||||
sceneManager: SceneManager
|
sceneManager: SceneManager
|
||||||
cameraManager: CameraManager
|
cameraManager: CameraManager
|
||||||
controlsManager: ControlsManager
|
controlsManager: ControlsManager
|
||||||
@@ -59,23 +55,16 @@ class Load3d {
|
|||||||
private readonly dragThreshold: number = 5
|
private readonly dragThreshold: number = 5
|
||||||
private contextMenuAbortController: AbortController | null = null
|
private contextMenuAbortController: AbortController | null = null
|
||||||
|
|
||||||
constructor(
|
constructor(container: Element | HTMLElement, options: Load3DOptions = {}) {
|
||||||
container: Element | HTMLElement,
|
|
||||||
options: Load3DOptions = {
|
|
||||||
node: {} as LGraphNode
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
this.node = options.node || ({} as LGraphNode)
|
|
||||||
this.clock = new THREE.Clock()
|
this.clock = new THREE.Clock()
|
||||||
this.isViewerMode = options.isViewerMode || false
|
this.isViewerMode = options.isViewerMode || false
|
||||||
|
this.onContextMenuCallback = options.onContextMenu
|
||||||
|
this.getDimensionsCallback = options.getDimensions
|
||||||
|
|
||||||
const widthWidget = this.node.widgets?.find((w) => w.name === 'width')
|
if (options.width && options.height) {
|
||||||
const heightWidget = this.node.widgets?.find((w) => w.name === 'height')
|
this.targetWidth = options.width
|
||||||
|
this.targetHeight = options.height
|
||||||
if (widthWidget && heightWidget) {
|
this.targetAspectRatio = options.width / options.height
|
||||||
this.targetWidth = widthWidget.value as number
|
|
||||||
this.targetHeight = heightWidget.value as number
|
|
||||||
this.targetAspectRatio = this.targetWidth / this.targetHeight
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true })
|
this.renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true })
|
||||||
@@ -87,7 +76,6 @@ class Load3d {
|
|||||||
container.appendChild(this.renderer.domElement)
|
container.appendChild(this.renderer.domElement)
|
||||||
|
|
||||||
this.eventManager = new EventManager()
|
this.eventManager = new EventManager()
|
||||||
this.nodeStorage = new NodeStorage(this.node)
|
|
||||||
|
|
||||||
this.sceneManager = new SceneManager(
|
this.sceneManager = new SceneManager(
|
||||||
this.renderer,
|
this.renderer,
|
||||||
@@ -96,17 +84,12 @@ class Load3d {
|
|||||||
this.eventManager
|
this.eventManager
|
||||||
)
|
)
|
||||||
|
|
||||||
this.cameraManager = new CameraManager(
|
this.cameraManager = new CameraManager(this.renderer, this.eventManager)
|
||||||
this.renderer,
|
|
||||||
this.eventManager,
|
|
||||||
this.nodeStorage
|
|
||||||
)
|
|
||||||
|
|
||||||
this.controlsManager = new ControlsManager(
|
this.controlsManager = new ControlsManager(
|
||||||
this.renderer,
|
this.renderer,
|
||||||
this.cameraManager.activeCamera,
|
this.cameraManager.activeCamera,
|
||||||
this.eventManager,
|
this.eventManager
|
||||||
this.nodeStorage
|
|
||||||
)
|
)
|
||||||
|
|
||||||
this.cameraManager.setControls(this.controlsManager.controls)
|
this.cameraManager.setControls(this.controlsManager.controls)
|
||||||
@@ -120,7 +103,7 @@ class Load3d {
|
|||||||
this.renderer,
|
this.renderer,
|
||||||
this.getActiveCamera.bind(this),
|
this.getActiveCamera.bind(this),
|
||||||
this.getControls.bind(this),
|
this.getControls.bind(this),
|
||||||
this.nodeStorage
|
this.eventManager
|
||||||
)
|
)
|
||||||
|
|
||||||
this.modelManager = new SceneModelManager(
|
this.modelManager = new SceneModelManager(
|
||||||
@@ -221,13 +204,9 @@ class Load3d {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private showNodeContextMenu(event: MouseEvent): void {
|
private showNodeContextMenu(event: MouseEvent): void {
|
||||||
const menuOptions = app.canvas.getNodeMenuOptions(this.node)
|
if (this.onContextMenuCallback) {
|
||||||
|
this.onContextMenuCallback(event)
|
||||||
new LiteGraph.ContextMenu(menuOptions, {
|
}
|
||||||
event,
|
|
||||||
title: this.node.type,
|
|
||||||
extra: this.node
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getEventManager(): EventManager {
|
getEventManager(): EventManager {
|
||||||
@@ -259,6 +238,17 @@ class Load3d {
|
|||||||
return this.recordingManager
|
return this.recordingManager
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getTargetSize(): { width: number; height: number } {
|
||||||
|
return {
|
||||||
|
width: this.targetWidth,
|
||||||
|
height: this.targetHeight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private shouldMaintainAspectRatio(): boolean {
|
||||||
|
return this.isViewerMode || (this.targetWidth > 0 && this.targetHeight > 0)
|
||||||
|
}
|
||||||
|
|
||||||
forceRender(): void {
|
forceRender(): void {
|
||||||
const delta = this.clock.getDelta()
|
const delta = this.clock.getDelta()
|
||||||
this.animationManager.update(delta)
|
this.animationManager.update(delta)
|
||||||
@@ -280,18 +270,16 @@ class Load3d {
|
|||||||
const containerWidth = this.renderer.domElement.clientWidth
|
const containerWidth = this.renderer.domElement.clientWidth
|
||||||
const containerHeight = this.renderer.domElement.clientHeight
|
const containerHeight = this.renderer.domElement.clientHeight
|
||||||
|
|
||||||
const widthWidget = this.node.widgets?.find((w) => w.name === 'width')
|
if (this.getDimensionsCallback) {
|
||||||
const heightWidget = this.node.widgets?.find((w) => w.name === 'height')
|
const dims = this.getDimensionsCallback()
|
||||||
const shouldMaintainAspectRatio =
|
if (dims) {
|
||||||
(widthWidget && heightWidget) || this.isViewerMode
|
this.targetWidth = dims.width
|
||||||
|
this.targetHeight = dims.height
|
||||||
if (shouldMaintainAspectRatio) {
|
this.targetAspectRatio = dims.width / dims.height
|
||||||
if (widthWidget && heightWidget) {
|
|
||||||
this.targetWidth = widthWidget.value as number
|
|
||||||
this.targetHeight = heightWidget.value as number
|
|
||||||
this.targetAspectRatio = this.targetWidth / this.targetHeight
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.shouldMaintainAspectRatio()) {
|
||||||
const containerAspectRatio = containerWidth / containerHeight
|
const containerAspectRatio = containerWidth / containerHeight
|
||||||
|
|
||||||
let renderWidth: number
|
let renderWidth: number
|
||||||
@@ -321,7 +309,7 @@ class Load3d {
|
|||||||
const renderAspectRatio = renderWidth / renderHeight
|
const renderAspectRatio = renderWidth / renderHeight
|
||||||
this.cameraManager.updateAspectRatio(renderAspectRatio)
|
this.cameraManager.updateAspectRatio(renderAspectRatio)
|
||||||
} else {
|
} else {
|
||||||
// Preview3D: fill the entire container
|
// No aspect ratio constraint: fill the entire container
|
||||||
this.renderer.setViewport(0, 0, containerWidth, containerHeight)
|
this.renderer.setViewport(0, 0, containerWidth, containerHeight)
|
||||||
this.renderer.setScissor(0, 0, containerWidth, containerHeight)
|
this.renderer.setScissor(0, 0, containerWidth, containerHeight)
|
||||||
this.renderer.setScissorTest(true)
|
this.renderer.setScissorTest(true)
|
||||||
@@ -459,13 +447,7 @@ class Load3d {
|
|||||||
const containerWidth = this.renderer.domElement.clientWidth
|
const containerWidth = this.renderer.domElement.clientWidth
|
||||||
const containerHeight = this.renderer.domElement.clientHeight
|
const containerHeight = this.renderer.domElement.clientHeight
|
||||||
|
|
||||||
// Calculate the actual render area based on target aspect ratio
|
if (this.shouldMaintainAspectRatio()) {
|
||||||
const widthWidget = this.node.widgets?.find((w) => w.name === 'width')
|
|
||||||
const heightWidget = this.node.widgets?.find((w) => w.name === 'height')
|
|
||||||
const shouldMaintainAspectRatio =
|
|
||||||
(widthWidget && heightWidget) || this.isViewerMode
|
|
||||||
|
|
||||||
if (shouldMaintainAspectRatio) {
|
|
||||||
const containerAspectRatio = containerWidth / containerHeight
|
const containerAspectRatio = containerWidth / containerHeight
|
||||||
|
|
||||||
let renderWidth: number
|
let renderWidth: number
|
||||||
@@ -486,7 +468,7 @@ class Load3d {
|
|||||||
renderHeight
|
renderHeight
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
// For Preview3D mode without aspect ratio constraints
|
// No aspect ratio constraints: fill container
|
||||||
this.sceneManager.updateBackgroundSize(
|
this.sceneManager.updateBackgroundSize(
|
||||||
this.sceneManager.backgroundTexture,
|
this.sceneManager.backgroundTexture,
|
||||||
this.sceneManager.backgroundMesh,
|
this.sceneManager.backgroundMesh,
|
||||||
@@ -609,6 +591,7 @@ class Load3d {
|
|||||||
this.targetWidth = width
|
this.targetWidth = width
|
||||||
this.targetHeight = height
|
this.targetHeight = height
|
||||||
this.targetAspectRatio = width / height
|
this.targetAspectRatio = width / height
|
||||||
|
this.handleResize()
|
||||||
this.forceRender()
|
this.forceRender()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -636,20 +619,16 @@ class Load3d {
|
|||||||
const containerWidth = parentElement.clientWidth
|
const containerWidth = parentElement.clientWidth
|
||||||
const containerHeight = parentElement.clientHeight
|
const containerHeight = parentElement.clientHeight
|
||||||
|
|
||||||
// Check if we have width/height widgets (Load3D nodes) or if it's viewer mode
|
if (this.getDimensionsCallback) {
|
||||||
const widthWidget = this.node.widgets?.find((w) => w.name === 'width')
|
const dims = this.getDimensionsCallback()
|
||||||
const heightWidget = this.node.widgets?.find((w) => w.name === 'height')
|
if (dims) {
|
||||||
const shouldMaintainAspectRatio =
|
this.targetWidth = dims.width
|
||||||
(widthWidget && heightWidget) || this.isViewerMode
|
this.targetHeight = dims.height
|
||||||
|
this.targetAspectRatio = dims.width / dims.height
|
||||||
if (shouldMaintainAspectRatio) {
|
|
||||||
// Load3D or viewer mode: maintain aspect ratio
|
|
||||||
if (widthWidget && heightWidget) {
|
|
||||||
this.targetWidth = widthWidget.value as number
|
|
||||||
this.targetHeight = heightWidget.value as number
|
|
||||||
this.targetAspectRatio = this.targetWidth / this.targetHeight
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.shouldMaintainAspectRatio()) {
|
||||||
const containerAspectRatio = containerWidth / containerHeight
|
const containerAspectRatio = containerWidth / containerHeight
|
||||||
let renderWidth: number
|
let renderWidth: number
|
||||||
let renderHeight: number
|
let renderHeight: number
|
||||||
@@ -666,7 +645,7 @@ class Load3d {
|
|||||||
this.cameraManager.handleResize(renderWidth, renderHeight)
|
this.cameraManager.handleResize(renderWidth, renderHeight)
|
||||||
this.sceneManager.handleResize(renderWidth, renderHeight)
|
this.sceneManager.handleResize(renderWidth, renderHeight)
|
||||||
} else {
|
} else {
|
||||||
// Preview3D: use container dimensions directly
|
// No aspect ratio constraint: use container dimensions directly
|
||||||
this.renderer.setSize(containerWidth, containerHeight)
|
this.renderer.setSize(containerWidth, containerHeight)
|
||||||
this.cameraManager.handleResize(containerWidth, containerHeight)
|
this.cameraManager.handleResize(containerWidth, containerHeight)
|
||||||
this.sceneManager.handleResize(containerWidth, containerHeight)
|
this.sceneManager.handleResize(containerWidth, containerHeight)
|
||||||
@@ -679,10 +658,6 @@ class Load3d {
|
|||||||
return this.sceneManager.captureScene(width, height)
|
return this.sceneManager.captureScene(width, height)
|
||||||
}
|
}
|
||||||
|
|
||||||
loadNodeProperty(name: string, defaultValue: any) {
|
|
||||||
return this.nodeStorage.loadNodeProperty(name, defaultValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
public async startRecording(): Promise<void> {
|
public async startRecording(): Promise<void> {
|
||||||
this.viewHelperManager.visibleViewHelper(false)
|
this.viewHelperManager.visibleViewHelper(false)
|
||||||
|
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
import { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
|
||||||
|
|
||||||
import { type NodeStorageInterface } from './interfaces'
|
|
||||||
|
|
||||||
export class NodeStorage implements NodeStorageInterface {
|
|
||||||
private node: LGraphNode
|
|
||||||
|
|
||||||
constructor(node: LGraphNode = {} as LGraphNode) {
|
|
||||||
this.node = node
|
|
||||||
}
|
|
||||||
|
|
||||||
storeNodeProperty(name: string, value: any): void {
|
|
||||||
if (this.node && this.node.properties) {
|
|
||||||
this.node.properties[name] = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
loadNodeProperty(name: string, defaultValue: any): any {
|
|
||||||
if (
|
|
||||||
!this.node ||
|
|
||||||
!this.node.properties ||
|
|
||||||
!(name in this.node.properties)
|
|
||||||
) {
|
|
||||||
return defaultValue
|
|
||||||
}
|
|
||||||
return this.node.properties[name]
|
|
||||||
}
|
|
||||||
|
|
||||||
setNode(node: LGraphNode): void {
|
|
||||||
this.node = node
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,7 @@ import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
|
|||||||
import { ViewHelper } from 'three/examples/jsm/helpers/ViewHelper'
|
import { ViewHelper } from 'three/examples/jsm/helpers/ViewHelper'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
type NodeStorageInterface,
|
type EventManagerInterface,
|
||||||
type ViewHelperManagerInterface
|
type ViewHelperManagerInterface
|
||||||
} from './interfaces'
|
} from './interfaces'
|
||||||
|
|
||||||
@@ -13,7 +13,7 @@ export class ViewHelperManager implements ViewHelperManagerInterface {
|
|||||||
|
|
||||||
private getActiveCamera: () => THREE.Camera
|
private getActiveCamera: () => THREE.Camera
|
||||||
private getControls: () => OrbitControls
|
private getControls: () => OrbitControls
|
||||||
private nodeStorage: NodeStorageInterface
|
private eventManager: EventManagerInterface
|
||||||
// @ts-expect-error unused variable
|
// @ts-expect-error unused variable
|
||||||
private renderer: THREE.WebGLRenderer
|
private renderer: THREE.WebGLRenderer
|
||||||
|
|
||||||
@@ -21,12 +21,12 @@ export class ViewHelperManager implements ViewHelperManagerInterface {
|
|||||||
renderer: THREE.WebGLRenderer,
|
renderer: THREE.WebGLRenderer,
|
||||||
getActiveCamera: () => THREE.Camera,
|
getActiveCamera: () => THREE.Camera,
|
||||||
getControls: () => OrbitControls,
|
getControls: () => OrbitControls,
|
||||||
nodeStorage: NodeStorageInterface
|
eventManager: EventManagerInterface
|
||||||
) {
|
) {
|
||||||
this.renderer = renderer
|
this.renderer = renderer
|
||||||
this.getActiveCamera = getActiveCamera
|
this.getActiveCamera = getActiveCamera
|
||||||
this.getControls = getControls
|
this.getControls = getControls
|
||||||
this.nodeStorage = nodeStorage
|
this.eventManager = eventManager
|
||||||
}
|
}
|
||||||
|
|
||||||
init(): void {}
|
init(): void {}
|
||||||
@@ -87,18 +87,7 @@ export class ViewHelperManager implements ViewHelperManagerInterface {
|
|||||||
: 'orthographic'
|
: 'orthographic'
|
||||||
}
|
}
|
||||||
|
|
||||||
const cameraConfig = this.nodeStorage.loadNodeProperty(
|
this.eventManager.emitEvent('cameraChanged', cameraState)
|
||||||
'Camera Config',
|
|
||||||
{
|
|
||||||
cameraType: cameraState.cameraType,
|
|
||||||
fov:
|
|
||||||
this.getActiveCamera() instanceof THREE.PerspectiveCamera
|
|
||||||
? (this.getActiveCamera() as THREE.PerspectiveCamera).fov
|
|
||||||
: 75
|
|
||||||
}
|
|
||||||
)
|
|
||||||
cameraConfig.state = cameraState
|
|
||||||
this.nodeStorage.storeNodeProperty('Camera Config', cameraConfig)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,9 +7,6 @@ import { MTLLoader } from 'three/examples/jsm/loaders/MTLLoader'
|
|||||||
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader'
|
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader'
|
||||||
import { STLLoader } from 'three/examples/jsm/loaders/STLLoader'
|
import { STLLoader } from 'three/examples/jsm/loaders/STLLoader'
|
||||||
|
|
||||||
import { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
|
||||||
import { type CustomInputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2'
|
|
||||||
|
|
||||||
export type MaterialMode = 'original' | 'normal' | 'wireframe' | 'depth'
|
export type MaterialMode = 'original' | 'normal' | 'wireframe' | 'depth'
|
||||||
export type UpDirection = 'original' | '-x' | '+x' | '-y' | '+y' | '-z' | '+z'
|
export type UpDirection = 'original' | '-x' | '+x' | '-y' | '+y' | '-z' | '+z'
|
||||||
export type CameraType = 'perspective' | 'orthographic'
|
export type CameraType = 'perspective' | 'orthographic'
|
||||||
@@ -49,10 +46,19 @@ export interface EventCallback {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface Load3DOptions {
|
export interface Load3DOptions {
|
||||||
node?: LGraphNode
|
// Optional target dimensions for aspect ratio control
|
||||||
inputSpec?: CustomInputSpec
|
width?: number
|
||||||
disablePreview?: boolean
|
height?: number
|
||||||
|
|
||||||
|
// Dynamic dimension provider (called on every render)
|
||||||
|
// Use this for reactive dimensions that change over time
|
||||||
|
getDimensions?: () => { width: number; height: number } | null
|
||||||
|
|
||||||
|
// Viewer mode flag (affects aspect ratio behavior)
|
||||||
isViewerMode?: boolean
|
isViewerMode?: boolean
|
||||||
|
|
||||||
|
// Optional context menu callback
|
||||||
|
onContextMenu?: (event: MouseEvent) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CaptureResult {
|
export interface CaptureResult {
|
||||||
@@ -121,11 +127,6 @@ export interface EventManagerInterface {
|
|||||||
emitEvent(event: string, data?: any): void
|
emitEvent(event: string, data?: any): void
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface NodeStorageInterface {
|
|
||||||
storeNodeProperty(name: string, value: any): void
|
|
||||||
loadNodeProperty(name: string, defaultValue: any): any
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AnimationManagerInterface extends BaseManager {
|
export interface AnimationManagerInterface extends BaseManager {
|
||||||
currentAnimation: THREE.AnimationMixer | null
|
currentAnimation: THREE.AnimationMixer | null
|
||||||
animationActions: THREE.AnimationAction[]
|
animationActions: THREE.AnimationAction[]
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ useExtensionService().registerExtension({
|
|||||||
|
|
||||||
modelWidget.value = filePath
|
modelWidget.value = filePath
|
||||||
|
|
||||||
const config = new Load3DConfiguration(load3d)
|
const config = new Load3DConfiguration(load3d, node.properties)
|
||||||
|
|
||||||
config.configureForSaveMesh(fileInfo['type'], filePath)
|
config.configureForSaveMesh(fileInfo['type'], filePath)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,9 @@ vi.mock('@/platform/updates/common/toastStore', () => ({
|
|||||||
|
|
||||||
vi.mock('@/scripts/api', () => ({
|
vi.mock('@/scripts/api', () => ({
|
||||||
api: {
|
api: {
|
||||||
apiURL: vi.fn()
|
apiURL: vi.fn(),
|
||||||
|
addEventListener: vi.fn(),
|
||||||
|
removeEventListener: vi.fn()
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@@ -157,9 +159,15 @@ describe('useLoad3d', () => {
|
|||||||
|
|
||||||
await composable.initializeLoad3d(containerRef)
|
await composable.initializeLoad3d(containerRef)
|
||||||
|
|
||||||
expect(Load3d).toHaveBeenCalledWith(containerRef, {
|
expect(Load3d).toHaveBeenCalledWith(
|
||||||
node: mockNode
|
containerRef,
|
||||||
})
|
expect.objectContaining({
|
||||||
|
width: 512,
|
||||||
|
height: 512,
|
||||||
|
getDimensions: expect.any(Function),
|
||||||
|
onContextMenu: expect.any(Function)
|
||||||
|
})
|
||||||
|
)
|
||||||
expect(nodeToLoad3dMap.has(mockNode)).toBe(true)
|
expect(nodeToLoad3dMap.has(mockNode)).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -161,9 +161,10 @@ describe('useLoad3dViewer', () => {
|
|||||||
await viewer.initializeViewer(containerRef, mockSourceLoad3d)
|
await viewer.initializeViewer(containerRef, mockSourceLoad3d)
|
||||||
|
|
||||||
expect(Load3d).toHaveBeenCalledWith(containerRef, {
|
expect(Load3d).toHaveBeenCalledWith(containerRef, {
|
||||||
disablePreview: true,
|
width: undefined,
|
||||||
isViewerMode: true,
|
height: undefined,
|
||||||
node: mockNode
|
getDimensions: undefined,
|
||||||
|
isViewerMode: true
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(mockLoad3dService.copyLoad3dState).toHaveBeenCalledWith(
|
expect(mockLoad3dService.copyLoad3dState).toHaveBeenCalledWith(
|
||||||
|
|||||||
Reference in New Issue
Block a user