forked from 1b-agent/fieldcraft-upstream
model: face splits along datum planes (#508)
A datum plane a split uses is in use, as one a partition cuts by. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
This commit is contained in:
1 parent
e8e3fc647c
commit
6c79498005
3 files changed
+84
-3
No files matched your search
@@ -174,7 +174,8 @@ impl Model {
|
||||
/// The definitions that refer to a datum (the partitions that cut by it), as `kind \`name\``
|
||||
/// strings.
|
||||
pub fn datum_users(&self, name: &str) -> Vec<String> {
|
||||
self.partitions.iter().filter(|p| p.datum == name).map(|p| format!("partition `{}`", p.name)).collect()
|
||||
let partitions = self.partitions.iter().filter(|p| p.datum == name).map(|p| format!("partition `{}`", p.name));
|
||||
partitions.chain(self.face_splits().iter().filter(|s| s.datum == name).map(|s| format!("face split `{}`", s.name))).collect()
|
||||
}
|
||||
|
||||
/// The partitions, in the order they cut.
|
||||
|
||||
+12
-2
@@ -85,7 +85,7 @@ pub use steps::{
|
||||
};
|
||||
pub use tie::Tie;
|
||||
pub use units::UnitSystem;
|
||||
pub use virtual_faces::{FaceMerge, Stitch};
|
||||
pub use virtual_faces::{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.
|
||||
@@ -180,6 +180,9 @@ pub struct Model {
|
||||
/// Stitches of imported surfaces, in the order they apply.
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
stitches: Vec<Stitch>,
|
||||
/// Face splits along datum planes, in the order they apply (after the merges).
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
face_splits: Vec<FaceSplit>,
|
||||
}
|
||||
|
||||
/// A [`Model`] as stored, before it is checked.
|
||||
@@ -236,6 +239,8 @@ struct ModelData {
|
||||
face_merges: Vec<FaceMerge>,
|
||||
#[serde(default)]
|
||||
stitches: Vec<Stitch>,
|
||||
#[serde(default)]
|
||||
face_splits: Vec<FaceSplit>,
|
||||
}
|
||||
|
||||
impl TryFrom<ModelData> for Model {
|
||||
@@ -331,6 +336,9 @@ impl TryFrom<ModelData> for Model {
|
||||
for v in d.stitches {
|
||||
m.add_stitch(v)?;
|
||||
}
|
||||
for v in d.face_splits {
|
||||
m.add_face_split(v)?;
|
||||
}
|
||||
Ok(m)
|
||||
}
|
||||
}
|
||||
@@ -465,6 +473,7 @@ impl Model {
|
||||
mesh_plans: self.mesh_plans.clone(),
|
||||
face_merges: self.face_merges.clone(),
|
||||
stitches: self.stitches.clone(),
|
||||
face_splits: self.face_splits.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -515,7 +524,7 @@ impl Model {
|
||||
}
|
||||
|
||||
/// Counts of each kind of definition, for summaries.
|
||||
pub fn definition_counts(&self) -> [(&'static str, usize); 18] {
|
||||
pub fn definition_counts(&self) -> [(&'static str, usize); 19] {
|
||||
[
|
||||
("sets", self.sets.len()),
|
||||
("materials", self.materials.len()),
|
||||
@@ -535,6 +544,7 @@ impl Model {
|
||||
("seeds", self.seeds.len()),
|
||||
("face_merges", self.face_merges.len()),
|
||||
("stitches", self.stitches.len()),
|
||||
("face_splits", self.face_splits.len()),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,59 @@ pub struct FaceMerge {
|
||||
pub into: Option<String>,
|
||||
}
|
||||
|
||||
/// Most face splits a model holds.
|
||||
pub const MAX_FACE_SPLITS: usize = 1024;
|
||||
|
||||
/// A face of a body split along a datum plane for meshing (`split:<name>:a|b`).
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct FaceSplit {
|
||||
pub name: String,
|
||||
pub body: String,
|
||||
pub face: String,
|
||||
/// The datum plane it splits along.
|
||||
pub datum: String,
|
||||
}
|
||||
|
||||
impl Model {
|
||||
/// The face splits, in the order they apply (after the merges).
|
||||
pub fn face_splits(&self) -> &[FaceSplit] {
|
||||
&self.face_splits
|
||||
}
|
||||
|
||||
/// Add a face split after the others.
|
||||
pub fn add_face_split(&mut self, split: FaceSplit) -> Result<()> {
|
||||
let name = split.name.clone();
|
||||
check_name("face split", &name)?;
|
||||
if self.face_splits.iter().any(|s| s.name == name) {
|
||||
return Err(Error::Invalid(format!("face split `{name}` already exists")));
|
||||
}
|
||||
if self.face_splits.len() >= MAX_FACE_SPLITS {
|
||||
return Err(Error::Limit(format!("a model holds at most {MAX_FACE_SPLITS} face splits")));
|
||||
}
|
||||
let invalid = |m: String| context("face split", &name, Error::Invalid(m));
|
||||
for n in [&split.body, &split.face] {
|
||||
if n.trim().is_empty() || n.len() > MAX_GEOMETRY_NAME_LEN {
|
||||
return Err(invalid(format!("body and face names are 1 to {MAX_GEOMETRY_NAME_LEN} bytes")));
|
||||
}
|
||||
}
|
||||
match self.datum(&split.datum) {
|
||||
None => return Err(context("face split", &name, Error::NotFound(format!("datum `{}`", split.datum)))),
|
||||
Some(d) if d.plane().is_none() => {
|
||||
return Err(invalid(format!("datum `{}` is a datum {}; a split is along a plane", split.datum, d.kind())));
|
||||
}
|
||||
Some(_) => {}
|
||||
}
|
||||
self.face_splits.push(split);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn remove_face_split(&mut self, name: &str) -> Result<FaceSplit> {
|
||||
let i = self.face_splits.iter().position(|s| s.name == name).ok_or_else(|| Error::NotFound(format!("face split `{name}`")))?;
|
||||
Ok(self.face_splits.remove(i))
|
||||
}
|
||||
}
|
||||
|
||||
/// Most stitches a model holds.
|
||||
pub const MAX_STITCHES: usize = 1024;
|
||||
|
||||
@@ -132,6 +185,23 @@ mod tests {
|
||||
FaceMerge { name: name.into(), body: "Base".into(), faces: faces.iter().map(|f| f.to_string()).collect(), into: None }
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn splits_need_a_datum_plane_and_hold_it() {
|
||||
let mut m = Model::default();
|
||||
m.add_datum("Mid", crate::Datum::Plane { point: [0.0; 3], normal: [1.0, 0.0, 0.0] }).unwrap();
|
||||
m.add_datum("P", crate::Datum::Point { point: [0.0; 3] }).unwrap();
|
||||
let sp = |n: &str, d: &str| FaceSplit { name: n.into(), body: "Base".into(), face: "F1:+z".into(), datum: d.into() };
|
||||
m.add_face_split(sp("S", "Mid")).unwrap();
|
||||
for bad in [sp("S", "Mid"), sp("T", "P"), sp("U", "nope"), FaceSplit { face: String::new(), ..sp("V", "Mid") }] {
|
||||
assert!(m.add_face_split(bad).is_err());
|
||||
}
|
||||
assert!(m.remove_datum("Mid").is_err(), "in use by the split");
|
||||
let back: Model = serde_json::from_str(&serde_json::to_string(&m).unwrap()).unwrap();
|
||||
assert_eq!(back.face_splits(), m.face_splits());
|
||||
m.remove_face_split("S").unwrap();
|
||||
m.remove_datum("Mid").unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stitches_are_kept_in_order_and_checked() {
|
||||
let mut m = Model::default();
|
||||
|
||||
Reference in new issue
Block a user