mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-30 03:01:54 +00:00
[3d] support using image as background (#2657)
Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
@@ -9,6 +9,7 @@
|
|||||||
:fov="fov"
|
:fov="fov"
|
||||||
:cameraType="cameraType"
|
:cameraType="cameraType"
|
||||||
:showPreview="showPreview"
|
:showPreview="showPreview"
|
||||||
|
:backgroundImage="backgroundImage"
|
||||||
@materialModeChange="listenMaterialModeChange"
|
@materialModeChange="listenMaterialModeChange"
|
||||||
@backgroundColorChange="listenBackgroundColorChange"
|
@backgroundColorChange="listenBackgroundColorChange"
|
||||||
@lightIntensityChange="listenLightIntensityChange"
|
@lightIntensityChange="listenLightIntensityChange"
|
||||||
@@ -16,6 +17,7 @@
|
|||||||
@cameraTypeChange="listenCameraTypeChange"
|
@cameraTypeChange="listenCameraTypeChange"
|
||||||
@showGridChange="listenShowGridChange"
|
@showGridChange="listenShowGridChange"
|
||||||
@showPreviewChange="listenShowPreviewChange"
|
@showPreviewChange="listenShowPreviewChange"
|
||||||
|
@backgroundImageChange="listenBackgroundImageChange"
|
||||||
/>
|
/>
|
||||||
<Load3DControls
|
<Load3DControls
|
||||||
:backgroundColor="backgroundColor"
|
:backgroundColor="backgroundColor"
|
||||||
@@ -27,6 +29,8 @@
|
|||||||
:showFOVButton="showFOVButton"
|
:showFOVButton="showFOVButton"
|
||||||
:showPreviewButton="showPreviewButton"
|
:showPreviewButton="showPreviewButton"
|
||||||
:cameraType="cameraType"
|
:cameraType="cameraType"
|
||||||
|
:hasBackgroundImage="hasBackgroundImage"
|
||||||
|
@updateBackgroundImage="handleBackgroundImageUpdate"
|
||||||
@switchCamera="switchCamera"
|
@switchCamera="switchCamera"
|
||||||
@toggleGrid="toggleGrid"
|
@toggleGrid="toggleGrid"
|
||||||
@updateBackgroundColor="handleBackgroundColorChange"
|
@updateBackgroundColor="handleBackgroundColorChange"
|
||||||
@@ -42,6 +46,7 @@ import { computed, ref } from 'vue'
|
|||||||
|
|
||||||
import Load3DControls from '@/components/load3d/Load3DControls.vue'
|
import Load3DControls from '@/components/load3d/Load3DControls.vue'
|
||||||
import Load3DScene from '@/components/load3d/Load3DScene.vue'
|
import Load3DScene from '@/components/load3d/Load3DScene.vue'
|
||||||
|
import Load3dUtils from '@/extensions/core/load3d/Load3dUtils'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
node: any
|
node: any
|
||||||
@@ -57,6 +62,8 @@ const showLightIntensityButton = ref(true)
|
|||||||
const fov = ref(75)
|
const fov = ref(75)
|
||||||
const showFOVButton = ref(true)
|
const showFOVButton = ref(true)
|
||||||
const cameraType = ref<'perspective' | 'orthographic'>('perspective')
|
const cameraType = ref<'perspective' | 'orthographic'>('perspective')
|
||||||
|
const hasBackgroundImage = ref(false)
|
||||||
|
const backgroundImage = ref('')
|
||||||
|
|
||||||
const showPreviewButton = computed(() => {
|
const showPreviewButton = computed(() => {
|
||||||
return !props.type.includes('Preview')
|
return !props.type.includes('Preview')
|
||||||
@@ -89,6 +96,19 @@ const handleUpdateLightIntensity = (value: number) => {
|
|||||||
node.value.properties['Light Intensity'] = lightIntensity.value
|
node.value.properties['Light Intensity'] = lightIntensity.value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleBackgroundImageUpdate = async (file: File | null) => {
|
||||||
|
if (!file) {
|
||||||
|
hasBackgroundImage.value = false
|
||||||
|
backgroundImage.value = ''
|
||||||
|
node.value.properties['Background Image'] = ''
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
backgroundImage.value = await Load3dUtils.uploadFile(file)
|
||||||
|
|
||||||
|
node.value.properties['Background Image'] = backgroundImage.value
|
||||||
|
}
|
||||||
|
|
||||||
const handleUpdateFOV = (value: number) => {
|
const handleUpdateFOV = (value: number) => {
|
||||||
fov.value = value
|
fov.value = value
|
||||||
|
|
||||||
@@ -137,4 +157,12 @@ const listenShowGridChange = (value: boolean) => {
|
|||||||
const listenShowPreviewChange = (value: boolean) => {
|
const listenShowPreviewChange = (value: boolean) => {
|
||||||
showPreview.value = value
|
showPreview.value = value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const listenBackgroundImageChange = (value: string) => {
|
||||||
|
backgroundImage.value = value
|
||||||
|
|
||||||
|
if (backgroundImage.value && backgroundImage.value !== '') {
|
||||||
|
hasBackgroundImage.value = true
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
:playing="playing"
|
:playing="playing"
|
||||||
:selectedSpeed="selectedSpeed"
|
:selectedSpeed="selectedSpeed"
|
||||||
:selectedAnimation="selectedAnimation"
|
:selectedAnimation="selectedAnimation"
|
||||||
|
:backgroundImage="backgroundImage"
|
||||||
@materialModeChange="listenMaterialModeChange"
|
@materialModeChange="listenMaterialModeChange"
|
||||||
@backgroundColorChange="listenBackgroundColorChange"
|
@backgroundColorChange="listenBackgroundColorChange"
|
||||||
@lightIntensityChange="listenLightIntensityChange"
|
@lightIntensityChange="listenLightIntensityChange"
|
||||||
@@ -22,6 +23,7 @@
|
|||||||
@cameraTypeChange="listenCameraTypeChange"
|
@cameraTypeChange="listenCameraTypeChange"
|
||||||
@showGridChange="listenShowGridChange"
|
@showGridChange="listenShowGridChange"
|
||||||
@showPreviewChange="listenShowPreviewChange"
|
@showPreviewChange="listenShowPreviewChange"
|
||||||
|
@backgroundImageChange="listenBackgroundImageChange"
|
||||||
@animationListChange="animationListChange"
|
@animationListChange="animationListChange"
|
||||||
/>
|
/>
|
||||||
<div class="absolute top-0 left-0 w-full h-full pointer-events-none">
|
<div class="absolute top-0 left-0 w-full h-full pointer-events-none">
|
||||||
@@ -35,6 +37,8 @@
|
|||||||
:showFOVButton="showFOVButton"
|
:showFOVButton="showFOVButton"
|
||||||
:showPreviewButton="showPreviewButton"
|
:showPreviewButton="showPreviewButton"
|
||||||
:cameraType="cameraType"
|
:cameraType="cameraType"
|
||||||
|
:hasBackgroundImage="hasBackgroundImage"
|
||||||
|
@updateBackgroundImage="handleBackgroundImageUpdate"
|
||||||
@switchCamera="switchCamera"
|
@switchCamera="switchCamera"
|
||||||
@toggleGrid="toggleGrid"
|
@toggleGrid="toggleGrid"
|
||||||
@updateBackgroundColor="handleBackgroundColorChange"
|
@updateBackgroundColor="handleBackgroundColorChange"
|
||||||
@@ -60,6 +64,7 @@ import Load3DAnimationControls from '@/components/load3d/Load3DAnimationControls
|
|||||||
import Load3DAnimationScene from '@/components/load3d/Load3DAnimationScene.vue'
|
import Load3DAnimationScene from '@/components/load3d/Load3DAnimationScene.vue'
|
||||||
import Load3DControls from '@/components/load3d/Load3DControls.vue'
|
import Load3DControls from '@/components/load3d/Load3DControls.vue'
|
||||||
import type { AnimationItem } from '@/extensions/core/load3d/Load3dAnimation'
|
import type { AnimationItem } from '@/extensions/core/load3d/Load3dAnimation'
|
||||||
|
import Load3dUtils from '@/extensions/core/load3d/Load3dUtils'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
node: any
|
node: any
|
||||||
@@ -75,11 +80,13 @@ const showLightIntensityButton = ref(true)
|
|||||||
const fov = ref(75)
|
const fov = ref(75)
|
||||||
const showFOVButton = ref(true)
|
const showFOVButton = ref(true)
|
||||||
const cameraType = ref<'perspective' | 'orthographic'>('perspective')
|
const cameraType = ref<'perspective' | 'orthographic'>('perspective')
|
||||||
|
const hasBackgroundImage = ref(false)
|
||||||
|
|
||||||
const animations = ref<AnimationItem[]>([])
|
const animations = ref<AnimationItem[]>([])
|
||||||
const playing = ref(false)
|
const playing = ref(false)
|
||||||
const selectedSpeed = ref(1)
|
const selectedSpeed = ref(1)
|
||||||
const selectedAnimation = ref(0)
|
const selectedAnimation = ref(0)
|
||||||
|
const backgroundImage = ref('')
|
||||||
|
|
||||||
const showPreviewButton = computed(() => {
|
const showPreviewButton = computed(() => {
|
||||||
return !props.type.includes('Preview')
|
return !props.type.includes('Preview')
|
||||||
@@ -112,6 +119,19 @@ const handleUpdateLightIntensity = (value: number) => {
|
|||||||
node.value.properties['Light Intensity'] = lightIntensity.value
|
node.value.properties['Light Intensity'] = lightIntensity.value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleBackgroundImageUpdate = async (file: File | null) => {
|
||||||
|
if (!file) {
|
||||||
|
hasBackgroundImage.value = false
|
||||||
|
backgroundImage.value = ''
|
||||||
|
node.value.properties['Background Image'] = ''
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
backgroundImage.value = await Load3dUtils.uploadFile(file)
|
||||||
|
|
||||||
|
node.value.properties['Background Image'] = backgroundImage.value
|
||||||
|
}
|
||||||
|
|
||||||
const handleUpdateFOV = (value: number) => {
|
const handleUpdateFOV = (value: number) => {
|
||||||
fov.value = value
|
fov.value = value
|
||||||
|
|
||||||
@@ -177,4 +197,12 @@ const listenShowGridChange = (value: boolean) => {
|
|||||||
const listenShowPreviewChange = (value: boolean) => {
|
const listenShowPreviewChange = (value: boolean) => {
|
||||||
showPreview.value = value
|
showPreview.value = value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const listenBackgroundImageChange = (value: string) => {
|
||||||
|
backgroundImage.value = value
|
||||||
|
|
||||||
|
if (backgroundImage.value && backgroundImage.value !== '') {
|
||||||
|
hasBackgroundImage.value = true
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
:cameraType="cameraType"
|
:cameraType="cameraType"
|
||||||
:showPreview="showPreview"
|
:showPreview="showPreview"
|
||||||
:extraListeners="animationListeners"
|
:extraListeners="animationListeners"
|
||||||
|
:backgroundImage="backgroundImage"
|
||||||
@materialModeChange="listenMaterialModeChange"
|
@materialModeChange="listenMaterialModeChange"
|
||||||
@backgroundColorChange="listenBackgroundColorChange"
|
@backgroundColorChange="listenBackgroundColorChange"
|
||||||
@lightIntensityChange="listenLightIntensityChange"
|
@lightIntensityChange="listenLightIntensityChange"
|
||||||
@@ -39,6 +40,7 @@ const props = defineProps<{
|
|||||||
playing: boolean
|
playing: boolean
|
||||||
selectedSpeed: number
|
selectedSpeed: number
|
||||||
selectedAnimation: number
|
selectedAnimation: number
|
||||||
|
backgroundImage: string
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const node = ref(props.node)
|
const node = ref(props.node)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="absolute top-2 left-2 flex flex-col gap-2 pointer-events-auto z-20"
|
class="absolute top-2 left-2 flex flex-col pointer-events-auto z-20 bg-gray-700 bg-opacity-30 rounded-lg"
|
||||||
>
|
>
|
||||||
<Button class="p-button-rounded p-button-text" @click="switchCamera">
|
<Button class="p-button-rounded p-button-text" @click="switchCamera">
|
||||||
<i
|
<i
|
||||||
@@ -20,21 +20,60 @@
|
|||||||
></i>
|
></i>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button class="p-button-rounded p-button-text" @click="openColorPicker">
|
<div v-if="!hasBackgroundImage">
|
||||||
<i
|
<Button class="p-button-rounded p-button-text" @click="openColorPicker">
|
||||||
class="pi pi-palette text-white text-lg"
|
<i
|
||||||
v-tooltip.right="{ value: t('load3d.backgroundColor'), showDelay: 300 }"
|
class="pi pi-palette text-white text-lg"
|
||||||
></i>
|
v-tooltip.right="{
|
||||||
<input
|
value: t('load3d.backgroundColor'),
|
||||||
type="color"
|
showDelay: 300
|
||||||
ref="colorPickerRef"
|
}"
|
||||||
:value="backgroundColor"
|
></i>
|
||||||
@input="
|
<input
|
||||||
updateBackgroundColor(($event.target as HTMLInputElement).value)
|
type="color"
|
||||||
"
|
ref="colorPickerRef"
|
||||||
class="absolute opacity-0 w-0 h-0 p-0 m-0 pointer-events-none"
|
:value="backgroundColor"
|
||||||
/>
|
@input="
|
||||||
</Button>
|
updateBackgroundColor(($event.target as HTMLInputElement).value)
|
||||||
|
"
|
||||||
|
class="absolute opacity-0 w-0 h-0 p-0 m-0 pointer-events-none"
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="!hasBackgroundImage">
|
||||||
|
<Button class="p-button-rounded p-button-text" @click="openImagePicker">
|
||||||
|
<i
|
||||||
|
class="pi pi-image text-white text-lg"
|
||||||
|
v-tooltip.right="{
|
||||||
|
value: t('load3d.uploadBackgroundImage'),
|
||||||
|
showDelay: 300
|
||||||
|
}"
|
||||||
|
></i>
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
ref="imagePickerRef"
|
||||||
|
accept="image/*"
|
||||||
|
@change="uploadBackgroundImage"
|
||||||
|
class="absolute opacity-0 w-0 h-0 p-0 m-0 pointer-events-none"
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="hasBackgroundImage">
|
||||||
|
<Button
|
||||||
|
class="p-button-rounded p-button-text"
|
||||||
|
@click="removeBackgroundImage"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="pi pi-times text-white text-lg"
|
||||||
|
v-tooltip.right="{
|
||||||
|
value: t('load3d.removeBackgroundImage'),
|
||||||
|
showDelay: 300
|
||||||
|
}"
|
||||||
|
></i>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="relative show-light-intensity" v-if="showLightIntensityButton">
|
<div class="relative show-light-intensity" v-if="showLightIntensityButton">
|
||||||
<Button
|
<Button
|
||||||
@@ -123,6 +162,7 @@ const props = defineProps<{
|
|||||||
showFOVButton: boolean
|
showFOVButton: boolean
|
||||||
showPreviewButton: boolean
|
showPreviewButton: boolean
|
||||||
cameraType: 'perspective' | 'orthographic'
|
cameraType: 'perspective' | 'orthographic'
|
||||||
|
hasBackgroundImage?: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
@@ -132,6 +172,7 @@ const emit = defineEmits<{
|
|||||||
(e: 'updateLightIntensity', value: number): void
|
(e: 'updateLightIntensity', value: number): void
|
||||||
(e: 'updateFOV', value: number): void
|
(e: 'updateFOV', value: number): void
|
||||||
(e: 'togglePreview', value: boolean): void
|
(e: 'togglePreview', value: boolean): void
|
||||||
|
(e: 'updateBackgroundImage', file: File | null): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const backgroundColor = ref(props.backgroundColor)
|
const backgroundColor = ref(props.backgroundColor)
|
||||||
@@ -145,6 +186,8 @@ const fov = ref(props.fov)
|
|||||||
const showFOV = ref(false)
|
const showFOV = ref(false)
|
||||||
const showFOVButton = ref(props.showFOVButton)
|
const showFOVButton = ref(props.showFOVButton)
|
||||||
const showPreviewButton = ref(props.showPreviewButton)
|
const showPreviewButton = ref(props.showPreviewButton)
|
||||||
|
const hasBackgroundImage = ref(props.hasBackgroundImage)
|
||||||
|
const imagePickerRef = ref<HTMLInputElement | null>(null)
|
||||||
|
|
||||||
const switchCamera = () => {
|
const switchCamera = () => {
|
||||||
emit('switchCamera')
|
emit('switchCamera')
|
||||||
@@ -196,6 +239,26 @@ const closeSlider = (e: MouseEvent) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const openImagePicker = () => {
|
||||||
|
imagePickerRef.value?.click()
|
||||||
|
}
|
||||||
|
|
||||||
|
const uploadBackgroundImage = (event: Event) => {
|
||||||
|
const input = event.target as HTMLInputElement
|
||||||
|
|
||||||
|
hasBackgroundImage.value = true
|
||||||
|
|
||||||
|
if (input.files && input.files[0]) {
|
||||||
|
emit('updateBackgroundImage', input.files[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeBackgroundImage = () => {
|
||||||
|
hasBackgroundImage.value = false
|
||||||
|
|
||||||
|
emit('updateBackgroundImage', null)
|
||||||
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.backgroundColor,
|
() => props.backgroundColor,
|
||||||
(newValue) => {
|
(newValue) => {
|
||||||
@@ -245,6 +308,13 @@ watch(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.hasBackgroundImage,
|
||||||
|
(newValue) => {
|
||||||
|
hasBackgroundImage.value = newValue
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
document.addEventListener('click', closeSlider)
|
document.addEventListener('click', closeSlider)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ const props = defineProps<{
|
|||||||
fov: number
|
fov: number
|
||||||
cameraType: 'perspective' | 'orthographic'
|
cameraType: 'perspective' | 'orthographic'
|
||||||
showPreview: boolean
|
showPreview: boolean
|
||||||
|
backgroundImage: string
|
||||||
extraListeners?: Record<string, (value: any) => void>
|
extraListeners?: Record<string, (value: any) => void>
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
@@ -34,7 +35,8 @@ const eventConfig = {
|
|||||||
fovChange: (value: number) => emit('fovChange', value),
|
fovChange: (value: number) => emit('fovChange', value),
|
||||||
cameraTypeChange: (value: string) => emit('cameraTypeChange', value),
|
cameraTypeChange: (value: string) => emit('cameraTypeChange', value),
|
||||||
showGridChange: (value: boolean) => emit('showGridChange', value),
|
showGridChange: (value: boolean) => emit('showGridChange', value),
|
||||||
showPreviewChange: (value: boolean) => emit('showPreviewChange', value)
|
showPreviewChange: (value: boolean) => emit('showPreviewChange', value),
|
||||||
|
backgroundImageChange: (value: string) => emit('backgroundImageChange', value)
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
@@ -47,6 +49,7 @@ watchEffect(() => {
|
|||||||
rawLoad3d.setFOV(props.fov)
|
rawLoad3d.setFOV(props.fov)
|
||||||
rawLoad3d.toggleCamera(props.cameraType)
|
rawLoad3d.toggleCamera(props.cameraType)
|
||||||
rawLoad3d.togglePreview(props.showPreview)
|
rawLoad3d.togglePreview(props.showPreview)
|
||||||
|
rawLoad3d.setBackgroundImage(props.backgroundImage)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -58,6 +61,7 @@ const emit = defineEmits<{
|
|||||||
(e: 'cameraTypeChange', cameraType: string): void
|
(e: 'cameraTypeChange', cameraType: string): void
|
||||||
(e: 'showGridChange', showGrid: boolean): void
|
(e: 'showGridChange', showGrid: boolean): void
|
||||||
(e: 'showPreviewChange', showPreview: boolean): void
|
(e: 'showPreviewChange', showPreview: boolean): void
|
||||||
|
(e: 'backgroundImageChange', backgroundImage: string): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const handleEvents = (action: 'add' | 'remove') => {
|
const handleEvents = (action: 'add' | 'remove') => {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import Load3DAnimation from '@/components/load3d/Load3DAnimation.vue'
|
|||||||
import Load3DConfiguration from '@/extensions/core/load3d/Load3DConfiguration'
|
import Load3DConfiguration from '@/extensions/core/load3d/Load3DConfiguration'
|
||||||
import Load3dAnimation from '@/extensions/core/load3d/Load3dAnimation'
|
import Load3dAnimation from '@/extensions/core/load3d/Load3dAnimation'
|
||||||
import Load3dUtils from '@/extensions/core/load3d/Load3dUtils'
|
import Load3dUtils from '@/extensions/core/load3d/Load3dUtils'
|
||||||
|
import { api } from '@/scripts/api'
|
||||||
import { app } from '@/scripts/app'
|
import { app } from '@/scripts/app'
|
||||||
import { useLoad3dService } from '@/services/load3dService'
|
import { useLoad3dService } from '@/services/load3dService'
|
||||||
import { useToastStore } from '@/stores/toastStore'
|
import { useToastStore } from '@/stores/toastStore'
|
||||||
@@ -71,14 +72,21 @@ app.registerExtension({
|
|||||||
) as IStringWidget
|
) as IStringWidget
|
||||||
|
|
||||||
const uploadPath = await Load3dUtils.uploadFile(
|
const uploadPath = await Load3dUtils.uploadFile(
|
||||||
useLoad3dService().getLoad3d(node),
|
fileInput.files[0]
|
||||||
fileInput.files[0],
|
|
||||||
fileInput
|
|
||||||
).catch((error) => {
|
).catch((error) => {
|
||||||
console.error('File upload failed:', error)
|
console.error('File upload failed:', error)
|
||||||
useToastStore().addAlert('File upload failed')
|
useToastStore().addAlert('File upload failed')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const modelUrl = api.apiURL(
|
||||||
|
Load3dUtils.getResourceURL(
|
||||||
|
...Load3dUtils.splitFilePath(uploadPath),
|
||||||
|
'input'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
await useLoad3dService().getLoad3d(node).loadModel(modelUrl)
|
||||||
|
|
||||||
if (uploadPath && modelWidget) {
|
if (uploadPath && modelWidget) {
|
||||||
if (!modelWidget.options?.values?.includes(uploadPath)) {
|
if (!modelWidget.options?.values?.includes(uploadPath)) {
|
||||||
modelWidget.options?.values?.push(uploadPath)
|
modelWidget.options?.values?.push(uploadPath)
|
||||||
@@ -232,15 +240,23 @@ app.registerExtension({
|
|||||||
const modelWidget = node.widgets?.find(
|
const modelWidget = node.widgets?.find(
|
||||||
(w: IWidget) => w.name === 'model_file'
|
(w: IWidget) => w.name === 'model_file'
|
||||||
) as IStringWidget
|
) as IStringWidget
|
||||||
|
|
||||||
const uploadPath = await Load3dUtils.uploadFile(
|
const uploadPath = await Load3dUtils.uploadFile(
|
||||||
useLoad3dService().getLoad3d(node),
|
fileInput.files[0]
|
||||||
fileInput.files[0],
|
|
||||||
fileInput
|
|
||||||
).catch((error) => {
|
).catch((error) => {
|
||||||
console.error('File upload failed:', error)
|
console.error('File upload failed:', error)
|
||||||
useToastStore().addAlert('File upload failed')
|
useToastStore().addAlert('File upload failed')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const modelUrl = api.apiURL(
|
||||||
|
Load3dUtils.getResourceURL(
|
||||||
|
...Load3dUtils.splitFilePath(uploadPath),
|
||||||
|
'input'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
await useLoad3dService().getLoad3d(node).loadModel(modelUrl)
|
||||||
|
|
||||||
if (uploadPath && modelWidget) {
|
if (uploadPath && modelWidget) {
|
||||||
if (!modelWidget.options?.values?.includes(uploadPath)) {
|
if (!modelWidget.options?.values?.includes(uploadPath)) {
|
||||||
modelWidget.options?.values?.push(uploadPath)
|
modelWidget.options?.values?.push(uploadPath)
|
||||||
|
|||||||
@@ -106,6 +106,10 @@ class Load3DConfiguration {
|
|||||||
const fov = this.load3d.loadNodeProperty('FOV', 75)
|
const fov = this.load3d.loadNodeProperty('FOV', 75)
|
||||||
|
|
||||||
this.load3d.setFOV(fov)
|
this.load3d.setFOV(fov)
|
||||||
|
|
||||||
|
const backgroundImage = this.load3d.loadNodeProperty('Background Image', '')
|
||||||
|
|
||||||
|
this.load3d.setBackgroundImage(backgroundImage)
|
||||||
}
|
}
|
||||||
|
|
||||||
private createModelUpdateHandler(
|
private createModelUpdateHandler(
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { LGraphNode } from '@comfyorg/litegraph'
|
import { LGraphNode } from '@comfyorg/litegraph'
|
||||||
import Tooltip from 'primevue/tooltip'
|
|
||||||
import * as THREE from 'three'
|
import * as THREE from 'three'
|
||||||
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
|
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
|
||||||
import { ViewHelper } from 'three/examples/jsm/helpers/ViewHelper'
|
import { ViewHelper } from 'three/examples/jsm/helpers/ViewHelper'
|
||||||
@@ -9,6 +8,7 @@ import { MTLLoader } from 'three/examples/jsm/loaders/MTLLoader'
|
|||||||
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader'
|
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader'
|
||||||
import { STLLoader } from 'three/examples/jsm/loaders/STLLoader'
|
import { STLLoader } from 'three/examples/jsm/loaders/STLLoader'
|
||||||
|
|
||||||
|
import Load3dUtils from '@/extensions/core/load3d/Load3dUtils'
|
||||||
import { useToastStore } from '@/stores/toastStore'
|
import { useToastStore } from '@/stores/toastStore'
|
||||||
|
|
||||||
interface Load3DOptions {
|
interface Load3DOptions {
|
||||||
@@ -55,7 +55,17 @@ class Load3d {
|
|||||||
showPreview: boolean = true
|
showPreview: boolean = true
|
||||||
previewWidth: number = 120
|
previewWidth: number = 120
|
||||||
node: LGraphNode = {} as LGraphNode
|
node: LGraphNode = {} as LGraphNode
|
||||||
private listeners: { [key: string]: Function[] } = {}
|
listeners: { [key: string]: Function[] } = {}
|
||||||
|
|
||||||
|
backgroundScene: THREE.Scene
|
||||||
|
backgroundCamera: THREE.OrthographicCamera
|
||||||
|
backgroundMesh: THREE.Mesh | null = null
|
||||||
|
backgroundTexture: THREE.Texture | null = null
|
||||||
|
|
||||||
|
previewBackgroundScene: THREE.Scene
|
||||||
|
previewBackgroundCamera: THREE.OrthographicCamera
|
||||||
|
previewBackgroundMesh: THREE.Mesh | null = null
|
||||||
|
previewBackgroundTexture: THREE.Texture | null = null
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
container: Element | HTMLElement,
|
container: Element | HTMLElement,
|
||||||
@@ -75,8 +85,8 @@ class Load3d {
|
|||||||
frustumSize / 2,
|
frustumSize / 2,
|
||||||
frustumSize / 2,
|
frustumSize / 2,
|
||||||
-frustumSize / 2,
|
-frustumSize / 2,
|
||||||
0.1,
|
0.01,
|
||||||
1000
|
10000
|
||||||
)
|
)
|
||||||
this.orthographicCamera.position.set(5, 5, 5)
|
this.orthographicCamera.position.set(5, 5, 5)
|
||||||
|
|
||||||
@@ -90,6 +100,8 @@ class Load3d {
|
|||||||
this.renderer.setClearColor(0x282828)
|
this.renderer.setClearColor(0x282828)
|
||||||
this.renderer.autoClear = false
|
this.renderer.autoClear = false
|
||||||
|
|
||||||
|
this.renderer.outputColorSpace = THREE.SRGBColorSpace
|
||||||
|
|
||||||
const rendererDomElement: HTMLCanvasElement = this.renderer.domElement
|
const rendererDomElement: HTMLCanvasElement = this.renderer.domElement
|
||||||
|
|
||||||
container.appendChild(rendererDomElement)
|
container.appendChild(rendererDomElement)
|
||||||
@@ -145,11 +157,145 @@ class Load3d {
|
|||||||
this.createCapturePreview(container)
|
this.createCapturePreview(container)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.backgroundScene = new THREE.Scene()
|
||||||
|
this.backgroundCamera = new THREE.OrthographicCamera(-1, 1, 1, -1, -1, 1)
|
||||||
|
this.previewBackgroundScene = this.backgroundScene.clone()
|
||||||
|
this.previewBackgroundCamera = this.backgroundCamera.clone()
|
||||||
|
|
||||||
|
const planeGeometry = new THREE.PlaneGeometry(2, 2)
|
||||||
|
const planeMaterial = new THREE.MeshBasicMaterial({
|
||||||
|
transparent: true,
|
||||||
|
depthWrite: false,
|
||||||
|
depthTest: false,
|
||||||
|
side: THREE.DoubleSide
|
||||||
|
})
|
||||||
|
|
||||||
|
this.backgroundMesh = new THREE.Mesh(planeGeometry, planeMaterial)
|
||||||
|
this.backgroundMesh.position.set(0, 0, 0)
|
||||||
|
|
||||||
|
this.previewBackgroundMesh = this.backgroundMesh.clone()
|
||||||
|
|
||||||
|
this.backgroundScene.add(this.backgroundMesh)
|
||||||
|
this.previewBackgroundScene.add(this.previewBackgroundMesh)
|
||||||
|
|
||||||
this.handleResize()
|
this.handleResize()
|
||||||
|
|
||||||
this.startAnimation()
|
this.startAnimation()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateBackgroundSize(
|
||||||
|
backgroundTexture: THREE.Texture | null,
|
||||||
|
backgroundMesh: THREE.Mesh | null,
|
||||||
|
targetWidth: number,
|
||||||
|
targetHeight: number
|
||||||
|
) {
|
||||||
|
if (!backgroundTexture || !backgroundMesh) return
|
||||||
|
|
||||||
|
const material = backgroundMesh.material as THREE.MeshBasicMaterial
|
||||||
|
|
||||||
|
if (!material.map) return
|
||||||
|
|
||||||
|
const imageAspect =
|
||||||
|
backgroundTexture.image.width / backgroundTexture.image.height
|
||||||
|
const targetAspect = targetWidth / targetHeight
|
||||||
|
|
||||||
|
if (imageAspect > targetAspect) {
|
||||||
|
backgroundMesh.scale.set(imageAspect / targetAspect, 1, 1)
|
||||||
|
} else {
|
||||||
|
backgroundMesh.scale.set(1, targetAspect / imageAspect, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
material.needsUpdate = true
|
||||||
|
}
|
||||||
|
|
||||||
|
async setBackgroundImage(uploadPath: string) {
|
||||||
|
if (uploadPath === '') {
|
||||||
|
this.removeBackgroundImage()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let imageUrl = Load3dUtils.getResourceURL(
|
||||||
|
...Load3dUtils.splitFilePath(uploadPath)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!imageUrl.startsWith('/api')) {
|
||||||
|
imageUrl = '/api' + imageUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const textureLoader = new THREE.TextureLoader()
|
||||||
|
const texture = await new Promise<THREE.Texture>((resolve, reject) => {
|
||||||
|
textureLoader.load(imageUrl, resolve, undefined, reject)
|
||||||
|
})
|
||||||
|
|
||||||
|
if (this.backgroundTexture) {
|
||||||
|
this.backgroundTexture.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.previewBackgroundTexture) {
|
||||||
|
this.previewBackgroundTexture.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
texture.colorSpace = THREE.SRGBColorSpace
|
||||||
|
|
||||||
|
this.backgroundTexture = texture
|
||||||
|
this.previewBackgroundTexture = texture
|
||||||
|
|
||||||
|
const material = this.backgroundMesh?.material as THREE.MeshBasicMaterial
|
||||||
|
material.map = texture
|
||||||
|
material.needsUpdate = true
|
||||||
|
|
||||||
|
const material2 = this.previewBackgroundMesh
|
||||||
|
?.material as THREE.MeshBasicMaterial
|
||||||
|
material2.map = texture
|
||||||
|
material2.needsUpdate = true
|
||||||
|
|
||||||
|
this.backgroundMesh?.position.set(0, 0, 0)
|
||||||
|
this.previewBackgroundMesh?.position.set(0, 0, 0)
|
||||||
|
|
||||||
|
this.updateBackgroundSize(
|
||||||
|
this.previewBackgroundTexture,
|
||||||
|
this.previewBackgroundMesh,
|
||||||
|
this.targetWidth,
|
||||||
|
this.targetHeight
|
||||||
|
)
|
||||||
|
|
||||||
|
this.emitEvent('backgroundImageChange', uploadPath)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading background image:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
removeBackgroundImage() {
|
||||||
|
if (this.backgroundMesh) {
|
||||||
|
const material = this.backgroundMesh.material as THREE.MeshBasicMaterial
|
||||||
|
material.map = null
|
||||||
|
material.needsUpdate = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.previewBackgroundMesh) {
|
||||||
|
const material2 = this.previewBackgroundMesh
|
||||||
|
.material as THREE.MeshBasicMaterial
|
||||||
|
material2.map = null
|
||||||
|
material2.needsUpdate = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.backgroundTexture) {
|
||||||
|
this.backgroundTexture.dispose()
|
||||||
|
this.backgroundTexture = null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.previewBackgroundTexture) {
|
||||||
|
this.previewBackgroundTexture.dispose()
|
||||||
|
this.previewBackgroundTexture = null
|
||||||
|
}
|
||||||
|
|
||||||
|
this.renderer.render(this.scene, this.activeCamera)
|
||||||
|
if (this.previewRenderer && this.previewCamera) {
|
||||||
|
this.previewRenderer.render(this.scene, this.previewCamera)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
addEventListener(event: string, callback: Function) {
|
addEventListener(event: string, callback: Function) {
|
||||||
if (!this.listeners[event]) {
|
if (!this.listeners[event]) {
|
||||||
this.listeners[event] = []
|
this.listeners[event] = []
|
||||||
@@ -191,10 +337,14 @@ class Load3d {
|
|||||||
createCapturePreview(container: Element | HTMLElement) {
|
createCapturePreview(container: Element | HTMLElement) {
|
||||||
this.previewRenderer = new THREE.WebGLRenderer({
|
this.previewRenderer = new THREE.WebGLRenderer({
|
||||||
alpha: true,
|
alpha: true,
|
||||||
antialias: true
|
antialias: true,
|
||||||
|
preserveDrawingBuffer: true
|
||||||
})
|
})
|
||||||
this.previewRenderer.setSize(this.targetWidth, this.targetHeight)
|
this.previewRenderer.setSize(this.targetWidth, this.targetHeight)
|
||||||
this.previewRenderer.setClearColor(0x282828)
|
this.previewRenderer.setClearColor(0x282828)
|
||||||
|
this.previewRenderer.autoClear = false
|
||||||
|
|
||||||
|
this.previewRenderer.outputColorSpace = THREE.SRGBColorSpace
|
||||||
|
|
||||||
this.previewContainer = document.createElement('div')
|
this.previewContainer = document.createElement('div')
|
||||||
this.previewContainer.style.cssText = `
|
this.previewContainer.style.cssText = `
|
||||||
@@ -293,6 +443,7 @@ class Load3d {
|
|||||||
;(this.previewCamera as THREE.PerspectiveCamera).fov = (
|
;(this.previewCamera as THREE.PerspectiveCamera).fov = (
|
||||||
this.activeCamera as THREE.PerspectiveCamera
|
this.activeCamera as THREE.PerspectiveCamera
|
||||||
).fov
|
).fov
|
||||||
|
;(this.previewCamera as THREE.PerspectiveCamera).updateProjectionMatrix()
|
||||||
}
|
}
|
||||||
|
|
||||||
this.previewCamera.lookAt(this.controls.target)
|
this.previewCamera.lookAt(this.controls.target)
|
||||||
@@ -300,6 +451,30 @@ class Load3d {
|
|||||||
const previewHeight =
|
const previewHeight =
|
||||||
(this.previewWidth * this.targetHeight) / this.targetWidth
|
(this.previewWidth * this.targetHeight) / this.targetWidth
|
||||||
this.previewRenderer.setSize(this.previewWidth, previewHeight, false)
|
this.previewRenderer.setSize(this.previewWidth, previewHeight, false)
|
||||||
|
|
||||||
|
this.previewRenderer.outputColorSpace = THREE.SRGBColorSpace
|
||||||
|
|
||||||
|
this.previewRenderer.clear()
|
||||||
|
|
||||||
|
if (this.previewBackgroundMesh && this.previewBackgroundTexture) {
|
||||||
|
const material = this.previewBackgroundMesh
|
||||||
|
.material as THREE.MeshBasicMaterial
|
||||||
|
if (material.map) {
|
||||||
|
const currentToneMapping = this.previewRenderer.toneMapping
|
||||||
|
const currentExposure = this.previewRenderer.toneMappingExposure
|
||||||
|
|
||||||
|
this.previewRenderer.toneMapping = THREE.NoToneMapping
|
||||||
|
|
||||||
|
this.previewRenderer.render(
|
||||||
|
this.previewBackgroundScene,
|
||||||
|
this.previewBackgroundCamera
|
||||||
|
)
|
||||||
|
|
||||||
|
this.previewRenderer.toneMapping = currentToneMapping
|
||||||
|
this.previewRenderer.toneMappingExposure = currentExposure
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.previewRenderer.render(this.scene, this.previewCamera)
|
this.previewRenderer.render(this.scene, this.previewCamera)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -313,9 +488,23 @@ class Load3d {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setTargetSize(width: number, height: number) {
|
setTargetSize(width: number, height: number) {
|
||||||
|
const oldAspect = this.targetWidth / this.targetHeight
|
||||||
|
|
||||||
this.targetWidth = width
|
this.targetWidth = width
|
||||||
this.targetHeight = height
|
this.targetHeight = height
|
||||||
|
|
||||||
this.updatePreviewSize()
|
this.updatePreviewSize()
|
||||||
|
|
||||||
|
const newAspect = width / height
|
||||||
|
if (Math.abs(oldAspect - newAspect) > 0.001) {
|
||||||
|
this.updateBackgroundSize(
|
||||||
|
this.previewBackgroundTexture,
|
||||||
|
this.previewBackgroundMesh,
|
||||||
|
width,
|
||||||
|
height
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if (this.previewRenderer && this.previewCamera) {
|
if (this.previewRenderer && this.previewCamera) {
|
||||||
if (this.previewCamera instanceof THREE.PerspectiveCamera) {
|
if (this.previewCamera instanceof THREE.PerspectiveCamera) {
|
||||||
this.previewCamera.aspect = width / height
|
this.previewCamera.aspect = width / height
|
||||||
@@ -698,6 +887,22 @@ class Load3d {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.renderer.clear()
|
this.renderer.clear()
|
||||||
|
|
||||||
|
if (this.backgroundMesh && this.backgroundTexture) {
|
||||||
|
const material = this.backgroundMesh.material as THREE.MeshBasicMaterial
|
||||||
|
if (material.map) {
|
||||||
|
const currentToneMapping = this.renderer.toneMapping
|
||||||
|
const currentExposure = this.renderer.toneMappingExposure
|
||||||
|
|
||||||
|
this.renderer.toneMapping = THREE.NoToneMapping
|
||||||
|
|
||||||
|
this.renderer.render(this.backgroundScene, this.backgroundCamera)
|
||||||
|
|
||||||
|
this.renderer.toneMapping = currentToneMapping
|
||||||
|
this.renderer.toneMappingExposure = currentExposure
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.controls.update()
|
this.controls.update()
|
||||||
this.renderer.render(this.scene, this.activeCamera)
|
this.renderer.render(this.scene, this.activeCamera)
|
||||||
this.viewHelper.render(this.renderer)
|
this.viewHelper.render(this.renderer)
|
||||||
@@ -785,6 +990,24 @@ class Load3d {
|
|||||||
cancelAnimationFrame(this.animationFrameId)
|
cancelAnimationFrame(this.animationFrameId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.backgroundTexture) {
|
||||||
|
this.backgroundTexture.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.previewBackgroundTexture) {
|
||||||
|
this.previewBackgroundTexture.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.backgroundMesh) {
|
||||||
|
this.backgroundMesh.geometry.dispose()
|
||||||
|
;(this.backgroundMesh.material as THREE.Material).dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.previewBackgroundMesh) {
|
||||||
|
this.previewBackgroundMesh.geometry.dispose()
|
||||||
|
;(this.previewBackgroundMesh.material as THREE.Material).dispose()
|
||||||
|
}
|
||||||
|
|
||||||
this.controls.dispose()
|
this.controls.dispose()
|
||||||
this.viewHelper.dispose()
|
this.viewHelper.dispose()
|
||||||
this.renderer.dispose()
|
this.renderer.dispose()
|
||||||
@@ -984,6 +1207,16 @@ class Load3d {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.renderer.setSize(width, height)
|
this.renderer.setSize(width, height)
|
||||||
|
|
||||||
|
if (this.backgroundTexture && this.backgroundMesh) {
|
||||||
|
this.updateBackgroundSize(
|
||||||
|
this.backgroundTexture,
|
||||||
|
this.backgroundMesh,
|
||||||
|
width,
|
||||||
|
height
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
this.setTargetSize(this.targetWidth, this.targetHeight)
|
this.setTargetSize(this.targetWidth, this.targetHeight)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1007,6 +1240,8 @@ class Load3d {
|
|||||||
new THREE.Color()
|
new THREE.Color()
|
||||||
)
|
)
|
||||||
const originalClearAlpha = this.renderer.getClearAlpha()
|
const originalClearAlpha = this.renderer.getClearAlpha()
|
||||||
|
const originalToneMapping = this.renderer.toneMapping
|
||||||
|
const originalExposure = this.renderer.toneMappingExposure
|
||||||
|
|
||||||
this.renderer.setSize(width, height)
|
this.renderer.setSize(width, height)
|
||||||
|
|
||||||
@@ -1023,7 +1258,29 @@ class Load3d {
|
|||||||
this.orthographicCamera.updateProjectionMatrix()
|
this.orthographicCamera.updateProjectionMatrix()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.backgroundTexture && this.backgroundMesh) {
|
||||||
|
this.updateBackgroundSize(
|
||||||
|
this.backgroundTexture,
|
||||||
|
this.backgroundMesh,
|
||||||
|
width,
|
||||||
|
height
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
this.renderer.clear()
|
this.renderer.clear()
|
||||||
|
|
||||||
|
if (this.backgroundMesh && this.backgroundTexture) {
|
||||||
|
const material = this.backgroundMesh
|
||||||
|
.material as THREE.MeshBasicMaterial
|
||||||
|
|
||||||
|
if (material.map) {
|
||||||
|
this.renderer.toneMapping = THREE.NoToneMapping
|
||||||
|
this.renderer.render(this.backgroundScene, this.backgroundCamera)
|
||||||
|
this.renderer.toneMapping = originalToneMapping
|
||||||
|
this.renderer.toneMappingExposure = originalExposure
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.renderer.render(this.scene, this.activeCamera)
|
this.renderer.render(this.scene, this.activeCamera)
|
||||||
const sceneData = this.renderer.domElement.toDataURL('image/png')
|
const sceneData = this.renderer.domElement.toDataURL('image/png')
|
||||||
|
|
||||||
|
|||||||
@@ -169,12 +169,6 @@ class Load3dAnimation extends Load3d {
|
|||||||
this.currentAnimation.update(delta)
|
this.currentAnimation.update(delta)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.controls.update()
|
|
||||||
|
|
||||||
this.renderer.clear()
|
|
||||||
|
|
||||||
this.renderer.render(this.scene, this.activeCamera)
|
|
||||||
|
|
||||||
if (this.viewHelper.animating) {
|
if (this.viewHelper.animating) {
|
||||||
this.viewHelper.update(delta)
|
this.viewHelper.update(delta)
|
||||||
|
|
||||||
@@ -183,6 +177,25 @@ class Load3dAnimation extends Load3d {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.renderer.clear()
|
||||||
|
|
||||||
|
if (this.backgroundMesh && this.backgroundTexture) {
|
||||||
|
const material = this.backgroundMesh.material as THREE.MeshBasicMaterial
|
||||||
|
if (material.map) {
|
||||||
|
const currentToneMapping = this.renderer.toneMapping
|
||||||
|
const currentExposure = this.renderer.toneMappingExposure
|
||||||
|
|
||||||
|
this.renderer.toneMapping = THREE.NoToneMapping
|
||||||
|
|
||||||
|
this.renderer.render(this.backgroundScene, this.backgroundCamera)
|
||||||
|
|
||||||
|
this.renderer.toneMapping = currentToneMapping
|
||||||
|
this.renderer.toneMappingExposure = currentExposure
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.controls.update()
|
||||||
|
this.renderer.render(this.scene, this.activeCamera)
|
||||||
this.viewHelper.render(this.renderer)
|
this.viewHelper.render(this.renderer)
|
||||||
}
|
}
|
||||||
animate()
|
animate()
|
||||||
|
|||||||
@@ -28,11 +28,7 @@ class Load3dUtils {
|
|||||||
return await resp.json()
|
return await resp.json()
|
||||||
}
|
}
|
||||||
|
|
||||||
static async uploadFile(
|
static async uploadFile(file: File) {
|
||||||
load3d: Load3d,
|
|
||||||
file: File,
|
|
||||||
fileInput?: HTMLInputElement
|
|
||||||
) {
|
|
||||||
let uploadPath
|
let uploadPath
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -49,36 +45,11 @@ class Load3dUtils {
|
|||||||
const data = await resp.json()
|
const data = await resp.json()
|
||||||
let path = data.name
|
let path = data.name
|
||||||
|
|
||||||
if (data.subfolder) path = data.subfolder + '/' + path
|
if (data.subfolder) {
|
||||||
|
path = data.subfolder + '/' + path
|
||||||
|
}
|
||||||
|
|
||||||
uploadPath = path
|
uploadPath = path
|
||||||
|
|
||||||
const modelUrl = api.apiURL(
|
|
||||||
this.getResourceURL(...this.splitFilePath(path), 'input')
|
|
||||||
)
|
|
||||||
await load3d.loadModel(modelUrl, file.name)
|
|
||||||
|
|
||||||
const fileExt = file.name.split('.').pop()?.toLowerCase()
|
|
||||||
if (fileExt === 'obj' && fileInput?.files) {
|
|
||||||
try {
|
|
||||||
const mtlFile = Array.from(fileInput.files).find((f) =>
|
|
||||||
f.name.toLowerCase().endsWith('.mtl')
|
|
||||||
)
|
|
||||||
|
|
||||||
if (mtlFile) {
|
|
||||||
const mtlFormData = new FormData()
|
|
||||||
mtlFormData.append('image', mtlFile)
|
|
||||||
mtlFormData.append('subfolder', '3d')
|
|
||||||
|
|
||||||
await api.fetchApi('/upload/image', {
|
|
||||||
method: 'POST',
|
|
||||||
body: mtlFormData
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} catch (mtlError) {
|
|
||||||
console.warn('Failed to upload MTL file:', mtlError)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
useToastStore().addAlert(resp.status + ' - ' + resp.statusText)
|
useToastStore().addAlert(resp.status + ' - ' + resp.statusText)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -845,6 +845,8 @@
|
|||||||
"backgroundColor": "Background Color",
|
"backgroundColor": "Background Color",
|
||||||
"lightIntensity": "Light Intensity",
|
"lightIntensity": "Light Intensity",
|
||||||
"fov": "FOV",
|
"fov": "FOV",
|
||||||
"previewOutput": "Preview Output"
|
"previewOutput": "Preview Output",
|
||||||
|
"uploadBackgroundImage": "Upload Background Image",
|
||||||
|
"removeBackgroundImage": "Remove Background Image"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -322,8 +322,10 @@
|
|||||||
"fov": "FOV",
|
"fov": "FOV",
|
||||||
"lightIntensity": "Intensité de la lumière",
|
"lightIntensity": "Intensité de la lumière",
|
||||||
"previewOutput": "Aperçu de la sortie",
|
"previewOutput": "Aperçu de la sortie",
|
||||||
|
"removeBackgroundImage": "Supprimer l'image de fond",
|
||||||
"showGrid": "Afficher la grille",
|
"showGrid": "Afficher la grille",
|
||||||
"switchCamera": "Changer de caméra"
|
"switchCamera": "Changer de caméra",
|
||||||
|
"uploadBackgroundImage": "Télécharger l'image de fond"
|
||||||
},
|
},
|
||||||
"maintenance": {
|
"maintenance": {
|
||||||
"None": "Aucun",
|
"None": "Aucun",
|
||||||
|
|||||||
@@ -322,8 +322,10 @@
|
|||||||
"fov": "FOV",
|
"fov": "FOV",
|
||||||
"lightIntensity": "光の強度",
|
"lightIntensity": "光の強度",
|
||||||
"previewOutput": "出力のプレビュー",
|
"previewOutput": "出力のプレビュー",
|
||||||
|
"removeBackgroundImage": "背景画像を削除",
|
||||||
"showGrid": "グリッドを表示",
|
"showGrid": "グリッドを表示",
|
||||||
"switchCamera": "カメラを切り替える"
|
"switchCamera": "カメラを切り替える",
|
||||||
|
"uploadBackgroundImage": "背景画像をアップロード"
|
||||||
},
|
},
|
||||||
"maintenance": {
|
"maintenance": {
|
||||||
"None": "なし",
|
"None": "なし",
|
||||||
|
|||||||
@@ -322,8 +322,10 @@
|
|||||||
"fov": "Угол обзора",
|
"fov": "Угол обзора",
|
||||||
"lightIntensity": "Интенсивность света",
|
"lightIntensity": "Интенсивность света",
|
||||||
"previewOutput": "Предварительный просмотр",
|
"previewOutput": "Предварительный просмотр",
|
||||||
|
"removeBackgroundImage": "Удалить фоновое изображение",
|
||||||
"showGrid": "Показать сетку",
|
"showGrid": "Показать сетку",
|
||||||
"switchCamera": "Переключить камеру"
|
"switchCamera": "Переключить камеру",
|
||||||
|
"uploadBackgroundImage": "Загрузить фоновое изображение"
|
||||||
},
|
},
|
||||||
"maintenance": {
|
"maintenance": {
|
||||||
"None": "Нет",
|
"None": "Нет",
|
||||||
|
|||||||
@@ -322,8 +322,10 @@
|
|||||||
"fov": "视场",
|
"fov": "视场",
|
||||||
"lightIntensity": "光照强度",
|
"lightIntensity": "光照强度",
|
||||||
"previewOutput": "预览输出",
|
"previewOutput": "预览输出",
|
||||||
|
"removeBackgroundImage": "移除背景图片",
|
||||||
"showGrid": "显示网格",
|
"showGrid": "显示网格",
|
||||||
"switchCamera": "切换摄像头"
|
"switchCamera": "切换摄像头",
|
||||||
|
"uploadBackgroundImage": "上传背景图片"
|
||||||
},
|
},
|
||||||
"maintenance": {
|
"maintenance": {
|
||||||
"None": "无",
|
"None": "无",
|
||||||
|
|||||||
Reference in New Issue
Block a user