use std::path::Path;
use crate::data::{CellArray, Points, PolyData, UnstructuredGrid};
use crate::types::{CellType, VtkError};
pub struct LsDynaReader;
impl LsDynaReader {
pub fn read_poly_data(path: &Path) -> Result<PolyData, VtkError> {
let content = std::fs::read_to_string(path)?;
parse_keyword_poly_data(&content)
}
pub fn read_poly_data_from_str(content: &str) -> Result<PolyData, VtkError> {
parse_keyword_poly_data(content)
}
pub fn read_unstructured(path: &Path) -> Result<UnstructuredGrid, VtkError> {
let content = std::fs::read_to_string(path)?;
parse_keyword_unstructured(&content)
}
}
fn parse_keyword_poly_data(content: &str) -> Result<PolyData, VtkError> {
let (nodes, shell_elements) = parse_keyword_content(content)?;
let mut pd = PolyData::new();
let mut id_to_idx = std::collections::HashMap::new();
for (i, (nid, pos)) in nodes.iter().enumerate() {
id_to_idx.insert(*nid, i);
pd.points.push(*pos);
}
for (_eid, node_ids) in &shell_elements {
let indices: Vec<i64> = node_ids
.iter()
.filter_map(|nid| id_to_idx.get(nid).map(|&idx| idx as i64))
.collect();
if indices.len() >= 3 {
if indices.len() == 4 && indices[2] == indices[3] {
pd.polys.push_cell(&indices[..3]);
} else {
pd.polys.push_cell(&indices);
}
}
}
Ok(pd)
}
fn parse_keyword_unstructured(content: &str) -> Result<UnstructuredGrid, VtkError> {
let (nodes, shell_elements) = parse_keyword_content(content)?;
let mut points = Points::new();
let mut cells = CellArray::new();
let mut cell_types = Vec::new();
let mut id_to_idx = std::collections::HashMap::new();
for (i, (nid, pos)) in nodes.iter().enumerate() {
id_to_idx.insert(*nid, i);
points.push(*pos);
}
for (_eid, node_ids) in &shell_elements {
let indices: Vec<i64> = node_ids
.iter()
.filter_map(|nid| id_to_idx.get(nid).map(|&idx| idx as i64))
.collect();
if indices.len() == 4 && indices[2] != indices[3] {
cells.push_cell(&indices);
cell_types.push(CellType::Quad);
} else if indices.len() >= 3 {
cells.push_cell(&indices[..3]);
cell_types.push(CellType::Triangle);
}
}
let mut ug = UnstructuredGrid::new();
ug.points = points;
for (i, cell) in cells.iter().enumerate() {
ug.push_cell(cell_types[i], cell);
}
Ok(ug)
}
fn parse_keyword_content(
content: &str,
) -> Result<(Vec<(i64, [f64; 3])>, Vec<(i64, Vec<i64>)>), VtkError> {
let mut nodes = Vec::new();
let mut elements = Vec::new();
let mut section = Section::None;
for line in content.lines() {
let trimmed = line.trim();
if trimmed.starts_with('$') || trimmed.is_empty() {
continue;
}
if trimmed.starts_with('*') {
let kw = trimmed.to_uppercase();
if kw.starts_with("*NODE") && !kw.contains("SET") {
section = Section::Node;
} else if kw.starts_with("*ELEMENT_SHELL") {
section = Section::ElementShell;
} else if kw.starts_with("*ELEMENT_SOLID") {
section = Section::ElementSolid;
} else {
section = Section::None;
}
continue;
}
match section {
Section::Node => {
if let Some(node) = parse_node_line(trimmed) {
nodes.push(node);
}
}
Section::ElementShell => {
if let Some(elem) = parse_element_shell_line(trimmed) {
elements.push(elem);
}
}
Section::ElementSolid => {
if let Some(elem) = parse_element_solid_line(trimmed) {
elements.push(elem);
}
}
Section::None => {}
}
}
Ok((nodes, elements))
}
#[derive(Clone, Copy)]
enum Section {
None,
Node,
ElementShell,
ElementSolid,
}
fn parse_node_line(line: &str) -> Option<(i64, [f64; 3])> {
if line.contains(',') {
let parts: Vec<&str> = line.split(',').collect();
if parts.len() >= 4 {
let nid: i64 = parts[0].trim().parse().ok()?;
let x: f64 = parts[1].trim().parse().ok()?;
let y: f64 = parts[2].trim().parse().ok()?;
let z: f64 = parts[3].trim().parse().ok()?;
return Some((nid, [x, y, z]));
}
}
if line.len() >= 56 {
let nid: i64 = line[0..8].trim().parse().ok()?;
let x: f64 = line[8..24].trim().parse().ok()?;
let y: f64 = line[24..40].trim().parse().ok()?;
let z: f64 = line[40..56].trim().parse().ok()?;
return Some((nid, [x, y, z]));
}
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() >= 4 {
let nid: i64 = parts[0].parse().ok()?;
let x: f64 = parts[1].parse().ok()?;
let y: f64 = parts[2].parse().ok()?;
let z: f64 = parts[3].parse().ok()?;
return Some((nid, [x, y, z]));
}
None
}
fn parse_element_shell_line(line: &str) -> Option<(i64, Vec<i64>)> {
let parts: Vec<i64> = if line.contains(',') {
line.split(',').filter_map(|s| s.trim().parse().ok()).collect()
} else {
line.split_whitespace().filter_map(|s| s.parse().ok()).collect()
};
if parts.len() >= 5 {
let eid = parts[0];
let node_ids: Vec<i64> = parts[2..].to_vec();
Some((eid, node_ids))
} else {
None
}
}
fn parse_element_solid_line(line: &str) -> Option<(i64, Vec<i64>)> {
parse_element_shell_line(line)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_simple_keyword() {
let content = r#"
$# LS-DYNA keyword file
*KEYWORD
*NODE
1 0.0 0.0 0.0
2 1.0 0.0 0.0
3 0.5 1.0 0.0
*ELEMENT_SHELL
1 1 1 2 3 3
*END
"#;
let pd = LsDynaReader::read_poly_data_from_str(content).unwrap();
assert_eq!(pd.points.len(), 3);
assert_eq!(pd.polys.num_cells(), 1);
}
#[test]
fn parse_comma_separated() {
let content = "*NODE\n1, 0.0, 0.0, 0.0\n2, 1.0, 0.0, 0.0\n3, 0.0, 1.0, 0.0\n*ELEMENT_SHELL\n1, 1, 1, 2, 3, 3\n*END\n";
let pd = LsDynaReader::read_poly_data_from_str(content).unwrap();
assert_eq!(pd.points.len(), 3);
assert_eq!(pd.polys.num_cells(), 1);
}
#[test]
fn parse_quad_element() {
let content = "*NODE\n1 0 0 0\n2 1 0 0\n3 1 1 0\n4 0 1 0\n*ELEMENT_SHELL\n1 1 1 2 3 4\n*END\n";
let pd = LsDynaReader::read_poly_data_from_str(content).unwrap();
assert_eq!(pd.points.len(), 4);
assert_eq!(pd.polys.num_cells(), 1);
assert_eq!(pd.polys.cell(0).len(), 4);
}
}