#![warn(clippy::all)]
#![warn(missing_docs)]
#![warn(clippy::missing_docs_in_private_items)]
#![doc = include_str!("../README.md")]
use num::{Float, NumCast};
use typenum::{P4, Z0};
use uom::si::{
f64::{Area, Length, Volume},
length::meter,
{Quantity, ISQ, SI},
};
type SecondAreaMomentofInertia = Quantity<ISQ<P4, Z0, Z0, Z0, Z0, Z0, Z0>, SI<f64>, f64>;
pub fn meters<T: Float>(l: T) -> Length {
Length::new::<meter>(NumCast::from(l).expect("The input must be castable to a float."))
}
#[derive(Clone, Copy, Debug)]
#[non_exhaustive]
pub enum StructuralShape {
Pipe {
outer_radius: Length,
thickness: Length,
center_of_gravity: (Length, Length),
},
IBeam {
width: Length,
height: Length,
web_thickness: Length,
flange_thickness: Length,
center_of_gravity: (Length, Length),
},
BoxBeam {
width: Length,
height: Length,
thickness: Length,
center_of_gravity: (Length, Length),
},
Rod {
radius: Length,
center_of_gravity: (Length, Length),
},
Rectangle {
width: Length,
height: Length,
center_of_gravity: (Length, Length),
},
}
impl StructuralShape {
pub fn new_rod(radius: f64) -> StructuralShape {
StructuralShape::Rod {
radius: meters(radius),
center_of_gravity: (meters(0.0), meters(0.0)),
}
}
pub fn new_pipe(radius: f64, thickness: f64) -> StructuralShape {
StructuralShape::Pipe {
outer_radius: meters(radius),
thickness: meters(thickness),
center_of_gravity: (meters(0.0), meters(0.0)),
}
}
pub fn new_rectangle(height: f64, width: f64) -> StructuralShape {
StructuralShape::Rectangle {
width: meters(width),
height: meters(height),
center_of_gravity: (meters(0.0), meters(0.0)),
}
}
pub fn new_boxbeam(height: f64, width: f64, thickness: f64) -> StructuralShape {
StructuralShape::BoxBeam {
width: meters(width),
height: meters(height),
thickness: meters(thickness),
center_of_gravity: (meters(0.0), meters(0.0)),
}
}
pub fn new_ibeam(
height: f64,
width: f64,
web_thickness: f64,
flange_thickness: f64,
) -> StructuralShape {
StructuralShape::IBeam {
width: meters(width),
height: meters(height),
web_thickness: meters(web_thickness),
center_of_gravity: (meters(0.0), meters(0.0)),
flange_thickness: meters(flange_thickness),
}
}
pub fn moi_x(&self) -> SecondAreaMomentofInertia {
match *self {
StructuralShape::Pipe {
outer_radius,
thickness,
center_of_gravity,
} => CompositeShape::new()
.add(StructuralShape::Rod {
radius: outer_radius,
center_of_gravity,
})
.sub(StructuralShape::Rod {
radius: (outer_radius - thickness),
center_of_gravity,
})
.moi_x(),
StructuralShape::IBeam {
width,
height,
flange_thickness,
web_thickness,
center_of_gravity,
} => composite_ibeam(
width,
height,
web_thickness,
flange_thickness,
center_of_gravity,
)
.moi_y(),
StructuralShape::BoxBeam {
width,
height,
thickness,
center_of_gravity,
} => CompositeShape::new()
.add(StructuralShape::Rectangle {
width,
height,
center_of_gravity,
})
.sub(StructuralShape::Rectangle {
width: (width - 2.0 * thickness),
height: (height - 2.0 * thickness),
center_of_gravity,
})
.moi_x(),
StructuralShape::Rod {
radius,
center_of_gravity,
} => {
std::f64::consts::PI * radius * radius * radius * radius / 4.0
+ self.area() * center_of_gravity.0 * center_of_gravity.0
}
StructuralShape::Rectangle {
width,
height,
center_of_gravity,
} => {
width * height * height * height / 12.0
+ self.area() * center_of_gravity.0 * center_of_gravity.0
}
}
.into()
}
pub fn moi_y(&self) -> SecondAreaMomentofInertia {
match *self {
StructuralShape::Pipe {
outer_radius,
thickness,
center_of_gravity,
} => StructuralShape::Pipe {
outer_radius,
thickness,
center_of_gravity: swap(center_of_gravity),
}
.moi_x(),
StructuralShape::IBeam {
height,
width,
flange_thickness,
web_thickness,
center_of_gravity,
} => composite_ibeam(
width,
height,
web_thickness,
flange_thickness,
center_of_gravity,
)
.moi_y(),
StructuralShape::BoxBeam {
width,
height,
thickness,
center_of_gravity,
} => StructuralShape::BoxBeam {
width: height,
height: width,
thickness,
center_of_gravity: swap(center_of_gravity),
}
.moi_x(),
StructuralShape::Rod {
radius,
center_of_gravity,
} => {
std::f64::consts::PI * radius * radius * radius * radius / 4.0
+ self.area() * center_of_gravity.1 * center_of_gravity.1
}
StructuralShape::Rectangle {
width,
height,
center_of_gravity,
} => {
width * height * height * height / 12.0
+ self.area() * center_of_gravity.1 * center_of_gravity.1
}
}
}
pub fn polar_moi(&self) -> SecondAreaMomentofInertia {
self.moi_x() + self.moi_y()
}
pub fn area(&self) -> Area {
match *self {
StructuralShape::Pipe {
outer_radius,
thickness,
..
} => {
std::f64::consts::PI
* (outer_radius * outer_radius
- (outer_radius - thickness) * (outer_radius - thickness))
}
StructuralShape::IBeam {
width,
height,
web_thickness,
flange_thickness,
..
} => width * height - (height - 2.0 * flange_thickness) * (width - web_thickness),
StructuralShape::BoxBeam {
width,
height,
thickness,
..
} => width * height - (width - 2.0 * thickness) * (height - 2.0 * thickness),
StructuralShape::Rod { radius, .. } => std::f64::consts::PI * radius * radius,
StructuralShape::Rectangle { width, height, .. } => width * height,
}
}
pub fn with_cog(&mut self, x: f64, y: f64) -> StructuralShape {
self.set_cog((meters(x), meters(y)));
self.clone()
}
pub(crate) fn get_cog(&self) -> (Length, Length) {
match *self {
StructuralShape::Pipe {
center_of_gravity, ..
} => center_of_gravity,
StructuralShape::IBeam {
center_of_gravity, ..
} => center_of_gravity,
StructuralShape::BoxBeam {
center_of_gravity, ..
} => center_of_gravity,
StructuralShape::Rod {
center_of_gravity, ..
} => center_of_gravity,
StructuralShape::Rectangle {
center_of_gravity, ..
} => center_of_gravity,
}
}
pub(crate) fn set_cog(&mut self, cog: (Length, Length)) {
match *self {
StructuralShape::Pipe {
ref mut center_of_gravity,
..
} => {
*center_of_gravity = cog;
}
StructuralShape::IBeam {
ref mut center_of_gravity,
..
} => {
*center_of_gravity = cog;
}
StructuralShape::BoxBeam {
ref mut center_of_gravity,
..
} => {
*center_of_gravity = cog;
}
StructuralShape::Rod {
ref mut center_of_gravity,
..
} => {
*center_of_gravity = cog;
}
StructuralShape::Rectangle {
ref mut center_of_gravity,
..
} => {
*center_of_gravity = cog;
}
};
}
}
#[derive(Clone, Debug)]
pub struct CompositeShape {
pub shapes: Vec<(i8, StructuralShape)>,
}
impl CompositeShape {
pub fn new() -> Self {
Self::default()
}
pub fn add(&mut self, new_shape: StructuralShape) -> Self {
self.shapes.push((1, new_shape));
self.clone()
}
pub fn sub(&mut self, new_shape: StructuralShape) -> Self {
self.shapes.push((-1, new_shape));
self.clone()
}
pub fn calculate_cog(&self) -> (Length, Length) {
let area = self.area();
let area_times_cx: Volume = self
.shapes
.iter()
.map(|x| {
let center_of_gravity = x.1.get_cog();
(x.0 as f64) * x.1.area() * center_of_gravity.0
})
.sum();
let area_times_cy: Volume = self
.shapes
.iter()
.map(|x| {
let center_of_gravity = x.1.get_cog();
(x.0 as f64) * x.1.area() * center_of_gravity.1
})
.sum();
let cog_x = area_times_cx / area;
let cog_y = area_times_cy / area;
(cog_x, cog_y)
}
pub fn update_cog(&mut self) {
let (cog_x, cog_y) = self.calculate_cog();
self.shapes.iter_mut().for_each(|x| {
let (_, ref mut shape) = x;
let (old_x, old_y) = shape.get_cog();
shape.set_cog((old_x - cog_x, old_y - cog_y));
});
}
pub fn moi_x(&self) -> SecondAreaMomentofInertia {
self.shapes.iter().map(|x| (x.0 as f64) * x.1.moi_x()).sum()
}
pub fn moi_y(&self) -> SecondAreaMomentofInertia {
self.shapes.iter().map(|x| (x.0 as f64) * x.1.moi_y()).sum()
}
pub fn polar_moi(&self) -> SecondAreaMomentofInertia {
self.moi_x() + self.moi_y()
}
pub fn area(&self) -> Area {
self.shapes.iter().map(|x| (x.0 as f64) * x.1.area()).sum()
}
}
impl Default for CompositeShape {
fn default() -> Self {
CompositeShape { shapes: vec![] }
}
}
fn swap(pair: (Length, Length)) -> (Length, Length) {
(pair.1, pair.0)
}
fn composite_ibeam(
width: Length,
height: Length,
web_thickness: Length,
flange_thickness: Length,
center_of_gravity: (Length, Length),
) -> CompositeShape {
CompositeShape::new()
.add(StructuralShape::Rectangle {
width,
height,
center_of_gravity,
})
.sub(StructuralShape::Rectangle {
width: ((width - web_thickness) / 2.0),
height: (height - 2.0 * flange_thickness),
center_of_gravity: (
center_of_gravity.0 - ((width - web_thickness) / 4.0) - web_thickness / 2.0,
center_of_gravity.1,
),
})
.sub(StructuralShape::Rectangle {
width: ((width - web_thickness) / 2.0),
height: (height - 2.0 * flange_thickness),
center_of_gravity: (
center_of_gravity.0 + ((width - web_thickness) / 4.0) + web_thickness / 2.0,
center_of_gravity.1,
),
})
}