Python CDBase Interface Help!

Hello!

I am trying to extract a list of cross-section numbers for each beam element from a database using the CDBase Interface with Python. I’ve written the following code but do not understand why the CSLN_BEAM code does not contain any information. If I read the database use KWH = 39 and KWL = 0, I can get a list of cross-sections that appear to have the same length as the total number of unique structural lines in my model (although I’m not sure if this is really true). The problem is that I have no way of relating these cross-sections back to the beam values I read using the CBEAM class. My understanding was that I could input the structural line number as the KWL value for the CSLN_BEAM class, but when I do this, I find no values because these keys do not exist. I would really appreciate some help with this because I’ve been stuck on this all day.

Best,

ggalloway

"""
Inputs (Grasshopper):
    iSofistikFolderPath: str  # e.g. 'C:\\Program Files\\SOFiSTiK\\2025\\SOFiSTiK 2025\\'
    iCDBFilePath: str         # full path to .cdb

Outputs:
    oBNr:   list[int]  # BEAM Numbers
    oBNrQ:  list[int]  # BEAM Cross-Section Numbers
    oBNRef: list[int]  # BEAM Structural Line Numbers
    oBGr:   list[int]  # BEAM GROUP Numbers
    oBN1:   list[int]  # BEAM NODE 1
    oBn2:   list[int]  # BEAM NODE 1

"""

import os
import sys
import platform
from ctypes import *

# -----------------------------
# 1) Normalise base folder path
# -----------------------------
root = os.path.normpath(iSofistikFolderPath)
print("SOFiSTiK root folder:", repr(root))

# Path to SOFiSTiK Python examples (sofistik_daten.py)
examples_dir = os.path.join(root, "interfaces", "examples", "python")
print("Examples dir:", repr(examples_dir))

if examples_dir not in sys.path:
    sys.path.append(examples_dir)

from sofistik_daten import *

# -----------------------------
# 2) Platform detection
# -----------------------------
sof_platform = platform.architecture()[0]  # '64bit' or '32bit'
is_32bit = "32" in sof_platform
print("Platform:", sof_platform)

# -----------------------------
# 3) Determine DLL name (year)
# -----------------------------
def extract_year(main_string, target_substring="20"):
    pos = main_string.find(target_substring)
    if pos != -1 and pos + 4 <= len(main_string):
        return main_string[pos:pos + 4]
    return "0"

year = extract_year(root)
if year == "0":
    print("Warning: could not detect year from Sofistik folder. "
          "You may need to hardcode the DLL name.")

if not is_32bit:
    dll_name = "sof_cdb_w-{}.dll".format(year)
else:
    dll_name = "cdb_w31.dll"

print("DLL name:", dll_name)

# -----------------------------
# 4) DLL directory + registration
# -----------------------------
if is_32bit:
    print("Environment: 32bit DLLs are used")
    dll_dir = os.path.join(root, "interfaces", "32bit")
else:
    print("Environment: 64bit DLLs are used")
    dll_dir = os.path.join(root, "interfaces", "64bit")

dll_dir = os.path.normpath(dll_dir)
print("DLL directory:", repr(dll_dir))

if not os.path.isdir(dll_dir):
    print("ERROR: DLL directory does not exist:\n  {}".format(dll_dir))
    oNNr = oNX = oNY = oNZ = []
    raise IOError("SOFiSTiK DLL directory not found.")

# Try add_dll_directory, but fall back to PATH if it fails
if hasattr(os, "add_dll_directory"):
    try:
        os.add_dll_directory(dll_dir)
        print("Added DLL directory via os.add_dll_directory.")
    except (FileNotFoundError, OSError) as e:
        print("os.add_dll_directory failed with:", repr(e))
        print("Falling back to modifying PATH instead.")
        os.environ["PATH"] = dll_dir + ";" + os.environ.get("PATH", "")
else:
    # Rhino 8 should have this, but just in case
    os.environ["PATH"] = dll_dir + ";" + os.environ.get("PATH", "")
    print("os.add_dll_directory not available, PATH modified.")

# -----------------------------
# 5) Load the DLL
# -----------------------------
dll_full_path = os.path.join(dll_dir, dll_name)
print("Full DLL path:", repr(dll_full_path))

try:
    myDLL = cdll.LoadLibrary(dll_full_path)
    print("DLL loaded successfully.")
except OSError as e:
    print("Error loading DLL '{}': {}".format(dll_full_path, e))
    oNNr = oNX = oNY = oNZ = []
    raise

py_sof_cdb_get = myDLL.sof_cdb_get
py_sof_cdb_get.restype = c_int

# -----------------------------
# 6) Connect to CDB (read-only)
# -----------------------------
Index = c_int()
cdb_index = 95  # 95 = read-only

file_name = iCDBFilePath
print("CDB file:", repr(file_name))

Index.value = myDLL.sof_cdb_init(file_name.encode("utf-8"), cdb_index)
if Index.value <= 0:
    print("Error! Could not open CDB. Check path:\n{}".format(file_name))
    oNNr = oNX = oNY = oNZ = []
else:
    cdb_status = myDLL.sof_cdb_status(Index.value)
    print("CDB Status:", cdb_status)

    # ---------------------------------------------------------------------------------------
    # YOUR CODE STARTS HERE!!!
    # ---------------------------------------------------------------------------------------
    # -----------------------------
    # 7) Read NODE data
    # -----------------------------

    """
    sof_cdb_get return values:
        0 -> no error
        1 -> item longer than data
        2 -> end of file
        3 -> key does not exist
    We read while ie < 2.
    """

    beamGrpList, maxElements, minElements, slnList = [], [], [], []
    bGr, bNr, bNrQ, bNRef, bN1, bN2 = [], [], [], [], [], []

    # Get ELEMENT Group Data
    KWH = 11
    KWL = 0

    """
    cgrp.m_typ ELEMENT values:
          0 = BASIC
         20 = NODE (SUPPORT CONDITIONS)
         21 = KINEMATIC CONSTRAINTS
        
        100 = BEAM
        150 = TRUSS
        160 = CABLE
        170 = SPRING
        180 = BOUNDARY

        200 = QUAD

        300 = BRICK
    """
    
    ETYP = 100 # BEAM

    rec_len = c_int(sizeof(CGRP))
    ie = c_int(0)

    while ie.value < 2:
        rec_len.value = sizeof(CGRP)

        ie.value = py_sof_cdb_get(
            Index,
            KWH,
            KWL,
            byref(cgrp),
            byref(rec_len),
            1
        )

        if ie.value >= 2:
            break

        if cgrp.m_typ == ETYP:
            beamGrpList.append(cgrp.m_ng)
            minElements.append(cgrp.m_min)
            maxElements.append(cgrp.m_max)

    # Sort BEAM group lists
    beam_ranges = sorted(
        zip(maxElements, minElements, beamGrpList),
        key=lambda t: t[0]  # sort by max element
    )
    maxElements_sorted  = [t[0] for t in beam_ranges]
    minElements_sorted  = [t[1] for t in beam_ranges]
    beamGrpList_sorted  = [t[2] for t in beam_ranges]

    # Get BEAM Element Data
    KWH = ETYP
    KWL = 0

    rec_len.value = sizeof(CBEAM)
    ie.value = 0

    while ie.value < 2:
        rec_len.value = sizeof(CBEAM)

        ie.value = py_sof_cdb_get(
            Index,
            KWH,
            KWL,
            byref(cbeam),
            byref(rec_len),
            1
        )

        if ie.value >= 2:
            break

        if cbeam.m_nr <= 0:
            continue

        bNr.append(cbeam.m_nr)
        bNRef.append(cbeam.m_nref)
        bN1.append(cbeam.m_node[0])
        bN2.append(cbeam.m_node[1])

        for i in range(len(maxElements_sorted)):
            if cbeam.m_nr <= maxElements_sorted[i]:
                bGr.append(beamGrpList_sorted[i])
                break

    # Get BEAM CROSS-SECTION Data

    # 1) Build a map: structural line (SLN) -> (cs_start, cs_end)
    sln_to_cs = {}

    # Unique structural line numbers referenced by beams
    unique_sln = sorted(set(bNRef))

    for sln in unique_sln:
        if sln <= 0:
            continue  

        KWH = 39          # Structural line table
        KWL = sln         # <-- key is the structural line number

        rec_len = c_int(sizeof(CSLN_BEAM))
        ie = c_int(0)

        print(myDLL.sof_cdb_kexist(KWH, KWL))

        while ie.value < 2:
            rec_len.value = sizeof(CSLN_BEAM)

            ie.value = py_sof_cdb_get(
                Index,
                KWH,
                KWL,
                byref(csln_beam),
                byref(rec_len),
                1           # read next record for this (KWH, KWL)
            )

            if ie.value >= 2:
                break

            # We only care about the beam-related record in table 39
            if csln_beam.m_id != 101:
                continue

            # Store cross-section numbers at start/end of this structural line
            cs_start = csln_beam.m_nq[0]
            cs_end   = csln_beam.m_nq[1]
            sln_to_cs[sln] = (cs_start, cs_end)

            # Usually a single CSLN_BEAM per structural line is enough
            break

    # 2) Expand to one cross-section number per BEAM element
    # For now assume constant cross-section along structural line
    # -> use cs_start for all elements on that line.
    bNrQ = []

    for nref in bNRef:
        cs_pair = sln_to_cs.get(nref)

        if cs_pair is None:
            # No CSLN_BEAM found for this structural line
            # (maybe beam not created from a structural line)
            # Use 0 or handle via m_np / other tables if needed.
            bNrQ.append(0)
        else:
            cs_start, cs_end = cs_pair
            # If I want tapered members later, I can decide between cs_start/cs_end
            # based on cbeam.m_spar here; for now take cs_start.
            bNrQ.append(cs_start)

    # -----------------------------
    # 8) Close the CDB
    # -----------------------------
    myDLL.sof_cdb_close(0)
    print("CDB closed.")

    # -----------------------------
    # 9) Outputs
    # -----------------------------
    oBNr   = bNr
    oBNrQ  = bNrQ
    oBNRef = bNRef
    oBGr   = bGr
    oBN1   = bN1
    oBN2   = bN2

    # ---------------------------------------------------------------------------------------
    # YOUR CODE ENDS HERE!!!
    # ---------------------------------------------------------------------------------------