use super::read::{code, err};
use crate::core::{Cell, Dataset, FieldLocation, Mesh, Result};
use std::collections::{BTreeMap, BTreeSet};
use std::io::Write;
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum Version {
V2_2,
#[default]
V4_1,
}
fn write_entities(mesh: &Mesh, writer: &mut impl Write) -> Result<Option<u32>> {
let dimensions: BTreeSet<u32> = mesh.cells.iter().map(|cell| code(cell.kind).1).collect();
let max_dim = dimensions.iter().next_back().copied();
let mut bounds = [[0.; 3]; 2];
if let Some(first) = mesh.points.first() {
bounds = [first.position; 2];
for point in &mesh.points {
let [low, high] = &mut bounds;
for ((min, max), coordinate) in low.iter_mut().zip(high.iter_mut()).zip(point.position)
{
*min = min.min(coordinate);
*max = max.max(coordinate);
}
}
}
writeln!(writer, "$MeshFormat\n4.1 0 8\n$EndMeshFormat\n$Entities")?;
let point_entities = if max_dim.is_none() {
mesh.points.len()
} else {
0
};
writeln!(
writer,
"{point_entities} {} {} {}",
u8::from(dimensions.contains(&1)),
u8::from(dimensions.contains(&2)),
u8::from(dimensions.contains(&3))
)?;
if max_dim.is_none() {
for (index, point) in mesh.points.iter().enumerate() {
writeln!(
writer,
"{} {} {} {} 0",
index + 1,
point.position[0],
point.position[1],
point.position[2]
)?;
}
}
for _dim in &dimensions {
writeln!(
writer,
"1 {} {} {} {} {} {} 0 0",
bounds[0][0], bounds[0][1], bounds[0][2], bounds[1][0], bounds[1][1], bounds[1][2]
)?;
}
writeln!(writer, "$EndEntities")?;
Ok(max_dim)
}
fn write_nodes(mesh: &Mesh, max_dim: Option<u32>, writer: &mut impl Write) -> Result<()> {
writeln!(writer, "$Nodes")?;
let min = mesh.points.iter().map(|p| p.id).min().unwrap_or(0);
let max = mesh.points.iter().map(|p| p.id).max().unwrap_or(0);
if let Some(dim) = max_dim {
writeln!(writer, "1 {} {min} {max}", mesh.points.len())?;
writeln!(writer, "{dim} 1 0 {}", mesh.points.len())?;
for point in &mesh.points {
writeln!(writer, "{}", point.id)?;
}
for point in &mesh.points {
writeln!(
writer,
"{} {} {}",
point.position[0], point.position[1], point.position[2]
)?;
}
} else {
writeln!(
writer,
"{} {} {min} {max}",
mesh.points.len(),
mesh.points.len()
)?;
for (index, point) in mesh.points.iter().enumerate() {
writeln!(writer, "0 {} 0 1\n{}", index + 1, point.id)?;
writeln!(
writer,
"{} {} {}",
point.position[0], point.position[1], point.position[2]
)?;
}
}
writeln!(writer, "$EndNodes")?;
Ok(())
}
pub fn write(dataset: &Dataset, mut writer: impl Write) -> Result<()> {
dataset.validate()?;
dataset.mesh.require_no_sets("E_MSH")?;
if dataset.mesh.cells.iter().any(|c| c.property_id.is_some()) {
return Err(err(
"property IDs have no lossless MSH mapping in this writer",
));
}
let mesh = &dataset.mesh;
let max_dim = write_entities(mesh, &mut writer)?;
write_nodes(mesh, max_dim, &mut writer)?;
writeln!(writer, "$Elements")?;
let mut groups: BTreeMap<u32, Vec<&Cell>> = BTreeMap::new();
for cell in &mesh.cells {
groups.entry(code(cell.kind).0).or_default().push(cell);
}
let min = mesh.cells.iter().map(|c| c.id).min().unwrap_or(0);
let max = mesh.cells.iter().map(|c| c.id).max().unwrap_or(0);
writeln!(writer, "{} {} {min} {max}", groups.len(), mesh.cells.len())?;
for (type_code, cells) in groups {
let dim = code(cells[0].kind).1;
writeln!(writer, "{dim} 1 {type_code} {}", cells.len())?;
for cell in cells {
write!(writer, "{}", cell.id)?;
for &idx in &cell.connectivity {
write!(writer, " {}", mesh.points[idx].id)?;
}
writeln!(writer)?;
}
}
writeln!(writer, "$EndElements")?;
write_fields(dataset, &mut writer)
}
fn write_fields(dataset: &Dataset, writer: &mut impl Write) -> Result<()> {
let mesh = &dataset.mesh;
for field in &dataset.fields {
let name = match field.location {
FieldLocation::Point => "NodeData",
FieldLocation::Cell => "ElementData",
};
if field.name.contains('"') || field.name.contains('\n') {
return Err(err("field name cannot be represented in MSH"));
}
let ids: Vec<u64> = match field.location {
FieldLocation::Point => mesh.points.iter().map(|p| p.id).collect(),
FieldLocation::Cell => mesh.cells.iter().map(|c| c.id).collect(),
};
writeln!(writer, "${name}\n1\n\"{}\"", field.name)?;
if let Some(time) = field.time {
writeln!(writer, "1\n{time}")?;
} else {
writeln!(writer, "0")?;
}
writeln!(
writer,
"3\n{}\n{}\n{}",
field.step.unwrap_or(0),
field.components.len(),
ids.len()
)?;
for (i, id) in ids.iter().enumerate() {
write!(writer, "{id}")?;
for value in &field.values[i * field.components.len()..(i + 1) * field.components.len()]
{
write!(writer, " {value}")?;
}
writeln!(writer)?;
}
writeln!(writer, "$End{name}")?;
}
Ok(())
}
pub fn write_22(dataset: &Dataset, mut writer: impl Write) -> Result<()> {
dataset.validate()?;
dataset.mesh.require_no_sets("E_MSH")?;
let mesh = &dataset.mesh;
if mesh.cells.iter().any(|cell| cell.property_id.is_some()) {
return Err(err(
"property IDs have no lossless MSH mapping in this writer",
));
}
if mesh.points.iter().any(|point| point.id > i32::MAX as u64)
|| mesh.cells.iter().any(|cell| cell.id > i32::MAX as u64)
{
return Err(err(
"MSH 2.2 node and element IDs must fit signed 32-bit integers",
));
}
writeln!(
writer,
"$MeshFormat\n2.2 0 8\n$EndMeshFormat\n$Nodes\n{}",
mesh.points.len()
)?;
for point in &mesh.points {
writeln!(
writer,
"{} {} {} {}",
point.id, point.position[0], point.position[1], point.position[2]
)?;
}
writeln!(writer, "$EndNodes\n$Elements\n{}", mesh.cells.len())?;
for cell in &mesh.cells {
write!(writer, "{} {} 0", cell.id, code(cell.kind).0)?;
for &index in &cell.connectivity {
write!(writer, " {}", mesh.points[index].id)?;
}
writeln!(writer)?;
}
writeln!(writer, "$EndElements")?;
write_fields(dataset, &mut writer)
}
pub fn write_version(dataset: &Dataset, version: Version, writer: impl Write) -> Result<()> {
match version {
Version::V2_2 => write_22(dataset, writer),
Version::V4_1 => write(dataset, writer),
}
}