From 8f9bbe996399cae2e01828e437a11c62eaded823 Mon Sep 17 00:00:00 2001 From: "Dylan M. Taylor" Date: Wed, 11 Nov 2026 23:24:20 +0000 Subject: [PATCH] Import: a model's left-out parts reach the user as an import warning --- crates/engine/src/tests/session.rs | 19 +++++++++++++++++++ crates/model/src/lib.rs | 5 +++++ 2 files changed, 24 insertions(+) diff --git a/crates/engine/src/tests/session.rs b/crates/engine/src/tests/session.rs index b253640e7..020c2daf0 100644 --- a/crates/engine/src/tests/session.rs +++ b/crates/engine/src/tests/session.rs @@ -96,6 +96,25 @@ fn imported_files_go_into_the_comp_where_they_were_dropped() { assert!(s.execute("layer.addItem", json!({"item": "a.png", "position": [1.0]})).is_err(), "position needs x and y"); } +/// What an import left out of a file (a 3D model's textures over the limits) comes back in the +/// import's `warnings`. +#[test] +fn what_an_import_left_out_is_a_warning() { + use effectcraft_project::{Footage, FootageKind}; + struct Probe; + impl crate::Importer for Probe { + fn probe(&self, path: &str) -> Result { + Ok(Footage { path: path.into(), kind: FootageKind::Model, ..Default::default() }) + } + fn probe_noting(&self, path: &str) -> Result<(Footage, Option), String> { + Ok((self.probe(path)?, Some(format!("{path}: texture `a.png` left out")))) + } + } + let mut s = Session { importer: Some(std::sync::Arc::new(Probe)), ..Default::default() }; + let r = s.execute("file.import", json!({"paths": ["/m/scene.obj"]})).unwrap(); + assert_eq!(r["warnings"], json!(["/m/scene.obj: texture `a.png` left out"])); +} + /// #270: files dropped or picked in the Import dialog (`file.import {background}`) are probed in /// a background job that says which file it is reading and how many are left; then they arrive /// in one undo step, all selected, with a count. The same file imported again arrives again. diff --git a/crates/model/src/lib.rs b/crates/model/src/lib.rs index f86f56040..2a7374a05 100644 --- a/crates/model/src/lib.rs +++ b/crates/model/src/lib.rs @@ -68,6 +68,11 @@ impl Texture { pub fn solid(c: [f32; 4]) -> Texture { Texture { width: 1, height: 1, data: vec![c], wrap: [Wrap::Repeat; 2] } } + /// The pixel size an image claims, read from its header without decoding it. + pub fn dimensions(bytes: &[u8]) -> Result<(u32, u32)> { + let reader = image::ImageReader::new(std::io::Cursor::new(bytes)).with_guessed_format().map_err(|e| perr(format!("image: {e}")))?; + reader.into_dimensions().map_err(|e| perr(format!("image: {e}"))) + } /// Decode a PNG or JPEG. pub fn decode(bytes: &[u8]) -> Result { let img = image::load_from_memory(bytes).map_err(|e| perr(format!("image: {e}")))?.to_rgba32f();