The --transform flag renames and reorganizes nodes in a structural JBeam file. It classifies every node as left, middle, right, or support based on its X position and beam connectivity, then assigns new sequential names and updates every beam, triangle, and flexbody reference in the file to match.
This document explains what the tool does and how to configure it.
Transformation targets structural files containing positional node data: frames, suspension arms, subframes, chassis rails. The file must have a nodes section with the ["id", "posX", "posY", "posZ"] header.
It is not intended for any file where nodes have semantic names like int_strsl, dshsl, e1, cam, which is common in engine files, gauges and interior parts. Those names carry meaning that transformation does not understand and will overwrite.
jbeam-edit --transform yourfile.jbeam
This writes the result to yourfile.jbeam and saves the original as yourfile.bak.jbeam. Pass -i to skip the backup and overwrite in place directly.
To override what prefix names the tool generates, pass --update-names:
jbeam-edit --transform --update-names rl_f:rl_front yourfile.jbeam
This tells the tool that when it would generate a prefix derived from rl_f, use rl_front instead. The result would be names like rl_frontl0, rl_frontl1, ... rather than rl_fl0, rl_fl1, ...
Transformation reads .jbeam-edit.yaml in the working directory if present. Without it, built-in defaults are used. Create this file to override any parameter.
examples/jbeam-edit.yaml is a working one to copy. It is the config the test suite transforms every example file with, so it cannot drift out of date the way a block quoted here would.
Parameter reference:
| Key | Default | Description |
|---|---|---|
y-sorting-threshold |
0.05 | Y distance (meters) below which two nodes are treated as the same depth band |
x-sorting-threshold |
off | X distance (meters) below which two nodes in one Y band count as the same column |
support-threshold |
96 | Minimum beam count as a percentage of group size to classify a node as support |
max-support-coordinates |
3 | Maximum number of support node candidates examined per spatial group |
x-group-breakpoints |
±0.09 | Rules that map X coordinate to Left, Middle, or Right (see Left, Middle, Right) |
x-sorting-threshold is off unless you set it, and off is the only word it
accepts besides a distance. There is no number that turns it off: 0 gives
every node its own column, which is the most column sorting rather than none.
Both settings are distances in meters, so 0.05 means 5 cm.
They also work the same way, and it is worth knowing how. The tool goes through the nodes in order and starts a new group as soon as a node is at least the threshold away from the first node of the group it is currently filling. It does not compare each node to the one right before it. That is deliberate: a long gentle slope would otherwise chain together into one enormous group.
Start with y-sorting-threshold. It decides how much front to back
variation still counts as the same row of nodes. When you place a row across a
panel the nodes are never at exactly the same Y, and without a threshold a
millimetre of difference would decide the order, which would also make the two
sides of the car come out differently.
The number you want is the depth of one row, not the space between rows. The default 5 cm suits a row you placed carefully on a flat face. A curved panel needs more, because the row follows the curve.
examples/regression_jbeam/y-sorting-repro.jbeam
shows what that looks like. Its five frontmost left side nodes are one row as
far as the modeller is concerned, but they cover a fair bit of depth:
| Measurement | Value |
|---|---|
| Depth of the front row, -1.967 back to -1.791 | 0.176 |
| Front row's first node back to the next row's | 0.323 |
| Space between the two rows, -1.791 to -1.644 | 0.147 |
| Biggest step inside the front row | 0.138 |
Anything above 0.176 and up to 0.323 keeps that row together, so 0.31 is a comfortable pick and it is what the regression test uses. The default 0.05 splits the row in two. Note the trap again: the space between the rows is 0.147, barely more than the 0.138 step inside the row, so a threshold picked from the space between rows lands in the wrong place.
| What you see | What to do |
|---|---|
| One row comes out ordered front to back | Raise it, the row is deeper than you thought |
| Nodes at clearly different depths are mixed | Lower it |
Then x-sorting-threshold. It does the same thing sideways, inside each
row. Turn it on if the file zigzags: the heights climb, drop back down and climb
again. That happens when one row covers two vertical columns of nodes, say the
nose face of a panel and the fender beside it, and the tool has nothing but
height to go on.
Here is the part that trips people up. The number you want is the width of a column, not the space between the columns. Because the tool measures from the first node of a group, the threshold has to be a bit wider than your widest column, and no wider than the step from one column's innermost node across to the next column's innermost node.
Those same five nodes show it. Once they are in one row, the inner column sits at X 0.780, 0.920 and 0.953, and the outer one at 0.998 and 1.036:
| Measurement | Value |
|---|---|
| Width of the inner column, 0.780 out to 0.953 | 0.173 |
| Inner column's innermost across to the outer's | 0.218 |
| Space between the two columns, 0.953 to 0.998 | 0.045 |
| Biggest step inside the inner column | 0.140 |
So anything above 0.173 and up to 0.218 does the job, and 0.2 is the obvious pick. Notice that the space between the columns is only 0.045, smaller than a step inside the inner column. Set 0.045 and the tool splits the inner column in two, leaves 0.780 on its own, and you get a third wrong order rather than the right one. The space between the columns is the number that looks right, so it is worth measuring the column itself instead.
To find the number for your own vehicle, transform once with the setting off, find a spot where the heights zigzag, and read the X values of those rows. The two columns separate by eye. Take the first and last X of the wider column, subtract, and pick something a little above that. Transform again and the zigzag should be gone.
If it still looks wrong:
| What you see | What to do |
|---|---|
| Nothing changed at all | The threshold is too big, try a smaller one |
| Runs of one or two nodes, still zigzagging | The threshold is too small, try a bigger one |
| Still zigzagging whatever you set | Raise y-sorting-threshold first, see below |
That last one is worth checking early, because no X value can fix it. Columns
only exist inside a row, so if your two columns sit further apart front to back
than y-sorting-threshold allows, they never end up in the same row and
x-sorting-threshold never gets to look at them together.
Three ways this file can fail without saying much:
The file is read from the directory you run the command in, not from the directory holding the file you are transforming. Run the tool from somewhere else and you get the defaults.
support-threshold is required. Leave it out and the whole file fails to parse. The tool prints one line to stderr, uses every built-in default and still exits 0, so a typo in that one key throws away all your other settings.
Unknown keys are ignored, so a misspelled key looks like it worked.
This example uses examples/jbeam/frame.jbeam, a chassis rail with 55 nodes all originally named under the rl_f* and rl* prefixes.
Before (excerpt):
["rl_f0", 0.523, -2.098, 0.319],
["rl_f1", -0.417, -2.098, 0.319],
["rl_f2", 0.053, -2.095, 0.319],
["rl_f3", 0.523, -2.093, 0.268],
["rl_f4", -0.417, -2.093, 0.268],
["rl_f5", 0.053, -2.090, 0.269],
["rl_f14", 0.053, -1.314, 0.382], // support for front
["rl15", 0.790, -0.919, 0.182],
["rl_r37", 0.651, 1.152, 0.565],
["rl_r48", 0.053, 1.710, 0.565], // support for rear
After (excerpt):
// Left side
// prefix group rl_fl
["rl_fl0", 0.523, -2.093, 0.268],
["rl_fl1", 0.523, -2.098, 0.319],
...
// prefix group rll
["rll0", 0.790, -0.919, 0.182],
...
// Middle side
// prefix group rl_fm
["rl_fm0", 0.053, -2.090, 0.269],
["rl_fm1", 0.053, -2.095, 0.319],
...
// Right side
...
// Support nodes
["rl_fsm", 0.053, -1.314, 0.382], // support for front
["rl_rsm", 0.053, 1.710, 0.565], // support for rear
["rlsm", 0.053, -0.024, 0.578], // support
All beam, triangle, and flexbody rows in the same file are updated to use the new names.
Each node is classified by its X coordinate using the configured breakpoints. With the defaults:
- x >= 0.09 - Left
- -0.09 < x < 0.09 - Middle
- x <= -0.09 - Right
BeamNG vehicles are modeled with positive X on the driver's left side. A threshold of ±0.09 meters excludes narrow centerline nodes from being assigned to either side.
You can adjust breakpoints in .jbeam-edit.yaml to match your vehicle's proportions. The breakpoints are evaluated in order; the first match wins. Only LeftTree, MiddleTree, and RightTree are valid values - SupportTree is assigned separately.
Within each spatial group (Left, Middle, Right), the tool counts how many beams connect to each node, considering only beams where both endpoints are in the same file. The max-support-coordinates most-connected nodes in each group become candidates. A candidate is classified as support if its beam count is at least support-threshold percent of the group's total node count.
For example, with 50 nodes in the Left group and support-threshold: 96, a candidate needs at least round(0.96 * 50) = 48 beam connections to qualify as a support node.
Beams are counted across the whole file, not just inside the group. A centreline node tying the two sides together gets all of them counted, so the figure is not capped by the group size and can go above 100 percent of it.
The threshold also scales with the group. A group of 5 needs 5 connections, a group of 50 needs 48. The same percentage is easy to reach in a small group and hard in a large one.
Support nodes are structural hubs - nodes that many beams radiate from. They are extracted from their spatial group and placed in a separate Support section at the end of the nodes list.
Nodes within each group are sorted by three coordinates in order:
- Y (front to back) - nodes at more negative Y (further forward) come first, grouped into depth bands. The group is walked in Y order and a new band starts as soon as a node sits
y-sorting-thresholdor further from the node that opened the current band (default 5 cm). - Z (height) - within the same Y band, lower nodes come first.
- X (side offset) - within the same Y and Z band, nodes are sorted by X.
A band starts at the node that opened it, not at the previous node, so a long run of small steps cannot chain into one band much wider than the threshold.
With x-sorting-threshold set, each Y band is banded a second time before step 2, by X and by the same opener rule. Nodes in the same Y band but different columns then come out one column at a time, lower node first within each. Without it a band that spans two columns is ordered by height alone, so the output climbs one column, jumps to the other and comes back. That is what the setting is for: on a body panel the nose face and the fender beside it can sit at heights that interleave, and no Y threshold separates them because they are at the same depth.
The threshold is there because nodes you placed at one depth are rarely at exactly the same Y. Without it, a millimetre of difference would order them by Y instead of by height, and the two sides of a symmetric vehicle would come out in different orders.
The tool derives a new name for each node from its original prefix (the name with trailing digits removed) and its spatial group.
Non-support nodes:
- If the original prefix ends in
l,m, orr(an existing side letter), the side letter is replaced with the new side suffix.- Example:
rl_r*nodes on the left side → prefix becomesrl_l, producingrl_l0,rl_l1, ...
- Example:
- Otherwise, the side suffix is appended directly.
- Example:
rl_f*nodes on the left side → prefix becomesrl_fl, producingrl_fl0,rl_fl1, ...
- Example:
Nodes with the same derived prefix form a prefix group and share sequential indices starting at 0.
Support nodes:
Support nodes are first processed by updateSupportVertexName, which drops the trailing digits and appends the side letter of their spatial group (l, m, or r). The resulting name is then used as the prefix input to the naming step, which inserts an s to mark them as support.
- Example:
rl_f14at x=0.053 (Middle) → intermediate namerl_fm→ final namerl_fsm - Example:
rl19at x=0.053 (Middle) → intermediate namerlm→ final namerlsm - Example:
rl_r48at x=0.053 (Middle) → intermediate namerl_rm→ final namerl_rsm
The first support node in a prefix group gets no index, and the rest are numbered from 1: rl_fsm, rl_fsm1, rl_fsm2. A group with only one support node therefore ends at rl_fsm.
Prefix comments:
When a spatial group contains more than one prefix group, the tool inserts a prefix group <name> comment before each group. This makes it easy to see where the original prefixes mapped to.
When transformation renames nodes, it also scans every other .jbeam file in the same directory and updates any references to the old names. No extra flags are needed - this happens automatically as part of --transform.
Use --validate-beams to check whether any beam in a file references an unknown node:
jbeam-edit --validate-beams yourdir/yourfile.jbeam
Without a filename argument, all .jbeam files in the directory are validated. The tool loads all files in the directory as the known vertex set, so cross-file references are checked correctly.
Named nodes are not protected. Transformation renames every node in the file without distinction, so a file that mixes structural nodes with semantically named ones (int_strsl, dshsl, rm_*) loses those names. Check a file for named nodes before transforming it. An excludePrefixes config option is planned to protect them.
The transformation feature is experimental. It is not included in the standard release binary. To use it, you need to build from source with the transformation flag enabled, or download a build that explicitly includes it.
Multi-section files are handled. Files with multiple nodes sections (common in suspension files) are all processed correctly.
Metadata is preserved. Per-node metadata objects (properties attached directly to a node row) are moved with the node and deduplicated against the section-level metadata above them.