From 490fcb9563974d6ee745a43b0f1f508a01c530e1 Mon Sep 17 00:00:00 2001 From: guille Date: Tue, 16 Mar 2021 12:33:22 -0400 Subject: [PATCH] Implement exports factories clases and export stl and obj city version. --- .idea/libs.iml | 2 +- city_model_structure/attributes/polyhedron.py | 20 +++--- city_model_structure/building.py | 12 ++-- exports/exports_factory.py | 58 ++++++++++++++++++ exports/{idf_helper.py => formats/idf.py} | 2 +- exports/formats/obj.py | 5 ++ exports/formats/stl.py | 5 ++ exports/formats/triangular.py | 21 +++++++ tests/test_exports.py | 39 ++++++++++++ tests/test_geometry_factory.py | 2 +- tests/test_idf.py | 5 +- tests/test_physics_factory.py | 2 +- tests/test_schedules_factory.py | 2 +- tests/test_usage_factory.py | 2 +- tests/test_weather_factory.py | 6 +- .../20190815_mitte_out_MC_FloursurfaceADD.gml | 0 .../tests_data}/20buildings.gml | 0 .../tests_data}/EngHT_Flat_model_lod1.gml | 0 ...SO_52016_1_BESTEST_ClimData_2016.08.24.xls | Bin .../tests_data}/bld100087.gml | 0 .../tests_data}/building_lod2.gml | 0 .../tests_data}/buildings.gml | 0 {tests_data => tests/tests_data}/energy+.idd | 0 .../tests_data}/inseldb_new_york_city.dat | 0 .../tests_data}/iso_52016_1_2017_lod2.gml | 0 .../tests_data}/lod2_buildings.gml | 0 {tests_data => tests/tests_data}/minimal.idf | 0 {tests_data => tests/tests_data}/montreal.epw | 0 {tests_data => tests/tests_data}/subway.osm | 0 tests/tests_outputs/.gitignore | 4 ++ 30 files changed, 159 insertions(+), 28 deletions(-) create mode 100644 exports/exports_factory.py rename exports/{idf_helper.py => formats/idf.py} (99%) create mode 100644 exports/formats/obj.py create mode 100644 exports/formats/stl.py create mode 100644 exports/formats/triangular.py create mode 100644 tests/test_exports.py rename {tests_data => tests/tests_data}/20190815_mitte_out_MC_FloursurfaceADD.gml (100%) rename {tests_data => tests/tests_data}/20buildings.gml (100%) rename {tests_data => tests/tests_data}/EngHT_Flat_model_lod1.gml (100%) rename {tests_data => tests/tests_data}/ISO_52016_1_BESTEST_ClimData_2016.08.24.xls (100%) rename {tests_data => tests/tests_data}/bld100087.gml (100%) rename {tests_data => tests/tests_data}/building_lod2.gml (100%) rename {tests_data => tests/tests_data}/buildings.gml (100%) rename {tests_data => tests/tests_data}/energy+.idd (100%) rename {tests_data => tests/tests_data}/inseldb_new_york_city.dat (100%) rename {tests_data => tests/tests_data}/iso_52016_1_2017_lod2.gml (100%) rename {tests_data => tests/tests_data}/lod2_buildings.gml (100%) rename {tests_data => tests/tests_data}/minimal.idf (100%) rename {tests_data => tests/tests_data}/montreal.epw (100%) rename {tests_data => tests/tests_data}/subway.osm (100%) create mode 100644 tests/tests_outputs/.gitignore diff --git a/.idea/libs.iml b/.idea/libs.iml index c268a131..7d99385a 100644 --- a/.idea/libs.iml +++ b/.idea/libs.iml @@ -6,7 +6,7 @@ - + diff --git a/city_model_structure/attributes/polyhedron.py b/city_model_structure/attributes/polyhedron.py index d9d6a2bb..d04a47b9 100644 --- a/city_model_structure/attributes/polyhedron.py +++ b/city_model_structure/attributes/polyhedron.py @@ -25,7 +25,7 @@ class Polyhedron: self._volume = None self._faces = None self._vertices = None - self._mesh = None + self._trimesh = None self._centroid = None self._max_z = None self._max_y = None @@ -348,10 +348,10 @@ class Polyhedron: return self._faces @property - def polyhedron_trimesh(self): - if self._mesh is None: - self._mesh = Trimesh(vertices=self.vertices, faces=self.faces) - return self._mesh + def trimesh(self) -> Trimesh: + if self._trimesh is None: + self._trimesh = Trimesh(vertices=self.vertices, faces=self.faces) + return self._trimesh @property def volume(self): @@ -360,10 +360,10 @@ class Polyhedron: :return: float """ if self._volume is None: - if not self.polyhedron_trimesh.is_volume: + if not self.trimesh.is_volume: self._volume = np.inf else: - self._volume = self.polyhedron_trimesh.volume + self._volume = self.trimesh.volume return self._volume @property @@ -466,7 +466,7 @@ class Polyhedron: :param full_path: str :return: None """ - self.polyhedron_trimesh.export(full_path, 'stl_ascii') + self.trimesh.export(full_path, 'stl_ascii') def obj_export(self, full_path): """ @@ -474,7 +474,7 @@ class Polyhedron: :param full_path: str :return: None """ - self.polyhedron_trimesh.export(full_path, 'obj') + self.trimesh.export(full_path, 'obj') def show(self): - self.polyhedron_trimesh.show() + self.trimesh.show() diff --git a/city_model_structure/building.py b/city_model_structure/building.py index 90d97647..75a37371 100644 --- a/city_model_structure/building.py +++ b/city_model_structure/building.py @@ -311,18 +311,18 @@ class Building(CityObject): # height = self.average_storey_height number_of_storeys = 4 height = 1.5 - mesh = self.simplified_polyhedron.polyhedron_trimesh + trimesh = self.simplified_polyhedron.trimesh normal_plane = [0, 0, -1] - rest_mesh = mesh + rest_trimesh = trimesh for n in range(0, number_of_storeys - 1): # todo: I need the lower corner of the building!! # point_plane = [self._lower_corner[0], self._lower_corner[1], self._lower_corner[2] + height] point_plane = [self._lower_corner[0] + 0.5, self._lower_corner[1] + 0.5, self._lower_corner[2] + height * (n + 1)] - meshes = gh.divide_mesh_by_plane(rest_mesh, normal_plane, point_plane) - storey = meshes[0] - rest_mesh = meshes[1] + trimeshes = gh.divide_mesh_by_plane(rest_trimesh, normal_plane, point_plane) + storey = trimeshes[0] + rest_trimesh = trimeshes[1] storeys.append(storey) - storeys.append(rest_mesh) + storeys.append(rest_trimesh) return storeys @property diff --git a/exports/exports_factory.py b/exports/exports_factory.py new file mode 100644 index 00000000..04f2f9be --- /dev/null +++ b/exports/exports_factory.py @@ -0,0 +1,58 @@ +""" +ExportsFactory export a city into several formats +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca +""" + +from exports.formats.stl import Stl +from exports.formats.obj import Obj + +class ExportsFactory: + """ + Exports factory class + """ + def __init__(self, export_type, city, path): + self._city = city + self._export_type = '_' + export_type.lower() + self._path = path + + @property + def _citygml(self): + """ + Export to citygml with application domain extensions + :return: None + """ + raise NotImplementedError() + + @property + def _stl(self): + """ + Export the city geometry to stl + :return: None + """ + return Stl(self._city, self._path) + + @property + def _obj(self): + """ + Export the city geometry to obj + :return: None + """ + return Obj(self._city, self._path) + + @property + def _idf(self): + """ + Export the city to Energy+ idf format + :return: + """ + raise NotImplementedError() + + @property + def export(self): + """ + Export the city model structure to the given export type + :return: City + """ + return getattr(self, self._export_type, lambda: None) + diff --git a/exports/idf_helper.py b/exports/formats/idf.py similarity index 99% rename from exports/idf_helper.py rename to exports/formats/idf.py index 1c5efbf2..d5758911 100644 --- a/exports/idf_helper.py +++ b/exports/formats/idf.py @@ -10,7 +10,7 @@ from pathlib import Path import helpers.constants as cte -class IdfHelper: +class Idf: _THERMOSTAT = 'HVACTEMPLATE:THERMOSTAT' _IDEAL_LOAD_AIR_SYSTEM = 'HVACTEMPLATE:ZONE:IDEALLOADSAIRSYSTEM' _SURFACE = 'BUILDINGSURFACE:DETAILED' diff --git a/exports/formats/obj.py b/exports/formats/obj.py new file mode 100644 index 00000000..56e30706 --- /dev/null +++ b/exports/formats/obj.py @@ -0,0 +1,5 @@ +from exports.formats.triangular import Triangular + +class Obj(Triangular): + def __init__(self, city, path): + super().__init__(city, path, 'obj') diff --git a/exports/formats/stl.py b/exports/formats/stl.py new file mode 100644 index 00000000..915d455d --- /dev/null +++ b/exports/formats/stl.py @@ -0,0 +1,5 @@ +from exports.formats.triangular import Triangular + +class Stl(Triangular): + def __init__(self, city, path): + super().__init__(city, path, 'stl') diff --git a/exports/formats/triangular.py b/exports/formats/triangular.py new file mode 100644 index 00000000..de5b9ec4 --- /dev/null +++ b/exports/formats/triangular.py @@ -0,0 +1,21 @@ +from pathlib import Path +from trimesh import Trimesh + +class Triangular: + def __init__(self, city, path, triangular_format): + self._city = city + self._path = path + self._triangular_format = triangular_format + self._files() + + def _files(self): + if self._city.name is None: + self._city.name = 'unknown_city' + file_name = self._city.name + '.' + self._triangular_format + file_path = (Path(self._path).resolve() / file_name).resolve() + print(file_path) + trimesh = Trimesh() + for building in self._city.buildings: + trimesh = trimesh.union(building.simplified_polyhedron.trimesh) + with open(file_path, 'w') as file: + file.write(trimesh.export(file_type=self._triangular_format)) diff --git a/tests/test_exports.py b/tests/test_exports.py new file mode 100644 index 00000000..99ddb926 --- /dev/null +++ b/tests/test_exports.py @@ -0,0 +1,39 @@ +""" +TestExports test and validate the city export formats +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca +""" + +from pathlib import Path +from unittest import TestCase +from imports.geometry_factory import GeometryFactory +from exports.exports_factory import ExportsFactory + +class TestExports(TestCase): + """ + TestGeometryFactory TestCase 1 + """ + def setUp(self) -> None: + """ + Test setup + :return: None + """ + self._city_gml = None + self._example_path = (Path(__file__).parent / 'tests_data').resolve() + self._output_path = (Path(__file__).parent / 'tests_outputs').resolve() + + def _get_city(self): + if self._city_gml is None: + file_path = (self._example_path / '20buildings.gml').resolve() + self._city_gml = GeometryFactory('citygml', file_path).city + return self._city_gml + + def _export(self, export_type): + self._city_gml = self._get_city() + ExportsFactory(export_type, self._city_gml, self._output_path).export + + def test_obj_export(self): + self._export('obj') + + def test_stl_export(self): + self._export('stl') diff --git a/tests/test_geometry_factory.py b/tests/test_geometry_factory.py index 945e0eec..8d56a706 100644 --- a/tests/test_geometry_factory.py +++ b/tests/test_geometry_factory.py @@ -21,7 +21,7 @@ class TestGeometryFactory(TestCase): :return: None """ self._city_gml = None - self._example_path = (Path(__file__).parent.parent / 'tests_data').resolve() + self._example_path = (Path(__file__).parent / 'tests_data').resolve() def _get_citygml(self, file): if self._city_gml is None: diff --git a/tests/test_idf.py b/tests/test_idf.py index 87775f20..1b474107 100644 --- a/tests/test_idf.py +++ b/tests/test_idf.py @@ -9,7 +9,6 @@ from imports.geometry_factory import GeometryFactory from imports.physics_factory import PhysicsFactory from imports.usage_factory import UsageFactory from imports.schedules_factory import SchedulesFactory -from exports.idf_helper import IdfHelper import os import glob @@ -25,8 +24,8 @@ class TestIdf(TestCase): :return: None """ self._city_gml = None - self._example_path = (Path(__file__).parent.parent / 'tests_data').resolve() - self._output_path = (Path(__file__).parent / 'ep_outputs').resolve() + self._example_path = (Path(__file__).parent / 'tests_data').resolve() + self._output_path = (Path(__file__).parent / 'tests_outputs').resolve() def _get_city(self): if self._city_gml is None: diff --git a/tests/test_physics_factory.py b/tests/test_physics_factory.py index 991aa7d3..5ea71ea6 100644 --- a/tests/test_physics_factory.py +++ b/tests/test_physics_factory.py @@ -21,7 +21,7 @@ class TestPhysicsFactory(TestCase): """ self._city_gml = None self._nyc_with_physics = None - self._example_path = (Path(__file__).parent.parent / 'tests_data').resolve() + self._example_path = (Path(__file__).parent / 'tests_data').resolve() def _get_citygml(self, file_path): if self._city_gml is None: diff --git a/tests/test_schedules_factory.py b/tests/test_schedules_factory.py index 23aa08a6..3894d5a2 100644 --- a/tests/test_schedules_factory.py +++ b/tests/test_schedules_factory.py @@ -23,7 +23,7 @@ class TestSchedulesFactory(TestCase): :return: None """ self._city_gml_with_usage = None - self._example_path = (Path(__file__).parent.parent / 'tests_data').resolve() + self._example_path = (Path(__file__).parent / 'tests_data').resolve() @property def _handler(self): diff --git a/tests/test_usage_factory.py b/tests/test_usage_factory.py index 2f671b27..e6f01e0e 100644 --- a/tests/test_usage_factory.py +++ b/tests/test_usage_factory.py @@ -21,7 +21,7 @@ class TestUsageFactory(TestCase): """ self._city_gml = None self._nyc_with_usage = None - self._example_path = (Path(__file__).parent.parent / 'tests_data').resolve() + self._example_path = (Path(__file__).parent / 'tests_data').resolve() def _get_citygml(self, file_path): if self._city_gml is None: diff --git a/tests/test_weather_factory.py b/tests/test_weather_factory.py index 0bc0f45e..0033ba28 100644 --- a/tests/test_weather_factory.py +++ b/tests/test_weather_factory.py @@ -22,7 +22,7 @@ class TestWeatherFactory(TestCase): self._city_gml = None self._city_with_weather = None self._city_name = 'new_york_city' - self._example_path = (Path(__file__).parent.parent / 'tests_data').resolve() + self._example_path = (Path(__file__).parent / 'tests_data').resolve() def _get_citygml(self, file_path): if self._city_gml is None: @@ -32,7 +32,7 @@ class TestWeatherFactory(TestCase): def _get_city_with_weather(self): if self._city_with_weather is None: - file_path = (Path(__file__).parent.parent / 'tests_data' / '20buildings.gml').resolve() + file_path = (Path(__file__).parent / 'tests_data' / '20buildings.gml').resolve() self._city_with_weather = self._get_citygml(file_path) WeatherFactory('dat', self._city_with_weather, city_name=self._city_name, base_path=self._example_path) return self._city_with_weather @@ -54,7 +54,7 @@ class TestWeatherFactory(TestCase): self.assertFalse(values.empty, 'wrong value beam') def test_weather_xls(self): - file_path = (Path(__file__).parent.parent / 'tests_data' / 'iso_52016_1_2017_lod2.gml').resolve() + file_path = (Path(__file__).parent / 'tests_data' / 'iso_52016_1_2017_lod2.gml').resolve() city_with_weather = self._get_citygml(file_path) WeatherFactory('xls', city_with_weather, city_name=self._city_name, base_path=self._example_path) for building in city_with_weather.buildings: diff --git a/tests_data/20190815_mitte_out_MC_FloursurfaceADD.gml b/tests/tests_data/20190815_mitte_out_MC_FloursurfaceADD.gml similarity index 100% rename from tests_data/20190815_mitte_out_MC_FloursurfaceADD.gml rename to tests/tests_data/20190815_mitte_out_MC_FloursurfaceADD.gml diff --git a/tests_data/20buildings.gml b/tests/tests_data/20buildings.gml similarity index 100% rename from tests_data/20buildings.gml rename to tests/tests_data/20buildings.gml diff --git a/tests_data/EngHT_Flat_model_lod1.gml b/tests/tests_data/EngHT_Flat_model_lod1.gml similarity index 100% rename from tests_data/EngHT_Flat_model_lod1.gml rename to tests/tests_data/EngHT_Flat_model_lod1.gml diff --git a/tests_data/ISO_52016_1_BESTEST_ClimData_2016.08.24.xls b/tests/tests_data/ISO_52016_1_BESTEST_ClimData_2016.08.24.xls similarity index 100% rename from tests_data/ISO_52016_1_BESTEST_ClimData_2016.08.24.xls rename to tests/tests_data/ISO_52016_1_BESTEST_ClimData_2016.08.24.xls diff --git a/tests_data/bld100087.gml b/tests/tests_data/bld100087.gml similarity index 100% rename from tests_data/bld100087.gml rename to tests/tests_data/bld100087.gml diff --git a/tests_data/building_lod2.gml b/tests/tests_data/building_lod2.gml similarity index 100% rename from tests_data/building_lod2.gml rename to tests/tests_data/building_lod2.gml diff --git a/tests_data/buildings.gml b/tests/tests_data/buildings.gml similarity index 100% rename from tests_data/buildings.gml rename to tests/tests_data/buildings.gml diff --git a/tests_data/energy+.idd b/tests/tests_data/energy+.idd similarity index 100% rename from tests_data/energy+.idd rename to tests/tests_data/energy+.idd diff --git a/tests_data/inseldb_new_york_city.dat b/tests/tests_data/inseldb_new_york_city.dat similarity index 100% rename from tests_data/inseldb_new_york_city.dat rename to tests/tests_data/inseldb_new_york_city.dat diff --git a/tests_data/iso_52016_1_2017_lod2.gml b/tests/tests_data/iso_52016_1_2017_lod2.gml similarity index 100% rename from tests_data/iso_52016_1_2017_lod2.gml rename to tests/tests_data/iso_52016_1_2017_lod2.gml diff --git a/tests_data/lod2_buildings.gml b/tests/tests_data/lod2_buildings.gml similarity index 100% rename from tests_data/lod2_buildings.gml rename to tests/tests_data/lod2_buildings.gml diff --git a/tests_data/minimal.idf b/tests/tests_data/minimal.idf similarity index 100% rename from tests_data/minimal.idf rename to tests/tests_data/minimal.idf diff --git a/tests_data/montreal.epw b/tests/tests_data/montreal.epw similarity index 100% rename from tests_data/montreal.epw rename to tests/tests_data/montreal.epw diff --git a/tests_data/subway.osm b/tests/tests_data/subway.osm similarity index 100% rename from tests_data/subway.osm rename to tests/tests_data/subway.osm diff --git a/tests/tests_outputs/.gitignore b/tests/tests_outputs/.gitignore new file mode 100644 index 00000000..86d0cb27 --- /dev/null +++ b/tests/tests_outputs/.gitignore @@ -0,0 +1,4 @@ +# Ignore everything in this directory +* +# Except this file +!.gitignore \ No newline at end of file