3. Automated Generation of *ELEMENT_SEATBELT Cards from Python Geometry
From Node Coordinates to *ELEMENT_SEATBELT Cards
In a lap-and-shoulder belt model, the webbing is discretized as a chain of 1D seatbelt elements. Each element references two nodes that must already exist in the deck, and the element's local orientation determines how the belt's cross-section is projected during contact. This case builds a Python routine that takes an ordered list of 3D node coordinates, emits the corresponding *NODE block, and then writes one *ELEMENT_SEATBELT card per segment. Before writing anything, the routine validates that consecutive nodes are distinct, that no segment is degenerate, and that the belt path does not fold back on itself in a way that would invert the element normal.
The input is deliberately minimal: a list of (x, y, z) tuples in millimeters, a starting node ID, a starting element ID, and the seatbelt part ID (SID). The output is a pair of keyword blocks that can be pasted into an existing deck or written to a standalone include file. The routine is pure Python 3 with no third-party dependencies, so it runs anywhere the rest of the parameterization pipeline runs.
Element Orientation and Why It Matters
An LS-DYNA seatbelt element is defined by its two end nodes and a reference vector that fixes the local s-axis (the webbing width direction). The element's normal is computed from the segment direction and that reference vector. If the segment direction reverses between adjacent elements — for example, because the node list was assembled in the wrong order around the D-ring — the normals flip and the belt will contact the occupant on the wrong face. The routine therefore checks the sign of the dot product between each segment direction and the previous one; a negative value beyond a small tolerance is flagged as a fold-back and reported with the offending element index.
Degenerate segments are the other common failure. Two coincident nodes produce a zero-length element, which LS-DYNA will either reject at initialization or, worse, accept with a zero normal and produce contact noise. The routine rejects any segment shorter than a configurable min_length (default 0.5 mm) and names the node pair in the error message so the geometry source can be corrected.
Implementation
The routine is split into three small functions so each validation can be tested in isolation. validate_path returns a list of human-readable problems; write_nodes and write_elements emit the keyword text. The element writer uses the standard seatbelt card layout: EID, PID, N1, N2, then the reference vector components on the second line.
import math
def validate_path(coords, min_length=0.5, dot_tol=1e-6):
problems = []
if len(coords) < 2:
problems.append("need at least two nodes to form a belt path")
return problems
prev_dir = None
for i in range(len(coords) - 1):
a, b = coords[i], coords[i + 1]
dx, dy, dz = b[0] - a[0], b[1] - a[1], b[2] - a[2]
length = math.sqrt(dx*dx + dy*dy + dz*dz)
if length < min_length:
problems.append(f"segment {i+1} (nodes {i+1}-{i+2}) length {length:.3f} < {min_length}")
continue
direction = (dx/length, dy/length, dz/length)
if prev_dir is not None:
dot = sum(p*q for p, q in zip(prev_dir, direction))
if dot < -dot_tol:
problems.append(f"segment {i+1} folds back (dot={dot:.3f})")
prev_dir = direction
return problems
def write_nodes(coords, start_id=1):
lines = ["*NODE"]
for k, (x, y, z) in enumerate(coords):
lines.append(f"{start_id + k:8d}{x:16.6f}{y:16.6f}{z:16.6f}")
return "\n".join(lines)
def write_elements(coords, start_eid=1, start_nid=1, pid=10, ref=(0.0, 0.0, 1.0)):
lines = ["*ELEMENT_SEATBELT"]
for k in range(len(coords) - 1):
eid = start_eid + k
n1 = start_nid + k
n2 = start_nid + k + 1
lines.append(f"{eid:8d}{pid:8d}{n1:8d}{n2:8d}")
lines.append(f"{ref[0]:16.6f}{ref[1]:16.6f}{ref[2]:16.6f}")
return "\n".join(lines)
def build_belt_block(coords, start_nid=1, start_eid=1, pid=10, ref=(0.0, 0.0, 1.0)):
problems = validate_path(coords)
if problems:
raise ValueError("belt path invalid:\n " + "\n ".join(problems))
return write_nodes(coords, start_nid) + "\n" + write_elements(
coords, start_eid, start_nid, pid, ref)
A representative lap-and-shoulder path in millimeters, with the shoulder anchor high and the buckle low, looks like this:
path = [
(120.0, -180.0, 620.0), # shoulder anchor
( 60.0, -120.0, 480.0), # upper webbing
( 10.0, -40.0, 300.0), # D-ring exit
(-20.0, 60.0, 120.0), # lap segment
(-40.0, 140.0, 40.0), # buckle stalk
]
block = build_belt_block(path, start_nid=1001, start_eid=5001, pid=10)
print(block)
The printed output begins with *NODE followed by five lines of coordinates, then *ELEMENT_SEATBELT with four element cards, each followed by its reference-vector line. The reference vector (0, 0, 1) is a reasonable default for a belt lying roughly in the X–Y plane; for a shoulder segment that twists toward vertical, pass a vector aligned with the webbing width instead.
Verifying the Generated Cards
Two checks confirm the block is usable before it reaches a solver run. First, count the cards: len(coords) - 1 element cards must appear, and the highest node ID must equal start_nid + len(coords) - 1. Second, re-read the emitted text and confirm that every N1/N2 pair matches consecutive node IDs — a mismatch usually means the start IDs were advanced between calls. A quick assertion covers both:
lines = block.splitlines()
elem_lines = [l for l in lines if l.strip() and l[0].isdigit()]
assert len(elem_lines) == 2 * (len(path) - 1), "unexpected card count"
*ELEMENT_SEATBELT block produces a twisted webbing surface that is hard to diagnose from contact output alone.validate_path reports a fold-back, do not simply reverse the node order to silence it. Check the physical routing first: a genuine fold-back at the D-ring usually means the shoulder and lap segments were sampled from different coordinate sources with opposite winding.Practical Notes
- Node and element IDs are written in 8-column integer fields and coordinates in 16-column float fields, matching the fixed-format convention LS-DYNA expects for these cards.
- The
pidargument ties every element to one seatbelt part; if the lap and shoulder segments belong to different parts, callwrite_elementstwice with differentpidvalues and split the coordinate list at the D-ring node. - For paths sampled from CAD, resample to roughly uniform segment lengths before calling the routine; highly uneven spacing stresses the belt material model and can trigger hourglassing in the webbing.
- The validation tolerance
dot_tolis intentionally small. Raising it hides shallow kinks that still produce visible normal discontinuities in contact results.
The routine is now a reusable building block: any geometry source that can produce an ordered coordinate list — a CSV export, a parametric anchor sweep, or a CAD centerline — can be converted into a validated seatbelt element block with a single call to build_belt_block.