Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/master'
Browse files Browse the repository at this point in the history
  • Loading branch information
mikkelkp committed Sep 16, 2021
2 parents ca456b2 + 0f1624c commit 292351a
Show file tree
Hide file tree
Showing 26 changed files with 418 additions and 211 deletions.
14 changes: 14 additions & 0 deletions .github/workflows/ci.yaml
Expand Up @@ -77,6 +77,10 @@ jobs:
PYPI_USERNAME: ${{ secrets.PYPI_USERNAME }}
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
- name: sleep for 5 minutes for PyPI update
if: contains(steps.new_release.outputs.tag, '.')
run: sleep 300s
shell: bash
- name: Update lbt-honeybee
if: contains(steps.new_release.outputs.tag, '.')
env:
Expand All @@ -87,6 +91,16 @@ jobs:
-H "Accept: application/vnd.github.everest-preview+json" \
-d '{"event_type": "honeybee_radiance_release", "client_payload": {"version": "${{ steps.new_release.outputs.tag }}"}}' \
-u ladybugbot:$DEPS_TOKEN
- name: Update pollination-honeybee-radiance
if: contains(steps.new_release.outputs.tag, '.')
env:
DISPATCH_REPO: pollination/honeybee-radiance
DEPS_TOKEN: ${{ secrets.DEPS_UPDATING }}
run: |
curl -X POST https://api.github.com/repos/$DISPATCH_REPO/dispatches \
-H "Accept: application/vnd.github.everest-preview+json" \
-d '{"event_type": "honeybee_radiance_release", "client_payload": {"version": "${{ steps.new_release.outputs.tag }}"}}' \
-u ladybugbot:$DEPS_TOKEN
docs:
name: Generate docs
Expand Down
14 changes: 7 additions & 7 deletions honeybee_radiance/cli/dc.py
Expand Up @@ -144,16 +144,16 @@ def rcontrib_command_with_postprocess(

@dc.command('scoeff')
@click.argument(
'octree', type=click.Path(exists=True, file_okay=True, resolve_path=True)
'octree', type=click.Path(exists=True, file_okay=True)
)
@click.argument(
'sensor-grid', type=click.Path(exists=True, file_okay=True, resolve_path=True)
'sensor-grid', type=click.Path(exists=True, file_okay=True)
)
@click.argument(
'sky-dome', type=click.Path(exists=True, file_okay=True, resolve_path=True)
'sky-dome', type=click.Path(exists=True, file_okay=True)
)
@click.argument(
'sky-mtx', type=click.Path(exists=True, file_okay=True, resolve_path=True)
'sky-mtx', type=click.Path(exists=True, file_okay=True)
)
@click.option(
'--sensor-count', type=click.INT, show_default=True,
Expand Down Expand Up @@ -228,8 +228,8 @@ def rfluxmtx_command_with_postprocess(
options.update_from_string('-aa 0.0 -faf -y {}'.format(sensor_count))

# create command.
cmd_template = 'rfluxmtx {rad_params} - {sky_dome} -i {octree} < ' \
'{sensors} | rmtxop -f{output_format} - {sky_mtx}'
cmd_template = 'rfluxmtx {rad_params} - "{sky_dome}" -i "{octree}" < ' \
'"{sensors}" | rmtxop -f{output_format} - "{sky_mtx}"'

if conversion and conversion.strip():
if multiply_by != 1:
Expand All @@ -243,7 +243,7 @@ def rfluxmtx_command_with_postprocess(
cmd_template = cmd_template + ' -t '

if output:
cmd_template = cmd_template + ' > {output}'.format(output=output)
cmd_template = cmd_template + ' > "{output}"'.format(output=output)

cmd = cmd_template.format(
rad_params=options.to_radiance(), sky_dome=sky_dome, octree=octree,
Expand Down
58 changes: 58 additions & 0 deletions honeybee_radiance/cli/lib.py
@@ -1,13 +1,18 @@
"""honeybee radiance standards library commands."""
import click
import sys
import os
import logging
import json

from honeybee_radiance.config import folders
from honeybee_radiance.lib.modifiers import modifier_by_identifier, MODIFIERS
from honeybee_radiance.lib.modifiersets import modifier_set_by_identifier, \
MODIFIER_SETS

from honeybee_radiance.lib._loadmodifiers import load_modifiers_from_folder
from honeybee_radiance.lib._loadmodifiersets import load_modifiersets_from_folder

_logger = logging.getLogger(__name__)


Expand Down Expand Up @@ -144,3 +149,56 @@ def modifier_sets_by_id(modifier_set_ids, none_defaults, abridged, output_file):
sys.exit(1)
else:
sys.exit(0)


@lib.command('to-model-properties')
@click.option(
'--standards-folder', '-s', default=None, help='A directory containing subfolders '
'of resource objects (modifiers, modifiersets) to be loaded as '
'ModelRadianceProperties. Note that this standards folder MUST contain these '
'subfolders. Each sub-folder can contain JSON files of objects following '
'honeybee schema or RAD/MAT files (if appropriate). If None, the honeybee '
'default standards folder will be used.',type=click.Path(
exists=True, file_okay=False, dir_okay=True, resolve_path=True)
)
@click.option(
'--output-file', '-f', help='Optional JSON file to output the JSON string of '
'the translation. By default this will be printed out to stdout',
type=click.File('w'), default='-', show_default=True
)
def to_model_properties(standards_folder, output_file):
"""Translate a lib folder of standards to a JSON of honeybee ModelRadianceProperties.
This is useful in workflows where one must import everything within a user's
standards folder and requires all objects to be in a consistent format.
All objects in the resulting ModelRadianceProperties will be abridged and
duplicated objects in the folder will be removed such that there
is only one of each object.
"""
try:
# set the folder to the default standards_folder if unspecified
folder = standards_folder if standards_folder is not None else \
folders.standards_data_folder

# load modifiers from the standards folder
mod_folder = os.path.join(folder, 'modifiers')
all_m = load_modifiers_from_folder(mod_folder)

# load modifier sets from the standards folder
mod_set_folder = os.path.join(folder, 'modifiersets')
all_mod_sets, misc_m = load_modifiersets_from_folder(mod_set_folder, all_m)
all_mods = set(list(all_m.values()) + misc_m)

# add all object dictionaries into one object
base = {'type': 'ModelRadianceProperties'}
base['modifiers'] = [m.to_dict() for m in all_mods]
base['modifier_sets'] = \
[ms.to_dict(abridged=True) for ms in all_mod_sets.values()]

# write out the JSON file
output_file.write(json.dumps(base))
except Exception as e:
_logger.exception('Loading standards to properties failed.\n{}'.format(e))
sys.exit(1)
else:
sys.exit(0)
7 changes: 6 additions & 1 deletion honeybee_radiance/cli/postprocess.py
Expand Up @@ -313,6 +313,11 @@ def annual_irradiance(folder, wea, timestep, sub_folder):
grids = [g.replace('.ill', '') for g in os.listdir(folder) if g.endswith('.ill')]
grid_info = os.path.join(folder, 'grids_info.json')

# write a record of the timestep into the result folder for result processing
t_step_f = os.path.join(folder, 'timestep.txt')
with open(t_step_f, 'w') as t_f:
t_f.write(str(timestep))

# setup the folder into which the metrics will be written
metrics_folder = os.path.join(folder, sub_folder)
metrics_folders = []
Expand Down Expand Up @@ -348,7 +353,7 @@ def annual_irradiance(folder, wea, timestep, sub_folder):
avg_i.write('{}\n'.format(total_val / wea_len))
cml_r.write('{}\n'.format(total_val / (timestep * 1000)))
except ValueError:
pass # last line of the file
pass # last line of the file
except Exception:
_logger.exception('Failed to compute irradiance metrics.')
sys.exit(1)
Expand Down
16 changes: 11 additions & 5 deletions honeybee_radiance/cli/sky.py
Expand Up @@ -151,17 +151,20 @@ def sky_climate_based(
@click.argument('irrad', default=558.659, type=float)
@click.option('--ground', '-g', type=float, default=0.2, show_default=True,
help='Fractional value for ground reflectance.')
@click.option('--cloudy/--uniform', '-u', default=True,
help='Flag to note whether the sky is uniform instead of cloudy.')
@click.option('--folder', help='Output folder.', default='.', show_default=True)
@click.option('--name', help='Sky file name.', default=None, show_default=True)
def sky_with_certain_irrad(irrad, ground, folder, name):
def sky_with_certain_irrad(irrad, ground, cloudy, folder, name):
"""Generate an overcast / cloudy sky with certain irradiance value.
\b
Args:
irrad: Desired irradiance value in W/m2. (Default: 558.659).
"""
try:
c_sky = hbsky.CertainIrradiance(irrad, ground)
uniform = not cloudy
c_sky = hbsky.CertainIrradiance(irrad, ground, uniform)
c_sky.to_file(folder, name, True)
except Exception:
_logger.exception('Failed to generate sky.')
Expand All @@ -172,17 +175,20 @@ def sky_with_certain_irrad(irrad, ground, folder, name):
@click.argument('illum', default=100000, type=float)
@click.option('--ground', '-g', type=float, default=0.2, show_default=True,
help='Fractional value for ground reflectance.')
@click.option('--cloudy/--uniform', '-u', default=True,
help='Flag to note whether the sky is uniform instead of cloudy.')
@click.option('--folder', help='Output folder.', default='.', show_default=True)
@click.option('--name', help='Sky file name.', default=None, show_default=True)
def sky_with_certain_illum(illum, ground, folder, name):
def sky_with_certain_illum(illum, ground, cloudy, folder, name):
"""Generate an overcast / cloudy sky with certain illuminance value.
\b
Args:
illum: Desired illuminance value in lux. (Default: 100000).
"""
try:
c_sky = hbsky.CertainIrradiance.from_illuminance(illum, ground)
uniform = not cloudy
c_sky = hbsky.CertainIrradiance.from_illuminance(illum, ground, uniform)
c_sky.to_file(folder, name, True)
except Exception:
_logger.exception('Failed to generate sky.')
Expand Down Expand Up @@ -217,7 +223,7 @@ def sky_dome(sky_density, folder, name):
@click.option('--folder', help='Output folder.', default='.', show_default=True)
@click.option('--name', help='Sky file name.', default='uniform_sky', show_default=True)
def uniform_sky(ground_emittance, folder, name):
"""Virtual skydome for daylight coefficient studies with constant radiance.
"""Virtual skydome with uniform characteristics.
This sky is usually used to create an octree that is sent to rcontrib command.
"""
Expand Down
27 changes: 20 additions & 7 deletions honeybee_radiance/cli/view.py
Expand Up @@ -42,6 +42,14 @@ def view():
'consuming in situations with very high numbers of rendering multiprocessors.',
default=True, show_default=True
)
@click.option(
'--resolution', '-r', default=None, type=int, show_default=True,
help='An optional integer for the maximum dimension of the image in pixels. '
'Specifying a value here will automatically lower the input --count to ensure '
'the resulting images can be combined to meet this dimension. If unspecified, '
'the --count will always be respected and the resulting images might not be '
'combine-able to meet a specific target dimension.'
)
@click.option(
'--octree', '-oct', help='Octree file for the overture calculation. This must be '
'specified when the overture is not skipped.', default=None, show_default=True,
Expand All @@ -60,7 +68,8 @@ def view():
' created views. By default the list will be printed out to stdout',
type=click.File('w'), default='-'
)
def split_view(view, count, skip_overture, octree, rad_params, folder, log_file):
def split_view(view, count, resolution, skip_overture, octree, rad_params,
folder, log_file):
"""Split a radiance view file into smaller views based on count.
\b
Expand All @@ -72,6 +81,11 @@ def split_view(view, count, skip_overture, octree, rad_params, folder, log_file)
the first three files will have 5 sensors and the last file will have 6.
"""
try:
# correct the count to meet the target resolution
if resolution is not None and resolution % count != 0:
while resolution % count != 0:
count = count - 1

# split the view into smaller views
view_obj = View.from_file(view)
views = view_obj.grid(y_div_count=count)
Expand All @@ -88,7 +102,7 @@ def split_view(view, count, skip_overture, octree, rad_params, folder, log_file)
})

# create the ambient cache file if specified
amb_file = os.path.basename(view).replace('.vf', '.amb')
amb_file = os.path.join(folder, os.path.basename(view).replace('.vf', '.amb'))
if not skip_overture:
options = RpictOptions()
if rad_params:
Expand All @@ -101,16 +115,15 @@ def split_view(view, count, skip_overture, octree, rad_params, folder, log_file)
# create command and run it to get the .amb file
assert octree is not None, \
'Octree must be specified for an overture calculation.'
out_file = os.path.basename(view).replace('.vf', '.unf')
out_file = os.path.join(
folder, os.path.basename(view).replace('.vf', '.unf'))
rpict = Rpict(options=options, output=out_file, octree=octree, view=view)
env = None
if folders.env != {}:
env = folders.env
env = dict(os.environ, **env) if env else None
rpict.run(env=env, cwd=folder)
os.remove(os.path.join(folder, out_file))
else: # write a dummy ambient file so that queenbee does not crash
write_to_file_by_name(folder, amb_file, '')
rpict.run(env=env)
os.remove(out_file)

# record all of the view files that were generated
log_file.write(json.dumps(views_info))
Expand Down
3 changes: 1 addition & 2 deletions honeybee_radiance/config.py
Expand Up @@ -264,8 +264,7 @@ def _find_radiance_folder():
"""Find the Radiance installation in its default location.
This method will first attempt to return the path of a standalone Radiance
installation and, if none are found, it will search for one that is
installed with OpenStudio.
installation.
Returns:
File directory and full path to executable in case of success.
Expand Down
58 changes: 35 additions & 23 deletions honeybee_radiance/lib/_loadmodifiers.py
Expand Up @@ -23,37 +23,49 @@


# then load modifiers from the user-supplied files
def load_modifier_object(mod_dict):
def load_modifier_object(mod_dict, user_modifiers):
"""Load a modifier object from a dictionary and add it to the library dict."""
try:
m_class = modifier_class_from_type_string(mod_dict['type'])
mod = m_class.from_dict(mod_dict)
mod.lock()
assert mod_dict['identifier'] not in _default_mods, 'Cannot overwrite ' \
'default modifier "{}".'.format(mod_dict['identifier'])
_loaded_modifiers[mod_dict['identifier']] = mod
user_modifiers[mod_dict['identifier']] = mod
except (TypeError, KeyError):
pass # not a Honeybee Modifier JSON; possibly a comment


for f in os.listdir(folders.modifier_lib):
f_path = os.path.join(folders.modifier_lib, f)
if os.path.isfile(f_path):
if f_path.endswith('.mat') or f_path.endswith('.rad'):
with open(f_path) as f:
try:
rad_dicts = string_to_dicts(f.read())
for mod_dict in rad_dicts:
mod = dict_to_modifier(mod_dict)
mod.lock()
_loaded_modifiers[mod.identifier] = mod
except ValueError:
pass # empty rad file with no modifiers in them
if f_path.endswith('.json'):
with open(f_path) as json_file:
data = json.load(json_file)
if 'type' in data: # single object
load_modifier_object(data)
else: # a collection of several objects
for mod_identifier in data:
load_modifier_object(data[mod_identifier])
def load_modifiers_from_folder(modifier_lib_folder):
"""Load all of the material layer objects from a modifier standards folder.
Args:
modifier_lib_folder: Path to a modifiers sub-folder within a
honeybee standards folder.
"""
user_modifiers = {}
for f in os.listdir(modifier_lib_folder):
f_path = os.path.join(modifier_lib_folder, f)
if os.path.isfile(f_path):
if f_path.endswith('.mat') or f_path.endswith('.rad'):
with open(f_path) as f:
try:
rad_dicts = string_to_dicts(f.read())
for mod_dict in rad_dicts:
mod = dict_to_modifier(mod_dict)
mod.lock()
user_modifiers[mod.identifier] = mod
except ValueError:
pass # empty rad file with no modifiers in them
if f_path.endswith('.json'):
with open(f_path) as json_file:
data = json.load(json_file)
if 'type' in data: # single object
load_modifier_object(data, user_modifiers)
else: # a collection of several objects
for mod_identifier in data:
load_modifier_object(data[mod_identifier], user_modifiers)
return user_modifiers

user_mods = load_modifiers_from_folder(folders.modifier_lib)
_loaded_modifiers.update(user_mods)

0 comments on commit 292351a

Please sign in to comment.