use crate::io::vtk_format::VtkFile; use anyhow::{anyhow, Context}; use log::{info, warn}; use splashsurf_lib::mesh::{ IntoVtkUnstructuredGridPiece, Mesh3d, MeshAttribute, MeshWithData, TriMesh3d, }; use splashsurf_lib::nalgebra::Vector3; use splashsurf_lib::Real; use splashsurf_lib::{io, profile}; use std::collections::HashSet; use std::fs::File; use std::io::{BufWriter, Write}; use std::path::Path; pub(crate) use splashsurf_lib::io::*; /// File format parameters for all io #[derive(Clone, Debug, Default)] pub struct FormatParameters { pub input: InputFormatParameters, pub output: OutputFormatParameters, } /// File format parameters for input files #[derive(Clone, Debug)] pub struct InputFormatParameters {} impl Default for InputFormatParameters { fn default() -> Self { Self {} } } /// File format parameters for output files #[derive(Clone, Debug)] pub struct OutputFormatParameters { /// Enable compression for formats that support it enable_compression: bool, } impl Default for OutputFormatParameters { fn default() -> Self { Self { enable_compression: true, } } } /// Loads particles positions from the given file path, automatically detects the file format pub fn read_particle_positions>( input_file: P, _format_params: &InputFormatParameters, ) -> Result>, anyhow::Error> { let input_file = input_file.as_ref(); info!( "Reading particle dataset from \"{}\"...", input_file.display() ); let particle_positions = { profile!("loading particle positions"); io::particles_from_file(input_file)? }; info!( "Successfully read dataset with {} particle positions.", particle_positions.len() ); Ok(particle_positions) } /// Tries to read particle positions as well as attributes with the given names from the specified file pub fn read_particle_positions_with_attributes>( input_file: P, attribute_names: &[String], format_params: &InputFormatParameters, ) -> Result<(Vec>, Vec>), anyhow::Error> { if attribute_names.is_empty() { return read_particle_positions(input_file, format_params).map(|p| (p, Vec::new())); } let input_file = input_file.as_ref(); info!( "Reading particle dataset and attributes from \"{}\"...", input_file.display() ); profile!("loading particle positions and attributes"); // Check file extension: only VTK is supported for reading attributes at the moment { let extension = input_file.extension().ok_or(anyhow!( "Unable to detect file format of particle input file (file name has to end with supported extension)", ))?.to_str().ok_or(anyhow!("Invalid extension of input file"))?.to_lowercase(); match extension.as_str() { "vtk" | "vtu" => {} _ => { return Err(anyhow!( "Unsupported file format extension \"{}\" for reading particles and attributes", extension )); } } } let vtk_pieces = VtkFile::load_file(input_file) .map(|f| f.into_pieces()) .with_context(|| format!("Failed to load particle positions from file"))?; if vtk_pieces.len() > 1 { warn!("VTK file contains more than one \"piece\". Only the first one will be loaded."); } let first_piece = vtk_pieces .into_iter() .next() .ok_or(anyhow!("VTK file does not contain a supported \"piece\"."))?; // Load particles let particle_positions = first_piece.load_as_particles()?; // Load attributes that should be interpolated let attributes = { // Check if all attributes to interpolate are present in the input file { let attributes_to_interpolate = attribute_names.iter().cloned().collect::>(); let attributes = first_piece .point_attribute_names() .into_iter() .collect::>(); let missing_attributes = attributes_to_interpolate .difference(&attributes) .cloned() .collect::>(); if !missing_attributes.is_empty() { return Err(anyhow!( "Missing attribute(s) \"{}\" in input file", missing_attributes.join("\", \""), )); } } first_piece.load_point_attributes::(attribute_names) }?; info!( "Successfully loaded point {} attribute(s): \"{}\"", attributes.len(), attribute_names.join("\", \"") ); Ok((particle_positions, attributes)) } /// Writes particles positions to the given file path, automatically detects the file format pub fn write_particle_positions>( particles: &[Vector3], output_file: P, format_params: &OutputFormatParameters, ) -> Result<(), anyhow::Error> { let output_file = output_file.as_ref(); info!( "Writing {} particles to \"{}\"...", particles.len(), output_file.display() ); if let Some(extension) = output_file.extension() { profile!("writing particle positions"); let extension = extension .to_str() .ok_or(anyhow!("Invalid extension of output file"))?; match extension.to_lowercase().as_str() { "vtk" => vtk_format::particles_to_vtk(particles, &output_file), "bgeo" => bgeo_format::particles_to_bgeo( particles, &output_file, format_params.enable_compression, ), "json" => json_format::particles_to_json(particles, &output_file), _ => Err(anyhow!( "Unsupported file format extension \"{}\" for writing particles", extension )), }?; } else { return Err(anyhow!( "Unable to detect file format of particle output file (file name has to end with supported extension)", )); }; info!("Successfully wrote particles to file."); Ok(()) } /// Loads a surface mesh from the given file path, automatically detects the file format pub fn read_surface_mesh>( input_file: P, _format_params: &InputFormatParameters, ) -> Result>, anyhow::Error> { let input_file = input_file.as_ref(); info!("Reading mesh from \"{}\"...", input_file.display()); let mesh = if let Some(extension) = input_file.extension() { profile!("loading surface mesh"); let extension = extension .to_str() .ok_or(anyhow!("Invalid extension of input file"))?; match extension.to_lowercase().as_str() { "vtk" => vtk_format::surface_mesh_from_vtk(&input_file), "ply" => ply_format::surface_mesh_from_ply(&input_file), _ => Err(anyhow!( "Unsupported file format extension \"{}\" for reading surface meshes", extension )), } } else { Err(anyhow!( "Unable to detect file format of mesh input file (file name has to end with supported extension)", )) }?; info!( "Successfully read mesh with {} vertices and {} cells.", mesh.mesh.vertices().len(), mesh.mesh.cells().len() ); Ok(mesh) } /// Writes a mesh and its attribute data to the given file path, automatically detects the file format pub fn write_mesh<'a, R: Real, MeshT: Mesh3d, P: AsRef>( mesh: &'a MeshWithData, output_file: P, _format_params: &OutputFormatParameters, ) -> Result<(), anyhow::Error> where for<'b> &'b MeshWithData: IntoVtkUnstructuredGridPiece, { let output_file = output_file.as_ref(); info!( "Writing mesh with {} vertices and {} cells to \"{}\"...", mesh.mesh.vertices().len(), mesh.mesh.cells().len(), output_file.display() ); if let Some(extension) = output_file.extension() { profile!("writing mesh"); let extension = extension .to_str() .ok_or(anyhow!("Invalid extension of output file"))?; match extension.to_lowercase().as_str() { "vtk" => vtk_format::write_vtk(mesh, &output_file, "mesh"), "ply" => ply_format::mesh_to_ply(mesh, &output_file), "obj" => obj_format::mesh_to_obj(mesh, &output_file), _ => Err(anyhow!( "Unsupported file format extension \"{}\"", extension, )), }?; } else { return Err(anyhow!( "Unable to detect file format of mesh output file (file name has to end with supported extension)", )); }; info!("Successfully wrote mesh to file."); Ok(()) } #[allow(dead_code)] pub fn to_binary_f32>(file: P, values: &[R]) -> Result<(), anyhow::Error> { let file = file.as_ref(); let file = File::create(file).context("Unable to create binary file")?; let mut writer = BufWriter::new(file); for v in values { let v_f32 = v.to_f32().unwrap(); writer.write(&v_f32.to_ne_bytes())?; } Ok(()) }