structural_shapes/
lib.rs

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
15/// A helper function supporting conversion of floating point numbers to meters
16pub 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/// This enum contains different structural shapes
21#[derive(Clone, Copy, Debug)]
22#[non_exhaustive]
23pub enum StructuralShape {
24    /// This is a pipe with an outer_radius and a thickness
25    Pipe {
26        /// Outer radius of hte pipe
27        outer_radius: Length,
28        /// Thickness of the pipe wall
29        thickness: Length,
30        /// Coordinates of center of gravity
31        center_of_gravity: (Length, Length),
32    },
33    /// This is an I-Beam, with a width, height, web thickness, and flange thickness
34    IBeam {
35        /// Width of the beam
36        width: Length,
37        /// Height of the beam
38        height: Length,
39        /// Thickness of the web
40        web_thickness: Length,
41        /// Thickness of the flange
42        flange_thickness: Length,
43        /// Coordinates of center of gravity
44        center_of_gravity: (Length, Length),
45    },
46    /// This is a box beam with a width, height, and thickness
47    BoxBeam {
48        /// Width of the box beam
49        width: Length,
50        /// Height of the box beam
51        height: Length,
52        /// Thickness of the wall
53        thickness: Length,
54        /// Coordinates of center of gravity
55        center_of_gravity: (Length, Length),
56    },
57    /// This is a rod with a radius only
58    Rod {
59        /// Radius of the road
60        radius: Length,
61        /// Coordinates of center of gravity
62        center_of_gravity: (Length, Length),
63    },
64    /// This is a solid rectangular with width and height
65    Rectangle {
66        /// Width of the rectangle
67        width: Length,
68        /// Height of the rectangle
69        height: Length,
70        /// Coordinates of center of gravity
71        center_of_gravity: (Length, Length),
72    },
73}
74
75impl StructuralShape {
76    /// Make a new rod without COG
77    /// ```
78    /// # use structural_shapes::StructuralShape;
79    /// let shape = StructuralShape::new_rod(2.0);
80    /// ```
81    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    /// Make a new pipe without COG
89    /// ```
90    /// # use structural_shapes::StructuralShape;
91    /// let shape = StructuralShape::new_pipe(2.0, 0.15);
92    /// ```
93    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    /// Make a new rectangle without COG
102    /// ```
103    /// # use structural_shapes::StructuralShape;
104    /// let shape = StructuralShape::new_rectangle(2.0, 2.0);
105    /// ```
106    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    /// Make a new boxbeam without COG
115    /// ```
116    /// # use structural_shapes::StructuralShape;
117    /// let shape = StructuralShape::new_boxbeam(2.0, 2.0, 0.15);
118    /// ```
119    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    /// Make a new Ibeam without COG
129    /// ```
130    /// # use structural_shapes::StructuralShape;
131    /// let shape = StructuralShape::new_ibeam(2.0, 2.0, 0.15, 0.15);
132    /// ```
133    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    /// This function returns the moment of inertia of the structural shape around the x-axis
149    /// ```
150    /// # use structural_shapes::{StructuralShape};
151    /// let shape = StructuralShape::new_rod(2.0);
152    /// let moi = shape.moi_x();
153    /// ```
154    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    /// This function returns the moment of inertia of hte structural shape around the y-axis
221    /// ```
222    /// # use structural_shapes::StructuralShape;
223    /// let shape = StructuralShape::new_rod(2.0);
224    /// let area = shape.moi_y();
225    /// ```
226    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    /// This function returns the polar moment of inertia of the composite shape about the origin.
284    /// ```
285    /// # use structural_shapes::StructuralShape;
286    /// let shape = StructuralShape::new_rod(2.0);
287    /// let area = shape.polar_moi();
288    /// ```
289    pub fn polar_moi(&self) -> SecondAreaMomentofInertia {
290        self.moi_x() + self.moi_y()
291    }
292
293    /// This function returns the cross-sectional area of the structural shape
294    /// ```
295    /// # use structural_shapes::StructuralShape;
296    /// let shape = StructuralShape::new_rod(2.0);
297    /// let area = shape.area();
298    /// ```
299    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    /// A function to set the center of gravity of a shape
329    /// ```
330    /// # use structural_shapes::{StructuralShape};
331    /// let shape = StructuralShape::new_rod(2.0).with_cog(0.0, 0.0);
332    /// let moi = shape.moi_x();
333    /// ```
334    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    /// A function to return the current center of gravity for a shape
340    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    /// A function to set the current center of gravity for a shape
361    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/// A composite composed of multiple individual shapes
398/// ```
399/// # use structural_shapes::*;
400/// let x = CompositeShape::new()
401///     .add(StructuralShape::new_rod(2.0).with_cog(2.0, 0.0))
402///     .add(StructuralShape::new_rod(2.0).with_cog(-2.0, 0.0));
403/// ```
404#[derive(Clone, Debug)]
405pub struct CompositeShape {
406    /// Constituent shapes
407    pub shapes: Vec<(i8, StructuralShape)>,
408}
409
410impl CompositeShape {
411    /// This creates a new composite shape, identical to default
412    pub fn new() -> Self {
413        Self::default()
414    }
415    /// This function adds a new shape to the composite
416    pub fn add(&mut self, new_shape: StructuralShape) -> Self {
417        self.shapes.push((1, new_shape));
418        self.clone()
419    }
420    /// This function subtracts a new shape to the composite
421    pub fn sub(&mut self, new_shape: StructuralShape) -> Self {
422        self.shapes.push((-1, new_shape));
423        self.clone()
424    }
425    /// Calculate center of gravity and update COG of members
426    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    /// Shift structure to have cog at (0.0,0.0)
449    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    /// This function returns the moment of inertia of the composite shape around the x-axis
459    pub fn moi_x(&self) -> SecondAreaMomentofInertia {
460        self.shapes.iter().map(|x| (x.0 as f64) * x.1.moi_x()).sum()
461    }
462    /// This function returns the moment of inertia of the composite shape around the y-axis
463    pub fn moi_y(&self) -> SecondAreaMomentofInertia {
464        self.shapes.iter().map(|x| (x.0 as f64) * x.1.moi_y()).sum()
465    }
466    /// This function returns the polar moment of inertia of the composite shape around the origin.
467    pub fn polar_moi(&self) -> SecondAreaMomentofInertia {
468        self.moi_x() + self.moi_y()
469    }
470    /// This function returns the area of the composite shape
471    pub fn area(&self) -> Area {
472        self.shapes.iter().map(|x| (x.0 as f64) * x.1.area()).sum()
473    }
474}
475
476/// Implement default
477impl Default for CompositeShape {
478    fn default() -> Self {
479        CompositeShape { shapes: vec![] }
480    }
481}
482
483/// Function for swapping values
484fn swap(pair: (Length, Length)) -> (Length, Length) {
485    (pair.1, pair.0)
486}
487
488/// Create a composite I-beam from some initial parameters
489fn 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}