use core::str;
use std::{
fmt::Display,
fs,
io::{self, BufRead, Write},
path::Path,
};
use num_traits::Float;
#[cfg(feature = "serde")]
use crate::types::computation::{model::ImplicitModel, traits::ModelFloat};
#[cfg(feature = "serde")]
use serde::de::DeserializeOwned;
use crate::types::{
computation::data::field_iterator::ValueIterator,
geometry::{Mesh, Vec3},
};
use crate::types::computation::data::field_iterator::PointIterator;
pub(crate) fn mesh_to_obj<T: Display>(mesh: &Mesh<T>) -> String {
let mut data = String::new();
for v in mesh.vertices().iter() {
let v_string = format!("v {} {} {}\n", v.x, v.y, v.z);
data.push_str(&v_string);
}
for f in mesh.faces() {
let f_string = format!("f {} {} {}\n", f[0] + 1, f[1] + 1, f[2] + 1);
data.push_str(&f_string);
}
if let Some(normals) = mesh.normals() {
for n in normals.iter() {
let v_string = format!("vn {} {} {}\n", n.x, n.y, n.z);
data.push_str(&v_string);
}
}
data
}
pub fn write_obj_file<T: Display>(mesh: &Mesh<T>, file_name: &str) -> io::Result<()> {
let file_path = Path::new(file_name).with_extension("obj");
let mut file = fs::File::create(file_path)?;
file.write_all(mesh_to_obj(mesh).as_bytes())?;
log::info!(
"Obj file with {} triangles and {} vertices written as {}",
mesh.num_faces(),
mesh.num_vertices(),
file_name.to_owned() + ".obj"
);
Ok(())
}
use std::fs::File;
pub fn parse_obj_file<T: Float>(
file_path: &str,
flip_yz: bool,
read_normals: bool,
) -> Result<Mesh<T>, Box<dyn std::error::Error>> {
let path = Path::new(file_path);
let extension = path
.extension()
.ok_or_else(|| format!("Cannot read file {file_path}. Only .obj files are supported."))?;
if !extension.eq_ignore_ascii_case("obj") {
return Err(format!("Cannot read file {file_path}. Only .obj files are supported.").into());
}
let file = File::open(path)?;
let mut vertices: Vec<Vec3<T>> = Vec::new();
let mut faces: Vec<[usize; 3]> = Vec::new();
let mut normals: Vec<Vec3<T>> = Vec::new();
let mut mesh = Mesh::new();
for line in io::BufReader::new(file).lines() {
let line = line?;
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.is_empty() {
continue;
}
match parts[0] {
"v" => {
if parts.len() != 4 {
return Err("Invalid vertex format. Make sure file is triangulated.,".into());
}
let x: f32 = parts[1].parse()?;
let y: f32 = parts[2].parse()?;
let z: f32 = parts[3].parse()?;
vertices.push(Vec3::new(
T::from(x).unwrap(),
if flip_yz {
-T::from(z).unwrap()
} else {
T::from(y).unwrap()
},
if flip_yz {
T::from(y).unwrap()
} else {
T::from(z).unwrap()
},
));
}
"f" => {
if parts.len() != 4 {
return Err("Invalid face format".into());
}
let mut face: [usize; 3] = [0; 3];
for i in 0..3 {
let indices: Vec<&str> = parts[i + 1].split("/").collect();
let index: usize = indices[0].parse().unwrap();
face[i] = index - 1;
}
faces.push(face);
}
"n" => {
if read_normals {
if parts.len() != 4 {
return Err(
"Invalid vertex format. Make sure file is triangulated.,".into()
);
}
let n_x: f32 = parts[1].parse()?;
let n_y: f32 = parts[2].parse()?;
let n_z: f32 = parts[3].parse()?;
normals.push(Vec3::new(
T::from(n_x).unwrap(),
T::from(n_y).unwrap(),
T::from(n_z).unwrap(),
));
}
}
_ => continue,
}
}
mesh.add_vertices(&vertices);
mesh.add_faces(&faces);
if read_normals {
mesh.set_normals(&normals);
} else {
mesh.compute_vertex_normals();
}
log::info!(
"Obj file {} with {} vertices and {} faces successfully read.",
file_path,
mesh.num_vertices(),
mesh.num_faces()
);
Ok(mesh)
}
pub fn write_field_csv<T, F>(field: &F, file_name: &str) -> io::Result<()>
where
T: Float + Display,
F: PointIterator<T> + ValueIterator<T>,
{
let file_path = Path::new(file_name).with_extension("csv");
let mut file = fs::File::create(file_path)?;
file.write_all(b"x, y, z, v\n")?;
for (point, value) in field.iter_points().zip(field.iter_values()) {
writeln!(file, "{}, {}, {}, {}", point.x, point.y, point.z, value)?;
}
log::info!("Field data written to {}", file_name.to_owned() + ".csv");
Ok(())
}
#[cfg(feature = "serde")]
pub fn write_model_to_file<T: ModelFloat>(
model: &ImplicitModel<T>,
file_name: &str,
) -> io::Result<()> {
let json = serde_json::ser::to_string_pretty(&model)?;
let file_path = Path::new(file_name).with_extension("json");
let mut file = fs::File::create(file_path)?;
file.write_all(json.as_bytes())?;
log::info!(
"Model with {} componets successfully saved as {}.",
model.all_components().len(),
file_name.to_owned() + ".json"
);
Ok(())
}
#[cfg(feature = "serde")]
pub fn read_model_from_file<T: ModelFloat + 'static + DeserializeOwned>(
file_path: &str,
) -> Result<ImplicitModel<T>, Box<dyn std::error::Error>> {
use std::io::Read;
let path = Path::new(file_path);
let extension = path.extension().ok_or_else(|| {
format!("Cannot read file {file_path}. No valid extension provided. Should be .json.")
})?;
if !extension.eq_ignore_ascii_case("json") {
return Err(
format!("Cannot read file {file_path}. Only .json files are supported.").into(),
);
}
let mut file = File::open(path)?;
let mut data: Vec<u8> = Vec::new();
file.read_to_end(&mut data)?;
let deserialized: ImplicitModel<T> = serde_json::de::from_slice(&data)?;
Ok(deserialized)
}
#[cfg(test)]
#[cfg(feature = "serde")]
mod tests {
use super::*;
#[test]
fn test_deserialize_simple_model() {
let model: ImplicitModel<f32> =
read_model_from_file("assets/models/gyroid_model.json").unwrap();
let val = model.evaluate_at("Output", 0., 0., 0.).unwrap();
let expected_val = 41.60254;
assert!(
(val - expected_val).abs() < f32::epsilon(),
"Wrong value returned from model. Was {val}, but should have been {expected_val}"
);
}
#[test]
fn test_deserialize_model_with_file() {
let model: ImplicitModel<f32> =
read_model_from_file("assets/models/bunny_model.json").unwrap();
let val = model.evaluate_at("Output", 0., 0., 0.).unwrap();
let expected_val = 13.442741;
assert!(
(val - expected_val).abs() < f32::epsilon(),
"Wrong value returned from model. Was {val}, but should have been {expected_val}"
);
}
}