From f4d85363cbc055e9db614bfba8becf815baeb54d Mon Sep 17 00:00:00 2001 From: BFlat Date: Wed, 24 Sep 2025 11:22:31 +0700 Subject: [PATCH] image copy paste fix in 3d editor (#841) --- .../app/src/pages/PageEnigma/Editor/scene.ts | 21 +++++++++++++++-- .../PageEnigma/Editor/scene_manager_api.ts | 23 +++++++++++++++++-- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/frontend/apps/editor3d/app/src/pages/PageEnigma/Editor/scene.ts b/frontend/apps/editor3d/app/src/pages/PageEnigma/Editor/scene.ts index 00b17f3b14..4465ae0fab 100644 --- a/frontend/apps/editor3d/app/src/pages/PageEnigma/Editor/scene.ts +++ b/frontend/apps/editor3d/app/src/pages/PageEnigma/Editor/scene.ts @@ -197,6 +197,7 @@ class Scene { ); obj = new THREE.Mesh(geometry, image_material); obj.userData["media_id"] = name; + obj.userData["media_file_type"] = MediaFileType.Video; } else { const loader = new THREE.TextureLoader(); // NB(bt,2025-08-26): Images loaded throughout the rest of ArtCraft with the tag set @@ -214,6 +215,7 @@ class Scene { }); obj = new THREE.Mesh(geometry, image_material); obj.userData["media_id"] = name; + obj.userData["media_file_type"] = MediaFileType.Image; } } else if (name == "PointLight") { const light_material = new THREE.MeshBasicMaterial({ @@ -553,7 +555,14 @@ class Scene { url.includes(".jpeg") || url.includes(".mp4") ) { - return await this.instantiate("Image::" + url); + const obj = await this.instantiate("Image::" + url); + obj.position.copy(position); + if (url.includes(".mp4")) { + obj.userData["media_file_type"] = MediaFileType.Video; + } else { + obj.userData["media_file_type"] = MediaFileType.Image; + } + return obj; } } @@ -579,7 +588,15 @@ class Scene { url.includes(".jpeg") || url.includes(".mp4") ) { - return await this.instantiate("Image::" + url); + const obj = await this.instantiate("Image::" + url); + // Preserve original token to enable copy/paste and timeline mapping + obj.userData["media_id"] = media_id; + if (url.includes(".mp4")) { + obj.userData["media_file_type"] = MediaFileType.Video; + } else { + obj.userData["media_file_type"] = MediaFileType.Image; + } + return obj; } return await this.loadGlbWithPlaceholder( media_id, diff --git a/frontend/apps/editor3d/app/src/pages/PageEnigma/Editor/scene_manager_api.ts b/frontend/apps/editor3d/app/src/pages/PageEnigma/Editor/scene_manager_api.ts index 1ba68fc66c..f6236574fe 100644 --- a/frontend/apps/editor3d/app/src/pages/PageEnigma/Editor/scene_manager_api.ts +++ b/frontend/apps/editor3d/app/src/pages/PageEnigma/Editor/scene_manager_api.ts @@ -147,18 +147,34 @@ export class SceneManager implements SceneManagerAPI { media_token: string, name: string, position: THREE.Vector3, - ): Promise> { + ): Promise | undefined> { if (media_token.includes("SKY::")) { const token = media_token.replace("SKY::", ""); this.scene.updateSkybox(token); } else if (media_token !== "Parim") { - return await this.scene.loadObject( + // Support direct image/video URL pastes via the Image:: scheme + if (media_token.startsWith("Image::")) { + const url = media_token.replace("Image::", ""); + const obj = await this.scene.loadObjectFromUrl(url, position); + if (obj) { + obj.name = name; + obj.userData["name"] = name; + } + return obj as THREE.Object3D; + } + + const obj = await this.scene.loadObject( media_token, name, true, position, this.version, ); + if (obj) { + obj.name = name; + obj.userData["name"] = name; + } + return obj as THREE.Object3D | undefined; } else { return this.scene.instantiate(name, position); } @@ -288,6 +304,9 @@ export class SceneManager implements SceneManagerAPI { const wasCharacter: boolean = !!this.copiedObject.userData.isCharacter; const obj = await this.create(media_id, name, position); + if (!obj) { + return; + } this.scene.setColor(obj.uuid, color); obj.position.copy(position.add(new THREE.Vector3(0.5, 0.0, 0.5))); obj.rotation.copy(rotation);