From 39e92a39318d09fdeaeebdb1d209037488baa495 Mon Sep 17 00:00:00 2001 From: Chris Price Date: Thu, 12 May 2022 21:12:05 +0100 Subject: [PATCH 1/3] Add support for exporting to STL files --- Cargo.lock | 21 +++++++++++ crates/fj-export/Cargo.toml | 2 + crates/fj-export/src/lib.rs | 75 +++++++++++++++++++++++++++++++++---- 3 files changed, 91 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d0c2a1ed6..7e935fb47 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -727,8 +727,10 @@ dependencies = [ name = "fj-export" version = "0.6.0" dependencies = [ + "anyhow", "fj-interop", "fj-math", + "stl_io", "threemf", ] @@ -816,6 +818,15 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "float-cmp" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1267f4ac4f343772758f7b1bdcbe767c218bbab93bb432acbf5162bbf85a6c4" +dependencies = [ + "num-traits", +] + [[package]] name = "fnv" version = "1.0.7" @@ -2483,6 +2494,16 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "stl_io" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c68e41108b3ec269d885cc56af66cd15f1392f68d2ae461df86d7d3285f065d" +dependencies = [ + "byteorder", + "float-cmp", +] + [[package]] name = "strsim" version = "0.10.0" diff --git a/crates/fj-export/Cargo.toml b/crates/fj-export/Cargo.toml index 0ea7ff4b9..80f8e37fc 100644 --- a/crates/fj-export/Cargo.toml +++ b/crates/fj-export/Cargo.toml @@ -13,7 +13,9 @@ categories = ["encoding", "mathematics", "rendering"] [dependencies] +anyhow = "1.0.57" threemf = "0.3.0" +stl_io = "0.6.0" [dependencies.fj-interop] version = "0.6.0" diff --git a/crates/fj-export/src/lib.rs b/crates/fj-export/src/lib.rs index 2165100e8..289d9836d 100644 --- a/crates/fj-export/src/lib.rs +++ b/crates/fj-export/src/lib.rs @@ -14,16 +14,35 @@ #![deny(missing_docs)] -use std::path::Path; +use std::{fs::File, path::Path}; + +use anyhow::{anyhow, Result}; use fj_interop::mesh::Mesh; -use fj_math::Point; +use fj_math::{Point, Triangle, Vector}; -/// Export the provided mesh to the file at the given path +/// Export the provided mesh to the file at the given path. +/// +/// This function will create a file if it does not exist, and will truncate it if it does. /// -/// Currently only 3MF is supported as an export format. The file extension of -/// the provided path is ignored. -pub fn export(mesh: &Mesh>, path: &Path) -> Result<(), Error> { +/// Currently 3MF & STL file types are supported. The case insensitive file extension of +/// the provided path is used to switch between supported types. +pub fn export(mesh: &Mesh>, path: &Path) -> Result<()> { + match path.extension() { + Some(extension) if extension.to_ascii_uppercase() == "3MF" => { + export_3mf(mesh, path) + } + Some(extension) if extension.to_ascii_uppercase() == "STL" => { + export_stl(mesh, path) + } + Some(extension) => { + Err(anyhow!("Extension not recognised, got {:?}", extension)) + } + None => Err(anyhow!("No extension specified")), + } +} + +fn export_3mf(mesh: &Mesh>, path: &Path) -> Result<()> { let vertices = mesh.vertices().map(|vertex| vertex.into()).collect(); let indices: Vec<_> = mesh.indices().collect(); @@ -48,4 +67,46 @@ pub fn export(mesh: &Mesh>, path: &Path) -> Result<(), Error> { Ok(()) } -pub use threemf::Error; +fn export_stl(mesh: &Mesh>, path: &Path) -> Result<()> { + let points = mesh + .triangles() + .map(|triangle| triangle.points) + .collect::>(); + + let vertices = points.iter().map(|points| { + points.map(|point| { + stl_io::Vertex::new([ + point.x.into_f32(), + point.y.into_f32(), + point.z.into_f32(), + ]) + }) + }); + + let normals = mesh + .triangles() + .map(|triangle| triangle.points.into()) + .map(|triangle: Triangle<3>| triangle.to_parry().normal()) + .collect::>>() + .ok_or_else(|| anyhow!("Unable to compute normal"))?; + + let normals = normals.iter().map(|vector| vector.into_inner().into()).map( + |vector: Vector<3>| { + stl_io::Normal::new([ + vector.x.into_f32(), + vector.y.into_f32(), + vector.z.into_f32(), + ]) + }, + ); + + let mesh = vertices + .zip(normals) + .map(|(vertices, normal)| stl_io::Triangle { normal, vertices }); + + let mut file = File::create(path)?; + + stl_io::write_stl(&mut file, mesh).unwrap(); + + Ok(()) +} From d2efc9b509f978117a48fda680ba88a06ac62920 Mon Sep 17 00:00:00 2001 From: Chris Price Date: Fri, 13 May 2022 16:56:28 +0100 Subject: [PATCH 2/3] Use stl rather than stl_io due to license --- Cargo.lock | 30 +++++++++++++----------------- crates/fj-export/Cargo.toml | 2 +- crates/fj-export/src/lib.rs | 37 ++++++++++++++++++++++++------------- 3 files changed, 38 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7e935fb47..f9e38d25b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -183,6 +183,12 @@ dependencies = [ "syn", ] +[[package]] +name = "byteorder" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c8b41881888cc08af32d47ac4edd52bc7fa27fef774be47a92443756451304" + [[package]] name = "byteorder" version = "1.4.3" @@ -730,7 +736,7 @@ dependencies = [ "anyhow", "fj-interop", "fj-math", - "stl_io", + "stl", "threemf", ] @@ -818,15 +824,6 @@ dependencies = [ "miniz_oxide", ] -[[package]] -name = "float-cmp" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1267f4ac4f343772758f7b1bdcbe767c218bbab93bb432acbf5162bbf85a6c4" -dependencies = [ - "num-traits", -] - [[package]] name = "fnv" version = "1.0.7" @@ -962,7 +959,7 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" dependencies = [ - "byteorder", + "byteorder 1.4.3", ] [[package]] @@ -2495,13 +2492,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] -name = "stl_io" -version = "0.6.0" +name = "stl" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c68e41108b3ec269d885cc56af66cd15f1392f68d2ae461df86d7d3285f065d" +checksum = "466e72b3a9258f51f0562a01f2aea3717fb71d9997f4050c65c251a623926e12" dependencies = [ - "byteorder", - "float-cmp", + "byteorder 0.4.2", ] [[package]] @@ -3282,7 +3278,7 @@ version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93ab48844d61251bb3835145c521d88aa4031d7139e8485990f60ca911fa0815" dependencies = [ - "byteorder", + "byteorder 1.4.3", "bzip2", "crc32fast", "flate2", diff --git a/crates/fj-export/Cargo.toml b/crates/fj-export/Cargo.toml index 80f8e37fc..dd85e88c9 100644 --- a/crates/fj-export/Cargo.toml +++ b/crates/fj-export/Cargo.toml @@ -15,7 +15,7 @@ categories = ["encoding", "mathematics", "rendering"] [dependencies] anyhow = "1.0.57" threemf = "0.3.0" -stl_io = "0.6.0" +stl = "0.2.1" [dependencies.fj-interop] version = "0.6.0" diff --git a/crates/fj-export/src/lib.rs b/crates/fj-export/src/lib.rs index 289d9836d..130fb0a2a 100644 --- a/crates/fj-export/src/lib.rs +++ b/crates/fj-export/src/lib.rs @@ -75,38 +75,49 @@ fn export_stl(mesh: &Mesh>, path: &Path) -> Result<()> { let vertices = points.iter().map(|points| { points.map(|point| { - stl_io::Vertex::new([ - point.x.into_f32(), - point.y.into_f32(), - point.z.into_f32(), - ]) + [point.x.into_f32(), point.y.into_f32(), point.z.into_f32()] }) }); - let normals = mesh - .triangles() - .map(|triangle| triangle.points.into()) + let normals = points + .iter() + .map(|&points| points.into()) .map(|triangle: Triangle<3>| triangle.to_parry().normal()) .collect::>>() .ok_or_else(|| anyhow!("Unable to compute normal"))?; let normals = normals.iter().map(|vector| vector.into_inner().into()).map( |vector: Vector<3>| { - stl_io::Normal::new([ + [ vector.x.into_f32(), vector.y.into_f32(), vector.z.into_f32(), - ]) + ] }, ); - let mesh = vertices + let triangles = vertices .zip(normals) - .map(|(vertices, normal)| stl_io::Triangle { normal, vertices }); + .map(|([v1, v2, v3], normal)| stl::Triangle { + normal, + v1, + v2, + v3, + attr_byte_count: 0, + }) + .collect::>(); let mut file = File::create(path)?; - stl_io::write_stl(&mut file, mesh).unwrap(); + let binary_stl_file = stl::BinaryStlFile { + header: stl::BinaryStlHeader { + header: [0u8; 80], + num_triangles: triangles.len().try_into()?, + }, + triangles, + }; + + stl::write_stl(&mut file, &binary_stl_file)?; Ok(()) } From 5d23fb9b1f8303a4c3e7d87e59b80320b11b711b Mon Sep 17 00:00:00 2001 From: Chris Price Date: Mon, 16 May 2022 20:13:48 +0100 Subject: [PATCH 3/3] Update export details in README --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 71e309f89..6b85af68d 100644 --- a/README.md +++ b/README.md @@ -96,9 +96,9 @@ As of this writing, Fornjot runs on Linux, Windows, and macOS. The project is pr Short- to mid-term, the plan is to add support for the web platform, so Fornjot can run in browsers. Long-term, the plan is to additionally support the major mobile platforms. -### Export to 3MF +### Export to 3MF & STL -Exporting models to the [3D Manufacturing Format](https://en.wikipedia.org/wiki/3D_Manufacturing_Format) (3MF), which is used in 3D printing, is supported. +Exporting models to both the [3D Manufacturing Format](https://en.wikipedia.org/wiki/3D_Manufacturing_Format) (3MF), which is used in 3D printing, and STL is supported. ## Usage @@ -126,12 +126,14 @@ So far, the host application is not published on [crates.io](https://crates.io/) ### Exporting models -To export a model to a 3MF file, run: +To export a model to a file, run: ``` sh cargo run -- -m spacer --export spacer.3mf ``` +The file type is based on the supplied extension. Both 3MF and STL are supported. + ### Model parameters Some models have parameters that can be overridden. For example, to override the inner and outer radii of the spacer model: