Skip to main content

plot3d/
read.rs

1use std::fs::File;
2use std::io::{self, BufRead, BufReader, Read};
3
4use crate::block::Block;
5use crate::utils::{self, read_fortran_record, Endian};
6
7#[derive(Copy, Clone, Debug)]
8pub enum BinaryFormat {
9    Fortran,
10    Raw,
11}
12
13#[derive(Copy, Clone, Debug)]
14pub enum FloatPrecision {
15    F32,
16    F64,
17}
18
19// If you want to re-export Endian from this module, do it from the original path:
20pub use crate::utils::Endian as EndianOrder;
21
22pub fn read_plot3d_ascii(path: &str) -> io::Result<Vec<Block>> {
23    let f = File::open(path)?;
24    let mut rdr = BufReader::new(f);
25
26    // first non-empty line = nblocks
27    let mut first = String::new();
28    loop {
29        first.clear();
30        let n = rdr.read_line(&mut first)?;
31        if n == 0 {
32            return Err(ioerr("empty file"));
33        }
34        if !first.trim().is_empty() {
35            break;
36        }
37    }
38    let nblocks: usize = first
39        .split_whitespace()
40        .next()
41        .ok_or_else(|| ioerr("bad nblocks"))?
42        .parse()
43        .map_err(|_| ioerr("bad nblocks"))?;
44
45    // dims
46    let mut dims = Vec::with_capacity(nblocks);
47    for _ in 0..nblocks {
48        let mut line = String::new();
49        loop {
50            line.clear();
51            rdr.read_line(&mut line)?;
52            if !line.trim().is_empty() {
53                break;
54            }
55        }
56        let mut it = line.split_whitespace();
57        let imax: usize = it
58            .next()
59            .ok_or_else(|| ioerr("bad dims"))?
60            .parse()
61            .map_err(|_| ioerr("bad dims"))?;
62        let jmax: usize = it
63            .next()
64            .ok_or_else(|| ioerr("bad dims"))?
65            .parse()
66            .map_err(|_| ioerr("bad dims"))?;
67        let kmax: usize = it
68            .next()
69            .ok_or_else(|| ioerr("bad dims"))?
70            .parse()
71            .map_err(|_| ioerr("bad dims"))?;
72        dims.push((imax, jmax, kmax));
73    }
74
75    // read N floats from ASCII
76    fn read_n(rdr: &mut BufReader<File>, n: usize) -> io::Result<Vec<f64>> {
77        let mut out = Vec::with_capacity(n);
78        while out.len() < n {
79            let mut line = String::new();
80            let cnt = rdr.read_line(&mut line)?;
81            if cnt == 0 {
82                break;
83            }
84            for t in line.split_whitespace() {
85                if out.len() == n {
86                    break;
87                }
88                out.push(t.parse::<f64>().map_err(|_| ioerr("bad float"))?);
89            }
90        }
91        if out.len() != n {
92            return Err(ioerr("unexpected EOF in payload"));
93        }
94        Ok(out)
95    }
96
97    // payload
98    let mut blocks = Vec::with_capacity(nblocks);
99    for (imax, jmax, kmax) in dims {
100        let n = imax * jmax * kmax;
101        let x = read_n(&mut rdr, n)?;
102        let y = read_n(&mut rdr, n)?;
103        let z = read_n(&mut rdr, n)?;
104        blocks.push(Block::new(imax, jmax, kmax, x, y, z));
105    }
106    Ok(blocks)
107}
108
109pub fn read_plot3d_binary(
110    path: &str,
111    format: BinaryFormat,
112    precision: FloatPrecision,
113    endian: Endian,
114) -> io::Result<Vec<Block>> {
115    let mut f = File::open(path)?;
116    match format {
117        BinaryFormat::Raw => read_binary_raw(&mut f, precision, endian),
118        BinaryFormat::Fortran => read_binary_fortran(&mut f, precision, endian),
119    }
120}
121
122fn read_binary_raw(
123    r: &mut impl Read,
124    precision: FloatPrecision,
125    endian: Endian,
126) -> io::Result<Vec<Block>> {
127    use byteorder::{BigEndian, LittleEndian, ReadBytesExt};
128
129    // header
130    let nblocks = match endian {
131        Endian::Little => r.read_u32::<LittleEndian>()?,
132        Endian::Big => r.read_u32::<BigEndian>()?,
133    } as usize;
134
135    let mut dims = Vec::with_capacity(nblocks);
136    for _ in 0..nblocks {
137        let imax = match endian {
138            Endian::Little => r.read_u32::<LittleEndian>()?,
139            Endian::Big => r.read_u32::<BigEndian>()?,
140        } as usize;
141        let jmax = match endian {
142            Endian::Little => r.read_u32::<LittleEndian>()?,
143            Endian::Big => r.read_u32::<BigEndian>()?,
144        } as usize;
145        let kmax = match endian {
146            Endian::Little => r.read_u32::<LittleEndian>()?,
147            Endian::Big => r.read_u32::<BigEndian>()?,
148        } as usize;
149        dims.push((imax, jmax, kmax));
150    }
151
152    // payload
153    let mut blocks = Vec::with_capacity(nblocks);
154    for (imax, jmax, kmax) in dims {
155        let n = imax * jmax * kmax;
156        let x = read_vec_num(r, n, precision, endian)?;
157        let y = read_vec_num(r, n, precision, endian)?;
158        let z = read_vec_num(r, n, precision, endian)?;
159        blocks.push(Block::new(imax, jmax, kmax, x, y, z));
160    }
161    Ok(blocks)
162}
163
164fn read_binary_fortran(
165    r: &mut impl Read,
166    precision: FloatPrecision,
167    endian: Endian,
168) -> io::Result<Vec<Block>> {
169    // nblocks record
170    let nb_rec = read_fortran_record(r, endian)?;
171    if nb_rec.len() < 4 {
172        return Err(ioerr("short nblocks record"));
173    }
174    let nblocks = utils::Endian::read_u32(&nb_rec[..4], endian) as usize;
175
176    // dims per block (one record per block)
177    let mut dims = Vec::with_capacity(nblocks);
178    for _ in 0..nblocks {
179        let rec = read_fortran_record(r, endian)?;
180        if rec.len() < 12 {
181            return Err(ioerr("short dims record"));
182        }
183        let imax = utils::Endian::read_u32(&rec[0..4], endian) as usize;
184        let jmax = utils::Endian::read_u32(&rec[4..8], endian) as usize;
185        let kmax = utils::Endian::read_u32(&rec[8..12], endian) as usize;
186        dims.push((imax, jmax, kmax));
187    }
188
189    // payload records: X, Y, Z for each block
190    let mut blocks = Vec::with_capacity(nblocks);
191    for (imax, jmax, kmax) in dims {
192        let n = imax * jmax * kmax;
193
194        let xr = read_fortran_record(r, endian)?;
195        let x = match precision {
196            FloatPrecision::F32 => utils::Endian::read_f32_slice(&xr, endian)
197                .into_iter()
198                .map(|v| v as f64)
199                .collect(),
200            FloatPrecision::F64 => utils::Endian::read_f64_slice(&xr, endian),
201        };
202        if x.len() != n {
203            return Err(ioerr("X size mismatch"));
204        }
205
206        let yr = read_fortran_record(r, endian)?;
207        let y = match precision {
208            FloatPrecision::F32 => utils::Endian::read_f32_slice(&yr, endian)
209                .into_iter()
210                .map(|v| v as f64)
211                .collect(),
212            FloatPrecision::F64 => utils::Endian::read_f64_slice(&yr, endian),
213        };
214        if y.len() != n {
215            return Err(ioerr("Y size mismatch"));
216        }
217
218        let zr = read_fortran_record(r, endian)?;
219        let z = match precision {
220            FloatPrecision::F32 => utils::Endian::read_f32_slice(&zr, endian)
221                .into_iter()
222                .map(|v| v as f64)
223                .collect(),
224            FloatPrecision::F64 => utils::Endian::read_f64_slice(&zr, endian),
225        };
226        if z.len() != n {
227            return Err(ioerr("Z size mismatch"));
228        }
229
230        blocks.push(Block::new(imax, jmax, kmax, x, y, z));
231    }
232
233    Ok(blocks)
234}
235
236fn read_vec_num(
237    r: &mut impl Read,
238    n: usize,
239    precision: FloatPrecision,
240    endian: Endian,
241) -> io::Result<Vec<f64>> {
242    use byteorder::{BigEndian, LittleEndian, ReadBytesExt};
243
244    let mut out = Vec::with_capacity(n);
245    match (precision, endian) {
246        (FloatPrecision::F32, Endian::Little) => {
247            for _ in 0..n {
248                out.push(r.read_f32::<LittleEndian>()? as f64);
249            }
250        }
251        (FloatPrecision::F32, Endian::Big) => {
252            for _ in 0..n {
253                out.push(r.read_f32::<BigEndian>()? as f64);
254            }
255        }
256        (FloatPrecision::F64, Endian::Little) => {
257            for _ in 0..n {
258                out.push(r.read_f64::<LittleEndian>()?);
259            }
260        }
261        (FloatPrecision::F64, Endian::Big) => {
262            for _ in 0..n {
263                out.push(r.read_f64::<BigEndian>()?);
264            }
265        }
266    }
267    Ok(out)
268}
269
270fn ioerr(msg: &str) -> io::Error {
271    io::Error::new(io::ErrorKind::InvalidData, msg)
272}
273
274/// Read an AP NASA Fortran unformatted file.
275///
276/// The AP NASA format stores a single block with header integers
277/// `[il, jl, kl, ile, ite, jtip, nbld]` followed by `jl` records
278/// of interleaved `(x, r, theta)` data (f32).
279///
280/// Returns `(block, nbld)` where `nbld` is the number of blades.
281/// Coordinates are converted from cylindrical `(x, r, theta)` to
282/// Cartesian `(x, y, z)`.
283pub fn read_ap_nasa(path: &str, endian: Endian) -> io::Result<(Block, i32)> {
284    let mut f = File::open(path)?;
285
286    // Record 1: 7 integers [il, jl, kl, ile, ite, jtip, nbld]
287    let int_rec = read_fortran_record(&mut f, endian)?;
288    if int_rec.len() < 28 {
289        return Err(ioerr("AP NASA header too short (expected 7 i32)"));
290    }
291    let il = utils::Endian::read_u32(&int_rec[0..4], endian) as usize;
292    let jl = utils::Endian::read_u32(&int_rec[4..8], endian) as usize;
293    let kl = utils::Endian::read_u32(&int_rec[8..12], endian) as usize;
294    // ile, ite, jtip are at offsets 12, 16, 20 (not needed for block)
295    let nbld = utils::Endian::read_u32(&int_rec[24..28], endian) as i32;
296
297    // Read jl records, each containing 3 * (il * kl) floats (f32)
298    let stride = il * kl;
299    let total = il * jl * kl;
300    let mut meshx = Vec::with_capacity(total);
301    let mut meshr = Vec::with_capacity(total);
302    let mut mesht = Vec::with_capacity(total);
303
304    for _j in 0..jl {
305        let rec = read_fortran_record(&mut f, endian)?;
306        let floats = utils::Endian::read_f32_slice(&rec, endian);
307        if floats.len() < 3 * stride {
308            return Err(ioerr("AP NASA data record too short"));
309        }
310        // Layout per record: [x(il*kl), r(il*kl), theta(il*kl)]
311        for idx in 0..stride {
312            meshx.push(floats[idx] as f64);
313            meshr.push(floats[stride + idx] as f64);
314            mesht.push(floats[2 * stride + idx] as f64);
315        }
316    }
317
318    // Data arrives in (j, k, i) order from reading jl records each of kl*il entries.
319    // Reorder to (i, j, k) with i-fastest for Rust Block convention.
320    // Convert from (x, r, theta) to (x, y, z):
321    //   y = r * cos(theta), z = r * sin(theta)
322    let mut x = Vec::with_capacity(total);
323    let mut y = Vec::with_capacity(total);
324    let mut z = Vec::with_capacity(total);
325
326    for k in 0..kl {
327        for j in 0..jl {
328            for i in 0..il {
329                let src = j * (kl * il) + k * il + i;
330                let r = meshr[src];
331                let theta = mesht[src];
332                x.push(meshx[src]);
333                y.push(r * theta.cos());
334                z.push(r * theta.sin());
335            }
336        }
337    }
338
339    Ok((Block::new(il, jl, kl, x, y, z), nbld))
340}