use super::read::err;
use crate::core::{CellKind, Dataset, FieldLocation, Result};
use std::io::Write;
pub(super) fn type_code(kind: CellKind) -> Result<u32> {
match kind {
CellKind::Hex8 => Ok(1),
CellKind::Wedge6 => Ok(2),
CellKind::Tet4 => Ok(3),
CellKind::Triangle3 => Ok(7),
CellKind::Quad4 => Ok(9),
CellKind::Line2 => Ok(11),
CellKind::Pyramid5 => Err(err("FRD has no supported five-node pyramid type")),
}
}
fn ascii_number(value: f64) -> Result<String> {
let scientific = format!("{value:.5E}");
let (mantissa, exponent) = scientific
.split_once('E')
.ok_or_else(|| err("cannot format FRD number"))?;
let exponent: i32 = exponent.parse().map_err(|_| err("invalid FRD exponent"))?;
if !(-99..=99).contains(&exponent) {
return Err(err("number exceeds FRD ASCII E12.5 range"));
}
let result = format!("{mantissa}E{exponent:+03}");
if result.len() > 12 {
return Err(err("number exceeds FRD ASCII E12.5 width"));
}
Ok(format!("{result:>12}"))
}
fn identifier(value: u64) -> Result<u64> {
if value > 9_999_999_999 {
return Err(err("FRD long-format IDs must fit ten digits"));
}
Ok(value)
}
fn label(value: &str, limit: usize, what: &str) -> Result<()> {
if value.is_empty()
|| value.len() > limit
|| !value.bytes().all(|b| b.is_ascii_graphic() && b != b' ')
{
return Err(err(format!(
"{what} must be 1–{limit} printable ASCII characters without spaces"
)));
}
Ok(())
}
#[allow(clippy::too_many_lines)] pub fn write(dataset: &Dataset, mut output: impl Write) -> Result<()> {
dataset.validate()?;
dataset.mesh.require_no_sets("E_FRD")?;
if dataset
.mesh
.cells
.iter()
.any(|cell| cell.property_id.is_some())
{
return Err(err("FRD cannot encode property IDs"));
}
if dataset.mesh.points.is_empty() || dataset.mesh.cells.is_empty() {
return Err(err("FRD requires nodes and elements"));
}
for point in &dataset.mesh.points {
identifier(point.id)?;
for value in point.position {
ascii_number(value)?;
}
}
for cell in &dataset.mesh.cells {
identifier(cell.id)?;
type_code(cell.kind)?;
}
for field in &dataset.fields {
if field.location != FieldLocation::Point {
return Err(err("FRD writer accepts complete nodal fields only"));
}
label(&field.name, 8, "FRD field name")?;
for component in &field.components {
label(component, 8, "FRD component name")?;
}
if field.components.len() > 99_999 {
return Err(err("FRD component count exceeds five-digit field"));
}
if field.step.is_some_and(|v| !(0..=99_999).contains(&v)) {
return Err(err("FRD step must fit a nonnegative five-digit field"));
}
ascii_number(field.time.unwrap_or(0.0))?;
for value in &field.values {
ascii_number(*value)?;
}
}
writeln!(output, " 1Ccaexfr")?;
writeln!(
output,
" 2C{:18}{:>12}{:37}1",
"",
dataset.mesh.points.len(),
""
)?;
for point in &dataset.mesh.points {
write!(output, " -1{:>10}", point.id)?;
for value in point.position {
write!(output, "{}", ascii_number(value)?)?;
}
writeln!(output)?;
}
writeln!(output, " -3")?;
writeln!(
output,
" 3C{:18}{:>12}{:37}1",
"",
dataset.mesh.cells.len(),
""
)?;
for cell in &dataset.mesh.cells {
writeln!(
output,
" -1{:>10}{:>5}{:>5}{:>5}",
cell.id,
type_code(cell.kind)?,
0,
0
)?;
write!(output, " -2")?;
for &index in &cell.connectivity {
write!(output, "{:>10}", dataset.mesh.points[index].id)?;
}
writeln!(output)?;
}
writeln!(output, " -3")?;
for field in &dataset.fields {
writeln!(
output,
" 100C{:<6}{}{:>12}{:<20}{:>2}{:>5}{:<10}{:>2}",
"",
ascii_number(field.time.unwrap_or(0.0))?,
dataset.mesh.points.len(),
"",
i32::from(field.time.is_some()),
field.step.unwrap_or(0),
"",
1
)?;
writeln!(
output,
" -4 {:<8}{:>5}{:>5}",
field.name,
field.components.len(),
1
)?;
for (i, component) in field.components.iter().enumerate() {
let kind = if field.components.len() == 3 { 2 } else { 1 };
writeln!(
output,
" -5 {:<8}{:>5}{:>5}{:>5}{:>5}{:>5}{:8}",
component,
1,
kind,
i + 1,
0,
0,
""
)?;
}
let components = field.components.len();
for (i, point) in dataset.mesh.points.iter().enumerate() {
for (chunk_index, chunk) in field.values[i * components..(i + 1) * components]
.chunks(6)
.enumerate()
{
if chunk_index == 0 {
write!(output, " -1{:>10}", point.id)?;
} else {
write!(output, " -2{:>10}", "")?;
}
for value in chunk {
write!(output, "{}", ascii_number(*value)?)?;
}
writeln!(output)?;
}
}
writeln!(output, " -3")?;
}
writeln!(output, " 9999")?;
Ok(())
}