def _group_by_identifier(sensor_grids): """Group sensor grids or views if they have the same full identifier.""" group_func = lambda grid: grid.full_identifier # noqa: E731 ordered_sensor_grids = sorted(sensor_grids, key=group_func) # check if there is any duplicated identifiers ids = {grid.full_identifier for grid in sensor_grids} if len(list(ids)) == len(sensor_grids): # there is no duplicated identifier - return the original list return sensor_grids updated_grids = [] for group_identifier, grids in itertools.groupby(ordered_sensor_grids, group_func): grids = list(grids) if len(grids) > 1: # merge grids into one sensors = [] for grid in grids: sensors.extend(grid.sensors) joined_grid = SensorGrid(grids[0].identifier, sensors) joined_grid.group_identifier = grids[0].group_identifier updated_grids.append(joined_grid) else: updated_grids.append(grids[0]) return updated_grids
def test_dict_to_object_sensor_grid(): """Test the dict_to_object method with SensorGrid objects.""" sensors = [Sensor((0, 0, 0), (0, 0, 1)), Sensor((0, 0, 10), (0, 0, 1))] sg_obj = SensorGrid('sg_1', sensors) sg_dict = sg_obj.to_dict() new_sg = dict_to_object(sg_dict) assert isinstance(new_sg, SensorGrid)
def test_assigning_group(): sg = SensorGrid('sg_1', sensors) sg.group_identifier = 'floor_1/dining_room' str(sg) # test string representation hash(sg) # test hashability assert sg.identifier == 'sg_1' assert sg.group_identifier == 'floor_1/dining_room' assert len(sg) == 2 assert sg[0] == sensors[0] assert sg[1] == sensors[1]
def test_move(): sensor = Sensor((0, 0, 10), (0, 0, -1)) sensors = [ Sensor((0, 0, 0), (0, 0, 1)), Sensor((0, 0, 10), (0, 0, 1)), sensor ] sg = SensorGrid('sg_1', sensors) sg.move(pv.Vector3D(10, 20, 30)) assert sensor.pos == (10, 20, 40) assert sensor.dir == (0, 0, -1)
def test_reflect(): sensor = Sensor((1, 0, 2), (2, 0, 0)) sensors = [ Sensor((0, 0, 0), (0, 0, 1)), Sensor((0, 0, 10), (0, 0, 1)), sensor ] sg = SensorGrid('sg_1', sensors) sg.reflect(Plane(pv.Vector3D(1, 0, 0), pv.Point3D(0, 0, 0))) assert round(sensor.pos[0]) == -1 assert round(sensor.pos[1]) == 0 assert round(sensor.pos[2]) == 2 assert round(sensor.dir[0]) == -2 assert round(sensor.dir[1]) == 0 assert round(sensor.dir[2]) == 0
def test_rotate_xy(): sensor = Sensor((1, 0, 2), (2, 0, 0)) sensors = [ Sensor((0, 0, 0), (0, 0, 1)), Sensor((0, 0, 10), (0, 0, 1)), sensor ] sg = SensorGrid('sg_1', sensors) sg.rotate_xy(90, pv.Point3D(0, 0, 0)) assert round(sensor.pos[0]) == 0 assert round(sensor.pos[1]) == 1 assert round(sensor.pos[2]) == 2 assert round(sensor.dir[0]) == 0 assert round(sensor.dir[1]) == 2 assert round(sensor.dir[2]) == 0
def test_rotate(): sensor = Sensor((0, 0, 0), (1, 0, 0)) sensors = [ Sensor((0, 0, 0), (0, 0, 1)), Sensor((0, 0, 10), (0, 0, 1)), sensor ] sg = SensorGrid('sg_1', sensors) sg.rotate(90, pv.Vector3D(0, 1, 1), pv.Point3D(0, 0, 20)) assert round(sensor.pos[0], 3) == -14.142 assert round(sensor.pos[1]) == -10 assert round(sensor.pos[2]) == 10 assert round(sensor.dir[0], 1) == 0.0 assert round(sensor.dir[1], 3) == 0.707 assert round(sensor.dir[2], 3) == -0.707
def test_scale(): sensor = Sensor((1, 0, 2), (1, 0, 0)) sensors = [ Sensor((0, 0, 0), (0, 0, 1)), Sensor((0, 0, 10), (0, 0, 1)), sensor ] sg = SensorGrid('sg_1', sensors) sg.scale(2) assert round(sensor.pos[0]) == 2 assert round(sensor.pos[1]) == 0 assert round(sensor.pos[2]) == 4 assert round(sensor.dir[0]) == 1 assert round(sensor.dir[1]) == 0 assert round(sensor.dir[2]) == 0
def test_from_planar_positions(): positions = [[0, 0, 0], [0, 0, 5], [0, 0, 10]] plane_normal = [0, 0, 1] sg = SensorGrid.from_planar_positions('test_grid', positions, plane_normal) assert len(sg) == 3 for sensor in sg: assert sensor.dir == (0, 0, 1)
def test_from_loc_dir(): positions = [[0, 0, 0], [0, 0, 5], [0, 0, 10]] directions = [[0, 0, 1], [0, 0, -1], [0, 0, 10]] sg = SensorGrid.from_position_and_direction('sg', positions, directions) for count, sensor in enumerate(sg): list(sensor.pos) == positions[count] list(sensor.dir) == directions[count]
def _load_grids(self) -> None: """Load sensor grids.""" if self._sensor_grids_option == SensorGridOptions.Ignore: return if hasattr(self._hb_model.properties, 'radiance'): # list of unique sensor_grid identifiers in the model ids = set([ grid.identifier for grid in self._hb_model.properties.radiance.sensor_grids ]) # if all the grids have the same identifier, merge them into one grid if len(ids) == 1: id = self._hb_model.properties.radiance.sensor_grids[ 0].identifier sensors = [ sensor for grid in self._hb_model.properties.radiance.sensor_grids for sensor in grid.sensors ] sensor_grid = SensorGrid(id, sensors) self._sensor_grids.data.append( convert_sensor_grid(sensor_grid, self._sensor_grids_option)) # else add them as separate grids else: for sensor_grid in self._hb_model.properties.radiance.sensor_grids: self._sensor_grids.data.append( convert_sensor_grid(sensor_grid, self._sensor_grids_option))
def test_from_file(): sensor_grid = SensorGrid.from_file('./tests/assets/test_points.pts') assert sensor_grid.identifier == 'test_points' assert sensor_grid[0].pos == (0, 0, 0) assert sensor_grid[0].dir == (0, 0, 1) assert len(sensor_grid) == 3 assert sensor_grid[1].to_radiance() == '0.2 0.3 0.4 0.5 0.6 0.7' assert sensor_grid[2].to_radiance() == '-10.0 -5.0 0.0 -50.0 -60.0 -70.0'
def test_split_single_grid(): """Test splitting a sensor grid.""" sensor_grid = SensorGrid.from_file( './tests/assets/grid/sensor_grid_split.pts') folder = './tests/assets/temp' info = sensor_grid.to_files(folder, 1, 'single_grid') assert len(info) == 1 assert info[0]['count'] == sensor_grid.count
def test_from_mesh3d(): mesh_2d = Mesh2D.from_grid(Point2D(1, 1), 8, 2, 0.25, 1) mesh = Mesh3D.from_mesh2d(mesh_2d) sg = SensorGrid.from_mesh3d('sg_1', mesh) assert len(sg.sensors) == 16 assert len(sg.mesh.vertices) == 27 assert len(sg.mesh.faces) == 16 assert mesh.area == 4
def test_to_radial_grid(): positions = [[0, 0, 0], [0, 0, 5], [0, 0, 10]] plane_normal = [0, 0, 1] sg = SensorGrid.from_planar_positions('test_grid', positions, plane_normal) assert len(sg) == 3 new_sg = sg.to_radial_grid(dir_count=8) assert len(new_sg) == 3 * 8 assert new_sg.identifier == sg.identifier
def test_creation(): sg = SensorGrid('sg_1', sensors) str(sg) # test string representation hash(sg) # test hashability assert sg.identifier == 'sg_1' assert len(sg) == 2 assert sg[0] == sensors[0] assert sg[1] == sensors[1]
def test_from_rooms_radial(): runner = CliRunner() input_hb_model = './tests/assets/model/model_radiance_dynamic_states.hbjson' result = runner.invoke(from_rooms_radial, [input_hb_model]) assert result.exit_code == 0 sg_dict = json.loads(result.output) new_grids = [SensorGrid.from_dict(sg) for sg in sg_dict] assert len(new_grids) == 2 assert all(isinstance(sg, SensorGrid) for sg in new_grids)
def test_from_positions_radial(): sg = SensorGrid.from_positions_radial('glare_grid', [(0, 0, 0.8), (2, 2, 0.8)], mesh_radius=1) assert len(sg.sensors) == 16 assert len(list(sg.positions)) == 16 assert len(list(sg.directions)) == 16 assert len(sg.mesh.faces) == 16 print(sg.mesh)
def _aperture_view_factor(project_folder, apertures, size=0.2, ambient_division=1000, receiver='rflux_sky.sky', octree='scene.oct', calc_folder='dmtx_aperture_grouping'): """Calculates the view factor for each aperture by sensor points.""" # Instantiate dictionary that will store the sensor count for each aperture. We need # a OrderedDict so that we can split the rfluxmtx output file by each aperture # (sensor count) in the correct order. ap_dict = OrderedDict() meshes = [] # Create a mesh for each aperture and add the the sensor count to dict. for aperture in apertures: ap_mesh = aperture.geometry.mesh_grid(size, flip=True, generate_centroids=False) meshes.append(ap_mesh) ap_dict[aperture.display_name] = { 'sensor_count': len(ap_mesh.faces) } # Create a sensor grid from joined aperture mesh. grid_mesh = SensorGrid.from_mesh3d('aperture_grid', Mesh3D.join_meshes(meshes)) # Write sensor grid to pts file. sensors = grid_mesh.to_file(os.path.join(project_folder, calc_folder), file_name='apertures') # rfluxmtx options rfluxOpt = RfluxmtxOptions() rfluxOpt.ad = ambient_division rfluxOpt.lw = 1.0 / float(rfluxOpt.ad) rfluxOpt.I = True rfluxOpt.h = True # rfluxmtx command rflux = Rfluxmtx() rflux.options = rfluxOpt rflux.receivers = receiver rflux.sensors = sensors rflux.octree = octree rflux.output = os.path.join(calc_folder, 'apertures_vf.mtx') # Run rfluxmtx command rflux.run(cwd=project_folder) # Get the output file of the rfluxmtx command. mtx_file = os.path.join(project_folder, rflux.output) return mtx_file, ap_dict
def test_split_grid_single(): input_grid = './tests/assets/grid/sensor_grid_single.pts' output_folder = './tests/assets/temp' runner = CliRunner() result = runner.invoke(split_grid, [input_grid, '100', '--folder', output_folder]) assert result.exit_code == 0 # check the file is created and named correctly pts_file = os.path.join(output_folder, 'sensor_grid_single_0000.pts') assert os.path.isfile(pts_file) grid = SensorGrid.from_file(pts_file) assert grid.count == 21
def test_split_grid(): """Test splitting a sensor grid.""" sensor_grid = SensorGrid.from_file( './tests/assets/grid/sensor_grid_split.pts') folder = './tests/assets/temp' info = sensor_grid.to_files(folder, 6, 'test_sensor_grid') assert len(info) == 6 for i in range(6 - 1): assert info[i]['count'] == 4 assert info[-1]['count'] == 1
def test_to_and_from_dict(): sg = SensorGrid('sg', sensors) sg_dict = sg.to_dict() assert sg_dict == { 'type': 'SensorGrid', 'identifier': 'sg', 'sensors': [{ 'pos': (0, 0, 0), 'dir': (0, 0, 1) }, { 'pos': (0, 0, 10), 'dir': (0, 0, 1) }] } sensor_from = SensorGrid.from_dict(sg_dict) assert sensor_from == sg assert sg_dict == sensor_from.to_dict()
def test_merge_grid(): base_name = 'sensor_grid_merge' input_folder = './tests/assets/grid' output_folder = './tests/assets/temp' runner = CliRunner() result = runner.invoke( merge_grid, [input_folder, base_name, '--folder', output_folder]) assert result.exit_code == 0 # check the file is created pts_file = os.path.join(output_folder, base_name + '.pts') assert os.path.isfile(pts_file) grid = SensorGrid.from_file(pts_file) assert grid.count == 21
def test_split_grid(): input_grid = './tests/assets/grid/sensor_grid_split.pts' output_folder = './tests/assets/temp' runner = CliRunner() result = runner.invoke(split_grid, [input_grid, '5', '--folder', output_folder]) assert result.exit_code == 0 # check the file is created for count in range(4): pts_file = os.path.join(output_folder, 'sensor_grid_split_%04d.pts' % count) assert os.path.isfile(pts_file) grid = SensorGrid.from_file(pts_file) if count != 3: assert grid.count == 5 else: assert grid.count == 6
def test_invalid_input(): with pytest.raises(ValueError): SensorGrid('sg_1', ((0, 0, 0, 0, 0, 1), ))
def test_updating_values(): sg = SensorGrid('sg_1', sensors) # sensor is immutable - hence no assignment with pytest.raises(TypeError): sg.sensors[0] = Sensor((0, 0, 100), (0, 0, -10))
try: # import ladybug_rhino dependencies from ladybug_rhino.grasshopper import all_required_inputs from ladybug_rhino.togeometry import to_mesh3d, to_face3d except ImportError as e: raise ImportError('\nFailed to import ladybug_rhino:\n\t{}'.format(e)) if all_required_inputs(ghenv.Component): # set the default name and process the points to tuples name = clean_and_id_rad_string('SensorGrid') if _name_ is None else _name_ pts = [(pt.X, pt.Y, pt.Z) for pt in _positions] # create the sensor grid object id = clean_rad_string(name) if '/' not in name else clean_rad_string( name.split('/')[0]) if len(_directions_) == 0: grid = SensorGrid.from_planar_positions(id, pts, (0, 0, 1)) else: vecs = [(vec.X, vec.Y, vec.Z) for vec in _directions_] grid = SensorGrid.from_position_and_direction(id, pts, vecs) # set the display name if _name_ is not None: grid.display_name = _name_ if '/' in name: grid.group_identifier = \ '/'.join(clean_rad_string(key) for key in name.split('/')[1:]) if mesh_ is not None: grid.mesh = to_mesh3d(mesh_) if base_geo_ is not None: grid.base_geometry = to_face3d(base_geo_)
lb_mesh = to_joined_gridded_mesh3d(floor_faces, _grid_size, _dist_floor_) # remove points outside of the room volume if requested if remove_out_: pattern = [ room.geometry.is_point_inside(pt) for pt in lb_mesh.face_centroids ] lb_mesh, vertex_pattern = lb_mesh.remove_faces(pattern) # extract positions and directions from the mesh base_points = [from_point3d(pt) for pt in lb_mesh.face_centroids] base_poss = [(pt.x, pt.y, pt.z) for pt in lb_mesh.face_centroids] base_dirs = [(vec.x, vec.y, vec.z) for vec in lb_mesh.face_normals] # create the sensor grid s_grid = SensorGrid.from_position_and_direction( room.identifier, base_poss, base_dirs) s_grid.display_name = clean_rad_string(room.display_name) s_grid.room_identifier = room.identifier s_grid.mesh = lb_mesh # append everything to the lists grid.append(s_grid) points.append(base_points) mesh.append(from_mesh3d(lb_mesh)) # convert the lists of points to data trees points = list_to_data_tree(points)
def test_to_radiance(): sg = SensorGrid('sg', sensors) sg.to_radiance() == \ """0.0 0.0 0.0 0.0 0.0 1.0\n0.0 0.0 10.0 0.0 0.0 1.0"""
try: # import the honeybee-radiance dependencies from honeybee_radiance.sensorgrid import SensorGrid except ImportError as e: raise ImportError('\nFailed to import honeybee_radiance:\n\t{}'.format(e)) try: # import ladybug_rhino dependencies from ladybug_rhino.grasshopper import all_required_inputs from ladybug_rhino.togeometry import to_mesh3d except ImportError as e: raise ImportError('\nFailed to import ladybug_rhino:\n\t{}'.format(e)) if all_required_inputs(ghenv.Component): # set the default name and process the points to tuples name = 'SensorGrid' if _name_ is None else _name_ pts = [(pt.X, pt.Y, pt.Z) for pt in _positions] # create the sensor grid object if len(_directions_) == 0: grid = SensorGrid.from_planar_positions(clean_and_id_rad_string(name), pts, (0, 0, 1)) else: vecs = [(vec.X, vec.Y, vec.Z) for vec in _directions_] grid = SensorGrid.from_position_and_direction( clean_and_id_rad_string(name), pts, vecs) # set the display name if _name_ is not None: grid.display_name = _name_ if mesh_ is not None: grid.mesh = to_mesh3d(mesh_)