diff --git a/opengeodeweb_back_schemas.json b/opengeodeweb_back_schemas.json index 5bafdb6a..63c64a08 100644 --- a/opengeodeweb_back_schemas.json +++ b/opengeodeweb_back_schemas.json @@ -309,14 +309,18 @@ "type": "string", "minLength": 1 }, - "component_id": { - "type": "string", - "minLength": 1 + "component_ids": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + }, + "minItems": 1 } }, "required": [ "id", - "component_id" + "component_ids" ], "additionalProperties": false }, @@ -332,14 +336,18 @@ "type": "string", "minLength": 1 }, - "component_id": { - "type": "string", - "minLength": 1 + "component_ids": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + }, + "minItems": 1 } }, "required": [ "id", - "component_id" + "component_ids" ], "additionalProperties": false }, @@ -355,14 +363,18 @@ "type": "string", "minLength": 1 }, - "component_id": { - "type": "string", - "minLength": 1 + "component_ids": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + }, + "minItems": 1 } }, "required": [ "id", - "component_id" + "component_ids" ], "additionalProperties": false }, @@ -378,14 +390,18 @@ "type": "string", "minLength": 1 }, - "component_id": { - "type": "string", - "minLength": 1 + "component_ids": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + }, + "minItems": 1 } }, "required": [ "id", - "component_id" + "component_ids" ], "additionalProperties": false }, diff --git a/requirements.txt b/requirements.txt index ff401cc6..b0223a5d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -66,4 +66,3 @@ werkzeug==3.1.8 # flask # flask-cors -opengeodeweb-microservice==1.*,>=1.2.0rc1 diff --git a/src/opengeodeweb_back/routes/blueprint_routes.py b/src/opengeodeweb_back/routes/blueprint_routes.py index 264816f5..c3d84905 100644 --- a/src/opengeodeweb_back/routes/blueprint_routes.py +++ b/src/opengeodeweb_back/routes/blueprint_routes.py @@ -1,10 +1,10 @@ # Standard library imports +from opengeodeweb_back.geode_objects.geode_solid_mesh3d import GeodeSolidMesh3D import os import time import shutil import math from threading import Timer -from typing import Any, Union, get_args # Third party imports import flask @@ -31,9 +31,8 @@ from opengeodeweb_back.geode_objects.geode_grid3d import GeodeGrid3D from opengeodeweb_back.geode_objects.geode_surface_mesh2d import GeodeSurfaceMesh2D from opengeodeweb_back.geode_objects.geode_surface_mesh3d import GeodeSurfaceMesh3D -from opengeodeweb_back.geode_objects.geode_solid_mesh3d import GeodeSolidMesh3D -ComponentMesh = Union[ +ComponentMesh = ( og.Corner2D, og.Corner3D, og.Line2D, @@ -41,7 +40,10 @@ og.Surface2D, og.Surface3D, og.Block3D, -] +) +ComponentLine = (og.Line2D, og.Line3D) +ComponentSurface = (og.Surface2D, og.Surface3D) +ComponentBlock = og.Block3D routes = flask.Blueprint("routes", __name__, url_prefix="/opengeodeweb_back") @@ -251,44 +253,46 @@ def texture_coordinates() -> flask.Response: def attributes_metadata( - manager: og.AttributeManager, + manager: og.AttributeManager | list[og.AttributeManager], ) -> list[dict[str, str | int | float | list[float]]]: + managers = manager if isinstance(manager, list) else [manager] attributes: list[dict[str, str | int | float | list[float]]] = [] - nb_elements = manager.nb_elements() - for name in manager.attribute_names(): - attribute = manager.find_generic_attribute(name) + first_manager = managers[0] + for name in first_manager.attribute_names(): + attribute = first_manager.find_generic_attribute(name) nb_items = 1 - min_value, max_value = -1.0, -1.0 min_values, max_values = [-1.0], [-1.0] if attribute.is_genericable(): nb_items = attribute.nb_items() min_values, max_values = [], [] for item in range(nb_items): - values = [ - attribute.generic_item_value(idx, item) - for idx in range(nb_elements) - ] - valid = [ - value - for value in values - if value is not None and not math.isnan(value) - ] - min_values.append(min(valid) if valid else -1.0) - max_values.append(max(valid) if valid else -1.0) - min_value, max_value = min_values[0], max_values[0] + valid_values: list[float] = [] + for attribute_manager in managers: + component_attribute = attribute_manager.find_generic_attribute(name) + if component_attribute.is_genericable(): + nb_elements = attribute_manager.nb_elements() + values = [ + component_attribute.generic_item_value(index, item) + for index in range(nb_elements) + ] + valid_values.extend( + value + for value in values + if value is not None and not math.isnan(value) + ) + min_values.append(min(valid_values) if valid_values else -1.0) + max_values.append(max(valid_values) if valid_values else -1.0) attributes.append( { "attribute_name": name, "nb_items": nb_items, - "min_value": min_value, - "max_value": max_value, + "min_value": min_values[0], + "max_value": max_values[0], "min_values": min_values, "max_values": max_values, } ) - print(f"[ATTRIBUTES] Number of items: {nb_items}") - print(f"[ATTRIBUTES] Attributes: {attributes}") return attributes @@ -399,13 +403,16 @@ def model_component_vertex_attribute_names() -> flask.Response: geode_object = geode_functions.load_geode_object(params.id) if not isinstance(geode_object, GeodeModel): flask.abort(400, f"{params.id} is not a GeodeModel") - component = geode_object.component(og.uuid(params.component_id)) - if not isinstance(component, get_args(ComponentMesh)): - flask.abort(400, f"{params.component_id} is not a valid ComponentMesh") - mesh = component.mesh() - attribute_manager = mesh.vertex_attribute_manager() + managers = [ + component.mesh().vertex_attribute_manager() + for component_id in params.component_ids + if isinstance( + (component := geode_object.component(og.uuid(component_id))), + ComponentMesh, + ) + ] return flask.make_response( - {"attributes": attributes_metadata(attribute_manager)}, + {"attributes": attributes_metadata(managers)}, 200, ) @@ -422,15 +429,16 @@ def model_component_edge_attribute_names() -> flask.Response: geode_object = geode_functions.load_geode_object(params.id) if not isinstance(geode_object, GeodeModel): flask.abort(400, f"{params.id} is not a GeodeModel") - component = geode_object.component(og.uuid(params.component_id)) - if not isinstance(component, get_args(ComponentMesh)): - flask.abort(400, f"{params.component_id} is not a valid ComponentMesh") - mesh = component.mesh() - if not hasattr(mesh, "edge_attribute_manager"): - flask.abort(400, "Component does not have edges") - attribute_manager = mesh.edge_attribute_manager() + managers = [ + component.mesh().edge_attribute_manager() + for component_id in params.component_ids + if isinstance( + (component := geode_object.component(og.uuid(component_id))), + ComponentLine, + ) + ] return flask.make_response( - {"attributes": attributes_metadata(attribute_manager)}, + {"attributes": attributes_metadata(managers)}, 200, ) @@ -447,15 +455,16 @@ def model_component_polygon_attribute_names() -> flask.Response: geode_object = geode_functions.load_geode_object(params.id) if not isinstance(geode_object, GeodeModel): flask.abort(400, f"{params.id} is not a GeodeModel") - component = geode_object.component(og.uuid(params.component_id)) - if not isinstance(component, get_args(ComponentMesh)): - flask.abort(400, f"{params.component_id} is not a valid ComponentMesh") - mesh = component.mesh() - if not hasattr(mesh, "polygon_attribute_manager"): - flask.abort(400, "Component does not have polygons") - attribute_manager = mesh.polygon_attribute_manager() + managers = [ + component.mesh().polygon_attribute_manager() + for component_id in params.component_ids + if isinstance( + (component := geode_object.component(og.uuid(component_id))), + ComponentSurface, + ) + ] return flask.make_response( - {"attributes": attributes_metadata(attribute_manager)}, + {"attributes": attributes_metadata(managers)}, 200, ) @@ -472,15 +481,16 @@ def model_component_polyhedron_attribute_names() -> flask.Response: geode_object = geode_functions.load_geode_object(params.id) if not isinstance(geode_object, GeodeModel): flask.abort(400, f"{params.id} is not a GeodeModel") - component = geode_object.component(og.uuid(params.component_id)) - if not isinstance(component, get_args(ComponentMesh)): - flask.abort(400, f"{params.component_id} is not a valid ComponentMesh") - mesh = component.mesh() - if not hasattr(mesh, "polyhedron_attribute_manager"): - flask.abort(400, "Component does not have polyhedra") - attribute_manager = mesh.polyhedron_attribute_manager() + managers = [ + component.mesh().polyhedron_attribute_manager() + for component_id in params.component_ids + if isinstance( + (component := geode_object.component(og.uuid(component_id))), + ComponentBlock, + ) + ] return flask.make_response( - {"attributes": attributes_metadata(attribute_manager)}, + {"attributes": attributes_metadata(managers)}, 200, ) diff --git a/src/opengeodeweb_back/routes/schemas/model_component_edge_attribute_names.json b/src/opengeodeweb_back/routes/schemas/model_component_edge_attribute_names.json index 08047402..df955c16 100644 --- a/src/opengeodeweb_back/routes/schemas/model_component_edge_attribute_names.json +++ b/src/opengeodeweb_back/routes/schemas/model_component_edge_attribute_names.json @@ -7,11 +7,15 @@ "type": "string", "minLength": 1 }, - "component_id": { - "type": "string", - "minLength": 1 + "component_ids": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + }, + "minItems": 1 } }, - "required": ["id", "component_id"], + "required": ["id", "component_ids"], "additionalProperties": false } diff --git a/src/opengeodeweb_back/routes/schemas/model_component_edge_attribute_names.py b/src/opengeodeweb_back/routes/schemas/model_component_edge_attribute_names.py index 75105832..a14c1181 100644 --- a/src/opengeodeweb_back/routes/schemas/model_component_edge_attribute_names.py +++ b/src/opengeodeweb_back/routes/schemas/model_component_edge_attribute_names.py @@ -1,5 +1,6 @@ from dataclasses_json import DataClassJsonMixin from dataclasses import dataclass +from typing import List @dataclass @@ -7,5 +8,5 @@ class ModelComponentEdgeAttributeNames(DataClassJsonMixin): def __post_init__(self) -> None: print(self, flush=True) - component_id: str + component_ids: List[str] id: str diff --git a/src/opengeodeweb_back/routes/schemas/model_component_polygon_attribute_names.json b/src/opengeodeweb_back/routes/schemas/model_component_polygon_attribute_names.json index 60f685a3..54e99511 100644 --- a/src/opengeodeweb_back/routes/schemas/model_component_polygon_attribute_names.json +++ b/src/opengeodeweb_back/routes/schemas/model_component_polygon_attribute_names.json @@ -7,11 +7,15 @@ "type": "string", "minLength": 1 }, - "component_id": { - "type": "string", - "minLength": 1 + "component_ids": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + }, + "minItems": 1 } }, - "required": ["id", "component_id"], + "required": ["id", "component_ids"], "additionalProperties": false } diff --git a/src/opengeodeweb_back/routes/schemas/model_component_polygon_attribute_names.py b/src/opengeodeweb_back/routes/schemas/model_component_polygon_attribute_names.py index 5a22b6a2..6214a477 100644 --- a/src/opengeodeweb_back/routes/schemas/model_component_polygon_attribute_names.py +++ b/src/opengeodeweb_back/routes/schemas/model_component_polygon_attribute_names.py @@ -1,5 +1,6 @@ from dataclasses_json import DataClassJsonMixin from dataclasses import dataclass +from typing import List @dataclass @@ -7,5 +8,5 @@ class ModelComponentPolygonAttributeNames(DataClassJsonMixin): def __post_init__(self) -> None: print(self, flush=True) - component_id: str + component_ids: List[str] id: str diff --git a/src/opengeodeweb_back/routes/schemas/model_component_polyhedron_attribute_names.json b/src/opengeodeweb_back/routes/schemas/model_component_polyhedron_attribute_names.json index 8115e3fc..725adaa5 100644 --- a/src/opengeodeweb_back/routes/schemas/model_component_polyhedron_attribute_names.json +++ b/src/opengeodeweb_back/routes/schemas/model_component_polyhedron_attribute_names.json @@ -7,11 +7,15 @@ "type": "string", "minLength": 1 }, - "component_id": { - "type": "string", - "minLength": 1 + "component_ids": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + }, + "minItems": 1 } }, - "required": ["id", "component_id"], + "required": ["id", "component_ids"], "additionalProperties": false } diff --git a/src/opengeodeweb_back/routes/schemas/model_component_polyhedron_attribute_names.py b/src/opengeodeweb_back/routes/schemas/model_component_polyhedron_attribute_names.py index 426c54bd..98985b69 100644 --- a/src/opengeodeweb_back/routes/schemas/model_component_polyhedron_attribute_names.py +++ b/src/opengeodeweb_back/routes/schemas/model_component_polyhedron_attribute_names.py @@ -1,5 +1,6 @@ from dataclasses_json import DataClassJsonMixin from dataclasses import dataclass +from typing import List @dataclass @@ -7,5 +8,5 @@ class ModelComponentPolyhedronAttributeNames(DataClassJsonMixin): def __post_init__(self) -> None: print(self, flush=True) - component_id: str + component_ids: List[str] id: str diff --git a/src/opengeodeweb_back/routes/schemas/model_component_vertex_attribute_names.json b/src/opengeodeweb_back/routes/schemas/model_component_vertex_attribute_names.json index dcb7322c..b92398bd 100644 --- a/src/opengeodeweb_back/routes/schemas/model_component_vertex_attribute_names.json +++ b/src/opengeodeweb_back/routes/schemas/model_component_vertex_attribute_names.json @@ -7,11 +7,15 @@ "type": "string", "minLength": 1 }, - "component_id": { - "type": "string", - "minLength": 1 + "component_ids": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + }, + "minItems": 1 } }, - "required": ["id", "component_id"], + "required": ["id", "component_ids"], "additionalProperties": false } diff --git a/src/opengeodeweb_back/routes/schemas/model_component_vertex_attribute_names.py b/src/opengeodeweb_back/routes/schemas/model_component_vertex_attribute_names.py index d56851d6..1f5f6177 100644 --- a/src/opengeodeweb_back/routes/schemas/model_component_vertex_attribute_names.py +++ b/src/opengeodeweb_back/routes/schemas/model_component_vertex_attribute_names.py @@ -1,5 +1,6 @@ from dataclasses_json import DataClassJsonMixin from dataclasses import dataclass +from typing import List @dataclass @@ -7,5 +8,5 @@ class ModelComponentVertexAttributeNames(DataClassJsonMixin): def __post_init__(self) -> None: print(self, flush=True) - component_id: str + component_ids: List[str] id: str diff --git a/tests/test_routes.py b/tests/test_routes.py index 3ed276da..9bf7ebff 100644 --- a/tests/test_routes.py +++ b/tests/test_routes.py @@ -676,13 +676,12 @@ def test_model_component_vertex_attribute_names(client: FlaskClient) -> None: corner_ids = by_type.get("Corner", []) assert len(corner_ids) > 0, "cube.og_brep should have Corner components" - component_id = corner_ids[0] - response = client.post(route, json={"id": model_id, "component_id": component_id}) + response = client.post(route, json={"id": model_id, "component_ids": corner_ids}) _assert_attributes_response(response) def get_full_data() -> test_utils.JsonData: - return {"id": model_id, "component_id": component_id} + return {"id": model_id, "component_ids": corner_ids} test_utils.test_route_wrong_params(client, route, get_full_data) @@ -693,13 +692,12 @@ def test_model_component_edge_attribute_names(client: FlaskClient) -> None: line_ids = by_type.get("Line", []) assert len(line_ids) > 0, "cube.og_brep should have Line components" - component_id = line_ids[0] - response = client.post(route, json={"id": model_id, "component_id": component_id}) + response = client.post(route, json={"id": model_id, "component_ids": line_ids}) _assert_attributes_response(response) def get_full_data() -> test_utils.JsonData: - return {"id": model_id, "component_id": component_id} + return {"id": model_id, "component_ids": line_ids} test_utils.test_route_wrong_params(client, route, get_full_data) @@ -710,13 +708,12 @@ def test_model_component_polygon_attribute_names(client: FlaskClient) -> None: surface_ids = by_type.get("Surface", []) assert len(surface_ids) > 0, "cube.og_brep should have Surface components" - component_id = surface_ids[0] - response = client.post(route, json={"id": model_id, "component_id": component_id}) + response = client.post(route, json={"id": model_id, "component_ids": surface_ids}) _assert_attributes_response(response) def get_full_data() -> test_utils.JsonData: - return {"id": model_id, "component_id": component_id} + return {"id": model_id, "component_ids": surface_ids} test_utils.test_route_wrong_params(client, route, get_full_data) @@ -727,12 +724,11 @@ def test_model_component_polyhedron_attribute_names(client: FlaskClient) -> None block_ids = by_type.get("Block", []) assert len(block_ids) > 0, "cube.og_brep should have Block components" - component_id = block_ids[0] - response = client.post(route, json={"id": model_id, "component_id": component_id}) + response = client.post(route, json={"id": model_id, "component_ids": block_ids}) _assert_attributes_response(response) def get_full_data() -> test_utils.JsonData: - return {"id": model_id, "component_id": component_id} + return {"id": model_id, "component_ids": block_ids} test_utils.test_route_wrong_params(client, route, get_full_data)