diff --git a/honeybee_grasshopper_radiance/icon/HB Annual Daylight Metrics.png b/honeybee_grasshopper_radiance/icon/HB Annual Daylight Metrics.png index 6f2bc91..60d7c1f 100644 Binary files a/honeybee_grasshopper_radiance/icon/HB Annual Daylight Metrics.png and b/honeybee_grasshopper_radiance/icon/HB Annual Daylight Metrics.png differ diff --git a/honeybee_grasshopper_radiance/icon/HB Aperture Group Schedule.png b/honeybee_grasshopper_radiance/icon/HB Aperture Group Schedule.png new file mode 100644 index 0000000..3c5c97c Binary files /dev/null and b/honeybee_grasshopper_radiance/icon/HB Aperture Group Schedule.png differ diff --git a/honeybee_grasshopper_radiance/icon/HB Get Dynamic Groups.png b/honeybee_grasshopper_radiance/icon/HB Get Dynamic Groups.png new file mode 100644 index 0000000..843de18 Binary files /dev/null and b/honeybee_grasshopper_radiance/icon/HB Get Dynamic Groups.png differ diff --git a/honeybee_grasshopper_radiance/json/HB_Annual_Daylight_Metrics.json b/honeybee_grasshopper_radiance/json/HB_Annual_Daylight_Metrics.json index b4e14c9..4c3bb73 100644 --- a/honeybee_grasshopper_radiance/json/HB_Annual_Daylight_Metrics.json +++ b/honeybee_grasshopper_radiance/json/HB_Annual_Daylight_Metrics.json @@ -1,5 +1,5 @@ { - "version": "1.6.0", + "version": "1.6.1", "nickname": "DaylightMetrics", "outputs": [ [ @@ -48,6 +48,13 @@ "type": "string", "default": null }, + { + "access": "list", + "name": "dyn_sch_", + "description": "Optional dynamic Aperture Group Schedules from the \"HB Aperture Group\nSchedule\" component, which will be used to customize the behavior\nof any dyanmic aperture geometry in the output metrics. If unsupplied,\nall dynamic aperture groups will be in their default state in for\nthe output metrics.", + "type": "System.Object", + "default": null + }, { "access": "item", "name": "_occ_sch_", @@ -78,7 +85,7 @@ } ], "subcategory": "4 :: Results", - "code": "\nimport os\nimport subprocess\n\ntry:\n from ladybug.datacollection import BaseCollection\n from ladybug.futil import write_to_file\nexcept ImportError as e:\n raise ImportError('\\nFailed to import ladybug:\\n\\t{}'.format(e))\n\ntry:\n from honeybee.config import folders\nexcept ImportError as e:\n raise ImportError('\\nFailed to import honeybee:\\n\\t{}'.format(e))\n\ntry:\n from honeybee_radiance.postprocess.annualdaylight import metrics_from_folder\nexcept ImportError as e:\n raise ImportError('\\nFailed to import honeybee_radiance:\\n\\t{}'.format(e))\n\ntry:\n from honeybee_energy.lib.schedules import schedule_by_identifier\nexcept ImportError as e: # honeybee schedule library is not available\n schedule_by_identifier = None\n\ntry:\n from pollination_handlers.outputs.daylight import read_da_from_folder, \\\n read_cda_from_folder, read_udi_from_folder\nexcept ImportError as e:\n raise ImportError('\\nFailed to import pollination_handlers:\\n\\t{}'.format(e))\n\ntry:\n from ladybug_{{cad}}.{{plugin}} import all_required_inputs, list_to_data_tree\nexcept ImportError as e:\n raise ImportError('\\nFailed to import ladybug_{{cad}}:\\n\\t{}'.format(e))\n\n\nif all_required_inputs(ghenv.Component):\n # set default values for the thresholds and the grid filter\n grid_filter_ = '*' if grid_filter_ is None else grid_filter_\n _threshold_ = _threshold_ if _threshold_ else 300\n if len(_min_max_) != 0:\n assert len(_min_max_), 'Expected two values for _min_max_.'\n min_t = _min_max_[0]\n max_t = _min_max_[1]\n else:\n min_t = 100\n max_t = 3000\n\n # process the schedule\n if _occ_sch_ is None:\n schedule = None\n elif isinstance(_occ_sch_, BaseCollection):\n schedule = _occ_sch_.values\n elif isinstance(_occ_sch_, str):\n if schedule_by_identifier is not None:\n try:\n schedule = schedule_by_identifier(_occ_sch_).values()\n except TypeError: # it's probably a ScheduleFixedInterval\n schedule = schedule_by_identifier(_occ_sch_).values\n else:\n raise ValueError('honeybee-energy must be installed to reference '\n 'occupancy schedules by identifier.')\n else: # assume that it is a honeybee schedule object\n try:\n schedule = _occ_sch_.values()\n except TypeError: # it's probably a ScheduleFixedInterval\n schedule = _occ_sch_.values\n if schedule is not None:\n bin_schedule = []\n for val in schedule:\n bin_val = 1 if val >= 0.1 else 0\n bin_schedule.append(bin_val)\n schedule = bin_schedule\n\n # compute the annual metrics\n res_folder = os.path.dirname(_results[0]) if os.path.isfile(_results[0]) \\\n else _results[0]\n if os.path.isdir(os.path.join(res_folder, '__static_apertures__')):\n cmds = [\n folders.python_exe_path, '-m', 'honeybee_radiance_postprocess',\n 'post-process', 'annual-daylight', res_folder, '-sf', 'metrics',\n '-t', str(_threshold_), '-lt', str(min_t), '-ut', str(max_t)\n ]\n if grid_filter_ != '*':\n cmds.extend(['--grids-filter', grid_filter_])\n if schedule is not None:\n sch_str = '\\n'.join(str(h) for h in schedule)\n sch_file = os.path.join(res_folder, 'schedule.txt')\n write_to_file(sch_file, sch_str)\n cmds.extend(['--schedule', sch_file])\n use_shell = True if os.name == 'nt' else False\n process = subprocess.Popen(\n cmds, cwd=res_folder, shell=use_shell,\n stdout=subprocess.PIPE, stderr=subprocess.PIPE)\n stdout = process.communicate() # wait for the process to finish\n if stdout[-1] != '':\n print(stdout[-1])\n raise ValueError('Failed to compute annual daylight metrics.')\n metric_dir = os.path.join(res_folder, 'metrics')\n DA = list_to_data_tree(read_da_from_folder(os.path.join(metric_dir, 'da')))\n cDA = list_to_data_tree(read_cda_from_folder(os.path.join(metric_dir, 'cda')))\n UDI = list_to_data_tree(read_udi_from_folder(os.path.join(metric_dir, 'udi')))\n UDI_low = list_to_data_tree(read_udi_from_folder(os.path.join(metric_dir, 'udi_lower')))\n UDI_up = list_to_data_tree(read_udi_from_folder(os.path.join(metric_dir, 'udi_upper')))\n else:\n DA, cDA, UDI_low, UDI, UDI_up = metrics_from_folder(\n res_folder, schedule, _threshold_, min_t, max_t, grid_filter_)\n DA = list_to_data_tree(DA)\n cDA = list_to_data_tree(cDA)\n UDI = list_to_data_tree(UDI)\n UDI_low = list_to_data_tree(UDI_low)\n UDI_up = list_to_data_tree(UDI_up)\n", + "code": "\nimport os\nimport subprocess\n\ntry:\n from ladybug.datacollection import BaseCollection\n from ladybug.futil import write_to_file\nexcept ImportError as e:\n raise ImportError('\\nFailed to import ladybug:\\n\\t{}'.format(e))\n\ntry:\n from honeybee.config import folders\nexcept ImportError as e:\n raise ImportError('\\nFailed to import honeybee:\\n\\t{}'.format(e))\n\ntry:\n from honeybee_radiance.postprocess.annualdaylight import metrics_from_folder\nexcept ImportError as e:\n raise ImportError('\\nFailed to import honeybee_radiance:\\n\\t{}'.format(e))\n\ntry:\n from honeybee_radiance_postprocess.dynamic import DynamicSchedule\nexcept ImportError as e:\n raise ImportError('\\nFailed to import honeybee_radiance:\\n\\t{}'.format(e))\n\ntry:\n from honeybee_energy.lib.schedules import schedule_by_identifier\nexcept ImportError as e: # honeybee schedule library is not available\n schedule_by_identifier = None\n\ntry:\n from pollination_handlers.outputs.daylight import read_da_from_folder, \\\n read_cda_from_folder, read_udi_from_folder\nexcept ImportError as e:\n raise ImportError('\\nFailed to import pollination_handlers:\\n\\t{}'.format(e))\n\ntry:\n from ladybug_{{cad}}.{{plugin}} import all_required_inputs, list_to_data_tree, \\\n give_warning\nexcept ImportError as e:\n raise ImportError('\\nFailed to import ladybug_{{cad}}:\\n\\t{}'.format(e))\n\n\nif all_required_inputs(ghenv.Component):\n # set default values for the thresholds and the grid filter\n grid_filter_ = '*' if grid_filter_ is None else grid_filter_\n _threshold_ = _threshold_ if _threshold_ else 300\n if len(_min_max_) != 0:\n assert len(_min_max_), 'Expected two values for _min_max_.'\n min_t = _min_max_[0]\n max_t = _min_max_[1]\n else:\n min_t = 100\n max_t = 3000\n\n # process the schedule\n if _occ_sch_ is None:\n schedule = None\n elif isinstance(_occ_sch_, BaseCollection):\n schedule = _occ_sch_.values\n elif isinstance(_occ_sch_, str):\n if schedule_by_identifier is not None:\n try:\n schedule = schedule_by_identifier(_occ_sch_).values()\n except TypeError: # it's probably a ScheduleFixedInterval\n schedule = schedule_by_identifier(_occ_sch_).values\n else:\n raise ValueError('honeybee-energy must be installed to reference '\n 'occupancy schedules by identifier.')\n else: # assume that it is a honeybee schedule object\n try:\n schedule = _occ_sch_.values()\n except TypeError: # it's probably a ScheduleFixedInterval\n schedule = _occ_sch_.values\n if schedule is not None:\n bin_schedule = []\n for val in schedule:\n bin_val = 1 if val >= 0.1 else 0\n bin_schedule.append(bin_val)\n schedule = bin_schedule\n\n # compute the annual metrics\n res_folder = os.path.dirname(_results[0]) if os.path.isfile(_results[0]) \\\n else _results[0]\n if os.path.isdir(os.path.join(res_folder, '__static_apertures__')) or \\\n os.path.isfile(os.path.join(res_folder, 'grid_states.json')):\n cmds = [\n folders.python_exe_path, '-m', 'honeybee_radiance_postprocess',\n 'post-process', 'annual-daylight', res_folder, '-sf', 'metrics',\n '-t', str(_threshold_), '-lt', str(min_t), '-ut', str(max_t)\n ]\n if grid_filter_ != '*':\n cmds.extend(['--grids-filter', grid_filter_])\n if len(dyn_sch_) != 0:\n dyn_sch = dyn_sch_[0] if isinstance(dyn_sch_[0], DynamicSchedule) else \\\n DynamicSchedule.from_group_schedules(dyn_sch_)\n dyn_sch_file = dyn_sch.to_json(folder=res_folder)\n cmds.extend(['--states', dyn_sch_file])\n if schedule is not None:\n sch_str = '\\n'.join(str(h) for h in schedule)\n sch_file = os.path.join(res_folder, 'schedule.txt')\n write_to_file(sch_file, sch_str)\n cmds.extend(['--schedule', sch_file])\n use_shell = True if os.name == 'nt' else False\n process = subprocess.Popen(\n cmds, cwd=res_folder, shell=use_shell,\n stdout=subprocess.PIPE, stderr=subprocess.PIPE)\n stdout = process.communicate() # wait for the process to finish\n if stdout[-1] != '':\n print(stdout[-1])\n raise ValueError('Failed to compute annual daylight metrics.')\n metric_dir = os.path.join(res_folder, 'metrics')\n DA = list_to_data_tree(read_da_from_folder(os.path.join(metric_dir, 'da')))\n cDA = list_to_data_tree(read_cda_from_folder(os.path.join(metric_dir, 'cda')))\n UDI = list_to_data_tree(read_udi_from_folder(os.path.join(metric_dir, 'udi')))\n UDI_low = list_to_data_tree(read_udi_from_folder(os.path.join(metric_dir, 'udi_lower')))\n UDI_up = list_to_data_tree(read_udi_from_folder(os.path.join(metric_dir, 'udi_upper')))\n else:\n if len(dyn_sch_) != 0:\n msg = 'Dynamic Schedules are currently only supported for Annual Daylight ' \\\n 'simulations.\\nThe input schedules will be ignored.'\n print(msg)\n give_warning(ghenv.Component, msg)\n DA, cDA, UDI_low, UDI, UDI_up = metrics_from_folder(\n res_folder, schedule, _threshold_, min_t, max_t, grid_filter_)\n DA = list_to_data_tree(DA)\n cDA = list_to_data_tree(cDA)\n UDI = list_to_data_tree(UDI)\n UDI_low = list_to_data_tree(UDI_low)\n UDI_up = list_to_data_tree(UDI_up)\n", "category": "HB-Radiance", "name": "HB Annual Daylight Metrics", "description": "Calculate Annual Daylight Metrics from a result (.ill) files.\n-" diff --git a/honeybee_grasshopper_radiance/json/HB_Aperture_Group_Schedule.json b/honeybee_grasshopper_radiance/json/HB_Aperture_Group_Schedule.json new file mode 100644 index 0000000..362fe67 --- /dev/null +++ b/honeybee_grasshopper_radiance/json/HB_Aperture_Group_Schedule.json @@ -0,0 +1,36 @@ +{ + "version": "1.6.0", + "nickname": "GroupSch", + "outputs": [ + [ + { + "access": "None", + "name": "dyn_sch", + "description": "A dynamic schedule object for the input aperture group, which can be plugged\ninto any of the Results components with a syn_sch input.", + "type": null, + "default": null + } + ] + ], + "inputs": [ + { + "access": "list", + "name": "_group_aps", + "description": "Honeybee Apertures that are a part of the same dynamic group and will\nbe assigned the same schedule for postprocessing. Typically, this is\nthe output of the \"HB Dynamic Aperture Group\" component but it can\nalso be the output of the \"HB Get Dynamic Groups\" component, which\nreturns all of the dynamic groups on a particular Model.", + "type": "System.Object", + "default": null + }, + { + "access": "list", + "name": "_schedule", + "description": "A list of 8760 integers refering to the index of the aperture group state\nto be used at each hour of the simulation. This can also be a single integer\nfor a static state to be used for the entire period of the simulation\nor a pattern of integers that is less than 8760 in length and will be\nrepeated until the 8760 is reached. Note that 0 refers to the first\nstate, 1 refers to the second state, and so on. -1 can be used to\ncompletely discout the aperture from the simulation for a given hour.", + "type": "int", + "default": null + } + ], + "subcategory": "4 :: Results", + "code": "\ntry:\n from honeybee.aperture import Aperture\nexcept ImportError as e:\n raise ImportError('\\nFailed to import honeybee:\\n\\t{}'.format(e))\n\ntry:\n from honeybee_radiance_postprocess.dynamic import ApertureGroupSchedule\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, recipe_result\nexcept ImportError as e:\n raise ImportError('\\nFailed to import ladybug_{{cad}}:\\n\\t{}'.format(e))\n\n\nif all_required_inputs(ghenv.Component):\n dyn_sch = []\n dyn_ids = set()\n for ap in _group_aps:\n assert isinstance(ap, Aperture), 'Expected Aperture. Got {}.'.format(type(ap))\n dyn_grp_id = ap.properties.radiance.dynamic_group_identifier\n if dyn_grp_id is None:\n raise ValueError(\n 'Input Aperture \"{}\" is not a part of a dynamic group.'.format(ap.display_name))\n if dyn_grp_id not in dyn_ids:\n dyn_ids.add(dyn_grp_id)\n _ap_group_sch = ApertureGroupSchedule(dyn_grp_id, _schedule)\n dyn_sch.append(_ap_group_sch)\n", + "category": "HB-Radiance", + "name": "HB Aperture Group Schedule", + "description": "Create a Dynamic Aperture Group Schedule, which can be used to process any dynamic\naperture geometry that was run in an annual simulation.\n-" +} \ No newline at end of file diff --git a/honeybee_grasshopper_radiance/json/HB_Automatic_Aperture_Group.json b/honeybee_grasshopper_radiance/json/HB_Automatic_Aperture_Group.json index 2a44aac..fd9a9a6 100644 --- a/honeybee_grasshopper_radiance/json/HB_Automatic_Aperture_Group.json +++ b/honeybee_grasshopper_radiance/json/HB_Automatic_Aperture_Group.json @@ -1,5 +1,5 @@ { - "version": "1.6.3", + "version": "1.6.4", "nickname": "AutoGroup", "outputs": [ [ @@ -48,6 +48,13 @@ "type": "double", "default": null }, + { + "access": "list", + "name": "states_", + "description": "An optional list of Honeybee State objects to be applied to all the generated groups.\nThese states should be ordered based on how they will be switched on.\nThe first state is the default state and, typically, higher states\nare more shaded. If the objects in the group have no states, the\nmodifiers already assigned the apertures will be used for all states.", + "type": "System.Object", + "default": null + }, { "access": "item", "name": "_run", @@ -57,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 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", + "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", "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-" diff --git a/honeybee_grasshopper_radiance/json/HB_Get_Dynamic_Groups.json b/honeybee_grasshopper_radiance/json/HB_Get_Dynamic_Groups.json new file mode 100644 index 0000000..7e2502a --- /dev/null +++ b/honeybee_grasshopper_radiance/json/HB_Get_Dynamic_Groups.json @@ -0,0 +1,36 @@ +{ + "version": "1.6.0", + "nickname": "GetDyn", + "outputs": [ + [ + { + "access": "None", + "name": "group_ids", + "description": "The identifiers of the dynamic groups assigned to the Model.", + "type": null, + "default": null + }, + { + "access": "None", + "name": "group_aps", + "description": "A data tree of Dynamic Apertures in the Model. Each branch of the\ntree represents a different Dynamic Aperture Group and corresponds to\nthe group_ids above. The data tree can be exploded with the native\nGrasshopper \"Explod Tree\" component to assign schedules to each\nDynamic Group for postprocessing.", + "type": null, + "default": null + } + ] + ], + "inputs": [ + { + "access": "item", + "name": "_model", + "description": "A Honeybee Model for which dynamic groups will be output.", + "type": "System.Object", + "default": null + } + ], + "subcategory": "0 :: Basic Properties", + "code": "\ntry: # import core honeybee dependencies\n from honeybee.model import Model\nexcept ImportError as e:\n raise ImportError('\\nFailed to import honeybee:\\n\\t{}'.format(e))\n\ntry: # import ladybug_{{cad}} dependencies\n from ladybug_{{cad}}.{{plugin}} import all_required_inputs, list_to_data_tree\nexcept ImportError as e:\n raise ImportError('\\nFailed to import ladybug_{{cad}}:\\n\\t{}'.format(e))\n\n\nif all_required_inputs(ghenv.Component):\n assert isinstance(_model, Model), \\\n 'Expected Honeybee Model. Got {}.'.format(type(_model))\n # get the dynamic group objects\n groups = _model.properties.radiance.dynamic_subface_groups\n groups.sort(key=lambda x: x.identifier)\n\n # get the group attributes\n group_ids, group_aps = [], []\n for group in groups:\n group_ids.append([group.identifier])\n group_aps.append(group.dynamic_objects)\n group_ids = list_to_data_tree(group_ids)\n group_aps = list_to_data_tree(group_aps)\n", + "category": "HB-Radiance", + "name": "HB Get Dynamic Groups", + "description": "Get all of the Dynamic Radiance Groups assigned to a Model.\n-" +} \ No newline at end of file diff --git a/honeybee_grasshopper_radiance/src/HB Annual Daylight Metrics.py b/honeybee_grasshopper_radiance/src/HB Annual Daylight Metrics.py index fa04a69..076251c 100644 --- a/honeybee_grasshopper_radiance/src/HB Annual Daylight Metrics.py +++ b/honeybee_grasshopper_radiance/src/HB Annual Daylight Metrics.py @@ -16,6 +16,11 @@ component (containing the .ill files and the sun-up-hours.txt). This can also be just the path to the folder containing these result files. + dyn_sch_: Optional dynamic Aperture Group Schedules from the "HB Aperture Group + Schedule" component, which will be used to customize the behavior + of any dyanmic aperture geometry in the output metrics. If unsupplied, + all dynamic aperture groups will be in their default state in for + the output metrics. _occ_sch_: An annual occupancy schedule as a Ladybug Data Collection or a HB-Energy schedule object. This can also be the identifier of a schedule in your HB-Energy schedule library. Any value in this schedule that is @@ -60,7 +65,7 @@ ghenv.Component.Name = "HB Annual Daylight Metrics" ghenv.Component.NickName = 'DaylightMetrics' -ghenv.Component.Message = '1.6.0' +ghenv.Component.Message = '1.6.1' ghenv.Component.Category = 'HB-Radiance' ghenv.Component.SubCategory = '4 :: Results' ghenv.Component.AdditionalHelpFromDocStrings = '1' @@ -84,6 +89,11 @@ except ImportError as e: raise ImportError('\nFailed to import honeybee_radiance:\n\t{}'.format(e)) +try: + from honeybee_radiance_postprocess.dynamic import DynamicSchedule +except ImportError as e: + raise ImportError('\nFailed to import honeybee_radiance:\n\t{}'.format(e)) + try: from honeybee_energy.lib.schedules import schedule_by_identifier except ImportError as e: # honeybee schedule library is not available @@ -96,7 +106,8 @@ raise ImportError('\nFailed to import pollination_handlers:\n\t{}'.format(e)) try: - from ladybug_rhino.grasshopper import all_required_inputs, list_to_data_tree + from ladybug_rhino.grasshopper import all_required_inputs, list_to_data_tree, \ + give_warning except ImportError as e: raise ImportError('\nFailed to import ladybug_rhino:\n\t{}'.format(e)) @@ -142,7 +153,8 @@ # compute the annual metrics res_folder = os.path.dirname(_results[0]) if os.path.isfile(_results[0]) \ else _results[0] - if os.path.isdir(os.path.join(res_folder, '__static_apertures__')): + if os.path.isdir(os.path.join(res_folder, '__static_apertures__')) or \ + os.path.isfile(os.path.join(res_folder, 'grid_states.json')): cmds = [ folders.python_exe_path, '-m', 'honeybee_radiance_postprocess', 'post-process', 'annual-daylight', res_folder, '-sf', 'metrics', @@ -150,6 +162,11 @@ ] if grid_filter_ != '*': cmds.extend(['--grids-filter', grid_filter_]) + if len(dyn_sch_) != 0: + dyn_sch = dyn_sch_[0] if isinstance(dyn_sch_[0], DynamicSchedule) else \ + DynamicSchedule.from_group_schedules(dyn_sch_) + dyn_sch_file = dyn_sch.to_json(folder=res_folder) + cmds.extend(['--states', dyn_sch_file]) if schedule is not None: sch_str = '\n'.join(str(h) for h in schedule) sch_file = os.path.join(res_folder, 'schedule.txt') @@ -170,6 +187,11 @@ UDI_low = list_to_data_tree(read_udi_from_folder(os.path.join(metric_dir, 'udi_lower'))) UDI_up = list_to_data_tree(read_udi_from_folder(os.path.join(metric_dir, 'udi_upper'))) else: + if len(dyn_sch_) != 0: + msg = 'Dynamic Schedules are currently only supported for Annual Daylight ' \ + 'simulations.\nThe input schedules will be ignored.' + print(msg) + give_warning(ghenv.Component, msg) DA, cDA, UDI_low, UDI, UDI_up = metrics_from_folder( res_folder, schedule, _threshold_, min_t, max_t, grid_filter_) DA = list_to_data_tree(DA) diff --git a/honeybee_grasshopper_radiance/src/HB Aperture Group Schedule.py b/honeybee_grasshopper_radiance/src/HB Aperture Group Schedule.py new file mode 100644 index 0000000..e3b6015 --- /dev/null +++ b/honeybee_grasshopper_radiance/src/HB Aperture Group Schedule.py @@ -0,0 +1,69 @@ +# Honeybee: A Plugin for Environmental Analysis (GPL) +# This file is part of Honeybee. +# +# Copyright (c) 2023, Ladybug Tools. +# You should have received a copy of the GNU Affero General Public License +# along with Honeybee; If not, see . +# +# @license AGPL-3.0-or-later + +""" +Create a Dynamic Aperture Group Schedule, which can be used to process any dynamic +aperture geometry that was run in an annual simulation. + +- + Args: + _group_aps: Honeybee Apertures that are a part of the same dynamic group and will + be assigned the same schedule for postprocessing. Typically, this is + the output of the "HB Dynamic Aperture Group" component but it can + also be the output of the "HB Get Dynamic Groups" component, which + returns all of the dynamic groups on a particular Model. + _schedule: A list of 8760 integers refering to the index of the aperture group state + to be used at each hour of the simulation. This can also be a single integer + for a static state to be used for the entire period of the simulation + or a pattern of integers that is less than 8760 in length and will be + repeated until the 8760 is reached. Note that 0 refers to the first + state, 1 refers to the second state, and so on. -1 can be used to + completely discout the aperture from the simulation for a given hour. + + Returns: + dyn_sch: A dynamic schedule object for the input aperture group, which can be plugged + into any of the Results components with a syn_sch input. +""" + +ghenv.Component.Name = 'HB Aperture Group Schedule' +ghenv.Component.NickName = 'GroupSch' +ghenv.Component.Message = '1.6.0' +ghenv.Component.Category = 'HB-Radiance' +ghenv.Component.SubCategory = '4 :: Results' +ghenv.Component.AdditionalHelpFromDocStrings = '1' + +try: + from honeybee.aperture import Aperture +except ImportError as e: + raise ImportError('\nFailed to import honeybee:\n\t{}'.format(e)) + +try: + from honeybee_radiance_postprocess.dynamic import ApertureGroupSchedule +except ImportError as e: + raise ImportError('\nFailed to import honeybee_radiance:\n\t{}'.format(e)) + +try: + from ladybug_rhino.grasshopper import all_required_inputs, recipe_result +except ImportError as e: + raise ImportError('\nFailed to import ladybug_rhino:\n\t{}'.format(e)) + + +if all_required_inputs(ghenv.Component): + dyn_sch = [] + dyn_ids = set() + for ap in _group_aps: + assert isinstance(ap, Aperture), 'Expected Aperture. Got {}.'.format(type(ap)) + dyn_grp_id = ap.properties.radiance.dynamic_group_identifier + if dyn_grp_id is None: + raise ValueError( + 'Input Aperture "{}" is not a part of a dynamic group.'.format(ap.display_name)) + if dyn_grp_id not in dyn_ids: + dyn_ids.add(dyn_grp_id) + _ap_group_sch = ApertureGroupSchedule(dyn_grp_id, _schedule) + dyn_sch.append(_ap_group_sch) diff --git a/honeybee_grasshopper_radiance/src/HB Automatic Aperture Group.py b/honeybee_grasshopper_radiance/src/HB Automatic Aperture Group.py index 7f71d77..7050eff 100644 --- a/honeybee_grasshopper_radiance/src/HB Automatic Aperture Group.py +++ b/honeybee_grasshopper_radiance/src/HB Automatic Aperture Group.py @@ -42,6 +42,11 @@ If the vertical distance between two Apertures is larger than this tolerance the Apertures cannot be grouped. If no value is given the vertical grouping will be skipped. (Default: None). + states_: An optional list of Honeybee State objects to be applied to all the generated groups. + These states should be ordered based on how they will be switched on. + The first state is the default state and, typically, higher states + are more shaded. If the objects in the group have no states, the + modifiers already assigned the apertures will be used for all states. _run: Set to True to run the automatic Aperture grouping. Returns: @@ -51,7 +56,7 @@ ghenv.Component.Name = 'HB Automatic Aperture Group' ghenv.Component.NickName = 'AutoGroup' -ghenv.Component.Message = '1.6.3' +ghenv.Component.Message = '1.6.4' ghenv.Component.Category = 'HB-Radiance' ghenv.Component.SubCategory = '0 :: Basic Properties' ghenv.Component.AdditionalHelpFromDocStrings = '2' @@ -184,6 +189,7 @@ 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: @@ -191,3 +197,22 @@ 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] diff --git a/honeybee_grasshopper_radiance/src/HB Get Dynamic Groups.py b/honeybee_grasshopper_radiance/src/HB Get Dynamic Groups.py new file mode 100644 index 0000000..2908233 --- /dev/null +++ b/honeybee_grasshopper_radiance/src/HB Get Dynamic Groups.py @@ -0,0 +1,57 @@ +# Honeybee: A Plugin for Environmental Analysis (GPL) +# This file is part of Honeybee. +# +# Copyright (c) 2023, Ladybug Tools. +# You should have received a copy of the GNU Affero General Public License +# along with Honeybee; If not, see . +# +# @license AGPL-3.0-or-later + +""" +Get all of the Dynamic Radiance Groups assigned to a Model. +- + + Args: + _model: A Honeybee Model for which dynamic groups will be output. + + Returns: + group_ids: The identifiers of the dynamic groups assigned to the Model. + group_aps: A data tree of Dynamic Apertures in the Model. Each branch of the + tree represents a different Dynamic Aperture Group and corresponds to + the group_ids above. The data tree can be exploded with the native + Grasshopper "Explod Tree" component to assign schedules to each + Dynamic Group for postprocessing. +""" + +ghenv.Component.Name = 'HB Get Dynamic Groups' +ghenv.Component.NickName = 'GetDyn' +ghenv.Component.Message = '1.6.0' +ghenv.Component.Category = 'HB-Radiance' +ghenv.Component.SubCategory = '0 :: Basic Properties' +ghenv.Component.AdditionalHelpFromDocStrings = '0' + +try: # import core honeybee dependencies + from honeybee.model import Model +except ImportError as e: + raise ImportError('\nFailed to import honeybee:\n\t{}'.format(e)) + +try: # import ladybug_rhino dependencies + from ladybug_rhino.grasshopper import all_required_inputs, list_to_data_tree +except ImportError as e: + raise ImportError('\nFailed to import ladybug_rhino:\n\t{}'.format(e)) + + +if all_required_inputs(ghenv.Component): + assert isinstance(_model, Model), \ + 'Expected Honeybee Model. Got {}.'.format(type(_model)) + # get the dynamic group objects + groups = _model.properties.radiance.dynamic_subface_groups + groups.sort(key=lambda x: x.identifier) + + # get the group attributes + group_ids, group_aps = [], [] + for group in groups: + group_ids.append([group.identifier]) + group_aps.append(group.dynamic_objects) + group_ids = list_to_data_tree(group_ids) + group_aps = list_to_data_tree(group_aps) diff --git a/honeybee_grasshopper_radiance/user_objects/HB Annual Daylight Metrics.ghuser b/honeybee_grasshopper_radiance/user_objects/HB Annual Daylight Metrics.ghuser index a8d6bcd..065bbbe 100644 Binary files a/honeybee_grasshopper_radiance/user_objects/HB Annual Daylight Metrics.ghuser and b/honeybee_grasshopper_radiance/user_objects/HB Annual Daylight Metrics.ghuser differ diff --git a/honeybee_grasshopper_radiance/user_objects/HB Aperture Group Schedule.ghuser b/honeybee_grasshopper_radiance/user_objects/HB Aperture Group Schedule.ghuser new file mode 100644 index 0000000..2012157 Binary files /dev/null and b/honeybee_grasshopper_radiance/user_objects/HB Aperture Group Schedule.ghuser differ diff --git a/honeybee_grasshopper_radiance/user_objects/HB Automatic Aperture Group.ghuser b/honeybee_grasshopper_radiance/user_objects/HB Automatic Aperture Group.ghuser index d755a4e..c4e93e4 100644 Binary files a/honeybee_grasshopper_radiance/user_objects/HB Automatic Aperture Group.ghuser and b/honeybee_grasshopper_radiance/user_objects/HB Automatic Aperture Group.ghuser differ diff --git a/honeybee_grasshopper_radiance/user_objects/HB Get Dynamic Groups.ghuser b/honeybee_grasshopper_radiance/user_objects/HB Get Dynamic Groups.ghuser new file mode 100644 index 0000000..ba6ba8f Binary files /dev/null and b/honeybee_grasshopper_radiance/user_objects/HB Get Dynamic Groups.ghuser differ diff --git a/samples/annual_daylight.gh b/samples/annual_daylight.gh index 1964b64..1833873 100644 Binary files a/samples/annual_daylight.gh and b/samples/annual_daylight.gh differ diff --git a/samples/auto_dynamic_apertures.gh b/samples/auto_dynamic_apertures.gh new file mode 100644 index 0000000..865a3b5 Binary files /dev/null and b/samples/auto_dynamic_apertures.gh differ diff --git a/samples/dynamic_apertures.gh b/samples/dynamic_apertures.gh new file mode 100644 index 0000000..723ce9b Binary files /dev/null and b/samples/dynamic_apertures.gh differ