Skip to main content

runmat_meshing_core/contracts/artifact/
mesh.rs

1use serde::{Deserialize, Serialize};
2
3use runmat_meshing_size::adaptive::AdaptiveIterationSummary;
4use runmat_meshing_size::field::MeshSizingField;
5
6use crate::quality::AnalysisMeshQualityReport;
7
8use super::MeshBackendSummary;
9use crate::contracts::{
10    provenance::{AnalysisMeshProvenance, MeshEntityProvenance},
11    topology::{BoundaryElementKind, VolumeElementKind},
12};
13
14pub const ANALYSIS_MESH_SCHEMA_VERSION: &str = "analysis-mesh/v1";
15
16pub const ANALYSIS_MESH_FIELD_TOPOLOGY_ID: &str = "analysis_mesh";
17pub const ANALYSIS_MESH_BOUNDARY_FACE_TOPOLOGY_ID: &str = "analysis_mesh_boundary_faces";
18pub const ANALYSIS_MESH_BOUNDARY_EDGE_TOPOLOGY_ID: &str = "analysis_mesh_boundary_edges";
19pub const TETRAHEDRON4_FIELD_ELEMENT_KIND: &str = "tetrahedron4";
20pub const TRI3_FIELD_ELEMENT_KIND: &str = "tri3";
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
23#[serde(rename_all = "snake_case")]
24pub enum AnalysisFieldTopologyLocation {
25    Node,
26    VolumeElement,
27    BoundaryFace,
28    BoundaryEdge,
29}
30
31#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
32pub struct AnalysisFieldTopologyDescriptor {
33    pub topology_id: String,
34    pub location: AnalysisFieldTopologyLocation,
35    pub entity_count: usize,
36    #[serde(default)]
37    pub element_kind: Option<String>,
38}
39
40#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
41pub struct AnalysisMeshNode {
42    pub node_id: u32,
43    pub coordinates_m: [f64; 3],
44    #[serde(default)]
45    pub provenance: Vec<MeshEntityProvenance>,
46}
47
48#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
49pub struct AnalysisVolumeElement {
50    pub element_id: String,
51    pub kind: VolumeElementKind,
52    pub node_ids: Vec<u32>,
53    pub material_region_id: String,
54    #[serde(default)]
55    pub provenance: Vec<MeshEntityProvenance>,
56}
57
58#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
59pub struct AnalysisBoundaryFace {
60    pub face_id: String,
61    pub kind: BoundaryElementKind,
62    pub node_ids: Vec<u32>,
63    #[serde(default)]
64    pub adjacent_volume_element_ids: Vec<String>,
65    #[serde(default)]
66    pub region_ids: Vec<String>,
67    #[serde(default)]
68    pub provenance: Vec<MeshEntityProvenance>,
69}
70
71#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
72pub struct AnalysisBoundaryEdge {
73    pub edge_id: String,
74    pub node_ids: [u32; 2],
75    #[serde(default)]
76    pub adjacent_boundary_face_ids: Vec<String>,
77    #[serde(default)]
78    pub region_ids: Vec<String>,
79    #[serde(default)]
80    pub provenance: Vec<MeshEntityProvenance>,
81}
82
83#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
84pub struct AnalysisMeshArtifact {
85    pub schema_version: String,
86    pub mesh_id: String,
87    pub nodes: Vec<AnalysisMeshNode>,
88    pub volume_elements: Vec<AnalysisVolumeElement>,
89    #[serde(default)]
90    pub boundary_faces: Vec<AnalysisBoundaryFace>,
91    #[serde(default)]
92    pub boundary_edges: Vec<AnalysisBoundaryEdge>,
93    pub quality: AnalysisMeshQualityReport,
94    pub sizing: MeshSizingField,
95    #[serde(default)]
96    pub field_topology: Vec<AnalysisFieldTopologyDescriptor>,
97    #[serde(default)]
98    pub backend: MeshBackendSummary,
99    #[serde(default)]
100    pub adaptive_iterations: Vec<AdaptiveIterationSummary>,
101    pub provenance: AnalysisMeshProvenance,
102}
103
104impl AnalysisMeshArtifact {
105    pub fn refresh_field_topology(&mut self) {
106        self.field_topology = analysis_mesh_field_topology(
107            &self.nodes,
108            &self.volume_elements,
109            &self.boundary_faces,
110            &self.boundary_edges,
111        );
112    }
113}
114
115pub fn analysis_mesh_field_topology(
116    nodes: &[AnalysisMeshNode],
117    volume_elements: &[AnalysisVolumeElement],
118    boundary_faces: &[AnalysisBoundaryFace],
119    boundary_edges: &[AnalysisBoundaryEdge],
120) -> Vec<AnalysisFieldTopologyDescriptor> {
121    let mut descriptors = vec![AnalysisFieldTopologyDescriptor {
122        topology_id: ANALYSIS_MESH_FIELD_TOPOLOGY_ID.to_string(),
123        location: AnalysisFieldTopologyLocation::Node,
124        entity_count: nodes.len(),
125        element_kind: None,
126    }];
127
128    descriptors.push(AnalysisFieldTopologyDescriptor {
129        topology_id: ANALYSIS_MESH_FIELD_TOPOLOGY_ID.to_string(),
130        location: AnalysisFieldTopologyLocation::VolumeElement,
131        entity_count: volume_elements.len(),
132        element_kind: uniform_volume_element_kind(volume_elements),
133    });
134
135    descriptors.push(AnalysisFieldTopologyDescriptor {
136        topology_id: ANALYSIS_MESH_BOUNDARY_FACE_TOPOLOGY_ID.to_string(),
137        location: AnalysisFieldTopologyLocation::BoundaryFace,
138        entity_count: boundary_faces.len(),
139        element_kind: uniform_boundary_face_element_kind(boundary_faces),
140    });
141
142    descriptors.push(AnalysisFieldTopologyDescriptor {
143        topology_id: ANALYSIS_MESH_BOUNDARY_EDGE_TOPOLOGY_ID.to_string(),
144        location: AnalysisFieldTopologyLocation::BoundaryEdge,
145        entity_count: boundary_edges.len(),
146        element_kind: None,
147    });
148
149    descriptors
150}
151
152fn uniform_volume_element_kind(elements: &[AnalysisVolumeElement]) -> Option<String> {
153    let first = elements.first()?.kind;
154    if elements.iter().all(|element| element.kind == first) {
155        match first {
156            VolumeElementKind::Tetrahedron4 => Some(TETRAHEDRON4_FIELD_ELEMENT_KIND.to_string()),
157            _ => None,
158        }
159    } else {
160        None
161    }
162}
163
164fn uniform_boundary_face_element_kind(faces: &[AnalysisBoundaryFace]) -> Option<String> {
165    let first = faces.first()?.kind;
166    if faces.iter().all(|face| face.kind == first) {
167        match first {
168            BoundaryElementKind::Tri3 => Some(TRI3_FIELD_ELEMENT_KIND.to_string()),
169            _ => None,
170        }
171    } else {
172        None
173    }
174}