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!!!
# ---------------------------------------------------------------------------------------