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);