"""
The read module contains common functions for reading material or section
databases.
The functions getSteelSections and getRectangularSections are intended for
for users to use directly.
Material DB functions are not inteded for use by user, instead they should use
material files from the specific design library they are targeting.
"""
import os
from dataclasses import dataclass
import pandas as pd
from math import isnan
from .material import MaterialAbstract
from .section import SectionAbstract, SectionRectangle, LayerClt, SectionCLT, LayerGroupClt, SectionSteel
__all__ = ["getSteelSections", "getRectangularSections"]
filepath = os.path.realpath(__file__)
basedir = os.path.dirname(filepath)
matBaseDir = os.path.join(os.path.dirname(basedir), 'design')
@dataclass
class DBConfig:
"""
A configuration file, used to load a file from a database.
Databases are stored in path code/dbTyp/dbname.csv, for example clt
csa/clt/prg320_2019
Parameters
----------
code : str
The building code to use.
dbType : str
The type of object to use for the database, i.e. glulam/clt/steel.
dbName : str
The name of the database to use. Typically this is in the form
supplier_year_qualifier, e.g. prg320_2019, or supplier_year_qualifier,
e.g.
"""
code:str
dbType:str
dbName:str
def _setupLoader(objType):
# @wraps()
def _loadDBDict(config:DBConfig) -> pd.DataFrame:
"""
Loads a database using the input configuration file and returns a
pd dataframe.
Note that converting a pd dataframe to a dictionary is expensive,
so any fitlering should be done before that operation.
Parameters
----------
config : DBConfig
The database configuration object.
Returns
-------
pd.DataFrame
A padas dataframe with the database information in it.
"""
base = os.path.join(basedir, objType, 'db')
dbPath = os.path.join(base, config.code, config.dbType)
fileID = config.dbName + '.csv'
dbPath = os.path.join(dbPath, fileID)
return pd.read_csv(dbPath)
return _loadDBDict
# @setupLoader
_loadMaterialDBDict = _setupLoader('material')
_loadSectionDBDict = _setupLoader('section')
def _loadMaterialDB(config:DBConfig,
MatClass:MaterialAbstract,
sUnit:str = 'MPa',
rhoUnit:str = 'kg/m3'):
"""
Loads a file and returns the results as a dictionary
"""
matdb = _loadMaterialDBDict(config)
matDict = matdb.to_dict(orient='index')
materials = []
for key in matDict.keys():
materials.append(MatClass(matDict[key], sUnit=sUnit, rhoUnit = rhoUnit))
return materials
# =============================================================================
# Section read files
# =============================================================================
# def _loadSectionDBDict(config:DBConfig) -> pd.DataFrame:
# """
# Loads a file and returns the results as a dictionary.
# Note that converting to a dictionary is expensive, and we want to do
# any fitlering before that operation.
# """
# base = os.path.join(basedir, 'section', 'db')
# dbPath = os.path.join(base, config.code, config.dbType)
# fileID = config.dbName + '.csv'
# dbPath = os.path.join(dbPath, fileID)
# return pd.read_csv(dbPath)
def _loadSectionDB(mat:MaterialAbstract,
config:DBConfig,
sectionClass:SectionAbstract,
lUnit='mm') -> pd.DataFrame:
"""
Loads a file and returns the results as a dictionary
"""
sectiondb = _loadSectionDBDict(config)
sectionDict = sectiondb.to_dict(orient='index')
sections = []
for key in sectionDict.keys():
sections.append(sectionClass(mat, sectionDict[key]), lUnit = lUnit)
return sections
def _loadSectionRectangular(mat:MaterialAbstract, config:DBConfig, lUnit) -> list[SectionRectangle]:
sectiondb = _loadSectionDBDict(config)
sectionDict = sectiondb.to_dict(orient='index')
sections = []
for key in sectionDict.keys():
tmpD = sectionDict[key]
sections.append(SectionRectangle(mat, tmpD['b'], tmpD['d'], lUnit))
return sections
def _getCodeUnits(code):
if code == 'us':
return 'in'
else:
return 'mm'
[docs]def getRectangularSections(mat:MaterialAbstract,
code:str,
dbType:str,
fileName:str) -> list[SectionRectangle]:
"""
Creates a set of rectangular sections from a input database.
The units the database are decided based on what code it's in, with
canadian given units of mm, and american codes given units of in.
Parameters
----------
mat : MaterialAbstract
The material to be applied to the sections.
code : str
The code to use, can be one of 'csa' or 'us'.
dbType : str
The type of section to use, i.e. glulam.
fileName : str
The specific database file to read from.
Returns
-------
sections : list[SectionRectangle]
A list of rectangular sections from the database.
"""
lUnit = _getCodeUnits(code)
config = DBConfig(code, dbType, fileName)
return _loadSectionRectangular(mat, config, lUnit)
def getSectionTypes(sectionRawDict, section_type: str) -> pd.DataFrame:
"""
Removes all empty entry from the dataframe.
"""
return sectionRawDict.loc[sectionRawDict.type == section_type.upper()]
#TODO Evaluate if we can make this a generic steel section class.
sectionDict = {'w':SectionSteel, 'hss':SectionSteel}
[docs]def getSteelSections(mat:MaterialAbstract,
code:str,
dbName:str,
steelShapeType:str) -> list[SectionSteel]:
"""
Returns a list of steel sections from a section database.
The section database must be one of the steel databases here:
https://limitstates.readthedocs.io/en/latest/rst/objects-section-db.html
Parameters
----------
mat : MaterialAbstract
The material to apply to each section.
code : str
The code to use, can be one of 'csa' or 'us'.
dbType : str
The type of database to use, i.e. aisc, cisc
steelShapeType : str
The type of steel section to load, i.e. W, hss.
Returns
-------
list[SectionSteel]
The steel sections from the input database.
"""
steelShapeType = steelShapeType.lower()
# !!! This is a bandaid
if '_si' in dbName:
lUnit = 'mm'
else:
lUnit = _getCodeUnits(code)
# !!! do we actually need a different section for
if steelShapeType in list(sectionDict.keys()):
dbName += '_' + steelShapeType.lower()
else:
raise Exception(f'Shape {steelShapeType} is not currently supported.')
config = DBConfig(code, 'steel', dbName)
rawDbData = _loadSectionDBDict(config)
filteredDbData = getSectionTypes(rawDbData, steelShapeType)
filteredDict = filteredDbData.to_dict(orient='index')
SectionClass = sectionDict[steelShapeType]
# !!! Consider making this a funciton with more logic.
tmpNameList = dbName.replace('.csv', '').strip(steelShapeType).split('_')
dbName = ''.join(tmpNameList[0:2])
sections = [None]*len(filteredDict.keys())
for ii, key in enumerate(filteredDict.keys()):
tmpD = filteredDict[key]
sections[ii] = SectionClass(mat, tmpD, lUnit)
sections[ii].sectionDB = dbName
return sections
# =============================================================================
# CLT loading functions
# =============================================================================
def _sortCLTMatDict(cltMatDBDict:dict)->list[[dict,dict]]:
"""
A function used by other internal client functions to sort a material data
base into other funcitons
A function that is used to sort through a material database. The material
database will have information for two materials
Sorts the CLT dictionary so it has a list for each of the two materials
in the clt section, i.e. the strong and weak layer
"""
matDicts = {}
for key in cltMatDBDict.keys():
subDict = cltMatDBDict[key]
dictStrong = {}
dictWeak = {}
for subKey in subDict.keys():
val = subDict[subKey]
# Set the weak axis
if 'W' in subKey:
subKeyMod = subKey.replace('W','')
dictWeak[subKeyMod] = val
# Set the strong axis
elif 'S' in subKey:
subKeyMod = subKey.replace('S','')
dictStrong[subKeyMod] = val
# set propreties common to both materials, e.g. rho.
else:
dictStrong[subKey] = val
dictWeak[subKey] = val
matDicts[subDict['grade']] = [dictStrong, dictWeak]
return matDicts
def _parseCLTDataFrame(matDfDict):
"""
Modfies the orginal raw dataframe dictionary, converting thickneses and
layer orientations into a list for each variable.
All other attributes will remain the same.
"""
newMatDict = {}
for key in matDfDict.keys():
tTemp = []
oTemp = []
parsedMatDict = {}
matDict = matDfDict[key]
for matKey in matDict.keys():
val = matDict[matKey]
if matKey[0] == 't' and not isnan(val):
tTemp.append(val)
continue
#collect orientation to lists.
elif matKey[0] == 'o' and not isnan(val):
oTemp.append(val)
continue
# store standard values without modifying them.
elif (not matKey[0] == 't') and (not matKey[0] == 'o'):
parsedMatDict[matKey] = val
parsedMatDict['t'] = tTemp
parsedMatDict['o'] = oTemp
newMatDict[key] = parsedMatDict
return newMatDict
def _getLayerInd(cltGrade, mats):
"""
Finds the index of the clt grade to use.
"""
for ii in range(len(mats)):
if mats[ii][0].grade == cltGrade:
return ii
def _getCLTSectionLayers(sectionDict:dict, mats:list,
lUnit:str) -> list[LayerClt]:
"""
Creates the group of layers for the CLT section.
Parameters
----------
sectionDict : dict
The input section dictionary containing geometry information.
mats : list
The input material list containing all possible materials.
Returns
-------
layerMats : list[LayerClt]
A list of the output layers, with the correct materials assigned..
"""
layerThicknesses = sectionDict['t']
layerOrientations = sectionDict['o']
cltGrade = sectionDict['grade']
Nlayer = len(layerThicknesses)
ind = _getLayerInd(cltGrade, mats)
strongMat, weakMat = mats[ind]
layerMats = []
for ii in range(Nlayer):
o = layerOrientations[ii]
if o == 0:
mat = strongMat
elif o == 90:
mat = weakMat
else:
raise Exception(f"Recieved layer orientation of {o}, \
expected 0 or 90")
layerMats.append(LayerClt(layerThicknesses[ii], mat, o==0, lUnit))
return layerMats
def _loadSectionsCLT(mats:list[[MaterialAbstract, MaterialAbstract]],
config:DBConfig,
lUnit = 'mm', **sectionkwargs) -> list[SectionCLT]:
"""
An internal function that can be used to load a set of CLT sections given
input materials that correspond to the grades of each section type.
Parameters
----------
mats : list[[MaterialAbstract, MaterialAbstract]]
The materials to assign to the strong and weak axis respectively.
config : DBConfig
A config object that contains the database file to read from.
lUnit : string, optional
The units to use in the section. The default is 'mm'.
kwargs : dict
The units to use in the section. The default is 'mm'.
Returns
-------
list[SectionCLT]
A list of loaded sections.
"""
tempDict = _loadSectionDBDict(config)
tempDict = tempDict.to_dict(orient='index')
sectionsDict = _parseCLTDataFrame(tempDict)
sections = []
for key in sectionsDict.keys():
sectionDict = sectionsDict[key]
layers = LayerGroupClt(_getCLTSectionLayers(sectionDict, mats, lUnit))
sections.append(SectionCLT(layers, **sectionkwargs))
return sections
# def _loadMatsCLT(cltDBname:str = "prg320_2019") -> list[[MaterialAbstract, MaterialAbstract]]:
# """
# Loads a set of CLT material from a database. For each material grade, two
# seperate CLT materials are loaded, one for the strong axis, and one for
# the weak axis.
# Loads materials for each layer of CLT from a supplier database in
# the form [strong, weak].
# Materials propreties will be loaded in 'MPa' and 'kg/m3'
# Parameters
# ----------
# cltDBname : str, optional
# The name of the database to load. The default is 'prg320_2019.csv'.
# Returns
# -------
# mats : list[[MaterialCLTLayerCSA19, MaterialCLTLayerCSA19]]
# A list of the desired CLT materials in the form
# [strongAxisMat, weakAxisMat] sections.
# """
# cltConfig = DBConfig('csa', 'clt', cltDBname)
# # Load the material dictionary.
# rawDB = _loadMaterialDBDict(cltConfig)
# rawMatDict = rawDB.to_dict(orient='index')
# sortedMatDict = _sortCLTMatDict(rawMatDict)
# mats = []
# for cltGrade in sortedMatDict.keys():
# tempMatDict = sortedMatDict[cltGrade]
# mats.append([MaterialCLTLayerCSA19(tempMatDict[0], 'MPa', 'kg/m3'),
# MaterialCLTLayerCSA19(tempMatDict[1], 'MPa', 'kg/m3')])
# return mats
# =============================================================================
# Steel Loading functions
# =============================================================================