3. Generate a Section Shell Card from Thickness and Integration Rules
Why *SECTION_SHELL formatting is unforgiving
LS-DYNA reads *SECTION_SHELL in fixed-field mode by default. Each of the eight data fields on the card occupies exactly 10 characters, and the parser does not care about whitespace alignment — it slices the line at column boundaries. A value that drifts one character to the left silently shifts every subsequent field, so a thickness of 2.0 can end up interpreted as an integration point count. The generator therefore has two jobs: compute the correct values, and place them at the correct columns.
The card layout for the standard (non-composite) shell section is:
| Field | Name | Meaning | Typical value |
|---|---|---|---|
| 1 | SECID | Section ID referenced by *PART | integer, unique |
| 2 | ELFORM | Shell element formulation | 2 (Belytschko-Tsay), 16 (fully integrated) |
| 3 | SHRF | Shear correction factor | 0.8333 (5/6) default |
| 4 | NIP | Through-thickness integration points | 2–5 for elastic, 3–5 for plasticity |
| 5 | QR | Quadrature rule | 0 (default), 1 (Gauss), 2 (Lobatto) |
| 6 | ICOMP | Composite flag | 0 for homogeneous shell |
| 7 | SETYP | Shell type | 1 (default), 2, 3 |
| 8 | T1 | Thickness at node 1 | e.g. 2.0 |
Fields 9–12 (T2, T3, T4) continue on the same line for variable-thickness shells; for a constant-thickness part you set T1 and leave the rest blank or repeat T1. The generator below handles the constant-thickness case and optionally emits the four nodal thicknesses.
Building the generator
The core function takes a parameter dictionary and returns a single formatted line. The critical helper is a fixed-width formatter that right-justifies each value into a 10-character slot, matching LS-DYNA's column convention.
def _f10(value):
"""Format a value into a 10-character LS-DYNA field."""
if value is None or value == "":
return " " * 10
if isinstance(value, float):
# LS-DYNA accepts E-notation; keep 6 significant digits
text = f"{value:.6g}"
else:
text = str(value)
if len(text) > 10:
raise ValueError(f"value {text!r} exceeds 10-character field")
return text.rjust(10)
def section_shell_card(params):
"""Emit a *SECTION_SHELL keyword block from a parameter dict.
Required keys: secid, elform, nip, t1
Optional keys: shrf (0.8333), qr (0.0), icomp (0), setyp (1),
t2, t3, t4 (default to t1)
"""
secid = params["secid"]
elform = params["elform"]
nip = params["nip"]
t1 = params["t1"]
shrf = params.get("shrf", 0.8333)
qr = params.get("qr", 0.0)
icomp = params.get("icomp", 0)
setyp = params.get("setyp", 1)
t2 = params.get("t2", t1)
t3 = params.get("t3", t1)
t4 = params.get("t4", t1)
line1 = "".join(_f10(v) for v in
(secid, elform, shrf, nip, qr, icomp, setyp, t1))
line2 = "".join(_f10(v) for v in (t2, t3, t4))
return (
"*SECTION_SHELL\n"
f"{line1}\n"
f"{line2}\n"
)
Two details matter here. First, _f10 rejects any value longer than 10 characters rather than truncating — silent truncation is exactly the failure mode that produces a corrupt deck. Second, floats are rendered with :.6g, which keeps 0.8333 as 0.8333 and 2.0 as 2, both of which LS-DYNA parses correctly while staying inside the field width.
Integration-point and formulation rules
The generator should not blindly accept any NIP/ELFORM combination. Two rules are worth encoding as validation before emission:
- NIP vs. ELFORM. For ELFORM 2 (Belytschko-Tsay) and ELFORM 16 (fully integrated), NIP controls through-thickness integration only. NIP=1 is legal but gives no bending response; NIP≥2 is required for any part that carries moment. For elastic-only parts NIP=2 is sufficient; for plasticity NIP=3 or 5 is the usual choice.
- QR interaction. QR=0 lets LS-DYNA pick the default rule for the given NIP. Setting QR=1 (Gauss) or QR=2 (Lobatto) explicitly is only meaningful when NIP matches the rule's supported point count; Lobatto with NIP=2 places points at the surfaces, which is useful for contact-heavy shells but changes the stress output locations.
Worked example
Suppose a 2.0 mm steel panel uses Belytschko-Tsay with 5 through-thickness points and default shear correction. The parameter dictionary and resulting card are:
params = {
"secid": 101,
"elform": 2,
"nip": 5,
"t1": 2.0,
"shrf": 0.8333,
"qr": 0.0,
"icomp": 0,
"setyp": 1,
}
print(section_shell_card(params))
Expected output (each field right-justified in 10 columns):
*SECTION_SHELL
101 2 0.8333 5 0 0 1 2
2 2 2
Reading the first data line column by column: 101 is SECID, 2 is ELFORM, 0.8333 is SHRF, 5 is NIP, then three zeros for QR/ICOMP/SETYP, and 2 as T1. The second line carries T2, T3, T4 — all equal to T1 because the part is constant-thickness.
Verification before submission
After generating the card, run three checks that catch the most common defects:
- Field count. Split each data line into 10-character slices and confirm the number of non-blank slices matches the expected count (8 on line 1, up to 4 on line 2). A slice that is entirely blank in the middle of a line means a value was omitted and everything after it shifted.
- Round-trip parse. Re-read the emitted line with the same fixed-width slicer and confirm each recovered value equals the input. This catches float formatting that changed the value (e.g.
0.833333truncated to0.8333is acceptable;1e-05rendered as1e-05is fine, but1234567.0would overflow the field and must raise). - Cross-reference. Confirm the SECID does not collide with an existing section in the include file, and that every
*PARTreferencing this SECID uses a compatible ELFORM for its element type.
The generator above is deliberately small so it can be embedded in a larger automation pipeline: feed it a parameter dictionary from a spreadsheet or YAML file, emit one card per part, and concatenate the results into an include file. The fixed-width formatter and the field-count check are the two pieces that make the output safe to hand to LS-DYNA without manual cleanup.