1
- // @ts -strict-ignore
2
1
import { app } from '@/scripts/app'
3
2
import { api } from '@/scripts/api'
4
3
import { useToastStore } from '@/stores/toastStore'
@@ -8,6 +7,7 @@ import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
8
7
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader'
9
8
import { MTLLoader } from 'three/examples/jsm/loaders/MTLLoader'
10
9
import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader'
10
+ import { STLLoader } from 'three/examples/jsm/loaders/STLLoader'
11
11
import { IWidget } from '@comfyorg/litegraph'
12
12
import { nextTick } from 'vue'
13
13
@@ -21,7 +21,7 @@ async function uploadFile(
21
21
try {
22
22
const body = new FormData ( )
23
23
body . append ( 'image' , file )
24
- body . append ( 'subfolder' , 'mesh ' )
24
+ body . append ( 'subfolder' , '3d ' )
25
25
26
26
const resp = await api . fetchApi ( '/upload/image' , {
27
27
method : 'POST' ,
@@ -53,7 +53,7 @@ async function uploadFile(
53
53
if ( mtlFile ) {
54
54
const mtlFormData = new FormData ( )
55
55
mtlFormData . append ( 'image' , mtlFile )
56
- mtlFormData . append ( 'subfolder' , 'mesh ' )
56
+ mtlFormData . append ( 'subfolder' , '3d ' )
57
57
58
58
await api . fetchApi ( '/upload/image' , {
59
59
method : 'POST' ,
@@ -86,6 +86,7 @@ class Load3d {
86
86
objLoader : OBJLoader
87
87
mtlLoader : MTLLoader
88
88
fbxLoader : FBXLoader
89
+ stlLoader : STLLoader
89
90
currentModel : THREE . Object3D | null = null
90
91
currentAnimation : THREE . AnimationMixer | null = null
91
92
animationActions : THREE . AnimationAction [ ] = [ ]
@@ -95,6 +96,13 @@ class Load3d {
95
96
gridHelper : THREE . GridHelper
96
97
lights : THREE . Light [ ] = [ ]
97
98
clock : THREE . Clock
99
+ normalMaterial : THREE . MeshNormalMaterial
100
+ standardMaterial : THREE . MeshStandardMaterial
101
+ wireframeMaterial : THREE . MeshBasicMaterial
102
+ originalMaterials : WeakMap < THREE . Mesh , THREE . Material | THREE . Material [ ] > =
103
+ new WeakMap ( )
104
+
105
+ materialMode : 'original' | 'normal' | 'wireframe' = 'original'
98
106
99
107
constructor ( container : Element | HTMLElement ) {
100
108
this . scene = new THREE . Scene ( )
@@ -136,6 +144,7 @@ class Load3d {
136
144
this . objLoader = new OBJLoader ( )
137
145
this . mtlLoader = new MTLLoader ( )
138
146
this . fbxLoader = new FBXLoader ( )
147
+ this . stlLoader = new STLLoader ( )
139
148
this . clock = new THREE . Clock ( )
140
149
141
150
this . setupLights ( )
@@ -144,13 +153,80 @@ class Load3d {
144
153
this . gridHelper . position . set ( 0 , 0 , 0 )
145
154
this . scene . add ( this . gridHelper )
146
155
156
+ this . normalMaterial = new THREE . MeshNormalMaterial ( {
157
+ flatShading : false ,
158
+ side : THREE . DoubleSide ,
159
+ normalScale : new THREE . Vector2 ( 1 , 1 ) ,
160
+ transparent : false ,
161
+ opacity : 1.0
162
+ } )
163
+
164
+ this . wireframeMaterial = new THREE . MeshBasicMaterial ( {
165
+ color : 0xffffff ,
166
+ wireframe : true ,
167
+ transparent : false ,
168
+ opacity : 1.0
169
+ } )
170
+
171
+ this . standardMaterial = this . createSTLMaterial ( )
172
+
147
173
this . animate ( )
148
174
149
175
this . handleResize ( )
150
176
151
177
this . startAnimation ( )
152
178
}
153
179
180
+ setMaterialMode ( mode : 'original' | 'normal' | 'wireframe' ) {
181
+ this . materialMode = mode
182
+
183
+ if ( this . currentModel ) {
184
+ this . currentModel . traverse ( ( child ) => {
185
+ if ( child instanceof THREE . Mesh ) {
186
+ switch ( mode ) {
187
+ case 'normal' :
188
+ if ( ! this . originalMaterials . has ( child ) ) {
189
+ this . originalMaterials . set ( child , child . material )
190
+ }
191
+ child . material = new THREE . MeshNormalMaterial ( {
192
+ flatShading : false ,
193
+ side : THREE . DoubleSide ,
194
+ normalScale : new THREE . Vector2 ( 1 , 1 ) ,
195
+ transparent : false ,
196
+ opacity : 1.0
197
+ } )
198
+ child . geometry . computeVertexNormals ( )
199
+ break
200
+
201
+ case 'wireframe' :
202
+ if ( ! this . originalMaterials . has ( child ) ) {
203
+ this . originalMaterials . set ( child , child . material )
204
+ }
205
+ child . material = new THREE . MeshBasicMaterial ( {
206
+ color : 0xffffff ,
207
+ wireframe : true ,
208
+ transparent : false ,
209
+ opacity : 1.0
210
+ } )
211
+ break
212
+
213
+ case 'original' :
214
+ const originalMaterial = this . originalMaterials . get ( child )
215
+ if ( originalMaterial ) {
216
+ child . material = originalMaterial
217
+ } else {
218
+ child . material = this . standardMaterial
219
+ }
220
+ break
221
+ }
222
+ }
223
+ } )
224
+
225
+ this . renderer . outputColorSpace = THREE . SRGBColorSpace
226
+ this . renderer . render ( this . scene , this . activeCamera )
227
+ }
228
+ }
229
+
154
230
setupLights ( ) {
155
231
const ambientLight = new THREE . AmbientLight ( 0xffffff , 0.5 )
156
232
this . scene . add ( ambientLight )
@@ -321,6 +397,9 @@ class Load3d {
321
397
this . controls . update ( )
322
398
323
399
this . renderer . render ( this . scene , this . activeCamera )
400
+
401
+ this . materialMode = 'original'
402
+ this . originalMaterials = new WeakMap ( )
324
403
}
325
404
326
405
toggleAnimation ( play ?: boolean ) {
@@ -358,17 +437,34 @@ class Load3d {
358
437
359
438
if ( ! fileExtension ) {
360
439
useToastStore ( ) . addAlert ( 'Could not determine file type' )
361
-
362
440
return
363
441
}
364
442
365
443
let model : THREE . Object3D | null = null
366
444
367
445
switch ( fileExtension ) {
446
+ case 'stl' :
447
+ const geometry = await this . stlLoader . loadAsync ( url )
448
+ geometry . computeVertexNormals ( )
449
+
450
+ const mesh = new THREE . Mesh ( geometry , this . standardMaterial )
451
+
452
+ const group = new THREE . Group ( )
453
+ group . add ( mesh )
454
+
455
+ model = group
456
+ break
457
+
368
458
case 'fbx' :
369
459
const fbxModel = await this . fbxLoader . loadAsync ( url )
370
460
model = fbxModel
371
461
462
+ fbxModel . traverse ( ( child ) => {
463
+ if ( child instanceof THREE . Mesh ) {
464
+ this . originalMaterials . set ( child , child . material )
465
+ }
466
+ } )
467
+
372
468
if ( fbxModel . animations . length > 0 ) {
373
469
this . currentAnimation = new THREE . AnimationMixer ( fbxModel )
374
470
this . animationActions = fbxModel . animations . map ( ( clip ) => {
@@ -382,24 +478,39 @@ class Load3d {
382
478
break
383
479
384
480
case 'obj' :
385
- const mtlUrl = url . replace ( / \. o b j ( [ ^ . ] * $ ) / , '.mtl$1' )
386
- try {
387
- const materials = await this . mtlLoader . loadAsync ( mtlUrl )
388
- materials . preload ( )
389
- this . objLoader . setMaterials ( materials )
390
- } catch ( e ) {
391
- console . log (
392
- 'No MTL file found or error loading it, continuing without materials'
393
- )
481
+ if ( this . materialMode === 'original' ) {
482
+ const mtlUrl = url . replace ( / \. o b j ( [ ^ . ] * $ ) / , '.mtl$1' )
483
+ try {
484
+ const materials = await this . mtlLoader . loadAsync ( mtlUrl )
485
+ materials . preload ( )
486
+ this . objLoader . setMaterials ( materials )
487
+ } catch ( e ) {
488
+ console . log (
489
+ 'No MTL file found or error loading it, continuing without materials'
490
+ )
491
+ }
394
492
}
395
493
396
494
model = await this . objLoader . loadAsync ( url )
495
+
496
+ model . traverse ( ( child ) => {
497
+ if ( child instanceof THREE . Mesh ) {
498
+ this . originalMaterials . set ( child , child . material )
499
+ }
500
+ } )
397
501
break
398
502
399
503
case 'gltf' :
400
504
case 'glb' :
401
505
const gltf = await this . gltfLoader . loadAsync ( url )
402
506
model = gltf . scene
507
+
508
+ gltf . scene . traverse ( ( child ) => {
509
+ if ( child instanceof THREE . Mesh ) {
510
+ child . geometry . computeVertexNormals ( )
511
+ this . originalMaterials . set ( child , child . material )
512
+ }
513
+ } )
403
514
break
404
515
405
516
default :
@@ -427,6 +538,10 @@ class Load3d {
427
538
428
539
this . scene . add ( model )
429
540
541
+ if ( this . materialMode !== 'original' ) {
542
+ this . setMaterialMode ( this . materialMode )
543
+ }
544
+
430
545
const distance = Math . max ( size . x , size . z ) * 2
431
546
const height = size . y * 2
432
547
@@ -451,6 +566,10 @@ class Load3d {
451
566
this . controls . target . set ( 0 , size . y / 2 , 0 )
452
567
this . controls . update ( )
453
568
569
+ this . renderer . outputColorSpace = THREE . SRGBColorSpace
570
+ this . renderer . toneMapping = THREE . ACESFilmicToneMapping
571
+ this . renderer . toneMappingExposure = 1
572
+
454
573
this . handleResize ( )
455
574
}
456
575
} catch ( error ) {
@@ -532,6 +651,16 @@ class Load3d {
532
651
} )
533
652
}
534
653
654
+ createSTLMaterial ( ) {
655
+ return new THREE . MeshStandardMaterial ( {
656
+ color : 0x808080 ,
657
+ metalness : 0.1 ,
658
+ roughness : 0.8 ,
659
+ flatShading : false ,
660
+ side : THREE . DoubleSide
661
+ } )
662
+ }
663
+
535
664
setViewPosition ( position : 'front' | 'top' | 'right' | 'isometric' ) {
536
665
const box = new THREE . Box3 ( )
537
666
let center = new THREE . Vector3 ( )
@@ -629,13 +758,13 @@ app.registerExtension({
629
758
load3d . remove ( )
630
759
}
631
760
632
- containerToLoad3D . delete ( container )
761
+ containerToLoad3D . delete ( container . id )
633
762
634
- origOnRemoved ?. apply ( this , arguments )
763
+ origOnRemoved ?. apply ( this , [ ] )
635
764
}
636
765
637
766
node . onDrawBackground = function ( ) {
638
- load3d . renderer . domElement . hidden = this . flags . collapsed
767
+ load3d . renderer . domElement . hidden = this . flags . collapsed ?? false
639
768
}
640
769
641
770
return {
@@ -689,6 +818,12 @@ app.registerExtension({
689
818
const modelUrl = api . apiURL ( getResourceURL ( ...splitFilePath ( filename ) ) )
690
819
691
820
load3d . loadModel ( modelUrl , filename )
821
+
822
+ const material = node . widgets . find (
823
+ ( w : IWidget ) => w . name === 'material'
824
+ )
825
+
826
+ load3d . setMaterialMode ( material . value )
692
827
}
693
828
}
694
829
@@ -722,6 +857,14 @@ app.registerExtension({
722
857
load3d . setViewPosition ( value )
723
858
}
724
859
860
+ const material = node . widgets . find ( ( w : IWidget ) => w . name === 'material' )
861
+
862
+ material . callback = ( value : 'original' | 'normal' | 'wireframe' ) => {
863
+ load3d . setMaterialMode ( value )
864
+ }
865
+
866
+ load3d . setMaterialMode ( material . value )
867
+
725
868
const bgColor = node . widgets . find ( ( w : IWidget ) => w . name === 'bg_color' )
726
869
727
870
load3d . setBackgroundColor ( bgColor . value )
@@ -774,7 +917,7 @@ app.registerExtension({
774
917
775
918
const fileInput = document . createElement ( 'input' )
776
919
fileInput . type = 'file'
777
- fileInput . accept = '.gltf,.glb,.obj,.mtl,.fbx'
920
+ fileInput . accept = '.gltf,.glb,.obj,.mtl,.fbx,.stl '
778
921
fileInput . style . display = 'none'
779
922
fileInput . onchange = ( ) => {
780
923
if ( fileInput . files ?. length ) {
0 commit comments