1#![warn(clippy::all)]
2#![warn(missing_docs)]
3#![warn(clippy::missing_docs_in_private_items)]
4#![doc = include_str!("../README.md")]
5
6use num::{Float, NumCast};
7use typenum::{P4, Z0};
8use uom::si::{
9 f64::{Area, Length, Volume},
10 length::meter,
11 {Quantity, ISQ, SI},
12};
13type SecondAreaMomentofInertia = Quantity<ISQ<P4, Z0, Z0, Z0, Z0, Z0, Z0>, SI<f64>, f64>;
14
15pub fn meters<T: Float>(l: T) -> Length {
17 Length::new::<meter>(NumCast::from(l).expect("The input must be castable to a float."))
18}
19
20#[derive(Clone, Copy, Debug)]
22#[non_exhaustive]
23pub enum StructuralShape {
24 Pipe {
26 outer_radius: Length,
28 thickness: Length,
30 center_of_gravity: (Length, Length),
32 },
33 IBeam {
35 width: Length,
37 height: Length,
39 web_thickness: Length,
41 flange_thickness: Length,
43 center_of_gravity: (Length, Length),
45 },
46 BoxBeam {
48 width: Length,
50 height: Length,
52 thickness: Length,
54 center_of_gravity: (Length, Length),
56 },
57 Rod {
59 radius: Length,
61 center_of_gravity: (Length, Length),
63 },
64 Rectangle {
66 width: Length,
68 height: Length,
70 center_of_gravity: (Length, Length),
72 },
73}
74
75impl StructuralShape {
76 pub fn new_rod(radius: f64) -> StructuralShape {
82 StructuralShape::Rod {
83 radius: meters(radius),
84 center_of_gravity: (meters(0.0), meters(0.0)),
85 }
86 }
87
88 pub fn new_pipe(radius: f64, thickness: f64) -> StructuralShape {
94 StructuralShape::Pipe {
95 outer_radius: meters(radius),
96 thickness: meters(thickness),
97 center_of_gravity: (meters(0.0), meters(0.0)),
98 }
99 }
100
101 pub fn new_rectangle(height: f64, width: f64) -> StructuralShape {
107 StructuralShape::Rectangle {
108 width: meters(width),
109 height: meters(height),
110 center_of_gravity: (meters(0.0), meters(0.0)),
111 }
112 }
113
114 pub fn new_boxbeam(height: f64, width: f64, thickness: f64) -> StructuralShape {
120 StructuralShape::BoxBeam {
121 width: meters(width),
122 height: meters(height),
123 thickness: meters(thickness),
124 center_of_gravity: (meters(0.0), meters(0.0)),
125 }
126 }
127
128 pub fn new_ibeam(
134 height: f64,
135 width: f64,
136 web_thickness: f64,
137 flange_thickness: f64,
138 ) -> StructuralShape {
139 StructuralShape::IBeam {
140 width: meters(width),
141 height: meters(height),
142 web_thickness: meters(web_thickness),
143 center_of_gravity: (meters(0.0), meters(0.0)),
144 flange_thickness: meters(flange_thickness),
145 }
146 }
147
148 pub fn moi_x(&self) -> SecondAreaMomentofInertia {
155 match *self {
156 StructuralShape::Pipe {
157 outer_radius,
158 thickness,
159 center_of_gravity,
160 } => CompositeShape::new()
161 .add(StructuralShape::Rod {
162 radius: outer_radius,
163 center_of_gravity,
164 })
165 .sub(StructuralShape::Rod {
166 radius: (outer_radius - thickness),
167 center_of_gravity,
168 })
169 .moi_x(),
170 StructuralShape::IBeam {
171 width,
172 height,
173 flange_thickness,
174 web_thickness,
175 center_of_gravity,
176 } => composite_ibeam(
177 width,
178 height,
179 web_thickness,
180 flange_thickness,
181 center_of_gravity,
182 )
183 .moi_y(),
184 StructuralShape::BoxBeam {
185 width,
186 height,
187 thickness,
188 center_of_gravity,
189 } => CompositeShape::new()
190 .add(StructuralShape::Rectangle {
191 width,
192 height,
193 center_of_gravity,
194 })
195 .sub(StructuralShape::Rectangle {
196 width: (width - 2.0 * thickness),
197 height: (height - 2.0 * thickness),
198 center_of_gravity,
199 })
200 .moi_x(),
201 StructuralShape::Rod {
202 radius,
203 center_of_gravity,
204 } => {
205 std::f64::consts::PI * radius * radius * radius * radius / 4.0
206 + self.area() * center_of_gravity.0 * center_of_gravity.0
207 }
208 StructuralShape::Rectangle {
209 width,
210 height,
211 center_of_gravity,
212 } => {
213 width * height * height * height / 12.0
214 + self.area() * center_of_gravity.0 * center_of_gravity.0
215 }
216 }
217 .into()
218 }
219
220 pub fn moi_y(&self) -> SecondAreaMomentofInertia {
227 match *self {
228 StructuralShape::Pipe {
229 outer_radius,
230 thickness,
231 center_of_gravity,
232 } => StructuralShape::Pipe {
233 outer_radius,
234 thickness,
235 center_of_gravity: swap(center_of_gravity),
236 }
237 .moi_x(),
238
239 StructuralShape::IBeam {
240 height,
241 width,
242 flange_thickness,
243 web_thickness,
244 center_of_gravity,
245 } => composite_ibeam(
246 width,
247 height,
248 web_thickness,
249 flange_thickness,
250 center_of_gravity,
251 )
252 .moi_y(),
253 StructuralShape::BoxBeam {
254 width,
255 height,
256 thickness,
257 center_of_gravity,
258 } => StructuralShape::BoxBeam {
259 width: height,
260 height: width,
261 thickness,
262 center_of_gravity: swap(center_of_gravity),
263 }
264 .moi_x(),
265 StructuralShape::Rod {
266 radius,
267 center_of_gravity,
268 } => {
269 std::f64::consts::PI * radius * radius * radius * radius / 4.0
270 + self.area() * center_of_gravity.1 * center_of_gravity.1
271 }
272 StructuralShape::Rectangle {
273 width,
274 height,
275 center_of_gravity,
276 } => {
277 width * height * height * height / 12.0
278 + self.area() * center_of_gravity.1 * center_of_gravity.1
279 }
280 }
281 }
282
283 pub fn polar_moi(&self) -> SecondAreaMomentofInertia {
290 self.moi_x() + self.moi_y()
291 }
292
293 pub fn area(&self) -> Area {
300 match *self {
301 StructuralShape::Pipe {
302 outer_radius,
303 thickness,
304 ..
305 } => {
306 std::f64::consts::PI
307 * (outer_radius * outer_radius
308 - (outer_radius - thickness) * (outer_radius - thickness))
309 }
310 StructuralShape::IBeam {
311 width,
312 height,
313 web_thickness,
314 flange_thickness,
315 ..
316 } => width * height - (height - 2.0 * flange_thickness) * (width - web_thickness),
317 StructuralShape::BoxBeam {
318 width,
319 height,
320 thickness,
321 ..
322 } => width * height - (width - 2.0 * thickness) * (height - 2.0 * thickness),
323 StructuralShape::Rod { radius, .. } => std::f64::consts::PI * radius * radius,
324 StructuralShape::Rectangle { width, height, .. } => width * height,
325 }
326 }
327
328 pub fn with_cog(&mut self, x: f64, y: f64) -> StructuralShape {
335 self.set_cog((meters(x), meters(y)));
336 self.clone()
337 }
338
339 pub(crate) fn get_cog(&self) -> (Length, Length) {
341 match *self {
342 StructuralShape::Pipe {
343 center_of_gravity, ..
344 } => center_of_gravity,
345 StructuralShape::IBeam {
346 center_of_gravity, ..
347 } => center_of_gravity,
348 StructuralShape::BoxBeam {
349 center_of_gravity, ..
350 } => center_of_gravity,
351 StructuralShape::Rod {
352 center_of_gravity, ..
353 } => center_of_gravity,
354 StructuralShape::Rectangle {
355 center_of_gravity, ..
356 } => center_of_gravity,
357 }
358 }
359
360 pub(crate) fn set_cog(&mut self, cog: (Length, Length)) {
362 match *self {
363 StructuralShape::Pipe {
364 ref mut center_of_gravity,
365 ..
366 } => {
367 *center_of_gravity = cog;
368 }
369 StructuralShape::IBeam {
370 ref mut center_of_gravity,
371 ..
372 } => {
373 *center_of_gravity = cog;
374 }
375 StructuralShape::BoxBeam {
376 ref mut center_of_gravity,
377 ..
378 } => {
379 *center_of_gravity = cog;
380 }
381 StructuralShape::Rod {
382 ref mut center_of_gravity,
383 ..
384 } => {
385 *center_of_gravity = cog;
386 }
387 StructuralShape::Rectangle {
388 ref mut center_of_gravity,
389 ..
390 } => {
391 *center_of_gravity = cog;
392 }
393 };
394 }
395}
396
397#[derive(Clone, Debug)]
405pub struct CompositeShape {
406 pub shapes: Vec<(i8, StructuralShape)>,
408}
409
410impl CompositeShape {
411 pub fn new() -> Self {
413 Self::default()
414 }
415 pub fn add(&mut self, new_shape: StructuralShape) -> Self {
417 self.shapes.push((1, new_shape));
418 self.clone()
419 }
420 pub fn sub(&mut self, new_shape: StructuralShape) -> Self {
422 self.shapes.push((-1, new_shape));
423 self.clone()
424 }
425 pub fn calculate_cog(&self) -> (Length, Length) {
427 let area = self.area();
428 let area_times_cx: Volume = self
429 .shapes
430 .iter()
431 .map(|x| {
432 let center_of_gravity = x.1.get_cog();
433 (x.0 as f64) * x.1.area() * center_of_gravity.0
434 })
435 .sum();
436 let area_times_cy: Volume = self
437 .shapes
438 .iter()
439 .map(|x| {
440 let center_of_gravity = x.1.get_cog();
441 (x.0 as f64) * x.1.area() * center_of_gravity.1
442 })
443 .sum();
444 let cog_x = area_times_cx / area;
445 let cog_y = area_times_cy / area;
446 (cog_x, cog_y)
447 }
448 pub fn update_cog(&mut self) {
450 let (cog_x, cog_y) = self.calculate_cog();
451 self.shapes.iter_mut().for_each(|x| {
452 let (_, ref mut shape) = x;
453 let (old_x, old_y) = shape.get_cog();
454 shape.set_cog((old_x - cog_x, old_y - cog_y));
455 });
456 }
457
458 pub fn moi_x(&self) -> SecondAreaMomentofInertia {
460 self.shapes.iter().map(|x| (x.0 as f64) * x.1.moi_x()).sum()
461 }
462 pub fn moi_y(&self) -> SecondAreaMomentofInertia {
464 self.shapes.iter().map(|x| (x.0 as f64) * x.1.moi_y()).sum()
465 }
466 pub fn polar_moi(&self) -> SecondAreaMomentofInertia {
468 self.moi_x() + self.moi_y()
469 }
470 pub fn area(&self) -> Area {
472 self.shapes.iter().map(|x| (x.0 as f64) * x.1.area()).sum()
473 }
474}
475
476impl Default for CompositeShape {
478 fn default() -> Self {
479 CompositeShape { shapes: vec![] }
480 }
481}
482
483fn swap(pair: (Length, Length)) -> (Length, Length) {
485 (pair.1, pair.0)
486}
487
488fn composite_ibeam(
490 width: Length,
491 height: Length,
492 web_thickness: Length,
493 flange_thickness: Length,
494 center_of_gravity: (Length, Length),
495) -> CompositeShape {
496 CompositeShape::new()
497 .add(StructuralShape::Rectangle {
498 width,
499 height,
500 center_of_gravity,
501 })
502 .sub(StructuralShape::Rectangle {
503 width: ((width - web_thickness) / 2.0),
504 height: (height - 2.0 * flange_thickness),
505 center_of_gravity: (
506 center_of_gravity.0 - ((width - web_thickness) / 4.0) - web_thickness / 2.0,
507 center_of_gravity.1,
508 ),
509 })
510 .sub(StructuralShape::Rectangle {
511 width: ((width - web_thickness) / 2.0),
512 height: (height - 2.0 * flange_thickness),
513 center_of_gravity: (
514 center_of_gravity.0 + ((width - web_thickness) / 4.0) + web_thickness / 2.0,
515 center_of_gravity.1,
516 ),
517 })
518}