runmat_meshing_core/contracts/artifact/
mesh.rs1use 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}