outram_foam_basic_lib/io/
poly_mesh.rs1use std::path::Path;
49
50use super::dict::{
51 fmt_scalar, tokenize, write_header, Token, BANNER, FOOTER, SEPARATOR,
52};
53use super::IoError;
54use crate::mesh::{BoundaryPatch, FvMesh, FvMeshBuilder, PatchKind};
55use crate::primitives::Vector3;
56
57#[derive(Debug, Clone, PartialEq)]
63pub struct MeshFace {
64 pub verts: Vec<usize>,
66 pub owner: usize,
68 pub neighbour: Option<usize>,
70}
71
72#[derive(Debug, Clone, PartialEq)]
78pub struct PolyMesh {
79 pub points: Vec<Vector3>,
81 pub faces: Vec<MeshFace>,
83 pub n_internal_faces: usize,
85 pub n_cells: usize,
87 pub patches: Vec<BoundaryPatch>,
89}
90
91impl PolyMesh {
92 pub fn n_points(&self) -> usize {
94 self.points.len()
95 }
96 pub fn n_faces(&self) -> usize {
98 self.faces.len()
99 }
100 pub fn n_boundary_faces(&self) -> usize {
102 self.faces.len() - self.n_internal_faces
103 }
104
105 pub fn total_volume(&self) -> f64 {
107 self.cell_geometry().0.iter().sum()
108 }
109
110 fn cell_geometry(&self) -> (Vec<f64>, Vec<Vector3>) {
113 let mut face_ctr = Vec::with_capacity(self.faces.len());
114 let mut face_sf = Vec::with_capacity(self.faces.len());
115 for f in &self.faces {
116 let (c, sf) = face_centre_and_area(&self.points, &f.verts);
117 face_ctr.push(c);
118 face_sf.push(sf);
119 }
120
121 let mut c_est = vec![Vector3::ZERO; self.n_cells];
123 let mut n_cell_faces = vec![0.0f64; self.n_cells];
124 for (fi, f) in self.faces.iter().enumerate() {
125 c_est[f.owner] += face_ctr[fi];
126 n_cell_faces[f.owner] += 1.0;
127 if let Some(nb) = f.neighbour {
128 c_est[nb] += face_ctr[fi];
129 n_cell_faces[nb] += 1.0;
130 }
131 }
132 for c in 0..self.n_cells {
133 if n_cell_faces[c] > 0.0 {
134 c_est[c] /= n_cell_faces[c];
135 }
136 }
137
138 let mut vol = vec![0.0f64; self.n_cells];
140 let mut ctr = vec![Vector3::ZERO; self.n_cells];
141 for (fi, f) in self.faces.iter().enumerate() {
142 let cf = face_ctr[fi];
143 {
144 let cell = f.owner;
145 let sf = face_sf[fi];
146 let pyr3 = sf.dot(cf - c_est[cell]);
147 let pyr_ctr = cf * 0.75 + c_est[cell] * 0.25;
148 vol[cell] += pyr3;
149 ctr[cell] += pyr_ctr * pyr3;
150 }
151 if let Some(cell) = f.neighbour {
152 let sf = face_sf[fi] * -1.0;
153 let pyr3 = sf.dot(cf - c_est[cell]);
154 let pyr_ctr = cf * 0.75 + c_est[cell] * 0.25;
155 vol[cell] += pyr3;
156 ctr[cell] += pyr_ctr * pyr3;
157 }
158 }
159 for c in 0..self.n_cells {
160 if vol[c].abs() > f64::EPSILON {
161 ctr[c] /= vol[c];
162 } else {
163 ctr[c] = c_est[c];
164 }
165 vol[c] /= 3.0;
166 }
167 (vol, ctr)
168 }
169
170 pub fn to_fv_mesh(&self) -> Result<FvMesh, IoError> {
177 let (cell_volumes, cell_centres) = self.cell_geometry();
178
179 let mut owner = Vec::with_capacity(self.faces.len());
180 let mut neighbour = Vec::with_capacity(self.n_internal_faces);
181 let mut face_area_vectors = Vec::with_capacity(self.faces.len());
182 let mut face_centres = Vec::with_capacity(self.faces.len());
183 for f in &self.faces {
184 let (c, sf) = face_centre_and_area(&self.points, &f.verts);
185 owner.push(f.owner);
186 if let Some(nb) = f.neighbour {
187 neighbour.push(nb);
188 }
189 face_area_vectors.push(sf);
190 face_centres.push(c);
191 }
192
193 FvMeshBuilder::new()
194 .n_cells(self.n_cells)
195 .n_internal_faces(self.n_internal_faces)
196 .owner(owner)
197 .neighbour(neighbour)
198 .patches(self.patches.clone())
199 .cell_volumes(cell_volumes)
200 .cell_centres(cell_centres)
201 .face_area_vectors(face_area_vectors)
202 .face_centres(face_centres)
203 .build()
204 .map_err(|e| IoError::Mesh(e.to_string()))
205 }
206
207 pub fn read(dir: impl AsRef<Path>) -> Result<Self, IoError> {
213 let dir = dir.as_ref();
214 let points = read_points(&dir.join("points"))?;
215 let face_loops = read_faces(&dir.join("faces"))?;
216 let owner = read_labels(&dir.join("owner"))?;
217 let neighbour = read_labels(&dir.join("neighbour"))?;
218 let raw_patches = read_boundary(&dir.join("boundary"))?;
219
220 if owner.len() != face_loops.len() {
221 return Err(IoError::parse(
222 "polyMesh",
223 format!(
224 "owner has {} entries but faces has {}",
225 owner.len(),
226 face_loops.len()
227 ),
228 ));
229 }
230 let n_internal_faces = neighbour.len();
231 let mut faces = Vec::with_capacity(face_loops.len());
232 for (fi, verts) in face_loops.into_iter().enumerate() {
233 let nb = if fi < n_internal_faces {
234 Some(neighbour[fi])
235 } else {
236 None
237 };
238 faces.push(MeshFace {
239 verts,
240 owner: owner[fi],
241 neighbour: nb,
242 });
243 }
244
245 let mut n_cells = 0usize;
247 for f in &faces {
248 n_cells = n_cells.max(f.owner + 1);
249 if let Some(nb) = f.neighbour {
250 n_cells = n_cells.max(nb + 1);
251 }
252 }
253
254 let patches = raw_patches
255 .into_iter()
256 .map(|p| BoundaryPatch::new(p.name, p.start_face, p.n_faces, kind_from_str(&p.kind)))
257 .collect();
258
259 Ok(Self {
260 points,
261 faces,
262 n_internal_faces,
263 n_cells,
264 patches,
265 })
266 }
267
268 pub fn write(&self, dir: impl AsRef<Path>) -> Result<(), IoError> {
272 let dir = dir.as_ref();
273 std::fs::create_dir_all(dir).map_err(|e| IoError::io(dir.display().to_string(), e))?;
274 write_file(&dir.join("points"), &self.points_string())?;
275 write_file(&dir.join("faces"), &self.faces_string())?;
276 write_file(&dir.join("owner"), &self.owner_string())?;
277 write_file(&dir.join("neighbour"), &self.neighbour_string())?;
278 write_file(&dir.join("boundary"), &self.boundary_string())?;
279 Ok(())
280 }
281
282 fn points_string(&self) -> String {
283 let mut s = header_str("vectorField", "points");
284 s.push_str(&format!("{}\n(\n", self.points.len()));
285 for p in &self.points {
286 s.push_str(&format!(
287 "({} {} {})\n",
288 fmt_scalar(p.x),
289 fmt_scalar(p.y),
290 fmt_scalar(p.z)
291 ));
292 }
293 s.push_str(")\n\n");
294 s.push_str(FOOTER);
295 s
296 }
297
298 fn faces_string(&self) -> String {
299 let mut s = header_str("faceList", "faces");
300 s.push_str(&format!("{}\n(\n", self.faces.len()));
301 for f in &self.faces {
302 s.push_str(&format!("{}(", f.verts.len()));
303 for (i, v) in f.verts.iter().enumerate() {
304 if i > 0 {
305 s.push(' ');
306 }
307 s.push_str(&v.to_string());
308 }
309 s.push_str(")\n");
310 }
311 s.push_str(")\n\n");
312 s.push_str(FOOTER);
313 s
314 }
315
316 fn owner_string(&self) -> String {
317 let mut s = header_str("labelList", "owner");
318 s.push_str(&format!("{}\n(\n", self.faces.len()));
319 for f in &self.faces {
320 s.push_str(&f.owner.to_string());
321 s.push('\n');
322 }
323 s.push_str(")\n\n");
324 s.push_str(FOOTER);
325 s
326 }
327
328 fn neighbour_string(&self) -> String {
329 let mut s = header_str("labelList", "neighbour");
330 s.push_str(&format!("{}\n(\n", self.n_internal_faces));
331 for f in self.faces.iter().take(self.n_internal_faces) {
332 s.push_str(&f.neighbour.unwrap_or(0).to_string());
333 s.push('\n');
334 }
335 s.push_str(")\n\n");
336 s.push_str(FOOTER);
337 s
338 }
339
340 fn boundary_string(&self) -> String {
341 let mut s = header_str("polyBoundaryMesh", "boundary");
342 s.push_str(&format!("{}\n(\n", self.patches.len()));
343 for p in &self.patches {
344 s.push_str(&format!(" {}\n {{\n", p.name));
345 s.push_str(&format!(" type {};\n", kind_to_str(p.kind)));
346 s.push_str(&format!(" nFaces {};\n", p.size));
347 s.push_str(&format!(" startFace {};\n", p.start));
348 s.push_str(" }\n");
349 }
350 s.push_str(")\n\n");
351 s.push_str(FOOTER);
352 s
353 }
354}
355
356fn face_centre_and_area(points: &[Vector3], verts: &[usize]) -> (Vector3, Vector3) {
361 let n = verts.len();
362 debug_assert!(n >= 3);
363 if n == 3 {
364 let a = points[verts[0]];
365 let b = points[verts[1]];
366 let c = points[verts[2]];
367 let centre = (a + b + c) / 3.0;
368 let area = (b - a).cross(c - a) * 0.5;
369 return (centre, area);
370 }
371 let mut c_est = Vector3::ZERO;
372 for &v in verts {
373 c_est += points[v];
374 }
375 c_est /= n as f64;
376
377 let mut sum_a = 0.0;
378 let mut sum_n = Vector3::ZERO;
379 let mut sum_ac = Vector3::ZERO;
380 for i in 0..n {
381 let p1 = points[verts[i]];
382 let p2 = points[verts[(i + 1) % n]];
383 let mid = (p1 + p2 + c_est) / 3.0;
384 let n_tri = (p2 - p1).cross(c_est - p1);
385 let a_tri = n_tri.mag();
386 sum_a += a_tri;
387 sum_n += n_tri;
388 sum_ac += mid * a_tri;
389 }
390 let centre = if sum_a > f64::EPSILON {
391 sum_ac / sum_a
392 } else {
393 c_est
394 };
395 (centre, sum_n * 0.5)
396}
397
398fn kind_from_str(s: &str) -> PatchKind {
401 match s {
402 "wall" => PatchKind::Wall,
403 "symmetry" | "symmetryPlane" => PatchKind::Symmetry,
404 "empty" => PatchKind::Empty,
405 "wedge" => PatchKind::Wedge,
406 "cyclic" => PatchKind::Cyclic,
407 "processor" => PatchKind::Processor,
408 _ => PatchKind::Patch,
409 }
410}
411
412fn kind_to_str(k: PatchKind) -> &'static str {
413 match k {
414 PatchKind::Patch => "patch",
415 PatchKind::Wall => "wall",
416 PatchKind::Symmetry => "symmetry",
417 PatchKind::Empty => "empty",
418 PatchKind::Wedge => "wedge",
419 PatchKind::Cyclic => "cyclic",
420 PatchKind::Processor => "processor",
421 }
422}
423
424struct ListCursor {
428 tokens: Vec<Token>,
429 pos: usize,
430 context: String,
431}
432
433impl ListCursor {
434 fn new(text: &str, context: impl Into<String>) -> Self {
435 let mut c = Self {
436 tokens: tokenize(text),
437 pos: 0,
438 context: context.into(),
439 };
440 c.skip_header();
441 c
442 }
443
444 fn err(&self, m: impl Into<String>) -> IoError {
445 IoError::parse(self.context.clone(), m.into())
446 }
447
448 fn peek(&self) -> Option<&str> {
449 self.tokens.get(self.pos).map(|t| t.text.as_str())
450 }
451
452 fn next(&mut self) -> Option<String> {
453 let t = self.tokens.get(self.pos).map(|t| t.text.clone());
454 if t.is_some() {
455 self.pos += 1;
456 }
457 t
458 }
459
460 fn expect(&mut self, tok: &str) -> Result<(), IoError> {
461 match self.next() {
462 Some(t) if t == tok => Ok(()),
463 other => Err(self.err(format!("expected `{tok}`, found {other:?}"))),
464 }
465 }
466
467 fn skip_header(&mut self) {
469 if self.peek() == Some("FoamFile") {
470 self.next(); let mut depth = 0usize;
473 while let Some(t) = self.next() {
474 match t.as_str() {
475 "{" => depth += 1,
476 "}" => {
477 depth -= 1;
478 if depth == 0 {
479 break;
480 }
481 }
482 _ => {}
483 }
484 }
485 }
486 }
487
488 fn parse_usize(&mut self) -> Result<usize, IoError> {
489 let t = self
490 .next()
491 .ok_or_else(|| self.err("expected an integer, found end of input"))?;
492 t.parse::<usize>()
493 .map_err(|_| self.err(format!("expected an integer, found `{t}`")))
494 }
495
496 fn parse_f64(&mut self) -> Result<f64, IoError> {
497 let t = self
498 .next()
499 .ok_or_else(|| self.err("expected a number, found end of input"))?;
500 t.parse::<f64>()
501 .map_err(|_| self.err(format!("expected a number, found `{t}`")))
502 }
503}
504
505fn read_points(path: &Path) -> Result<Vec<Vector3>, IoError> {
506 let text = read_to_string(path)?;
507 let mut c = ListCursor::new(&text, path.display().to_string());
508 let count = c.parse_usize()?;
509 c.expect("(")?;
510 let mut pts = Vec::with_capacity(count);
511 for _ in 0..count {
512 c.expect("(")?;
513 let x = c.parse_f64()?;
514 let y = c.parse_f64()?;
515 let z = c.parse_f64()?;
516 c.expect(")")?;
517 pts.push(Vector3::new(x, y, z));
518 }
519 c.expect(")")?;
520 Ok(pts)
521}
522
523fn read_faces(path: &Path) -> Result<Vec<Vec<usize>>, IoError> {
524 let text = read_to_string(path)?;
525 let mut c = ListCursor::new(&text, path.display().to_string());
526 let count = c.parse_usize()?;
527 c.expect("(")?;
528 let mut faces = Vec::with_capacity(count);
529 for _ in 0..count {
530 let nverts = c.parse_usize()?;
531 c.expect("(")?;
532 let mut verts = Vec::with_capacity(nverts);
533 for _ in 0..nverts {
534 verts.push(c.parse_usize()?);
535 }
536 c.expect(")")?;
537 faces.push(verts);
538 }
539 c.expect(")")?;
540 Ok(faces)
541}
542
543fn read_labels(path: &Path) -> Result<Vec<usize>, IoError> {
544 let text = read_to_string(path)?;
545 let mut c = ListCursor::new(&text, path.display().to_string());
546 let count = c.parse_usize()?;
547 c.expect("(")?;
548 let mut out = Vec::with_capacity(count);
549 for _ in 0..count {
550 out.push(c.parse_usize()?);
551 }
552 c.expect(")")?;
553 Ok(out)
554}
555
556struct RawPatch {
557 name: String,
558 kind: String,
559 n_faces: usize,
560 start_face: usize,
561}
562
563fn read_boundary(path: &Path) -> Result<Vec<RawPatch>, IoError> {
564 let text = read_to_string(path)?;
565 let mut c = ListCursor::new(&text, path.display().to_string());
566 let count = c.parse_usize()?;
567 c.expect("(")?;
568 let mut patches = Vec::with_capacity(count);
569 for _ in 0..count {
570 let name = c
571 .next()
572 .ok_or_else(|| c.err("expected a patch name"))?
573 .to_string();
574 c.expect("{")?;
575 let mut kind = String::from("patch");
576 let mut n_faces = 0usize;
577 let mut start_face = 0usize;
578 while c.peek() != Some("}") {
581 let key = c
582 .next()
583 .ok_or_else(|| c.err("unexpected end of input in patch entry"))?
584 .to_string();
585 match key.as_str() {
586 "type" => {
587 kind = c
588 .next()
589 .ok_or_else(|| c.err("expected a patch type"))?
590 .to_string();
591 c.expect(";")?;
592 }
593 "nFaces" => {
594 n_faces = c.parse_usize()?;
595 c.expect(";")?;
596 }
597 "startFace" => {
598 start_face = c.parse_usize()?;
599 c.expect(";")?;
600 }
601 _ => {
602 let mut depth = 0usize;
604 loop {
605 match c.next().as_deref() {
606 Some("(") => depth += 1,
607 Some(")") => depth = depth.saturating_sub(1),
608 Some(";") if depth == 0 => break,
609 Some(_) => {}
610 None => return Err(c.err("unterminated patch entry")),
611 }
612 }
613 }
614 }
615 }
616 c.expect("}")?;
617 patches.push(RawPatch {
618 name,
619 kind,
620 n_faces,
621 start_face,
622 });
623 }
624 c.expect(")")?;
625 Ok(patches)
626}
627
628fn read_to_string(path: &Path) -> Result<String, IoError> {
631 std::fs::read_to_string(path).map_err(|e| IoError::io(path.display().to_string(), e))
632}
633
634fn write_file(path: &Path, contents: &str) -> Result<(), IoError> {
635 std::fs::write(path, contents).map_err(|e| IoError::io(path.display().to_string(), e))
636}
637
638fn header_str(class: &str, object: &str) -> String {
640 let mut s = String::new();
641 s.push_str(BANNER);
642 let header = super::dict::FoamHeader::standard_with_location(class, "constant/polyMesh", object);
643 write_header(&mut s, &header);
644 s.push_str(SEPARATOR);
645 s.push_str("\n\n");
646 s
647}