mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-25 16:59:45 +00:00
Road to No Explicit Any Part 5: load3d Module (#8064)
## Summary - Removes all `any` types from the load3d module - Uses generics for EventCallback to provide type-safe event handling - Types node parameters with LGraphNode - Types onExecuted callback with NodeExecutionOutput - Types Camera Config property casts with CameraConfig interface ## Changes - `interfaces.ts`: EventCallback<T> generic, EventManagerInterface generic methods - `EventManager.ts`: Generic emitEvent/addEventListener/removeEventListener - `AnimationManager.ts`: setupModelAnimations originalModel typed as GLTF union - `Load3d.ts`: Event listener methods use EventCallback<T> - `load3d.ts`: Node params typed as LGraphNode, onExecuted uses NodeExecutionOutput, CameraConfig casts ## Test plan - [x] `pnpm typecheck` passes - [x] `pnpm lint` passes - [x] `pnpm test:unit` passes ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-8064-Road-to-No-Explicit-Any-Part-5-load3d-Module-2e96d73d365081efbc01f2d8a4f3c11f) by [Unito](https://www.unito.io)
This commit is contained in:
committed by
GitHub
parent
3069c24f81
commit
538f007f1d
@@ -9,6 +9,7 @@ import type {
|
|||||||
CameraConfig,
|
CameraConfig,
|
||||||
CameraState,
|
CameraState,
|
||||||
CameraType,
|
CameraType,
|
||||||
|
EventCallback,
|
||||||
LightConfig,
|
LightConfig,
|
||||||
MaterialMode,
|
MaterialMode,
|
||||||
ModelConfig,
|
ModelConfig,
|
||||||
@@ -564,7 +565,7 @@ export const useLoad3d = (nodeOrRef: MaybeRef<LGraphNode | null>) => {
|
|||||||
const handleEvents = (action: 'add' | 'remove') => {
|
const handleEvents = (action: 'add' | 'remove') => {
|
||||||
Object.entries(eventConfig).forEach(([event, handler]) => {
|
Object.entries(eventConfig).forEach(([event, handler]) => {
|
||||||
const method = `${action}EventListener` as const
|
const method = `${action}EventListener` as const
|
||||||
load3d?.[method](event, handler)
|
load3d?.[method](event, handler as EventCallback)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,10 @@ import Load3D from '@/components/load3d/Load3D.vue'
|
|||||||
import Load3DViewerContent from '@/components/load3d/Load3dViewerContent.vue'
|
import Load3DViewerContent from '@/components/load3d/Load3dViewerContent.vue'
|
||||||
import { nodeToLoad3dMap, useLoad3d } from '@/composables/useLoad3d'
|
import { nodeToLoad3dMap, useLoad3d } from '@/composables/useLoad3d'
|
||||||
import { createExportMenuItems } from '@/extensions/core/load3d/exportMenuHelper'
|
import { createExportMenuItems } from '@/extensions/core/load3d/exportMenuHelper'
|
||||||
|
import type {
|
||||||
|
CameraConfig,
|
||||||
|
CameraState
|
||||||
|
} from '@/extensions/core/load3d/interfaces'
|
||||||
import Load3DConfiguration from '@/extensions/core/load3d/Load3DConfiguration'
|
import Load3DConfiguration from '@/extensions/core/load3d/Load3DConfiguration'
|
||||||
import Load3dUtils from '@/extensions/core/load3d/Load3dUtils'
|
import Load3dUtils from '@/extensions/core/load3d/Load3dUtils'
|
||||||
import { t } from '@/i18n'
|
import { t } from '@/i18n'
|
||||||
@@ -11,7 +15,8 @@ import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode'
|
|||||||
import type { IContextMenuValue } from '@/lib/litegraph/src/interfaces'
|
import type { IContextMenuValue } from '@/lib/litegraph/src/interfaces'
|
||||||
import type { IStringWidget } from '@/lib/litegraph/src/types/widgets'
|
import type { IStringWidget } from '@/lib/litegraph/src/types/widgets'
|
||||||
import { useToastStore } from '@/platform/updates/common/toastStore'
|
import { useToastStore } from '@/platform/updates/common/toastStore'
|
||||||
import { type CustomInputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2'
|
import type { NodeExecutionOutput } from '@/schemas/apiSchema'
|
||||||
|
import type { CustomInputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2'
|
||||||
import { api } from '@/scripts/api'
|
import { api } from '@/scripts/api'
|
||||||
import { ComfyApp, app } from '@/scripts/app'
|
import { ComfyApp, app } from '@/scripts/app'
|
||||||
import { ComponentWidgetImpl, addWidget } from '@/scripts/domWidget'
|
import { ComponentWidgetImpl, addWidget } from '@/scripts/domWidget'
|
||||||
@@ -32,12 +37,12 @@ const inputSpecPreview3D: CustomInputSpec = {
|
|||||||
isPreview: true
|
isPreview: true
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleModelUpload(files: FileList, node: any) {
|
async function handleModelUpload(files: FileList, node: LGraphNode) {
|
||||||
if (!files?.length) return
|
if (!files?.length) return
|
||||||
|
|
||||||
const modelWidget = node.widgets?.find(
|
const modelWidget = node.widgets?.find((w) => w.name === 'model_file') as
|
||||||
(w: any) => w.name === 'model_file'
|
| IStringWidget
|
||||||
) as IStringWidget
|
| undefined
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const resourceFolder = (node.properties['Resource Folder'] as string) || ''
|
const resourceFolder = (node.properties['Resource Folder'] as string) || ''
|
||||||
@@ -81,7 +86,7 @@ async function handleModelUpload(files: FileList, node: any) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleResourcesUpload(files: FileList, node: any) {
|
async function handleResourcesUpload(files: FileList, node: LGraphNode) {
|
||||||
if (!files?.length) return
|
if (!files?.length) return
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -330,7 +335,9 @@ useExtensionService().registerExtension({
|
|||||||
await nextTick()
|
await nextTick()
|
||||||
|
|
||||||
useLoad3d(node).waitForLoad3d((load3d) => {
|
useLoad3d(node).waitForLoad3d((load3d) => {
|
||||||
const cameraConfig = node.properties['Camera Config'] as any
|
const cameraConfig = node.properties['Camera Config'] as
|
||||||
|
| CameraConfig
|
||||||
|
| undefined
|
||||||
const cameraState = cameraConfig?.state
|
const cameraState = cameraConfig?.state
|
||||||
|
|
||||||
const config = new Load3DConfiguration(load3d, node.properties)
|
const config = new Load3DConfiguration(load3d, node.properties)
|
||||||
@@ -357,7 +364,9 @@ useExtensionService().registerExtension({
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const cameraConfig = (node.properties['Camera Config'] as any) || {
|
const cameraConfig: CameraConfig = (node.properties[
|
||||||
|
'Camera Config'
|
||||||
|
] as CameraConfig | undefined) || {
|
||||||
cameraType: currentLoad3d.getCurrentCameraType(),
|
cameraType: currentLoad3d.getCurrentCameraType(),
|
||||||
fov: currentLoad3d.cameraManager.perspectiveCamera.fov
|
fov: currentLoad3d.cameraManager.perspectiveCamera.fov
|
||||||
}
|
}
|
||||||
@@ -388,7 +397,8 @@ useExtensionService().registerExtension({
|
|||||||
mask: `threed/${dataMask.name} [temp]`,
|
mask: `threed/${dataMask.name} [temp]`,
|
||||||
normal: `threed/${dataNormal.name} [temp]`,
|
normal: `threed/${dataNormal.name} [temp]`,
|
||||||
camera_info:
|
camera_info:
|
||||||
(node.properties['Camera Config'] as any)?.state || null,
|
(node.properties['Camera Config'] as CameraConfig | undefined)
|
||||||
|
?.state || null,
|
||||||
recording: ''
|
recording: ''
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -472,7 +482,9 @@ useExtensionService().registerExtension({
|
|||||||
if (lastTimeModelFile) {
|
if (lastTimeModelFile) {
|
||||||
modelWidget.value = lastTimeModelFile
|
modelWidget.value = lastTimeModelFile
|
||||||
|
|
||||||
const cameraConfig = node.properties['Camera Config'] as any
|
const cameraConfig = node.properties['Camera Config'] as
|
||||||
|
| CameraConfig
|
||||||
|
| undefined
|
||||||
const cameraState = cameraConfig?.state
|
const cameraState = cameraConfig?.state
|
||||||
|
|
||||||
const settings = {
|
const settings = {
|
||||||
@@ -484,10 +496,13 @@ useExtensionService().registerExtension({
|
|||||||
config.configure(settings)
|
config.configure(settings)
|
||||||
}
|
}
|
||||||
|
|
||||||
node.onExecuted = function (message: any) {
|
node.onExecuted = function (output: NodeExecutionOutput) {
|
||||||
onExecuted?.apply(this, arguments as any)
|
onExecuted?.call(this, output)
|
||||||
|
|
||||||
let filePath = message.result[0]
|
const result = (output as Record<string, unknown>).result as
|
||||||
|
| unknown[]
|
||||||
|
| undefined
|
||||||
|
const filePath = result?.[0] as string | undefined
|
||||||
|
|
||||||
if (!filePath) {
|
if (!filePath) {
|
||||||
const msg = t('toastMessages.unableToGetModelFilePath')
|
const msg = t('toastMessages.unableToGetModelFilePath')
|
||||||
@@ -495,10 +510,10 @@ useExtensionService().registerExtension({
|
|||||||
useToastStore().addAlert(msg)
|
useToastStore().addAlert(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
let cameraState = message.result[1]
|
const cameraState = result?.[1] as CameraState | undefined
|
||||||
let bgImagePath = message.result[2]
|
const bgImagePath = result?.[2] as string | undefined
|
||||||
|
|
||||||
modelWidget.value = filePath.replaceAll('\\', '/')
|
modelWidget.value = filePath?.replaceAll('\\', '/')
|
||||||
|
|
||||||
node.properties['Last Time Model File'] = modelWidget.value
|
node.properties['Last Time Model File'] = modelWidget.value
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import * as THREE from 'three'
|
import * as THREE from 'three'
|
||||||
|
import type { GLTF } from 'three/examples/jsm/loaders/GLTFLoader'
|
||||||
|
|
||||||
import {
|
import type {
|
||||||
type AnimationItem,
|
AnimationItem,
|
||||||
type AnimationManagerInterface,
|
AnimationManagerInterface,
|
||||||
type EventManagerInterface
|
EventManagerInterface
|
||||||
} from '@/extensions/core/load3d/interfaces'
|
} from '@/extensions/core/load3d/interfaces'
|
||||||
|
|
||||||
export class AnimationManager implements AnimationManagerInterface {
|
export class AnimationManager implements AnimationManagerInterface {
|
||||||
@@ -38,7 +39,10 @@ export class AnimationManager implements AnimationManagerInterface {
|
|||||||
this.eventManager.emitEvent('animationListChange', [])
|
this.eventManager.emitEvent('animationListChange', [])
|
||||||
}
|
}
|
||||||
|
|
||||||
setupModelAnimations(model: THREE.Object3D, originalModel: any): void {
|
setupModelAnimations(
|
||||||
|
model: THREE.Object3D,
|
||||||
|
originalModel: THREE.Object3D | THREE.BufferGeometry | GLTF | null
|
||||||
|
): void {
|
||||||
if (this.currentAnimation) {
|
if (this.currentAnimation) {
|
||||||
this.currentAnimation.stopAllAction()
|
this.currentAnimation.stopAllAction()
|
||||||
this.animationActions = []
|
this.animationActions = []
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import { type EventCallback, type EventManagerInterface } from './interfaces'
|
import { type EventCallback, type EventManagerInterface } from './interfaces'
|
||||||
|
|
||||||
export class EventManager implements EventManagerInterface {
|
export class EventManager implements EventManagerInterface {
|
||||||
private listeners: { [key: string]: EventCallback[] } = {}
|
private listeners: Record<string, EventCallback[]> = {}
|
||||||
|
|
||||||
addEventListener(event: string, callback: EventCallback): void {
|
addEventListener<T>(event: string, callback: EventCallback<T>): void {
|
||||||
if (!this.listeners[event]) {
|
if (!this.listeners[event]) {
|
||||||
this.listeners[event] = []
|
this.listeners[event] = []
|
||||||
}
|
}
|
||||||
this.listeners[event].push(callback)
|
this.listeners[event].push(callback as EventCallback)
|
||||||
}
|
}
|
||||||
|
|
||||||
removeEventListener(event: string, callback: EventCallback): void {
|
removeEventListener<T>(event: string, callback: EventCallback<T>): void {
|
||||||
if (this.listeners[event]) {
|
if (this.listeners[event]) {
|
||||||
this.listeners[event] = this.listeners[event].filter(
|
this.listeners[event] = this.listeners[event].filter(
|
||||||
(cb) => cb !== callback
|
(cb) => cb !== callback
|
||||||
@@ -18,7 +18,7 @@ export class EventManager implements EventManagerInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
emitEvent(event: string, data?: any): void {
|
emitEvent<T>(event: string, data: T): void {
|
||||||
if (this.listeners[event]) {
|
if (this.listeners[event]) {
|
||||||
this.listeners[event].forEach((callback) => callback(data))
|
this.listeners[event].forEach((callback) => callback(data))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { ViewHelperManager } from './ViewHelperManager'
|
|||||||
import {
|
import {
|
||||||
type CameraState,
|
type CameraState,
|
||||||
type CaptureResult,
|
type CaptureResult,
|
||||||
|
type EventCallback,
|
||||||
type Load3DOptions,
|
type Load3DOptions,
|
||||||
type MaterialMode,
|
type MaterialMode,
|
||||||
type UpDirection
|
type UpDirection
|
||||||
@@ -610,11 +611,11 @@ class Load3d {
|
|||||||
this.forceRender()
|
this.forceRender()
|
||||||
}
|
}
|
||||||
|
|
||||||
addEventListener(event: string, callback: (data?: any) => void): void {
|
addEventListener<T>(event: string, callback: EventCallback<T>): void {
|
||||||
this.eventManager.addEventListener(event, callback)
|
this.eventManager.addEventListener(event, callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
removeEventListener(event: string, callback: (data?: any) => void): void {
|
removeEventListener<T>(event: string, callback: EventCallback<T>): void {
|
||||||
this.eventManager.removeEventListener(event, callback)
|
this.eventManager.removeEventListener(event, callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -47,8 +47,8 @@ export interface LightConfig {
|
|||||||
intensity: number
|
intensity: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EventCallback {
|
export interface EventCallback<T = unknown> {
|
||||||
(data?: any): void
|
(data: T): void
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Load3DOptions {
|
export interface Load3DOptions {
|
||||||
@@ -128,9 +128,9 @@ export interface ViewHelperManagerInterface extends BaseManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface EventManagerInterface {
|
export interface EventManagerInterface {
|
||||||
addEventListener(event: string, callback: EventCallback): void
|
addEventListener<T>(event: string, callback: EventCallback<T>): void
|
||||||
removeEventListener(event: string, callback: EventCallback): void
|
removeEventListener<T>(event: string, callback: EventCallback<T>): void
|
||||||
emitEvent(event: string, data?: any): void
|
emitEvent<T>(event: string, data: T): void
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AnimationManagerInterface extends BaseManager {
|
export interface AnimationManagerInterface extends BaseManager {
|
||||||
@@ -141,7 +141,10 @@ export interface AnimationManagerInterface extends BaseManager {
|
|||||||
isAnimationPlaying: boolean
|
isAnimationPlaying: boolean
|
||||||
animationSpeed: number
|
animationSpeed: number
|
||||||
|
|
||||||
setupModelAnimations(model: THREE.Object3D, originalModel: any): void
|
setupModelAnimations(
|
||||||
|
model: THREE.Object3D,
|
||||||
|
originalModel: THREE.Object3D | THREE.BufferGeometry | GLTF | null
|
||||||
|
): void
|
||||||
updateAnimationList(): void
|
updateAnimationList(): void
|
||||||
setAnimationSpeed(speed: number): void
|
setAnimationSpeed(speed: number): void
|
||||||
updateSelectedAnimation(index: number): void
|
updateSelectedAnimation(index: number): void
|
||||||
|
|||||||
Reference in New Issue
Block a user