1- import type {
2- IComboWidget ,
3- IStringWidget
4- } from '@comfyorg/litegraph/dist/types/widgets'
1+ import type { IStringWidget } from '@comfyorg/litegraph/dist/types/widgets'
52import { nextTick } from 'vue'
63
74import Load3D from '@/components/load3d/Load3D.vue'
@@ -17,6 +14,80 @@ import { useExtensionService } from '@/services/extensionService'
1714import { useLoad3dService } from '@/services/load3dService'
1815import { useToastStore } from '@/stores/toastStore'
1916
17+ async function handleModelUpload ( files : FileList , node : any ) {
18+ if ( ! files ?. length ) return
19+
20+ const modelWidget = node . widgets ?. find (
21+ ( w : any ) => w . name === 'model_file'
22+ ) as IStringWidget
23+
24+ node . properties [ 'Texture' ] = undefined
25+
26+ try {
27+ const resourceFolder = ( node . properties [ 'Resource Folder' ] as string ) || ''
28+
29+ const subfolder = resourceFolder . trim ( )
30+ ? `3d/${ resourceFolder . trim ( ) } `
31+ : '3d'
32+
33+ const uploadPath = await Load3dUtils . uploadFile ( files [ 0 ] , subfolder )
34+
35+ if ( ! uploadPath ) {
36+ useToastStore ( ) . addAlert ( t ( 'toastMessages.fileUploadFailed' ) )
37+ return
38+ }
39+
40+ const modelUrl = api . apiURL (
41+ Load3dUtils . getResourceURL (
42+ ...Load3dUtils . splitFilePath ( uploadPath ) ,
43+ 'input'
44+ )
45+ )
46+
47+ await useLoad3dService ( ) . getLoad3d ( node ) ?. loadModel ( modelUrl )
48+
49+ if ( uploadPath && modelWidget ) {
50+ if ( ! modelWidget . options ?. values ?. includes ( uploadPath ) ) {
51+ modelWidget . options ?. values ?. push ( uploadPath )
52+ }
53+
54+ modelWidget . value = uploadPath
55+ }
56+ } catch ( error ) {
57+ console . error ( 'Model upload failed:' , error )
58+ useToastStore ( ) . addAlert ( t ( 'toastMessages.fileUploadFailed' ) )
59+ }
60+ }
61+
62+ async function handleResourcesUpload ( files : FileList , node : any ) {
63+ if ( ! files ?. length ) return
64+
65+ try {
66+ const resourceFolder = ( node . properties [ 'Resource Folder' ] as string ) || ''
67+
68+ const subfolder = resourceFolder . trim ( )
69+ ? `3d/${ resourceFolder . trim ( ) } `
70+ : '3d'
71+
72+ await Load3dUtils . uploadMultipleFiles ( files , subfolder )
73+ } catch ( error ) {
74+ console . error ( 'Extra resources upload failed:' , error )
75+ useToastStore ( ) . addAlert ( t ( 'toastMessages.extraResourcesUploadFailed' ) )
76+ }
77+ }
78+
79+ function createFileInput (
80+ accept : string ,
81+ multiple : boolean = false
82+ ) : HTMLInputElement {
83+ const input = document . createElement ( 'input' )
84+ input . type = 'file'
85+ input . accept = accept
86+ input . multiple = multiple
87+ input . style . display = 'none'
88+ return input
89+ }
90+
2091useExtensionService ( ) . registerExtension ( {
2192 name : 'Comfy.Load3D' ,
2293 settings : [
@@ -110,49 +181,34 @@ useExtensionService().registerExtension({
110181 getCustomWidgets ( ) {
111182 return {
112183 LOAD_3D ( node ) {
113- const fileInput = document . createElement ( 'input' )
114- fileInput . type = 'file'
115- fileInput . accept = '.gltf,.glb,.obj,.fbx,.stl'
116- fileInput . style . display = 'none'
184+ const fileInput = createFileInput ( '.gltf,.glb,.obj,.fbx,.stl' , false )
117185
118- fileInput . onchange = async ( ) => {
119- if ( fileInput . files ?. length ) {
120- const modelWidget = node . widgets ?. find (
121- ( w ) => w . name === 'model_file'
122- ) as IComboWidget & { options : { values : string [ ] } }
186+ node . properties [ 'Resource Folder' ] = ''
123187
124- node . properties [ 'Texture' ] = undefined
125-
126- const uploadPath = await Load3dUtils . uploadFile (
127- fileInput . files [ 0 ]
128- ) . catch ( ( error ) => {
129- console . error ( 'File upload failed:' , error )
130- useToastStore ( ) . addAlert ( t ( 'toastMessages.fileUploadFailed' ) )
131- } )
132-
133- const modelUrl = api . apiURL (
134- Load3dUtils . getResourceURL (
135- ...Load3dUtils . splitFilePath ( uploadPath ) ,
136- 'input'
137- )
138- )
139-
140- await useLoad3dService ( ) . getLoad3d ( node ) ?. loadModel ( modelUrl )
141-
142- if ( uploadPath && modelWidget ) {
143- if ( ! modelWidget . options ?. values ?. includes ( uploadPath ) ) {
144- modelWidget . options ?. values ?. push ( uploadPath )
145- }
146-
147- modelWidget . value = uploadPath
148- }
149- }
188+ fileInput . onchange = async ( ) => {
189+ await handleModelUpload ( fileInput . files ! , node )
150190 }
151191
152192 node . addWidget ( 'button' , 'upload 3d model' , 'upload3dmodel' , ( ) => {
153193 fileInput . click ( )
154194 } )
155195
196+ const resourcesInput = createFileInput ( '*' , true )
197+
198+ resourcesInput . onchange = async ( ) => {
199+ await handleResourcesUpload ( resourcesInput . files ! , node )
200+ resourcesInput . value = ''
201+ }
202+
203+ node . addWidget (
204+ 'button' ,
205+ 'upload extra resources' ,
206+ 'uploadExtraResources' ,
207+ ( ) => {
208+ resourcesInput . click ( )
209+ }
210+ )
211+
156212 node . addWidget ( 'button' , 'clear' , 'clear' , ( ) => {
157213 useLoad3dService ( ) . getLoad3d ( node ) ?. clearModel ( )
158214
@@ -264,46 +320,34 @@ useExtensionService().registerExtension({
264320 getCustomWidgets ( ) {
265321 return {
266322 LOAD_3D_ANIMATION ( node ) {
267- const fileInput = document . createElement ( 'input' )
268- fileInput . type = 'file'
269- fileInput . accept = '.gltf,.glb,.fbx '
270- fileInput . style . display = 'none'
323+ const fileInput = createFileInput ( '.gltf,.glb,.fbx' , false )
324+
325+ node . properties [ 'Resource Folder' ] = ''
326+
271327 fileInput . onchange = async ( ) => {
272- if ( fileInput . files ?. length ) {
273- const modelWidget = node . widgets ?. find (
274- ( w ) => w . name === 'model_file'
275- ) as IStringWidget
276-
277- const uploadPath = await Load3dUtils . uploadFile (
278- fileInput . files [ 0 ]
279- ) . catch ( ( error ) => {
280- console . error ( 'File upload failed:' , error )
281- useToastStore ( ) . addAlert ( t ( 'toastMessages.fileUploadFailed' ) )
282- } )
283-
284- const modelUrl = api . apiURL (
285- Load3dUtils . getResourceURL (
286- ...Load3dUtils . splitFilePath ( uploadPath ) ,
287- 'input'
288- )
289- )
290-
291- await useLoad3dService ( ) . getLoad3d ( node ) ?. loadModel ( modelUrl )
292-
293- if ( uploadPath && modelWidget ) {
294- if ( ! modelWidget . options ?. values ?. includes ( uploadPath ) ) {
295- modelWidget . options ?. values ?. push ( uploadPath )
296- }
297-
298- modelWidget . value = uploadPath
299- }
300- }
328+ await handleModelUpload ( fileInput . files ! , node )
301329 }
302330
303331 node . addWidget ( 'button' , 'upload 3d model' , 'upload3dmodel' , ( ) => {
304332 fileInput . click ( )
305333 } )
306334
335+ const resourcesInput = createFileInput ( '*' , true )
336+
337+ resourcesInput . onchange = async ( ) => {
338+ await handleResourcesUpload ( resourcesInput . files ! , node )
339+ resourcesInput . value = ''
340+ }
341+
342+ node . addWidget (
343+ 'button' ,
344+ 'upload extra resources' ,
345+ 'uploadExtraResources' ,
346+ ( ) => {
347+ resourcesInput . click ( )
348+ }
349+ )
350+
307351 node . addWidget ( 'button' , 'clear' , 'clear' , ( ) => {
308352 useLoad3dService ( ) . getLoad3d ( node ) ?. clearModel ( )
309353
0 commit comments