[3d] add support to upload texture (#3224)

Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
Terry Jia
2025-03-24 16:58:45 -04:00
committed by GitHub
parent a1cfb68116
commit abe65e58a0
14 changed files with 205 additions and 10 deletions

View File

@@ -32,6 +32,8 @@ useExtensionService().registerExtension({
(w: IWidget) => w.name === 'model_file'
) as IStringWidget
node.properties['Texture'] = undefined
const uploadPath = await Load3dUtils.uploadFile(
fileInput.files[0]
).catch((error) => {
@@ -70,6 +72,8 @@ useExtensionService().registerExtension({
)
if (modelWidget) {
modelWidget.value = ''
node.properties['Texture'] = undefined
}
})

View File

@@ -61,7 +61,12 @@ class Load3DConfiguration {
if (modelWidget.value) {
onModelWidgetUpdate(modelWidget.value)
}
modelWidget.callback = onModelWidgetUpdate
modelWidget.callback = (value: string | number | boolean | object) => {
this.load3d.node.properties['Texture'] = undefined
onModelWidgetUpdate(value)
}
}
private setupDefaultProperties() {
@@ -136,6 +141,12 @@ class Load3DConfiguration {
this.load3d.setEdgeThreshold(edgeThreshold)
const texturePath = this.load3d.loadNodeProperty('Texture', null)
if (texturePath) {
await this.load3d.applyTexture(texturePath)
}
if (isFirstLoad && cameraState && typeof cameraState === 'object') {
try {
this.load3d.setCameraState(cameraState)

View File

@@ -26,7 +26,7 @@ class Load3d {
renderer: THREE.WebGLRenderer
protected clock: THREE.Clock
protected animationFrameId: number | null = null
protected node: LGraphNode
node: LGraphNode
protected eventManager: EventManager
protected nodeStorage: NodeStorage
@@ -267,6 +267,23 @@ class Load3d {
}
}
async applyTexture(texturePath: string): Promise<void> {
if (!this.modelManager.currentModel) {
throw new Error('No model to apply texture to')
}
this.eventManager.emitEvent('textureLoadingStart', null)
try {
await this.modelManager.applyTexture(texturePath)
} catch (error) {
console.error('Error applying texture:', error)
throw error
} finally {
this.eventManager.emitEvent('textureLoadingEnd', null)
}
}
setBackgroundColor(color: string): void {
this.sceneManager.setBackgroundColor(color)
this.forceRender()

View File

@@ -5,6 +5,7 @@ import { LineSegmentsGeometry } from 'three/examples/jsm/lines/LineSegmentsGeome
import { GLTF } from 'three/examples/jsm/loaders/GLTFLoader'
import { mergeVertices } from 'three/examples/jsm/utils/BufferGeometryUtils'
import Load3dUtils from './Load3dUtils'
import { ColoredShadowMaterial } from './conditional-lines/ColoredShadowMaterial'
import { ConditionalEdgesGeometry } from './conditional-lines/ConditionalEdgesGeometry'
import { ConditionalEdgesShader } from './conditional-lines/ConditionalEdgesShader.js'
@@ -37,6 +38,8 @@ export class ModelManager implements ModelManagerInterface {
depthMaterial: THREE.MeshDepthMaterial
originalFileName: string | null = null
originalURL: string | null = null
appliedTexture: THREE.Texture | null = null
textureLoader: THREE.TextureLoader
private scene: THREE.Scene
private renderer: THREE.WebGLRenderer
@@ -68,6 +71,7 @@ export class ModelManager implements ModelManagerInterface {
this.eventManager = eventManager
this.activeCamera = getActiveCamera()
this.setupCamera = setupCamera
this.textureLoader = new THREE.TextureLoader()
if (
options &&
@@ -113,6 +117,11 @@ export class ModelManager implements ModelManagerInterface {
this.wireframeMaterial.dispose()
this.depthMaterial.dispose()
if (this.appliedTexture) {
this.appliedTexture.dispose()
this.appliedTexture = null
}
this.disposeLineartModel()
}
@@ -126,6 +135,66 @@ export class ModelManager implements ModelManagerInterface {
})
}
async applyTexture(texturePath: string): Promise<void> {
if (!this.currentModel) {
throw new Error('No model available to apply texture to')
}
if (this.appliedTexture) {
this.appliedTexture.dispose()
}
try {
let imageUrl = Load3dUtils.getResourceURL(
...Load3dUtils.splitFilePath(texturePath)
)
if (!imageUrl.startsWith('/api')) {
imageUrl = '/api' + imageUrl
}
this.appliedTexture = await new Promise<THREE.Texture>(
(resolve, reject) => {
this.textureLoader.load(
imageUrl,
(texture) => {
texture.colorSpace = THREE.SRGBColorSpace
texture.wrapS = THREE.RepeatWrapping
texture.wrapT = THREE.RepeatWrapping
resolve(texture)
},
undefined,
(error) => reject(error)
)
}
)
if (this.materialMode === 'original') {
this.currentModel.traverse((child) => {
if (child instanceof THREE.Mesh) {
const material = new THREE.MeshStandardMaterial({
map: this.appliedTexture,
metalness: 0.1,
roughness: 0.8,
side: THREE.DoubleSide
})
if (!this.originalMaterials.has(child)) {
this.originalMaterials.set(child, child.material)
}
child.material = material
}
})
}
return Promise.resolve()
} catch (error) {
console.error('Error applying texture:', error)
return Promise.reject(error)
}
}
disposeLineartModel(): void {
this.disposeEdgesModel()
this.disposeShadowModel()
@@ -576,7 +645,16 @@ export class ModelManager implements ModelManagerInterface {
if (originalMaterial) {
child.material = originalMaterial
} else {
child.material = this.standardMaterial
if (this.appliedTexture) {
child.material = new THREE.MeshStandardMaterial({
map: this.appliedTexture,
metalness: 0.1,
roughness: 0.8,
side: THREE.DoubleSide
})
} else {
child.material = this.standardMaterial
}
}
break
}
@@ -638,6 +716,11 @@ export class ModelManager implements ModelManagerInterface {
this.originalFileName = null
this.originalURL = null
if (this.appliedTexture) {
this.appliedTexture.dispose()
this.appliedTexture = null
}
this.originalMaterials = new WeakMap()
}