diff --git a/requirements.txt b/requirements.txt index 26be316f..d1953d29 100644 --- a/requirements.txt +++ b/requirements.txt @@ -66,4 +66,3 @@ werkzeug==3.1.8 # flask # flask-cors -opengeodeweb-microservice==1.*,>=1.1.4 diff --git a/src/opengeodeweb_back/geode_objects/__init__.py b/src/opengeodeweb_back/geode_objects/__init__.py index d1329a1d..5eb46f65 100644 --- a/src/opengeodeweb_back/geode_objects/__init__.py +++ b/src/opengeodeweb_back/geode_objects/__init__.py @@ -31,6 +31,7 @@ from .geode_cross_section import GeodeCrossSection from .geode_implicit_structural_model import GeodeImplicitStructuralModel from .geode_implicit_cross_section import GeodeImplicitCrossSection +from .geode_horizon_stack3d import GeodeHorizonStack3D geode_objects: dict[GeodeObjectType, type[GeodeObject]] = { "VertexSet": GeodeVertexSet, @@ -58,4 +59,5 @@ "CrossSection": GeodeCrossSection, "ImplicitStructuralModel": GeodeImplicitStructuralModel, "ImplicitCrossSection": GeodeImplicitCrossSection, + "HorizonStack3D": GeodeHorizonStack3D, } diff --git a/src/opengeodeweb_back/geode_objects/geode_horizon_stack3d.py b/src/opengeodeweb_back/geode_objects/geode_horizon_stack3d.py new file mode 100644 index 00000000..161bb35b --- /dev/null +++ b/src/opengeodeweb_back/geode_objects/geode_horizon_stack3d.py @@ -0,0 +1,117 @@ +# Standard library imports +from __future__ import annotations +from typing import Any + +# Third party imports +import opengeode as og +import opengeode_geosciences as og_geosciences +from opengeodeweb_microservice.database.data_types import GeodeModelType + +# Local application imports +from .geode_model import GeodeModel, ComponentRegistry + + +class GeodeHorizonStack3D(GeodeModel): + horizon_stack: og_geosciences.HorizonsStack3D + + def __init__( + self, horizon_stack: og_geosciences.HorizonsStack3D | None = None + ) -> None: + self.horizon_stack = ( + horizon_stack + if horizon_stack is not None + else og_geosciences.HorizonsStack3D() + ) + super().__init__(self.horizon_stack) + + @classmethod + def geode_object_type(cls) -> GeodeModelType: + return "HorizonStack3D" + + def native_extension(self) -> str: + return self.horizon_stack.native_extension() + + @classmethod + def is_3D(cls) -> bool: + return True + + @classmethod + def is_viewable(cls) -> bool: + return False + + def builder(self) -> og_geosciences.HorizonsStackBuilder3D: + return og_geosciences.HorizonsStackBuilder3D(self.horizon_stack) + + @classmethod + def load(cls, filename: str) -> GeodeHorizonStack3D: + return GeodeHorizonStack3D(og_geosciences.load_horizons_stack3D(filename)) + + @classmethod + def additional_files(cls, filename: str) -> og.AdditionalFiles: + return og_geosciences.horizons_stack_additional_files3D(filename) + + @classmethod + def is_loadable(cls, filename: str) -> og.Percentage: + return og_geosciences.is_horizons_stack_loadable3D(filename) + + @classmethod + def input_extensions(cls) -> list[str]: + if hasattr(og_geosciences, "HorizonsStackInputFactory3D"): + return og_geosciences.HorizonsStackInputFactory3D.list_creators() + return [] + + @classmethod + def output_extensions(cls) -> list[str]: + if hasattr(og_geosciences, "HorizonsStackOutputFactory3D"): + return og_geosciences.HorizonsStackOutputFactory3D.list_creators() + return [] + + @classmethod + def object_priority(cls, filename: str) -> int: + if hasattr(og_geosciences, "horizons_stack_object_priority3D"): + return og_geosciences.horizons_stack_object_priority3D(filename) + return 0 + + def is_saveable(self, filename: str) -> bool: + if hasattr(og_geosciences, "is_horizons_stack_saveable3D"): + return bool( + og_geosciences.is_horizons_stack_saveable3D( + self.horizon_stack, filename + ) + ) + return True + + def save(self, filename: str) -> list[str]: + if hasattr(og_geosciences, "save_horizons_stack3D"): + return og_geosciences.save_horizons_stack3D(self.horizon_stack, filename) + return [] + + def save_viewable(self, filename_without_extension: str) -> str: + return "" + + def save_light_viewable(self, filename_without_extension: str) -> str: + return "" + + def mesh_components(self) -> ComponentRegistry: + return {} + + def collection_components(self) -> ComponentRegistry: + return {} + + def boundaries(self, id: og.uuid) -> list[og.ComponentID]: + return [] + + def internals(self, id: og.uuid) -> list[og.ComponentID]: + return [] + + def items(self, id: og.uuid) -> list[og.ComponentID]: + return [] + + def component(self, id: og.uuid) -> og.Component3D: + raise NotImplementedError("HorizonStack3D has no mesh components") + + def inspect(self) -> Any: + return None + + def validate(self) -> Any: + return None diff --git a/src/opengeodeweb_back/utils_functions.py b/src/opengeodeweb_back/utils_functions.py index 34e5cf3b..de067853 100644 --- a/src/opengeodeweb_back/utils_functions.py +++ b/src/opengeodeweb_back/utils_functions.py @@ -200,18 +200,20 @@ def create_data_folder_from_id(data_id: str) -> str: def model_components( data_id: str, model: GeodeModel, viewable_file: str ) -> dict[str, Any]: - vtm_file_path = geode_functions.data_file_path(data_id, viewable_file) - tree = ET.parse(vtm_file_path) - root = tree.find("vtkMultiBlockDataSet") - if root is None: - flask.abort(500, "Failed to read viewable file") - uuid_to_flat_index = {} - current_index = 0 - assert root is not None - for elem in root.iter(): - if "uuid" in elem.attrib and elem.tag == "DataSet": - uuid_to_flat_index[elem.attrib["uuid"]] = current_index - current_index += 1 + uuid_to_flat_index: dict[str, int] = {} + if viewable_file: + vtm_file_path = geode_functions.data_file_path(data_id, viewable_file) + tree = ET.parse(vtm_file_path) + root = tree.find("vtkMultiBlockDataSet") + if root is None: + flask.abort(500, "Failed to read viewable file") + current_index = 0 + assert root is not None + for elem in root.iter(): + if "uuid" in elem.attrib and elem.tag == "DataSet": + uuid_to_flat_index[elem.attrib["uuid"]] = current_index + current_index += 1 + model_mesh_components = model.mesh_components() mesh_components = [] for mesh_component, ids in model_mesh_components.items(): @@ -270,27 +272,39 @@ def save_all_viewables_and_return_info( data_path: str, ) -> dict[str, Any]: with ThreadPoolExecutor() as executor: - native_files, viewable_path, light_path = executor.map( - lambda args: args[0](args[1]), - [ - ( - geode_object.save, - os.path.join( - data_path, "native." + geode_object.native_extension() + tasks: list[tuple[Callable[[str], Any], str]] = [ + ( + geode_object.save, + os.path.join(data_path, "native." + geode_object.native_extension()), + ) + ] + if geode_object.is_viewable(): + tasks.extend( + [ + (geode_object.save_viewable, os.path.join(data_path, "viewable")), + ( + geode_object.save_light_viewable, + os.path.join(data_path, "light_viewable"), ), - ), - (geode_object.save_viewable, os.path.join(data_path, "viewable")), - ( - geode_object.save_light_viewable, - os.path.join(data_path, "light_viewable"), - ), - ], - ) - with open(light_path, "rb") as f: - binary_light_viewable = f.read() + ] + ) + + results = list(executor.map(lambda args: args[0](args[1]), tasks)) + native_files = results[0] + if geode_object.is_viewable(): + viewable_path = results[1] + light_path = results[2] + with open(light_path, "rb") as f: + binary_light_viewable = f.read() + binary_light_viewable_str = binary_light_viewable.decode("utf-8") + data.viewable_file = os.path.basename(viewable_path) + data.light_viewable_file = os.path.basename(light_path) + else: + binary_light_viewable_str = "not_viewable" + data.viewable_file = "" + data.light_viewable_file = "" + data.native_file = os.path.basename(native_files[0]) - data.viewable_file = os.path.basename(viewable_path) - data.light_viewable_file = os.path.basename(light_path) assert data.native_file is not None assert data.viewable_file is not None @@ -305,7 +319,7 @@ def save_all_viewables_and_return_info( "id": data.id, "name": name, "viewer_type": data.viewer_object, - "binary_light_viewable": binary_light_viewable.decode("utf-8"), + "binary_light_viewable": binary_light_viewable_str, "geode_object_type": data.geode_object, } if isinstance(geode_object, GeodeVertexSet): diff --git a/tests/data/test.og_hst3d b/tests/data/test.og_hst3d new file mode 100644 index 00000000..f007de2e Binary files /dev/null and b/tests/data/test.og_hst3d differ