1#![allow(clippy::should_implement_trait)]
2use std::fmt::Write as FmtWrite;
12use std::fs;
13use std::io::{self, BufRead};
14
15use crate::Error as IoError;
16
17#[derive(Debug, Clone, PartialEq)]
23pub struct AbaqusNode {
24 pub id: usize,
26 pub coordinates: [f64; 3],
28}
29
30impl AbaqusNode {
31 pub fn new(id: usize, coordinates: [f64; 3]) -> Self {
33 Self { id, coordinates }
34 }
35}
36
37#[derive(Debug, Clone, PartialEq, Eq)]
43pub enum ElementType {
44 C3D4,
46 C3D8,
48 S4,
50 T3D2,
52 Unknown(String),
54}
55
56impl ElementType {
57 pub fn as_str(&self) -> &str {
59 match self {
60 ElementType::C3D4 => "C3D4",
61 ElementType::C3D8 => "C3D8",
62 ElementType::S4 => "S4",
63 ElementType::T3D2 => "T3D2",
64 ElementType::Unknown(s) => s.as_str(),
65 }
66 }
67
68 pub fn from_str(s: &str) -> Self {
70 match s.trim().to_uppercase().as_str() {
71 "C3D4" => ElementType::C3D4,
72 "C3D8" => ElementType::C3D8,
73 "S4" => ElementType::S4,
74 "T3D2" => ElementType::T3D2,
75 other => ElementType::Unknown(other.to_string()),
76 }
77 }
78}
79
80#[derive(Debug, Clone, PartialEq)]
86pub struct AbaqusElement {
87 pub id: usize,
89 pub element_type: ElementType,
91 pub node_ids: Vec<usize>,
93}
94
95impl AbaqusElement {
96 pub fn new(id: usize, element_type: ElementType, node_ids: Vec<usize>) -> Self {
98 Self {
99 id,
100 element_type,
101 node_ids,
102 }
103 }
104}
105
106#[derive(Debug, Clone)]
112pub struct AbaqusSection {
113 pub name: String,
115 pub material_name: String,
117 pub elements: Vec<usize>,
119}
120
121impl AbaqusSection {
122 pub fn new(
124 name: impl Into<String>,
125 material_name: impl Into<String>,
126 elements: Vec<usize>,
127 ) -> Self {
128 Self {
129 name: name.into(),
130 material_name: material_name.into(),
131 elements,
132 }
133 }
134}
135
136#[derive(Debug, Clone)]
142pub struct ElasticProps {
143 pub young_modulus: f64,
145 pub poisson_ratio: f64,
147}
148
149#[derive(Debug, Clone)]
151pub struct PlasticProps {
152 pub yield_stress: f64,
154 pub hardening_modulus: f64,
156}
157
158#[derive(Debug, Clone)]
160pub struct AbaqusMaterial {
161 pub name: String,
163 pub elastic: ElasticProps,
165 pub density: f64,
167 pub plastic: Option<PlasticProps>,
169}
170
171impl AbaqusMaterial {
172 pub fn new_elastic(
174 name: impl Into<String>,
175 young_modulus: f64,
176 poisson_ratio: f64,
177 density: f64,
178 ) -> Self {
179 Self {
180 name: name.into(),
181 elastic: ElasticProps {
182 young_modulus,
183 poisson_ratio,
184 },
185 density,
186 plastic: None,
187 }
188 }
189
190 pub fn new_plastic(
192 name: impl Into<String>,
193 young_modulus: f64,
194 poisson_ratio: f64,
195 density: f64,
196 yield_stress: f64,
197 hardening_modulus: f64,
198 ) -> Self {
199 Self {
200 name: name.into(),
201 elastic: ElasticProps {
202 young_modulus,
203 poisson_ratio,
204 },
205 density,
206 plastic: Some(PlasticProps {
207 yield_stress,
208 hardening_modulus,
209 }),
210 }
211 }
212}
213
214#[derive(Debug, Clone, PartialEq)]
220pub enum BoundaryCondition {
221 Encastre {
223 node_set: String,
225 },
226 Pinned {
228 node_set: String,
230 },
231 SymmetryPlane {
233 node_set: String,
235 axis: u8,
237 },
238}
239
240impl BoundaryCondition {
241 pub fn keyword(&self) -> &'static str {
243 match self {
244 BoundaryCondition::Encastre { .. } => "ENCASTRE",
245 BoundaryCondition::Pinned { .. } => "PINNED",
246 BoundaryCondition::SymmetryPlane { .. } => "SYMMETRY",
247 }
248 }
249}
250
251#[derive(Debug, Clone, Default)]
257pub struct AbaqusMesh {
258 pub nodes: Vec<AbaqusNode>,
260 pub elements: Vec<AbaqusElement>,
262 pub sections: Vec<AbaqusSection>,
264 pub materials: Vec<AbaqusMaterial>,
266 pub boundary_conditions: Vec<BoundaryCondition>,
268}
269
270impl AbaqusMesh {
271 pub fn new() -> Self {
273 Self::default()
274 }
275}
276
277#[derive(Debug, Clone, Default)]
283pub struct AbaqusWriter;
284
285impl AbaqusWriter {
286 pub fn new() -> Self {
288 Self
289 }
290
291 pub fn write(&self, mesh: &AbaqusMesh, path: &str) -> Result<(), IoError> {
295 let mut buf = String::new();
296
297 let _ = writeln!(buf, "** Generated by OxiPhysics AbaqusWriter");
298 let _ = writeln!(buf, "*Heading");
299 let _ = writeln!(buf, "OxiPhysics model");
300
301 let _ = writeln!(buf, "*Node");
303 for node in &mesh.nodes {
304 writeln!(
305 buf,
306 "{}, {:.15e}, {:.15e}, {:.15e}",
307 node.id, node.coordinates[0], node.coordinates[1], node.coordinates[2]
308 )
309 .expect("operation should succeed");
310 }
311
312 let mut types_seen: Vec<String> = Vec::new();
315 for el in &mesh.elements {
316 let t = el.element_type.as_str().to_string();
317 if !types_seen.contains(&t) {
318 types_seen.push(t);
319 }
320 }
321
322 for etype in &types_seen {
323 let _ = writeln!(buf, "*Element, type={etype}");
324 for el in mesh
325 .elements
326 .iter()
327 .filter(|e| e.element_type.as_str() == etype)
328 {
329 let ids: Vec<String> = el.node_ids.iter().map(|n| n.to_string()).collect();
330 let _ = writeln!(buf, "{}, {}", el.id, ids.join(", "));
331 }
332 }
333
334 for mat in &mesh.materials {
336 let _ = writeln!(buf, "*Material, name={}", mat.name);
337 let _ = writeln!(buf, "*Density");
338 let _ = writeln!(buf, "{:.15e}", mat.density);
339 let _ = writeln!(buf, "*Elastic");
340 writeln!(
341 buf,
342 "{:.15e}, {:.15e}",
343 mat.elastic.young_modulus, mat.elastic.poisson_ratio
344 )
345 .expect("operation should succeed");
346 if let Some(p) = &mat.plastic {
347 let _ = writeln!(buf, "*Plastic");
348 let _ = writeln!(buf, "{:.15e}, {:.15e}", p.yield_stress, p.hardening_modulus);
349 }
350 }
351
352 for sec in &mesh.sections {
354 let el_set: Vec<String> = sec.elements.iter().map(|e| e.to_string()).collect();
355 writeln!(
356 buf,
357 "*Solid Section, elset={}, material={}",
358 sec.name, sec.material_name
359 )
360 .expect("operation should succeed");
361 if !el_set.is_empty() {
362 let _ = writeln!(buf, "{}", el_set.join(", "));
363 }
364 }
365
366 for bc in &mesh.boundary_conditions {
368 match bc {
369 BoundaryCondition::Encastre { node_set } => {
370 let _ = writeln!(buf, "*Boundary");
371 let _ = writeln!(buf, "{node_set}, ENCASTRE");
372 }
373 BoundaryCondition::Pinned { node_set } => {
374 let _ = writeln!(buf, "*Boundary");
375 let _ = writeln!(buf, "{node_set}, PINNED");
376 }
377 BoundaryCondition::SymmetryPlane { node_set, axis } => {
378 let _ = writeln!(buf, "*Boundary");
379 let sym = match axis {
380 1 => "XSYMM",
381 2 => "YSYMM",
382 3 => "ZSYMM",
383 _ => "XSYMM",
384 };
385 let _ = writeln!(buf, "{node_set}, {sym}");
386 }
387 }
388 }
389
390 let _ = writeln!(buf, "*End Part");
391
392 fs::write(path, buf).map_err(IoError::Io)
393 }
394}
395
396#[derive(Debug, Clone, Default)]
402pub struct AbaqusReader;
403
404impl AbaqusReader {
405 pub fn new() -> Self {
407 Self
408 }
409
410 pub fn parse(&self, path: &str) -> Result<AbaqusMesh, IoError> {
416 let file = fs::File::open(path).map_err(IoError::Io)?;
417 let reader = io::BufReader::new(file);
418
419 let mut mesh = AbaqusMesh::new();
420 let mut current_block = Block::None;
421
422 for line_res in reader.lines() {
423 let line = line_res.map_err(IoError::Io)?;
424 let trimmed = line.trim();
425
426 if trimmed.is_empty() || trimmed.starts_with("**") {
427 continue;
428 }
429
430 if trimmed.starts_with('*') {
431 let upper = trimmed.to_uppercase();
433 if upper.starts_with("*NODE") && !upper.starts_with("*NSET") {
434 current_block = Block::Node;
435 } else if upper.starts_with("*ELEMENT") {
436 let etype = Self::extract_param(trimmed, "TYPE")
438 .unwrap_or_else(|| "UNKNOWN".to_string());
439 current_block = Block::Element(ElementType::from_str(&etype));
440 } else {
441 current_block = Block::None;
442 }
443 continue;
444 }
445
446 match ¤t_block {
447 Block::Node => {
448 if let Some(node) = Self::parse_node_line(trimmed) {
449 mesh.nodes.push(node);
450 }
451 }
452 Block::Element(etype) => {
453 if let Some(el) = Self::parse_element_line(trimmed, etype.clone()) {
454 mesh.elements.push(el);
455 }
456 }
457 Block::None => {}
458 }
459 }
460
461 Ok(mesh)
462 }
463
464 fn extract_param(line: &str, key: &str) -> Option<String> {
466 let upper = line.to_uppercase();
467 let key_eq = format!("{key}=");
468 let pos = upper.find(&key_eq)?;
469 let rest = &line[pos + key_eq.len()..];
470 let end = rest.find(',').unwrap_or(rest.len());
472 Some(rest[..end].trim().to_string())
473 }
474
475 fn parse_node_line(line: &str) -> Option<AbaqusNode> {
477 let parts: Vec<&str> = line.split(',').collect();
478 if parts.len() < 4 {
479 return None;
480 }
481 let id: usize = parts[0].trim().parse().ok()?;
482 let x: f64 = parts[1].trim().parse().ok()?;
483 let y: f64 = parts[2].trim().parse().ok()?;
484 let z: f64 = parts[3].trim().parse().ok()?;
485 Some(AbaqusNode::new(id, [x, y, z]))
486 }
487
488 fn parse_element_line(line: &str, etype: ElementType) -> Option<AbaqusElement> {
490 let parts: Vec<&str> = line.split(',').collect();
491 if parts.len() < 2 {
492 return None;
493 }
494 let id: usize = parts[0].trim().parse().ok()?;
495 let node_ids: Vec<usize> = parts[1..]
496 .iter()
497 .filter_map(|s| s.trim().parse().ok())
498 .collect();
499 if node_ids.is_empty() {
500 return None;
501 }
502 Some(AbaqusElement::new(id, etype, node_ids))
503 }
504}
505
506#[derive(Debug, Clone)]
508enum Block {
509 None,
511 Node,
513 Element(ElementType),
515}
516
517#[cfg(test)]
522mod tests {
523 use super::*;
524
525 #[test]
528 fn test_element_type_from_str_c3d4() {
529 assert_eq!(ElementType::from_str("C3D4"), ElementType::C3D4);
530 }
531
532 #[test]
533 fn test_element_type_from_str_case_insensitive() {
534 assert_eq!(ElementType::from_str("c3d8"), ElementType::C3D8);
535 }
536
537 #[test]
538 fn test_element_type_from_str_s4() {
539 assert_eq!(ElementType::from_str("S4"), ElementType::S4);
540 }
541
542 #[test]
543 fn test_element_type_from_str_t3d2() {
544 assert_eq!(ElementType::from_str("T3D2"), ElementType::T3D2);
545 }
546
547 #[test]
548 fn test_element_type_from_str_unknown() {
549 match ElementType::from_str("FOOBAR") {
550 ElementType::Unknown(s) => assert_eq!(s, "FOOBAR"),
551 _ => panic!("expected Unknown"),
552 }
553 }
554
555 #[test]
556 fn test_element_type_as_str() {
557 assert_eq!(ElementType::C3D4.as_str(), "C3D4");
558 assert_eq!(ElementType::C3D8.as_str(), "C3D8");
559 assert_eq!(ElementType::S4.as_str(), "S4");
560 assert_eq!(ElementType::T3D2.as_str(), "T3D2");
561 }
562
563 #[test]
566 fn test_node_new() {
567 let n = AbaqusNode::new(1, [1.0, 2.0, 3.0]);
568 assert_eq!(n.id, 1);
569 assert_eq!(n.coordinates, [1.0, 2.0, 3.0]);
570 }
571
572 #[test]
575 fn test_material_elastic() {
576 let m = AbaqusMaterial::new_elastic("Steel", 210e9, 0.3, 7800.0);
577 assert_eq!(m.name, "Steel");
578 assert!((m.elastic.young_modulus - 210e9).abs() < 1.0);
579 assert!(m.plastic.is_none());
580 }
581
582 #[test]
583 fn test_material_plastic() {
584 let m = AbaqusMaterial::new_plastic("Steel", 210e9, 0.3, 7800.0, 250e6, 1e9);
585 assert!(m.plastic.is_some());
586 let p = m.plastic.unwrap();
587 assert!((p.yield_stress - 250e6).abs() < 1.0);
588 }
589
590 #[test]
593 fn test_bc_keyword_encastre() {
594 let bc = BoundaryCondition::Encastre {
595 node_set: "FIXED".to_string(),
596 };
597 assert_eq!(bc.keyword(), "ENCASTRE");
598 }
599
600 #[test]
601 fn test_bc_keyword_pinned() {
602 let bc = BoundaryCondition::Pinned {
603 node_set: "PIN".to_string(),
604 };
605 assert_eq!(bc.keyword(), "PINNED");
606 }
607
608 #[test]
609 fn test_bc_keyword_symmetry() {
610 let bc = BoundaryCondition::SymmetryPlane {
611 node_set: "SYM".to_string(),
612 axis: 2,
613 };
614 assert_eq!(bc.keyword(), "SYMMETRY");
615 }
616
617 fn sample_mesh() -> AbaqusMesh {
620 let mut mesh = AbaqusMesh::new();
621 mesh.nodes = vec![
622 AbaqusNode::new(1, [0.0, 0.0, 0.0]),
623 AbaqusNode::new(2, [1.0, 0.0, 0.0]),
624 AbaqusNode::new(3, [0.0, 1.0, 0.0]),
625 AbaqusNode::new(4, [0.0, 0.0, 1.0]),
626 ];
627 mesh.elements = vec![AbaqusElement::new(1, ElementType::C3D4, vec![1, 2, 3, 4])];
628 mesh
629 }
630
631 #[test]
632 fn test_write_creates_file() {
633 let path = "/tmp/oxiphysics_abaqus_test_write.inp";
634 let mesh = sample_mesh();
635 let writer = AbaqusWriter::new();
636 writer.write(&mesh, path).expect("write failed");
637 assert!(std::path::Path::new(path).exists());
638 }
639
640 #[test]
641 fn test_roundtrip_node_count() {
642 let path = "/tmp/oxiphysics_abaqus_roundtrip.inp";
643 let mesh = sample_mesh();
644 let writer = AbaqusWriter::new();
645 writer.write(&mesh, path).expect("write failed");
646
647 let reader = AbaqusReader::new();
648 let parsed = reader.parse(path).expect("parse failed");
649 assert_eq!(parsed.nodes.len(), 4);
650 }
651
652 #[test]
653 fn test_roundtrip_element_count() {
654 let path = "/tmp/oxiphysics_abaqus_rt_elem.inp";
655 let mesh = sample_mesh();
656 AbaqusWriter::new().write(&mesh, path).expect("write");
657 let parsed = AbaqusReader::new().parse(path).expect("parse");
658 assert_eq!(parsed.elements.len(), 1);
659 }
660
661 #[test]
662 fn test_roundtrip_node_ids() {
663 let path = "/tmp/oxiphysics_abaqus_rt_nodeids.inp";
664 let mesh = sample_mesh();
665 AbaqusWriter::new().write(&mesh, path).expect("write");
666 let parsed = AbaqusReader::new().parse(path).expect("parse");
667 let ids: Vec<usize> = parsed.nodes.iter().map(|n| n.id).collect();
668 assert_eq!(ids, vec![1, 2, 3, 4]);
669 }
670
671 #[test]
672 fn test_roundtrip_node_coordinates() {
673 let path = "/tmp/oxiphysics_abaqus_rt_coords.inp";
674 let mesh = sample_mesh();
675 AbaqusWriter::new().write(&mesh, path).expect("write");
676 let parsed = AbaqusReader::new().parse(path).expect("parse");
677 let n1 = &parsed.nodes[0];
678 assert!((n1.coordinates[0]).abs() < 1e-10);
679 let n2 = &parsed.nodes[1];
680 assert!((n2.coordinates[0] - 1.0).abs() < 1e-10);
681 }
682
683 #[test]
684 fn test_roundtrip_element_type() {
685 let path = "/tmp/oxiphysics_abaqus_rt_etype.inp";
686 let mesh = sample_mesh();
687 AbaqusWriter::new().write(&mesh, path).expect("write");
688 let parsed = AbaqusReader::new().parse(path).expect("parse");
689 assert_eq!(parsed.elements[0].element_type, ElementType::C3D4);
690 }
691
692 #[test]
693 fn test_roundtrip_element_nodes() {
694 let path = "/tmp/oxiphysics_abaqus_rt_enodes.inp";
695 let mesh = sample_mesh();
696 AbaqusWriter::new().write(&mesh, path).expect("write");
697 let parsed = AbaqusReader::new().parse(path).expect("parse");
698 assert_eq!(parsed.elements[0].node_ids, vec![1, 2, 3, 4]);
699 }
700
701 #[test]
702 fn test_roundtrip_element_id() {
703 let path = "/tmp/oxiphysics_abaqus_rt_eid.inp";
704 let mesh = sample_mesh();
705 AbaqusWriter::new().write(&mesh, path).expect("write");
706 let parsed = AbaqusReader::new().parse(path).expect("parse");
707 assert_eq!(parsed.elements[0].id, 1);
708 }
709
710 #[test]
711 fn test_multiple_element_types() {
712 let path = "/tmp/oxiphysics_abaqus_multtype.inp";
713 let mut mesh = AbaqusMesh::new();
714 mesh.nodes = vec![
715 AbaqusNode::new(1, [0.0, 0.0, 0.0]),
716 AbaqusNode::new(2, [1.0, 0.0, 0.0]),
717 AbaqusNode::new(3, [0.0, 1.0, 0.0]),
718 AbaqusNode::new(4, [0.0, 0.0, 1.0]),
719 AbaqusNode::new(5, [1.0, 1.0, 0.0]),
720 ];
721 mesh.elements = vec![
722 AbaqusElement::new(1, ElementType::C3D4, vec![1, 2, 3, 4]),
723 AbaqusElement::new(2, ElementType::T3D2, vec![4, 5]),
724 ];
725 AbaqusWriter::new().write(&mesh, path).expect("write");
726 let parsed = AbaqusReader::new().parse(path).expect("parse");
727 assert_eq!(parsed.elements.len(), 2);
728 }
729
730 #[test]
731 fn test_write_contains_heading() {
732 let path = "/tmp/oxiphysics_abaqus_heading.inp";
733 let mesh = sample_mesh();
734 AbaqusWriter::new().write(&mesh, path).expect("write");
735 let content = std::fs::read_to_string(path).unwrap();
736 assert!(content.contains("*Heading"), "no *Heading found");
737 }
738
739 #[test]
740 fn test_write_contains_node_keyword() {
741 let path = "/tmp/oxiphysics_abaqus_nkw.inp";
742 let mesh = sample_mesh();
743 AbaqusWriter::new().write(&mesh, path).expect("write");
744 let content = std::fs::read_to_string(path).unwrap();
745 assert!(content.contains("*Node"), "no *Node found");
746 }
747
748 #[test]
749 fn test_write_contains_element_keyword() {
750 let path = "/tmp/oxiphysics_abaqus_ekw.inp";
751 let mesh = sample_mesh();
752 AbaqusWriter::new().write(&mesh, path).expect("write");
753 let content = std::fs::read_to_string(path).unwrap();
754 assert!(content.contains("*Element"), "no *Element found");
755 }
756
757 #[test]
758 fn test_write_material() {
759 let path = "/tmp/oxiphysics_abaqus_mat.inp";
760 let mut mesh = sample_mesh();
761 mesh.materials
762 .push(AbaqusMaterial::new_elastic("Steel", 210e9, 0.3, 7800.0));
763 AbaqusWriter::new().write(&mesh, path).expect("write");
764 let content = std::fs::read_to_string(path).unwrap();
765 assert!(content.contains("*Material"), "no *Material found");
766 assert!(content.contains("Steel"));
767 }
768
769 #[test]
770 fn test_write_plastic_material() {
771 let path = "/tmp/oxiphysics_abaqus_plastic.inp";
772 let mut mesh = sample_mesh();
773 mesh.materials.push(AbaqusMaterial::new_plastic(
774 "Steel", 210e9, 0.3, 7800.0, 250e6, 1e9,
775 ));
776 AbaqusWriter::new().write(&mesh, path).expect("write");
777 let content = std::fs::read_to_string(path).unwrap();
778 assert!(content.contains("*Plastic"), "no *Plastic found");
779 }
780
781 #[test]
782 fn test_write_section() {
783 let path = "/tmp/oxiphysics_abaqus_sec.inp";
784 let mut mesh = sample_mesh();
785 mesh.sections
786 .push(AbaqusSection::new("SEC1", "Steel", vec![1]));
787 AbaqusWriter::new().write(&mesh, path).expect("write");
788 let content = std::fs::read_to_string(path).unwrap();
789 assert!(content.contains("*Solid Section"), "no *Solid Section");
790 }
791
792 #[test]
793 fn test_write_bc_encastre() {
794 let path = "/tmp/oxiphysics_abaqus_bc_enc.inp";
795 let mut mesh = sample_mesh();
796 mesh.boundary_conditions.push(BoundaryCondition::Encastre {
797 node_set: "FIXED".to_string(),
798 });
799 AbaqusWriter::new().write(&mesh, path).expect("write");
800 let content = std::fs::read_to_string(path).unwrap();
801 assert!(content.contains("ENCASTRE"), "no ENCASTRE");
802 }
803
804 #[test]
805 fn test_write_bc_pinned() {
806 let path = "/tmp/oxiphysics_abaqus_bc_pin.inp";
807 let mut mesh = sample_mesh();
808 mesh.boundary_conditions.push(BoundaryCondition::Pinned {
809 node_set: "PINSET".to_string(),
810 });
811 AbaqusWriter::new().write(&mesh, path).expect("write");
812 let content = std::fs::read_to_string(path).unwrap();
813 assert!(content.contains("PINNED"), "no PINNED");
814 }
815
816 #[test]
817 fn test_write_bc_symmetry() {
818 let path = "/tmp/oxiphysics_abaqus_bc_sym.inp";
819 let mut mesh = sample_mesh();
820 mesh.boundary_conditions
821 .push(BoundaryCondition::SymmetryPlane {
822 node_set: "SYMSET".to_string(),
823 axis: 2,
824 });
825 AbaqusWriter::new().write(&mesh, path).expect("write");
826 let content = std::fs::read_to_string(path).unwrap();
827 assert!(content.contains("YSYMM"), "no YSYMM");
828 }
829
830 #[test]
831 fn test_parse_empty_file() {
832 let path = "/tmp/oxiphysics_abaqus_empty.inp";
833 std::fs::write(path, "** empty\n").unwrap();
834 let parsed = AbaqusReader::new().parse(path).expect("parse");
835 assert!(parsed.nodes.is_empty());
836 assert!(parsed.elements.is_empty());
837 }
838
839 #[test]
840 fn test_parse_missing_file() {
841 let result = AbaqusReader::new().parse("/tmp/does_not_exist_oxiphysics.inp");
842 assert!(result.is_err());
843 }
844
845 #[test]
846 fn test_abaqus_mesh_default() {
847 let m = AbaqusMesh::default();
848 assert!(m.nodes.is_empty());
849 assert!(m.elements.is_empty());
850 }
851
852 #[test]
853 fn test_section_new() {
854 let s = AbaqusSection::new("S1", "Mat1", vec![1, 2, 3]);
855 assert_eq!(s.name, "S1");
856 assert_eq!(s.elements, vec![1, 2, 3]);
857 }
858
859 #[test]
860 fn test_abaqus_element_new() {
861 let e = AbaqusElement::new(5, ElementType::S4, vec![10, 11, 12, 13]);
862 assert_eq!(e.id, 5);
863 assert_eq!(e.element_type, ElementType::S4);
864 assert_eq!(e.node_ids.len(), 4);
865 }
866
867 #[test]
868 fn test_large_mesh_roundtrip() {
869 let path = "/tmp/oxiphysics_abaqus_large.inp";
870 let mut mesh = AbaqusMesh::new();
871 for i in 1..=100 {
873 mesh.nodes.push(AbaqusNode::new(i, [i as f64, 0.0, 0.0]));
874 }
875 for i in 0..24usize {
877 mesh.elements.push(AbaqusElement::new(
878 i + 1,
879 ElementType::C3D4,
880 vec![
881 i * 4 % 97 + 1,
882 i * 4 % 97 + 2,
883 i * 4 % 97 + 3,
884 i * 4 % 97 + 4,
885 ],
886 ));
887 }
888 AbaqusWriter::new().write(&mesh, path).expect("write");
889 let parsed = AbaqusReader::new().parse(path).expect("parse");
890 assert_eq!(parsed.nodes.len(), 100);
891 assert_eq!(parsed.elements.len(), 24);
892 }
893
894 #[test]
895 fn test_node_roundtrip_precision() {
896 let path = "/tmp/oxiphysics_abaqus_prec.inp";
897 let mut mesh = AbaqusMesh::new();
898 mesh.nodes.push(AbaqusNode::new(
899 1,
900 [1.23456789012345, -9.87654321098765, 2.89793238462643],
901 ));
902 AbaqusWriter::new().write(&mesh, path).expect("write");
903 let parsed = AbaqusReader::new().parse(path).expect("parse");
904 let c = parsed.nodes[0].coordinates;
905 assert!((c[0] - 1.23456789012345).abs() < 1e-10);
906 assert!((c[1] - (-9.87654321098765)).abs() < 1e-10);
907 assert!((c[2] - 2.89793238462643).abs() < 1e-10);
908 }
909}