forked from 1b-agent/fieldcraft-upstream
mesh: node relaxation with UV surface slides and relaxation fields (#398)
Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
This commit is contained in:
1 parent
2030a81327
commit
75f9e5ae6e
6 files changed
+1477
No files matched your search
@@ -44,6 +44,7 @@ pub mod materials;
|
||||
mod mesh;
|
||||
pub mod nodal;
|
||||
pub mod progress;
|
||||
pub mod relaxations;
|
||||
pub mod rename;
|
||||
pub mod sections;
|
||||
pub mod seeds;
|
||||
@@ -70,6 +71,7 @@ pub use mesh::{
|
||||
Element, ElementEdge, ElementFace, ElementKind, MIN_NORMALIZED_JACOBIAN, Node, admissible_jacobian, check_coordinates, normalized_jacobian,
|
||||
tet4_jacobian,
|
||||
};
|
||||
pub use relaxations::{Relaxation, RelaxationCell};
|
||||
pub use sections::{BeamProfile, Extent, Formulation, Integration, Section, SectionKind};
|
||||
pub use seeds::{Bias, BiasToward, Divisions, Seed, SeedConstraint};
|
||||
pub use sets::Set;
|
||||
@@ -162,6 +164,9 @@ pub struct Model {
|
||||
/// Edge seeds, sorted by edge.
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
seeds: Vec<Seed>,
|
||||
/// Relaxation fields (`mesh.node.relax`), by name.
|
||||
#[serde(skip_serializing_if = "BTreeMap::is_empty")]
|
||||
relaxations: BTreeMap<String, Relaxation>,
|
||||
}
|
||||
|
||||
/// A [`Model`] as stored, before it is checked.
|
||||
@@ -210,6 +215,8 @@ struct ModelData {
|
||||
suppressed_datums: BTreeSet<String>,
|
||||
#[serde(default)]
|
||||
seeds: Vec<Seed>,
|
||||
#[serde(default)]
|
||||
relaxations: BTreeMap<String, Relaxation>,
|
||||
}
|
||||
|
||||
impl TryFrom<ModelData> for Model {
|
||||
@@ -293,6 +300,9 @@ impl TryFrom<ModelData> for Model {
|
||||
for s in d.seeds {
|
||||
m.set_seed(s)?;
|
||||
}
|
||||
for (n, v) in d.relaxations {
|
||||
m.set_relaxation(&n, v)?;
|
||||
}
|
||||
Ok(m)
|
||||
}
|
||||
}
|
||||
@@ -423,9 +433,33 @@ impl Model {
|
||||
partitions: self.partitions.clone(),
|
||||
suppressed_datums: self.suppressed_datums.clone(),
|
||||
seeds: self.seeds.clone(),
|
||||
relaxations: self.relaxations.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// The relaxation fields, by name.
|
||||
pub fn relaxations(&self) -> &BTreeMap<String, Relaxation> {
|
||||
&self.relaxations
|
||||
}
|
||||
|
||||
pub fn relaxation(&self, name: &str) -> Option<&Relaxation> {
|
||||
self.relaxations.get(name)
|
||||
}
|
||||
|
||||
/// Add a relaxation field, or replace the one of that name.
|
||||
pub fn set_relaxation(&mut self, name: &str, field: Relaxation) -> Result<()> {
|
||||
field.check().map_err(|e| context("relaxation", name, e))?;
|
||||
if self.relaxations.contains_key(name) {
|
||||
replace_existing(&mut self.relaxations, "relaxation", name, field)
|
||||
} else {
|
||||
insert_new(&mut self.relaxations, "relaxation", name, field)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_relaxation(&mut self, name: &str) -> Result<Relaxation> {
|
||||
remove_existing(&mut self.relaxations, "relaxation", name)
|
||||
}
|
||||
|
||||
/// Counts of each kind of definition, for summaries.
|
||||
pub fn definition_counts(&self) -> [(&'static str, usize); 16] {
|
||||
[
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
//! Relaxation fields: what a node relaxation (`mesh.node.relax`) did, kept so it can be done again
|
||||
//! on a new mesh of the same geometry.
|
||||
//!
|
||||
//! A field is the relaxed region of the mesh it was made on: each element there as its corners
|
||||
//! were (`at`) and where they went (`moved`). A node of a later mesh that lies in one of these
|
||||
//! cells takes the displacement interpolated with the cell's own shape functions at its
|
||||
//! parametric coordinates (barycentric in a tetrahedron or triangle, trilinear `(u, v, w)` in a
|
||||
//! hexahedron, …), so the relaxed shape carries over to a coarser or finer mesh. Fields are
|
||||
//! definitions of the model: journalled with the command that made them and saved in the
|
||||
//! `.fieldcraft` file.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{ElementKind, Error, Result, check_coordinates};
|
||||
|
||||
/// Most cells one field holds.
|
||||
pub const MAX_RELAXATION_CELLS: usize = 1_000_000;
|
||||
|
||||
/// One cell of a field: a linear element's corners before and after.
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct RelaxationCell {
|
||||
/// tri3, quad4, tet4, wedge6 or hex8 (a quadratic element's corners).
|
||||
pub kind: ElementKind,
|
||||
pub at: Vec<[f64; 3]>,
|
||||
pub moved: Vec<[f64; 3]>,
|
||||
}
|
||||
|
||||
/// A relaxation field.
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Relaxation {
|
||||
/// The bodies it was made on (empty: any).
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub bodies: Vec<String>,
|
||||
pub cells: Vec<RelaxationCell>,
|
||||
/// Applied again by itself after each remesh of its bodies.
|
||||
#[serde(default)]
|
||||
pub reapply: bool,
|
||||
}
|
||||
|
||||
impl Relaxation {
|
||||
pub fn check(&self) -> Result<()> {
|
||||
if self.cells.len() > MAX_RELAXATION_CELLS {
|
||||
return Err(Error::Limit(format!("a relaxation field holds at most {MAX_RELAXATION_CELLS} cells")));
|
||||
}
|
||||
for c in &self.cells {
|
||||
if !matches!(c.kind, ElementKind::Tri3 | ElementKind::Quad4 | ElementKind::Tet4 | ElementKind::Wedge6 | ElementKind::Hex8) {
|
||||
return Err(Error::Invalid(format!("a relaxation cell is linear, not {}", c.kind.name())));
|
||||
}
|
||||
let n = c.kind.node_count();
|
||||
if c.at.len() != n || c.moved.len() != n {
|
||||
return Err(Error::Invalid(format!("a {} relaxation cell has {n} corners", c.kind.name())));
|
||||
}
|
||||
check_coordinates(c.at.as_flattened())?;
|
||||
check_coordinates(c.moved.as_flattened())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user