1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
/*
* Magba is licensed under The 3-Clause BSD, see LICENSE.
* Copyright 2025 Sira Pornsiriprasert <code@psira.me>
*/
use nalgebra::Vector3;
#[cfg(feature = "mesh")]
use openmesh::MeshError;
use crate::base::Float;
use crate::base::mesh::TriMesh;
use crate::crate_utils::define_source;
use alloc::vec::Vec;
define_source! {
/// Triangular mesh with homogeneous magnetic surface charge.
MeshMagnet
field_fn: mesh_B
args: {
polarization: Vector3<T> = Vector3::z(),
mesh: @ref TriMesh<T> = TriMesh::new_unchecked(Vec::new(), Vec::new()),
}
arg_display: "pol={}, triangles count: {}";
arg_fmt: [format_vector3, format_trimesh_count]
docs: {
new: {
/// Construct a [MeshMagnet].
///
/// This constructor does not validate the mesh. The field computation guarantees
/// correct results only if the mesh is closed, connected, not self-intersecting
/// and all faces are oriented outwards. It is recommended to validate the mesh
/// first or use [MeshMagnet::from_vertices_and_faces] constructor that validates the mesh.
///
/// # Examples
///
/// ```
/// # use magba::magnets::MeshMagnet;
/// # use magba::base::mesh::TriMesh;
/// # use nalgebra::{UnitQuaternion, vector, Vector3};
/// let vertices = vec![vector![-0.1, -0.1, -0.1], vector![0.1, -0.1, -0.1], vector![0.0, 0.1, -0.1], vector![0.0, 0.0, 0.1]];
/// let faces = vec![[0, 2, 1], [0, 1, 3], [1, 2, 3], [0, 3, 2]];
/// let mesh = TriMesh::new(vertices, faces)
/// .expect("Invalid mesh: The mesh has holes or zero-faces.");
///
/// let magnet = MeshMagnet::new(
/// [0.0, 0.0, 0.0], // position (m)
/// UnitQuaternion::identity(), // orientation as unit quaternion
/// [0.0, 0.0, 1.0], // polarization (T)
/// mesh,
/// );
/// ```
}
}
}
impl<T: Float + core::iter::Sum> MeshMagnet<T> {
/// Validates and constructs a [`MeshMagnet`] from a [TriMesh].
///
/// # Returns
///
/// Returns a [MeshError] if the mesh has holes or zero-faces.
///
/// # Notes
///
/// To construct a [MeshMagnet] without validation, use [MeshMagnet::new].
#[cfg(feature = "mesh")]
#[inline]
pub fn from_vertices_and_faces(
vertices: Vec<Vector3<T>>,
faces: Vec<[usize; 3]>,
polarization: impl Into<Vector3<T>>,
) -> Result<Self, MeshError> {
let trimesh = TriMesh::new(vertices, faces)?;
Ok(Self::new(
[T::zero(); 3],
nalgebra::UnitQuaternion::identity(),
polarization,
trimesh,
))
}
/// Validates and constructs a [`MeshMagnet`] from an STL reader.
#[cfg(feature = "io-stl")]
#[inline]
pub fn from_stl<R>(reader: &mut R, polarization: impl Into<Vector3<T>>) -> std::io::Result<Self>
where
R: std::io::Read + std::io::Seek,
{
let trimesh = TriMesh::from_stl(reader)?;
Ok(Self::new(
[T::zero(); 3],
nalgebra::UnitQuaternion::identity(),
polarization,
trimesh,
))
}
}
#[cfg(all(test, feature = "std"))]
crate::testing_util::generate_tests! {
MeshMagnet
filename: triangularmesh
params: {
polarization: vector![1.0, 2.0, 3.0],
mesh: TriMesh::new_unchecked(
vec![vector![-0.1, -0.1, -0.1], vector![0.1, -0.1, -0.1], vector![0.0, 0.1, -0.1], vector![0.0, 0.0, 0.1]],
vec![[0, 2, 1], [0, 1, 3], [1, 2, 3], [0, 3, 2]],
),
}
rtols: {
static: 2e-10,
static_small: 1e-9,
translate: 1e-9,
rotate: 1e-10,
}
p95_rtols: {
static: 2e-10,
static_small: 1e-9,
translate: 1e-9,
rotate: 1e-10,
}
f32_rtols: {
static: 5e-2,
static_small: 0.2,
translate: 5e-2,
rotate: 1e-2,
}
f32_p95_rtols: {
static: 1e-3,
static_small: 1e-3,
translate: 1e-3,
rotate: 1e-3,
}
}
#[cfg(all(test, feature = "io-stl"))]
mod stl_tests {
use super::*;
use crate::testing_util::*;
#[test]
fn test_suzanne() {
let mut file =
std::fs::File::open("./tests/test-data/suzanne.stl").expect("Cannot open suzanne.stl");
let mesh: MeshMagnet<f64> =
MeshMagnet::from_stl(&mut file, [0.0, 0.0, 1.0]).expect("Failed to read STL");
compare_B_with_file(
&mesh,
"./tests/test-data/points.csv",
"./tests/test-data/suzanne-stl.csv",
1e-11,
1e-11,
);
}
}