def test_default_modifiers(): """Test the auto-assigning of modifiers by face type and boundary condition.""" vertices_parent_wall = [[0, 0, 0], [0, 10, 0], [0, 10, 3], [0, 0, 3]] vertices_parent_wall_2 = list(reversed(vertices_parent_wall)) vertices_wall = [[0, 1, 0], [0, 2, 0], [0, 2, 2], [0, 0, 2]] vertices_wall_2 = list(reversed(vertices_wall)) vertices_floor = [[0, 0, 0], [0, 10, 0], [10, 10, 0], [10, 0, 0]] vertices_roof = [[10, 0, 3], [10, 10, 3], [0, 10, 3], [0, 0, 3]] wf = Face.from_vertices('wall_face', vertices_parent_wall) wa = Aperture.from_vertices('wall_window', vertices_wall) wf.add_aperture(wa) Room('TestRoom1', [wf]) assert wa.properties.radiance.modifier == generic_exterior_window wf2 = Face.from_vertices('wall_face2', vertices_parent_wall_2) wa2 = Aperture.from_vertices('wall_window2', vertices_wall_2) wf2.add_aperture(wa2) Room('TestRoom2', [wf2]) wa.set_adjacency(wa2) assert wa.properties.radiance.modifier == generic_interior_window ra = Aperture.from_vertices('roof_window', vertices_roof) assert ra.properties.radiance.modifier == generic_exterior_window fa = Aperture.from_vertices('floor_window', vertices_floor) assert fa.properties.radiance.modifier == generic_exterior_window
def test_default_constructions(): """Test the auto-assigning of constructions by boundary condition.""" vertices_parent_wall = [[0, 0, 0], [0, 10, 0], [0, 10, 3], [0, 0, 3]] vertices_parent_wall_2 = list(reversed(vertices_parent_wall)) vertices_wall = [[0, 1, 0], [0, 2, 0], [0, 2, 2], [0, 0, 2]] vertices_wall_2 = list(reversed(vertices_wall)) vertices_floor = [[0, 0, 0], [0, 10, 0], [10, 10, 0], [10, 0, 0]] vertices_roof = [[10, 0, 3], [10, 10, 3], [0, 10, 3], [0, 0, 3]] wf = Face.from_vertices('wall face', vertices_parent_wall) wa = Aperture.from_vertices('wall window', vertices_wall) wf.add_aperture(wa) Room('Test Room 1', [wf]) assert wa.properties.energy.construction.name == 'Generic Double Pane' wf2 = Face.from_vertices('wall face2', vertices_parent_wall_2) wa2 = Aperture.from_vertices('wall window2', vertices_wall_2) wf2.add_aperture(wa2) Room('Test Room 2', [wf2]) wa.set_adjacency(wa2) assert wa.properties.energy.construction.name == 'Generic Single Pane' ra = Aperture.from_vertices('roof window', vertices_roof) assert ra.properties.energy.construction.name == 'Generic Double Pane' fa = Aperture.from_vertices('floor window', vertices_floor) assert fa.properties.energy.construction.name == 'Generic Double Pane'
def test_duplicate(): """Test what happens to radiance properties when duplicating a Aperture.""" verts = [ Point3D(0, 0, 0), Point3D(10, 0, 0), Point3D(10, 0, 10), Point3D(0, 0, 10) ] triple_pane = Glass.from_single_transmittance('TriplePane', 0.45) ap_original = Aperture.from_vertices('wall_aper', Face3D(verts)) ap_dup_1 = ap_original.duplicate() assert ap_original.properties.radiance.host is ap_original assert ap_dup_1.properties.radiance.host is ap_dup_1 assert ap_original.properties.radiance.host is not ap_dup_1.properties.radiance.host assert ap_original.properties.radiance.modifier == \ ap_dup_1.properties.radiance.modifier ap_dup_1.properties.radiance.modifier = triple_pane assert ap_original.properties.radiance.modifier != \ ap_dup_1.properties.radiance.modifier ap_dup_2 = ap_dup_1.duplicate() assert ap_dup_1.properties.radiance.modifier == \ ap_dup_2.properties.radiance.modifier ap_dup_2.properties.radiance.modifier = None assert ap_dup_1.properties.radiance.modifier != \ ap_dup_2.properties.radiance.modifier
def test_to_from_dict_with_states(): """Test the Aperture from_dict method with radiance properties.""" ap = Aperture.from_vertices( 'wall_aperture', [[0, 0, 0], [10, 0, 0], [10, 0, 10], [0, 0, 10]]) shd1 = StateGeometry.from_vertices( 'wall_overhang1', [[0, 0, 10], [10, 0, 10], [10, 2, 10], [0, 2, 10]]) shd2 = StateGeometry.from_vertices( 'wall_overhang2', [[0, 0, 5], [10, 0, 5], [10, 2, 5], [0, 2, 5]]) ecglass1 = Glass.from_single_transmittance('ElectrochromicState1', 0.4) ecglass2 = Glass.from_single_transmittance('ElectrochromicState2', 0.27) ecglass3 = Glass.from_single_transmittance('ElectrochromicState3', 0.14) ecglass4 = Glass.from_single_transmittance('ElectrochromicState4', 0.01) tint1 = RadianceSubFaceState(ecglass1) tint2 = RadianceSubFaceState(ecglass2) tint3 = RadianceSubFaceState(ecglass3, [shd1]) tint4 = RadianceSubFaceState(ecglass4, [shd1.duplicate(), shd2]) states = (tint1, tint2, tint3, tint4) ap.properties.radiance.dynamic_group_identifier = 'ElectrochromicWindow1' ap.properties.radiance.states = states tint4.gen_geos_from_tmtx_thickness(0.05) ad = ap.to_dict() new_aperture = Aperture.from_dict(ad) assert new_aperture.properties.radiance.dynamic_group_identifier == \ ap.properties.radiance.dynamic_group_identifier state_ids1 = [state.modifier for state in states] state_ids2 = [ state.modifier for state in new_aperture.properties.radiance.states ] assert state_ids1 == state_ids2 assert new_aperture.to_dict() == ad
def test_writer(): """Test the Aperture writer object.""" vertices = [[0, 0, 0], [0, 10, 0], [0, 10, 3], [0, 0, 3]] ap = Aperture.from_vertices('RectangleWindow', vertices) writers = [mod for mod in dir(ap.to) if not mod.startswith('_')] for writer in writers: assert callable(getattr(ap.to, writer))
def test_energy_properties(): """Test the existence of the Aperture energy properties.""" aperture = Aperture.from_vertices( 'wall_aperture', [[0, 0, 1], [10, 0, 1], [10, 0, 2], [0, 0, 2]]) assert hasattr(aperture.properties, 'energy') assert isinstance(aperture.properties.energy, ApertureEnergyProperties) assert isinstance(aperture.properties.energy.construction, WindowConstruction) assert not aperture.properties.energy.is_construction_set_by_user
def test_to_from_dict(): """Test the to/from dict of Aperture objects.""" vertices = [[0, 0, 0], [0, 10, 0], [0, 10, 3], [0, 0, 3]] ap = Aperture.from_vertices('RectangleWindow', vertices) ap_dict = ap.to_dict() new_ap = Aperture.from_dict(ap_dict) assert isinstance(new_ap, Aperture) assert new_ap.to_dict() == ap_dict
def test_radiance_properties(): """Test the existence of the Aperture radiance properties.""" aperture = Aperture.from_vertices( 'wall_aper', [[0, 0, 0], [10, 0, 0], [10, 0, 10], [0, 0, 10]]) assert hasattr(aperture.properties, 'radiance') assert isinstance(aperture.properties.radiance, ApertureRadianceProperties) assert aperture.properties.radiance.modifier == generic_exterior_window assert aperture.properties.radiance.modifier_blk == black assert not aperture.properties.radiance.is_modifier_set_on_object
def test_to_from_dict(): """Test the Aperture from_dict method with states.""" aperture = Aperture.from_vertices( 'wall_aperture', [[0, 0, 0], [10, 0, 0], [10, 0, 10], [0, 0, 10]]) triple_pane = Glass.from_single_transmittance('TriplePane', 0.45) aperture.properties.radiance.modifier = triple_pane ad = aperture.to_dict() new_aperture = Aperture.from_dict(ad) assert new_aperture.properties.radiance.modifier == triple_pane assert new_aperture.to_dict() == ad
def test_set_modifier(): """Test the setting of a modifier on an Aperture.""" aperture = Aperture.from_vertices( 'wall_aper', [[0, 0, 0], [10, 0, 0], [10, 0, 10], [0, 0, 10]]) triple_pane = Glass.from_single_transmittance('TriplePane', 0.45) aperture.properties.radiance.modifier = triple_pane assert aperture.properties.radiance.modifier == triple_pane assert aperture.properties.radiance.is_modifier_set_on_object with pytest.raises(AttributeError): aperture.properties.radiance.modifier.r_transmittance = 0.45
def test_writer_to_rad(): """Test the Aperture to.rad method.""" pts = [[0, 0, 0], [10, 0, 0], [10, 0, 10], [0, 0, 10]] aperture = Aperture.from_vertices('wall_aperture', pts) triple_pane = Glass.from_single_transmittance('TriplePane', 0.45) aperture.properties.radiance.modifier = triple_pane assert hasattr(aperture.to, 'rad') rad_string = aperture.to.rad(aperture) assert 'polygon wall_aperture' in rad_string assert 'TriplePane' in rad_string for pt in pts: assert ' '.join([str(float(x)) for x in pt]) in rad_string
def test_to_dict(): """Test the Aperture to_dict method.""" vertices = [[0, 0, 0], [0, 10, 0], [0, 10, 3], [0, 0, 3]] ap = Aperture.from_vertices('Rectangle Window', vertices) ad = ap.to_dict() assert ad['type'] == 'Aperture' assert ad['name'] == 'RectangleWindow' assert ad['display_name'] == 'Rectangle Window' assert 'geometry' in ad assert len(ad['geometry']['boundary']) == len(vertices) assert 'properties' in ad assert ad['properties']['type'] == 'ApertureProperties' assert not ad['is_operable'] assert ad['boundary_condition']['type'] == 'Outdoors'
def test_from_dict(): """Test the Aperture from_dict method with energy properties.""" aperture = Aperture.from_vertices( 'wall_window', [[0, 0, 0], [10, 0, 0], [10, 0, 10], [0, 0, 10]]) clear_glass = EnergyWindowMaterialGlazing('Clear Glass', 0.005715, 0.770675, 0.07, 0.8836, 0.0804, 0, 0.84, 0.84, 1.0) gap = EnergyWindowMaterialGas('air gap', thickness=0.0127) triple_pane = WindowConstruction( 'Triple Pane', [clear_glass, gap, clear_glass, gap, clear_glass]) aperture.properties.energy.construction = triple_pane ad = aperture.to_dict() new_aperture = Aperture.from_dict(ad) assert new_aperture.properties.energy.construction == triple_pane assert new_aperture.to_dict() == ad
def test_default_properties(): """Test the auto-assigning of shade properties.""" out_shade = Shade.from_vertices( 'overhang', [[0, 0, 3], [1, 0, 3], [1, 1, 3], [0, 1, 3]]) in_shade = Shade.from_vertices( 'light_shelf', [[0, 0, 3], [-1, 0, 3], [-1, -1, 3], [0, -1, 3]]) aperture = Aperture.from_vertices( 'parent_aperture', [[0, 0, 0], [0, 10, 0], [0, 10, 3], [0, 0, 3]]) assert out_shade.properties.radiance.modifier == generic_context assert in_shade.properties.radiance.modifier == generic_context aperture.add_outdoor_shade(out_shade) assert out_shade.properties.radiance.modifier == generic_exterior_shade aperture.add_indoor_shade(in_shade) assert in_shade.properties.radiance.modifier == generic_interior_shade
def test_writer_to_idf(): """Test the Aperture to_idf method.""" aperture = Aperture.from_vertices( 'wall_window', [[0, 0, 0], [10, 0, 0], [10, 0, 10], [0, 0, 10]]) clear_glass = EnergyWindowMaterialGlazing('Clear Glass', 0.005715, 0.770675, 0.07, 0.8836, 0.0804, 0, 0.84, 0.84, 1.0) gap = EnergyWindowMaterialGas('air gap', thickness=0.0127) triple_pane = WindowConstruction( 'TriplePane', [clear_glass, gap, clear_glass, gap, clear_glass]) aperture.properties.energy.construction = triple_pane assert hasattr(aperture.to, 'idf') idf_string = aperture.to.idf(aperture) assert 'wall_window,' in idf_string assert 'FenestrationSurface:Detailed,' in idf_string assert 'TriplePane' in idf_string
def test_to_dict(): """Test the Aperture to_dict method.""" vertices = [[0, 0, 0], [0, 10, 0], [0, 10, 3], [0, 0, 3]] unique_id = str(py_uuid.uuid4()) ap = Aperture.from_vertices(unique_id, vertices) ap.display_name = 'Rectangle Window' ad = ap.to_dict() assert ad['type'] == 'Aperture' assert ad['identifier'] == unique_id assert ad['display_name'] == 'Rectangle Window' assert 'geometry' in ad assert len(ad['geometry']['boundary']) == len(vertices) assert 'properties' in ad assert ad['properties']['type'] == 'ApertureProperties' assert not ad['is_operable'] assert ad['boundary_condition']['type'] == 'Outdoors'
def test_default_properties(): """Test the auto-assigning of shade properties.""" shade = Shade.from_vertices('overhang', [[0, 0, 3], [1, 0, 3], [1, 1, 3], [0, 1, 3]]) aperture = Aperture.from_vertices( 'ParentAperture', [[0, 0, 0], [0, 10, 0], [0, 10, 3], [0, 0, 3]]) assert shade.properties.energy.transmittance_schedule is None assert shade.properties.energy.construction.solar_reflectance == 0.2 assert shade.properties.energy.construction.visible_reflectance == 0.2 assert not shade.properties.energy.construction.is_specular assert shade.properties.energy.transmittance_schedule is None aperture.add_outdoor_shade(shade) assert shade.properties.energy.construction.solar_reflectance == 0.35 assert shade.properties.energy.construction.visible_reflectance == 0.35 assert not shade.properties.energy.construction.is_specular
def test_set_construction(): """Test the setting of a construction on an Aperture.""" vertices_wall = [[0, 0, 0], [0, 10, 0], [0, 10, 3], [0, 0, 3]] clear_glass = EnergyWindowMaterialGlazing('Clear Glass', 0.005715, 0.770675, 0.07, 0.8836, 0.0804, 0, 0.84, 0.84, 1.0) gap = EnergyWindowMaterialGas('air gap', thickness=0.0127) triple_pane = WindowConstruction( 'Triple Pane', [clear_glass, gap, clear_glass, gap, clear_glass]) aperture = Aperture.from_vertices('wall window', vertices_wall) aperture.properties.energy.construction = triple_pane assert aperture.properties.energy.construction == triple_pane assert aperture.properties.energy.is_construction_set_by_user with pytest.raises(AttributeError): aperture.properties.energy.construction[0].thickness = 0.1
def test_aperture_from_vertices(): """Test the initialization of Aperture objects from vertices.""" pts = (Point3D(0, 0, 0), Point3D(0, 0, 3), Point3D(5, 0, 3), Point3D(5, 0, 0)) aperture = Aperture.from_vertices('Test Window', pts) assert aperture.name == 'TestWindow' assert aperture.display_name == 'Test Window' assert isinstance(aperture.geometry, Face3D) assert len(aperture.vertices) == 4 assert aperture.upper_left_vertices[0] == Point3D(5, 0, 3) assert len(aperture.triangulated_mesh3d.faces) == 2 assert aperture.normal == Vector3D(0, 1, 0) assert aperture.center == Point3D(2.5, 0, 1.5) assert aperture.area == 15 assert aperture.perimeter == 16 assert isinstance(aperture.boundary_condition, Outdoors) assert aperture.is_operable is False assert not aperture.has_parent
def test_to_dict(): """Test the Aperture to_dict method with radiance properties.""" aperture = Aperture.from_vertices( 'wall_aperture', [[0, 0, 0], [10, 0, 0], [10, 0, 10], [0, 0, 10]]) triple_pane = Glass.from_single_transmittance('TriplePane', 0.45) ad = aperture.to_dict() assert 'properties' in ad assert ad['properties']['type'] == 'ApertureProperties' assert 'radiance' in ad['properties'] assert ad['properties']['radiance']['type'] == 'ApertureRadianceProperties' aperture.properties.radiance.modifier = triple_pane ad = aperture.to_dict() assert 'modifier' in ad['properties']['radiance'] assert ad['properties']['radiance']['modifier'] is not None assert ad['properties']['radiance']['modifier'][ 'identifier'] == 'TriplePane'
def test_to_dict(): """Test the Aperture to_dict method with energy properties.""" aperture = Aperture.from_vertices( 'wall_window', [[0, 0, 0], [10, 0, 0], [10, 0, 10], [0, 0, 10]]) clear_glass = EnergyWindowMaterialGlazing('Clear Glass', 0.005715, 0.770675, 0.07, 0.8836, 0.0804, 0, 0.84, 0.84, 1.0) gap = EnergyWindowMaterialGas('air gap', thickness=0.0127) triple_pane = WindowConstruction( 'Triple Pane', [clear_glass, gap, clear_glass, gap, clear_glass]) ad = aperture.to_dict() assert 'properties' in ad assert ad['properties']['type'] == 'ApertureProperties' assert 'energy' in ad['properties'] assert ad['properties']['energy']['type'] == 'ApertureEnergyProperties' aperture.properties.energy.construction = triple_pane ad = aperture.to_dict() assert 'construction' in ad['properties']['energy'] assert ad['properties']['energy']['construction'] is not None
def dmtx_group_command( folder, octree, rflux_sky, name, size, threshold, ambient_division, output_folder, ): """Calculate aperture groups for daylight matrix purposes. This command calculates view factor from apertures to sky patches (rfluxmtx). Each aperture is represented by a sensor grid, and the view factor for the whole aperture is the average of the grid. The apertures are grouped based on the threshold. \b Args: folder: Path to a Radiance model folder. octree: Path to octree file. rflux_sky: Path to rflux sky file. """ def _index_and_min(distance_matrix): """Return the minimum value of the distance matrix, as well as the index [j, i] of the minimum value of the distance matrix.""" min_value = min([min(sublist) for sublist in distance_matrix]) for i, _i in enumerate(distance_matrix): for j, _j in enumerate(distance_matrix): if distance_matrix[i][j] == min_value: index = [j, i] break return min_value, index def _pairwise_maximum(array1, array2): """Return an array of the pairwise maximum of two arrays.""" pair_array = [array1, array2] max_array = list(map(max, zip(*pair_array))) return max_array def _tranpose_matrix(matrix): """Transposes the distance matrix.""" matrix = list(map(list, zip(*matrix))) return matrix def _rmse_from_matrix(input): """Calculates RMSE.""" rmse = [] for i, predicted in enumerate(input): r_list = [] for j, observed in enumerate(input): error = [(p - o) for p, o in zip(predicted, observed)] square_error = [e**2 for e in error] mean_square_error = sum(square_error) / len(square_error) root_mean_square_error = mean_square_error**0.5 r_list.append(root_mean_square_error) rmse.append(r_list) return rmse def _flatten(container): """Flatten an array.""" if not isinstance(container, list): container = [container] for i in container: if isinstance(i, (list, tuple)): for j in _flatten(i): yield j else: yield i def _agglomerative_clustering_complete(distance_matrix, ap_name, threshold=0.001): """Cluster apertures based on the threshold.""" # Fill the diagonal with 9999 so a diagonal of zeros will NOT be stored as min_value. for i in range(len(distance_matrix)): distance_matrix[i][i] = 9999 # Create starting list of aperture groups. Each aperture starts as its own group. ap_groups = ap_name # Set the number of samples and the minimum value of the distance matrix. n_samples = len(distance_matrix) # Set the minimum value of the distance matrix and find the indices of the minimum # value in the distance matrix. min_value, index = _index_and_min(distance_matrix) while n_samples > 1 and min_value < threshold: # Combine the two groups and place it at index 0, and remove item at index 1. ap_groups[index[0]] = [ap_groups[index[0]], ap_groups[index[1]]] ap_groups.pop(index[1]) # Update the values in the distance matrix. We need the maximum values between # the new cluster and all the remaining apertures or clusters still in the # distance matrix. distance_matrix[index[0]] = \ _pairwise_maximum(distance_matrix[index[0]], distance_matrix[index[1]]) distance_matrix = _tranpose_matrix(distance_matrix) distance_matrix[index[0]] = \ _pairwise_maximum(distance_matrix[index[0]], distance_matrix[index[1]]) # Remove the values at index 1 along both axes. distance_matrix.pop(index[1]) distance_matrix = _tranpose_matrix(distance_matrix) distance_matrix.pop(index[1]) # Update the number of samples that are left in the distance matrix. n_samples -= 1 # Update the minimum value and the indices. min_value, index = _index_and_min(distance_matrix) return ap_groups 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 try: model_folder = ModelFolder.from_model_folder(folder) apertures = [] states = model_folder.aperture_groups_states(full=True) ap_group_folder = model_folder.aperture_group_folder(full=True) for ap_group in states.keys(): if 'dmtx' in states[ap_group][0]: mtx_file = os.path.join( ap_group_folder, os.path.basename(states[ap_group][0]['dmtx'])) polygon_string = parse_from_file(mtx_file) polygon = Polygon.from_string('\n'.join(polygon_string)) apertures.append( Aperture.from_vertices(ap_group, polygon.vertices)) assert len(apertures) != 0, \ 'Found no valid dynamic apertures. There should at least be one aperture ' \ 'with transmittance matrix in your model.' # Calculate view factor. mtx_file, ap_dict = _aperture_view_factor( model_folder.folder, apertures, size=size, ambient_division=ambient_division, receiver=rflux_sky, octree=octree, calc_folder=output_folder) view_factor = [] # Read view factor file, convert to one channel output, and divide by Pi. with open(mtx_file) as mtx_data: for sensor in mtx_data: sensor_split = sensor.strip().split() if len(sensor_split) % 3 == 0: one_channel = sensor_split[::3] convert_to_vf = lambda x: float(x) / math.pi view_factor.append(list(map(convert_to_vf, one_channel))) ap_view_factor = [] # Split the view factor file by the aperture sensor count. for aperture in ap_dict.values(): sensor_count = aperture['sensor_count'] ap_vf, view_factor = view_factor[:sensor_count], view_factor[ sensor_count:] ap_view_factor.append(ap_vf) ap_view_factor_mean = [] # Get the mean view factor per sky patch for each aperture. for aperture in ap_view_factor: ap_t = _tranpose_matrix(aperture) ap_view_factor_mean.append( [sum(sky_patch) / len(sky_patch) for sky_patch in ap_t]) # Calculate RMSE between all combinations of averaged aperture view factors. rmse = _rmse_from_matrix(ap_view_factor_mean) ap_name = list(ap_dict.keys()) # Cluster the apertures by the 'complete method'. ap_groups = _agglomerative_clustering_complete(rmse, ap_name, threshold) # Flatten the groups. This will break the intercluster structure, but we do not need # to know that. ap_groups = [list(_flatten(cluster)) for cluster in ap_groups] # Add the aperture group to each aperture in the dictionary and write the aperture # group rad files. group_names = [] groups_folder = os.path.join(model_folder.folder, output_folder, 'groups') if not os.path.isdir(groups_folder): os.mkdir(groups_folder) for idx, group in enumerate(ap_groups): group_name = "group_{}".format(idx) group_file = os.path.join(groups_folder, group_name + '.rad') xform = [] group_names.append({ 'identifier': group_name, 'aperture_groups': group }) for ap in group: xform.append( "!xform ./model/aperture_group/{}..mtx.rad".format(ap)) with open(group_file, "w") as file: file.write('\n'.join(xform)) # Write aperture dictionary to json file. output = os.path.join(model_folder.folder, output_folder, 'groups', '%s.json' % name) with open(output, 'w') as fp: json.dump(group_names, fp, indent=2) except Exception: _logger.exception("Failed to run dmtx-group command.") traceback.print_exc() sys.exit(1) else: sys.exit(0)