use crate::io::vtk_format::VtkFile;
use anyhow::{Context, anyhow};
use log::{info, warn};
use splashsurf_lib::Real;
use splashsurf_lib::mesh::{
IntoVtkUnstructuredGridPiece, Mesh3d, MeshWithData, OwnedMeshAttribute, TriMesh3d,
};
use splashsurf_lib::nalgebra::Vector3;
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::*;
#[derive(Clone, Debug, Default)]
pub struct FormatParameters {
pub input: InputFormatParameters,
pub output: OutputFormatParameters,
}
#[derive(Clone, Debug, Default)]
pub struct InputFormatParameters {}
#[derive(Clone, Debug)]
pub struct OutputFormatParameters {
enable_compression: bool,
}
impl Default for OutputFormatParameters {
fn default() -> Self {
Self {
enable_compression: true,
}
}
}
pub fn read_particle_positions<R: Real, P: AsRef<Path>>(
input_file: P,
_format_params: &InputFormatParameters,
) -> Result<Vec<Vector3<R>>, 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)
}
pub fn read_particle_positions_with_attributes<R: Real, P: AsRef<Path>>(
input_file: P,
attribute_names: &[String],
format_params: &InputFormatParameters,
) -> Result<(Vec<Vector3<R>>, Vec<OwnedMeshAttribute<R>>), 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");
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();
let attributes_to_interpolate = attribute_names.iter().cloned().collect::<HashSet<_>>();
let (particle_positions, attributes) = match extension.as_str() {
"vtk" | "vtu" => {
let vtk_pieces = VtkFile::load_file(input_file)
.map(|f| f.into_pieces())
.with_context(|| "Error while loading VTK file".to_string())?;
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\"."))?;
let particle_positions = first_piece.load_as_particles()?;
let attributes = {
{
let available_attributes = first_piece
.point_attribute_names()
.into_iter()
.collect::<HashSet<_>>();
let missing_attributes = attributes_to_interpolate
.difference(&available_attributes)
.cloned()
.collect::<Vec<_>>();
if !missing_attributes.is_empty() {
return Err(anyhow!(
"Missing attribute(s) \"{}\" in input file",
missing_attributes.join("\", \""),
));
}
}
first_piece.load_point_attributes::<R>(attribute_names)
}?;
(particle_positions, attributes)
}
"bgeo" => {
let bgeo_file = bgeo_format::load_bgeo_file(input_file)
.with_context(|| "Error while loading BGEO file".to_string())?;
let particle_positions = bgeo_format::particles_from_bgeo_file::<R>(&bgeo_file)
.with_context(|| {
"Error while loading particle positions from BGEO file".to_string()
})?;
let attributes = {
{
let available_attributes = bgeo_file
.attribute_definitions
.iter()
.map(|a| a.name.clone())
.collect::<HashSet<_>>();
let missing_attributes = attributes_to_interpolate
.difference(&available_attributes)
.cloned()
.collect::<Vec<_>>();
if !missing_attributes.is_empty() {
return Err(anyhow!(
"Missing attribute(s) \"{}\" in input file",
missing_attributes.join("\", \""),
));
}
}
bgeo_format::attributes_from_bgeo_file(
&bgeo_file,
&Vec::from_iter(attributes_to_interpolate),
)?
};
(particle_positions, attributes)
}
_ => {
return Err(anyhow!(
"Unsupported file format extension \"{}\" for reading particles and attributes",
extension
));
}
};
info!(
"Successfully loaded point {} attribute(s): \"{}\"",
attributes.len(),
attribute_names.join("\", \"")
);
Ok((particle_positions, attributes))
}
pub fn write_particle_positions<R: Real, P: AsRef<Path>>(
particles: &[Vector3<R>],
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(())
}
pub fn read_surface_mesh<R: Real, P: AsRef<Path>>(
input_file: P,
_format_params: &InputFormatParameters,
) -> Result<MeshWithData<R, TriMesh3d<R>>, 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)
}
pub fn write_mesh<'a, R: Real, MeshT: Mesh3d<R>, P: AsRef<Path>>(
mesh: &'a MeshWithData<R, MeshT>,
output_file: P,
_format_params: &OutputFormatParameters,
) -> Result<(), anyhow::Error>
where
for<'b> &'b MeshWithData<R, MeshT>: 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() {
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<R: Real, P: AsRef<Path>>(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_all(&v_f32.to_ne_bytes())?;
}
Ok(())
}