diff --git a/hub/exports/formats/stl.py b/hub/exports/formats/stl.py index 51a96d8..479f163 100644 --- a/hub/exports/formats/stl.py +++ b/hub/exports/formats/stl.py @@ -69,38 +69,47 @@ class Stl: stl_name = f'{self._city.name}.stl' stl_file_path = (Path(self._path).resolve() / stl_name).resolve() with open(stl_file_path, 'w', encoding='utf-8') as stl: - for building in self._city.buildings: - stl.write(f"solid building{building.name}\n") - for surface in building.surfaces: - vertices = [] - normal = self._to_normal_vertex_stl(surface.perimeter_polygon.coordinates) #the normal vector should be calculated for every surface - for coordinate in surface.perimeter_polygon.coordinates: - vertex = self._to_vertex_stl(coordinate) - if vertex not in vertices: - vertices.append(vertex) - vertices = np.array(vertices) - #After collecting the unique vertices of a surface, there is a need to identify if it is located on the roof, floor, or side walls - roofStatus=1 #multiplication of the height of all vertices in a surface - heightSum=0 #summation of the height of all vertices in a surface - for vertex in vertices: - roofStatus *= vertex[1] - heightSum += vertex[1] - if roofStatus>0: - #this surface is the roof (first and third elements of vertices should be passed to the triangulation function) - triangles=self._triangulate_stl(vertices[:, [0, 2]], vertices[0][1]) - elif roofStatus==0 and heightSum==0: - # this surface is the floor - triangles=self._triangulate_stl(vertices[:, [0, 2]], vertices[0][1]) - elif roofStatus==0 and heightSum>0: - # this surface is a vertical wall (no need for triangulation as it can be done manually) - triangles = [[vertices[0],vertices[1],vertices[2]], [vertices[2], vertices[3], vertices[0]]] + with open(stl_file_path.parent/'buildingSurfaceId.txt', 'w') as textfile: + for building in self._city.buildings: + stl.write(f"solid building{building.name}\n") + # write the building id in the buildingSurfaceId.txt file + textfile.write(building.name + " ") + for surface in building.surfaces: + surfaceID=surface.id + vertices = [] + normal = self._to_normal_vertex_stl( + surface.perimeter_polygon.coordinates) # the normal vector should be calculated for every surface + for coordinate in surface.perimeter_polygon.coordinates: + vertex = self._to_vertex_stl(coordinate) + if vertex not in vertices: + vertices.append(vertex) + vertices = np.array(vertices) + # After collecting the unique vertices of a surface, there is a need to identify if it is located on the roof, floor, or side walls + roofStatus = 1 # multiplication of the height of all vertices in a surface + heightSum = 0 # summation of the height of all vertices in a surface + for vertex in vertices: + roofStatus *= vertex[1] + heightSum += vertex[1] + if roofStatus > 0: + # this surface is the roof (first and third elements of vertices should be passed to the triangulation function) + triangles = self._triangulate_stl(vertices[:, [0, 2]], vertices[0][1]) + elif roofStatus == 0 and heightSum == 0: + # this surface is the floor + triangles = self._triangulate_stl(vertices[:, [0, 2]], vertices[0][1]) + elif roofStatus == 0 and heightSum > 0: + # this surface is a vertical wall (no need for triangulation as it can be done manually) + triangles = [[vertices[0], vertices[1], vertices[2]], [vertices[2], vertices[3], vertices[0]]] - # write the facets (triangles) in the stl file - for triangle in triangles: - stl.write(f"facet normal {normal[0]} {normal[2]} {normal[1]}\n") #following the idea that y axis is the height - stl.write(" outer loop\n") - for vertex in triangle: - stl.write(f" vertex {vertex[0]} {vertex[1]} {vertex[2]}\n") - stl.write(" endloop\n") - stl.write("endfacet\n") - stl.write(f"endsolid building{building.name}\n") \ No newline at end of file + # write the facets (triangles) in the stl file + for triangle in triangles: + #write the id of the surface (the triangles that belong to one surface should have similar id). Note that surface id is number like 2,3,4, + textfile.write(surfaceID + " ") + stl.write( + f"facet normal {normal[0]} {normal[2]} {normal[1]}\n") # following the idea that y axis is the height + stl.write(" outer loop\n") + for vertex in triangle: + stl.write(f" vertex {vertex[0]} {vertex[1]} {vertex[2]}\n") + stl.write(" endloop\n") + stl.write("endfacet\n") + textfile.write("\n") # End the line after all surface IDs are written for a given building + stl.write(f"endsolid building{building.name}\n") diff --git a/main.py b/main.py index b1365e7..0e3faf2 100644 --- a/main.py +++ b/main.py @@ -17,7 +17,10 @@ import subprocess # Specify the GeoJSON file path # geojson_file = process_geojson(a=-73.5681295982132, b=45.49218262677643, c=-73.5681295982132, d=45.51218262677643, # e=-73.5881295982132, f=45.49218262677643,g=-73.5881295982132, h=45.51218262677643) -geojson_file = process_geojson(x=-73.5681295982132, y=45.49218262677643, diff=0.0001) +geojson_file = process_geojson(x=-73.57711768607801, y=45.495098237967, diff=0.0001) +#-73.63805499801724; 45.415970792153075 +#x=-73.5681295982132, y=45.49218262677643 + file_path = (Path(__file__).parent / 'input_files' / 'output_buildings.geojson') # Specify the output path for the PDF file @@ -35,13 +38,12 @@ city = GeometryFactory('geojson', ConstructionFactory('nrcan', city).enrich() UsageFactory('nrcan', city).enrich() WeatherFactory('epw', city).enrich() - ExportsFactory('stl', city, output_path/'CityBEM_input_output').export() - -#ExportsFactory('sra', city, tmp_folder).export() -#sra_path = (tmp_folder / f'{city.name}_sra.xml').resolve() +ExportsFactory('sra', city, tmp_folder).export() +sra_path = (tmp_folder / f'{city.name}_sra.xml').resolve() #subprocess.run(['sra', str(sra_path)]) #ResultFactory('sra', city, tmp_folder).enrich() -CityBEM_workflow(city) #run the city using a fast City-Building Energy Model, CityBEM + +CityBEM_workflow(city,SRA_radiation_status=True, SRA_path=tmp_folder) #run the city using a fast City-Building Energy Model, CityBEM #energy_plus_workflow(city) print ("test done") \ No newline at end of file diff --git a/out_files/CityBEM_input_output/SRA_settings.txt b/out_files/CityBEM_input_output/SRA_settings.txt new file mode 100644 index 0000000..fae1086 --- /dev/null +++ b/out_files/CityBEM_input_output/SRA_settings.txt @@ -0,0 +1,3 @@ +cityName: Montreal +SRA_radiation_status: True +SRA_path: C:\Users\User\PycharmProjects\CityBEM-CityLayers-SaeedRayegan\tmp diff --git a/out_files/CityBEM_input_output/buildingSurfaceId.txt b/out_files/CityBEM_input_output/buildingSurfaceId.txt new file mode 100644 index 0000000..98dc9a2 --- /dev/null +++ b/out_files/CityBEM_input_output/buildingSurfaceId.txt @@ -0,0 +1 @@ +177628 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9 10 10 11 11 12 12 13 13 diff --git a/scripts/CityBEM_run.py b/scripts/CityBEM_run.py index 476d5b3..6460284 100644 --- a/scripts/CityBEM_run.py +++ b/scripts/CityBEM_run.py @@ -11,12 +11,15 @@ from hub.imports.weather.epw_weather_parameters import EpwWeatherParameters sys.path.append('./') -def CityBEM_workflow(city): +def CityBEM_workflow(city, SRA_radiation_status=False, SRA_path=None): """ Main function to run the CityBEM under the CityLayer's hub. :Note: City object contains necessary attributes for the CityBEM workflow. The first final version is created at 10/07/2024 + Note:CityBEM includes a built-in function to calculate solar radiation on building facades. + However, SRA can account for shading effects from nearby buildings, both direct and diffuse. If the user opts to use SRA, CityBEM will read the radiation data generated by SRA. + """ #general output path for the CityLayer's hub out_path = Path(__file__).parent.parent / 'out_files' @@ -31,7 +34,8 @@ def CityBEM_workflow(city): #load the geojson file (for now this is necessary, later, it should be removed to extract building usage type code, center lat and lon). Later, these should be added to the building class with open(file_path, 'r') as f: geojson_data = json.load(f) - + #city name + cityName = city.name #call functions to provide inputs for CityBEM and finally run CityBEM export_geometry(city, CityBEM_path) export_building_info(city, CityBEM_path,geojson_data) @@ -39,6 +43,7 @@ def CityBEM_workflow(city): export_comprehensive_building_data(city, CityBEM_path) export_indoor_temperature_setpoint_data(city, CityBEM_path) export_internal_heat_gain_data(city, CityBEM_path) + write_user_setting_for_SRA_radiation_data(CityBEM_path,cityName, SRA_radiation_status, SRA_path) run_CityBEM(CityBEM_path) def export_geometry(city, CityBEM_path): """ @@ -371,6 +376,13 @@ def export_internal_heat_gain_data(city, CityBEM_path): # write the values to the text file for this building textfile.write(values_str + '\n') print("Internal heat gains for every building is successfully exported into a text file named Input_internal_heat_gain_CityLayer.txt") +def write_user_setting_for_SRA_radiation_data(CityBEM_path,cityName, SRA_radiation_status, SRA_path): + # Open the file in write mode and write the settings + with open(CityBEM_path / 'SRA_settings.txt', 'w') as textfile: + textfile.write(f"cityName: {cityName}\n") + textfile.write(f"SRA_radiation_status: {SRA_radiation_status}\n") + textfile.write(f"SRA_path: {SRA_path}\n") + def run_CityBEM(CityBEM_path): """ Run the CityBEM executable after all inputs are processed.