"""Fairyfly_therm configurations.
Import this into every module where access configurations are needed.
Usage:
.. code-block:: python
from fairyfly_therm.config import folders
print(folders.lbnl_data_path)
folders.lbnl_data_path = "C:/Users/Person/LBNL"
"""
import os
import json
import pkgutil
import ladybug.config as lb_config
[docs]
class Folders(object):
"""Fairyfly_therm folders.
Args:
config_file: The path to the config.json file from which folders are loaded.
If None, the config.json module included in this package will be used.
Default: None.
mute: If False, the paths to the various folders will be printed as they
are found. If True, no printing will occur upon initialization of this
class. Default: True.
Properties:
* therm_path
* therm_exe
* therm_version
* therm_version_str
* fortran_dll_path
* lbnl_data_path
* therm_settings_path
* therm_lib_path
* material_lib_file
* gas_lib_file
* bc_steady_state_lib_file
* user_material_folder
* user_gas_folder
* user_steady_state_folder
* config_file
* mute
"""
THERM_VERSION = (8, 1, 30, 0)
LBNL_URL = 'https://windows.lbl.gov/therm-software-downloads'
FORTRAN_URL = 'https://windows.lbl.gov/redistributable-packages'
def __init__(self, config_file=None, mute=True):
self.mute = bool(mute) # set the mute value
self.config_file = config_file # load paths from the config JSON file
@property
def therm_path(self):
"""Get or set the path to Therm installation folder.
This is typically the folder within the lbnl program files directory
that starts with THERM and contains executables and dlls.
"""
return self._therm_path
@therm_path.setter
def therm_path(self, t_path):
exe_name = 'THERM{}.exe'.format(self.THERM_VERSION[0])
if not t_path: # check the default installation location
t_path = self._find_therm_folder()
therm_exe_file = os.path.join(t_path, exe_name) if t_path is not None else None
# if the executable exists, set the variables
if t_path and os.path.isfile(therm_exe_file):
self._therm_path = t_path
self._therm_exe = therm_exe_file
self._therm_version = self.THERM_VERSION
self._therm_version_str = '.'.join(str(i) for i in self.THERM_VERSION)
if not self.mute:
print("Path to Therm is set to: %s" % self._therm_path)
else:
if t_path is not None and os.path.isdir(t_path):
self._therm_path = t_path
else:
if t_path is not None:
msg = '{} is not a valid path to a Therm installation.'.format(t_path)
print(msg)
self._therm_path = None
self._therm_exe = None
self._therm_version = None
self._therm_version_str = None
@property
def therm_exe(self):
"""Get the path to Therm executable.
Will be none if no Therm installation was found.
"""
return self._therm_exe
@property
def therm_version(self):
"""Get a tuple for the version of therm (eg. (8, 1, 30)).
This will be None if the version could not be sensed or if no Therm
installation was found.
"""
return self._therm_version
@property
def therm_version_str(self):
"""Get text for the full version of therm (eg."8.1.30").
This will be None if the version could not be sensed or if no Therm
installation was found.
"""
return self._therm_version_str
@property
def fortran_dll_path(self):
"""Get or set the path to the directory with DLLs for FORTRAN runtime routines.
This is typically a folder within the Shared Libraries folder of
the common Intel files under Program Files (x86).
"""
return self._fortran_dll_path
@fortran_dll_path.setter
def fortran_dll_path(self, f_path):
if not f_path: # check the default installation location
f_path = self._find_fortran_dll()
# if the executable exists, set the variables
if f_path and os.path.isdir(f_path):
self._fortran_dll_path = f_path
if not self.mute:
print("Path to Fortran DLL is set to: %s" % self._fortran_dll_path)
else:
self._fortran_dll_path = None
@property
def lbnl_data_path(self):
"""Get or set the path to the folder containing the LBNL data.
This folder typically exists under the User profile and contains sub-folders
for all installed versions of LBNL THERM and WINDOW. The THERM sub-folder
contains a lib sub-folder with XML files for all materials, boundary
conditions, etc.
"""
return self._lbnl_data_path
@lbnl_data_path.setter
def lbnl_data_path(self, path):
if not path: # check the default locations of the template library
path = self._find_lbnl_data_folder()
# gather all of the sub folders underneath the master folder
if path and os.path.isdir(path):
self._lbnl_data_path = path
self._therm_settings_path = self._check_therm_settings(path)
self._material_lib_file = self._check_therm_lib_file(path, 'Materials.xml')
self._gas_lib_file = self._check_therm_lib_file(path, 'Gases.xml')
self._bc_steady_state_lib_file = self._check_therm_lib_file(
path, 'BoundaryConditionsSteadyState.xml')
if not self.mute:
print('Path to LBNL data is set to: {}'.format(self._lbnl_data_path))
else:
if path:
msg = '{} is not a valid path to a LBNL data folder.'.format(path)
print(msg)
self._lbnl_data_path = None
self._therm_settings_path = None
self._material_lib_file = None
self._gas_lib_file = None
self._bc_steady_state_lib_file = None
@property
def therm_settings_path(self):
"""Get the path to the .ini file with THERM settings.
Will be None if no LBNL data folder was found.
"""
return self._therm_settings_path
@property
def therm_lib_path(self):
"""Get or set the path to the folder from which therm materials are loaded.
This will be the therm folder within within the user's standards folder
if it exists.
"""
return self._therm_lib_path
@therm_lib_path.setter
def therm_lib_path(self, path):
if not path: # check the default locations of the template library
path = self._find_therm_lib()
# gather all of the sub folders underneath the master folder
if path and os.path.isdir(path):
self._therm_lib_path = path
mat_dir = os.path.join(path, 'materials')
gas_dir = os.path.join(path, 'gases')
bc_dir = os.path.join(path, 'conditions')
self._user_material_folder = mat_dir if os.path.isdir(mat_dir) else None
self._user_gas_folder = gas_dir if os.path.isdir(gas_dir) else None
self._user_steady_state_folder = bc_dir if os.path.isdir(bc_dir) else None
if not self.mute:
print('Path to THERM library is set to: {}'.format(self._lbnl_data_path))
else:
if path:
msg = '{} is not a valid path to a THERM standards library.'.format(path)
print(msg)
self._therm_lib_path = None
self._user_material_folder = None
self._user_gas_folder = None
self._user_steady_state_folder = None
@property
def material_lib_file(self):
"""Get the path to the material library file."""
return self._material_lib_file
@property
def gas_lib_file(self):
"""Get the path to the gas library file."""
return self._gas_lib_file
@property
def bc_steady_state_lib_file(self):
"""Get the path to the steady state condition library file."""
return self._bc_steady_state_lib_file
@property
def user_material_folder(self):
"""Get the path to the user material library folder."""
return self._user_material_folder
@property
def user_gas_folder(self):
"""Get the path to the user gas library folder."""
return self._user_gas_folder
@property
def user_steady_state_folder(self):
"""Get the path to the user steady state condition library folder."""
return self._user_steady_state_folder
@property
def config_file(self):
"""Get or set the path to the config.json file from which folders are loaded.
Setting this to None will result in using the config.json module included
in this package.
"""
return self._config_file
@config_file.setter
def config_file(self, cfg):
if cfg is None:
cfg = os.path.join(os.path.dirname(__file__), 'config.json')
self._load_from_file(cfg)
self._config_file = cfg
[docs]
def check_therm_version(self):
"""Raise an exception message about the THERM installation if it is not usable."""
# first, check that we are on the correct operating system
if os.name != 'nt':
msg = 'LBNL THERM can only run on Windows machines and so it cannot ' \
'be used while on "{}" operating system.'.format(os.name)
raise ValueError(msg)
# then, check that THERM is installed and it is the correct version
ver_str = '.'.join(str(i) for i in self.THERM_VERSION)
dn_msg = 'Download and install THERM version {} from:\n{}'.format(
ver_str, self.LBNL_URL)
if self.therm_exe is not None:
# make sure that the FORTRAN DLL library was installed
if self.fortran_dll_path is None:
msg = 'A valid THERM installation was found at "{}"\n' \
'but it does not have the required redistributable packages.\n' \
'To install these packages, follow the instructions at:\n ' \
'{}'.format(self.therm_path, self.FORTRAN_URL)
raise ValueError(msg)
return None # everything is good
elif self.therm_path is not None:
msg = 'A THERM installation was found at "{}" but it is not ' \
'for version {}.\nFairyfly is currently only compatible with ' \
'this version of THERM.\n{}.'.format(self.therm_path, ver_str, dn_msg)
raise ValueError(msg)
else:
msg = 'No THERM installation was found on this machine.\n{}'.format(dn_msg)
raise ValueError(msg)
def _load_from_file(self, file_path):
"""Set all of the the properties of this object from a config JSON file.
Args:
file_path: Path to a JSON file containing the file paths. A sample of this
JSON is the config.json file within this package.
"""
# check the default file path
assert os.path.isfile(str(file_path)), \
ValueError('No file found at {}'.format(file_path))
# set the default paths to be all blank
default_path = {
"therm_path": r'',
"fortran_dll_path": r'',
"lbnl_data_path": r'',
"therm_lib_path": r''
}
with open(file_path, 'r') as cfg:
try:
paths = json.load(cfg)
except Exception as e:
print('Failed to load paths from {}.\n{}'.format(file_path, e))
else:
for key, p in paths.items():
if not key.startswith('__') and p.strip():
default_path[key] = p.strip()
# set paths for therm installations
self.therm_path = default_path["therm_path"]
self.fortran_dll_path = default_path["fortran_dll_path"]
self.lbnl_data_path = default_path["lbnl_data_path"]
self.therm_lib_path = default_path["therm_lib_path"]
@staticmethod
def _find_therm_folder():
"""Find the Therm installation in its default location."""
# first check if there's a version installed in the ladybug_tools folder
# note that this option is not likely to be used because of the THERM license
lb_install = lb_config.folders.ladybug_tools_folder
thm_path = None
if os.path.isdir(lb_install):
test_path = os.path.join(lb_install, 'THERM')
thm_path = test_path if os.path.isdir(test_path) else None
def getversion(therm_path):
"""Get digits for the version of Version."""
try:
ver = ''.join(s for s in therm_path if (s.isdigit() or s == '.'))
return sum(int(d) * (10 ** i)
for i, d in enumerate(reversed(ver.split('.'))))
except ValueError: # folder starting with 'THERM' and no version
return 0
# then check for the default location where standalone Therm is installed
if thm_path is None and os.name == 'nt': # search the C:/ drive on Windows
major, minor, _, _ = Folders.THERM_VERSION
lbnl_install_dir = 'C:/Program Files (x86)/lbnl'
test_path = '{}/THERM{}.{}'.format(lbnl_install_dir, major, minor)
if os.path.isdir(test_path):
thm_path = test_path
elif os.path.isdir(lbnl_install_dir):
therm_folders = []
for f in os.listdir(lbnl_install_dir):
f_path = os.path.join(lbnl_install_dir, f)
if f.lower().startswith('therm') and os.path.isdir(f_path):
therm_folders.append(f_path)
if len(therm_folders) != 0:
thm_path = sorted(therm_folders, key=getversion, reverse=True)[0]
return thm_path
@staticmethod
def _find_fortran_dll():
"""Find the folder to the Fortran DLLs in its default location."""
# check for the default location in Program Files (x86)
if os.name == 'nt': # search the C:/ drive on Windows
dll_dir = 'C:/Program Files (x86)/Common Files/Intel/Shared Libraries/ia32'
if os.path.isdir(dll_dir):
return dll_dir
@staticmethod
def _find_lbnl_data_folder():
"""Find the LBNL data folder in its default location."""
# then check the default location where the LBNL installer puts it
lib_folder = None
if os.name == 'nt': # search the C:/ drive on Windows
test_path = 'C:/Users/Public/LBNL/'
if os.path.isdir(test_path):
lib_folder = test_path
return lib_folder
@staticmethod
def _check_therm_settings(path):
"""Check that a settings file exists within the LBNL data folder."""
if not path: # first check that a path exists
return None
major, minor, _, _ = Folders.THERM_VERSION
settings_dir = os.path.join(path, 'Settings')
set_file = os.path.join(settings_dir, 'therm{}.{}.ini'.format(major, minor))
if os.path.isfile(set_file):
return set_file
@staticmethod
def _find_therm_lib():
"""Find the user standards folder in its default location."""
# first check if there's a user-defined folder in AppData
app_folder = os.getenv('APPDATA')
if app_folder is not None:
lib_folder = os.path.join(app_folder, 'ladybug_tools', 'standards', 'therm')
if os.path.isdir(lib_folder):
return lib_folder
# then check next to the Python library
for finder, name, ispkg in pkgutil.iter_modules():
if name == 'fairyfly_therm_standards':
lib_folder = os.path.join(finder.path, name)
if os.path.isdir(lib_folder):
return lib_folder
@staticmethod
def _check_therm_lib_file(path, lib_file):
"""Check that a XML file exists within the LBNL therm library."""
if not path: # first check that a path exists
return None
if os.name == 'nt':
major, minor, _, _ = Folders.THERM_VERSION
lib_path = os.path.join(path, 'THERM{}.{}/lib'.format(major, minor), lib_file)
if os.path.isfile(lib_path):
return lib_path
"""Object possesing all key folders within the configuration."""
folders = Folders(mute=True)