use std::{borrow::Cow, sync::Arc};
use stem_material::prelude::*;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "serde")]
use serde_mosaic::serialize_arc_link;
use compare_variables::compare_variables;
use planar_geo::prelude::*;
use super::magnet::Magnet;
#[doc = ""]
#[cfg_attr(
feature = "doc-images",
doc = "![Block magnet vs. bread loaf magnet excitation][bread_loaf_magnet_excitation]"
)]
#[cfg_attr(
feature = "doc-images",
embed_doc_image::embed_doc_image(
"bread_loaf_magnet_excitation",
"docs/img/bread_loaf_magnet_excitation.svg"
)
)]
#[cfg_attr(
not(feature = "doc-images"),
doc = "**Doc images not enabled**. Compile docs with
`cargo doc --features 'doc-images'` and Rust version >= 1.54."
)]
#[doc = ""]
#[cfg_attr(
feature = "doc-images",
doc = "![Breadloaf magnet definitions][drawing_breadloaf_magnet]"
)]
#[cfg_attr(
feature = "doc-images",
embed_doc_image::embed_doc_image(
"drawing_breadloaf_magnet",
"docs/img/drawing_breadloaf_magnet.svg"
)
)]
#[cfg_attr(
not(feature = "doc-images"),
doc = "**Doc images not enabled**. Compile docs with
`cargo doc --features 'doc-images'` and Rust version >= 1.54."
)]
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize))]
#[cfg_attr(feature = "serde", serde(deny_unknown_fields))]
pub struct BreadLoafMagnet {
#[cfg_attr(feature = "serde", serde(serialize_with = "serialize_quantity"))]
length: Length,
#[cfg_attr(feature = "serde", serde(serialize_with = "serialize_quantity"))]
width: Length,
#[cfg_attr(feature = "serde", serde(serialize_with = "serialize_quantity"))]
side_thickness: Length,
#[cfg_attr(feature = "serde", serde(serialize_with = "serialize_quantity"))]
radius: Length,
#[cfg_attr(feature = "serde", serde(serialize_with = "serialize_arc_link",))]
material: Arc<Material>,
#[cfg_attr(feature = "serde", serde(skip))]
shape: Shape,
#[cfg_attr(feature = "serde", serde(skip))]
north_shape: Shape,
#[cfg_attr(feature = "serde", serde(skip))]
south_shape: Shape,
}
impl BreadLoafMagnet {
pub fn new(
length: Length,
width: Length,
side_thickness: Length,
radius: Length,
material: Arc<Material>,
) -> Result<Self, crate::error::Error> {
use uom::typenum::P2;
let halfed_magnet_width = width / 2.0;
let abs_radius = radius.abs();
compare_variables!(abs_radius >= halfed_magnet_width)?;
let offset =
radius.abs() - 0.5 * (4.0 * radius.powi(P2::new()) - width.powi(P2::new())).sqrt();
let center_thickness = if radius.is_sign_positive() {
side_thickness + offset
} else {
side_thickness - offset
};
return Self::new_priv(
length,
width,
side_thickness,
center_thickness,
Some(radius),
material,
);
}
pub fn with_center_thickness(
length: Length,
width: Length,
side_thickness: Length,
center_thickness: Length,
material: Arc<Material>,
) -> Result<Self, crate::error::Error> {
return Self::new_priv(
length,
width,
side_thickness,
center_thickness,
None,
material,
);
}
fn new_priv(
length: Length,
width: Length,
side_thickness: Length,
center_thickness: Length,
radius: Option<Length>,
material: Arc<Material>,
) -> Result<Self, crate::error::Error> {
let zero = Length::new::<meter>(0.0);
compare_variables!(val zero < length)?;
compare_variables!(val zero < width)?;
compare_variables!(val zero <= side_thickness)?;
compare_variables!(val zero <= center_thickness)?;
let e = DEFAULT_EPSILON;
let m = DEFAULT_MAX_ULPS;
let mut polysegment = Polysegment::with_capacity(5);
let line = LineSegment::new([0.0, 0.0], [0.5 * width.get::<meter>(), 0.0], e, m)?;
polysegment.push_back(line.into());
polysegment.extend_back([0.5 * width.get::<meter>(), side_thickness.get::<meter>()]);
let arc = ArcSegment::from_start_middle_stop(
[0.5 * width.get::<meter>(), side_thickness.get::<meter>()],
[0.0, center_thickness.get::<meter>()],
[-0.5 * width.get::<meter>(), side_thickness.get::<meter>()],
e,
m,
)?;
let radius = radius.unwrap_or_else(|| {
let r = Length::new::<meter>(arc.radius());
if center_thickness >= side_thickness {
r
} else {
-r
}
});
polysegment.push_back(arc.into());
polysegment.extend_back([-0.5 * width.get::<meter>(), side_thickness.get::<meter>()]);
polysegment.extend_back([-0.5 * width.get::<meter>(), 0.0]);
let contour = Contour::new(polysegment);
let shape = Shape::from_outer(contour)?;
let c = shape.centroid()[1];
let cut =
Polysegment::from_points(&[[-width.get::<meter>(), c], [width.get::<meter>(), c]]);
let mut chains = shape.contour().intersection_cut(&cut, e, m);
let number_north_south_chains = chains.len();
compare_variables!(number_north_south_chains == 2)?;
let south_shape = Shape::from_outer(chains.pop().expect("has two elements").into())?;
let north_shape = Shape::from_outer(chains.pop().expect("has two elements").into())?;
return Ok(BreadLoafMagnet {
length,
width,
side_thickness,
radius,
material,
shape,
north_shape,
south_shape,
});
}
pub fn side_thickness(&self) -> Length {
return self.side_thickness;
}
pub fn center_thickness(&self) -> Length {
return self.side_thickness() + self.arc_segment_height();
}
pub fn radius(&self) -> Length {
return self.radius;
}
pub fn arc_segment_height(&self) -> Length {
use uom::typenum::P2;
let val = self.radius().abs()
- 0.5 * (4.0 * self.radius().powi(P2::new()) - self.width().powi(P2::new())).sqrt();
if self.radius().is_sign_positive() {
val
} else {
-val
}
}
}
#[cfg_attr(feature = "serde", typetag::serde)]
impl Magnet for BreadLoafMagnet {
fn width(&self) -> Length {
return self.width;
}
fn length(&self) -> Length {
return self.length;
}
fn thickness(&self) -> Length {
return self.side_thickness() + 0.5 * self.arc_segment_height();
}
fn material(&self) -> &Material {
return &*self.material;
}
fn material_arc(&self) -> Arc<Material> {
return self.material.clone();
}
fn shape(&self) -> Cow<'_, Shape> {
return Cow::Borrowed(&self.shape);
}
fn north_south_shapes(&self) -> [Cow<'_, Shape>; 2] {
return [
Cow::Borrowed(&self.north_shape),
Cow::Borrowed(&self.south_shape),
];
}
fn area(&self) -> Area {
use uom::typenum::P2;
let rect_area = self.side_thickness() * self.width();
let r = self.radius().abs();
let h = self.arc_segment_height().abs();
let arc_seg_area = r.powi(P2::new()) * (1.0 - (h / r).get::<ratio>()).acos()
- (r - h) * (h * (2.0 * r - h)).sqrt();
if self.radius().is_sign_positive() {
rect_area + arc_seg_area
} else {
rect_area - arc_seg_area
}
}
}
#[cfg(feature = "serde")]
impl<'de> Deserialize<'de> for BreadLoafMagnet {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde_mosaic::deserialize_arc_link;
use stem_material::prelude::deserialize_quantity;
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct WithRadius {
#[serde(deserialize_with = "deserialize_quantity")]
length: Length,
#[serde(deserialize_with = "deserialize_quantity")]
width: Length,
#[serde(deserialize_with = "deserialize_quantity")]
side_thickness: Length,
#[serde(deserialize_with = "deserialize_quantity")]
radius: Length,
#[serde(deserialize_with = "deserialize_arc_link")]
material: Arc<Material>,
}
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct WithCenterThickness {
#[serde(deserialize_with = "deserialize_quantity")]
length: Length,
#[serde(deserialize_with = "deserialize_quantity")]
width: Length,
#[serde(deserialize_with = "deserialize_quantity")]
side_thickness: Length,
#[serde(deserialize_with = "deserialize_quantity")]
center_thickness: Length,
#[serde(deserialize_with = "deserialize_arc_link")]
material: Arc<Material>,
}
#[derive(deserialize_untagged_verbose_error::DeserializeUntaggedVerboseError)]
enum MagnetEnum {
WithRadius(WithRadius),
WithCenterThickness(WithCenterThickness),
}
let m = MagnetEnum::deserialize(deserializer)?;
match m {
MagnetEnum::WithRadius(m) => {
return Self::new(m.length, m.width, m.side_thickness, m.radius, m.material)
.map_err(serde::de::Error::custom);
}
MagnetEnum::WithCenterThickness(m) => {
return Self::with_center_thickness(
m.length,
m.width,
m.side_thickness,
m.center_thickness,
m.material,
)
.map_err(serde::de::Error::custom);
}
}
}
}