Skip to main content

geop_ops_model/placed/
solids.rs

1//! The solids a part shows, wherever they are placed — its own, and those
2//! of the parts placed in it, however deep — each of its own part's
3//! material, if it has one: what weighing a part, checking it for
4//! interference, ordering it and simulating it read.
5//!
6//! What a step says of a solid beyond its shape (see [`crate::notes`]) is
7//! read here: a solid only drawn is no body.
8//!
9//! Lengths are millimetres and densities kg/m³ (see
10//! [`crate::parameters::Material`]), so a mass is in kilograms.
11
12use geop_core_program::{placed::Located, reader::Reader};
13use std::collections::HashMap;
14
15use crate::{
16    NamedModel, notes_of,
17    parameters::{Material, read_properties},
18    placed::placed_parts,
19    read_model,
20};
21use geop_core_math::{
22    geop_error::{GeopError, GeopResult, WithContext},
23    primitives::Pose,
24    scalars::Scalar,
25};
26use geop_core_topology::{SolidId, mass::MassProperties};
27
28/// A solid of a part, or of a part placed in it: named as the part names it
29/// (`/pin:extrude(pin)` for one of the part placed as `pin`, see
30/// [`Located::key`]), where it is.
31pub struct PlacedSolid<'a> {
32    pub name: String,
33    /// The path of the part placed it is of: empty for the part's own.
34    pub instance: String,
35    /// The part it is a solid of — whose material it is made of.
36    pub reader: Reader<'a>,
37    pub model: std::borrow::Cow<'a, NamedModel>,
38    pub solid: SolidId,
39    /// Where its part is placed; `None` for the part's own solids.
40    pub pose: Option<Pose>,
41}
42
43impl PlacedSolid<'_> {
44    /// The thickness of the sheet it is bent from (mm), for a sheet-metal
45    /// body (see [`crate::SolidNote::thickness`]).
46    pub fn thickness(&self) -> Option<f64> {
47        let own = self.model.name_of(self.solid)?;
48        notes_of(&self.reader)?.get(own)?.thickness
49    }
50
51    /// What it is made of: its part's material, none where it is not
52    /// given.
53    pub fn material(&self) -> Option<Material> {
54        let properties = read_properties(&self.reader).ok()?;
55        properties.material().cloned()
56    }
57
58    /// The mass properties of its shape alone, where it is: of density one,
59    /// so its mass is its volume and its centre the centroid — what it has
60    /// whatever it is made of. Its shape's are taken from `shapes`.
61    pub fn shape_properties(&self, shapes: &mut Shapes) -> GeopResult<MassProperties> {
62        let ctx = |e: GeopError| e.with_context(format!("the shape of {}", self.name));
63        let own = shapes.of(self).with_context(&ctx)?;
64        match &self.pose {
65            Some(pose) => own.placed(pose).with_context(&ctx),
66            None => Ok(own),
67        }
68    }
69
70    /// Its mass properties, where it is, of its part's material: none for a
71    /// solid of no material, which has no mass. Its shape's are taken from
72    /// `shapes`.
73    pub fn mass_properties(&self, shapes: &mut Shapes) -> GeopResult<Option<MassProperties>> {
74        let Some(material) = self.material() else {
75            return Ok(None);
76        };
77        let ctx = |e: GeopError| e.with_context(format!("the mass of {}", self.name));
78        let per_mm3 = Scalar::from_f64(material.density)
79            .div(Scalar::from_f64(1e9))
80            .with_context(&ctx)?;
81        let shape = self.shape_properties(shapes)?;
82        Ok(Some(shape.with_density(per_mm3)))
83    }
84}
85
86/// The mass properties of solids' shapes — of unit density, each in its
87/// own part's frame — each integrated once, however often its part is
88/// placed.
89#[derive(Default)]
90pub struct Shapes(HashMap<(*const NamedModel, SolidId), MassProperties>);
91
92impl Shapes {
93    /// The mass properties of the shape of `solid`.
94    pub fn of(&mut self, solid: &PlacedSolid) -> GeopResult<MassProperties> {
95        let key = (std::ptr::from_ref(&*solid.model), solid.solid);
96        if let Some(found) = self.0.get(&key) {
97            return Ok(*found);
98        }
99        let found = solid.model.topology().mass_properties(solid.solid)?;
100        self.0.insert(key, found);
101        Ok(found)
102    }
103}
104
105/// The solids of the part `reader` reads itself — not of the parts placed in
106/// it, and none only drawn (see [`crate::SolidNote::drawn_only`]) — the oldest
107/// first, each named behind `prefix` and placed at `pose`: as the part
108/// placed there at the path `instance` has them.
109pub fn own_solids_at<'a>(
110    reader: &Reader<'a>,
111    instance: &str,
112    pose: Option<Pose>,
113) -> GeopResult<Vec<PlacedSolid<'a>>> {
114    let model = read_model(reader)?;
115    let notes = notes_of(reader);
116    let drawn_only =
117        |name: &str| (notes.and_then(|notes| notes.get(name))).is_some_and(|note| note.drawn_only);
118    (model.solid_names().into_iter())
119        .filter(|name| !drawn_only(name))
120        .map(|name| {
121            Ok(PlacedSolid {
122                solid: model.solid_id(&name)?,
123                name: Located::in_instance(instance, name.as_str()).key(),
124                instance: instance.to_string(),
125                reader: reader.clone(),
126                model: model.clone(),
127                pose,
128            })
129        })
130        .collect()
131}
132
133/// The bodies of the part `reader` reads itself, the oldest first: not those
134/// of the parts placed in it.
135pub fn own_solids<'a>(reader: &Reader<'a>) -> GeopResult<Vec<PlacedSolid<'a>>> {
136    own_solids_at(reader, "", None)
137}
138
139/// Every body of the part `reader` reads and of the parts placed in it,
140/// however deep — every solid but those only drawn — each where it is:
141/// the part's own first, then those of each part placed, in the order they
142/// were placed.
143pub fn placed_solids<'a>(reader: &Reader<'a>) -> GeopResult<Vec<PlacedSolid<'a>>> {
144    let mut out = own_solids(reader)?;
145    for placed in placed_parts(reader)? {
146        let inner = placed.located.part.clone();
147        out.extend(own_solids_at(
148            &inner,
149            &placed.name,
150            Some(placed.located.world),
151        )?);
152    }
153    Ok(out)
154}