From 538f007f1d7e157cf4745c1932fac9649d1fc9d0 Mon Sep 17 00:00:00 2001 From: Johnpaul Chiwetelu <49923152+Myestery@users.noreply.github.com> Date: Thu, 15 Jan 2026 02:21:38 +0100 Subject: [PATCH] Road to No Explicit Any Part 5: load3d Module (#8064) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 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 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 - `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) --- src/composables/useLoad3d.ts | 3 +- src/extensions/core/load3d.ts | 47 ++++++++++++------- .../core/load3d/AnimationManager.ts | 14 ++++-- src/extensions/core/load3d/EventManager.ts | 10 ++-- src/extensions/core/load3d/Load3d.ts | 5 +- src/extensions/core/load3d/interfaces.ts | 15 +++--- 6 files changed, 59 insertions(+), 35 deletions(-) diff --git a/src/composables/useLoad3d.ts b/src/composables/useLoad3d.ts index ca6871e15..c8312051a 100644 --- a/src/composables/useLoad3d.ts +++ b/src/composables/useLoad3d.ts @@ -9,6 +9,7 @@ import type { CameraConfig, CameraState, CameraType, + EventCallback, LightConfig, MaterialMode, ModelConfig, @@ -564,7 +565,7 @@ export const useLoad3d = (nodeOrRef: MaybeRef) => { const handleEvents = (action: 'add' | 'remove') => { Object.entries(eventConfig).forEach(([event, handler]) => { const method = `${action}EventListener` as const - load3d?.[method](event, handler) + load3d?.[method](event, handler as EventCallback) }) } diff --git a/src/extensions/core/load3d.ts b/src/extensions/core/load3d.ts index 55358d62c..668a1fd15 100644 --- a/src/extensions/core/load3d.ts +++ b/src/extensions/core/load3d.ts @@ -4,6 +4,10 @@ import Load3D from '@/components/load3d/Load3D.vue' import Load3DViewerContent from '@/components/load3d/Load3dViewerContent.vue' import { nodeToLoad3dMap, useLoad3d } from '@/composables/useLoad3d' import { createExportMenuItems } from '@/extensions/core/load3d/exportMenuHelper' +import type { + CameraConfig, + CameraState +} from '@/extensions/core/load3d/interfaces' import Load3DConfiguration from '@/extensions/core/load3d/Load3DConfiguration' import Load3dUtils from '@/extensions/core/load3d/Load3dUtils' 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 { IStringWidget } from '@/lib/litegraph/src/types/widgets' 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 { ComfyApp, app } from '@/scripts/app' import { ComponentWidgetImpl, addWidget } from '@/scripts/domWidget' @@ -32,12 +37,12 @@ const inputSpecPreview3D: CustomInputSpec = { isPreview: true } -async function handleModelUpload(files: FileList, node: any) { +async function handleModelUpload(files: FileList, node: LGraphNode) { if (!files?.length) return - const modelWidget = node.widgets?.find( - (w: any) => w.name === 'model_file' - ) as IStringWidget + const modelWidget = node.widgets?.find((w) => w.name === 'model_file') as + | IStringWidget + | undefined try { 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 try { @@ -330,7 +335,9 @@ useExtensionService().registerExtension({ await nextTick() 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 config = new Load3DConfiguration(load3d, node.properties) @@ -357,7 +364,9 @@ useExtensionService().registerExtension({ return null } - const cameraConfig = (node.properties['Camera Config'] as any) || { + const cameraConfig: CameraConfig = (node.properties[ + 'Camera Config' + ] as CameraConfig | undefined) || { cameraType: currentLoad3d.getCurrentCameraType(), fov: currentLoad3d.cameraManager.perspectiveCamera.fov } @@ -388,7 +397,8 @@ useExtensionService().registerExtension({ mask: `threed/${dataMask.name} [temp]`, normal: `threed/${dataNormal.name} [temp]`, camera_info: - (node.properties['Camera Config'] as any)?.state || null, + (node.properties['Camera Config'] as CameraConfig | undefined) + ?.state || null, recording: '' } @@ -472,7 +482,9 @@ useExtensionService().registerExtension({ if (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 settings = { @@ -484,10 +496,13 @@ useExtensionService().registerExtension({ config.configure(settings) } - node.onExecuted = function (message: any) { - onExecuted?.apply(this, arguments as any) + node.onExecuted = function (output: NodeExecutionOutput) { + onExecuted?.call(this, output) - let filePath = message.result[0] + const result = (output as Record).result as + | unknown[] + | undefined + const filePath = result?.[0] as string | undefined if (!filePath) { const msg = t('toastMessages.unableToGetModelFilePath') @@ -495,10 +510,10 @@ useExtensionService().registerExtension({ useToastStore().addAlert(msg) } - let cameraState = message.result[1] - let bgImagePath = message.result[2] + const cameraState = result?.[1] as CameraState | undefined + const bgImagePath = result?.[2] as string | undefined - modelWidget.value = filePath.replaceAll('\\', '/') + modelWidget.value = filePath?.replaceAll('\\', '/') node.properties['Last Time Model File'] = modelWidget.value diff --git a/src/extensions/core/load3d/AnimationManager.ts b/src/extensions/core/load3d/AnimationManager.ts index 80fc6f153..c6dc3428b 100644 --- a/src/extensions/core/load3d/AnimationManager.ts +++ b/src/extensions/core/load3d/AnimationManager.ts @@ -1,9 +1,10 @@ import * as THREE from 'three' +import type { GLTF } from 'three/examples/jsm/loaders/GLTFLoader' -import { - type AnimationItem, - type AnimationManagerInterface, - type EventManagerInterface +import type { + AnimationItem, + AnimationManagerInterface, + EventManagerInterface } from '@/extensions/core/load3d/interfaces' export class AnimationManager implements AnimationManagerInterface { @@ -38,7 +39,10 @@ export class AnimationManager implements AnimationManagerInterface { 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) { this.currentAnimation.stopAllAction() this.animationActions = [] diff --git a/src/extensions/core/load3d/EventManager.ts b/src/extensions/core/load3d/EventManager.ts index 64b87eb9b..3a0a747e9 100644 --- a/src/extensions/core/load3d/EventManager.ts +++ b/src/extensions/core/load3d/EventManager.ts @@ -1,16 +1,16 @@ import { type EventCallback, type EventManagerInterface } from './interfaces' export class EventManager implements EventManagerInterface { - private listeners: { [key: string]: EventCallback[] } = {} + private listeners: Record = {} - addEventListener(event: string, callback: EventCallback): void { + addEventListener(event: string, callback: EventCallback): void { if (!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(event: string, callback: EventCallback): void { if (this.listeners[event]) { this.listeners[event] = this.listeners[event].filter( (cb) => cb !== callback @@ -18,7 +18,7 @@ export class EventManager implements EventManagerInterface { } } - emitEvent(event: string, data?: any): void { + emitEvent(event: string, data: T): void { if (this.listeners[event]) { this.listeners[event].forEach((callback) => callback(data)) } diff --git a/src/extensions/core/load3d/Load3d.ts b/src/extensions/core/load3d/Load3d.ts index 91173346b..657a1796b 100644 --- a/src/extensions/core/load3d/Load3d.ts +++ b/src/extensions/core/load3d/Load3d.ts @@ -14,6 +14,7 @@ import { ViewHelperManager } from './ViewHelperManager' import { type CameraState, type CaptureResult, + type EventCallback, type Load3DOptions, type MaterialMode, type UpDirection @@ -610,11 +611,11 @@ class Load3d { this.forceRender() } - addEventListener(event: string, callback: (data?: any) => void): void { + addEventListener(event: string, callback: EventCallback): void { this.eventManager.addEventListener(event, callback) } - removeEventListener(event: string, callback: (data?: any) => void): void { + removeEventListener(event: string, callback: EventCallback): void { this.eventManager.removeEventListener(event, callback) } diff --git a/src/extensions/core/load3d/interfaces.ts b/src/extensions/core/load3d/interfaces.ts index d368d8d3c..7beb3882a 100644 --- a/src/extensions/core/load3d/interfaces.ts +++ b/src/extensions/core/load3d/interfaces.ts @@ -47,8 +47,8 @@ export interface LightConfig { intensity: number } -export interface EventCallback { - (data?: any): void +export interface EventCallback { + (data: T): void } export interface Load3DOptions { @@ -128,9 +128,9 @@ export interface ViewHelperManagerInterface extends BaseManager { } export interface EventManagerInterface { - addEventListener(event: string, callback: EventCallback): void - removeEventListener(event: string, callback: EventCallback): void - emitEvent(event: string, data?: any): void + addEventListener(event: string, callback: EventCallback): void + removeEventListener(event: string, callback: EventCallback): void + emitEvent(event: string, data: T): void } export interface AnimationManagerInterface extends BaseManager { @@ -141,7 +141,10 @@ export interface AnimationManagerInterface extends BaseManager { isAnimationPlaying: boolean animationSpeed: number - setupModelAnimations(model: THREE.Object3D, originalModel: any): void + setupModelAnimations( + model: THREE.Object3D, + originalModel: THREE.Object3D | THREE.BufferGeometry | GLTF | null + ): void updateAnimationList(): void setAnimationSpeed(speed: number): void updateSelectedAnimation(index: number): void