diff --git a/.rpm/stl-thumb.spec b/.rpm/stl-thumb.spec index 0b66817..11fed3f 100644 --- a/.rpm/stl-thumb.spec +++ b/.rpm/stl-thumb.spec @@ -3,7 +3,7 @@ %define debug_package %{nil} Name: stl-thumb -Summary: A fast lightweight thumbnail generator for STL files +Summary: A fast lightweight thumbnail generator for 3D model(STL, OBJ, 3MF) files Version: @@VERSION@@ Release: @@RELEASE@@%{?dist} License: MIT diff --git a/Cargo.toml b/Cargo.toml index 353da8e..1899745 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,12 +2,12 @@ name = "stl-thumb" version = "0.5.0" authors = ["Tyler Anderson "] -description = "A fast lightweight thumbnail generator for STL files" +description = "A fast lightweight thumbnail generator for 3D model(STL, OBJ, 3MF) files" readme = "README.md" repository = "https://github.com/unlimitedbacon/stl-thumb" license = "MIT" homepage = "https://github.com/unlimitedbacon/stl-thumb" -keywords = ["3d", "3dprinting", "stl"] +keywords = ["3d", "3dprinting", "stl", "obj", "3mf"] categories = ["command-line-utilities", "graphics"] exclude = [ "test_data/*" @@ -63,8 +63,8 @@ harness = false license-file = ["LICENSE", "3"] depends = "libgl1, libc6, libgcc1, libosmesa6-dev" extended-description = """\ -Stl-thumb is a fast lightweight thumbnail generator for STL files. \ -It can show previews for STL files in your file manager on Linux and Windows. \ +Stl-thumb is a fast lightweight thumbnail generator for 3D model(STL, OBJ, 3MF) files. \ +It can show previews for 3D model files in your file manager on Linux and Windows. \ It is written in Rust and uses OpenGL. """ section = "graphics" priority = "optional" diff --git a/README.md b/README.md index e659c0f..6afbaf0 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![Documentation](https://img.shields.io/docsrs/stl-thumb/latest)](https://docs.rs/stl-thumb/latest/stl_thumb/) [![Crates.io](https://img.shields.io/crates/v/stl-thumb.svg)](https://crates.io/crates/stl-thumb) -Stl-thumb is a fast lightweight thumbnail generator for STL files. It can show previews for STL files in your file manager on Linux and Windows. It is written in Rust and uses OpenGL. +Stl-thumb is a fast lightweight thumbnail generator for 3D model(STL, OBJ, 3MF) files. It can show previews for model files in your file manager on Linux and Windows. It is written in Rust and uses OpenGL. ![Screenshot](https://user-images.githubusercontent.com/3131268/116009182-f3f89c80-a5cc-11eb-817d-91e8a9fad279.png) @@ -76,22 +76,22 @@ $ cargo generate-rpm ## Command Line Usage ``` -$ stl-thumb [IMG_FILE] +$ stl-thumb [IMG_FILE] ``` ### Options -| Option | Description | -| ------------- | ------------------------------------------------------- | -| | The STL file you want a picture of. Use - to read from stdin instead of a file. | -| | The thumbnail image file that will be created. Use - to write to stdout instead of a file. | -| -s, --size \ | Specify width of the image. It will always be a square. | +| Option | Description | +| ------------- |---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| | The model file you want a picture of. Use - to read from stdin instead of a file. | +| | The thumbnail image file that will be created. Use - to write to stdout instead of a file. | +| -s, --size \ | Specify width of the image. It will always be a square. | | -f, --format \ | The format of the image file. If not specified it will be determined from the file extension, or default to PNG if there is no extension. Supported formats: PNG, JPEG, GIF, ICO, BMP | -| -m, --material \ \ \ | Colors for rendering the mesh using the Phong reflection model. Requires 3 colors as rgb hex values: ambient, diffuse, and specular. Defaults to blue. | -| -b, --backround \ | The background color with transparency (rgba). Default is ffffff00. | -| -a, --antialiasing [none, fxaa] | Anti-aliasing method. Default is FXAA, which is fast but may introduce artifacts. | -| --recalc-normals | Force recalculation of face normals. Use when dealing with malformed STL files. | -| -x | Display the image in a window instead of saving a file. | -| -h, --help | Prints help information. | -| -V, --version | Prints version information. | -| -v[v][v] | Increase message verbosity. Levels: Errors, Warnings, Info, Debugging | +| -m, --material \ \ \ | Colors for rendering the mesh using the Phong reflection model. Requires 3 colors as rgb hex values: ambient, diffuse, and specular. Defaults to blue. | +| -b, --backround \ | The background color with transparency (rgba). Default is ffffff00. | +| -a, --antialiasing [none, fxaa] | Anti-aliasing method. Default is FXAA, which is fast but may introduce artifacts. | +| --recalc-normals | Force recalculation of face normals. Use when dealing with malformed STL files. | +| -x | Display the image in a window instead of saving a file. | +| -h, --help | Prints help information. | +| -V, --version | Prints version information. | +| -v[v][v] | Increase message verbosity. Levels: Errors, Warnings, Info, Debugging | diff --git a/benches/benchy.rs b/benches/benchy.rs index b405c45..d02bcb5 100644 --- a/benches/benchy.rs +++ b/benches/benchy.rs @@ -7,7 +7,7 @@ use stl_thumb::config::Config; fn benchy_stl() { let config = Config { - stl_filename: "test_data/3DBenchy.stl".to_string(), + model_filename: "test_data/3DBenchy.stl".to_string(), img_filename: "benchy.png".to_string(), width: 1024, height: 768, @@ -19,7 +19,7 @@ fn benchy_stl() { fn benchy_obj() { let config = Config { - stl_filename: "test_data/3DBenchy.obj".to_string(), + model_filename: "test_data/3DBenchy.obj".to_string(), img_filename: "3DBenchy_obj.png".to_string(), width: 1024, height: 768, diff --git a/benches/cube.rs b/benches/cube.rs index 6539873..45963d7 100644 --- a/benches/cube.rs +++ b/benches/cube.rs @@ -7,7 +7,7 @@ use stl_thumb::config::Config; fn cube() { let config = Config { - stl_filename: "test_data/cube.stl".to_string(), + model_filename: "test_data/cube.stl".to_string(), img_filename: "cube.png".to_string(), width: 1024, height: 768, @@ -19,7 +19,7 @@ fn cube() { fn cube_obj() { let config = Config { - stl_filename: "test_data/cube.obj".to_string(), + model_filename: "test_data/cube.obj".to_string(), img_filename: "cube_obj.png".to_string(), width: 1024, height: 768, diff --git a/benches/shipwreck.rs b/benches/shipwreck.rs index d9c19fb..b49cba8 100644 --- a/benches/shipwreck.rs +++ b/benches/shipwreck.rs @@ -7,7 +7,7 @@ use stl_thumb::config::Config; fn shipwreck() { let config = Config { - stl_filename: "test_data/shipwreck.stl".to_string(), + model_filename: "test_data/shipwreck.stl".to_string(), img_filename: "shipwreck.png".to_string(), width: 1024, height: 768, diff --git a/libstl_thumb.h b/libstl_thumb.h index 457207e..2db8d72 100644 --- a/libstl_thumb.h +++ b/libstl_thumb.h @@ -10,29 +10,29 @@ extern "C" { /// Allows utilizing `stl-thumb` from C-like languages /// -/// This function renders an image of the file `stl_filename_c` and stores it into the buffer `buf_ptr`. +/// This function renders an image of the file `model_filename_c` and stores it into the buffer `buf_ptr`. /// /// You must provide a memory buffer large enough to store the image. Images are written in 8-bit RGBA format, -/// so the buffer must be at least `width`*`height`*4 bytes in size. `stl_filename_c` is a pointer to a C string with +/// so the buffer must be at least `width`*`height`*4 bytes in size. `model_filename_c` is a pointer to a C string with /// the file path. /// /// Returns `true` if succesful and `false` if unsuccesful. /// /// # Example in C /// ```c -/// const char* stl_filename_c = "3DBenchy.stl"; +/// const char* model_filename_c = "3DBenchy.stl"; /// int width = 256; /// int height = 256; /// /// int img_size = width * height * 4; /// buf_ptr = (uchar *) malloc(img_size); /// -/// render_to_buffer(buf_ptr, width, height, stl_filename_c); +/// render_to_buffer(buf_ptr, width, height, model_filename_c); /// ``` bool render_to_buffer(uint8_t *buf_ptr, uint32_t width, uint32_t height, - const char *stl_filename_c); + const char *model_filename_c); #ifdef __cplusplus } // extern "C" diff --git a/src/config.rs b/src/config.rs index c23f294..5db239d 100644 --- a/src/config.rs +++ b/src/config.rs @@ -17,7 +17,7 @@ pub enum AAMethod { #[derive(Clone)] pub struct Config { - pub stl_filename: String, + pub model_filename: String, pub img_filename: String, pub format: ImageFormat, pub width: u32, @@ -33,7 +33,7 @@ pub struct Config { impl Default for Config { fn default() -> Self { Config { - stl_filename: "".to_string(), + model_filename: "".to_string(), img_filename: "".to_string(), format: ImageFormat::Png, width: 1024, @@ -59,7 +59,7 @@ impl Config { .version(env!("CARGO_PKG_VERSION")) .author(env!("CARGO_PKG_AUTHORS")) .arg( - clap::Arg::new("STL_FILE") + clap::Arg::new("MODEL_FILE") .help("STL file. Use - to read from stdin instead of a file.") .required(true) .index(1), @@ -130,9 +130,9 @@ impl Config { ..Default::default() }; - c.stl_filename = matches - .remove_one::("STL_FILE") - .expect("STL_FILE not provided"); + c.model_filename = matches + .remove_one::("MODEL_FILE") + .expect("MODEL_FILE not provided"); c.img_filename = matches .remove_one::("IMG_FILE") .expect("IMG_FILE not provided"); diff --git a/src/lib.rs b/src/lib.rs index 632328d..f86155b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -263,9 +263,9 @@ where } pub fn render_to_window(config: Config) -> Result<(), Box> { - // Get geometry from STL file + // Get geometry from model file // ========================== - let mesh = Mesh::load(&config.stl_filename, config.recalc_normals)?; + let mesh = Mesh::load(&config.model_filename, config.recalc_normals)?; // Create GL context // ================= @@ -323,9 +323,9 @@ pub fn render_to_window(config: Config) -> Result<(), Box> { } pub fn render_to_image(config: &Config) -> Result> { - // Get geometry from STL file + // Get geometry from model file // ========================= - let mesh = Mesh::load(&config.stl_filename, config.recalc_normals)?; + let mesh = Mesh::load(&config.model_filename, config.recalc_normals)?; // Create GL context // ================= @@ -414,36 +414,36 @@ pub fn render_to_file(config: &Config) -> Result<(), Box> { /// Allows utilizing `stl-thumb` from C-like languages /// -/// This function renders an image of the file `stl_filename_c` and stores it into the buffer `buf_ptr`. +/// This function renders an image of the file `model_filename_c` and stores it into the buffer `buf_ptr`. /// /// You must provide a memory buffer large enough to store the image. Images are written in 8-bit RGBA format, -/// so the buffer must be at least `width`*`height`*4 bytes in size. `stl_filename_c` is a pointer to a C string with +/// so the buffer must be at least `width`*`height`*4 bytes in size. `model_filename_c` is a pointer to a C string with /// the file path. /// /// Returns `true` if succesful and `false` if unsuccesful. /// /// # Example in C /// ```c -/// const char* stl_filename_c = "3DBenchy.stl"; +/// const char* model_filename_c = "3DBenchy.stl"; /// int width = 256; /// int height = 256; /// /// int img_size = width * height * 4; /// buf_ptr = (uchar *) malloc(img_size); /// -/// render_to_buffer(buf_ptr, width, height, stl_filename_c); +/// render_to_buffer(buf_ptr, width, height, model_filename_c); /// ``` /// /// # Safety /// /// * `buf_ptr` _must_ point to a valid initialized buffer, at least `width * height * 4` bytes long. -/// * `stl_filename_c` must point to a valid null-terminated string. +/// * `model_filename_c` must point to a valid null-terminated string. #[no_mangle] pub unsafe extern "C" fn render_to_buffer( buf_ptr: *mut u8, width: u32, height: u32, - stl_filename_c: *const c_char, + model_filename_c: *const c_char, ) -> bool { // Workaround for issues with OpenGL 3.1 on Mesa 18.3 #[cfg(target_os = "linux")] @@ -458,24 +458,24 @@ pub unsafe extern "C" fn render_to_buffer( let buf = unsafe { slice::from_raw_parts_mut(buf_ptr, buf_size) }; // Check validity of provided file path string - let stl_filename_cstr = unsafe { - if stl_filename_c.is_null() { - error!("STL file path pointer is null"); + let model_filename_cstr = unsafe { + if model_filename_c.is_null() { + error!("model file path pointer is null"); return false; } - CStr::from_ptr(stl_filename_c) + CStr::from_ptr(model_filename_c) }; - let stl_filename_str = match stl_filename_cstr.to_str() { + let model_filename_str = match model_filename_cstr.to_str() { Ok(s) => s, Err(_) => { - error!("Invalid STL file path {:?}", stl_filename_cstr); + error!("Invalid model file path {:?}", model_filename_cstr); return false; } }; // Setup configuration for the renderer let config = Config { - stl_filename: stl_filename_str.to_string(), + model_filename: model_filename_str.to_string(), width, height, ..Default::default() @@ -517,7 +517,7 @@ mod tests { fn cube_stl() { let img_filename = "cube-stl.png".to_string(); let config = Config { - stl_filename: "test_data/cube.stl".to_string(), + model_filename: "test_data/cube.stl".to_string(), img_filename: img_filename.clone(), format: image::ImageFormat::Png, ..Default::default() @@ -542,7 +542,7 @@ mod tests { fn cube_obj() { let img_filename = "cube-obj.png".to_string(); let config = Config { - stl_filename: "test_data/cube.obj".to_string(), + model_filename: "test_data/cube.obj".to_string(), img_filename: img_filename.clone(), format: image::ImageFormat::Png, ..Default::default() @@ -567,7 +567,7 @@ mod tests { fn cube_3mf() { let img_filename = "cube-3mf.png".to_string(); let config = Config { - stl_filename: "test_data/cube.3mf".to_string(), + model_filename: "test_data/cube.3mf".to_string(), img_filename: img_filename.clone(), format: image::ImageFormat::Png, ..Default::default() diff --git a/src/main.rs b/src/main.rs index 23623ee..c1d102f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -25,7 +25,7 @@ fn main() { .init() .unwrap(); - info!("STL File: {}", config.stl_filename); + info!("MODEL File: {}", config.model_filename); info!("IMG File: {}", config.img_filename); if config.visible { diff --git a/src/mesh.rs b/src/mesh.rs index 5a465ca..c01fd61 100644 --- a/src/mesh.rs +++ b/src/mesh.rs @@ -101,15 +101,15 @@ pub struct Mesh { pub normals: Vec, pub indices: Vec, pub bounds: BoundingBox, - stl_had_normals: bool, + model_had_normals: bool, } impl Mesh { // Load mesh data from file (if provided) or stdin - pub fn load(stl_filename: &str, recalc_normals: bool) -> Result> { + pub fn load(model_filename: &str, recalc_normals: bool) -> Result> { // TODO: Add support for URIs instead of plain file names // https://developer.gnome.org/integration-guide/stable/thumbnailer.html.en - match stl_filename { + match model_filename { "-" => { // create_stl_reader requires Seek, so we must read the entire stream into memory before proceeding. // So I guess this can just consume all RAM if it gets bad input. Hmmm.... @@ -118,20 +118,20 @@ impl Mesh { Mesh::from_stl(Cursor::new(input_buffer), recalc_normals) } _ => { - let stl_filename = std::path::Path::new(stl_filename); + let model_filename = std::path::Path::new(model_filename); // TODO: Try BufReader and see if it's faster - let stl_file = File::open(stl_filename)?; + let model_file = File::open(model_filename)?; Ok( - match stl_filename + match model_filename .extension() .and_then(std::ffi::OsStr::to_str) .unwrap_or("") .to_lowercase() .as_str() { - "obj" => Mesh::from_obj(stl_file, recalc_normals)?, - "stl" => Mesh::from_stl(stl_file, recalc_normals)?, - "3mf" => Mesh::from_3mf(stl_file, recalc_normals)?, + "obj" => Mesh::from_obj(model_file, recalc_normals)?, + "stl" => Mesh::from_stl(model_file, recalc_normals)?, + "3mf" => Mesh::from_3mf(model_file, recalc_normals)?, _ => unimplemented!("Format not supported"), }, ) @@ -139,11 +139,11 @@ impl Mesh { } } - pub fn from_3mf(stl_file: R, _recalc_normals: bool) -> Result> + pub fn from_3mf(model_file: R, _recalc_normals: bool) -> Result> where R: Read + Seek, { - let models = threemf::read(stl_file)?; + let models = threemf::read(model_file)?; let mut result = None; @@ -173,7 +173,7 @@ impl Mesh { normals: Vec::new(), indices: Vec::new(), bounds: BoundingBox::new(&triangle.vertices[0]), - stl_had_normals: false, + model_had_normals: false, }) .process_tri(&triangle, true); } @@ -184,13 +184,13 @@ impl Mesh { Ok(result.unwrap()) } - pub fn from_stl(mut stl_file: R, recalc_normals: bool) -> Result> + pub fn from_stl(mut model_file: R, recalc_normals: bool) -> Result> where R: Read + Seek, { - //let stl = stl_io::read_stl(&mut stl_file)?; - //debug!("{:?}", stl); - let mut stl_iter = stl_io::create_stl_reader(&mut stl_file)?; + //let model = stl_io::read_stl(&mut model_file)?; + //debug!("{:?}", model); + let mut stl_iter = stl_io::create_stl_reader(&mut model_file)?; // Get starting point for finding bounding box // TODO: Remove unwraps so lib can fail gracefully instead of panicing @@ -202,7 +202,7 @@ impl Mesh { normals: Vec::new(), indices: Vec::new(), bounds: BoundingBox::new(&v1), - stl_had_normals: true, + model_had_normals: true, }; let mut face_count = 0; @@ -215,7 +215,7 @@ impl Mesh { //debug!("{:?}",triangle); } - if !mesh.stl_had_normals { + if !mesh.model_had_normals { warn!("STL file missing surface normals"); } info!("Bounds:"); @@ -227,9 +227,9 @@ impl Mesh { } pub fn from_obj(obj_file: File, _recalc_normals: bool) -> Result> { - let mut input = BufReader::new(obj_file); + let mut model = BufReader::new(obj_file); let (models, _) = tobj::load_obj_buf( - &mut input, + &mut model, &LoadOptions { single_index: true, triangulate: true, @@ -248,7 +248,7 @@ impl Mesh { *first_vertex.next().ok_or("Empty Mesh")?, *first_vertex.next().ok_or("Empty Mesh")?, ])), - stl_had_normals: true, + model_had_normals: true, }; for model in &models { let tri_idx = &model.mesh.indices; @@ -308,7 +308,7 @@ impl Mesh { // Use normal from STL file if it is provided, otherwise calculate it ourselves let n: Normal; if recalc_normals || (tri.normal == stl_io::Vector::new([0.0, 0.0, 0.0])) { - self.stl_had_normals = false; + self.model_had_normals = false; n = normal(tri); } else { n = Normal {