mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-10 01:50:08 +00:00
feat: use Web Worker for OBJ loading to prevent UI blocking (#7846)
## Summary - Replace OBJLoader with OBJLoader2Parallel from wwobjloader2 - OBJ parsing now runs in a Web Worker, keeping UI responsive - Add 100MB file size limit with user-friendly error message reduce loading time for 97M obj from 9s to 3s fix https://github.com/Comfy-Org/ComfyUI_frontend/issues/7843 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-7846-feat-use-Web-Worker-for-OBJ-loading-to-prevent-UI-blocking-2df6d73d36508140bea3c87e83d11278) by [Unito](https://www.unito.io)
This commit is contained in:
@@ -187,6 +187,7 @@
|
||||
"vue-i18n": "catalog:",
|
||||
"vue-router": "catalog:",
|
||||
"vuefire": "catalog:",
|
||||
"wwobjloader2": "catalog:",
|
||||
"yjs": "catalog:",
|
||||
"zod": "catalog:",
|
||||
"zod-validation-error": "catalog:"
|
||||
|
||||
32
pnpm-lock.yaml
generated
32
pnpm-lock.yaml
generated
@@ -309,6 +309,9 @@ catalogs:
|
||||
vuefire:
|
||||
specifier: ^3.2.1
|
||||
version: 3.2.1
|
||||
wwobjloader2:
|
||||
specifier: ^6.2.1
|
||||
version: 6.2.1
|
||||
yjs:
|
||||
specifier: ^13.6.27
|
||||
version: 13.6.27
|
||||
@@ -497,6 +500,9 @@ importers:
|
||||
vuefire:
|
||||
specifier: 'catalog:'
|
||||
version: 3.2.1(consola@3.4.2)(firebase@11.6.0)(vue@3.5.13(typescript@5.9.3))
|
||||
wwobjloader2:
|
||||
specifier: 'catalog:'
|
||||
version: 6.2.1(three@0.170.0)
|
||||
yjs:
|
||||
specifier: 'catalog:'
|
||||
version: 13.6.27
|
||||
@@ -8225,6 +8231,19 @@ packages:
|
||||
resolution: {integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
wtd-core@3.0.0:
|
||||
resolution: {integrity: sha512-LSPfAQ5ULSV5vPhipcjdQvV5xOx25QesYK23jwUOF99xOx6fuulk7CMQerERRwA4uoQooNmRd8AT6IPBwORlWQ==}
|
||||
|
||||
wtd-three-ext@3.0.0:
|
||||
resolution: {integrity: sha512-PLZJipCAiinot8D1uB4A7+XHxPAYeZXDhczbbazK7pKdqpE77zMizQH4rSZsaNbzktgnIfpgK/ODqhJTdrUjUw==}
|
||||
peerDependencies:
|
||||
three: '>= 0.137.5 < 1'
|
||||
|
||||
wwobjloader2@6.2.1:
|
||||
resolution: {integrity: sha512-/v/sfUX0PMQAI8souzCs6xsO9LR3RyL+ujnOiS/1pngUlakKyHYC5XMQvu77pTeWzY3rzNyt5Q/bg5O3RukA+g==}
|
||||
peerDependencies:
|
||||
three: '>= 0.137.5 < 1'
|
||||
|
||||
xdg-basedir@5.1.0:
|
||||
resolution: {integrity: sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==}
|
||||
engines: {node: '>=12'}
|
||||
@@ -17125,6 +17144,19 @@ snapshots:
|
||||
dependencies:
|
||||
is-wsl: 3.1.0
|
||||
|
||||
wtd-core@3.0.0: {}
|
||||
|
||||
wtd-three-ext@3.0.0(three@0.170.0):
|
||||
dependencies:
|
||||
three: 0.170.0
|
||||
wtd-core: 3.0.0
|
||||
|
||||
wwobjloader2@6.2.1(three@0.170.0):
|
||||
dependencies:
|
||||
three: 0.170.0
|
||||
wtd-core: 3.0.0
|
||||
wtd-three-ext: 3.0.0(three@0.170.0)
|
||||
|
||||
xdg-basedir@5.1.0: {}
|
||||
|
||||
xml-name-validator@4.0.0: {}
|
||||
|
||||
@@ -104,6 +104,7 @@ catalog:
|
||||
vue-router: ^4.4.3
|
||||
vue-tsc: ^3.2.1
|
||||
vuefire: ^3.2.1
|
||||
wwobjloader2: ^6.2.1
|
||||
yjs: ^13.6.27
|
||||
zod: ^3.23.8
|
||||
zod-to-json-schema: ^3.24.1
|
||||
|
||||
@@ -34,9 +34,26 @@ class Load3dUtils {
|
||||
return await resp.json()
|
||||
}
|
||||
|
||||
static readonly MAX_UPLOAD_SIZE_MB = 100
|
||||
|
||||
static async uploadFile(file: File, subfolder: string) {
|
||||
let uploadPath
|
||||
|
||||
const fileSizeMB = file.size / 1024 / 1024
|
||||
if (fileSizeMB > this.MAX_UPLOAD_SIZE_MB) {
|
||||
const message = t('toastMessages.fileTooLarge', {
|
||||
size: fileSizeMB.toFixed(1),
|
||||
maxSize: this.MAX_UPLOAD_SIZE_MB
|
||||
})
|
||||
console.warn(
|
||||
'[Load3D] uploadFile: file too large',
|
||||
fileSizeMB.toFixed(2),
|
||||
'MB'
|
||||
)
|
||||
useToastStore().addAlert(message)
|
||||
return undefined
|
||||
}
|
||||
|
||||
try {
|
||||
const body = new FormData()
|
||||
body.append('image', file)
|
||||
@@ -61,7 +78,7 @@ class Load3dUtils {
|
||||
useToastStore().addAlert(resp.status + ' - ' + resp.statusText)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Upload error:', error)
|
||||
console.error('[Load3D] uploadFile: exception', error)
|
||||
useToastStore().addAlert(
|
||||
error instanceof Error
|
||||
? error.message
|
||||
|
||||
@@ -3,9 +3,10 @@ import * as THREE from 'three'
|
||||
import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader'
|
||||
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
|
||||
import { MTLLoader } from 'three/examples/jsm/loaders/MTLLoader'
|
||||
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader'
|
||||
import { PLYLoader } from 'three/examples/jsm/loaders/PLYLoader'
|
||||
import { STLLoader } from 'three/examples/jsm/loaders/STLLoader'
|
||||
import { MtlObjBridge, OBJLoader2Parallel } from 'wwobjloader2'
|
||||
import OBJLoader2WorkerUrl from 'wwobjloader2/worker?url'
|
||||
|
||||
import { t } from '@/i18n'
|
||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||
@@ -22,7 +23,7 @@ import { FastPLYLoader } from './loader/FastPLYLoader'
|
||||
|
||||
export class LoaderManager implements LoaderManagerInterface {
|
||||
gltfLoader: GLTFLoader
|
||||
objLoader: OBJLoader
|
||||
objLoader: OBJLoader2Parallel
|
||||
mtlLoader: MTLLoader
|
||||
fbxLoader: FBXLoader
|
||||
stlLoader: STLLoader
|
||||
@@ -41,7 +42,12 @@ export class LoaderManager implements LoaderManagerInterface {
|
||||
this.eventManager = eventManager
|
||||
|
||||
this.gltfLoader = new GLTFLoader()
|
||||
this.objLoader = new OBJLoader()
|
||||
this.objLoader = new OBJLoader2Parallel()
|
||||
// Set worker URL for Vite compatibility
|
||||
this.objLoader.setWorkerUrl(
|
||||
true,
|
||||
new URL(OBJLoader2WorkerUrl, import.meta.url)
|
||||
)
|
||||
this.mtlLoader = new MTLLoader()
|
||||
this.fbxLoader = new FBXLoader()
|
||||
this.stlLoader = new STLLoader()
|
||||
@@ -173,7 +179,9 @@ export class LoaderManager implements LoaderManagerInterface {
|
||||
|
||||
const materials = await this.mtlLoader.loadAsync(mtlFileName)
|
||||
materials.preload()
|
||||
this.objLoader.setMaterials(materials)
|
||||
const materialsFromMtl =
|
||||
MtlObjBridge.addMaterialsFromMtlLoader(materials)
|
||||
this.objLoader.setMaterials(materialsFromMtl)
|
||||
} catch (e) {
|
||||
console.log(
|
||||
'No MTL file found or error loading it, continuing without materials'
|
||||
@@ -181,8 +189,10 @@ export class LoaderManager implements LoaderManagerInterface {
|
||||
}
|
||||
}
|
||||
|
||||
this.objLoader.setPath(path)
|
||||
model = await this.objLoader.loadAsync(filename)
|
||||
// OBJLoader2Parallel uses Web Worker for parsing (non-blocking)
|
||||
const objUrl = path + encodeURIComponent(filename)
|
||||
model = await this.objLoader.loadAsync(objUrl)
|
||||
|
||||
model.traverse((child) => {
|
||||
if (child instanceof THREE.Mesh) {
|
||||
this.modelManager.originalMaterials.set(child, child.material)
|
||||
@@ -193,7 +203,6 @@ export class LoaderManager implements LoaderManagerInterface {
|
||||
case 'gltf':
|
||||
case 'glb':
|
||||
this.gltfLoader.setPath(path)
|
||||
|
||||
const gltf = await this.gltfLoader.loadAsync(filename)
|
||||
|
||||
this.modelManager.setOriginalModel(gltf)
|
||||
|
||||
@@ -4,8 +4,8 @@ import { ViewHelper } from 'three/examples/jsm/helpers/ViewHelper'
|
||||
import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader'
|
||||
import { type GLTF, GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
|
||||
import { MTLLoader } from 'three/examples/jsm/loaders/MTLLoader'
|
||||
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader'
|
||||
import { STLLoader } from 'three/examples/jsm/loaders/STLLoader'
|
||||
import { type OBJLoader2Parallel } from 'wwobjloader2'
|
||||
|
||||
export type MaterialMode =
|
||||
| 'original'
|
||||
@@ -179,7 +179,7 @@ export interface ModelManagerInterface {
|
||||
|
||||
export interface LoaderManagerInterface {
|
||||
gltfLoader: GLTFLoader
|
||||
objLoader: OBJLoader
|
||||
objLoader: OBJLoader2Parallel
|
||||
mtlLoader: MTLLoader
|
||||
fbxLoader: FBXLoader
|
||||
stlLoader: STLLoader
|
||||
|
||||
@@ -1702,6 +1702,7 @@
|
||||
"pleaseSelectNodesToGroup": "Please select the nodes (or other groups) to create a group for",
|
||||
"emptyCanvas": "Empty canvas",
|
||||
"fileUploadFailed": "File upload failed",
|
||||
"fileTooLarge": "File too large ({size} MB). Maximum supported size is {maxSize} MB",
|
||||
"unableToGetModelFilePath": "Unable to get model file path",
|
||||
"couldNotDetermineFileType": "Could not determine file type",
|
||||
"errorLoadingModel": "Error loading model",
|
||||
|
||||
Reference in New Issue
Block a user