Mass .jbeam editing Python help.

Discussion in 'Programming' started by mrwallace888, Feb 25, 2025.

  1. mrwallace888

    mrwallace888
    Expand Collapse

    Joined:
    Jun 17, 2015
    Messages:
    534
    I need some help, I was using DeepSeek to help me write a Python script to help me mass-edit a bunch of .jbeam files, but I'm having some trouble. Python on its own doesn't support .jbeam format, and I couldn't find anything to help it support it. Python tries to convert to .json and vice versa and has issues that way. Also tried having DeepSeek help write a custom parser for the script that's specifically for .jbeam and it still has a lot of trouble. Various elements are added to new lines instead of sharing lines, spacing and indentation ends up wrong, etc.

    Here's what I'm trying to do:

    I'm creating new vehicle parts in the vehicle customizer for doors, hoods, and trunks. These doors when selected will not have break open and will stay closed.

    The script will search a specified directory's .jbeam files and check for part elements that are openable (autocoupler and couplernodes and stuff). If an element in the list of parts in the jbeam file does not contain them, delete the element from the list of parts in the jbeam. If the entire .jbeam file does not contain any elements with coupling, delete the file. If it does, append "_latched" to the part internal name, "(Latched)" to the part display name, and change the "autoCouplingStrength" to "999999999".

    For example:
    Code:
    "bastion_hood": {
        "information":{
            "authors":"BeamNG",
            "name":"Hood",
            "value":320,
        },
        "slotType" : "bastion_hood",
        "flexbodies": [
            ["mesh", "[group]:", "nonFlexMaterials"],
            ["bastion_hood", ["bastion_hood"]],
            ["bastion_hood_shield", ["bastion_hood"]],
        ],
        "controller": [
            ["fileName"],
            ["advancedCouplerControl", {"name":"hoodLatchCoupler"}],
            ["advancedCouplerControl", {"name":"hoodCatchCoupler"}],
        ]
        "hoodLatchCoupler":{
            "groupType": "autoCoupling",
            "couplerNodes":[
                ["cid1",    "cid2",     "autoCouplingStrength",    "autoCouplingRadius",   "autoCouplingLockRadius",   "autoCouplingSpeed", "couplingStartRadius",  "breakGroup"]
                ["f15",     "h8",        30000,                     0.01,                   0.005,                      0.2,                 0.1,                   "hood_latch"]
            ]
            "attachSoundVolume":1,
            "detachSoundVolume":1,
            "soundNode:":["f15"]
            "attachSoundEvent": "event:>Vehicle>Latches>Hood>90s_02_close",
            "detachSoundEvent": "event:>Vehicle>Latches>Hood>90s_03_open_lever",
            "breakSoundEvent":""
            "openForceMagnitude": 0,
            "openForceDuration": 0.0,
            "closeForceMagnitude": 0,
            "closeForceDuration": 0.0
        }
    }
    Becomes:
    Code:
    "bastion_hood_latched": {
        "information":{
            "authors":"BeamNG",
            "name":"Hood (Latched)",
            "value":320,
        },
        "slotType" : "bastion_hood",
        "flexbodies": [
            ["mesh", "[group]:", "nonFlexMaterials"],
            ["bastion_hood", ["bastion_hood"]],
            ["bastion_hood_shield", ["bastion_hood"]],
        ],
        "controller": [
            ["fileName"],
            ["advancedCouplerControl", {"name":"hoodLatchCoupler"}],
            ["advancedCouplerControl", {"name":"hoodCatchCoupler"}],
        ]
        "hoodLatchCoupler":{
            "groupType": "autoCoupling",
            "couplerNodes":[
                ["cid1",    "cid2",     "autoCouplingStrength",    "autoCouplingRadius",   "autoCouplingLockRadius",   "autoCouplingSpeed", "couplingStartRadius",  "breakGroup"]
                ["f15",     "h8",        999999999,                     0.01,                   0.005,                      0.2,                 0.1,                   "hood_latch"]
            ]
            "attachSoundVolume":1,
            "detachSoundVolume":1,
            "soundNode:":["f15"]
            "attachSoundEvent": "event:>Vehicle>Latches>Hood>90s_02_close",
            "detachSoundEvent": "event:>Vehicle>Latches>Hood>90s_03_open_lever",
            "breakSoundEvent":""
            "openForceMagnitude": 0,
            "openForceDuration": 0.0,
            "closeForceMagnitude": 0,
            "closeForceDuration": 0.0
        }
    }
    But like I said, attempts to make a script for this has caused there to be incorrect spacing, indentation, and lines, causing errors. Other times I try to make something it puts EVERYTHING on just one very long line like a paragraph.

    Is there anything I can use for Python to have a script that reads/writes .jbeam properly?
     
  2. _N_S_

    _N_S_
    Expand Collapse

    Joined:
    Oct 28, 2017
    Messages:
    73
    For parsing, you can try hjson: https://github.com/hjson
    implementation for c++ seems to work, not sure how it will work with python
     
  3. r3eckon

    r3eckon
    Expand Collapse

    Joined:
    Jun 15, 2013
    Messages:
    595
    You could do this entire thing through lua without having to deal with parsing issues from a python json library.

    The game has util functions for loading and saving json format that work perfectly with jbeam files: jsonReadFile and jsonWriteFile. I use these for editing config files to save custom data like mirror offsets. The write function has a parameter to have the proper indentations. The read function will return a table you can iterate through to find the fields you're looking for, remove or edit them and then save the edited table with the write function.

    You can loop over all jbeam files with FS:findFiles and then FS:removeFile to delete those you don't want. But because of the way the BeamNG virtual filesystem works, only the edited jbeam files you save would end up in the userfolder so you don't need to delete anything. If you plan on releasing a mod, starting from a brand new userfolder you can basically just grab the userfolder/vehicles folder that will only contain your edited files and throw it in a zip.
     
  4. mrwallace888

    mrwallace888
    Expand Collapse

    Joined:
    Jun 17, 2015
    Messages:
    534
    How can I do this?
     
  5. r3eckon

    r3eckon
    Expand Collapse

    Joined:
    Jun 15, 2013
    Messages:
    595
    Throw this code in a lua script (ex: userfolder/myScript.lua) and run it from console (extensions.myScript.myFunction())

    Code:
    local M = {}
    
    local function myFunction()
    local jbeamFiles = FS:findFiles("/vehicles/", "*.jbeam", -1)
    local cjbeam = {}
    local saveCurrent = false
    local cpartedited = {}
    local cjbeamedited = {}
    
    for _,path in pairs(jbeamFiles) do
    saveCurrent = false
    cjbeam = jsonReadFile(path)
    cjbeamedited = deepcopy(cjbeam)
    
    for pname,part in pairs(cjbeam) do
    cpartedited = deepcopy(part)
    
    for k,v in pairs(cpartedited) do
    if string.find(k, "Coupler") then -- found coupler
    if v["couplerNodes"] then
     
        saveCurrent = true -- mark file for saving
     
        for field_index,field_name in ipairs(v["couplerNodes"][1]) do -- loop over couplerNodes headers
            if field_name == "autoCouplingStrength" then
                for index,array in ipairs(v["couplerNodes"]) do
                    if index ~= 1 then
                        array[field_index] = 999999999
                    end
                end
            end
        end
     
    end
    end
    end
    
    if saveCurrent then
    cpartedited["information"]["name"] = cpartedited["information"]["name"] .. " Latched"
    cjbeamedited[pname .. "_latched"] = cpartedited
    end
    
    end -- parts loop
    
    if saveCurrent then
    jsonWriteFile(path, cjbeamedited, true)
    end
    
    end -- files loop
    
    
    end
    
    M.myFunction = myFunction
    return M
    I think it does what you want, at least as far as the jbeam files are concerned. The formatting isn't the same as the original files which are most likely hand made for the most part, but the jbeam file still works fine in game.

    upload_2025-2-27_21-56-45.png

    In game the latched part doesn't release no matter how hard you pull on it.

    upload_2025-2-27_21-59-57.png

    Also attached the output from that script in a zip file. Put it in your mods folder and it should add the latched parts.
     

    Attached Files:

  6. mrwallace888

    mrwallace888
    Expand Collapse

    Joined:
    Jun 17, 2015
    Messages:
    534
    Thanks a lot! Will this still delete the entries for non-coupler parts? Because that way I don't override the other misc parts. Like if you look at, say, a door, it'll have the door itself in the list, as well as any extra doors (like custom ones or for different body types), but then they also contain door cards and stuff in the list too, and so deleting the door cards and any misc parts in those lists means I'm not overriding anything else.
     
  7. r3eckon

    r3eckon
    Expand Collapse

    Joined:
    Jun 15, 2013
    Messages:
    595
    It only saves jbeam files that have edited coupler parts in them but those files could indeed contain non coupler parts, although they aren't edited. If you don't want any non coupler parts in the edited jbeam files you need to create a new unique jbeam file that only contains the edited coupler parts.

    Edit: I previously posted changes to make but realized it needed a few extra lines so I just edited the code. Still haven't tested but I believe this should work. First edit is using an empty table instead of copying the existing jbeam data so only edited parts are stored, second edit is using a bool to only add the edited parts to that jbeam table and final edit is to append "_latched" to the filename so it creates a unique jbeam file instead of overwriting the original.

    Code:
    local M = {}
    
    local function myFunction()
    local jbeamFiles = FS:findFiles("/vehicles/", "*.jbeam", -1)
    local cjbeam = {}
    local saveCurrent = false
    local addPart = false
    local cpartedited = {}
    local cjbeamedited = {}
    
    for _,path in pairs(jbeamFiles) do
    saveCurrent = false
    cjbeam = jsonReadFile(path)
    cjbeamedited = {}
    
    for pname,part in pairs(cjbeam) do
    addPart = false
    cpartedited = deepcopy(part)
    
    for k,v in pairs(cpartedited) do
    if string.find(k, "Coupler") then -- found coupler
    if v["couplerNodes"] then
     
        saveCurrent = true -- mark file for saving
        addPart = true -- mark part to add to edited jbeam
     
        for field_index,field_name in ipairs(v["couplerNodes"][1]) do -- loop over couplerNodes headers
            if field_name == "autoCouplingStrength" then
                for index,array in ipairs(v["couplerNodes"]) do
                    if index ~= 1 then
                        array[field_index] = 999999999
                    end
                end
            end
        end
     
    end
    end
    end
    
    if addPart then
    cpartedited["information"]["name"] = cpartedited["information"]["name"] .. " Latched"
    cjbeamedited[pname .. "_latched"] = cpartedited
    end
    
    end -- parts loop
    
    if saveCurrent then
    jsonWriteFile(string.gsub(path, ".jbeam", "_latched.jbeam"), cjbeamedited, true)
    end
    
    end -- files loop
    
    
    end
    
    M.myFunction = myFunction
    return M
     
    #7 r3eckon, Feb 28, 2025
    Last edited: Feb 28, 2025
  8. mrwallace888

    mrwallace888
    Expand Collapse

    Joined:
    Jun 17, 2015
    Messages:
    534
    Tried running extensions.myScript.myFunction() and this happened. The lua is just saved in the root userfolder.
    upload_2025-2-28_15-27-32.png
    upload_2025-2-28_15-27-44.png
     
  9. r3eckon

    r3eckon
    Expand Collapse

    Joined:
    Jun 15, 2013
    Messages:
    595
    Works fine for me. If you had the game running while you added the script you need to hit CTRL+L to reload lua scripts. If you make edits you need to do it as well to load changes.

    I just tested it and the only issue I noticed is that the string matching to replace ".jbeam" wasn't detecting dot character literally which could change some folder names, I don't think it would cause any problems but the fix is just adding a percent sign before the dot so:

    Replace this line
    Code:
    jsonWriteFile(string.gsub(path, ".jbeam", "_latched.jbeam"), cjbeamedited, true)
    With this line
    Code:
    jsonWriteFile(string.gsub(path, "%.jbeam", "_latched.jbeam"), cjbeamedited, true)
    I've once again attached the new output, you can see it worked as expected and created jbeam files that only contain latched variants of parts that have couplers.
     

    Attached Files:

  10. mrwallace888

    mrwallace888
    Expand Collapse

    Joined:
    Jun 17, 2015
    Messages:
    534
    Anything I'm doing wrong?
     
  11. r3eckon

    r3eckon
    Expand Collapse

    Joined:
    Jun 15, 2013
    Messages:
    595
    The script should be inside the 0.34 subfolder, the game doesn't load anything before that into the virtual filesystem so that version specific subfolder is the actual root of the userfolder as far as the game is concerned. This post explains in detail how this system works, you should take some time to read through it. Even if you manually migrate the userfolder to a different location, the game will still create version specific subfolders inside the folder you picked. That version specific subfolder is where all your configs, settings and mods are saved. That's the folder that opens up if you use the launcher to go to the userfolder.

    I think all the old data might have contributed to the confusion, prior to 0.22 the game didn't have version specific userfolder so that folder was the root of the userfolder but now the actual root is a version specific subfolder. You can remove pretty much everything except the version specific userfolders (all of which are optional except the current version) and the file version.txt which the game uses to pick the correct userfolder to load. Should look something like this:

    upload_2025-3-1_16-58-47.png

    Just move the extra stuff to another folder so you don't lose anything in there, but keep in mind the game hasn't been using any of these files since 0.22 dropped so most likely it's all useless.
     
  12. mrwallace888

    mrwallace888
    Expand Collapse

    Joined:
    Jun 17, 2015
    Messages:
    534
    The code stops after the scintilla because it can't decode scintilla_engine_v8_ece.jbeam or something like that. No idea what that file is or where it came from. I already checked my mods for it and it DID exist in the main game's scintilla.zip but I deleted the whole zip and just verified and it's not there anymore, but the issue persists. I'll have to check again. I can't use safe mode because then I can't use the lua.

    Edit: Figured out what mod is causing the issue, but then it gets hung up on another mod jbeam. It's a little annoying because the biggest thing is not only having it do vanilla cars but also modded parts and vehicles, and I don't know why it keeps getting hung up on various modded jbeams.
     
    #12 mrwallace888, Mar 1, 2025
    Last edited: Mar 2, 2025
  13. r3eckon

    r3eckon
    Expand Collapse

    Joined:
    Jun 15, 2013
    Messages:
    595
    Modded jbeam files very often have issues because of typos, missing fields, etc. Best you can do is avoid files that cause problems. Show me console output if there are any errors as well as the contents of a jbeam file that doesn't work.
     
  1. This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
    By continuing to use this site, you are consenting to our use of cookies.
    Dismiss Notice