diff --git a/crates/model/src/lib.rs b/crates/model/src/lib.rs index 5418553..d087a35 100644 --- a/crates/model/src/lib.rs +++ b/crates/model/src/lib.rs @@ -87,7 +87,7 @@ pub use steps::{ }; pub use tie::Tie; pub use units::UnitSystem; -pub use virtual_faces::{FaceMerge, FaceSplit, Stitch}; +pub use virtual_faces::{Defeature, FaceMerge, FaceSplit, Stitch}; /// The largest node or element label. Nastran's GRID and element ids stop at 99 999 999 (eight /// digits in a small field), the tightest of the three formats. @@ -191,6 +191,9 @@ pub struct Model { /// Face splits along datum planes, in the order they apply (after the merges). #[serde(skip_serializing_if = "Vec::is_empty")] face_splits: Vec, + /// Features of the design taken out for meshing, in the order they were made. + #[serde(skip_serializing_if = "Vec::is_empty")] + defeatures: Vec, } /// A [`Model`] as stored, before it is checked. @@ -253,6 +256,8 @@ struct ModelData { stitches: Vec, #[serde(default)] face_splits: Vec, + #[serde(default)] + defeatures: Vec, } impl TryFrom for Model { @@ -355,6 +360,9 @@ impl TryFrom for Model { for v in d.face_splits { m.add_face_split(v)?; } + for v in d.defeatures { + m.set_defeature(v)?; + } Ok(m) } } @@ -492,6 +500,7 @@ impl Model { face_merges: self.face_merges.clone(), stitches: self.stitches.clone(), face_splits: self.face_splits.clone(), + defeatures: self.defeatures.clone(), } } @@ -542,7 +551,7 @@ impl Model { } /// Counts of each kind of definition, for summaries. - pub fn definition_counts(&self) -> [(&'static str, usize); 20] { + pub fn definition_counts(&self) -> [(&'static str, usize); 21] { [ ("sets", self.sets.len()), ("materials", self.materials.len()), @@ -564,6 +573,7 @@ impl Model { ("face_merges", self.face_merges.len()), ("stitches", self.stitches.len()), ("face_splits", self.face_splits.len()), + ("defeatures", self.defeatures.len()), ] } } diff --git a/crates/model/src/virtual_faces.rs b/crates/model/src/virtual_faces.rs index 151ca13..4205dc4 100644 --- a/crates/model/src/virtual_faces.rs +++ b/crates/model/src/virtual_faces.rs @@ -237,3 +237,56 @@ mod tests { assert_eq!(m.face_merges().len(), 2); } } + +/// Most defeatures a model holds. +pub const MAX_DEFEATURES: usize = 1024; +/// Most features one defeature names. +pub const MAX_DEFEATURE_FEATURES: usize = 1024; + +/// Features of the design's timeline taken out for meshing (`geometry.defeature`): holes filled, +/// fillets and chamfers back to their sharp edges, bosses removed, by feature id (`F` in +/// persistent names). +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Defeature { + pub name: String, + pub features: Vec, +} + +impl Model { + /// The defeatures, in the order they were made. + pub fn defeatures(&self) -> &[Defeature] { + &self.defeatures + } + + /// Add a defeature, or replace the features of the one of that name (in its place). + pub fn set_defeature(&mut self, defeature: Defeature) -> Result<()> { + let name = defeature.name.clone(); + check_name("defeature", &name)?; + let invalid = |m: String| context("defeature", &name, Error::Invalid(m)); + if defeature.features.is_empty() || defeature.features.len() > MAX_DEFEATURE_FEATURES { + return Err(invalid(format!("1 to {MAX_DEFEATURE_FEATURES} features"))); + } + let mut seen = std::collections::BTreeSet::new(); + if let Some(id) = defeature.features.iter().find(|id| !seen.insert(**id)) { + return Err(invalid(format!("feature F{id} is listed twice"))); + } + match self.defeatures.iter().position(|d| d.name == name) { + Some(i) => { + if let Some(d) = self.defeatures.get_mut(i) { + *d = defeature; + } + } + None if self.defeatures.len() >= MAX_DEFEATURES => { + return Err(Error::Limit(format!("a model holds at most {MAX_DEFEATURES} defeatures"))); + } + None => self.defeatures.push(defeature), + } + Ok(()) + } + + pub fn remove_defeature(&mut self, name: &str) -> Result { + let i = self.defeatures.iter().position(|d| d.name == name).ok_or_else(|| Error::NotFound(format!("defeature `{name}`")))?; + Ok(self.defeatures.remove(i)) + } +}