Skip to content

Commit

Permalink
fix(basic): Refactor auto aperture group to have less duplicated code
Browse files Browse the repository at this point in the history
  • Loading branch information
chriswmackey committed Nov 16, 2024
1 parent 6dbeaf5 commit c604adc
Show file tree
Hide file tree
Showing 4 changed files with 8 additions and 126 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "1.8.0",
"version": "1.8.1",
"nickname": "AutoGroup",
"outputs": [
[
Expand Down Expand Up @@ -64,7 +64,7 @@
}
],
"subcategory": "0 :: Basic Properties",
"code": "\nimport os\nimport json\n\ntry: # import honeybee_radiance dependencies\n from ladybug.futil import write_to_file_by_name\nexcept ImportError as e:\n raise ImportError('\\nFailed to import ladybug:\\n\\t{}'.format(e))\n\ntry:\n from honeybee.model import Model\n from honeybee.boundarycondition import Outdoors\n from honeybee.config import folders\nexcept ImportError as e:\n raise ImportError('\\nFailed to import honeybee:\\n\\t{}'.format(e))\n\ntry: # import honeybee_radiance_command dependencies\n from honeybee_radiance_command.oconv import Oconv\nexcept ImportError as e:\n raise ImportError('\\nFailed to import honeybee_radiance_command:\\n\\t{}'.format(e))\n\ntry:\n from honeybee_radiance.config import folders as rad_folders\n from honeybee_radiance.dynamic.multiphase import aperture_view_factor, \\\n aperture_view_factor_postprocess, cluster_view_factor, \\\n cluster_orientation, cluster_output\n from honeybee_radiance.lightsource.sky.skydome import SkyDome\nexcept ImportError as e:\n raise ImportError('\\nFailed to import honeybee_radiance:\\n\\t{}'.format(e))\n\ntry:\n from ladybug_{{cad}}.{{plugin}} import all_required_inputs\nexcept ImportError as e:\n raise ImportError('\\nFailed to import ladybug_{{cad}}:\\n\\t{}'.format(e))\n\n\nif all_required_inputs(ghenv.Component) and _run:\n assert isinstance(_model, Model), \\\n 'Input _model must be a Model. Got {}'.format(type(_model))\n # duplicate model\n model = _model.duplicate()\n\n # set defaults\n room_based = True if _room_based_ is None else _room_based_\n view_factor = False if _view_factor_ is None else _view_factor_\n size = 0.2 if _size_ is None else _size_\n vertical_tolerance = None if vert_tolerance_ is None else vert_tolerance_\n\n # create directory\n folder_dir = os.path.join(folders.default_simulation_folder, 'aperture_groups')\n if not os.path.isdir(folder_dir):\n os.makedirs(folder_dir)\n\n apertures = []\n room_apertures = {}\n # get all room-based apertures with Outdoors boundary condition\n for room in model.rooms:\n for face in room.faces:\n for ap in face.apertures:\n if isinstance(ap.boundary_condition, Outdoors):\n apertures.append(ap)\n if not room.identifier in room_apertures:\n room_apertures[room.identifier] = {}\n if not 'apertures' in room_apertures[room.identifier]:\n room_apertures[room.identifier]['apertures'] = \\\n [ap]\n else:\n room_apertures[room.identifier]['apertures'].append(ap)\n if not 'display_name' in room_apertures[room.identifier]:\n room_apertures[room.identifier]['display_name'] = \\\n room.display_name\n assert len(apertures) != 0, \\\n 'Found no apertures. There should at least be one aperture ' \\\n 'in your model.'\n\n if view_factor:\n # write octree\n model_content, modifier_content = model.to.rad(model, minimal=True)\n scene_file, mat_file = 'scene.rad', 'scene.mat'\n write_to_file_by_name(folder_dir, scene_file, model_content)\n write_to_file_by_name(folder_dir, mat_file, modifier_content)\n \n octree = 'scene.oct'\n oconv = Oconv(inputs=[mat_file, scene_file], output=octree)\n oconv.options.f = True\n \n # run Oconv command\n env = None\n if rad_folders.env != {}:\n env = rad_folders.env\n env = dict(os.environ, **env) if env else None\n oconv.run(env, cwd=folder_dir)\n \n rflux_sky = SkyDome()\n rflux_sky = rflux_sky.to_file(folder_dir, name='rflux_sky.sky')\n \n # calculate view factor\n mtx_file, ap_dict = aperture_view_factor(\n folder_dir, apertures, size=size, ambient_division=1000,\n receiver=rflux_sky, octree=octree, calc_folder=folder_dir\n )\n rmse = aperture_view_factor_postprocess(\n mtx_file, ap_dict, room_apertures, room_based\n )\n\n # cluster apertures into groups\n if view_factor:\n ap_groups = cluster_view_factor(\n rmse, room_apertures, apertures, 0.001, room_based, vertical_tolerance)\n else:\n ap_groups = cluster_orientation(\n room_apertures, apertures, room_based, vertical_tolerance\n )\n\n # process clusters\n group_names, group_dict = \\\n cluster_output(ap_groups, room_apertures, room_based)\n\n # write aperture groups to JSON file\n dyn_gr = os.path.join(folder_dir, 'aperture_groups.json')\n with open(dyn_gr, 'w') as fp:\n json.dump(group_names, fp, indent=2)\n\n # write dynamic group identifiers to JSON file\n dyn_gr_ids = os.path.join(folder_dir, 'dynamic_group_identifiers.json')\n with open(dyn_gr_ids, 'w') as fp:\n json.dump(group_dict, fp, indent=2)\n\n # assign dynamic group identifiers for each aperture\n group_ap_dict = {}\n for room in model.rooms:\n for face in room.faces:\n for ap in face.apertures:\n if isinstance(ap.boundary_condition, Outdoors):\n dyn_group_id = group_dict[ap.identifier]\n ap.properties.radiance.dynamic_group_identifier = \\\n dyn_group_id\n try:\n group_ap_dict[dyn_group_id].append(ap)\n except KeyError:\n group_ap_dict[dyn_group_id] = [ap]\n\n # assign any states if they are connected\n if len(states_) != 0:\n for group_aps in group_ap_dict.values():\n # assign states (including shades) to the first aperture\n group_aps[0].properties.radiance.states = [state.duplicate() for state in states_]\n # remove shades from following apertures to ensure they aren't double-counted\n states_wo_shades = []\n for state in states_:\n new_state = state.duplicate()\n new_state.remove_shades()\n states_wo_shades.append(new_state)\n for ap in group_aps[1:]:\n ap.properties.radiance.states = \\\n [state.duplicate() for state in states_wo_shades]\n",
"code": "\ntry:\n from honeybee.model import Model\nexcept ImportError as e:\n raise ImportError('\\nFailed to import honeybee:\\n\\t{}'.format(e))\n\ntry: # import honeybee_radiance_command dependencies\n from honeybee_radiance_command.oconv import Oconv\nexcept ImportError as e:\n raise ImportError('\\nFailed to import honeybee_radiance_command:\\n\\t{}'.format(e))\n\ntry:\n from honeybee_radiance.dynamic.multiphase import automatic_aperture_grouping\nexcept ImportError as e:\n raise ImportError('\\nFailed to import honeybee_radiance:\\n\\t{}'.format(e))\n\ntry:\n from ladybug_{{cad}}.{{plugin}} import all_required_inputs\nexcept ImportError as e:\n raise ImportError('\\nFailed to import ladybug_{{cad}}:\\n\\t{}'.format(e))\n\n\nif all_required_inputs(ghenv.Component) and _run:\n assert isinstance(_model, Model), \\\n 'Input _model must be a Model. Got {}'.format(type(_model))\n # duplicate model\n model = _model.duplicate()\n\n # set defaults\n room_based = True if _room_based_ is None else _room_based_\n view_factor = False if _view_factor_ is None else _view_factor_\n size = 0.2 if _size_ is None else _size_\n vertical_tolerance = None if vert_tolerance_ is None else vert_tolerance_\n\n # automatically assign groups\n automatic_aperture_grouping(\n model, size=size, room_based=room_based, view_factor_or_orientation=view_factor,\n vertical_tolerance=vertical_tolerance, states=states_)\n",
"category": "HB-Radiance",
"name": "HB Automatic Aperture Group",
"description": "Calculate Aperture groups for exterior Apertures.\n_\nThe Apertures are grouped by orientation unless _view_factor_ is set to True.\n_\nIf grouping based on view factor the component calculates view factor from\nApertures to sky patches (rfluxmtx). Each Aperture is represented by a sensor\ngrid, and the view factor for the whole Aperture is the average of the grid. The\nRMSE of the view factor to each sky patch is calculated between all Apertures.\nAgglomerative hierarchical clustering (with complete-linkage method) is used to\ngroup the Apertures by using a distance matrix of the RMSE values.\nThe view factor approach is Radiance-based (and slower) and will likely group\nApertures more accurately considering the context geometry of the Honeybee\nModel.\n-"
Expand Down
130 changes: 6 additions & 124 deletions honeybee_grasshopper_radiance/src/HB Automatic Aperture Group.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,23 +56,13 @@

ghenv.Component.Name = 'HB Automatic Aperture Group'
ghenv.Component.NickName = 'AutoGroup'
ghenv.Component.Message = '1.8.0'
ghenv.Component.Message = '1.8.1'
ghenv.Component.Category = 'HB-Radiance'
ghenv.Component.SubCategory = '0 :: Basic Properties'
ghenv.Component.AdditionalHelpFromDocStrings = '2'

import os
import json

try: # import honeybee_radiance dependencies
from ladybug.futil import write_to_file_by_name
except ImportError as e:
raise ImportError('\nFailed to import ladybug:\n\t{}'.format(e))

try:
from honeybee.model import Model
from honeybee.boundarycondition import Outdoors
from honeybee.config import folders
except ImportError as e:
raise ImportError('\nFailed to import honeybee:\n\t{}'.format(e))

Expand All @@ -82,11 +72,7 @@
raise ImportError('\nFailed to import honeybee_radiance_command:\n\t{}'.format(e))

try:
from honeybee_radiance.config import folders as rad_folders
from honeybee_radiance.dynamic.multiphase import aperture_view_factor, \
aperture_view_factor_postprocess, cluster_view_factor, \
cluster_orientation, cluster_output
from honeybee_radiance.lightsource.sky.skydome import SkyDome
from honeybee_radiance.dynamic.multiphase import automatic_aperture_grouping
except ImportError as e:
raise ImportError('\nFailed to import honeybee_radiance:\n\t{}'.format(e))

Expand All @@ -108,111 +94,7 @@
size = 0.2 if _size_ is None else _size_
vertical_tolerance = None if vert_tolerance_ is None else vert_tolerance_

# create directory
folder_dir = os.path.join(folders.default_simulation_folder, 'aperture_groups')
if not os.path.isdir(folder_dir):
os.makedirs(folder_dir)

apertures = []
room_apertures = {}
# get all room-based apertures with Outdoors boundary condition
for room in model.rooms:
for face in room.faces:
for ap in face.apertures:
if isinstance(ap.boundary_condition, Outdoors):
apertures.append(ap)
if not room.identifier in room_apertures:
room_apertures[room.identifier] = {}
if not 'apertures' in room_apertures[room.identifier]:
room_apertures[room.identifier]['apertures'] = \
[ap]
else:
room_apertures[room.identifier]['apertures'].append(ap)
if not 'display_name' in room_apertures[room.identifier]:
room_apertures[room.identifier]['display_name'] = \
room.display_name
assert len(apertures) != 0, \
'Found no apertures. There should at least be one aperture ' \
'in your model.'

if view_factor:
# write octree
model_content, modifier_content = model.to.rad(model, minimal=True)
scene_file, mat_file = 'scene.rad', 'scene.mat'
write_to_file_by_name(folder_dir, scene_file, model_content)
write_to_file_by_name(folder_dir, mat_file, modifier_content)

octree = 'scene.oct'
oconv = Oconv(inputs=[mat_file, scene_file], output=octree)
oconv.options.f = True

# run Oconv command
env = None
if rad_folders.env != {}:
env = rad_folders.env
env = dict(os.environ, **env) if env else None
oconv.run(env, cwd=folder_dir)

rflux_sky = SkyDome()
rflux_sky = rflux_sky.to_file(folder_dir, name='rflux_sky.sky')

# calculate view factor
mtx_file, ap_dict = aperture_view_factor(
folder_dir, apertures, size=size, ambient_division=1000,
receiver=rflux_sky, octree=octree, calc_folder=folder_dir
)
rmse = aperture_view_factor_postprocess(
mtx_file, ap_dict, room_apertures, room_based
)

# cluster apertures into groups
if view_factor:
ap_groups = cluster_view_factor(
rmse, room_apertures, apertures, 0.001, room_based, vertical_tolerance)
else:
ap_groups = cluster_orientation(
room_apertures, apertures, room_based, vertical_tolerance
)

# process clusters
group_names, group_dict = \
cluster_output(ap_groups, room_apertures, room_based)

# write aperture groups to JSON file
dyn_gr = os.path.join(folder_dir, 'aperture_groups.json')
with open(dyn_gr, 'w') as fp:
json.dump(group_names, fp, indent=2)

# write dynamic group identifiers to JSON file
dyn_gr_ids = os.path.join(folder_dir, 'dynamic_group_identifiers.json')
with open(dyn_gr_ids, 'w') as fp:
json.dump(group_dict, fp, indent=2)

# assign dynamic group identifiers for each aperture
group_ap_dict = {}
for room in model.rooms:
for face in room.faces:
for ap in face.apertures:
if isinstance(ap.boundary_condition, Outdoors):
dyn_group_id = group_dict[ap.identifier]
ap.properties.radiance.dynamic_group_identifier = \
dyn_group_id
try:
group_ap_dict[dyn_group_id].append(ap)
except KeyError:
group_ap_dict[dyn_group_id] = [ap]

# assign any states if they are connected
if len(states_) != 0:
for group_aps in group_ap_dict.values():
# assign states (including shades) to the first aperture
group_aps[0].properties.radiance.states = [state.duplicate() for state in states_]
# remove shades from following apertures to ensure they aren't double-counted
states_wo_shades = []
for state in states_:
new_state = state.duplicate()
new_state.remove_shades()
states_wo_shades.append(new_state)
for ap in group_aps[1:]:
ap.properties.radiance.states = \
[state.duplicate() for state in states_wo_shades]
# automatically assign groups
automatic_aperture_grouping(
model, size=size, room_based=room_based, view_factor_or_orientation=view_factor,
vertical_tolerance=vertical_tolerance, states=states_)
Binary file not shown.
Binary file modified samples/auto_dynamic_apertures.gh
Binary file not shown.

0 comments on commit c604adc

Please sign in to comment.