forked from 1b-agent/fieldcraft-upstream
1D elements I: trusses (rod2, rod3), point masses, rotary inertia, springs and dashpots (#38)
* Model, fem and decks: point masses, rotary inertia, springs, dashpots and rod3 trusses Model: PointMass and Spring definitions (spring | dashpot; to ground or two-point; along a degree of freedom, a vector or the line of the ends) on a set or reference point, resolved per node; ElementKind::Rod3. fem: rod2/rod3 truss kernel on Basis::Line2/Line3 declaring ElementDofs (translations). Decks: Abaqus T3D3, MASS, ROTARYI, SPRING1/2/A, DASHPOT1/2/A; Nastran CONM1, CELAS2, CDAMP2; LS-DYNA ELEMENT_MASS/INERTIA and ELEMENT_DISCRETE; what a format cannot take is reported. Commands model.mass.* and model.spring.*. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm * Implicit solver: trusses, point masses, rotary inertia and springs on the DofLayout Trusses declare their translations like solids; point masses, inertia and springs ask the layout for the kinds they need at their nodes (translations, rotations), and reference points they use get six degrees of freedom. The matrix pattern takes rows for those points; dashpots are ignored with a warning. Existing results are bit-identical (hash-checked). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm * Explicit solver: trusses, point masses, springs and dashpots on the node-major layout Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm * docs: fem.md on trusses and discrete elements in the layout; regenerate features.md Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm * docs: trusses in plane models and corotational explicit trusses Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm --------- Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
53 files changed
+3626
-110
No files matched your search
@@ -35,7 +35,13 @@
|
||||
//! (`PPLANE`/`PLPLANE` with `CPLSTN*`/`CQUADX`, `CTRIAX6`, `PAXI`) differ between Nastran
|
||||
//! dialects, so a deck could not be read the same everywhere;
|
||||
//! - centrifugal loads: `RFORCE` about an axis through the origin of the basic system (speed in
|
||||
//! revolutions per time unit).
|
||||
//! revolutions per time unit);
|
||||
//! - point masses: `CONM1`, the node's 6 × 6 mass matrix (the masses per direction on the
|
||||
//! translations, the inertia tensor's components on the rotations, so no product-of-inertia sign
|
||||
//! convention is involved); springs and dashpots along a global degree of freedom: `CELAS2` and
|
||||
//! `CDAMP2` (the second grid left blank for one to the ground). Their ids follow the rigid
|
||||
//! elements'. A spring or dashpot along a skew direction and the three-node truss (rod3) have
|
||||
//! no counterpart here and are reported.
|
||||
//!
|
||||
//! Field widths, continuations and number forms are in [`card`]. Model content Nastran cannot
|
||||
//! express is reported as [`Error::Unsupported`], every item at once, and nothing is written.
|
||||
@@ -96,7 +102,7 @@ pub fn order(kind: ElementKind) -> &'static [usize] {
|
||||
/// The connection entry's name for an element kind.
|
||||
pub fn element_card(kind: ElementKind) -> &'static str {
|
||||
match kind {
|
||||
ElementKind::Rod2 => "CROD",
|
||||
ElementKind::Rod2 | ElementKind::Rod3 => "CROD",
|
||||
ElementKind::Beam2 => "CBEAM",
|
||||
ElementKind::Tri3 => "CTRIA3",
|
||||
ElementKind::Tri6 => "CTRIA6",
|
||||
@@ -257,6 +263,8 @@ struct Deck<'m> {
|
||||
element_pid: BTreeMap<u32, u32>,
|
||||
/// Rigid elements: (id, coupling name, coupling).
|
||||
rigid: Vec<(u32, &'m str, &'m Coupling)>,
|
||||
/// Point masses, springs and dashpots: (comment, card).
|
||||
discrete: Vec<(String, Card)>,
|
||||
/// Node → (dependent degrees of freedom, coupling that makes them dependent).
|
||||
dependent: BTreeMap<u32, (Dofs, &'m str)>,
|
||||
/// Constraint sets by id.
|
||||
@@ -304,6 +312,7 @@ impl<'m> Deck<'m> {
|
||||
properties: Vec::new(),
|
||||
element_pid: BTreeMap::new(),
|
||||
rigid: Vec::new(),
|
||||
discrete: Vec::new(),
|
||||
dependent: BTreeMap::new(),
|
||||
spc_sets: Vec::new(),
|
||||
load_sids: BTreeMap::new(),
|
||||
@@ -315,6 +324,7 @@ impl<'m> Deck<'m> {
|
||||
self.plan_materials()?;
|
||||
self.plan_sections()?;
|
||||
self.plan_couplings()?;
|
||||
self.plan_discrete()?;
|
||||
self.plan_constraints()?;
|
||||
self.plan_loads()?;
|
||||
for sub in &mut self.subcases {
|
||||
@@ -447,6 +457,9 @@ impl<'m> Deck<'m> {
|
||||
self.add_property(sname, Property::Beam { mid, profile, orientation }, elements.iter().copied())?;
|
||||
}
|
||||
SectionKind::Rod { area } => {
|
||||
if elements.iter().any(|e| kind_of(*e) == Some(ElementKind::Rod3)) {
|
||||
self.problems.insert(format!("section `{sname}`: three-node trusses (rod3; Nastran's rods have two grids)"));
|
||||
}
|
||||
let Some(mid) = ids.isotropic else { continue };
|
||||
self.add_property(sname, Property::Rod { mid, area }, elements.iter().copied())?;
|
||||
}
|
||||
@@ -520,6 +533,44 @@ impl<'m> Deck<'m> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Point masses, springs and dashpots, numbered after the rigid elements.
|
||||
fn plan_discrete(&mut self) -> Result<()> {
|
||||
let r = self.r;
|
||||
let mut next = self.rigid.last().map(|e| e.0).or_else(|| self.model.elements().keys().next_back().copied()).unwrap_or(0);
|
||||
let mut id = || -> Result<u32> {
|
||||
next = next
|
||||
.checked_add(1)
|
||||
.filter(|n| *n <= MAX_LABEL)
|
||||
.ok_or_else(|| Error::Limit(format!("no element id left below {} for point masses, springs and dashpots", MAX_LABEL as u64 + 1)))?;
|
||||
Ok(next)
|
||||
};
|
||||
for m in &r.masses {
|
||||
let [mx, my, mz] = m.mass;
|
||||
let [i11, i22, i33, i12, i13, i23] = m.inertia;
|
||||
// M11, M21, M22, M31, M32, M33, then the rotations' rows: M41…M44, M51…M55, M61…M66.
|
||||
let card = Card::new("CONM1")
|
||||
.int(id()?)
|
||||
.int(m.node)
|
||||
.blank()
|
||||
.reals([mx, 0.0, my, 0.0, 0.0, mz, 0.0, 0.0, 0.0, i11, 0.0, 0.0, 0.0, i12, i22, 0.0, 0.0, 0.0, i13, i23, i33]);
|
||||
self.discrete.push((format!("point mass {}", m.name), card));
|
||||
}
|
||||
for s in &r.springs {
|
||||
let what = s.kind();
|
||||
let Some(dof) = crate::axis_dof(s.direction) else {
|
||||
self.problems.insert(format!("{what} `{}`: a direction along no global axis (CELAS and CDAMP act along grid components)", s.name));
|
||||
continue;
|
||||
};
|
||||
let card = Card::new(if s.dashpot { "CDAMP2" } else { "CELAS2" }).int(id()?).real(s.value).int(s.first).int(dof);
|
||||
let card = match s.second {
|
||||
Some(b) => card.int(b).int(dof),
|
||||
None => card,
|
||||
};
|
||||
self.discrete.push((format!("{what} {}", s.name), card));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// One constraint set per distinct step constraint, checked against coupled nodes.
|
||||
fn plan_constraints(&mut self) -> Result<()> {
|
||||
let (model, r) = (self.model, self.r);
|
||||
@@ -780,6 +831,13 @@ impl<'m> Deck<'m> {
|
||||
};
|
||||
write_named(out, &format!("coupling {name}"), *eid, card)?;
|
||||
}
|
||||
if !self.discrete.is_empty() {
|
||||
comment(out, "point masses, springs and dashpots");
|
||||
}
|
||||
for (what, card) in &self.discrete {
|
||||
comment(out, what);
|
||||
card.write(out)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user