From fb366d270de29c81afd114cdcaf499230afdb1da Mon Sep 17 00:00:00 2001 From: Jean Fechter Date: Mon, 5 May 2025 11:10:16 +0200 Subject: [PATCH 01/16] feat: add properties checker --- geos-trame/src/geos_trame/app/core.py | 8 ++- geos-trame/src/geos_trame/app/ui/inspector.py | 27 ++++++++- .../src/geos_trame/app/utils/__init__.py | 0 .../app/utils/properties_checker.py | 55 +++++++++++++++++++ 4 files changed, 87 insertions(+), 3 deletions(-) create mode 100644 geos-trame/src/geos_trame/app/utils/__init__.py create mode 100644 geos-trame/src/geos_trame/app/utils/properties_checker.py diff --git a/geos-trame/src/geos_trame/app/core.py b/geos-trame/src/geos_trame/app/core.py index a059c62cc..8254de37b 100644 --- a/geos-trame/src/geos_trame/app/core.py +++ b/geos-trame/src/geos_trame/app/core.py @@ -1,6 +1,7 @@ # SPDX-License-Identifier: Apache-2.0 # SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. # SPDX-FileContributor: Lionel Untereiner + from trame.ui.vuetify3 import VAppLayout from trame.decorators import TrameApp from trame.widgets import html, simput @@ -9,6 +10,7 @@ from geos_trame import module from geos_trame.app.deck.tree import DeckTree +from geos_trame.app.utils.properties_checker import PropertiesChecker from geos_trame.app.ui.editor import DeckEditor from geos_trame.app.ui.inspector import DeckInspector from geos_trame.app.ui.plotting import DeckPlotting @@ -49,6 +51,9 @@ def __init__( self, server, file_name: str ): # Tree self.tree = DeckTree( self.state.sm_id ) + # Properties checker + self.properties_checker = PropertiesChecker(self.tree, trame_server=server) + # TODO put as a modal window self.set_input_file( file_name=self.state.input_file ) @@ -74,7 +79,8 @@ def deck_ui( self ): cols=2, order=1, ): - self.deckInspector = DeckInspector( source=self.tree, classes="fill-height" ) + self.deckInspector = DeckInspector( source=self.tree, classes="fit-content" ) + vuetify.VBtn(text="Check fields", classes="ma-4", click=(self.properties_checker.check_fields,)) with vuetify.VCol( cols=10, diff --git a/geos-trame/src/geos_trame/app/ui/inspector.py b/geos-trame/src/geos_trame/app/ui/inspector.py index 309ba0994..75951b4af 100644 --- a/geos-trame/src/geos_trame/app/ui/inspector.py +++ b/geos-trame/src/geos_trame/app/ui/inspector.py @@ -6,8 +6,7 @@ import yaml from pydantic import BaseModel -from trame.widgets import vuetify3 as vuetify -from trame.widgets import html +from trame.widgets import vuetify3 as vuetify, html from trame_simput import get_simput_manager from typing import Any @@ -62,6 +61,7 @@ class TreeNode: hidden_children: list is_drawable: bool drawn: bool + valid: int @property def json( self ) -> dict: @@ -71,6 +71,7 @@ def json( self ) -> dict: title=self.title, is_drawable=self.is_drawable, drawn=self.drawn, + valid=self.valid, children=[ c.json for c in self.children ], hidden_children=[ c.json for c in self.hidden_children ], ) @@ -79,6 +80,7 @@ def json( self ) -> dict: title=self.title, is_drawable=self.is_drawable, drawn=self.drawn, + valid=self.valid, children=None, hidden_children=[], ) @@ -104,6 +106,7 @@ def get_node_dict( obj, node_id, path ): hidden_children=[], is_drawable=node_id in ( k.value for k in Renderable ), drawn=False, + valid=0 ) @@ -222,6 +225,26 @@ def on_change( topic, ids=None, **kwargs ): with self: with vuetify.Template( v_slot_append="{ item }" ): + with vuetify.VTooltip(v_if=("item.valid == 2",)): + with vuetify.Template( + v_slot_activator=("{ props }",), + __properties__=[("v_slot_activator", "v-slot:activator")], + ): + vuetify.VIcon( + v_bind=("props",), + classes="mr-2", + icon="mdi-close", + color="red" + ) + html.Div(v_if=("item.invalid_properties",), v_text=("'Invalid properties: ' + item.invalid_properties",)) + html.Div(v_if=("item.invalid_children",), v_text=("'Invalid children: ' + item.invalid_children",)) + + vuetify.VIcon( + v_if=("item.valid < 2",), + classes="mr-2", + icon='mdi-check', + color=("['gray', 'green'][item.valid]",) + ) vuetify.VCheckboxBtn( v_if="item.is_drawable", focused=True, dense=True, diff --git a/geos-trame/src/geos_trame/app/utils/__init__.py b/geos-trame/src/geos_trame/app/utils/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/geos-trame/src/geos_trame/app/utils/properties_checker.py b/geos-trame/src/geos_trame/app/utils/properties_checker.py new file mode 100644 index 000000000..bc7efe1e4 --- /dev/null +++ b/geos-trame/src/geos_trame/app/utils/properties_checker.py @@ -0,0 +1,55 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. +# SPDX-FileContributor: Kitware + +from trame_client.widgets.core import AbstractElement + +from geos_trame.app.deck.tree import DeckTree + +expected_properties = [ + ("region_attribute", ["attribute", "{}"]), + ("fields_to_import", ["attribute", "{}"]), +] + +class PropertiesChecker(AbstractElement): + """ + Class to check the validity of properties within a deck tree. + """ + def __init__( self, tree: DeckTree, **kwargs ): + super().__init__("div", **kwargs) + + self.tree = tree + + def check_fields(self): + for field in self.state.deck_tree: + self.check_field( field ) + self.state.dirty("deck_tree") + self.state.flush() + + def check_attr_value(self, obj, attr, expected, field): + if hasattr(obj, attr) and getattr(obj, attr) not in expected: + field["invalid_properties"].append(attr) + + def check_field(self, field): + field["valid"] = 1 + block = self.tree.decode(field["id"]) + field["invalid_properties"] = [] + + for attr, expected in expected_properties: + self.check_attr_value(block, attr, expected, field) + + if len(field["invalid_properties"]) != 0: + field["valid"] = 2 + else: + field.pop("invalid_properties", None) + + if field["children"] is not None: + # Parents are only valid if all children are valid + field["invalid_children"] = [] + for child in field["children"]: + self.check_field(child) + if child["valid"] == 2: + field["valid"] = 2 + field["invalid_children"].append(child["title"]) + if len(field["invalid_children"]) == 0: + field.pop("invalid_children", None) From f85ae355cb8002cbd74dfd3c4ddc74203f1a2776 Mon Sep 17 00:00:00 2001 From: Jean Fechter Date: Mon, 5 May 2025 11:53:18 +0200 Subject: [PATCH 02/16] test: add properties checker test --- geos-trame/tests/test_properties_checker.py | 19 +++++++++++++++++++ geos-trame/tests/trame_fixtures.py | 21 +++++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 geos-trame/tests/test_properties_checker.py create mode 100644 geos-trame/tests/trame_fixtures.py diff --git a/geos-trame/tests/test_properties_checker.py b/geos-trame/tests/test_properties_checker.py new file mode 100644 index 000000000..11db0a302 --- /dev/null +++ b/geos-trame/tests/test_properties_checker.py @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. +# SPDX-FileContributor: Kitware +from pathlib import Path + +from geos_trame.app.core import GeosTrame +from tests.trame_fixtures import trame_state, trame_server_layout + + +def test_properties_checker(trame_server_layout, trame_state): + root_path = Path(__file__).parent.absolute().__str__() + file_name = root_path + "/data/singlePhaseFlow/FieldCaseTutorial3_smoke.xml" + + geos_trame = GeosTrame(trame_server_layout[0], file_name) + + field = trame_state.deck_tree[4]["children"][0] + assert field["valid"] == 0 + geos_trame.properties_checker.check_field(field) + assert field["valid"] == 1 diff --git a/geos-trame/tests/trame_fixtures.py b/geos-trame/tests/trame_fixtures.py new file mode 100644 index 000000000..3c340a13f --- /dev/null +++ b/geos-trame/tests/trame_fixtures.py @@ -0,0 +1,21 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. +# SPDX-FileContributor: Kitware +import pytest +from trame_server import Server +from trame_vuetify.ui.vuetify3 import VAppLayout + + +@pytest.fixture +def trame_server_layout(): + server = Server() + server.debug = True + + with VAppLayout(server) as layout: + yield server, layout + + +@pytest.fixture +def trame_state(trame_server_layout): + trame_server_layout[0].state.ready() + yield trame_server_layout[0].state \ No newline at end of file From 9451188cef76e7aed8d91f5bd276fc0703b9e7d1 Mon Sep 17 00:00:00 2001 From: Jean Fechter Date: Mon, 12 May 2025 09:08:19 +0200 Subject: [PATCH 03/16] feat: use simput to check values --- .../src/geos_trame/app/utils/properties_checker.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/geos-trame/src/geos_trame/app/utils/properties_checker.py b/geos-trame/src/geos_trame/app/utils/properties_checker.py index bc7efe1e4..03a68220e 100644 --- a/geos-trame/src/geos_trame/app/utils/properties_checker.py +++ b/geos-trame/src/geos_trame/app/utils/properties_checker.py @@ -3,6 +3,7 @@ # SPDX-FileContributor: Kitware from trame_client.widgets.core import AbstractElement +from trame_simput import get_simput_manager from geos_trame.app.deck.tree import DeckTree @@ -19,6 +20,7 @@ def __init__( self, tree: DeckTree, **kwargs ): super().__init__("div", **kwargs) self.tree = tree + self.simput_manager = get_simput_manager(id=self.state.sm_id) def check_fields(self): for field in self.state.deck_tree: @@ -26,17 +28,18 @@ def check_fields(self): self.state.dirty("deck_tree") self.state.flush() - def check_attr_value(self, obj, attr, expected, field): - if hasattr(obj, attr) and getattr(obj, attr) not in expected: + def check_attr_value(self, proxy, attr, expected, field): + if attr in proxy.definition and proxy[attr] not in expected: field["invalid_properties"].append(attr) def check_field(self, field): field["valid"] = 1 - block = self.tree.decode(field["id"]) field["invalid_properties"] = [] - for attr, expected in expected_properties: - self.check_attr_value(block, attr, expected, field) + proxy = self.simput_manager.proxymanager.get(field["id"]) + if proxy is not None: + for attr, expected in expected_properties: + self.check_attr_value(proxy, attr, expected, field) if len(field["invalid_properties"]) != 0: field["valid"] = 2 From c610c1a36676f1bfd67c6a615073142e9e86a9f4 Mon Sep 17 00:00:00 2001 From: Jean Fechter Date: Mon, 26 May 2025 11:46:58 +0200 Subject: [PATCH 04/16] refactor: type checking and split files --- geos-trame/pyproject.toml | 4 +- geos-trame/src/geos_trame/app/core.py | 39 ++- geos-trame/src/geos_trame/app/deck/file.py | 302 +----------------- geos-trame/src/geos_trame/app/deck/tree.py | 31 +- .../src/geos_trame/app/io/data_loader.py | 136 ++++++++ .../src/geos_trame/app/io/xml_parser.py | 2 +- .../src/geos_trame/app/types/renderable.py | 12 + .../src/geos_trame/app/types/tree_node.py | 37 +++ geos-trame/src/geos_trame/app/ui/inspector.py | 264 +++++---------- geos-trame/src/geos_trame/app/ui/timeline.py | 34 +- .../geos_trame/app/ui/viewer/regionViewer.py | 4 +- .../src/geos_trame/app/ui/viewer/viewer.py | 122 ++----- .../geos_trame/app/ui/viewer/wellViewer.py | 5 +- .../src/geos_trame/app/utils/dict_utils.py | 30 ++ .../src/geos_trame/app/utils/file_utils.py | 203 ++++++++++++ .../app/utils/properties_checker.py | 58 ++-- .../src/geos_trame/app/utils/pv_utils.py | 8 + geos-trame/src/geos_trame/widgets/__init__.py | 0 .../src/geos_trame/widgets/geos_trame.py | 18 -- geos-trame/tests/test_well_intersection.py | 38 ++- 20 files changed, 631 insertions(+), 716 deletions(-) create mode 100644 geos-trame/src/geos_trame/app/io/data_loader.py create mode 100644 geos-trame/src/geos_trame/app/types/renderable.py create mode 100644 geos-trame/src/geos_trame/app/types/tree_node.py create mode 100644 geos-trame/src/geos_trame/app/utils/dict_utils.py create mode 100644 geos-trame/src/geos_trame/app/utils/file_utils.py create mode 100644 geos-trame/src/geos_trame/app/utils/pv_utils.py delete mode 100644 geos-trame/src/geos_trame/widgets/__init__.py delete mode 100644 geos-trame/src/geos_trame/widgets/geos_trame.py diff --git a/geos-trame/pyproject.toml b/geos-trame/pyproject.toml index 1031aa23a..9ed61c548 100644 --- a/geos-trame/pyproject.toml +++ b/geos-trame/pyproject.toml @@ -57,13 +57,15 @@ dev = [ "pylint", "mypy", "black", - "isort" + "isort", + "mypy", ] test = [ "pytest==8.3.3", "pytest-seleniumbase==4.31.6", "pixelmatch==0.3.0", "Pillow==11.0.0", + "pytest-mypy==0.10.3", "pytest-xprocess==1.0.2" ] diff --git a/geos-trame/src/geos_trame/app/core.py b/geos-trame/src/geos_trame/app/core.py index 8254de37b..cdec00b6e 100644 --- a/geos-trame/src/geos_trame/app/core.py +++ b/geos-trame/src/geos_trame/app/core.py @@ -10,6 +10,9 @@ from geos_trame import module from geos_trame.app.deck.tree import DeckTree +from geos_trame.app.io.data_loader import DataLoader +from geos_trame.app.ui.viewer.regionViewer import RegionViewer +from geos_trame.app.ui.viewer.wellViewer import WellViewer from geos_trame.app.utils.properties_checker import PropertiesChecker from geos_trame.app.ui.editor import DeckEditor from geos_trame.app.ui.inspector import DeckInspector @@ -26,6 +29,12 @@ class GeosTrame: def __init__( self, server, file_name: str ): + self.alertHandler = None + self.deckPlotting = None + self.deckViewer = None + self.deckEditor = None + self.timelineEditor = None + self.deckInspector = None self.server = server server.enable_module( module ) @@ -51,14 +60,21 @@ def __init__( self, server, file_name: str ): # Tree self.tree = DeckTree( self.state.sm_id ) + # Viewers + self.region_viewer = RegionViewer() + self.well_viewer = WellViewer( 5, 5 ) + + # Data loader + self.data_loader = DataLoader( self.tree, self.region_viewer, self.well_viewer, trame_server=server ) + # Properties checker - self.properties_checker = PropertiesChecker(self.tree, trame_server=server) + self.properties_checker = PropertiesChecker( self.tree, trame_server=server ) # TODO put as a modal window self.set_input_file( file_name=self.state.input_file ) # Load components - self.ui = self.build_ui() + self.build_ui() @property def state( self ): @@ -68,7 +84,7 @@ def state( self ): def ctrl( self ): return self.server.controller - def set_input_file( self, file_name, file_str=None ): + def set_input_file( self, file_name ): """sets the input file of the InputTree object and populates simput/ui""" self.tree.set_input_file( file_name ) @@ -80,7 +96,7 @@ def deck_ui( self ): order=1, ): self.deckInspector = DeckInspector( source=self.tree, classes="fit-content" ) - vuetify.VBtn(text="Check fields", classes="ma-4", click=(self.properties_checker.check_fields,)) + vuetify.VBtn( text="Check fields", classes="ma-4", click=( self.properties_checker.check_fields, ) ) with vuetify.VCol( cols=10, @@ -105,6 +121,8 @@ def deck_ui( self ): ): self.deckViewer = DeckViewer( source=self.tree, + region_viewer=self.region_viewer, + well_viewer=self.well_viewer, classes="ma-2", style="flex: 1; height: 60%; width: 100%;", ) @@ -115,7 +133,7 @@ def deck_ui( self ): style="flex: 1; height: 40%; width: 100%;", ) - def build_ui( self, *args, **kwargs ): + def build_ui( self ): """Generates the full UI for the GEOS Trame Application""" with VAppLayout( self.server ) as layout: @@ -123,15 +141,11 @@ def build_ui( self, *args, **kwargs ): self.alertHandler = AlertHandler() - def on_tab_change( tab_idx ): - pass - with html.Div( style="position: relative; display: flex; border-bottom: 1px solid gray", ): with vuetify.VTabs( v_model=( "tab_idx", 0 ), style="z-index: 1;", color="grey", - change=( on_tab_change, "[$event]" ), ): for tab_label in [ "Input File", "Execute", "Results Viewer" ]: vuetify.VTab( tab_label ) @@ -160,21 +174,14 @@ def on_tab_change( tab_idx ): ): vuetify.VBtn( "Run", - # click=self.executor.run, - # disabled=( - # "exe_running || exe_use_threading && exe_threads < 2 || exe_use_mpi && exe_processes < 2", - # ), style="z-index: 1;", ) vuetify.VBtn( "Kill", - # click=self.executor.kill, - # disabled=("!exe_running",), style="z-index: 1;", ) vuetify.VBtn( "Clear", - # click=self.ctrl.terminal_clear, style="z-index: 1;", ) diff --git a/geos-trame/src/geos_trame/app/deck/file.py b/geos-trame/src/geos_trame/app/deck/file.py index 3840aa534..146a2a154 100644 --- a/geos-trame/src/geos_trame/app/deck/file.py +++ b/geos-trame/src/geos_trame/app/deck/file.py @@ -2,103 +2,18 @@ # SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. # SPDX-FileContributor: Lionel Untereiner import os -import re -import typing -from dataclasses import fields, is_dataclass -from io import StringIO -from typing import Any, Iterator, List, TextIO +from typing import Any -import typing_extensions -import typing_inspect from lxml import etree as ElementTree # type: ignore[import-untyped] -from pydantic import BaseModel - -# from xsdata.formats.dataclass.context import XmlContext -# from xsdata.formats.dataclass.parsers import XmlParser from xsdata.formats.dataclass.parsers.config import ParserConfig - -# from xsdata.formats.dataclass.serializers import DictEncoder -# from xsdata.formats.dataclass.serializers import XmlSerializer from xsdata.formats.dataclass.serializers.config import SerializerConfig from xsdata.utils import text from xsdata_pydantic.bindings import DictEncoder, XmlContext, XmlParser, XmlSerializer -# from geos_trame.app.deck.inspector import object_to_tree from geos_trame.app.geosTrameException import GeosTrameException -from geos_trame.schema_generated.schema_mod import Problem - from geos_trame.app.io.xml_parser import XMLParser - - -def get_origin( v: typing.Any ) -> typing.Any: - pydantic_generic_metadata = getattr( v, "__pydantic_generic_metadata__", None ) #: PydanticGenericMetadata | None - if pydantic_generic_metadata: - return pydantic_generic_metadata.get( "origin" ) - return typing_extensions.get_origin( v ) - - -def all_fields( c: type, already_checked ) -> list[ str ]: - resolved_hints = typing.get_type_hints( c ) - field_names = [ field.name for field in fields( c ) ] - resolved_field_types = { name: resolved_hints[ name ] for name in field_names } - - field_list = [] - for key in resolved_field_types: - current_type = resolved_field_types[ key ] - if typing_inspect.get_origin( current_type ) in ( list, typing.List ): - inner_type = typing_inspect.get_args( current_type )[ 0 ] - if inner_type not in already_checked: - already_checked.append( inner_type ) - field_list.extend( all_fields( inner_type, already_checked ) ) - if is_dataclass( current_type ) and current_type not in already_checked: - already_checked.append( current_type ) - field_list.extend( all_fields( current_type, already_checked ) ) - - # {"id": i, "name": f, "children": [], "hidden_children": []} - - return field_list - - -def required_fields( model: type[ BaseModel ], recursive: bool = False ) -> Iterator[ str ]: - for name, field in model.model_fields.items(): - print( name ) - if not field.is_required(): - continue - t = field.annotation - print( t ) - if recursive and isinstance( t, type ) and issubclass( t, BaseModel ): - yield from required_fields( t, recursive=True ) - else: - yield name - - -def is_pydantic_model( obj ): - try: - return issubclass( obj, BaseModel ) - except TypeError: - return False - - -def show_hierarchy( Model: BaseModel, processed_types: set, indent: int = 0 ): - print( type( Model ).__name__ ) - if type( Model ).__name__ not in processed_types: - processed_types.add( type( Model ).__name__ ) - print( processed_types ) - for k, v in Model.model_fields.items(): - print( f'{" "*indent}{k}: ' - f"type={v.annotation}, " - f"required={v.is_required()}" ) - if is_pydantic_model( typing.get_args( v.annotation )[ 0 ] ): - # print("plop") - show_hierarchy( typing.get_args( v.annotation )[ 0 ], processed_types, indent + 2 ) - - -def normalize_path( x ): - tmp = os.path.expanduser( x ) - tmp = os.path.abspath( tmp ) - if os.path.isfile( tmp ): - x = tmp - return x +from geos_trame.app.utils.file_utils import normalize_path +from geos_trame.schema_generated.schema_mod import Problem class DeckFile( object ): @@ -115,6 +30,10 @@ def __init__( self, filename: str, **kwargs ) -> None: """ super( DeckFile, self ).__init__( **kwargs ) + self.inspect_tree: dict[ Any, Any ] | None = None + self.pb_dict: dict[ str, Any ] | None = None + self.problem: Problem | None = None + self.xml_parser: XMLParser | None = None self.root_node = None self.filename = normalize_path( filename ) if self.filename: @@ -162,7 +81,7 @@ def open_deck_file( self, filename: str ) -> None: attribute_name_generator=text.camel_case, ) parser = XmlParser( context=context, config=ParserConfig( - ) ) # fail_on_unknown_properties=True, fail_on_unknown_attributes=True, fail_on_converter_warnings=True)) + ) ) # fail_on_unknown_properties=True, fail_on_unknown_attributes=True, fail_on_converter_warnings=True try: self.problem = parser.parse( simulation_deck, Problem ) except ElementTree.XMLSyntaxError as e: @@ -183,7 +102,7 @@ def to_str( self ) -> str: return serializer.render( self.problem ) -def build_inspect_tree( obj, *, dict_factory=dict ) -> dict: +def build_inspect_tree( obj ) -> dict: """Return the fields of a dataclass instance as a new dictionary mapping field names to field values. @@ -202,8 +121,6 @@ class C: dataclass instances. This will also look into built-in containers: tuples, lists, and dicts. Other objects are copied with 'copy.deepcopy()'. """ - # if not _is_dataclass_instance(obj): - # raise TypeError("asdict() should be called on dataclass instances") return _build_inspect_tree_inner( "Problem", obj, [] ) @@ -214,9 +131,7 @@ def _build_inspect_tree_inner( key, obj, path ) -> dict: sub_node[ "title" ] = obj[ "name" ] else: sub_node[ "title" ] = key - # sub_node["id"] = randrange(150) sub_node[ "children" ] = list() - # sub_node["hidden_children"] = list() sub_node[ "is_drawable" ] = key in [ "VTKMesh", "InternalMesh", @@ -235,205 +150,6 @@ def _build_inspect_tree_inner( key, obj, path ) -> dict: # for another_result in more_results: sub_node[ "children" ].append( more_results ) - # sub_node["path"] = path + [sub_node["name"]] sub_node[ "id" ] = "Problem/" + "/".join( map( str, path ) ) return sub_node - - -def format_attribute( attribute_indent: str, ka: str, attribute_value: str ) -> str: - """Format xml attribute strings - - Args: - attribute_indent (str): Attribute indent string - ka (str): Attribute name - attribute_value (str): Attribute value - - Returns: - str: Formatted attribute value - """ - # Make sure that a space follows commas - attribute_value = re.sub( r",\s*", ", ", attribute_value ) - - # Handle external brackets - attribute_value = re.sub( r"{\s*", "{ ", attribute_value ) - attribute_value = re.sub( r"\s*}", " }", attribute_value ) - - # Consolidate whitespace - attribute_value = re.sub( r"\s+", " ", attribute_value ) - - # Identify and split multi-line attributes - if re.match( r"\s*{\s*({[-+.,0-9a-zA-Z\s]*},?\s*)*\s*}", attribute_value ): - split_positions: List[ Any ] = [ match.end() for match in re.finditer( r"}\s*,", attribute_value ) ] - newline_indent = "\n%s" % ( " " * ( len( attribute_indent ) + len( ka ) + 4 ) ) - new_values = [] - for a, b in zip( [ None ] + split_positions, split_positions + [ None ] ): - new_values.append( attribute_value[ a:b ].strip() ) - if new_values: - attribute_value = newline_indent.join( new_values ) - - return attribute_value - - -def format_xml_level( - output: TextIO, - node: ElementTree.Element, - level: int, - indent: str = " " * 2, - block_separation_max_depth: int = 2, - modify_attribute_indent: bool = False, - sort_attributes: bool = False, - close_tag_newline: bool = False, - include_namespace: bool = False, -) -> None: - """Iteratively format the xml file - - Args: - output (file): the output text file handle - node (lxml.etree.Element): the current xml element - level (int): the xml depth - indent (str): the xml indent style - block_separation_max_depth (int): the maximum depth to separate adjacent elements - modify_attribute_indent (bool): option to have flexible attribute indentation - sort_attributes (bool): option to sort attributes alphabetically - close_tag_newline (bool): option to place close tag on a separate line - include_namespace (bool): option to include the xml namespace in the output - """ - - # Handle comments - if node.tag is ElementTree.Comment: - output.write( "\n%s" % ( indent * level, node.text ) ) - - else: - # Write opening line - opening_line = "\n%s<%s" % ( indent * level, node.tag ) - output.write( opening_line ) - - # Write attributes - if len( node.attrib ) > 0: - # Choose indentation - attribute_indent = "%s" % ( indent * ( level + 1 ) ) - if modify_attribute_indent: - attribute_indent = " " * ( len( opening_line ) ) - - # Get a copy of the attributes - attribute_dict = {} - attribute_dict = node.attrib - - # Sort attribute names - akeys = list( attribute_dict.keys() ) - if sort_attributes: - akeys = sorted( akeys ) - - # Format attributes - for ka in akeys: - # Avoid formatting mathpresso expressions - if not ( node.tag in [ "SymbolicFunction", "CompositeFunction" ] and ka == "expression" ): - attribute_dict[ ka ] = format_attribute( attribute_indent, ka, attribute_dict[ ka ] ) - - for ii in range( 0, len( akeys ) ): - k = akeys[ ii ] - if ( ii == 0 ) & modify_attribute_indent: - # TODO: attrib_ute_dict isn't define here which leads to an error - # output.write(' %s="%s"' % (k, attrib_ute_dict[k])) - pass - else: - output.write( '\n%s%s="%s"' % ( attribute_indent, k, attribute_dict[ k ] ) ) - - # Write children - if len( node ): - output.write( ">" ) - Nc = len( node ) - for ii, child in zip( range( Nc ), node ): - format_xml_level( - output, - child, - level + 1, - indent, - block_separation_max_depth, - modify_attribute_indent, - sort_attributes, - close_tag_newline, - include_namespace, - ) - - # Add space between blocks - if ( ( level < block_separation_max_depth ) - & ( ii < Nc - 1 ) - & ( child.tag is not ElementTree.Comment ) ): - output.write( "\n" ) - - # Write the end tag - output.write( "\n%s" % ( indent * level, node.tag ) ) - else: - if close_tag_newline: - output.write( "\n%s/>" % ( indent * level ) ) - else: - output.write( "/>" ) - - -def format_xml( - input: str, - indent_size: int = 2, - indent_style: bool = False, - block_separation_max_depth: int = 2, - alphebitize_attributes: bool = False, - close_style: bool = False, - namespace: bool = False, -) -> None: - """Script to format xml files - - Args: - input (str): Input str - indent_size (int): Indent size - indent_style (bool): Style of indentation (0=fixed, 1=hanging) - block_separation_max_depth (int): Max depth to separate xml blocks - alphebitize_attributes (bool): Alphebitize attributes - close_style (bool): Style of close tag (0=same line, 1=new line) - namespace (bool): Insert this namespace in the xml description - """ - try: - root = ElementTree.fromstring( input ) - # root = tree.getroot() - prologue_comments = [ tmp.text for tmp in root.itersiblings( preceding=True ) ] - epilog_comments = [ tmp.text for tmp in root.itersiblings() ] - - f = StringIO() - f.write( '\n' ) - - for comment in reversed( prologue_comments ): - f.write( "\n" % ( comment ) ) - - format_xml_level( - f, - root, - 0, - indent=" " * indent_size, - block_separation_max_depth=block_separation_max_depth, - modify_attribute_indent=indent_style, - sort_attributes=alphebitize_attributes, - close_tag_newline=close_style, - include_namespace=namespace, - ) - - for comment in epilog_comments: - f.write( "\n" % ( comment ) ) - f.write( "\n" ) - - return f.getvalue() - - except ElementTree.ParseError as err: - print( "\nCould not load file: %s" % ( f ) ) - print( err.msg ) - raise Exception( "\nCheck input file!" ) - - -if __name__ == "__main__": - import sys - - if len( sys.argv ) < 2: - print( "Need an input file as argument" ) - exit( 1 ) - filename = sys.argv[ 1 ] - deck_file = DeckFile( filename ) - print( deck_file.root_fields ) diff --git a/geos-trame/src/geos_trame/app/deck/tree.py b/geos-trame/src/geos_trame/app/deck/tree.py index 708bf34b5..d63a978da 100644 --- a/geos-trame/src/geos_trame/app/deck/tree.py +++ b/geos-trame/src/geos_trame/app/deck/tree.py @@ -11,22 +11,18 @@ from xsdata.utils import text from xsdata_pydantic.bindings import DictDecoder, XmlContext, XmlSerializer +from geos_trame.app.deck.file import DeckFile from geos_trame.app.geosTrameException import GeosTrameException +from geos_trame.app.utils.file_utils import normalize_path, format_xml from geos_trame.schema_generated.schema_mod import BaseModel, Problem, Included, File -from .file import DeckFile, format_xml, normalize_path - from collections import defaultdict from trame_simput import get_simput_manager -def recursive_dict( element ): - return element.tag, dict( map( recursive_dict, element ) ) or element.text - - class DeckTree( object ): """ - A tree that represents an deck file along with all the available blocks and parameters. + A tree that represents a deck file along with all the available blocks and parameters. """ def __init__( self, sm_id=None, **kwds ): @@ -82,16 +78,16 @@ def update( self, path, key, value ) -> None: new_path.append( key ) funcy.set_in( self.input_file.pb_dict, new_path, value ) - def search( self, path ) -> dict: - new_path = [ int( x ) if x.isdigit() else x for x in path.split( "/" ) ] + def search( self, path ) -> list | None: + new_path = path.split( "/" ) if self.input_file is None: - return + return None return dpath.values( self.input_file.pb_dict, new_path ) def decode( self, path ): data = self.search( path ) if data is None: - return + return None context = XmlContext( element_name_generator=text.pascal_case, @@ -101,7 +97,7 @@ def decode( self, path ): node = decoder.decode( data[ 0 ] ) return node - def decode_data( self, data: BaseModel ) -> str: + def decode_data( self, data: BaseModel | None ) -> str: """ Convert a data to a xml serializable file """ @@ -385,14 +381,3 @@ def _split( self, xml: str ) -> dict[ str, str ]: restructured_files[ file_path ][ tag ] = contents return restructured_files - - -if __name__ == "__main__": - import sys - - if len( sys.argv ) < 3: - print( "Usage: " ) - exit( 1 ) - input_file_path = sys.argv[ 2 ] - deck_tree = DeckTree() - deck_tree.setInputFile( input_file_path ) diff --git a/geos-trame/src/geos_trame/app/io/data_loader.py b/geos-trame/src/geos_trame/app/io/data_loader.py new file mode 100644 index 000000000..6095c9725 --- /dev/null +++ b/geos-trame/src/geos_trame/app/io/data_loader.py @@ -0,0 +1,136 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. +# SPDX-FileContributor: Kitware +from typing import Type, Any + +import numpy as np +from trame_client.widgets.core import AbstractElement +import pyvista as pv + +from geos_trame.app.geosTrameException import GeosTrameException +from geos_trame.app.ui.viewer.regionViewer import RegionViewer +from geos_trame.app.ui.viewer.wellViewer import WellViewer +from geos_trame.app.utils.pv_utils import read_unstructured_grid +from geos_trame.schema_generated.schema_mod import ( + Vtkmesh, + Vtkwell, + Perforation, + InternalWell, +) + + +class DataLoader( AbstractElement ): + + def __init__( self, source, region_viewer: RegionViewer, well_viewer: WellViewer, **kwargs ): + super().__init__( "span", **kwargs ) + + self.source = source + self.region_viewer = region_viewer + self.well_viewer = well_viewer + + self.state.change( "object_state" )( self.update_object_state ) + + def update_object_state( self, object_state: tuple[ str, bool ], **_ ): + + path, show_obj = object_state + + if path == "": + return + + active_block = self.source.decode( path ) + + if isinstance( active_block, Vtkmesh ): + self._update_vtkmesh( active_block, show_obj ) + + if isinstance( active_block, Vtkwell ): + if self.region_viewer.input.number_of_cells == 0 and show_obj: + self.ctrl.on_add_warning( + "Can't display " + active_block.name, + "Please display the mesh before creating a well.", + ) + return + + self._update_vtkwell( active_block, path, show_obj ) + + if isinstance( active_block, InternalWell ): + if self.region_viewer.input.number_of_cells == 0 and show_obj: + self.ctrl.on_add_warning( + "Can't display " + active_block.name, + "Please display the mesh before creating a well", + ) + return + + self._update_internalwell( active_block, path, show_obj ) + + if isinstance( active_block, Perforation ): + if self.well_viewer.get_number_of_wells() == 0 and show_obj: + self.ctrl.on_add_warning( + "Can't display " + active_block.name, + "Please display a well before creating a perforation", + ) + return + + self.ctrl.update_viewer( active_block, path, show_obj ) + + def _update_vtkmesh( self, mesh: Vtkmesh, show: bool ) -> None: + if not show: + self.region_viewer.reset() + return + + unstructured_grid = read_unstructured_grid( self.source.get_abs_path( mesh.file ) ) + self.region_viewer.add_mesh( unstructured_grid ) + + def _update_vtkwell( self, well: Vtkwell, path: str, show: bool ) -> None: + if not show: + self.well_viewer.remove( path ) + return + + well_polydata = pv.read( self.source.get_abs_path( well.file ) ).cast_to_poly_points() + self.well_viewer.add_mesh( well_polydata, path ) + + def _update_internalwell( self, well: InternalWell, path: str, show: bool ) -> None: + """ + Used to control the visibility of the InternalWell. + This method will create the mesh if it doesn't exist. + """ + if not show: + self.well_engine.remove( path ) + return + + points = self.__parse_polyline_property( well.polyline_node_coords, dtype=float ) + connectivity = self.__parse_polyline_property( well.polyline_segment_conn, dtype=int ) + connectivity = connectivity.flatten() + + sorted_points = [] + for point_id in connectivity: + sorted_points.append( points[ point_id ] ) + + well_polydata = pv.MultipleLines( sorted_points ) + self.well_engine.add_mesh( well_polydata, path ) + + @staticmethod + def __parse_polyline_property( polyline_property: str, dtype: Type[ Any ] ) -> np.ndarray: + """ + Internal method used to parse and convert a property, such as polyline_node_coords, from an InternalWell. + This string always follow this for : + "{ { 800, 1450, 395.646 }, { 800, 1450, -554.354 } }" + """ + try: + nodes_str = polyline_property.split( "}, {" ) + points = [] + for i in range( 0, len( nodes_str ) ): + + nodes_str[ i ] = nodes_str[ i ].replace( " ", "" ) + nodes_str[ i ] = nodes_str[ i ].replace( "{", "" ) + nodes_str[ i ] = nodes_str[ i ].replace( "}", "" ) + + point = np.array( nodes_str[ i ].split( "," ), dtype=dtype ) + + points.append( point ) + + return np.array( points, dtype=dtype ) + except ValueError: + raise GeosTrameException( + "cannot be able to convert the property into a numeric array: ", + ValueError, + ) diff --git a/geos-trame/src/geos_trame/app/io/xml_parser.py b/geos-trame/src/geos_trame/app/io/xml_parser.py index a5e9a5958..13a74e5e6 100644 --- a/geos-trame/src/geos_trame/app/io/xml_parser.py +++ b/geos-trame/src/geos_trame/app/io/xml_parser.py @@ -22,7 +22,7 @@ class XMLParser( object ): Useful to be able to able to save it later. """ - def __init__( self, filename: str ) -> None: + def __init__( self, filename: str ): """ Constructor which takes in input the xml file used to generate pedantic file. """ diff --git a/geos-trame/src/geos_trame/app/types/renderable.py b/geos-trame/src/geos_trame/app/types/renderable.py new file mode 100644 index 000000000..58e31d8af --- /dev/null +++ b/geos-trame/src/geos_trame/app/types/renderable.py @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. +# SPDX-FileContributor: Kitware +from enum import Enum + + +class Renderable( Enum ): + VTKMESH = "VTKMesh" + INTERNALMESH = "InternalMesh" + INTERNALWELL = "InternalWell" + PERFORATION = "Perforation" + VTKWELL = "VTKWell" \ No newline at end of file diff --git a/geos-trame/src/geos_trame/app/types/tree_node.py b/geos-trame/src/geos_trame/app/types/tree_node.py new file mode 100644 index 000000000..b637fe9f9 --- /dev/null +++ b/geos-trame/src/geos_trame/app/types/tree_node.py @@ -0,0 +1,37 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. +# SPDX-FileContributor: Kitware +from dataclasses import dataclass + + +@dataclass +class TreeNode: + id: str + title: str + children: list + hidden_children: list + is_drawable: bool + drawn: bool + valid: int + + @property + def json( self ) -> dict: + if self.children: + return dict( + id=self.id, + title=self.title, + is_drawable=self.is_drawable, + drawn=self.drawn, + valid=self.valid, + children=[ c.json for c in self.children ], + hidden_children=[ c.json for c in self.hidden_children ], + ) + return dict( + id=self.id, + title=self.title, + is_drawable=self.is_drawable, + drawn=self.drawn, + valid=self.valid, + children=None, + hidden_children=[], + ) \ No newline at end of file diff --git a/geos-trame/src/geos_trame/app/ui/inspector.py b/geos-trame/src/geos_trame/app/ui/inspector.py index 75951b4af..ec28661ee 100644 --- a/geos-trame/src/geos_trame/app/ui/inspector.py +++ b/geos-trame/src/geos_trame/app/ui/inspector.py @@ -1,176 +1,16 @@ # SPDX-License-Identifier: Apache-2.0 # SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. # SPDX-FileContributor: Lionel Untereiner -from dataclasses import dataclass -from enum import Enum +from typing import Any import yaml from pydantic import BaseModel from trame.widgets import vuetify3 as vuetify, html from trame_simput import get_simput_manager -from typing import Any - - -class Renderable( Enum ): - VTKMESH = "VTKMesh" - INTERNALMESH = "InternalMesh" - INTERNALWELL = "InternalWell" - VTKWELL = "VTKWell" - PERFORATION = "Perforation" - - -# Pure pydantic version -# -# class TreeNode(BaseModel): -# id: str -# name: str -# is_drawable: bool -# drawn: bool -# children: list['TreeNode'] -# hidden_children: list['TreeNode'] - -# def get_node(obj, node_id, path): -# children = [] -# for name, info in obj.model_fields.items(): -# if name in obj.model_fields_set: -# print(type(info)) -# print(name, "-", info.annotation, " - ", get_origin(info.annotation), get_args(info.annotation)[0]) -# metadata = getattr(info, "xsdata_metadata", None) or {} -# print(metadata["name"]) -# if get_origin(info.annotation) is list: -# attr= getattr(obj, name) -# print(attr) -# for idx, item in enumerate(attr): -# children.append(get_node(item, name, path + [name] + [idx])) - -# return TreeNode( -# id = "Problem/" + "/".join(map(str, path)), -# name = "metadata", -# children = children, -# hidden_children = [], -# is_drawable = node_id in Renderable, -# drawn = False, -# ) - - -@dataclass -class TreeNode: - id: str - title: str - children: list - hidden_children: list - is_drawable: bool - drawn: bool - valid: int - - @property - def json( self ) -> dict: - if self.children: - return dict( - id=self.id, - title=self.title, - is_drawable=self.is_drawable, - drawn=self.drawn, - valid=self.valid, - children=[ c.json for c in self.children ], - hidden_children=[ c.json for c in self.hidden_children ], - ) - return dict( - id=self.id, - title=self.title, - is_drawable=self.is_drawable, - drawn=self.drawn, - valid=self.valid, - children=None, - hidden_children=[], - ) - - -def get_node_dict( obj, node_id, path ): - children = [] - for key, value in obj.items(): - # todo look isinstance(value, dict): - if isinstance( value, list ): - for idx, item in enumerate( value ): - if isinstance( item, dict ): - children.append( get_node_dict( item, key, path + [ key ] + [ idx ] ) ) - - node_name = node_id - if "name" in obj: - node_name = obj[ "name" ] - - return TreeNode( - id="Problem/" + "/".join( map( str, path ) ), - title=node_name, - children=children if len( children ) else [], - hidden_children=[], - is_drawable=node_id in ( k.value for k in Renderable ), - drawn=False, - valid=0 - ) - - -def object_to_tree( obj: dict ) -> dict: - return get_node_dict( obj, "Problem", [] ).json - - -def dump( item ): - match item: - case BaseModel() as model: - subitems: dict[ str, Any ] = dict() - model.model_fields - - for field, value in model: - - if isinstance( value, str ): - subitems[ field ] = value - continue - - return subitems - case list() | tuple() | set(): # pyright: ignore - # Pyright finds this disgusting; this passes `mypy` though. ` # type: - # ignore` would fail `mypy` is it'd be unused (because there's nothing to - # ignore because `mypy` is content) - # return type(container)( # pyright: ignore - # _dump(i) for i in container # pyright: ignore - # ) - pass - case dict(): - # return { - # k: _dump(v) - # for k, v in item.items() # pyright: ignore[reportUnknownVariableType] - # } - pass - case _: - return item - - -def iterate_nested_dict( iterable, returned="key" ): - """Returns an iterator that returns all keys or values - of a (nested) iterable. - - Arguments: - - iterable: or - - returned: "key" or "value" - - Returns: - - - """ - - if isinstance( iterable, dict ): - for key, value in iterable.items(): - if key == "id": - if not ( isinstance( value, dict ) or isinstance( value, list ) ): - yield value - # else: - # raise ValueError("'returned' keyword only accepts 'key' or 'value'.") - for ret in iterate_nested_dict( value, returned=returned ): - yield ret - elif isinstance( iterable, list ): - for el in iterable: - for ret in iterate_nested_dict( el, returned=returned ): - yield ret +from geos_trame.app.types.renderable import Renderable +from geos_trame.app.types.tree_node import TreeNode +from geos_trame.app.utils.dict_utils import iterate_nested_dict vuetify.enable_lab() @@ -201,7 +41,7 @@ def __init__( self, listen_to_active=True, source=None, **kwargs ): self._source = None self.listen_to_active = listen_to_active - self.state.object_state = [ "", False ] + self.state.object_state = ( "", False ) # register used types from Problem self.simput_types = [] @@ -225,26 +65,21 @@ def on_change( topic, ids=None, **kwargs ): with self: with vuetify.Template( v_slot_append="{ item }" ): - with vuetify.VTooltip(v_if=("item.valid == 2",)): + with vuetify.VTooltip( v_if=( "item.valid == 2", ) ): with vuetify.Template( - v_slot_activator=("{ props }",), - __properties__=[("v_slot_activator", "v-slot:activator")], + v_slot_activator=( "{ props }", ), + __properties__=[ ( "v_slot_activator", "v-slot:activator" ) ], ): - vuetify.VIcon( - v_bind=("props",), - classes="mr-2", - icon="mdi-close", - color="red" - ) - html.Div(v_if=("item.invalid_properties",), v_text=("'Invalid properties: ' + item.invalid_properties",)) - html.Div(v_if=("item.invalid_children",), v_text=("'Invalid children: ' + item.invalid_children",)) - - vuetify.VIcon( - v_if=("item.valid < 2",), - classes="mr-2", - icon='mdi-check', - color=("['gray', 'green'][item.valid]",) - ) + vuetify.VIcon( v_bind=( "props", ), classes="mr-2", icon="mdi-close", color="red" ) + html.Div( v_if=( "item.invalid_properties", ), + v_text=( "'Invalid properties: ' + item.invalid_properties", ) ) + html.Div( v_if=( "item.invalid_children", ), + v_text=( "'Invalid children: ' + item.invalid_children", ) ) + + vuetify.VIcon( v_if=( "item.valid < 2", ), + classes="mr-2", + icon='mdi-check', + color=( "['gray', 'green'][item.valid]", ) ) vuetify.VCheckboxBtn( v_if="item.is_drawable", focused=True, dense=True, @@ -252,10 +87,10 @@ def on_change( topic, ids=None, **kwargs ): icon=True, false_icon="mdi-eye-off", true_icon="mdi-eye", - update_modelValue=( self.to_draw_change, "[ item, item.id, $event ] " ) ) + update_modelValue=( self.to_draw_change, "[ item.id, $event ] " ) ) - def to_draw_change( self, item, item_id, drawn ): - self.state.object_state = [ item_id, drawn ] + def to_draw_change( self, item_id, drawn ): + self.state.object_state = ( item_id, drawn ) @property def source( self ): @@ -328,3 +163,60 @@ def change_current_id( self, item_id=None ): return self.state.active_id = item_id + + +def get_node_dict( obj, node_id, path ): + children = [] + for key, value in obj.items(): + # todo look isinstance(value, dict): + if isinstance( value, list ): + for idx, item in enumerate( value ): + if isinstance( item, dict ): + children.append( get_node_dict( item, key, path + [ key ] + [ idx ] ) ) + + node_name = node_id + if "name" in obj: + node_name = obj[ "name" ] + + return TreeNode( id="Problem/" + "/".join( map( str, path ) ), + title=node_name, + children=children if len( children ) else [], + hidden_children=[], + is_drawable=node_id in ( k.value for k in Renderable ), + drawn=False, + valid=0 ) + + +def object_to_tree( obj: dict ) -> dict: + return get_node_dict( obj, "Problem", [] ).json + + +def dump( item ): + match item: + case BaseModel() as model: + subitems: dict[ str, Any ] = dict() + model.model_fields + + for field, value in model: + + if isinstance( value, str ): + subitems[ field ] = value + continue + + return subitems + case list() | tuple() | set(): # pyright: ignore + # Pyright finds this disgusting; this passes `mypy` though. ` # type: + # ignore` would fail `mypy` is it'd be unused (because there's nothing to + # ignore because `mypy` is content) + # return type(container)( # pyright: ignore + # _dump(i) for i in container # pyright: ignore + # ) + pass + case dict(): + # return { + # k: _dump(v) + # for k, v in item.items() # pyright: ignore[reportUnknownVariableType] + # } + pass + case _: + return item diff --git a/geos-trame/src/geos_trame/app/ui/timeline.py b/geos-trame/src/geos_trame/app/ui/timeline.py index 7dba36672..3d09926d3 100644 --- a/geos-trame/src/geos_trame/app/ui/timeline.py +++ b/geos-trame/src/geos_trame/app/ui/timeline.py @@ -1,7 +1,7 @@ # SPDX-License-Identifier: Apache-2.0 # SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. # SPDX-FileContributor: Lionel Untereiner -from trame.widgets import code, gantt, html, simput +from trame.widgets import gantt from trame.widgets import vuetify3 as vuetify from trame_simput import get_simput_manager @@ -15,32 +15,6 @@ def __init__( self, source=None, **kwargs ): self.simput_manager = get_simput_manager( id=self.state.sm_id ) items = self.tree.timeline() - # print(items) - - # DRAFT - # items = [ - # {"id": 1, - # "summary": "outputInjectionPeriod", - # "start_date": "2024-11-02 00:00", - # "end_date": "2024-11-02 00:00", - # "duration": 23 - # }, - # { - # "id": 2, - # "summary": "This is a task with a longer description.", - # "start_date": "2024-11-03 00:00", - # "end_date": "2024-11-04 00:00", - # "duration": 1 - # } - # ] - - items_alt = [ { - "id": 3, - "summary": "Lorem ipsum.", - "start_date": "2024-11-07 00:00", - "end_date": "2024-11-09 00:00", - "duration": 2, - } ] fields = [ { "summary": { @@ -72,12 +46,6 @@ def __init__( self, source=None, **kwargs ): } ] with self: - # with vuetify.VRow( - # rows=2, - # style="width: 100%;", - # dense=True - # classes="fill-height" - # ): vuetify.VCardTitle( "Events View" ) vuetify.VDateInput( label="Select starting simulation date", diff --git a/geos-trame/src/geos_trame/app/ui/viewer/regionViewer.py b/geos-trame/src/geos_trame/app/ui/viewer/regionViewer.py index 0ab47c7fd..54bb2ea7b 100644 --- a/geos-trame/src/geos_trame/app/ui/viewer/regionViewer.py +++ b/geos-trame/src/geos_trame/app/ui/viewer/regionViewer.py @@ -10,10 +10,10 @@ class RegionViewer: This mesh is represented in GEOS with a Region. """ + input: pv.UnstructuredGrid + clip: pv.UnstructuredGrid def __init__( self ) -> None: - self.input: pv.UnstructuredGrid - self.clip: pv.UnstructuredGrid self.reset() def __call__( self, normal: tuple[ float ], origin: tuple[ float ] ) -> None: diff --git a/geos-trame/src/geos_trame/app/ui/viewer/viewer.py b/geos-trame/src/geos_trame/app/ui/viewer/viewer.py index b5d11acdf..b7e8dde03 100644 --- a/geos-trame/src/geos_trame/app/ui/viewer/viewer.py +++ b/geos-trame/src/geos_trame/app/ui/viewer/viewer.py @@ -3,9 +3,12 @@ # SPDX-FileContributor: Lucas Givord - Kitware import pyvista as pv from pyvista.trame.ui import plotter_ui -from trame.widgets import vuetify3 as vuetify from trame.widgets import html +from trame.widgets import vuetify3 as vuetify +import geos_trame.app.ui.viewer.perforationViewer as PerforationViewer +import geos_trame.app.ui.viewer.regionViewer as RegionViewer +import geos_trame.app.ui.viewer.wellViewer as WellViewer from geos_trame.schema_generated.schema_mod import ( Vtkmesh, Vtkwell, @@ -13,14 +16,6 @@ InternalWell, ) -import geos_trame.app.ui.viewer.regionViewer as RegionViewer -import geos_trame.app.ui.viewer.wellViewer as WellViewer -import geos_trame.app.ui.viewer.perforationViewer as PerforationViewer -from geos_trame.app.geosTrameException import GeosTrameException - -import numpy as np -from typing import Type, Any - pv.OFF_SCREEN = True @@ -41,7 +36,7 @@ class DeckViewer( vuetify.VCard ): Perforation settings. """ - def __init__( self, source, **kwargs ): + def __init__( self, source, region_viewer: RegionViewer, well_viewer: WellViewer, **kwargs ): super().__init__( **kwargs ) self._source = source @@ -52,11 +47,11 @@ def __init__( self, source, **kwargs ): self.server.state[ self.CUT_PLANE ] = True self.server.state[ self.ZAMPLIFICATION ] = 1 - self.region_engine = RegionViewer.RegionViewer() - self.well_engine = WellViewer.WellViewer( 5, 5 ) + self.region_engine = region_viewer + self.well_engine = well_viewer self._perforations: dict[ str, PerforationViewer.PerforationViewer ] = dict() - self.state.change( "object_state" )( self.update_viewer ) + self.ctrl.update_viewer.add( self.update_viewer ) with self: vuetify.VCardTitle( "3D View" ) @@ -96,51 +91,24 @@ def rendering_menu_extra_items( self ): ) html.Span( "Show/Hide widgets" ) - def update_viewer( self, object_state: list[ str, bool ], **kwargs ) -> None: + def update_viewer( self, active_block, path, show_obj ) -> None: """ Add from path the dataset given by the user. Supported data type is: Vtkwell, Vtkmesh, InternalWell, Perforation. object_state : array used to store path to the data and if we want to show it or not. """ - path = object_state[ 0 ] - show_obj = object_state[ 1 ] - - if path == "": - return - active_block = self.source.decode( path ) if isinstance( active_block, Vtkmesh ): - self._update_vtkmesh( active_block, show_obj ) + self._update_vtkmesh( show_obj ) if isinstance( active_block, Vtkwell ): - if self.region_engine.input.number_of_cells == 0 and show_obj: - - self.ctrl.on_add_warning( - "Can't display " + active_block.name, - "Please display the mesh before creating a well.", - ) - return - - self._update_vtkwell( active_block, path, show_obj ) + self._update_vtkwell( path, show_obj ) if isinstance( active_block, InternalWell ): - if self.region_engine.input.number_of_cells == 0 and show_obj: - self.ctrl.on_add_warning( - "Can't display " + active_block.name, - "Please display the mesh before creating a well", - ) - return - - self._update_internalwell( active_block, path, show_obj ) + self._update_internalwell( path, show_obj ) if isinstance( active_block, Perforation ): - if self.well_engine.get_number_of_wells() == 0 and show_obj: - self.ctrl.on_add_warning( - "Can't display " + active_block.name, - "Please display a well before creating a perforation", - ) - return self._update_perforation( active_block, show_obj, path ) def _on_clip_visibility_change( self, **kwargs ): @@ -210,58 +178,43 @@ def _on_change_perforation_size( self, value ) -> None: for key, perforation in self._perforations.items(): perforation.update_perforation_radius( value ) - def _get_perforation_size( self ) -> float: + def _get_perforation_size( self ) -> float | None: if len( self._perforations ) <= 0: - return 5 + return 5.0 for key, perforation in self._perforations.items(): return perforation.get_perforation_size() + return None - def _update_internalwell( self, well: InternalWell, path: str, show: bool ) -> None: + def _update_internalwell( self, path: str, show: bool ) -> None: """ Used to control the visibility of the InternalWell. This method will create the mesh if it doesn't exist. """ if not show: self.plotter.remove_actor( self.well_engine.get_actor( path ) ) - self.well_engine.remove( path ) return - points = self.__parse_polyline_property( well.polyline_node_coords, dtype=float ) - connectivity = self.__parse_polyline_property( well.polyline_segment_conn, dtype=int ) - connectivity = connectivity.flatten() - - sorted_points = [] - for id in connectivity: - sorted_points.append( points[ id ] ) - - well_polydata = pv.MultipleLines( sorted_points ) - index = self.well_engine.add_mesh( well_polydata, path ) - - tube_actor = self.plotter.add_mesh( self.well_engine.get_tube( index ) ) + tube_actor = self.plotter.add_mesh( self.well_engine.get_tube( self.well_engine.get_last_mesh_idx() ) ) self.well_engine.append_actor( path, tube_actor ) self.server.controller.view_update() - def _update_vtkwell( self, well: Vtkwell, path: str, show: bool ) -> None: + def _update_vtkwell( self, path: str, show: bool ) -> None: """ Used to control the visibility of the Vtkwell. This method will create the mesh if it doesn't exist. """ if not show: self.plotter.remove_actor( self.well_engine.get_actor( path ) ) - self.well_engine.remove( path ) return - well_polydata = pv.PolyData.SafeDownCast( pv.read( self.source.get_abs_path( well.file ) ) ) - index = self.well_engine.add_mesh( well_polydata, path ) - - tube_actor = self.plotter.add_mesh( self.well_engine.get_tube( index ) ) + tube_actor = self.plotter.add_mesh( self.well_engine.get_tube( self.well_engine.get_last_mesh_idx() ) ) self.well_engine.append_actor( path, tube_actor ) self.server.controller.view_update() - def _update_vtkmesh( self, mesh: Vtkmesh, show: bool ) -> None: + def _update_vtkmesh( self, show: bool ) -> None: """ Used to control the visibility of the Vtkmesh. This method will create the mesh if it doesn't exist. @@ -270,13 +223,10 @@ def _update_vtkmesh( self, mesh: Vtkmesh, show: bool ) -> None: """ if not show: - self.region_engine.reset() self.plotter.clear_plane_widgets() self.plotter.remove_actor( self._clip_mesh ) return - unsctructured_grid = pv.UnstructuredGrid.SafeDownCast( pv.read( self.source.get_abs_path( mesh.file ) ) ) - self.region_engine.add_mesh( unsctructured_grid ) active_scalar = self.region_engine.input.active_scalars_name self._clip_mesh = self.plotter.add_mesh_clip_plane( self.region_engine.input, @@ -328,41 +278,15 @@ def _add_perforation( self, distance_from_head: float, path: str ) -> None: point[ 2 ] - distance_from_head, ] - center = [ point[ 0 ], point[ 1 ], point[ 2 ] - float( distance_from_head ) ] + center = [ float( point[ 0 ] ), float( point[ 1 ] ), point[ 2 ] - float( distance_from_head ) ] sphere = pv.Sphere( radius=5, center=center ) perforation_actor = self.plotter.add_mesh( sphere ) saved_perforation = PerforationViewer.PerforationViewer( sphere, center, 5, perforation_actor ) - id = self.region_engine.input.find_closest_cell( point_offsetted ) - cell = self.region_engine.input.extract_cells( [ id ] ) + cell_id = self.region_engine.input.find_closest_cell( point_offsetted ) + cell = self.region_engine.input.extract_cells( [ cell_id ] ) cell_actor = self.plotter.add_mesh( cell ) saved_perforation.add_extracted_cell( cell_actor ) self._perforations[ path ] = saved_perforation - - def __parse_polyline_property( self, property: str, dtype: Type[ Any ] ) -> np.ndarray[ Any ]: - """ - Internal method used to parse and convert a property, such as polyline_node_coords, from an InternalWell. - This string always follow this for : - "{ { 800, 1450, 395.646 }, { 800, 1450, -554.354 } }" - """ - try: - nodes_str = property.split( "}, {" ) - points = [] - for i in range( 0, len( nodes_str ) ): - - nodes_str[ i ] = nodes_str[ i ].replace( " ", "" ) - nodes_str[ i ] = nodes_str[ i ].replace( "{", "" ) - nodes_str[ i ] = nodes_str[ i ].replace( "}", "" ) - - point = np.array( nodes_str[ i ].split( "," ), dtype=dtype ) - - points.append( point ) - - return np.array( points, dtype=dtype ) - except ValueError: - raise GeosTrameException( - "cannot be able to convert the property into a numeric array: ", - ValueError, - ) diff --git a/geos-trame/src/geos_trame/app/ui/viewer/wellViewer.py b/geos-trame/src/geos_trame/app/ui/viewer/wellViewer.py index ece7d5482..0f95067f3 100644 --- a/geos-trame/src/geos_trame/app/ui/viewer/wellViewer.py +++ b/geos-trame/src/geos_trame/app/ui/viewer/wellViewer.py @@ -38,6 +38,9 @@ def __init__( self, size: float, amplification: float ) -> None: def __call__( self, value: float ) -> None: self.update( value ) + def get_last_mesh_idx( self ): + return len( self._wells ) - 1 + def add_mesh( self, mesh: pv.PolyData, mesh_path: str ) -> int: """ Store a given mesh representing a polyline. @@ -88,7 +91,7 @@ def append_actor( self, perforation_path: str, tube_actor: pv.Actor ) -> None: index = self._get_index_from_perforation( perforation_path ) if index == -1: print( "Cannot found the well to remove from path: ", perforation_path ) - return None + return self._wells[ index ].actor = tube_actor diff --git a/geos-trame/src/geos_trame/app/utils/dict_utils.py b/geos-trame/src/geos_trame/app/utils/dict_utils.py new file mode 100644 index 000000000..d6df582a7 --- /dev/null +++ b/geos-trame/src/geos_trame/app/utils/dict_utils.py @@ -0,0 +1,30 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. +# SPDX-FileContributor: Kitware + + +def iterate_nested_dict( iterable, returned="key" ): + """Returns an iterator that returns all keys or values + of a (nested) iterable. + + Arguments: + - iterable: or + - returned: "key" or "value" + + Returns: + - + """ + + if isinstance( iterable, dict ): + for key, value in iterable.items(): + if key == "id": + if not ( isinstance( value, dict ) or isinstance( value, list ) ): + yield value + # else: + # raise ValueError("'returned' keyword only accepts 'key' or 'value'.") + for ret in iterate_nested_dict( value, returned=returned ): + yield ret + elif isinstance( iterable, list ): + for el in iterable: + for ret in iterate_nested_dict( el, returned=returned ): + yield ret diff --git a/geos-trame/src/geos_trame/app/utils/file_utils.py b/geos-trame/src/geos_trame/app/utils/file_utils.py new file mode 100644 index 000000000..c357f17ef --- /dev/null +++ b/geos-trame/src/geos_trame/app/utils/file_utils.py @@ -0,0 +1,203 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. +# SPDX-FileContributor: Kitware +import os +import re +from io import StringIO +from typing import Any, List, TextIO +from lxml import etree as ElementTree # type: ignore[import-untyped] + + +def normalize_path( x ): + tmp = os.path.expanduser( x ) + tmp = os.path.abspath( tmp ) + if os.path.isfile( tmp ): + x = tmp + return x + + +def format_attribute( attribute_indent: str, ka: str, attribute_value: str ) -> str: + """Format xml attribute strings + + Args: + attribute_indent (str): Attribute indent string + ka (str): Attribute name + attribute_value (str): Attribute value + + Returns: + str: Formatted attribute value + """ + # Make sure that a space follows commas + attribute_value = re.sub( r",\s*", ", ", attribute_value ) + + # Handle external brackets + attribute_value = re.sub( r"{\s*", "{ ", attribute_value ) + attribute_value = re.sub( r"\s*}", " }", attribute_value ) + + # Consolidate whitespace + attribute_value = re.sub( r"\s+", " ", attribute_value ) + + # Identify and split multi-line attributes + if re.match( r"\s*{\s*({[-+.,0-9a-zA-Z\s]*},?\s*)*\s*}", attribute_value ): + split_positions: List[ Any ] = [ match.end() for match in re.finditer( r"}\s*,", attribute_value ) ] + newline_indent = "\n%s" % ( " " * ( len( attribute_indent ) + len( ka ) + 4 ) ) + new_values = [] + for a, b in zip( [ None ] + split_positions, split_positions + [ None ] ): + new_values.append( attribute_value[ a:b ].strip() ) + if new_values: + attribute_value = newline_indent.join( new_values ) + + return attribute_value + + +def format_xml_level( + output: TextIO, + node: ElementTree.Element, + level: int, + indent: str = " " * 2, + block_separation_max_depth: int = 2, + modify_attribute_indent: bool = False, + sort_attributes: bool = False, + close_tag_newline: bool = False, + include_namespace: bool = False, +) -> None: + """Iteratively format the xml file + + Args: + output (file): the output text file handle + node (lxml.etree.Element): the current xml element + level (int): the xml depth + indent (str): the xml indent style + block_separation_max_depth (int): the maximum depth to separate adjacent elements + modify_attribute_indent (bool): option to have flexible attribute indentation + sort_attributes (bool): option to sort attributes alphabetically + close_tag_newline (bool): option to place close tag on a separate line + include_namespace (bool): option to include the xml namespace in the output + """ + + # Handle comments + if node.tag is ElementTree.Comment: + output.write( "\n%s" % ( indent * level, node.text ) ) + + else: + # Write opening line + opening_line = "\n%s<%s" % ( indent * level, node.tag ) + output.write( opening_line ) + + # Write attributes + if len( node.attrib ) > 0: + # Choose indentation + attribute_indent = "%s" % ( indent * ( level + 1 ) ) + if modify_attribute_indent: + attribute_indent = " " * ( len( opening_line ) ) + + # Get a copy of the attributes + attribute_dict = {} + attribute_dict = node.attrib + + # Sort attribute names + akeys = list( attribute_dict.keys() ) + if sort_attributes: + akeys = sorted( akeys ) + + # Format attributes + for ka in akeys: + # Avoid formatting mathpresso expressions + if not ( node.tag in [ "SymbolicFunction", "CompositeFunction" ] and ka == "expression" ): + attribute_dict[ ka ] = format_attribute( attribute_indent, ka, attribute_dict[ ka ] ) + + for ii in range( 0, len( akeys ) ): + k = akeys[ ii ] + if ( ii == 0 ) & modify_attribute_indent: + # TODO: attrib_ute_dict isn't define here which leads to an error + # output.write(' %s="%s"' % (k, attrib_ute_dict[k])) + pass + else: + output.write( '\n%s%s="%s"' % ( attribute_indent, k, attribute_dict[ k ] ) ) + + # Write children + if len( node ): + output.write( ">" ) + Nc = len( node ) + for ii, child in zip( range( Nc ), node ): + format_xml_level( + output, + child, + level + 1, + indent, + block_separation_max_depth, + modify_attribute_indent, + sort_attributes, + close_tag_newline, + include_namespace, + ) + + # Add space between blocks + if ( ( level < block_separation_max_depth ) + & ( ii < Nc - 1 ) + & ( child.tag is not ElementTree.Comment ) ): + output.write( "\n" ) + + # Write the end tag + output.write( "\n%s" % ( indent * level, node.tag ) ) + else: + if close_tag_newline: + output.write( "\n%s/>" % ( indent * level ) ) + else: + output.write( "/>" ) + + +def format_xml( + input: str, + indent_size: int = 2, + indent_style: bool = False, + block_separation_max_depth: int = 2, + alphebitize_attributes: bool = False, + close_style: bool = False, + namespace: bool = False, +) -> None: + """Script to format xml files + + Args: + input (str): Input str + indent_size (int): Indent size + indent_style (bool): Style of indentation (0=fixed, 1=hanging) + block_separation_max_depth (int): Max depth to separate xml blocks + alphebitize_attributes (bool): Alphebitize attributes + close_style (bool): Style of close tag (0=same line, 1=new line) + namespace (bool): Insert this namespace in the xml description + """ + try: + root = ElementTree.fromstring( input ) + # root = tree.getroot() + prologue_comments = [ tmp.text for tmp in root.itersiblings( preceding=True ) ] + epilog_comments = [ tmp.text for tmp in root.itersiblings() ] + + f = StringIO() + f.write( '\n' ) + + for comment in reversed( prologue_comments ): + f.write( "\n" % ( comment ) ) + + format_xml_level( + f, + root, + 0, + indent=" " * indent_size, + block_separation_max_depth=block_separation_max_depth, + modify_attribute_indent=indent_style, + sort_attributes=alphebitize_attributes, + close_tag_newline=close_style, + include_namespace=namespace, + ) + + for comment in epilog_comments: + f.write( "\n" % ( comment ) ) + f.write( "\n" ) + + return f.getvalue() + + except ElementTree.ParseError as err: + print( "\nCould not load file: %s" % ( f ) ) + print( err.msg ) + raise Exception( "\nCheck input file!" ) diff --git a/geos-trame/src/geos_trame/app/utils/properties_checker.py b/geos-trame/src/geos_trame/app/utils/properties_checker.py index 03a68220e..7f3ae9bc0 100644 --- a/geos-trame/src/geos_trame/app/utils/properties_checker.py +++ b/geos-trame/src/geos_trame/app/utils/properties_checker.py @@ -8,51 +8,55 @@ from geos_trame.app.deck.tree import DeckTree expected_properties = [ - ("region_attribute", ["attribute", "{}"]), - ("fields_to_import", ["attribute", "{}"]), + ( "region_attribute", [ "attribute", "{}" ] ), + ( "fields_to_import", [ "attribute", "{}" ] ), + ( "surfacicFieldsToImport", [ "attribute", "{}" ] ), ] -class PropertiesChecker(AbstractElement): + +class PropertiesChecker( AbstractElement ): """ Class to check the validity of properties within a deck tree. """ + def __init__( self, tree: DeckTree, **kwargs ): - super().__init__("div", **kwargs) + super().__init__( "div", **kwargs ) self.tree = tree - self.simput_manager = get_simput_manager(id=self.state.sm_id) + self.simput_manager = get_simput_manager( id=self.state.sm_id ) - def check_fields(self): + def check_fields( self ): for field in self.state.deck_tree: self.check_field( field ) - self.state.dirty("deck_tree") + self.state.dirty( "deck_tree" ) self.state.flush() - def check_attr_value(self, proxy, attr, expected, field): - if attr in proxy.definition and proxy[attr] not in expected: - field["invalid_properties"].append(attr) + @staticmethod + def check_attr_value( proxy, attr, expected, field ): + if attr in proxy.definition and proxy[ attr ] not in expected: + field[ "invalid_properties" ].append( attr ) - def check_field(self, field): - field["valid"] = 1 - field["invalid_properties"] = [] + def check_field( self, field ): + field[ "valid" ] = 1 + field[ "invalid_properties" ] = [] - proxy = self.simput_manager.proxymanager.get(field["id"]) + proxy = self.simput_manager.proxymanager.get( field[ "id" ] ) if proxy is not None: for attr, expected in expected_properties: - self.check_attr_value(proxy, attr, expected, field) + PropertiesChecker.check_attr_value( proxy, attr, expected, field ) - if len(field["invalid_properties"]) != 0: - field["valid"] = 2 + if len( field[ "invalid_properties" ] ) != 0: + field[ "valid" ] = 2 else: - field.pop("invalid_properties", None) + field.pop( "invalid_properties", None ) - if field["children"] is not None: + if field[ "children" ] is not None: # Parents are only valid if all children are valid - field["invalid_children"] = [] - for child in field["children"]: - self.check_field(child) - if child["valid"] == 2: - field["valid"] = 2 - field["invalid_children"].append(child["title"]) - if len(field["invalid_children"]) == 0: - field.pop("invalid_children", None) + field[ "invalid_children" ] = [] + for child in field[ "children" ]: + self.check_field( child ) + if child[ "valid" ] == 2: + field[ "valid" ] = 2 + field[ "invalid_children" ].append( child[ "title" ] ) + if len( field[ "invalid_children" ] ) == 0: + field.pop( "invalid_children", None ) diff --git a/geos-trame/src/geos_trame/app/utils/pv_utils.py b/geos-trame/src/geos_trame/app/utils/pv_utils.py new file mode 100644 index 000000000..95610c474 --- /dev/null +++ b/geos-trame/src/geos_trame/app/utils/pv_utils.py @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. +# SPDX-FileContributor: Kitware +import pyvista as pv + + +def read_unstructured_grid( filename ): + return pv.read( filename ).cast_to_unstructured_grid() diff --git a/geos-trame/src/geos_trame/widgets/__init__.py b/geos-trame/src/geos_trame/widgets/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/geos-trame/src/geos_trame/widgets/geos_trame.py b/geos-trame/src/geos_trame/widgets/geos_trame.py deleted file mode 100644 index 314d4b1fc..000000000 --- a/geos-trame/src/geos_trame/widgets/geos_trame.py +++ /dev/null @@ -1,18 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. -# SPDX-FileContributor: Lionel Untereiner -from trame_client.widgets.core import AbstractElement - -from .. import module - -__all__ = [ - "Editor", -] - - -class HtmlElement( AbstractElement ): - - def __init__( self, _elem_name, children=None, **kwargs ): - super().__init__( _elem_name, children, **kwargs ) - if self.server: - self.server.enable_module( module ) diff --git a/geos-trame/tests/test_well_intersection.py b/geos-trame/tests/test_well_intersection.py index f45841da8..d14f5bac3 100644 --- a/geos-trame/tests/test_well_intersection.py +++ b/geos-trame/tests/test_well_intersection.py @@ -1,6 +1,8 @@ # SPDX-License-Identifier: Apache-2.0 # SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. # SPDX-FileContributor: Lucas Givord - Kitware +from pathlib import Path + from trame.app import get_server from trame_client.utils.testing import enable_testing from geos_trame.app.core import GeosTrame @@ -9,30 +11,32 @@ def test_internal_well_intersection(): server = enable_testing( get_server( client_type="vue3" ), "message" ) - file_name = "geos-trame/tests/data/geosDeck/geosDeck.xml" + + root_path = Path(__file__).parent.absolute().__str__() + file_name = root_path + "/data/geosDeck/geosDeck.xml" app = GeosTrame( server, file_name ) app.state.ready() - app.deckInspector.state.object_state = [ "Problem/Mesh/0/VTKMesh/0", True ] + app.deckInspector.state.object_state = ( "Problem/Mesh/0/VTKMesh/0", True ) app.deckInspector.state.flush() - app.deckInspector.state.object_state = [ + app.deckInspector.state.object_state = ( "Problem/Mesh/0/VTKMesh/0/InternalWell/0", True, - ] + ) app.deckInspector.state.flush() - app.deckInspector.state.object_state = [ + app.deckInspector.state.object_state = ( "Problem/Mesh/0/VTKMesh/0/InternalWell/0/Perforation/0", True, - ] + ) app.deckInspector.state.flush() - app.deckInspector.state.object_state = [ + app.deckInspector.state.object_state = ( "Problem/Mesh/0/VTKMesh/0/InternalWell/0/Perforation/1", True, - ] + ) app.deckInspector.state.flush() assert app.deckViewer.well_engine.get_number_of_wells() == 1 @@ -42,33 +46,35 @@ def test_internal_well_intersection(): def test_vtk_well_intersection(): server = enable_testing( get_server( client_type="vue3" ), "message" ) - file_name = "geos-trame/tests/data/geosDeck/geosDeck.xml" + + root_path = Path(__file__).parent.absolute().__str__() + file_name = root_path + "/data/geosDeck/geosDeck.xml" app = GeosTrame( server, file_name ) app.state.ready() - app.deckInspector.state.object_state = [ "Problem/Mesh/0/VTKMesh/0", True ] + app.deckInspector.state.object_state = ( "Problem/Mesh/0/VTKMesh/0", True ) app.deckInspector.state.flush() - app.deckInspector.state.object_state = [ "Problem/Mesh/0/VTKMesh/0/VTKWell/0", True ] + app.deckInspector.state.object_state = ( "Problem/Mesh/0/VTKMesh/0/VTKWell/0", True ) app.deckInspector.state.flush() - app.deckInspector.state.object_state = [ + app.deckInspector.state.object_state = ( "Problem/Mesh/0/VTKMesh/0/VTKWell/0/Perforation/0", True, - ] + ) app.deckInspector.state.flush() - app.deckInspector.state.object_state = [ + app.deckInspector.state.object_state = ( "Problem/Mesh/0/VTKMesh/0/VTKWell/0/Perforation/1", True, - ] + ) app.deckInspector.state.flush() assert app.deckViewer.well_engine.get_number_of_wells() == 1 assert len( app.deckViewer._perforations ) == 2 - app.deckInspector.state.object_state = [ "Problem/Mesh/0/VTKMesh/0/VTKWell/0", False ] + app.deckInspector.state.object_state = ( "Problem/Mesh/0/VTKMesh/0/VTKWell/0", False ) app.deckInspector.state.flush() assert app.deckViewer.well_engine.get_number_of_wells() == 0 From d9986cf2e73f9b69ebd111911f01a202955f18e8 Mon Sep 17 00:00:00 2001 From: Jean Fechter Date: Mon, 26 May 2025 15:37:28 +0200 Subject: [PATCH 05/16] feat: properties checked using geometry data --- geos-trame/src/geos_trame/app/core.py | 2 +- .../app/utils/properties_checker.py | 29 +++++++++---------- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/geos-trame/src/geos_trame/app/core.py b/geos-trame/src/geos_trame/app/core.py index cdec00b6e..3af52c4fe 100644 --- a/geos-trame/src/geos_trame/app/core.py +++ b/geos-trame/src/geos_trame/app/core.py @@ -68,7 +68,7 @@ def __init__( self, server, file_name: str ): self.data_loader = DataLoader( self.tree, self.region_viewer, self.well_viewer, trame_server=server ) # Properties checker - self.properties_checker = PropertiesChecker( self.tree, trame_server=server ) + self.properties_checker = PropertiesChecker( self.tree, self.region_viewer, trame_server=server ) # TODO put as a modal window self.set_input_file( file_name=self.state.input_file ) diff --git a/geos-trame/src/geos_trame/app/utils/properties_checker.py b/geos-trame/src/geos_trame/app/utils/properties_checker.py index 7f3ae9bc0..01355690b 100644 --- a/geos-trame/src/geos_trame/app/utils/properties_checker.py +++ b/geos-trame/src/geos_trame/app/utils/properties_checker.py @@ -6,12 +6,9 @@ from trame_simput import get_simput_manager from geos_trame.app.deck.tree import DeckTree +from geos_trame.app.ui.viewer.regionViewer import RegionViewer -expected_properties = [ - ( "region_attribute", [ "attribute", "{}" ] ), - ( "fields_to_import", [ "attribute", "{}" ] ), - ( "surfacicFieldsToImport", [ "attribute", "{}" ] ), -] +attributes_to_check = [ "region_attribute", "fields_to_import", "surfacicFieldsToImport" ] class PropertiesChecker( AbstractElement ): @@ -19,31 +16,31 @@ class PropertiesChecker( AbstractElement ): Class to check the validity of properties within a deck tree. """ - def __init__( self, tree: DeckTree, **kwargs ): + def __init__( self, tree: DeckTree, region_viewer: RegionViewer, **kwargs ): super().__init__( "div", **kwargs ) self.tree = tree + self.region_viewer = region_viewer self.simput_manager = get_simput_manager( id=self.state.sm_id ) def check_fields( self ): + cellData = self.region_viewer.input.GetCellData() + arrayNames = [ cellData.GetArrayName( i ) for i in range( cellData.GetNumberOfArrays() ) ] + arrayNames.extend( [ "", "{}" ] ) # default values for field in self.state.deck_tree: - self.check_field( field ) + self.check_field( field, arrayNames ) self.state.dirty( "deck_tree" ) self.state.flush() - @staticmethod - def check_attr_value( proxy, attr, expected, field ): - if attr in proxy.definition and proxy[ attr ] not in expected: - field[ "invalid_properties" ].append( attr ) - - def check_field( self, field ): + def check_field( self, field, array_names: list[ str ] ): field[ "valid" ] = 1 field[ "invalid_properties" ] = [] proxy = self.simput_manager.proxymanager.get( field[ "id" ] ) if proxy is not None: - for attr, expected in expected_properties: - PropertiesChecker.check_attr_value( proxy, attr, expected, field ) + for attr in attributes_to_check: + if attr in proxy.definition and proxy[ attr ] not in array_names: + field[ "invalid_properties" ].append( attr ) if len( field[ "invalid_properties" ] ) != 0: field[ "valid" ] = 2 @@ -54,7 +51,7 @@ def check_field( self, field ): # Parents are only valid if all children are valid field[ "invalid_children" ] = [] for child in field[ "children" ]: - self.check_field( child ) + self.check_field( child, array_names ) if child[ "valid" ] == 2: field[ "valid" ] = 2 field[ "invalid_children" ].append( child[ "title" ] ) From 6956205fc48eaf7eebbb174e3ba36c5b5c432852 Mon Sep 17 00:00:00 2001 From: Jean Fechter Date: Wed, 28 May 2025 10:01:49 +0200 Subject: [PATCH 06/16] refactor: remove src nesting + mypy checks working --- geos-trame/{src => }/geos_trame/__init__.py | 0 .../{src => }/geos_trame/app/__init__.py | 0 .../{src => }/geos_trame/app/__main__.py | 0 .../app/components}/__init__.py | 0 .../app/components}/alertHandler.py | 0 geos-trame/{src => }/geos_trame/app/core.py | 14 +- .../app/data_types}/__init__.py | 0 .../geos_trame/app/data_types/field_status.py | 7 + .../app/data_types}/renderable.py | 2 +- .../app/data_types}/tree_node.py | 7 +- .../utils => geos_trame/app/deck}/__init__.py | 0 .../{src => }/geos_trame/app/deck/file.py | 0 .../{src => }/geos_trame/app/deck/tree.py | 153 +++++++++--------- .../geos_trame/app/geosTrameException.py | 0 geos-trame/geos_trame/app/io/__init__.py | 0 .../geos_trame/app/io/data_loader.py | 11 +- .../{src => }/geos_trame/app/io/xml_parser.py | 62 ++++--- geos-trame/geos_trame/app/ui/__init__.py | 0 .../{src => }/geos_trame/app/ui/editor.py | 0 .../{src => }/geos_trame/app/ui/inspector.py | 66 ++++---- .../{src => }/geos_trame/app/ui/plotting.py | 0 .../{src => }/geos_trame/app/ui/timeline.py | 0 .../geos_trame/app/ui/viewer/__init__.py | 0 .../app/ui/viewer/perforationViewer.py | 0 .../geos_trame/app/ui/viewer/regionViewer.py | 4 +- .../geos_trame/app/ui/viewer/viewer.py | 17 +- .../geos_trame/app/ui/viewer/wellViewer.py | 0 geos-trame/geos_trame/app/utils/__init__.py | 0 .../geos_trame/app/utils/dict_utils.py | 0 .../geos_trame/app/utils/file_utils.py | 23 ++- .../app/utils/properties_checker.py | 17 +- .../geos_trame/app/utils/pv_utils.py | 5 +- .../{src => }/geos_trame/module/.gitignore | 0 .../{src => }/geos_trame/module/__init__.py | 0 .../geos_trame/schema_generated/README.md | 0 .../geos_trame/schema_generated/__init__.py | 0 .../schema_generated/old_schema_mod.py | 0 .../geos_trame/schema_generated/schema_mod.py | 5 - geos-trame/tests/test_properties_checker.py | 15 +- geos-trame/tests/test_well_intersection.py | 59 +++---- geos-trame/tests/trame_fixtures.py | 8 +- 41 files changed, 242 insertions(+), 233 deletions(-) rename geos-trame/{src => }/geos_trame/__init__.py (100%) rename geos-trame/{src => }/geos_trame/app/__init__.py (100%) rename geos-trame/{src => }/geos_trame/app/__main__.py (100%) rename geos-trame/{src/geos_trame/app/io => geos_trame/app/components}/__init__.py (100%) rename geos-trame/{src/geos_trame/app/ui => geos_trame/app/components}/alertHandler.py (100%) rename geos-trame/{src => }/geos_trame/app/core.py (95%) rename geos-trame/{src/geos_trame/app/ui/viewer => geos_trame/app/data_types}/__init__.py (100%) create mode 100644 geos-trame/geos_trame/app/data_types/field_status.py rename geos-trame/{src/geos_trame/app/types => geos_trame/app/data_types}/renderable.py (92%) rename geos-trame/{src/geos_trame/app/types => geos_trame/app/data_types}/tree_node.py (87%) rename geos-trame/{src/geos_trame/app/utils => geos_trame/app/deck}/__init__.py (100%) rename geos-trame/{src => }/geos_trame/app/deck/file.py (100%) rename geos-trame/{src => }/geos_trame/app/deck/tree.py (72%) rename geos-trame/{src => }/geos_trame/app/geosTrameException.py (100%) create mode 100644 geos-trame/geos_trame/app/io/__init__.py rename geos-trame/{src => }/geos_trame/app/io/data_loader.py (93%) rename geos-trame/{src => }/geos_trame/app/io/xml_parser.py (79%) create mode 100644 geos-trame/geos_trame/app/ui/__init__.py rename geos-trame/{src => }/geos_trame/app/ui/editor.py (100%) rename geos-trame/{src => }/geos_trame/app/ui/inspector.py (83%) rename geos-trame/{src => }/geos_trame/app/ui/plotting.py (100%) rename geos-trame/{src => }/geos_trame/app/ui/timeline.py (100%) create mode 100644 geos-trame/geos_trame/app/ui/viewer/__init__.py rename geos-trame/{src => }/geos_trame/app/ui/viewer/perforationViewer.py (100%) rename geos-trame/{src => }/geos_trame/app/ui/viewer/regionViewer.py (92%) rename geos-trame/{src => }/geos_trame/app/ui/viewer/viewer.py (93%) rename geos-trame/{src => }/geos_trame/app/ui/viewer/wellViewer.py (100%) create mode 100644 geos-trame/geos_trame/app/utils/__init__.py rename geos-trame/{src => }/geos_trame/app/utils/dict_utils.py (100%) rename geos-trame/{src => }/geos_trame/app/utils/file_utils.py (92%) rename geos-trame/{src => }/geos_trame/app/utils/properties_checker.py (73%) rename geos-trame/{src => }/geos_trame/app/utils/pv_utils.py (62%) rename geos-trame/{src => }/geos_trame/module/.gitignore (100%) rename geos-trame/{src => }/geos_trame/module/__init__.py (100%) rename geos-trame/{src => }/geos_trame/schema_generated/README.md (100%) create mode 100644 geos-trame/geos_trame/schema_generated/__init__.py rename geos-trame/{src => }/geos_trame/schema_generated/old_schema_mod.py (100%) rename geos-trame/{src => }/geos_trame/schema_generated/schema_mod.py (99%) diff --git a/geos-trame/src/geos_trame/__init__.py b/geos-trame/geos_trame/__init__.py similarity index 100% rename from geos-trame/src/geos_trame/__init__.py rename to geos-trame/geos_trame/__init__.py diff --git a/geos-trame/src/geos_trame/app/__init__.py b/geos-trame/geos_trame/app/__init__.py similarity index 100% rename from geos-trame/src/geos_trame/app/__init__.py rename to geos-trame/geos_trame/app/__init__.py diff --git a/geos-trame/src/geos_trame/app/__main__.py b/geos-trame/geos_trame/app/__main__.py similarity index 100% rename from geos-trame/src/geos_trame/app/__main__.py rename to geos-trame/geos_trame/app/__main__.py diff --git a/geos-trame/src/geos_trame/app/io/__init__.py b/geos-trame/geos_trame/app/components/__init__.py similarity index 100% rename from geos-trame/src/geos_trame/app/io/__init__.py rename to geos-trame/geos_trame/app/components/__init__.py diff --git a/geos-trame/src/geos_trame/app/ui/alertHandler.py b/geos-trame/geos_trame/app/components/alertHandler.py similarity index 100% rename from geos-trame/src/geos_trame/app/ui/alertHandler.py rename to geos-trame/geos_trame/app/components/alertHandler.py diff --git a/geos-trame/src/geos_trame/app/core.py b/geos-trame/geos_trame/app/core.py similarity index 95% rename from geos-trame/src/geos_trame/app/core.py rename to geos-trame/geos_trame/app/core.py index 3af52c4fe..0c4e2d1cc 100644 --- a/geos-trame/src/geos_trame/app/core.py +++ b/geos-trame/geos_trame/app/core.py @@ -19,7 +19,7 @@ from geos_trame.app.ui.plotting import DeckPlotting from geos_trame.app.ui.timeline import TimelineEditor from geos_trame.app.ui.viewer.viewer import DeckViewer -from geos_trame.app.ui.alertHandler import AlertHandler +from geos_trame.app.components.alertHandler import AlertHandler import sys @@ -29,12 +29,12 @@ class GeosTrame: def __init__( self, server, file_name: str ): - self.alertHandler = None - self.deckPlotting = None - self.deckViewer = None - self.deckEditor = None - self.timelineEditor = None - self.deckInspector = None + self.alertHandler: AlertHandler | None = None + self.deckPlotting: DeckPlotting | None = None + self.deckViewer: DeckViewer | None = None + self.deckEditor: DeckEditor | None = None + self.timelineEditor: TimelineEditor | None = None + self.deckInspector: DeckInspector | None = None self.server = server server.enable_module( module ) diff --git a/geos-trame/src/geos_trame/app/ui/viewer/__init__.py b/geos-trame/geos_trame/app/data_types/__init__.py similarity index 100% rename from geos-trame/src/geos_trame/app/ui/viewer/__init__.py rename to geos-trame/geos_trame/app/data_types/__init__.py diff --git a/geos-trame/geos_trame/app/data_types/field_status.py b/geos-trame/geos_trame/app/data_types/field_status.py new file mode 100644 index 000000000..b9ac00a30 --- /dev/null +++ b/geos-trame/geos_trame/app/data_types/field_status.py @@ -0,0 +1,7 @@ +from enum import Enum + + +class FieldStatus( Enum ): + UNCHECKED = 0 + VALID = 1 + INVALID = 2 diff --git a/geos-trame/src/geos_trame/app/types/renderable.py b/geos-trame/geos_trame/app/data_types/renderable.py similarity index 92% rename from geos-trame/src/geos_trame/app/types/renderable.py rename to geos-trame/geos_trame/app/data_types/renderable.py index 58e31d8af..20e84932a 100644 --- a/geos-trame/src/geos_trame/app/types/renderable.py +++ b/geos-trame/geos_trame/app/data_types/renderable.py @@ -9,4 +9,4 @@ class Renderable( Enum ): INTERNALMESH = "InternalMesh" INTERNALWELL = "InternalWell" PERFORATION = "Perforation" - VTKWELL = "VTKWell" \ No newline at end of file + VTKWELL = "VTKWell" diff --git a/geos-trame/src/geos_trame/app/types/tree_node.py b/geos-trame/geos_trame/app/data_types/tree_node.py similarity index 87% rename from geos-trame/src/geos_trame/app/types/tree_node.py rename to geos-trame/geos_trame/app/data_types/tree_node.py index b637fe9f9..3601ff9aa 100644 --- a/geos-trame/src/geos_trame/app/types/tree_node.py +++ b/geos-trame/geos_trame/app/data_types/tree_node.py @@ -3,9 +3,14 @@ # SPDX-FileContributor: Kitware from dataclasses import dataclass +from geos_trame.app.data_types.field_status import FieldStatus + @dataclass class TreeNode: + """ + Single element of the tree, used by `DeckTree`. + """ id: str title: str children: list @@ -34,4 +39,4 @@ def json( self ) -> dict: valid=self.valid, children=None, hidden_children=[], - ) \ No newline at end of file + ) diff --git a/geos-trame/src/geos_trame/app/utils/__init__.py b/geos-trame/geos_trame/app/deck/__init__.py similarity index 100% rename from geos-trame/src/geos_trame/app/utils/__init__.py rename to geos-trame/geos_trame/app/deck/__init__.py diff --git a/geos-trame/src/geos_trame/app/deck/file.py b/geos-trame/geos_trame/app/deck/file.py similarity index 100% rename from geos-trame/src/geos_trame/app/deck/file.py rename to geos-trame/geos_trame/app/deck/file.py diff --git a/geos-trame/src/geos_trame/app/deck/tree.py b/geos-trame/geos_trame/app/deck/tree.py similarity index 72% rename from geos-trame/src/geos_trame/app/deck/tree.py rename to geos-trame/geos_trame/app/deck/tree.py index d63a978da..d565b8d37 100644 --- a/geos-trame/src/geos_trame/app/deck/tree.py +++ b/geos-trame/geos_trame/app/deck/tree.py @@ -1,11 +1,13 @@ # SPDX-License-Identifier: Apache-2.0 # SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. # SPDX-FileContributor: Lionel Untereiner -import dpath -import funcy - import os +from collections import defaultdict +import dpath +import funcy +from pydantic import BaseModel +from trame_simput import get_simput_manager from xsdata.formats.dataclass.parsers.config import ParserConfig from xsdata.formats.dataclass.serializers.config import SerializerConfig from xsdata.utils import text @@ -14,10 +16,7 @@ from geos_trame.app.deck.file import DeckFile from geos_trame.app.geosTrameException import GeosTrameException from geos_trame.app.utils.file_utils import normalize_path, format_xml -from geos_trame.schema_generated.schema_mod import BaseModel, Problem, Included, File - -from collections import defaultdict -from trame_simput import get_simput_manager +from geos_trame.schema_generated.schema_mod import Problem, Included, File class DeckTree( object ): @@ -25,23 +24,21 @@ class DeckTree( object ): A tree that represents a deck file along with all the available blocks and parameters. """ - def __init__( self, sm_id=None, **kwds ): + def __init__( self, sm_id=None, **kwargs ): """ Constructor. """ - super( DeckTree, self ).__init__( **kwds ) + super( DeckTree, self ).__init__( **kwargs ) - self.input_file = None - self.input_filename = None - self.input_folder = None - self.input_real_filename = None - # self._copyDefaultTree() + self.input_file: DeckFile | None = None + self.input_filename: str | None = None + self.input_folder: str | None = None self.root = None self.path_map = {} self.input_has_errors = False self._sm_id = sm_id - def set_input_file( self, input_filename ): + def set_input_file( self, input_filename: str ): """ Set a new input file Input: @@ -53,38 +50,39 @@ def set_input_file( self, input_filename ): self.input_filename = input_filename self.input_file = DeckFile( self.input_filename ) self.input_folder = os.path.dirname( self.input_file.filename ) - self.input_real_filename = os.path.basename( self.input_file.filename ) - except Exception as e: - msg = "set_input_file exception: %s" % e - return GeosTrameException( msg ) - - def root_fields( self ) -> list[ str ]: - return self.input_file.root_fields + except GeosTrameException: + return def get_mesh( self ) -> str: + assert self.input_file is not None and self.input_file.problem is not None return normalize_path( self.input_file.path + "/" + self.input_file.problem.mesh[ 0 ].vtkmesh[ 0 ].file ) def get_abs_path( self, file ) -> str: + assert self.input_file is not None and self.input_file.path is not None return normalize_path( self.input_file.path + "/" + file ) def to_str( self ) -> str: + assert self.input_file is not None return self.input_file.to_str() def get_tree( self ) -> dict: + assert self.input_file is not None and self.input_file.inspect_tree is not None return self.input_file.inspect_tree def update( self, path, key, value ) -> None: new_path = [ int( x ) if x.isdigit() else x for x in path.split( "/" ) ] new_path.append( key ) + assert self.input_file is not None and self.input_file.pb_dict is not None funcy.set_in( self.input_file.pb_dict, new_path, value ) def search( self, path ) -> list | None: new_path = path.split( "/" ) if self.input_file is None: return None + assert self.input_file.pb_dict is not None return dpath.values( self.input_file.pb_dict, new_path ) - def decode( self, path ): + def decode( self, path ) -> BaseModel | None: data = self.search( path ) if data is None: return None @@ -94,25 +92,23 @@ def decode( self, path ): attribute_name_generator=text.camel_case, ) decoder = DictDecoder( context=context, config=ParserConfig() ) - node = decoder.decode( data[ 0 ] ) - return node + return decoder.decode( data[ 0 ] ) - def decode_data( self, data: BaseModel | None ) -> str: + @staticmethod + def decode_data( data: dict ) -> Problem: """ Convert a data to a xml serializable file """ - if data is None: - return - context = XmlContext( element_name_generator=text.pascal_case, attribute_name_generator=text.camel_case, ) decoder = DictDecoder( context=context, config=ParserConfig() ) - node = decoder.decode( data ) + node: Problem = decoder.decode( data ) return node - def to_xml( self, obj ) -> str: + @staticmethod + def to_xml( obj ) -> str: context = XmlContext( element_name_generator=text.pascal_case, attribute_name_generator=text.camel_case, @@ -123,19 +119,17 @@ def to_xml( self, obj ) -> str: return format_xml( serializer.render( obj ) ) - def timeline( self ) -> dict: + def timeline( self ) -> list[ dict ] | None: if self.input_file is None: - return + return None if self.input_file.problem is None: - return - if self.input_file.problem.events is None: - return + return None timeline = list() # list root events global_id = 0 for e in self.input_file.problem.events[ 0 ].periodic_event: - item = dict() + item: dict[ str, str | int ] = dict() item[ "id" ] = global_id item[ "summary" ] = e.name item[ "start_date" ] = e.begin_time @@ -145,34 +139,39 @@ def timeline( self ) -> dict: return timeline def plots( self ): + assert self.input_file is not None and self.input_file.problem is not None return self.input_file.problem.functions - def write_files( self ): + def write_files( self ) -> None: """ Write geos files with all changes made by the user. """ pb = self.search( "Problem" ) + if pb is None: + return files = self._split( pb ) for filepath, content in files.items(): - model_loaded: BaseModel = self.decode_data( content ) - model_with_changes: BaseModel = self._apply_changed_properties( model_loaded ) + model_loaded: Problem = DeckTree.decode_data( content ) + model_with_changes: Problem = self._apply_changed_properties( model_loaded ) + assert self.input_file is not None and self.input_file.xml_parser is not None if self.input_file.xml_parser.contains_include_files(): includeName: str = self.input_file.xml_parser.get_relative_path_of_file( filepath ) - self._append_include_file( model_with_changes, includeName ) + DeckTree._append_include_file( model_with_changes, includeName ) - model_as_xml: str = self.to_xml( model_with_changes ) + model_as_xml: str = DeckTree.to_xml( model_with_changes ) basename = os.path.basename( filepath ) + assert self.input_folder is not None edited_folder_path = self.input_folder - location = edited_folder_path + "/" + self._append_id( basename ) + location = edited_folder_path + "/" + DeckTree._append_id( basename ) with open( location, "w" ) as file: file.write( model_as_xml ) file.close() - def _setInputFile( self, input_file ): + def _set_input_file( self, input_file ): """ Copies the nodes of an input file into the tree Input: @@ -188,26 +187,28 @@ def _setInputFile( self, input_file ): return False - def _append_include_file( self, model: Problem, includedFilePath: str ) -> None: + @staticmethod + def _append_include_file( model: Problem, included_file_path: str ) -> None: """ Append an Included object which follows this structure according to the documentation: - Only Problem can contains an included tag: + Only Problem can contain an included tag: https://geosx-geosx.readthedocs-hosted.com/en/latest/docs/sphinx/datastructure/CompleteXMLSchema.html """ - if len( includedFilePath ) == 0: - return None + if len( included_file_path ) == 0: + return includedTag = Included() - includedTag.file.append( File( name=self._append_id( includedFilePath ) ) ) + includedTag.file.append( File( name=DeckTree._append_id( included_file_path ) ) ) model.included.append( includedTag ) - def _append_id( self, filename: str ) -> str: + @staticmethod + def _append_id( filename: str ) -> str: """ Return the new filename with the correct suffix and his extension. The suffix added will be '_vX' where X is the incremented value of the current version. @@ -227,7 +228,8 @@ def _append_id( self, filename: str ) -> str: suffix += str( version ) return f"{name}{suffix}{ext}" - def _convert_to_camel_case( self, content: str ) -> str: + @staticmethod + def _convert_to_camel_case( content: str ) -> str: """ Convert any given string in CamelCase. @@ -236,7 +238,8 @@ def _convert_to_camel_case( self, content: str ) -> str: camel_case_str: str = content.title() return camel_case_str.replace( "_", "" ) - def _convert_to_snake_case( self, content: str ) -> str: + @staticmethod + def _convert_to_snake_case( content: str ) -> str: """ Convert any given string in snake case. @@ -261,39 +264,39 @@ def _apply_changed_properties( self, model: Problem ) -> Problem: for proxy_id in modified_proxy_ids: properties = manager.data( proxy_id )[ "properties" ] - events = self._get_base_model_from_path( model_as_dict, proxy_id ) - if events is None: - continue + events = DeckTree._get_base_model_from_path( model_as_dict, proxy_id ) events_as_dict = dict( events ) for property_name, value in properties.items(): events_as_dict[ property_name ] = value - self._set_base_model_properties( model_as_dict, proxy_id, events_as_dict ) + DeckTree._set_base_model_properties( model_as_dict, proxy_id, events_as_dict ) model = getattr( model, "model_validate" )( model_as_dict ) return model - def _convert_proxy_path_into_proxy_names( self, proxy_path: str ) -> list[ str ]: + @staticmethod + def _convert_proxy_path_into_proxy_names( proxy_path: str ) -> list[ str ]: """ Split a given proxy path into a list of proxy names. note: each proxy name will be converted in snake case to fit with the pydantic model naming convention. """ - splitted_path = proxy_path.split( "/" ) - splitted_path_without_root = splitted_path[ 1: ] + split_path = proxy_path.split( "/" ) + split_path_without_root = split_path[ 1: ] - return [ self._convert_to_snake_case( proxy ) for proxy in splitted_path_without_root ] + return [ DeckTree._convert_to_snake_case( proxy ) for proxy in split_path_without_root ] - def _set_base_model_properties( self, model: dict, proxy_path: str, properties: dict ) -> None: + @staticmethod + def _set_base_model_properties( model: dict, proxy_path: str, properties: dict ) -> None: """ Apply all changed property to the model for a specific proxy. """ # retrieve the whole BaseModel list to the modified proxy - proxy_names = self._convert_proxy_path_into_proxy_names( proxy_path ) + proxy_names = DeckTree._convert_proxy_path_into_proxy_names( proxy_path ) model_copy = model - models = [] + models: list[ tuple[ str, dict ] ] = [] for proxy_name in proxy_names: is_dict = type( model_copy ) is dict is_list = type( model_copy ) is list @@ -303,22 +306,22 @@ def _set_base_model_properties( self, model: dict, proxy_path: str, properties: model_copy = dict( model_copy ) if proxy_name.isnumeric() and int( proxy_name ) < len( model_copy ): - models.append( [ proxy_name, model_copy ] ) - model_copy = model_copy[ int( proxy_name ) ] + models.append( ( proxy_name, model_copy ) ) + model_copy = model_copy[ proxy_name ] continue if proxy_name in model_copy: - models.append( [ proxy_name, model_copy ] ) + models.append( ( proxy_name, model_copy ) ) model_copy = model_copy[ proxy_name ] else: - return None + return models.reverse() # propagate the modification to the parent node index = -1 for model_inverted in models: - prop_identifier = model_inverted[ 0 ] + prop_identifier: str = model_inverted[ 0 ] if prop_identifier.isnumeric(): index = int( prop_identifier ) @@ -333,18 +336,17 @@ def _set_base_model_properties( self, model: dict, proxy_path: str, properties: current_node[ prop_identifier ][ index ] = current_base_model - properties = dict( current_base_model ) break models.reverse() - model = models[ 0 ] - def _get_base_model_from_path( self, model: dict, proxy_id: str ) -> BaseModel: + @staticmethod + def _get_base_model_from_path( model: dict, proxy_id: str ) -> dict: """ Retrieve the BaseModel changed from the proxy id. The proxy_id is a unique path from the simput manager. """ - proxy_names = self._convert_proxy_path_into_proxy_names( proxy_id ) + proxy_names = DeckTree._convert_proxy_path_into_proxy_names( proxy_id ) model_found: dict = model @@ -357,7 +359,7 @@ def _get_base_model_from_path( self, model: dict, proxy_id: str ) -> BaseModel: if is_class: model_found = dict( model_found ) - # path can contains a numerical index, useful to be sure that each + # path can contain a numerical index, useful to be sure that each # proxy is unique, typically used for a list of proxy located at the same level if proxy_name.isnumeric() and int( proxy_name ) < len( model_found ): model_found = model_found[ int( proxy_name ) ] @@ -368,9 +370,10 @@ def _get_base_model_from_path( self, model: dict, proxy_id: str ) -> BaseModel: return model_found - def _split( self, xml: str ) -> dict[ str, str ]: + def _split( self, xml: list ) -> defaultdict[ str, dict[ str, str ] ]: + assert self.input_file is not None and self.input_file.xml_parser is not None data = self.input_file.xml_parser.file_to_tags - restructured_files = defaultdict( dict ) + restructured_files: defaultdict[ str, dict ] = defaultdict( dict ) for file_path, associated_tags in data.items(): restructured_files[ file_path ] = dict() for tag, contents in xml[ 0 ].items(): diff --git a/geos-trame/src/geos_trame/app/geosTrameException.py b/geos-trame/geos_trame/app/geosTrameException.py similarity index 100% rename from geos-trame/src/geos_trame/app/geosTrameException.py rename to geos-trame/geos_trame/app/geosTrameException.py diff --git a/geos-trame/geos_trame/app/io/__init__.py b/geos-trame/geos_trame/app/io/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/geos-trame/src/geos_trame/app/io/data_loader.py b/geos-trame/geos_trame/app/io/data_loader.py similarity index 93% rename from geos-trame/src/geos_trame/app/io/data_loader.py rename to geos-trame/geos_trame/app/io/data_loader.py index 6095c9725..e55cdaeaa 100644 --- a/geos-trame/src/geos_trame/app/io/data_loader.py +++ b/geos-trame/geos_trame/app/io/data_loader.py @@ -20,6 +20,9 @@ class DataLoader( AbstractElement ): + """ + Helper class to handle IO operations for data loading. + """ def __init__( self, source, region_viewer: RegionViewer, well_viewer: WellViewer, **kwargs ): super().__init__( "span", **kwargs ) @@ -85,7 +88,9 @@ def _update_vtkwell( self, well: Vtkwell, path: str, show: bool ) -> None: self.well_viewer.remove( path ) return - well_polydata = pv.read( self.source.get_abs_path( well.file ) ).cast_to_poly_points() + well_polydata = pv.read( self.source.get_abs_path( well.file ) ) + if not isinstance( well_polydata, pv.PolyData ): + raise GeosTrameException( f"Expected PolyData, got {type(well_polydata).__name__}" ) self.well_viewer.add_mesh( well_polydata, path ) def _update_internalwell( self, well: InternalWell, path: str, show: bool ) -> None: @@ -94,7 +99,7 @@ def _update_internalwell( self, well: InternalWell, path: str, show: bool ) -> N This method will create the mesh if it doesn't exist. """ if not show: - self.well_engine.remove( path ) + self.well_viewer.remove( path ) return points = self.__parse_polyline_property( well.polyline_node_coords, dtype=float ) @@ -106,7 +111,7 @@ def _update_internalwell( self, well: InternalWell, path: str, show: bool ) -> N sorted_points.append( points[ point_id ] ) well_polydata = pv.MultipleLines( sorted_points ) - self.well_engine.add_mesh( well_polydata, path ) + self.well_viewer.add_mesh( well_polydata, path ) @staticmethod def __parse_polyline_property( polyline_property: str, dtype: Type[ Any ] ) -> np.ndarray: diff --git a/geos-trame/src/geos_trame/app/io/xml_parser.py b/geos-trame/geos_trame/app/io/xml_parser.py similarity index 79% rename from geos-trame/src/geos_trame/app/io/xml_parser.py rename to geos-trame/geos_trame/app/io/xml_parser.py index 13a74e5e6..05148b56c 100644 --- a/geos-trame/src/geos_trame/app/io/xml_parser.py +++ b/geos-trame/geos_trame/app/io/xml_parser.py @@ -1,7 +1,7 @@ # SPDX-License-Identifier: Apache-2.0 # SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. # SPDX-FileContributor: Lionel Untereiner -import os +import sys import re from os.path import expandvars from pathlib import Path @@ -19,7 +19,7 @@ class XMLParser( object ): Class used to parse a valid XML geos file and construct a link between each file when they are included. - Useful to be able to able to save it later. + Useful to be able to save it later. """ def __init__( self, filename: str ): @@ -28,8 +28,8 @@ def __init__( self, filename: str ): """ self.filename = filename - self.file_to_tags = defaultdict( list ) - self.file_to_relative_path = {} + self.file_to_tags: defaultdict = defaultdict( list ) + self.file_to_relative_path: dict = {} expanded_file = Path( expandvars( self.filename ) ).expanduser().resolve() self.file_path = expanded_file.parent @@ -40,14 +40,14 @@ def __init__( self, filename: str ): tree = ElementTree.parse( expanded_file, parser=parser ) self.root = tree.getroot() except XMLSyntaxError as err: - error_msg = "Invalid XML file. Cannot load " + expanded_file + error_msg = "Invalid XML file. Cannot load " + str( expanded_file ) error_msg += ". Outputted error:\n" + err.msg - print( error_msg, file=os.sys.stderr ) + print( error_msg, file=sys.stderr ) self._is_valid = False def is_valid( self ) -> bool: if not self._is_valid: - print( "XMLParser isn't valid", file=os.sys.stderr ) + print( "XMLParser isn't valid", file=sys.stderr ) return self._is_valid def build( self ) -> None: @@ -58,7 +58,6 @@ def build( self ) -> None: def get_simulation_deck( self ) -> ElementTree.Element: if not self.is_valid(): raise GeosTrameException( "Not valid file, cannot return the deck." ) - return return self.simulation_deck def contains_include_files( self ) -> bool: @@ -74,10 +73,7 @@ def get_relative_path_of_file( self, filename: str ) -> str: return self.file_to_relative_path[ filename ] def _read( self ) -> ElementTree.Element: - """Reads an xml file (and recursively its included files) into memory - - Args: - xmlFilepath (str): The path the file to read. + """Reads a xml file (and recursively its included files) into memory Returns: SimulationDeck: The simulation deck @@ -91,7 +87,7 @@ def _read( self ) -> ElementTree.Element: if include_node.tag == "Included": for f in include_node.findall( "File" ): self.file_to_relative_path[ self.filename ] = f.get( "name" ) - self._merge_included_xml_files( self.root, self.file_path, f.get( "name" ), includeCount ) + self._merge_included_xml_files( self.root, str( self.file_path ), f.get( "name" ), includeCount ) # Remove 'Included' nodes for include_node in self.root.findall( "Included" ): @@ -107,30 +103,29 @@ def _read( self ) -> ElementTree.Element: def _merge_xml_nodes( self, - existingNode: ElementTree.Element, - targetNode: ElementTree.Element, + existing_node: ElementTree.Element, + target_node: ElementTree.Element, fname: str, level: int, ) -> None: """Merge nodes in an included file into the current structure level by level. Args: - existingNode (lxml.etree.Element): The current node in the base xml structure. - targetNode (lxml.etree.Element): The node to insert. + existing_node (lxml.etree.Element): The current node in the base xml structure. + target_node (lxml.etree.Element): The node to insert. level (int): The xml file depth. """ if not self.is_valid(): raise GeosTrameException( "Not valid file, cannot merge nodes" ) - return # Copy attributes on the current level - for tk in targetNode.attrib.keys(): - existingNode.set( tk, targetNode.get( tk ) ) + for tk in target_node.attrib.keys(): + existing_node.set( tk, target_node.get( tk ) ) # Copy target children into the xml structure currentTag = "" matchingSubNodes = [] - for target in targetNode.getchildren(): + for target in target_node.getchildren(): tags = self.file_to_tags[ fname ] tags.append( target.tag ) insertCurrentLevel = True @@ -139,7 +134,7 @@ def _merge_xml_nodes( # exists at this level if currentTag != target.tag: currentTag = target.tag - matchingSubNodes = existingNode.findall( target.tag ) + matchingSubNodes = existing_node.findall( target.tag ) if matchingSubNodes: targetName = target.get( "name" ) @@ -159,42 +154,41 @@ def _merge_xml_nodes( # Insert any unnamed nodes or named nodes that aren't present # in the current xml structure if insertCurrentLevel: - existingNode.insert( -1, target ) + existing_node.insert( -1, target ) def _merge_included_xml_files( self, root: ElementTree.Element, file_path: str, fname: str, - includeCount: int, - maxInclude: int = 100, + include_count: int, + max_include: int = 100, ) -> None: """Recursively merge included files into the current structure. Args: root (lxml.etree.Element): The root node of the base xml structure. fname (str): The name of the target xml file to merge. - includeCount (int): The current recursion depth. - maxInclude (int): The maximum number of xml files to include (default = 100) + include_count (int): The current recursion depth. + max_include (int): The maximum number of xml files to include (default = 100) """ if not self.is_valid(): raise GeosTrameException( "Not valid file, cannot merge nodes" ) - return included_file_path = Path( expandvars( file_path ), fname ) expanded_file = included_file_path.expanduser().resolve() self.file_to_relative_path[ fname ] = "" # Check to see if the code has fallen into a loop - includeCount += 1 - if includeCount > maxInclude: + include_count += 1 + if include_count > max_include: raise Exception( "Reached maximum recursive includes... Is there an include loop?" ) # Check to make sure the file exists if not included_file_path.is_file(): print( - "Included file does not exist: %s" % ( included_file_path ), - file=os.sys.stderr, + "Included file does not exist: %s" % included_file_path, + file=sys.stderr, ) raise Exception( "Check included file path!" ) @@ -204,7 +198,7 @@ def _merge_included_xml_files( includeTree = ElementTree.parse( included_file_path, parser ) includeRoot = includeTree.getroot() except XMLSyntaxError as err: - print( "\nCould not load included file: %s" % ( included_file_path ) ) + print( "\nCould not load included file: %s" % included_file_path ) print( err.msg ) raise Exception( "\nCheck included file!" ) from err @@ -212,7 +206,7 @@ def _merge_included_xml_files( for include_node in includeRoot.findall( "Included" ): for f in include_node.findall( "File" ): self.file_to_relative_path[ fname ] = f.get( "name" ) - self._merge_included_xml_files( root, expanded_file.parent, f.get( "name" ), includeCount ) + self._merge_included_xml_files( root, str( expanded_file.parent ), f.get( "name" ), include_count ) # Merge the results into the xml tree self._merge_xml_nodes( root, includeRoot, fname, 0 ) diff --git a/geos-trame/geos_trame/app/ui/__init__.py b/geos-trame/geos_trame/app/ui/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/geos-trame/src/geos_trame/app/ui/editor.py b/geos-trame/geos_trame/app/ui/editor.py similarity index 100% rename from geos-trame/src/geos_trame/app/ui/editor.py rename to geos-trame/geos_trame/app/ui/editor.py diff --git a/geos-trame/src/geos_trame/app/ui/inspector.py b/geos-trame/geos_trame/app/ui/inspector.py similarity index 83% rename from geos-trame/src/geos_trame/app/ui/inspector.py rename to geos-trame/geos_trame/app/ui/inspector.py index ec28661ee..7c90eca9d 100644 --- a/geos-trame/src/geos_trame/app/ui/inspector.py +++ b/geos-trame/geos_trame/app/ui/inspector.py @@ -8,8 +8,9 @@ from trame.widgets import vuetify3 as vuetify, html from trame_simput import get_simput_manager -from geos_trame.app.types.renderable import Renderable -from geos_trame.app.types.tree_node import TreeNode +from geos_trame.app.data_types.field_status import FieldStatus +from geos_trame.app.data_types.renderable import Renderable +from geos_trame.app.data_types.tree_node import TreeNode from geos_trame.app.utils.dict_utils import iterate_nested_dict vuetify.enable_lab() @@ -53,7 +54,7 @@ def __init__( self, listen_to_active=True, source=None, **kwargs ): self.set_source( source.input_file.problem ) - def on_change( topic, ids=None, **kwargs ): + def on_change( topic, ids=None, **_ ): if topic == "changed": for obj_id in ids: proxy = self.simput_manager.proxymanager.get( obj_id ) @@ -158,7 +159,7 @@ def change_current_id( self, item_id=None ): This function is called when the user click on the tree. """ if item_id is None: - # Silently ignore, it could occurs is the user click on the tree + # Silently ignore, it could occur if the user click on the tree # and this item is already selected return @@ -184,39 +185,36 @@ def get_node_dict( obj, node_id, path ): hidden_children=[], is_drawable=node_id in ( k.value for k in Renderable ), drawn=False, - valid=0 ) + valid=FieldStatus.UNCHECKED.value ) def object_to_tree( obj: dict ) -> dict: return get_node_dict( obj, "Problem", [] ).json -def dump( item ): - match item: - case BaseModel() as model: - subitems: dict[ str, Any ] = dict() - model.model_fields - - for field, value in model: - - if isinstance( value, str ): - subitems[ field ] = value - continue - - return subitems - case list() | tuple() | set(): # pyright: ignore - # Pyright finds this disgusting; this passes `mypy` though. ` # type: - # ignore` would fail `mypy` is it'd be unused (because there's nothing to - # ignore because `mypy` is content) - # return type(container)( # pyright: ignore - # _dump(i) for i in container # pyright: ignore - # ) - pass - case dict(): - # return { - # k: _dump(v) - # for k, v in item.items() # pyright: ignore[reportUnknownVariableType] - # } - pass - case _: - return item +def dump( item ) -> dict[ str, Any ] | None: + if isinstance( item, BaseModel ): + subitems: dict[ str, Any ] = dict() + + for field, value in item: + + if isinstance( value, str ): + subitems[ field ] = value + continue + return subitems + elif isinstance( item, ( list, tuple, set ) ): # pyright: ignore + # Pyright finds this disgusting; this passes `mypy` though. ` # type: + # ignore` would fail `mypy` is it'd be unused (because there's nothing to + # ignore because `mypy` is content) + # return type(container)( # pyright: ignore + # _dump(i) for i in container # pyright: ignore + # ) + return None + elif isinstance( item, dict ): + # return { + # k: _dump(v) + # for k, v in item.items() # pyright: ignore[reportUnknownVariableType] + # } + return None + else: + return item diff --git a/geos-trame/src/geos_trame/app/ui/plotting.py b/geos-trame/geos_trame/app/ui/plotting.py similarity index 100% rename from geos-trame/src/geos_trame/app/ui/plotting.py rename to geos-trame/geos_trame/app/ui/plotting.py diff --git a/geos-trame/src/geos_trame/app/ui/timeline.py b/geos-trame/geos_trame/app/ui/timeline.py similarity index 100% rename from geos-trame/src/geos_trame/app/ui/timeline.py rename to geos-trame/geos_trame/app/ui/timeline.py diff --git a/geos-trame/geos_trame/app/ui/viewer/__init__.py b/geos-trame/geos_trame/app/ui/viewer/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/geos-trame/src/geos_trame/app/ui/viewer/perforationViewer.py b/geos-trame/geos_trame/app/ui/viewer/perforationViewer.py similarity index 100% rename from geos-trame/src/geos_trame/app/ui/viewer/perforationViewer.py rename to geos-trame/geos_trame/app/ui/viewer/perforationViewer.py diff --git a/geos-trame/src/geos_trame/app/ui/viewer/regionViewer.py b/geos-trame/geos_trame/app/ui/viewer/regionViewer.py similarity index 92% rename from geos-trame/src/geos_trame/app/ui/viewer/regionViewer.py rename to geos-trame/geos_trame/app/ui/viewer/regionViewer.py index 54bb2ea7b..4892bfd03 100644 --- a/geos-trame/src/geos_trame/app/ui/viewer/regionViewer.py +++ b/geos-trame/geos_trame/app/ui/viewer/regionViewer.py @@ -10,10 +10,10 @@ class RegionViewer: This mesh is represented in GEOS with a Region. """ - input: pv.UnstructuredGrid - clip: pv.UnstructuredGrid def __init__( self ) -> None: + self.input = pv.UnstructuredGrid() + self.clip = self.input self.reset() def __call__( self, normal: tuple[ float ], origin: tuple[ float ] ) -> None: diff --git a/geos-trame/src/geos_trame/app/ui/viewer/viewer.py b/geos-trame/geos_trame/app/ui/viewer/viewer.py similarity index 93% rename from geos-trame/src/geos_trame/app/ui/viewer/viewer.py rename to geos-trame/geos_trame/app/ui/viewer/viewer.py index b7e8dde03..9b53f9f48 100644 --- a/geos-trame/src/geos_trame/app/ui/viewer/viewer.py +++ b/geos-trame/geos_trame/app/ui/viewer/viewer.py @@ -5,10 +5,11 @@ from pyvista.trame.ui import plotter_ui from trame.widgets import html from trame.widgets import vuetify3 as vuetify +from vtkmodules.vtkRenderingCore import vtkActor -import geos_trame.app.ui.viewer.perforationViewer as PerforationViewer -import geos_trame.app.ui.viewer.regionViewer as RegionViewer -import geos_trame.app.ui.viewer.wellViewer as WellViewer +from geos_trame.app.ui.viewer.perforationViewer import PerforationViewer +from geos_trame.app.ui.viewer.regionViewer import RegionViewer +from geos_trame.app.ui.viewer.wellViewer import WellViewer from geos_trame.schema_generated.schema_mod import ( Vtkmesh, Vtkwell, @@ -49,7 +50,7 @@ def __init__( self, source, region_viewer: RegionViewer, well_viewer: WellViewer self.region_engine = region_viewer self.well_engine = well_viewer - self._perforations: dict[ str, PerforationViewer.PerforationViewer ] = dict() + self._perforations: dict[ str, PerforationViewer ] = dict() self.ctrl.update_viewer.add( self.update_viewer ) @@ -228,7 +229,7 @@ def _update_vtkmesh( self, show: bool ) -> None: return active_scalar = self.region_engine.input.active_scalars_name - self._clip_mesh = self.plotter.add_mesh_clip_plane( + self._clip_mesh: vtkActor = self.plotter.add_mesh_clip_plane( self.region_engine.input, origin=self.region_engine.input.center, normal=[ -1, 0, 0 ], @@ -257,7 +258,7 @@ def _remove_perforation( self, path: str ) -> None: """ Remove all actor related to the given path and clean the stored perforation """ - saved_perforation: PerforationViewer.PerforationViewer = self._perforations[ path ] + saved_perforation: PerforationViewer = self._perforations[ path ] self.plotter.remove_actor( saved_perforation.extracted_cell ) self.plotter.remove_actor( saved_perforation.perforation_actor ) saved_perforation.reset() @@ -267,7 +268,7 @@ def _add_perforation( self, distance_from_head: float, path: str ) -> None: Generate perforation dataset based on the distance from the top of a polyline """ - polyline: pv.PolyData = self.well_engine.get_mesh( path ) + polyline: pv.PolyData | None = self.well_engine.get_mesh( path ) if polyline is None: return @@ -282,7 +283,7 @@ def _add_perforation( self, distance_from_head: float, path: str ) -> None: sphere = pv.Sphere( radius=5, center=center ) perforation_actor = self.plotter.add_mesh( sphere ) - saved_perforation = PerforationViewer.PerforationViewer( sphere, center, 5, perforation_actor ) + saved_perforation = PerforationViewer( sphere, center, 5, perforation_actor ) cell_id = self.region_engine.input.find_closest_cell( point_offsetted ) cell = self.region_engine.input.extract_cells( [ cell_id ] ) diff --git a/geos-trame/src/geos_trame/app/ui/viewer/wellViewer.py b/geos-trame/geos_trame/app/ui/viewer/wellViewer.py similarity index 100% rename from geos-trame/src/geos_trame/app/ui/viewer/wellViewer.py rename to geos-trame/geos_trame/app/ui/viewer/wellViewer.py diff --git a/geos-trame/geos_trame/app/utils/__init__.py b/geos-trame/geos_trame/app/utils/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/geos-trame/src/geos_trame/app/utils/dict_utils.py b/geos-trame/geos_trame/app/utils/dict_utils.py similarity index 100% rename from geos-trame/src/geos_trame/app/utils/dict_utils.py rename to geos-trame/geos_trame/app/utils/dict_utils.py diff --git a/geos-trame/src/geos_trame/app/utils/file_utils.py b/geos-trame/geos_trame/app/utils/file_utils.py similarity index 92% rename from geos-trame/src/geos_trame/app/utils/file_utils.py rename to geos-trame/geos_trame/app/utils/file_utils.py index c357f17ef..48abda1e9 100644 --- a/geos-trame/src/geos_trame/app/utils/file_utils.py +++ b/geos-trame/geos_trame/app/utils/file_utils.py @@ -92,7 +92,6 @@ def format_xml_level( attribute_indent = " " * ( len( opening_line ) ) # Get a copy of the attributes - attribute_dict = {} attribute_dict = node.attrib # Sort attribute names @@ -148,28 +147,27 @@ def format_xml_level( def format_xml( - input: str, + input_str: str, indent_size: int = 2, indent_style: bool = False, block_separation_max_depth: int = 2, - alphebitize_attributes: bool = False, + alphabetize_attributes: bool = False, close_style: bool = False, namespace: bool = False, -) -> None: +) -> str: """Script to format xml files Args: - input (str): Input str + input_str (str): Input str indent_size (int): Indent size indent_style (bool): Style of indentation (0=fixed, 1=hanging) block_separation_max_depth (int): Max depth to separate xml blocks - alphebitize_attributes (bool): Alphebitize attributes + alphabetize_attributes (bool): Alphebitize attributes close_style (bool): Style of close tag (0=same line, 1=new line) namespace (bool): Insert this namespace in the xml description """ try: - root = ElementTree.fromstring( input ) - # root = tree.getroot() + root = ElementTree.fromstring( input_str ) prologue_comments = [ tmp.text for tmp in root.itersiblings( preceding=True ) ] epilog_comments = [ tmp.text for tmp in root.itersiblings() ] @@ -177,7 +175,7 @@ def format_xml( f.write( '\n' ) for comment in reversed( prologue_comments ): - f.write( "\n" % ( comment ) ) + f.write( "\n" % comment ) format_xml_level( f, @@ -186,18 +184,17 @@ def format_xml( indent=" " * indent_size, block_separation_max_depth=block_separation_max_depth, modify_attribute_indent=indent_style, - sort_attributes=alphebitize_attributes, + sort_attributes=alphabetize_attributes, close_tag_newline=close_style, include_namespace=namespace, ) for comment in epilog_comments: - f.write( "\n" % ( comment ) ) + f.write( "\n" % comment ) f.write( "\n" ) return f.getvalue() except ElementTree.ParseError as err: - print( "\nCould not load file: %s" % ( f ) ) print( err.msg ) - raise Exception( "\nCheck input file!" ) + raise Exception( "Failed to format xml file" ) diff --git a/geos-trame/src/geos_trame/app/utils/properties_checker.py b/geos-trame/geos_trame/app/utils/properties_checker.py similarity index 73% rename from geos-trame/src/geos_trame/app/utils/properties_checker.py rename to geos-trame/geos_trame/app/utils/properties_checker.py index 01355690b..805c76dac 100644 --- a/geos-trame/src/geos_trame/app/utils/properties_checker.py +++ b/geos-trame/geos_trame/app/utils/properties_checker.py @@ -5,6 +5,7 @@ from trame_client.widgets.core import AbstractElement from trame_simput import get_simput_manager +from geos_trame.app.data_types.field_status import FieldStatus from geos_trame.app.deck.tree import DeckTree from geos_trame.app.ui.viewer.regionViewer import RegionViewer @@ -24,6 +25,10 @@ def __init__( self, tree: DeckTree, region_viewer: RegionViewer, **kwargs ): self.simput_manager = get_simput_manager( id=self.state.sm_id ) def check_fields( self ): + """ + Get the names of all the cell data arrays from the input of the region viewer, then check that all the attributes + in `attributes_to_check` have a value corresponding to one of the array names. + """ cellData = self.region_viewer.input.GetCellData() arrayNames = [ cellData.GetArrayName( i ) for i in range( cellData.GetNumberOfArrays() ) ] arrayNames.extend( [ "", "{}" ] ) # default values @@ -33,7 +38,11 @@ def check_fields( self ): self.state.flush() def check_field( self, field, array_names: list[ str ] ): - field[ "valid" ] = 1 + """ + Check that all the attributes in `attributes_to_check` have a value corresponding to one of the array names. + Set the `valid` property to the result of this check, and if necessary, indicate which properties are invalid. + """ + field[ "valid" ] = FieldStatus.VALID.value field[ "invalid_properties" ] = [] proxy = self.simput_manager.proxymanager.get( field[ "id" ] ) @@ -43,7 +52,7 @@ def check_field( self, field, array_names: list[ str ] ): field[ "invalid_properties" ].append( attr ) if len( field[ "invalid_properties" ] ) != 0: - field[ "valid" ] = 2 + field[ "valid" ] = FieldStatus.INVALID.value else: field.pop( "invalid_properties", None ) @@ -52,8 +61,8 @@ def check_field( self, field, array_names: list[ str ] ): field[ "invalid_children" ] = [] for child in field[ "children" ]: self.check_field( child, array_names ) - if child[ "valid" ] == 2: - field[ "valid" ] = 2 + if child[ "valid" ] == FieldStatus.INVALID.value: + field[ "valid" ] = FieldStatus.INVALID.value field[ "invalid_children" ].append( child[ "title" ] ) if len( field[ "invalid_children" ] ) == 0: field.pop( "invalid_children", None ) diff --git a/geos-trame/src/geos_trame/app/utils/pv_utils.py b/geos-trame/geos_trame/app/utils/pv_utils.py similarity index 62% rename from geos-trame/src/geos_trame/app/utils/pv_utils.py rename to geos-trame/geos_trame/app/utils/pv_utils.py index 95610c474..5ef368bce 100644 --- a/geos-trame/src/geos_trame/app/utils/pv_utils.py +++ b/geos-trame/geos_trame/app/utils/pv_utils.py @@ -4,5 +4,8 @@ import pyvista as pv -def read_unstructured_grid( filename ): +def read_unstructured_grid( filename ) -> pv.UnstructuredGrid: + """ + Read an unstructured grid from a .vtu file. + """ return pv.read( filename ).cast_to_unstructured_grid() diff --git a/geos-trame/src/geos_trame/module/.gitignore b/geos-trame/geos_trame/module/.gitignore similarity index 100% rename from geos-trame/src/geos_trame/module/.gitignore rename to geos-trame/geos_trame/module/.gitignore diff --git a/geos-trame/src/geos_trame/module/__init__.py b/geos-trame/geos_trame/module/__init__.py similarity index 100% rename from geos-trame/src/geos_trame/module/__init__.py rename to geos-trame/geos_trame/module/__init__.py diff --git a/geos-trame/src/geos_trame/schema_generated/README.md b/geos-trame/geos_trame/schema_generated/README.md similarity index 100% rename from geos-trame/src/geos_trame/schema_generated/README.md rename to geos-trame/geos_trame/schema_generated/README.md diff --git a/geos-trame/geos_trame/schema_generated/__init__.py b/geos-trame/geos_trame/schema_generated/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/geos-trame/src/geos_trame/schema_generated/old_schema_mod.py b/geos-trame/geos_trame/schema_generated/old_schema_mod.py similarity index 100% rename from geos-trame/src/geos_trame/schema_generated/old_schema_mod.py rename to geos-trame/geos_trame/schema_generated/old_schema_mod.py diff --git a/geos-trame/src/geos_trame/schema_generated/schema_mod.py b/geos-trame/geos_trame/schema_generated/schema_mod.py similarity index 99% rename from geos-trame/src/geos_trame/schema_generated/schema_mod.py rename to geos-trame/geos_trame/schema_generated/schema_mod.py index 7c0105a3d..c88d1e912 100644 --- a/geos-trame/src/geos_trame/schema_generated/schema_mod.py +++ b/geos-trame/geos_trame/schema_generated/schema_mod.py @@ -16302,8 +16302,3 @@ class Meta: "namespace": "", }, ) - - -class Problem( Problem ): - pass - model_config = ConfigDict( defer_build=True ) diff --git a/geos-trame/tests/test_properties_checker.py b/geos-trame/tests/test_properties_checker.py index 11db0a302..d86e20fb5 100644 --- a/geos-trame/tests/test_properties_checker.py +++ b/geos-trame/tests/test_properties_checker.py @@ -4,16 +4,17 @@ from pathlib import Path from geos_trame.app.core import GeosTrame +from geos_trame.app.data_types.field_status import FieldStatus from tests.trame_fixtures import trame_state, trame_server_layout -def test_properties_checker(trame_server_layout, trame_state): - root_path = Path(__file__).parent.absolute().__str__() +def test_properties_checker( trame_server_layout, trame_state ): + root_path = Path( __file__ ).parent.absolute().__str__() file_name = root_path + "/data/singlePhaseFlow/FieldCaseTutorial3_smoke.xml" - geos_trame = GeosTrame(trame_server_layout[0], file_name) + geos_trame = GeosTrame( trame_server_layout[ 0 ], file_name ) - field = trame_state.deck_tree[4]["children"][0] - assert field["valid"] == 0 - geos_trame.properties_checker.check_field(field) - assert field["valid"] == 1 + field = trame_state.deck_tree[ 4 ][ "children" ][ 0 ] + assert field[ "valid" ] == FieldStatus.UNCHECKED.value + geos_trame.properties_checker.check_fields() + assert field[ "valid" ] == FieldStatus.INVALID.value diff --git a/geos-trame/tests/test_well_intersection.py b/geos-trame/tests/test_well_intersection.py index d14f5bac3..30810ec41 100644 --- a/geos-trame/tests/test_well_intersection.py +++ b/geos-trame/tests/test_well_intersection.py @@ -3,78 +3,69 @@ # SPDX-FileContributor: Lucas Givord - Kitware from pathlib import Path -from trame.app import get_server -from trame_client.utils.testing import enable_testing from geos_trame.app.core import GeosTrame +from tests.trame_fixtures import trame_state, trame_server_layout -def test_internal_well_intersection(): - - server = enable_testing( get_server( client_type="vue3" ), "message" ) - - root_path = Path(__file__).parent.absolute().__str__() +def test_internal_well_intersection( trame_server_layout, trame_state ): + root_path = Path( __file__ ).parent.absolute().__str__() file_name = root_path + "/data/geosDeck/geosDeck.xml" - app = GeosTrame( server, file_name ) - app.state.ready() + app = GeosTrame( trame_server_layout[ 0 ], file_name ) - app.deckInspector.state.object_state = ( "Problem/Mesh/0/VTKMesh/0", True ) - app.deckInspector.state.flush() + trame_state.object_state = ( "Problem/Mesh/0/VTKMesh/0", True ) + trame_state.flush() - app.deckInspector.state.object_state = ( + trame_state.object_state = ( "Problem/Mesh/0/VTKMesh/0/InternalWell/0", True, ) - app.deckInspector.state.flush() + trame_state.flush() - app.deckInspector.state.object_state = ( + trame_state.object_state = ( "Problem/Mesh/0/VTKMesh/0/InternalWell/0/Perforation/0", True, ) - app.deckInspector.state.flush() + trame_state.flush() - app.deckInspector.state.object_state = ( + trame_state.object_state = ( "Problem/Mesh/0/VTKMesh/0/InternalWell/0/Perforation/1", True, ) - app.deckInspector.state.flush() + trame_state.flush() assert app.deckViewer.well_engine.get_number_of_wells() == 1 assert len( app.deckViewer._perforations ) == 2 -def test_vtk_well_intersection(): - - server = enable_testing( get_server( client_type="vue3" ), "message" ) - - root_path = Path(__file__).parent.absolute().__str__() +def test_vtk_well_intersection( trame_server_layout, trame_state ): + root_path = Path( __file__ ).parent.absolute().__str__() file_name = root_path + "/data/geosDeck/geosDeck.xml" - app = GeosTrame( server, file_name ) - app.state.ready() + app = GeosTrame( trame_server_layout[ 0 ], file_name ) - app.deckInspector.state.object_state = ( "Problem/Mesh/0/VTKMesh/0", True ) - app.deckInspector.state.flush() + trame_state.object_state = ( "Problem/Mesh/0/VTKMesh/0", True ) + trame_state.flush() - app.deckInspector.state.object_state = ( "Problem/Mesh/0/VTKMesh/0/VTKWell/0", True ) - app.deckInspector.state.flush() + trame_state.object_state = ( "Problem/Mesh/0/VTKMesh/0/VTKWell/0", True ) + trame_state.flush() - app.deckInspector.state.object_state = ( + trame_state.object_state = ( "Problem/Mesh/0/VTKMesh/0/VTKWell/0/Perforation/0", True, ) - app.deckInspector.state.flush() + trame_state.flush() - app.deckInspector.state.object_state = ( + trame_state.object_state = ( "Problem/Mesh/0/VTKMesh/0/VTKWell/0/Perforation/1", True, ) - app.deckInspector.state.flush() + trame_state.flush() assert app.deckViewer.well_engine.get_number_of_wells() == 1 assert len( app.deckViewer._perforations ) == 2 - app.deckInspector.state.object_state = ( "Problem/Mesh/0/VTKMesh/0/VTKWell/0", False ) - app.deckInspector.state.flush() + trame_state.object_state = ( "Problem/Mesh/0/VTKMesh/0/VTKWell/0", False ) + trame_state.flush() assert app.deckViewer.well_engine.get_number_of_wells() == 0 diff --git a/geos-trame/tests/trame_fixtures.py b/geos-trame/tests/trame_fixtures.py index 3c340a13f..6fbbbb5f5 100644 --- a/geos-trame/tests/trame_fixtures.py +++ b/geos-trame/tests/trame_fixtures.py @@ -11,11 +11,11 @@ def trame_server_layout(): server = Server() server.debug = True - with VAppLayout(server) as layout: + with VAppLayout( server ) as layout: yield server, layout @pytest.fixture -def trame_state(trame_server_layout): - trame_server_layout[0].state.ready() - yield trame_server_layout[0].state \ No newline at end of file +def trame_state( trame_server_layout ): + trame_server_layout[ 0 ].state.ready() + yield trame_server_layout[ 0 ].state From 6153521512a6ea0b399455e59c62b003b8689d6f Mon Sep 17 00:00:00 2001 From: Jean Fechter Date: Wed, 28 May 2025 10:05:05 +0200 Subject: [PATCH 07/16] chore: add geos-trame to type checked packages --- .github/workflows/typing-check.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/typing-check.yml b/.github/workflows/typing-check.yml index 1ac277103..495619a52 100644 --- a/.github/workflows/typing-check.yml +++ b/.github/workflows/typing-check.yml @@ -16,7 +16,7 @@ jobs: max-parallel: 3 matrix: # add packages to check typing - package-name: ["geos-geomechanics", "geos-posp", "geos-timehistory", "geos-utils", "geos-xml-tools", "hdf5-wrapper"] + package-name: ["geos-geomechanics", "geos-posp", "geos-timehistory", "geos-utils", "geos-trame", "geos-xml-tools", "hdf5-wrapper"] steps: - uses: actions/checkout@v4 @@ -35,6 +35,7 @@ jobs: - name: Typing check with mypy # working-directory: ./${{ matrix.package-name }} run: | + python -m mypy geos-trame/geos_trame/app/__main__.py --install-types --non-interactive python -m mypy --config-file ./.mypy.ini --check-untyped-defs ./${{ matrix.package-name }} - name: Format and linting check with ruff From f5c8661c3a0c9145410d2ea32b86a21fa3a48771 Mon Sep 17 00:00:00 2001 From: Jean Fechter Date: Tue, 3 Jun 2025 09:44:54 +0200 Subject: [PATCH 08/16] chore: remove black/isort and add ruff --- geos-trame/.pre-commit-config.yaml | 26 ++++++++++++++++---------- geos-trame/pyproject.toml | 19 ++----------------- 2 files changed, 18 insertions(+), 27 deletions(-) diff --git a/geos-trame/.pre-commit-config.yaml b/geos-trame/.pre-commit-config.yaml index dc0f93736..406666fee 100644 --- a/geos-trame/.pre-commit-config.yaml +++ b/geos-trame/.pre-commit-config.yaml @@ -1,17 +1,23 @@ repos: - - repo: https://github.com/psf/black - rev: 22.3.0 - hooks: - - id: black - exclude: ^.*\b(schema_generated)\b.*$ - entry: black --check --force-exclude - - repo: https://github.com/codespell-project/codespell rev: v2.1.0 hooks: - id: codespell - - repo: https://github.com/PyCQA/flake8 - rev: 4.0.1 + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.11.12 + hooks: + - id: ruff + args: ["--config", "./.ruff.toml"] + + - repo: https://github.com/google/yapf + rev: v0.43.0 + hooks: + - id: yapf + args: ["-ir", "--style", "./.style.yapf"] + + - repo: https://github.com/pre-commit/mirrors-mypy + rev: v1.16.0 hooks: - - id: flake8 + - id: mypy + additional_dependencies: [types-PyYAML] diff --git a/geos-trame/pyproject.toml b/geos-trame/pyproject.toml index 9ed61c548..aef468b2b 100644 --- a/geos-trame/pyproject.toml +++ b/geos-trame/pyproject.toml @@ -56,9 +56,9 @@ build = [ dev = [ "pylint", "mypy", - "black", - "isort", "mypy", + "ruff", + "pre-commit" ] test = [ "pytest==8.3.3", @@ -118,18 +118,3 @@ disable = [ "R0913", # (too-many-arguments) "W0105", # (pointless-string-statement) ] - -[tool.black] -line-length = 88 -target-version = ['py310'] -include = '\.pyi?$' -extend-exclude = ''' -src/geos_trame/schema_generated/*.py -''' - -[tool.isort] -profile = "black" -src_paths = ["src", "tests"] -blackArgs = ["--preview"] -py_version = 310 - From ffe81fbff4f4ec4c328df6ac091a6fadfb6ec59d Mon Sep 17 00:00:00 2001 From: Jean Fechter Date: Tue, 3 Jun 2025 09:45:00 +0200 Subject: [PATCH 09/16] refactor: ruff checks --- geos-trame/geos_trame/app/__main__.py | 5 +- .../geos_trame/app/components/alertHandler.py | 65 ++++---- .../properties_checker.py | 46 ++++-- geos-trame/geos_trame/app/core.py | 52 ++++--- .../geos_trame/app/data_types/renderable.py | 1 + .../geos_trame/app/data_types/tree_node.py | 40 ++--- geos-trame/geos_trame/app/deck/file.py | 51 +++--- geos-trame/geos_trame/app/deck/tree.py | 125 ++++++--------- geos-trame/geos_trame/app/io/data_loader.py | 45 +++--- geos-trame/geos_trame/app/io/xml_parser.py | 30 ++-- geos-trame/geos_trame/app/ui/editor.py | 15 +- geos-trame/geos_trame/app/ui/inspector.py | 135 ++++++++-------- geos-trame/geos_trame/app/ui/plotting.py | 29 ++-- geos-trame/geos_trame/app/ui/timeline.py | 24 ++- .../app/ui/viewer/perforationViewer.py | 17 +- .../geos_trame/app/ui/viewer/regionViewer.py | 13 +- geos-trame/geos_trame/app/ui/viewer/viewer.py | 145 +++++++++--------- .../geos_trame/app/ui/viewer/wellViewer.py | 54 +++---- geos-trame/geos_trame/app/utils/dict_utils.py | 18 +-- geos-trame/geos_trame/app/utils/file_utils.py | 12 +- geos-trame/geos_trame/app/utils/geos_utils.py | 11 ++ geos-trame/geos_trame/app/utils/pv_utils.py | 6 +- geos-trame/geos_trame/module/__init__.py | 4 +- .../schema_generated/old_schema_mod.py | 2 + .../geos_trame/schema_generated/schema_mod.py | 2 + geos-trame/tests/conftest.py | 4 +- geos-trame/tests/test_file_handling.py | 7 +- geos-trame/tests/test_import.py | 3 +- ...st_load_and_visualize_synthetic_dataset.py | 2 + geos-trame/tests/test_properties_checker.py | 10 +- .../test_saving_attribute_modification.py | 1 + .../tests/test_saving_node_modification.py | 1 + .../test_saving_subnode_modifications.py | 1 + geos-trame/tests/test_well_intersection.py | 13 +- geos-trame/tests/trame_fixtures.py | 9 +- geos-trame/tests/utils/testing_tools.py | 21 +-- 36 files changed, 528 insertions(+), 491 deletions(-) rename geos-trame/geos_trame/app/{utils => components}/properties_checker.py (54%) create mode 100644 geos-trame/geos_trame/app/utils/geos_utils.py diff --git a/geos-trame/geos_trame/app/__main__.py b/geos-trame/geos_trame/app/__main__.py index 238394aee..12c2201b2 100644 --- a/geos-trame/geos_trame/app/__main__.py +++ b/geos-trame/geos_trame/app/__main__.py @@ -2,13 +2,16 @@ # SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. # SPDX-FileContributor: Lionel Untereiner from pathlib import Path +from typing import Any from trame.app import get_server +from trame_server import Server from geos_trame.app.core import GeosTrame -def main( server=None, **kwargs ): +def main( server: Server = None, **kwargs: Any ) -> None: + """Main function.""" # Get or create server if server is None: server = get_server() diff --git a/geos-trame/geos_trame/app/components/alertHandler.py b/geos-trame/geos_trame/app/components/alertHandler.py index b3d416468..4ae36030f 100644 --- a/geos-trame/geos_trame/app/components/alertHandler.py +++ b/geos-trame/geos_trame/app/components/alertHandler.py @@ -7,14 +7,14 @@ class AlertHandler( vuetify3.VContainer ): - """ - Vuetify component used to display an alert status. + """Vuetify component used to display an alert status. This alert will be displayed in the bottom right corner of the screen. It will be displayed until closed by the user or after 10 seconds if it is a success or warning. """ - def __init__( self ): + def __init__( self ) -> None: + """Constructor.""" super().__init__( fluid=True, classes="pa-0 ma-0", @@ -31,31 +31,32 @@ def __init__( self ): self.generate_alert_ui() - def generate_alert_ui( self ): - """ - Generate the alert UI. + def generate_alert_ui( self ) -> None: + """Generate the alert UI. The alert will be displayed in the bottom right corner of the screen. Use an abritary z-index value to put the alert on top of the other components. """ - with self: - with vuetify3.VCol( style="width: 40%; position: fixed; right: 50px; bottom: 50px; z-index: 100;", ): - vuetify3.VAlert( - style="max-height: 20vh; overflow-y: auto", - classes="ma-2", - v_for=( "(status, index) in alerts", ), - key="status", - type=( "status.type", "info" ), - text=( "status.message", "" ), - title=( "status.title", "" ), - closable=True, - click_close=( self.on_close, f"[status.id]" ), - ) - - def add_alert( self, type: str, title: str, message: str ): - """ - Add a status to the stack with a unique id. + with ( + self, + vuetify3.VCol( style="width: 40%; position: fixed; right: 50px; bottom: 50px; z-index: 100;", ), + ): + vuetify3.VAlert( + style="max-height: 20vh; overflow-y: auto", + classes="ma-2", + v_for=( "(status, index) in alerts", ), + key="status", + type=( "status.type", "info" ), + text=( "status.message", "" ), + title=( "status.title", "" ), + closable=True, + click_close=( self.on_close, "[status.id]" ), + ) + + def add_alert( self, type: str, title: str, message: str ) -> None: + """Add a status to the stack with a unique id. + If there are more than 5 alerts displayed, remove the oldest. A warning will be automatically closed after 10 seconds. """ @@ -77,21 +78,15 @@ def add_alert( self, type: str, title: str, message: str ): if type == "warning": asyncio.get_event_loop().call_later( self.__lifetime_of_alert, self.on_close, alert_id ) - async def add_warning( self, title: str, message: str ): - """ - Add an alert of type "warning" - """ + async def add_warning( self, title: str, message: str ) -> None: + """Add an alert of type 'warning'.""" self.add_alert( "warning", title, message ) - async def add_error( self, title: str, message: str ): - """ - Add an alert of type "error" - """ + async def add_error( self, title: str, message: str ) -> None: + """Add an alert of type 'error'.""" self.add_alert( "error", title, message ) - def on_close( self, alert_id ): - """ - Remove in the state the alert associated to the given id. - """ + def on_close( self, alert_id: int ) -> None: + """Remove in the state the alert associated to the given id.""" self.state.alerts = list( filter( lambda i: i[ "id" ] != alert_id, self.state.alerts ) ) self.state.flush() diff --git a/geos-trame/geos_trame/app/utils/properties_checker.py b/geos-trame/geos_trame/app/components/properties_checker.py similarity index 54% rename from geos-trame/geos_trame/app/utils/properties_checker.py rename to geos-trame/geos_trame/app/components/properties_checker.py index 805c76dac..0c351499a 100644 --- a/geos-trame/geos_trame/app/utils/properties_checker.py +++ b/geos-trame/geos_trame/app/components/properties_checker.py @@ -1,6 +1,7 @@ # SPDX-License-Identifier: Apache-2.0 # SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. # SPDX-FileContributor: Kitware +from typing import Any from trame_client.widgets.core import AbstractElement from trame_simput import get_simput_manager @@ -8,38 +9,39 @@ from geos_trame.app.data_types.field_status import FieldStatus from geos_trame.app.deck.tree import DeckTree from geos_trame.app.ui.viewer.regionViewer import RegionViewer +from geos_trame.app.utils.geos_utils import group_name_ref_array_to_list -attributes_to_check = [ "region_attribute", "fields_to_import", "surfacicFieldsToImport" ] +# Doc reference: https://geosx-geosx.readthedocs-hosted.com/en/latest/docs/sphinx/datastructure/CompleteXMLSchema.html +attributes_to_check = [ ( "region_attribute", str ), ( "fields_to_import", list ), ( "surfacicFieldsToImport", list ) ] class PropertiesChecker( AbstractElement ): - """ - Class to check the validity of properties within a deck tree. - """ + """Validity checker of properties within a deck tree.""" - def __init__( self, tree: DeckTree, region_viewer: RegionViewer, **kwargs ): + def __init__( self, tree: DeckTree, region_viewer: RegionViewer, **kwargs: Any ) -> None: + """Constructor.""" super().__init__( "div", **kwargs ) self.tree = tree self.region_viewer = region_viewer self.simput_manager = get_simput_manager( id=self.state.sm_id ) - def check_fields( self ): - """ - Get the names of all the cell data arrays from the input of the region viewer, then check that all the attributes - in `attributes_to_check` have a value corresponding to one of the array names. + def check_fields( self ) -> None: + """Check all the fields in the deck_tree. + + Get the names of all the cell data arrays from the input of the region viewer, then check that + all the attributes in `attributes_to_check` have a value corresponding to one of the array names. """ cellData = self.region_viewer.input.GetCellData() arrayNames = [ cellData.GetArrayName( i ) for i in range( cellData.GetNumberOfArrays() ) ] - arrayNames.extend( [ "", "{}" ] ) # default values for field in self.state.deck_tree: self.check_field( field, arrayNames ) self.state.dirty( "deck_tree" ) self.state.flush() - def check_field( self, field, array_names: list[ str ] ): - """ - Check that all the attributes in `attributes_to_check` have a value corresponding to one of the array names. + def check_field( self, field: dict, array_names: list[ str ] ) -> None: + """Check that all the attributes in `attributes_to_check` have a value corresponding to one of the array names. + Set the `valid` property to the result of this check, and if necessary, indicate which properties are invalid. """ field[ "valid" ] = FieldStatus.VALID.value @@ -47,9 +49,21 @@ def check_field( self, field, array_names: list[ str ] ): proxy = self.simput_manager.proxymanager.get( field[ "id" ] ) if proxy is not None: - for attr in attributes_to_check: - if attr in proxy.definition and proxy[ attr ] not in array_names: - field[ "invalid_properties" ].append( attr ) + for attr, expected_type in attributes_to_check: + if attr in proxy.definition: + if ( expected_type is str and proxy[ attr ] # value is not empty (valid) + and proxy[ attr ] not in array_names # value is not in the expected names + ): + field[ "invalid_properties" ].append( attr ) + elif expected_type is list: + arrays: list[ str ] | None = group_name_ref_array_to_list( proxy[ attr ] ) + if arrays is None: + field[ "invalid_properties" ].append( attr ) + continue + for array_name in arrays: + if array_name not in array_names: + field[ "invalid_properties" ].append( attr ) + break if len( field[ "invalid_properties" ] ) != 0: field[ "valid" ] = FieldStatus.INVALID.value diff --git a/geos-trame/geos_trame/app/core.py b/geos-trame/geos_trame/app/core.py index 0c4e2d1cc..80231ed2a 100644 --- a/geos-trame/geos_trame/app/core.py +++ b/geos-trame/geos_trame/app/core.py @@ -6,6 +6,9 @@ from trame.decorators import TrameApp from trame.widgets import html, simput from trame.widgets import vuetify3 as vuetify +from trame_server import Server +from trame_server.controller import Controller +from trame_server.state import State from trame_simput import get_simput_manager from geos_trame import module @@ -13,7 +16,7 @@ from geos_trame.app.io.data_loader import DataLoader from geos_trame.app.ui.viewer.regionViewer import RegionViewer from geos_trame.app.ui.viewer.wellViewer import WellViewer -from geos_trame.app.utils.properties_checker import PropertiesChecker +from geos_trame.app.components.properties_checker import PropertiesChecker from geos_trame.app.ui.editor import DeckEditor from geos_trame.app.ui.inspector import DeckInspector from geos_trame.app.ui.plotting import DeckPlotting @@ -27,8 +30,8 @@ @TrameApp() class GeosTrame: - def __init__( self, server, file_name: str ): - + def __init__( self, server: Server, file_name: str ) -> None: + """Constructor.""" self.alertHandler: AlertHandler | None = None self.deckPlotting: DeckPlotting | None = None self.deckViewer: DeckViewer | None = None @@ -77,26 +80,32 @@ def __init__( self, server, file_name: str ): self.build_ui() @property - def state( self ): + def state( self ) -> State: + """Getter for the state.""" return self.server.state @property - def ctrl( self ): + def ctrl( self ) -> Controller: + """Getter for the controller.""" return self.server.controller - def set_input_file( self, file_name ): - """sets the input file of the InputTree object and populates simput/ui""" + def set_input_file( self, file_name: str ) -> None: + """Sets the input file of the InputTree object and populates simput/ui.""" self.tree.set_input_file( file_name ) - def deck_ui( self ): - """Generates the UI for the deck edition / visualization tab""" + def deck_ui( self ) -> None: + """Generates the UI for the deck edition / visualization tab.""" with vuetify.VRow( classes="mb-6 fill-height" ): with vuetify.VCol( cols=2, order=1, ): self.deckInspector = DeckInspector( source=self.tree, classes="fit-content" ) - vuetify.VBtn( text="Check fields", classes="ma-4", click=( self.properties_checker.check_fields, ) ) + vuetify.VBtn( + text="Check fields", + classes="ma-4", + click=( self.properties_checker.check_fields, ), + ) with vuetify.VCol( cols=10, @@ -133,9 +142,8 @@ def deck_ui( self ): style="flex: 1; height: 40%; width: 100%;", ) - def build_ui( self ): - """Generates the full UI for the GEOS Trame Application""" - + def build_ui( self ) -> None: + """Generates the full UI for the GEOS Trame Application.""" with VAppLayout( self.server ) as layout: self.simput_widget.register_layout( layout ) @@ -154,18 +162,20 @@ def build_ui( self ): style= "position: absolute; top: 0; left: 0; height: 100%; width: 100%; display: flex; align-items: center; justify-content: center;", ): - with html.Div( - v_if=( "tab_idx == 0", ), - style= - "height: 100%; width: 100%; display: flex; align-items: center; justify-content: flex-end;", - ): - with vuetify.VBtn( + with ( + html.Div( + v_if=( "tab_idx == 0", ), + style= + "height: 100%; width: 100%; display: flex; align-items: center; justify-content: flex-end;", + ), + vuetify.VBtn( click=self.tree.write_files, icon=True, style="z-index: 1;", id="save-button", - ): - vuetify.VIcon( "mdi-content-save-outline" ) + ), + ): + vuetify.VIcon( "mdi-content-save-outline" ) with html.Div( style= diff --git a/geos-trame/geos_trame/app/data_types/renderable.py b/geos-trame/geos_trame/app/data_types/renderable.py index 20e84932a..e0312401d 100644 --- a/geos-trame/geos_trame/app/data_types/renderable.py +++ b/geos-trame/geos_trame/app/data_types/renderable.py @@ -5,6 +5,7 @@ class Renderable( Enum ): + """Enum class for renderable types and their ids.""" VTKMESH = "VTKMesh" INTERNALMESH = "InternalMesh" INTERNALWELL = "InternalWell" diff --git a/geos-trame/geos_trame/app/data_types/tree_node.py b/geos-trame/geos_trame/app/data_types/tree_node.py index 3601ff9aa..f8c7e50fe 100644 --- a/geos-trame/geos_trame/app/data_types/tree_node.py +++ b/geos-trame/geos_trame/app/data_types/tree_node.py @@ -3,14 +3,17 @@ # SPDX-FileContributor: Kitware from dataclasses import dataclass -from geos_trame.app.data_types.field_status import FieldStatus - @dataclass class TreeNode: + """Single element of the tree, used by `DeckTree`. + + `valid` has to be an int for serialization purposes, but is actually a FieldStatus so only possibles values are: + - 0 (UNCHECKED): Validity check has not been performed. + - 1 (VALID): TreeNode is checked and valid. + - 2 (INVALID): TreeNode is checked and invalid. """ - Single element of the tree, used by `DeckTree`. - """ + id: str title: str children: list @@ -21,22 +24,13 @@ class TreeNode: @property def json( self ) -> dict: - if self.children: - return dict( - id=self.id, - title=self.title, - is_drawable=self.is_drawable, - drawn=self.drawn, - valid=self.valid, - children=[ c.json for c in self.children ], - hidden_children=[ c.json for c in self.hidden_children ], - ) - return dict( - id=self.id, - title=self.title, - is_drawable=self.is_drawable, - drawn=self.drawn, - valid=self.valid, - children=None, - hidden_children=[], - ) + """Get the tree node as json.""" + return { + "id": self.id, + "title": self.title, + "is_drawable": self.is_drawable, + "drawn": self.drawn, + "valid": self.valid, + "children": [ c.json for c in self.children ] if self.children else None, + "hidden_children": ( [ c.json for c in self.hidden_children ] if self.hidden_children else [] ), + } diff --git a/geos-trame/geos_trame/app/deck/file.py b/geos-trame/geos_trame/app/deck/file.py index 146a2a154..2755761f9 100644 --- a/geos-trame/geos_trame/app/deck/file.py +++ b/geos-trame/geos_trame/app/deck/file.py @@ -10,6 +10,7 @@ from xsdata.utils import text from xsdata_pydantic.bindings import DictEncoder, XmlContext, XmlParser, XmlSerializer +from geos_trame.app.data_types.renderable import Renderable from geos_trame.app.geosTrameException import GeosTrameException from geos_trame.app.io.xml_parser import XMLParser from geos_trame.app.utils.file_utils import normalize_path @@ -17,14 +18,11 @@ class DeckFile( object ): - """ - Holds the information of a deck file. - Can be empty. - """ + """Holds the information of a deck file. Can be empty.""" + + def __init__( self, filename: str, **kwargs: Any ) -> None: + """Constructor. - def __init__( self, filename: str, **kwargs ) -> None: - """ - Constructor. Input: filename: file name of the deck file """ @@ -44,8 +42,8 @@ def __init__( self, filename: str, **kwargs ) -> None: self.path = os.path.dirname( self.filename ) def open_deck_file( self, filename: str ) -> None: - """ - Opens a file and parses it. + """Opens a file and parses it. + Input: filename: file name of the input file Signals: @@ -53,7 +51,6 @@ def open_deck_file( self, filename: str ) -> None: Raises: GeosTrameException: On invalid input file """ - self.changed = False self.root_node = None @@ -80,19 +77,19 @@ def open_deck_file( self, filename: str ) -> None: element_name_generator=text.pascal_case, attribute_name_generator=text.camel_case, ) - parser = XmlParser( context=context, config=ParserConfig( - ) ) # fail_on_unknown_properties=True, fail_on_unknown_attributes=True, fail_on_converter_warnings=True + parser = XmlParser( context=context, config=ParserConfig() ) try: self.problem = parser.parse( simulation_deck, Problem ) except ElementTree.XMLSyntaxError as e: msg = "Failed to parse input file %s:\n%s\n" % ( filename, e ) - raise GeosTrameException( msg ) + raise GeosTrameException( msg ) from e encoder = DictEncoder( context=context, config=SerializerConfig( indent=" " ) ) self.pb_dict = { "Problem": encoder.encode( self.problem ) } self.inspect_tree = build_inspect_tree( encoder.encode( self.problem ) ) def to_str( self ) -> str: + """Get the problem as a string.""" config = SerializerConfig( indent=" ", xml_declaration=False ) context = XmlContext( element_name_generator=text.pascal_case, @@ -102,9 +99,8 @@ def to_str( self ) -> str: return serializer.render( self.problem ) -def build_inspect_tree( obj ) -> dict: - """Return the fields of a dataclass instance as a new dictionary mapping - field names to field values. +def build_inspect_tree( obj: dict ) -> dict: + """Return the fields of a dataclass instance as a new dictionary mapping field names to field values. Example usage:: @@ -121,25 +117,16 @@ class C: dataclass instances. This will also look into built-in containers: tuples, lists, and dicts. Other objects are copied with 'copy.deepcopy()'. """ - return _build_inspect_tree_inner( "Problem", obj, [] ) -def _build_inspect_tree_inner( key, obj, path ) -> dict: - sub_node = dict() - if "name" in obj: - sub_node[ "title" ] = obj[ "name" ] - else: - sub_node[ "title" ] = key - sub_node[ "children" ] = list() - sub_node[ "is_drawable" ] = key in [ - "VTKMesh", - "InternalMesh", - "InternalWell", - "VTKWell", - "Perforation", - ] - sub_node[ "drawn" ] = False +def _build_inspect_tree_inner( key: str, obj: dict, path: list ) -> dict: + sub_node = { + "title": obj.get( "name", key ), + "children": [], + "is_drawable": key in ( item.value for item in Renderable ), + "drawn": False, + } for key, value in obj.items(): diff --git a/geos-trame/geos_trame/app/deck/tree.py b/geos-trame/geos_trame/app/deck/tree.py index d565b8d37..cadf2bf78 100644 --- a/geos-trame/geos_trame/app/deck/tree.py +++ b/geos-trame/geos_trame/app/deck/tree.py @@ -3,6 +3,7 @@ # SPDX-FileContributor: Lionel Untereiner import os from collections import defaultdict +from typing import Any import dpath import funcy @@ -16,31 +17,26 @@ from geos_trame.app.deck.file import DeckFile from geos_trame.app.geosTrameException import GeosTrameException from geos_trame.app.utils.file_utils import normalize_path, format_xml -from geos_trame.schema_generated.schema_mod import Problem, Included, File +from geos_trame.schema_generated.schema_mod import Problem, Included, File, Functions class DeckTree( object ): - """ - A tree that represents a deck file along with all the available blocks and parameters. - """ + """A tree that represents a deck file along with all the available blocks and parameters.""" - def __init__( self, sm_id=None, **kwargs ): - """ - Constructor. - """ + def __init__( self, sm_id: str | None = None, **kwargs: Any ) -> None: + """Constructor.""" super( DeckTree, self ).__init__( **kwargs ) self.input_file: DeckFile | None = None self.input_filename: str | None = None self.input_folder: str | None = None self.root = None - self.path_map = {} self.input_has_errors = False self._sm_id = sm_id - def set_input_file( self, input_filename: str ): - """ - Set a new input file + def set_input_file( self, input_filename: str ) -> None: + """Set a new input file. + Input: input_filename[str]: The name of the input file Return: @@ -54,36 +50,42 @@ def set_input_file( self, input_filename: str ): return def get_mesh( self ) -> str: + """Get the path of the mesh.""" assert self.input_file is not None and self.input_file.problem is not None return normalize_path( self.input_file.path + "/" + self.input_file.problem.mesh[ 0 ].vtkmesh[ 0 ].file ) - def get_abs_path( self, file ) -> str: + def get_abs_path( self, file: str ) -> str: + """Get the absolute path from a path.""" assert self.input_file is not None and self.input_file.path is not None return normalize_path( self.input_file.path + "/" + file ) def to_str( self ) -> str: + """Get the input file as a string.""" assert self.input_file is not None return self.input_file.to_str() def get_tree( self ) -> dict: + """Get the tree from the input file.""" assert self.input_file is not None and self.input_file.inspect_tree is not None return self.input_file.inspect_tree - def update( self, path, key, value ) -> None: + def update( self, path: str, key: str, value: Any ) -> None: + """Update the tree.""" new_path = [ int( x ) if x.isdigit() else x for x in path.split( "/" ) ] new_path.append( key ) assert self.input_file is not None and self.input_file.pb_dict is not None funcy.set_in( self.input_file.pb_dict, new_path, value ) - def search( self, path ) -> list | None: + def _search( self, path: str ) -> list | None: new_path = path.split( "/" ) if self.input_file is None: return None assert self.input_file.pb_dict is not None return dpath.values( self.input_file.pb_dict, new_path ) - def decode( self, path ) -> BaseModel | None: - data = self.search( path ) + def decode( self, path: str ) -> BaseModel | None: + """Decode the given file to a BaseModel.""" + data = self._search( path ) if data is None: return None @@ -96,9 +98,7 @@ def decode( self, path ) -> BaseModel | None: @staticmethod def decode_data( data: dict ) -> Problem: - """ - Convert a data to a xml serializable file - """ + """Convert a data to a xml serializable file.""" context = XmlContext( element_name_generator=text.pascal_case, attribute_name_generator=text.camel_case, @@ -108,7 +108,8 @@ def decode_data( data: dict ) -> Problem: return node @staticmethod - def to_xml( obj ) -> str: + def to_xml( obj: BaseModel ) -> str: + """Convert the given obj to xml.""" context = XmlContext( element_name_generator=text.pascal_case, attribute_name_generator=text.camel_case, @@ -120,34 +121,34 @@ def to_xml( obj ) -> str: return format_xml( serializer.render( obj ) ) def timeline( self ) -> list[ dict ] | None: + """Get the timeline.""" if self.input_file is None: return None if self.input_file.problem is None: return None - timeline = list() + timeline = [] # list root events global_id = 0 for e in self.input_file.problem.events[ 0 ].periodic_event: - item: dict[ str, str | int ] = dict() - item[ "id" ] = global_id - item[ "summary" ] = e.name - item[ "start_date" ] = e.begin_time + item: dict[ str, str | int ] = { + "id": global_id, + "summary": e.name, + "start_date": e.begin_time, + } timeline.append( item ) global_id = global_id + 1 return timeline - def plots( self ): + def plots( self ) -> list[ Functions ]: + """Get the functions in the current problem.""" assert self.input_file is not None and self.input_file.problem is not None return self.input_file.problem.functions def write_files( self ) -> None: - """ - Write geos files with all changes made by the user. - """ - - pb = self.search( "Problem" ) + """Write geos files with all changes made by the user.""" + pb = self._search( "Problem" ) if pb is None: return files = self._split( pb ) @@ -156,7 +157,7 @@ def write_files( self ) -> None: model_loaded: Problem = DeckTree.decode_data( content ) model_with_changes: Problem = self._apply_changed_properties( model_loaded ) - assert self.input_file is not None and self.input_file.xml_parser is not None + assert ( self.input_file is not None and self.input_file.xml_parser is not None ) if self.input_file.xml_parser.contains_include_files(): includeName: str = self.input_file.xml_parser.get_relative_path_of_file( filepath ) DeckTree._append_include_file( model_with_changes, includeName ) @@ -171,26 +172,10 @@ def write_files( self ) -> None: file.write( model_as_xml ) file.close() - def _set_input_file( self, input_file ): - """ - Copies the nodes of an input file into the tree - Input: - input_file[InputFile]: Input file to copy - Return: - bool: True if successful - """ - self.input_has_errors = False - if input_file.root_node is None: - return False - self.input_file = input_file - self.input_filename = input_file.filename - - return False - @staticmethod def _append_include_file( model: Problem, included_file_path: str ) -> None: - """ - Append an Included object which follows this structure according to the documentation: + """Append an Included object which follows this structure according to the documentation. + @@ -209,9 +194,9 @@ def _append_include_file( model: Problem, included_file_path: str ) -> None: @staticmethod def _append_id( filename: str ) -> str: - """ - Return the new filename with the correct suffix and his extension. The suffix - added will be '_vX' where X is the incremented value of the current version. + """Return the new filename with the correct suffix and his extension. + + The suffix added will be '_vX' where X is the incremented value of the current version. '_v0' if any suffix is present. """ name, ext = os.path.splitext( filename ) @@ -230,8 +215,7 @@ def _append_id( filename: str ) -> str: @staticmethod def _convert_to_camel_case( content: str ) -> str: - """ - Convert any given string in CamelCase. + """Convert any given string in CamelCase. Useful to transform trame_simput convention in geos schema names convention. """ @@ -240,20 +224,14 @@ def _convert_to_camel_case( content: str ) -> str: @staticmethod def _convert_to_snake_case( content: str ) -> str: - """ - Convert any given string in snake case. + """Convert any given string in snake case. Useful to transform geos schema names convention in trame_simput convention. """ return "".join( [ "_" + char.lower() if char.isupper() else char for char in content ] ).lstrip( "_" ) def _apply_changed_properties( self, model: Problem ) -> Problem: - """ - Retrieves all edited 'properties' from the simput_manager and apply it to a - given model. - - """ - + """Retrieves all edited 'properties' from the simput_manager and apply it to a given model.""" manager = get_simput_manager( self._sm_id ) modified_proxy_ids: set[ str ] = manager.proxymanager.dirty_proxy_data @@ -271,13 +249,12 @@ def _apply_changed_properties( self, model: Problem ) -> Problem: DeckTree._set_base_model_properties( model_as_dict, proxy_id, events_as_dict ) - model = getattr( model, "model_validate" )( model_as_dict ) + model = model.model_validate( model_as_dict ) return model @staticmethod def _convert_proxy_path_into_proxy_names( proxy_path: str ) -> list[ str ]: - """ - Split a given proxy path into a list of proxy names. + """Split a given proxy path into a list of proxy names. note: each proxy name will be converted in snake case to fit with the pydantic model naming convention. @@ -289,10 +266,7 @@ def _convert_proxy_path_into_proxy_names( proxy_path: str ) -> list[ str ]: @staticmethod def _set_base_model_properties( model: dict, proxy_path: str, properties: dict ) -> None: - """ - Apply all changed property to the model for a specific proxy. - """ - + """Apply all changed property to the model for a specific proxy.""" # retrieve the whole BaseModel list to the modified proxy proxy_names = DeckTree._convert_proxy_path_into_proxy_names( proxy_path ) model_copy = model @@ -332,7 +306,7 @@ def _set_base_model_properties( model: dict, proxy_path: str, properties: dict ) current_node = model_inverted[ 1 ] current_base_model = current_node[ prop_identifier ][ index ] - current_base_model = getattr( current_base_model, "model_validate" )( properties ) + current_base_model = current_base_model.model_validate( properties ) current_node[ prop_identifier ][ index ] = current_base_model @@ -342,10 +316,7 @@ def _set_base_model_properties( model: dict, proxy_path: str, properties: dict ) @staticmethod def _get_base_model_from_path( model: dict, proxy_id: str ) -> dict: - """ - Retrieve the BaseModel changed from the proxy id. The proxy_id is a unique path - from the simput manager. - """ + """Retrieve the BaseModel changed from the proxy id. The proxy_id is a unique path from the simput manager.""" proxy_names = DeckTree._convert_proxy_path_into_proxy_names( proxy_id ) model_found: dict = model @@ -375,7 +346,7 @@ def _split( self, xml: list ) -> defaultdict[ str, dict[ str, str ] ]: data = self.input_file.xml_parser.file_to_tags restructured_files: defaultdict[ str, dict ] = defaultdict( dict ) for file_path, associated_tags in data.items(): - restructured_files[ file_path ] = dict() + restructured_files[ file_path ] = {} for tag, contents in xml[ 0 ].items(): if len( contents ) == 0: continue diff --git a/geos-trame/geos_trame/app/io/data_loader.py b/geos-trame/geos_trame/app/io/data_loader.py index e55cdaeaa..ff27c9fcc 100644 --- a/geos-trame/geos_trame/app/io/data_loader.py +++ b/geos-trame/geos_trame/app/io/data_loader.py @@ -7,6 +7,7 @@ from trame_client.widgets.core import AbstractElement import pyvista as pv +from geos_trame.app.deck.tree import DeckTree from geos_trame.app.geosTrameException import GeosTrameException from geos_trame.app.ui.viewer.regionViewer import RegionViewer from geos_trame.app.ui.viewer.wellViewer import WellViewer @@ -20,20 +21,25 @@ class DataLoader( AbstractElement ): - """ - Helper class to handle IO operations for data loading. - """ - - def __init__( self, source, region_viewer: RegionViewer, well_viewer: WellViewer, **kwargs ): + """Helper class to handle IO operations for data loading.""" + + def __init__( + self, + source: DeckTree, + region_viewer: RegionViewer, + well_viewer: WellViewer, + **kwargs: Any, + ) -> None: + """Constructor.""" super().__init__( "span", **kwargs ) self.source = source self.region_viewer = region_viewer self.well_viewer = well_viewer - self.state.change( "object_state" )( self.update_object_state ) + self.state.change( "object_state" )( self._update_object_state ) - def update_object_state( self, object_state: tuple[ str, bool ], **_ ): + def _update_object_state( self, object_state: tuple[ str, bool ], **_: dict ) -> None: path, show_obj = object_state @@ -65,13 +71,12 @@ def update_object_state( self, object_state: tuple[ str, bool ], **_ ): self._update_internalwell( active_block, path, show_obj ) - if isinstance( active_block, Perforation ): - if self.well_viewer.get_number_of_wells() == 0 and show_obj: - self.ctrl.on_add_warning( - "Can't display " + active_block.name, - "Please display a well before creating a perforation", - ) - return + if ( isinstance( active_block, Perforation ) and self.well_viewer.get_number_of_wells() == 0 and show_obj ): + self.ctrl.on_add_warning( + "Can't display " + active_block.name, + "Please display a well before creating a perforation", + ) + return self.ctrl.update_viewer( active_block, path, show_obj ) @@ -94,8 +99,8 @@ def _update_vtkwell( self, well: Vtkwell, path: str, show: bool ) -> None: self.well_viewer.add_mesh( well_polydata, path ) def _update_internalwell( self, well: InternalWell, path: str, show: bool ) -> None: - """ - Used to control the visibility of the InternalWell. + """Used to control the visibility of the InternalWell. + This method will create the mesh if it doesn't exist. """ if not show: @@ -115,8 +120,8 @@ def _update_internalwell( self, well: InternalWell, path: str, show: bool ) -> N @staticmethod def __parse_polyline_property( polyline_property: str, dtype: Type[ Any ] ) -> np.ndarray: - """ - Internal method used to parse and convert a property, such as polyline_node_coords, from an InternalWell. + """Internal method used to parse and convert a property, such as polyline_node_coords, from an InternalWell. + This string always follow this for : "{ { 800, 1450, 395.646 }, { 800, 1450, -554.354 } }" """ @@ -134,8 +139,8 @@ def __parse_polyline_property( polyline_property: str, dtype: Type[ Any ] ) -> n points.append( point ) return np.array( points, dtype=dtype ) - except ValueError: + except ValueError as e: raise GeosTrameException( "cannot be able to convert the property into a numeric array: ", ValueError, - ) + ) from e diff --git a/geos-trame/geos_trame/app/io/xml_parser.py b/geos-trame/geos_trame/app/io/xml_parser.py index 05148b56c..7e2edc0e9 100644 --- a/geos-trame/geos_trame/app/io/xml_parser.py +++ b/geos-trame/geos_trame/app/io/xml_parser.py @@ -15,18 +15,13 @@ class XMLParser( object ): - """ - Class used to parse a valid XML geos file and construct a link between - each file when they are included. + """Class used to parse a valid XML geos file and construct a link between each file when they are included. Useful to be able to save it later. """ - def __init__( self, filename: str ): - """ - Constructor which takes in input the xml file used to generate pedantic file. - """ - + def __init__( self, filename: str ) -> None: + """Constructor which takes in input the xml file used to generate pedantic file.""" self.filename = filename self.file_to_tags: defaultdict = defaultdict( list ) self.file_to_relative_path: dict = {} @@ -46,34 +41,33 @@ def __init__( self, filename: str ): self._is_valid = False def is_valid( self ) -> bool: + """Getter for is_valid.""" if not self._is_valid: print( "XMLParser isn't valid", file=sys.stderr ) return self._is_valid def build( self ) -> None: + """Read the file.""" if not self.is_valid(): raise GeosTrameException( "Cannot parse this file." ) self._read() def get_simulation_deck( self ) -> ElementTree.Element: + """Get the simulation deck.""" if not self.is_valid(): raise GeosTrameException( "Not valid file, cannot return the deck." ) return self.simulation_deck def contains_include_files( self ) -> bool: - """ - Return True if the parsed file contains included file or not. - """ + """Return True if the parsed file contains included file or not.""" return len( self.file_to_relative_path ) > 0 def get_relative_path_of_file( self, filename: str ) -> str: - """ - Return the relative path of a given filename. - """ + """Return the relative path of a given filename.""" return self.file_to_relative_path[ filename ] def _read( self ) -> ElementTree.Element: - """Reads a xml file (and recursively its included files) into memory + """Reads a xml file (and recursively its included files) into memory. Returns: SimulationDeck: The simulation deck @@ -94,7 +88,7 @@ def _read( self ) -> ElementTree.Element: self.root.remove( include_node ) for neighbor in self.root.iter(): - for key in neighbor.attrib.keys(): + for key in neighbor.attrib: # remove unnecessary whitespaces for indentation s = re.sub( r"\s{2,}", " ", neighbor.get( key ) ) neighbor.set( key, s ) @@ -113,12 +107,13 @@ def _merge_xml_nodes( Args: existing_node (lxml.etree.Element): The current node in the base xml structure. target_node (lxml.etree.Element): The node to insert. + fname (str): The target file name. level (int): The xml file depth. """ if not self.is_valid(): raise GeosTrameException( "Not valid file, cannot merge nodes" ) # Copy attributes on the current level - for tk in target_node.attrib.keys(): + for tk in target_node.attrib: existing_node.set( tk, target_node.get( tk ) ) # Copy target children into the xml structure @@ -168,6 +163,7 @@ def _merge_included_xml_files( Args: root (lxml.etree.Element): The root node of the base xml structure. + file_path (str): The file path. fname (str): The name of the target xml file to merge. include_count (int): The current recursion depth. max_include (int): The maximum number of xml files to include (default = 100) diff --git a/geos-trame/geos_trame/app/ui/editor.py b/geos-trame/geos_trame/app/ui/editor.py index a693da434..1757c6d36 100644 --- a/geos-trame/geos_trame/app/ui/editor.py +++ b/geos-trame/geos_trame/app/ui/editor.py @@ -1,14 +1,19 @@ # SPDX-License-Identifier: Apache-2.0 # SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. # SPDX-FileContributor: Lionel Untereiner +from typing import Any + from trame.widgets import code, simput from trame.widgets import vuetify3 as vuetify from trame_simput import get_simput_manager +from geos_trame.app.deck.tree import DeckTree + class DeckEditor( vuetify.VCard ): - def __init__( self, source=None, **kwargs ): + def __init__( self, source: DeckTree, **kwargs: Any ) -> None: + """Constructor.""" super().__init__( **kwargs ) self.tree = source @@ -19,7 +24,7 @@ def __init__( self, source=None, **kwargs ): self.state.active_name = "Problem" self.state.active_snippet = "" - self.state.change( "active_id" )( self.on_active_id ) + self.state.change( "active_id" )( self._on_active_id ) with self: with vuetify.VCardTitle( "Components editor" ): @@ -68,7 +73,7 @@ def __init__( self, source=None, **kwargs ): textmate=( "editor_textmate", None ), ) - def on_active_id( self, active_id, **kwargs ): + def _on_active_id( self, active_id: str | None, **_: Any ) -> None: # this function triggers when a block is selected from the tree in the ui if active_id is None: @@ -86,7 +91,7 @@ def on_active_id( self, active_id, **kwargs ): self.state.active_id = active_id self.state.active_ids = [ active_id ] - if hasattr( active_block, "name" ): + if active_block is not None and hasattr( active_block, "name" ): self.state.active_name = active_block.name else: self.state.active_name = None @@ -97,4 +102,4 @@ def on_active_id( self, active_id, **kwargs ): self.state.active_type = simput_type self.state.active_types = [ simput_type ] - self.state.active_snippet = self.tree.to_xml( active_block ) + self.state.active_snippet = DeckTree.to_xml( active_block ) diff --git a/geos-trame/geos_trame/app/ui/inspector.py b/geos-trame/geos_trame/app/ui/inspector.py index 7c90eca9d..e7897e80a 100644 --- a/geos-trame/geos_trame/app/ui/inspector.py +++ b/geos-trame/geos_trame/app/ui/inspector.py @@ -11,14 +11,17 @@ from geos_trame.app.data_types.field_status import FieldStatus from geos_trame.app.data_types.renderable import Renderable from geos_trame.app.data_types.tree_node import TreeNode +from geos_trame.app.deck.tree import DeckTree from geos_trame.app.utils.dict_utils import iterate_nested_dict +from geos_trame.schema_generated.schema_mod import Problem vuetify.enable_lab() class DeckInspector( vuetify.VTreeview ): - def __init__( self, listen_to_active=True, source=None, **kwargs ): + def __init__( self, source: DeckTree, listen_to_active: bool = True, **kwargs: Any ) -> None: + """Constructor.""" super().__init__( # data items=( "deck_tree", ), @@ -39,68 +42,76 @@ def __init__( self, listen_to_active=True, source=None, **kwargs ): }, ) self.tree = source - self._source = None + self._source: dict | None = None self.listen_to_active = listen_to_active self.state.object_state = ( "", False ) # register used types from Problem - self.simput_types = [] + self.simput_types: list = [] self.simput_manager = get_simput_manager( id=self.state.sm_id ) if source.input_file is None: return - self.set_source( source.input_file.problem ) + self._set_source( source.input_file.problem ) - def on_change( topic, ids=None, **_ ): - if topic == "changed": + def _on_change( topic: str, ids: list | None = None ) -> None: + if ids is not None and topic == "changed": for obj_id in ids: proxy = self.simput_manager.proxymanager.get( obj_id ) self.tree.decode( obj_id ) for prop in proxy.edited_property_names: self.tree.update( obj_id, prop, proxy.get_property( prop ) ) - self.simput_manager.proxymanager.on( on_change ) - - with self: - with vuetify.Template( v_slot_append="{ item }" ): - with vuetify.VTooltip( v_if=( "item.valid == 2", ) ): - with vuetify.Template( - v_slot_activator=( "{ props }", ), - __properties__=[ ( "v_slot_activator", "v-slot:activator" ) ], - ): - vuetify.VIcon( v_bind=( "props", ), classes="mr-2", icon="mdi-close", color="red" ) - html.Div( v_if=( "item.invalid_properties", ), - v_text=( "'Invalid properties: ' + item.invalid_properties", ) ) - html.Div( v_if=( "item.invalid_children", ), - v_text=( "'Invalid children: ' + item.invalid_children", ) ) - - vuetify.VIcon( v_if=( "item.valid < 2", ), - classes="mr-2", - icon='mdi-check', - color=( "['gray', 'green'][item.valid]", ) ) - vuetify.VCheckboxBtn( v_if="item.is_drawable", - focused=True, - dense=True, - hide_details=True, - icon=True, - false_icon="mdi-eye-off", - true_icon="mdi-eye", - update_modelValue=( self.to_draw_change, "[ item.id, $event ] " ) ) - - def to_draw_change( self, item_id, drawn ): + self.simput_manager.proxymanager.on( _on_change ) + + with self, vuetify.Template( v_slot_append="{ item }" ): + with vuetify.VTooltip( v_if=( "item.valid == 2", ) ): + with vuetify.Template( + v_slot_activator=( "{ props }", ), + __properties__=[ ( "v_slot_activator", "v-slot:activator" ) ], + ): + vuetify.VIcon( v_bind=( "props", ), classes="mr-2", icon="mdi-close", color="red" ) + html.Div( + v_if=( "item.invalid_properties", ), + v_text=( "'Invalid properties: ' + item.invalid_properties", ), + ) + html.Div( + v_if=( "item.invalid_children", ), + v_text=( "'Invalid children: ' + item.invalid_children", ), + ) + + vuetify.VIcon( + v_if=( "item.valid < 2", ), + classes="mr-2", + icon="mdi-check", + color=( "['gray', 'green'][item.valid]", ), + ) + vuetify.VCheckboxBtn( + v_if="item.is_drawable", + focused=True, + dense=True, + hide_details=True, + icon=True, + false_icon="mdi-eye-off", + true_icon="mdi-eye", + update_modelValue=( self._to_draw_change, "[ item.id, $event ] " ), + ) + + def _to_draw_change( self, item_id: str, drawn: bool ) -> None: self.state.object_state = ( item_id, drawn ) @property - def source( self ): + def source( self ) -> dict | None: + """Getter for source.""" return self._source # TODO # v should be a proxy like the one in paraview simple # maybe it can be Any of schema_mod (e.g. Problem) - def set_source( self, v ): + def _set_source( self, v: Problem | None ) -> None: # TODO replace this snippet from xsdata.formats.dataclass.serializers.config import SerializerConfig @@ -114,17 +125,18 @@ def set_source( self, v ): encoder = DictEncoder( context=context, config=SerializerConfig( indent=" " ) ) self._source = encoder.encode( v ) + assert self._source is not None # with this one by passing v as Problem # self._source = v if v is None: self.state.deck_tree = [] else: - self.state.deck_tree = object_to_tree( self._source ).get( "children", [] ) + self.state.deck_tree = _object_to_tree( self._source ).get( "children", [] ) - for v in iterate_nested_dict( self.state.deck_tree ): + for path in iterate_nested_dict( self.state.deck_tree ): - active_block = self.tree.decode( v ) + active_block = self.tree.decode( path ) # active_name = None # if hasattr(active_block, "name"): @@ -132,13 +144,12 @@ def set_source( self, v ): simput_type = type( active_block ).__name__ - test = dump( active_block ) + test = _dump( active_block ) if test: params_dict = {} - for key, value in test.items(): + for key, _ in test.items(): params_dict[ key ] = { - # "initial": str(v), "type": "string", } @@ -147,15 +158,15 @@ def set_source( self, v ): self.simput_manager.load_model( yaml_content=yaml_str ) - debug = self.simput_manager.proxymanager.create( simput_type, proxy_id=v ) + debug = self.simput_manager.proxymanager.create( simput_type, proxy_id=path ) - for key, value in test.items(): + for key, _ in test.items(): debug.set_property( key, getattr( active_block, key ) ) debug.commit() - def change_current_id( self, item_id=None ): - """ - Change the current id of the tree. + def change_current_id( self, item_id: str | None = None ) -> None: + """Change the current id of the tree. + This function is called when the user click on the tree. """ if item_id is None: @@ -166,35 +177,37 @@ def change_current_id( self, item_id=None ): self.state.active_id = item_id -def get_node_dict( obj, node_id, path ): +def _get_node_dict( obj: dict, node_id: str, path: list ) -> TreeNode: children = [] for key, value in obj.items(): # todo look isinstance(value, dict): if isinstance( value, list ): for idx, item in enumerate( value ): if isinstance( item, dict ): - children.append( get_node_dict( item, key, path + [ key ] + [ idx ] ) ) + children.append( _get_node_dict( item, key, path + [ key ] + [ idx ] ) ) node_name = node_id if "name" in obj: node_name = obj[ "name" ] - return TreeNode( id="Problem/" + "/".join( map( str, path ) ), - title=node_name, - children=children if len( children ) else [], - hidden_children=[], - is_drawable=node_id in ( k.value for k in Renderable ), - drawn=False, - valid=FieldStatus.UNCHECKED.value ) + return TreeNode( + id="Problem/" + "/".join( map( str, path ) ), + title=node_name, + children=children if len( children ) else [], + hidden_children=[], + is_drawable=node_id in ( k.value for k in Renderable ), + drawn=False, + valid=FieldStatus.UNCHECKED.value, + ) -def object_to_tree( obj: dict ) -> dict: - return get_node_dict( obj, "Problem", [] ).json +def _object_to_tree( obj: dict ) -> dict: + return _get_node_dict( obj, "Problem", [] ).json -def dump( item ) -> dict[ str, Any ] | None: +def _dump( item: Any ) -> dict[ str, Any ] | None: if isinstance( item, BaseModel ): - subitems: dict[ str, Any ] = dict() + subitems: dict[ str, Any ] = {} for field, value in item: diff --git a/geos-trame/geos_trame/app/ui/plotting.py b/geos-trame/geos_trame/app/ui/plotting.py index ff7bcab8c..3e8fd306e 100644 --- a/geos-trame/geos_trame/app/ui/plotting.py +++ b/geos-trame/geos_trame/app/ui/plotting.py @@ -1,15 +1,21 @@ # SPDX-License-Identifier: Apache-2.0 # SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. # SPDX-FileContributor: Lionel Untereiner +from typing import Any + import matplotlib.pyplot as plt import numpy as np +from matplotlib.figure import Figure from trame.widgets import matplotlib from trame.widgets import vuetify3 as vuetify +from geos_trame.app.deck.tree import DeckTree + class DeckPlotting( vuetify.VCard ): - def __init__( self, source=None, **kwargs ): + def __init__( self, source: DeckTree, **kwargs: Any ) -> None: + """Constructor.""" super().__init__( **kwargs ) self._source = source @@ -18,8 +24,8 @@ def __init__( self, source=None, **kwargs ): self._filepath = ( source.input_file.path, ) - self.ctrl.permeability = self.permeability - self.ctrl.figure_size = self.figure_size + self.ctrl.permeability = self._permeability + self.ctrl.figure_size = self._figure_size with self: vuetify.VCardTitle( "2D View" ) @@ -29,13 +35,14 @@ def __init__( self, source=None, **kwargs ): self.ctrl.update_figure = html_viewX.update @property - def source( self ): + def source( self ) -> DeckTree: + """Getter for source.""" return self._source - def update_view( self, **kwargs ): + def _update_view( self ) -> None: self.ctrl.view_update( figure=self.ctrl.permeability( **self.ctrl.figure_size() ) ) - def figure_size( self ): + def _figure_size( self ) -> dict: if self.state.figure_size is None: return {} @@ -50,23 +57,27 @@ def figure_size( self ): "dpi": dpi, } - def inverse_gaz( self, x ): + @staticmethod + def _inverse_gaz( x: np.ndarray ) -> np.ndarray: return 1 - x - def permeability( self, **kwargs ): + def _permeability( self, **kwargs: Any ) -> Figure: # read data + assert self.source.input_file is not None for f in self.source.plots(): for t in f.table_function: if t.name == "waterRelativePermeabilityTable": fileX = t.coordinate_files.strip( "{(.+)}" ).strip() + assert fileX is not None and t.voxel_file is not None self.water_x = np.loadtxt( self.source.input_file.path + "/" + fileX ) self.water_y = np.loadtxt( self.source.input_file.path + "/" + t.voxel_file ) if t.name == "gasRelativePermeabilityTable": fileX = t.coordinate_files.strip( "{(.+)}" ).strip() + assert fileX is not None and t.voxel_file is not None gaz_x = np.loadtxt( self.source.input_file.path + "/" + fileX ) - self.gaz_x = self.inverse_gaz( gaz_x ) + self.gaz_x = self._inverse_gaz( gaz_x ) self.gaz_y = np.loadtxt( self.source.input_file.path + "/" + t.voxel_file ) # make drawing diff --git a/geos-trame/geos_trame/app/ui/timeline.py b/geos-trame/geos_trame/app/ui/timeline.py index 3d09926d3..9429cb347 100644 --- a/geos-trame/geos_trame/app/ui/timeline.py +++ b/geos-trame/geos_trame/app/ui/timeline.py @@ -1,14 +1,19 @@ # SPDX-License-Identifier: Apache-2.0 # SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. # SPDX-FileContributor: Lionel Untereiner +from typing import Any + from trame.widgets import gantt from trame.widgets import vuetify3 as vuetify from trame_simput import get_simput_manager +from geos_trame.app.deck.tree import DeckTree + class TimelineEditor( vuetify.VCard ): - def __init__( self, source=None, **kwargs ): + def __init__( self, source: DeckTree, **kwargs: Any ) -> None: + """Constructor.""" super().__init__( **kwargs ) self.tree = source @@ -54,16 +59,18 @@ def __init__( self, source=None, **kwargs ): placeholder="09/18/2024", ) vuetify.VDivider() - with vuetify.VContainer( "Events timeline" ): - with vuetify.VTimeline( + with ( + vuetify.VContainer( "Events timeline" ), + vuetify.VTimeline( direction="horizontal", truncate_line="both", align="center", side="end", - ): # , truncate_line="both", side="end", line_inset="12"): - with vuetify.VTimelineItem( v_for=( f"item in {items}", ), key="i", value="item", size="small" ): - vuetify.VAlert( "{{ item.summary }}" ) - vuetify.Template( "{{ item.start_date }}", raw_attrs=[ "v-slot:opposite" ] ) + ), + vuetify.VTimelineItem( v_for=( f"item in {items}", ), key="i", value="item", size="small" ), + ): + vuetify.VAlert( "{{ item.summary }}" ) + vuetify.Template( "{{ item.start_date }}", raw_attrs=[ "v-slot:opposite" ] ) with vuetify.VContainer( "Events chart" ): gantt.Gantt( @@ -78,5 +85,6 @@ def __init__( self, source=None, **kwargs ): classes="fill_height", ) - def update_from_js( self, *items ): + def update_from_js( self, *items: tuple ) -> None: + """Update method called from javascript.""" self.state.items = list( items ) diff --git a/geos-trame/geos_trame/app/ui/viewer/perforationViewer.py b/geos-trame/geos_trame/app/ui/viewer/perforationViewer.py index 28678c5e3..d9db51f9f 100644 --- a/geos-trame/geos_trame/app/ui/viewer/perforationViewer.py +++ b/geos-trame/geos_trame/app/ui/viewer/perforationViewer.py @@ -5,15 +5,14 @@ class PerforationViewer: - """ - Class representing how storing a GEOS Perforation. - - A perforation is represented by 2 meshes: - _perforation_mesh : which is a sphere located where the perforation is - _extracted_cell : the extracted cell at the perforation location - """ def __init__( self, mesh: pv.PolyData, center: list[ float ], radius: float, actor: pv.Actor ) -> None: + """Class representing how storing a GEOS Perforation. + + A perforation is represented by 2 meshes: + _perforation_mesh : which is a sphere located where the perforation is + _extracted_cell : the extracted cell at the perforation location + """ self.perforation_mesh: pv.PolyData = mesh self.center: list[ float ] = center self.radius: float = radius @@ -21,18 +20,22 @@ def __init__( self, mesh: pv.PolyData, center: list[ float ], radius: float, act self.extracted_cell: pv.Actor def add_extracted_cell( self, cell_actor: pv.Actor ) -> None: + """Set the extracted cell to the given actor.""" self.extracted_cell = cell_actor def update_perforation_radius( self, value: float ) -> None: + """Update the perforation radius with the given value.""" self.radius = value self.perforation_mesh = pv.Sphere( radius=self.radius, center=self.center ) self.perforation_actor.GetMapper().SetInputDataObject( self.perforation_mesh ) self.perforation_actor.GetMapper().Update() def get_perforation_size( self ) -> float: + """Get the perforation radius.""" return self.radius def reset( self ) -> None: + """Reset the mesh, actor, and extracted cell.""" self.perforation_actor = pv.Actor() self.perforation_mesh = pv.PolyData() self.extracted_cell = pv.Actor() diff --git a/geos-trame/geos_trame/app/ui/viewer/regionViewer.py b/geos-trame/geos_trame/app/ui/viewer/regionViewer.py index 4892bfd03..8929bf675 100644 --- a/geos-trame/geos_trame/app/ui/viewer/regionViewer.py +++ b/geos-trame/geos_trame/app/ui/viewer/regionViewer.py @@ -5,27 +5,30 @@ class RegionViewer: - """ - Stores all related data information to represent the whole mesh. - - This mesh is represented in GEOS with a Region. - """ def __init__( self ) -> None: + """Stores all related data information to represent the whole mesh. + + This mesh is represented in GEOS with a Region. + """ self.input = pv.UnstructuredGrid() self.clip = self.input self.reset() def __call__( self, normal: tuple[ float ], origin: tuple[ float ] ) -> None: + """Update clip.""" self.update_clip( normal, origin ) def add_mesh( self, mesh: pv.UnstructuredGrid ) -> None: + """Set the input to the given mesh.""" self.input = mesh # type: ignore self.clip = self.input.copy() # type: ignore def update_clip( self, normal: tuple[ float ], origin: tuple[ float ] ) -> None: + """Update the current clip with the given normal and origin.""" self.clip.copy_from( self.input.clip( normal=normal, origin=origin, crinkle=True ) ) # type: ignore def reset( self ) -> None: + """Reset the input mesh and clip.""" self.input = pv.UnstructuredGrid() self.clip = self.input diff --git a/geos-trame/geos_trame/app/ui/viewer/viewer.py b/geos-trame/geos_trame/app/ui/viewer/viewer.py index 9b53f9f48..16fa03df7 100644 --- a/geos-trame/geos_trame/app/ui/viewer/viewer.py +++ b/geos-trame/geos_trame/app/ui/viewer/viewer.py @@ -1,12 +1,16 @@ # SPDX-License-Identifier: Apache-2.0 # SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. # SPDX-FileContributor: Lucas Givord - Kitware +from typing import Any + import pyvista as pv +from pydantic import BaseModel from pyvista.trame.ui import plotter_ui from trame.widgets import html from trame.widgets import vuetify3 as vuetify from vtkmodules.vtkRenderingCore import vtkActor +from geos_trame.app.deck.tree import DeckTree from geos_trame.app.ui.viewer.perforationViewer import PerforationViewer from geos_trame.app.ui.viewer.regionViewer import RegionViewer from geos_trame.app.ui.viewer.wellViewer import WellViewer @@ -21,23 +25,28 @@ class DeckViewer( vuetify.VCard ): - """ - Deck representing the 3D View using PyVista. - - This view can show: - - Vtkmesh, - - Vtkwell, - - Perforation, - - InternalWell - - Everything is handle in the method 'update_viewer()' which is trigger when the - 'state.object_state' changed (see DeckTree). - - This View handle widgets, such as clip widget or slider to control Wells or - Perforation settings. - """ - def __init__( self, source, region_viewer: RegionViewer, well_viewer: WellViewer, **kwargs ): + def __init__( + self, + source: DeckTree, + region_viewer: RegionViewer, + well_viewer: WellViewer, + **kwargs: Any, + ) -> None: + """Deck representing the 3D View using PyVista. + + This view can show: + - Vtkmesh, + - Vtkwell, + - Perforation, + - InternalWell + + Everything is handle in the method 'update_viewer()' which is trigger when the + 'state.object_state' changed (see DeckTree). + + This View handle widgets, such as clip widget or slider to control Wells or + Perforation settings. + """ super().__init__( **kwargs ) self._source = source @@ -50,7 +59,7 @@ def __init__( self, source, region_viewer: RegionViewer, well_viewer: WellViewer self.region_engine = region_viewer self.well_engine = well_viewer - self._perforations: dict[ str, PerforationViewer ] = dict() + self._perforations: dict[ str, PerforationViewer ] = {} self.ctrl.update_viewer.add( self.update_viewer ) @@ -64,42 +73,44 @@ def __init__( self, source, region_viewer: RegionViewer, well_viewer: WellViewer self.ctrl.view_update = view.update @property - def plotter( self ): + def plotter( self ) -> pv.Plotter: + """Getter for plotter.""" return self._pl @property - def source( self ): + def source( self ) -> DeckTree: + """Getter for source.""" return self._source - def rendering_menu_extra_items( self ): - """ - Extend the default pyvista menu with custom button. + def rendering_menu_extra_items( self ) -> None: + """Extend the default pyvista menu with custom button. For now, adding a button to show/hide all widgets. """ self.state.change( self.CUT_PLANE )( self._on_clip_visibility_change ) vuetify.VDivider( vertical=True, classes="mr-1" ) with vuetify.VTooltip( location="bottom" ): - with vuetify.Template( v_slot_activator=( "{ props }", ) ): - with html.Div( v_bind=( "props", ) ): - vuetify.VCheckbox( - v_model=( self.CUT_PLANE, True ), - icon=True, - true_icon="mdi-eye", - false_icon="mdi-eye-off", - dense=True, - hide_details=True, - ) + with ( + vuetify.Template( v_slot_activator=( "{ props }", ) ), + html.Div( v_bind=( "props", ) ), + ): + vuetify.VCheckbox( + v_model=( self.CUT_PLANE, True ), + icon=True, + true_icon="mdi-eye", + false_icon="mdi-eye-off", + dense=True, + hide_details=True, + ) html.Span( "Show/Hide widgets" ) - def update_viewer( self, active_block, path, show_obj ) -> None: - """ - Add from path the dataset given by the user. + def update_viewer( self, active_block: BaseModel, path: str, show_obj: bool ) -> None: + """Add from path the dataset given by the user. + Supported data type is: Vtkwell, Vtkmesh, InternalWell, Perforation. object_state : array used to store path to the data and if we want to show it or not. """ - if isinstance( active_block, Vtkmesh ): self._update_vtkmesh( show_obj ) @@ -112,7 +123,7 @@ def update_viewer( self, active_block, path, show_obj ) -> None: if isinstance( active_block, Perforation ): self._update_perforation( active_block, show_obj, path ) - def _on_clip_visibility_change( self, **kwargs ): + def _on_clip_visibility_change( self, **kwargs: Any ) -> None: """Toggle cut plane visibility for all actors. Parameters @@ -133,10 +144,7 @@ def _on_clip_visibility_change( self, **kwargs ): self.plotter.render() def _setup_slider( self ) -> None: - """ - Create slider to control in the gui well parameters. - """ - + """Create slider to control in the gui well parameters.""" wells_radius = self._get_tube_size() self.plotter.add_slider_widget( self._on_change_tube_size, @@ -164,36 +172,34 @@ def _setup_slider( self ) -> None: ) def _remove_slider( self ) -> None: - """ - Create slider to control in the gui well parameters. - """ + """Create slider to control in the gui well parameters.""" self.plotter.clear_slider_widgets() - def _on_change_tube_size( self, value ) -> None: + def _on_change_tube_size( self, value: float ) -> None: self.well_engine.update( value ) def _get_tube_size( self ) -> float: return self.well_engine.get_tube_size() - def _on_change_perforation_size( self, value ) -> None: - for key, perforation in self._perforations.items(): + def _on_change_perforation_size( self, value: float ) -> None: + for _, perforation in self._perforations.items(): perforation.update_perforation_radius( value ) def _get_perforation_size( self ) -> float | None: if len( self._perforations ) <= 0: return 5.0 - for key, perforation in self._perforations.items(): + for _, perforation in self._perforations.items(): return perforation.get_perforation_size() return None def _update_internalwell( self, path: str, show: bool ) -> None: - """ - Used to control the visibility of the InternalWell. + """Used to control the visibility of the InternalWell. + This method will create the mesh if it doesn't exist. """ if not show: - self.plotter.remove_actor( self.well_engine.get_actor( path ) ) + self.plotter.remove_actor( self.well_engine.get_actor( path ) ) # type: ignore return tube_actor = self.plotter.add_mesh( self.well_engine.get_tube( self.well_engine.get_last_mesh_idx() ) ) @@ -202,12 +208,12 @@ def _update_internalwell( self, path: str, show: bool ) -> None: self.server.controller.view_update() def _update_vtkwell( self, path: str, show: bool ) -> None: - """ - Used to control the visibility of the Vtkwell. + """Used to control the visibility of the Vtkwell. + This method will create the mesh if it doesn't exist. """ if not show: - self.plotter.remove_actor( self.well_engine.get_actor( path ) ) + self.plotter.remove_actor( self.well_engine.get_actor( path ) ) # type: ignore return tube_actor = self.plotter.add_mesh( self.well_engine.get_tube( self.well_engine.get_last_mesh_idx() ) ) @@ -216,16 +222,15 @@ def _update_vtkwell( self, path: str, show: bool ) -> None: self.server.controller.view_update() def _update_vtkmesh( self, show: bool ) -> None: - """ - Used to control the visibility of the Vtkmesh. + """Used to control the visibility of the Vtkmesh. + This method will create the mesh if it doesn't exist. Additionally, a clip filter will be added. """ - if not show: self.plotter.clear_plane_widgets() - self.plotter.remove_actor( self._clip_mesh ) + self.plotter.remove_actor( self._clip_mesh ) # type: ignore return active_scalar = self.region_engine.input.active_scalars_name @@ -242,10 +247,7 @@ def _update_vtkmesh( self, show: bool ) -> None: self.server.controller.view_update() def _update_perforation( self, perforation: Perforation, show: bool, path: str ) -> None: - """ - Generate VTK dataset from a perforation. - """ - + """Generate VTK dataset from a perforation.""" if not show: if path in self._perforations: self._remove_perforation( path ) @@ -255,19 +257,14 @@ def _update_perforation( self, perforation: Perforation, show: bool, path: str ) self._add_perforation( distance_from_head, path ) def _remove_perforation( self, path: str ) -> None: - """ - Remove all actor related to the given path and clean the stored perforation - """ + """Remove all actor related to the given path and clean the stored perforation.""" saved_perforation: PerforationViewer = self._perforations[ path ] - self.plotter.remove_actor( saved_perforation.extracted_cell ) - self.plotter.remove_actor( saved_perforation.perforation_actor ) + self.plotter.remove_actor( saved_perforation.extracted_cell ) # type: ignore + self.plotter.remove_actor( saved_perforation.perforation_actor ) # type: ignore saved_perforation.reset() def _add_perforation( self, distance_from_head: float, path: str ) -> None: - """ - Generate perforation dataset based on the distance from the top of a polyline - """ - + """Generate perforation dataset based on the distance from the top of a polyline.""" polyline: pv.PolyData | None = self.well_engine.get_mesh( path ) if polyline is None: return @@ -279,7 +276,11 @@ def _add_perforation( self, distance_from_head: float, path: str ) -> None: point[ 2 ] - distance_from_head, ] - center = [ float( point[ 0 ] ), float( point[ 1 ] ), point[ 2 ] - float( distance_from_head ) ] + center = [ + float( point[ 0 ] ), + float( point[ 1 ] ), + point[ 2 ] - float( distance_from_head ), + ] sphere = pv.Sphere( radius=5, center=center ) perforation_actor = self.plotter.add_mesh( sphere ) diff --git a/geos-trame/geos_trame/app/ui/viewer/wellViewer.py b/geos-trame/geos_trame/app/ui/viewer/wellViewer.py index 0f95067f3..4196f7b87 100644 --- a/geos-trame/geos_trame/app/ui/viewer/wellViewer.py +++ b/geos-trame/geos_trame/app/ui/viewer/wellViewer.py @@ -8,8 +8,7 @@ @dataclass class Well: - """ - A Well is represented by a polyline and a tube. + """A Well is represented by a polyline and a tube. This class stores also the related actor and his given path to simplify data management. @@ -22,13 +21,12 @@ class Well: class WellViewer: - """ - WellViewer stores all Well used in the pv.Plotter(). - - A Well in GEOS could a InternalWell or a Vtkwell. - """ def __init__( self, size: float, amplification: float ) -> None: + """WellViewer stores all Well used in the pv.Plotter(). + + A Well in GEOS could a InternalWell or a Vtkwell. + """ self._wells: list[ Well ] = [] self.size: float = size @@ -36,14 +34,16 @@ def __init__( self, size: float, amplification: float ) -> None: self.STARTING_VALUE: float = 5.0 def __call__( self, value: float ) -> None: + """Call update.""" self.update( value ) - def get_last_mesh_idx( self ): + def get_last_mesh_idx( self ) -> int: + """Returns the index of the last mesh.""" return len( self._wells ) - 1 def add_mesh( self, mesh: pv.PolyData, mesh_path: str ) -> int: - """ - Store a given mesh representing a polyline. + """Store a given mesh representing a polyline. + This polyline will be used then to create a tube to represent this line. return the indexed position of the stored well. @@ -56,9 +56,7 @@ def add_mesh( self, mesh: pv.PolyData, mesh_path: str ) -> int: return len( self._wells ) - 1 def get_mesh( self, perforation_path: str ) -> pv.PolyData | None: - """ - Retrieve the polyline linked to a given perforation path. - """ + """Retrieve the polyline linked to a given perforation path.""" index = self._get_index_from_perforation( perforation_path ) if index == -1: print( "Cannot found the well to remove from path: ", perforation_path ) @@ -67,9 +65,7 @@ def get_mesh( self, perforation_path: str ) -> pv.PolyData | None: return self._wells[ index ].polyline def get_tube( self, index: int ) -> pv.PolyData | None: - """ - Retrieve the polyline linked to a given perforation path. - """ + """Retrieve the polyline linked to a given perforation path.""" if index < 0 or index > len( self._wells ): print( "Cannot get the tube at index: ", index ) return None @@ -77,17 +73,11 @@ def get_tube( self, index: int ) -> pv.PolyData | None: return self._wells[ index ].tube def get_tube_size( self ) -> float: - """ - get the size used for the tube. - """ + """Get the size used for the tube.""" return self.size def append_actor( self, perforation_path: str, tube_actor: pv.Actor ) -> None: - """ - Append a given actor, typically the Actor returned by - the pv.Plotter() when a given mes is added. - """ - + """Append a given actor, typically the Actor returned by the pv.Plotter() when a given mes is added.""" index = self._get_index_from_perforation( perforation_path ) if index == -1: print( "Cannot found the well to remove from path: ", perforation_path ) @@ -96,9 +86,7 @@ def append_actor( self, perforation_path: str, tube_actor: pv.Actor ) -> None: self._wells[ index ].actor = tube_actor def get_actor( self, perforation_path: str ) -> pv.Actor | None: - """ - Retrieve the polyline linked to a given perforation path. - """ + """Retrieve the polyline linked to a given perforation path.""" index = self._get_index_from_perforation( perforation_path ) if index == -1: print( "Cannot found the well to remove from path: ", perforation_path ) @@ -107,14 +95,13 @@ def get_actor( self, perforation_path: str ) -> pv.Actor | None: return self._wells[ index ].actor def update( self, value: float ) -> None: + """Update the radius of the tubes.""" self.size = value for idx, m in enumerate( self._wells ): self._wells[ idx ].tube.copy_from( m.polyline.tube( radius=self.size, n_sides=50 ) ) def remove( self, perforation_path: str ) -> None: - """ - Clear all data stored in this class. - """ + """Clear all data stored in this class.""" index = self._get_index_from_perforation( perforation_path ) if index == -1: print( "Cannot found the well to remove from path: ", perforation_path ) @@ -122,9 +109,7 @@ def remove( self, perforation_path: str ) -> None: self._wells.remove( self._wells[ index ] ) def _get_index_from_perforation( self, perforation_path: str ) -> int: - """ - Retrieve the well associated to a given perforation, otherwise return -1. - """ + """Retrieve the well associated to a given perforation, otherwise return -1.""" index = -1 if len( self._wells ) == 0: return index @@ -136,5 +121,6 @@ def _get_index_from_perforation( self, perforation_path: str ) -> int: return index - def get_number_of_wells( self ): + def get_number_of_wells( self ) -> int: + """Get the number of wells in the viewer.""" return len( self._wells ) diff --git a/geos-trame/geos_trame/app/utils/dict_utils.py b/geos-trame/geos_trame/app/utils/dict_utils.py index d6df582a7..46df9a050 100644 --- a/geos-trame/geos_trame/app/utils/dict_utils.py +++ b/geos-trame/geos_trame/app/utils/dict_utils.py @@ -1,27 +1,23 @@ # SPDX-License-Identifier: Apache-2.0 # SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. # SPDX-FileContributor: Kitware +from typing import Any -def iterate_nested_dict( iterable, returned="key" ): - """Returns an iterator that returns all keys or values - of a (nested) iterable. +def iterate_nested_dict( iterable: dict | list, returned: str = "key" ) -> Any: + """Returns an iterator that returns all keys or values of a (nested) iterable. Arguments: - - iterable: or - - returned: "key" or "value" + iterable: or + returned: "key" or "value" Returns: - """ - if isinstance( iterable, dict ): for key, value in iterable.items(): - if key == "id": - if not ( isinstance( value, dict ) or isinstance( value, list ) ): - yield value - # else: - # raise ValueError("'returned' keyword only accepts 'key' or 'value'.") + if key == "id" and not isinstance( value, ( dict, list ) ): + yield value for ret in iterate_nested_dict( value, returned=returned ): yield ret elif isinstance( iterable, list ): diff --git a/geos-trame/geos_trame/app/utils/file_utils.py b/geos-trame/geos_trame/app/utils/file_utils.py index 48abda1e9..9ffcc5319 100644 --- a/geos-trame/geos_trame/app/utils/file_utils.py +++ b/geos-trame/geos_trame/app/utils/file_utils.py @@ -8,7 +8,8 @@ from lxml import etree as ElementTree # type: ignore[import-untyped] -def normalize_path( x ): +def normalize_path( x: str ) -> str: + """Normalize the given path.""" tmp = os.path.expanduser( x ) tmp = os.path.abspath( tmp ) if os.path.isfile( tmp ): @@ -17,7 +18,7 @@ def normalize_path( x ): def format_attribute( attribute_indent: str, ka: str, attribute_value: str ) -> str: - """Format xml attribute strings + """Format xml attribute strings. Args: attribute_indent (str): Attribute indent string @@ -61,7 +62,7 @@ def format_xml_level( close_tag_newline: bool = False, include_namespace: bool = False, ) -> None: - """Iteratively format the xml file + """Iteratively format the xml file. Args: output (file): the output text file handle @@ -74,7 +75,6 @@ def format_xml_level( close_tag_newline (bool): option to place close tag on a separate line include_namespace (bool): option to include the xml namespace in the output """ - # Handle comments if node.tag is ElementTree.Comment: output.write( "\n%s" % ( indent * level, node.text ) ) @@ -155,7 +155,7 @@ def format_xml( close_style: bool = False, namespace: bool = False, ) -> str: - """Script to format xml files + """Script to format xml files. Args: input_str (str): Input str @@ -197,4 +197,4 @@ def format_xml( except ElementTree.ParseError as err: print( err.msg ) - raise Exception( "Failed to format xml file" ) + raise Exception( "Failed to format xml file" ) from err diff --git a/geos-trame/geos_trame/app/utils/geos_utils.py b/geos-trame/geos_trame/app/utils/geos_utils.py new file mode 100644 index 000000000..2c2af348b --- /dev/null +++ b/geos-trame/geos_trame/app/utils/geos_utils.py @@ -0,0 +1,11 @@ +def group_name_ref_array_to_list( group_name_ref_array: str ) -> list[ str ] | None: + """Convert GEOS type groupNameRef_array to a list of string. + + Example: "{ test1, test2 }" becomes ["test1", "test2"] + """ + if ( not group_name_ref_array or not group_name_ref_array.strip().startswith( '{' ) + or not group_name_ref_array.strip().endswith( '}' ) ): + return None + + stripped = group_name_ref_array.strip().strip( '{}' ) + return [ item.strip() for item in stripped.split( ',' ) if item.strip() ] diff --git a/geos-trame/geos_trame/app/utils/pv_utils.py b/geos-trame/geos_trame/app/utils/pv_utils.py index 5ef368bce..cb3af1330 100644 --- a/geos-trame/geos_trame/app/utils/pv_utils.py +++ b/geos-trame/geos_trame/app/utils/pv_utils.py @@ -4,8 +4,6 @@ import pyvista as pv -def read_unstructured_grid( filename ) -> pv.UnstructuredGrid: - """ - Read an unstructured grid from a .vtu file. - """ +def read_unstructured_grid( filename: str ) -> pv.UnstructuredGrid: + """Read an unstructured grid from a .vtu file.""" return pv.read( filename ).cast_to_unstructured_grid() diff --git a/geos-trame/geos_trame/module/__init__.py b/geos-trame/geos_trame/module/__init__.py index 5e04d0c9b..1c705fceb 100644 --- a/geos-trame/geos_trame/module/__init__.py +++ b/geos-trame/geos_trame/module/__init__.py @@ -20,6 +20,6 @@ # Optional if you want to execute custom initialization at module load -def setup( app, **kwargs ): - """Method called at initialization with possibly some custom keyword arguments""" +def setup( app, **kwargs ): # noqa + """Method called at initialization with possibly some custom keyword arguments.""" pass diff --git a/geos-trame/geos_trame/schema_generated/old_schema_mod.py b/geos-trame/geos_trame/schema_generated/old_schema_mod.py index 909a521a9..45919c59b 100644 --- a/geos-trame/geos_trame/schema_generated/old_schema_mod.py +++ b/geos-trame/geos_trame/schema_generated/old_schema_mod.py @@ -4,6 +4,8 @@ See: https://xsdata.readthedocs.io/ """ +# ruff: noqa + from __future__ import annotations from dataclasses import field diff --git a/geos-trame/geos_trame/schema_generated/schema_mod.py b/geos-trame/geos_trame/schema_generated/schema_mod.py index c88d1e912..cc62720dc 100644 --- a/geos-trame/geos_trame/schema_generated/schema_mod.py +++ b/geos-trame/geos_trame/schema_generated/schema_mod.py @@ -4,6 +4,8 @@ See: https://xsdata.readthedocs.io/ """ +# ruff: noqa + from typing import List, Optional from pydantic import BaseModel, ConfigDict diff --git a/geos-trame/tests/conftest.py b/geos-trame/tests/conftest.py index 90183df81..f38de5e35 100644 --- a/geos-trame/tests/conftest.py +++ b/geos-trame/tests/conftest.py @@ -5,6 +5,8 @@ from pathlib import Path from trame_client.utils.testing import FixtureHelper +# ruff: noqa + ROOT_PATH = Path( __file__ ).parent.parent.absolute() print( ROOT_PATH ) HELPER = FixtureHelper( ROOT_PATH ) @@ -18,7 +20,7 @@ def baseline_image(): @pytest.fixture -def server( xprocess, server_path ): +def server( xprocess, server_path: str ): name, Starter, Monitor = HELPER.get_xprocess_args( server_path ) Starter.timeout = 10 diff --git a/geos-trame/tests/test_file_handling.py b/geos-trame/tests/test_file_handling.py index 2cbf4c2cc..081af74cc 100644 --- a/geos-trame/tests/test_file_handling.py +++ b/geos-trame/tests/test_file_handling.py @@ -1,17 +1,18 @@ # SPDX-License-Identifier: Apache-2.0 # SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. # SPDX-FileContributor: Kitware +from _pytest.capture import CaptureFixture from trame.app import get_server from trame_client.utils.testing import enable_testing from geos_trame.app.core import GeosTrame -def test_unsupported_file( capsys ): - +def test_unsupported_file( capsys: CaptureFixture[ str ] ) -> None: + """Test unsupported file.""" server = enable_testing( get_server( client_type="vue3" ), "message" ) file_name = "tests/data/acous3D/acous3D_vtu.xml" GeosTrame( server, file_name ) captured = capsys.readouterr() - assert captured.err == "The file tests/data/acous3D/acous3D_vtu.xml cannot be parsed.\n" + assert ( captured.err == "The file tests/data/acous3D/acous3D_vtu.xml cannot be parsed.\n" ) diff --git a/geos-trame/tests/test_import.py b/geos-trame/tests/test_import.py index a3e3070eb..e47b88163 100644 --- a/geos-trame/tests/test_import.py +++ b/geos-trame/tests/test_import.py @@ -1,5 +1,6 @@ # SPDX-License-Identifier: Apache-2.0 # SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. # SPDX-FileContributor: Lionel Untereiner -def test_import(): +def test_import() -> None: + """Test GeosTrame import.""" from geos_trame.app.core import GeosTrame # noqa: F401 diff --git a/geos-trame/tests/test_load_and_visualize_synthetic_dataset.py b/geos-trame/tests/test_load_and_visualize_synthetic_dataset.py index 490f7bee2..261835b3c 100644 --- a/geos-trame/tests/test_load_and_visualize_synthetic_dataset.py +++ b/geos-trame/tests/test_load_and_visualize_synthetic_dataset.py @@ -11,6 +11,8 @@ from selenium.webdriver.common.action_chains import ActionChains from selenium.webdriver.common.by import By +# ruff: noqa + @pytest.mark.skip( "Test to fix" ) @pytest.mark.parametrize( "server_path", [ "tests/utils/start_geos_trame_for_testing.py" ] ) diff --git a/geos-trame/tests/test_properties_checker.py b/geos-trame/tests/test_properties_checker.py index d86e20fb5..f74cfe58f 100644 --- a/geos-trame/tests/test_properties_checker.py +++ b/geos-trame/tests/test_properties_checker.py @@ -1,14 +1,20 @@ # SPDX-License-Identifier: Apache-2.0 # SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. # SPDX-FileContributor: Kitware +# ruff: noqa from pathlib import Path +from trame_server import Server +from trame_server.state import State +from trame_vuetify.ui.vuetify3 import VAppLayout + from geos_trame.app.core import GeosTrame from geos_trame.app.data_types.field_status import FieldStatus -from tests.trame_fixtures import trame_state, trame_server_layout +from tests.trame_fixtures import trame_server_layout, trame_state -def test_properties_checker( trame_server_layout, trame_state ): +def test_properties_checker( trame_server_layout: tuple[ Server, VAppLayout ], trame_state: State ) -> None: + """Test properties checker.""" root_path = Path( __file__ ).parent.absolute().__str__() file_name = root_path + "/data/singlePhaseFlow/FieldCaseTutorial3_smoke.xml" diff --git a/geos-trame/tests/test_saving_attribute_modification.py b/geos-trame/tests/test_saving_attribute_modification.py index 7995ab6dc..d797af007 100644 --- a/geos-trame/tests/test_saving_attribute_modification.py +++ b/geos-trame/tests/test_saving_attribute_modification.py @@ -13,6 +13,7 @@ from selenium.webdriver.common.by import By +# ruff: noqa @pytest.mark.skip( "Test to fix" ) @pytest.mark.parametrize( "server_path", [ "tests/utils/start_geos_trame_for_testing.py" ] ) def test_saving_attribute_modification( server, capsys ): diff --git a/geos-trame/tests/test_saving_node_modification.py b/geos-trame/tests/test_saving_node_modification.py index 336de0884..23be3daed 100644 --- a/geos-trame/tests/test_saving_node_modification.py +++ b/geos-trame/tests/test_saving_node_modification.py @@ -13,6 +13,7 @@ from selenium.webdriver.common.by import By +# ruff: noqa @pytest.mark.skip( "Test to fix" ) @pytest.mark.parametrize( "server_path", [ "tests/utils/start_geos_trame_for_testing.py" ] ) def test_saving_node_modification( server, capsys ): diff --git a/geos-trame/tests/test_saving_subnode_modifications.py b/geos-trame/tests/test_saving_subnode_modifications.py index 6876738ae..1f5144f4d 100644 --- a/geos-trame/tests/test_saving_subnode_modifications.py +++ b/geos-trame/tests/test_saving_subnode_modifications.py @@ -13,6 +13,7 @@ from selenium.webdriver.common.by import By +# ruff: noqa @pytest.mark.skip( "Test to fix" ) @pytest.mark.parametrize( "server_path", [ "tests/utils/start_geos_trame_for_testing.py" ] ) def test_saving_subnode_modifications( server, capsys ): diff --git a/geos-trame/tests/test_well_intersection.py b/geos-trame/tests/test_well_intersection.py index 30810ec41..760409f5e 100644 --- a/geos-trame/tests/test_well_intersection.py +++ b/geos-trame/tests/test_well_intersection.py @@ -1,13 +1,19 @@ # SPDX-License-Identifier: Apache-2.0 # SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. # SPDX-FileContributor: Lucas Givord - Kitware +# ruff: noqa from pathlib import Path +from trame_server import Server +from trame_server.state import State +from trame_vuetify.ui.vuetify3 import VAppLayout +from tests.trame_fixtures import trame_server_layout, trame_state + from geos_trame.app.core import GeosTrame -from tests.trame_fixtures import trame_state, trame_server_layout -def test_internal_well_intersection( trame_server_layout, trame_state ): +def test_internal_well_intersection( trame_server_layout: tuple[ Server, VAppLayout ], trame_state: State ) -> None: + """Test internal well intersection.""" root_path = Path( __file__ ).parent.absolute().__str__() file_name = root_path + "/data/geosDeck/geosDeck.xml" @@ -38,7 +44,8 @@ def test_internal_well_intersection( trame_server_layout, trame_state ): assert len( app.deckViewer._perforations ) == 2 -def test_vtk_well_intersection( trame_server_layout, trame_state ): +def test_vtk_well_intersection( trame_server_layout: tuple[ Server, VAppLayout ], trame_state: State ) -> None: + """Test vtk well intersection.""" root_path = Path( __file__ ).parent.absolute().__str__() file_name = root_path + "/data/geosDeck/geosDeck.xml" diff --git a/geos-trame/tests/trame_fixtures.py b/geos-trame/tests/trame_fixtures.py index 6fbbbb5f5..0866dbd96 100644 --- a/geos-trame/tests/trame_fixtures.py +++ b/geos-trame/tests/trame_fixtures.py @@ -1,13 +1,17 @@ # SPDX-License-Identifier: Apache-2.0 # SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. # SPDX-FileContributor: Kitware + import pytest from trame_server import Server +from trame_server.state import State from trame_vuetify.ui.vuetify3 import VAppLayout +from typing import Generator @pytest.fixture -def trame_server_layout(): +def trame_server_layout() -> Generator[ tuple[ Server, VAppLayout ], None, None ]: + """Yield a test server and layout.""" server = Server() server.debug = True @@ -16,6 +20,7 @@ def trame_server_layout(): @pytest.fixture -def trame_state( trame_server_layout ): +def trame_state( trame_server_layout: tuple[ Server, VAppLayout ] ) -> Generator[ State, None, None ]: + """Yield a test state.""" trame_server_layout[ 0 ].state.ready() yield trame_server_layout[ 0 ].state diff --git a/geos-trame/tests/utils/testing_tools.py b/geos-trame/tests/utils/testing_tools.py index 7cef8984e..9d74e5c4f 100644 --- a/geos-trame/tests/utils/testing_tools.py +++ b/geos-trame/tests/utils/testing_tools.py @@ -1,24 +1,19 @@ # SPDX-License-Identifier: Apache-2.0 # SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. # SPDX-FileContributor: Lionel Untereiner -from PIL import Image -from PIL import ImageChops +from PIL import Image, ImageChops -def image_pixel_differences( base_image_path, compare_image_path ): - """ - Calculates the bounding box of the non-zero regions in the image. - :param base_image: target image to find - :param compare_image: set of images containing the target image +def image_pixel_differences( base_image_path: str, compare_image_path: str ) -> bool: + """Calculates the bounding box of the non-zero regions in the image. + + :param base_image_path: target image to find + :param compare_image_path: set of images containing the target image :return: True is the L1 value between each image is identitic, - False otherwise + False otherwise. """ - base_image = Image.open( base_image_path ) compare_image = Image.open( compare_image_path ) diff = ImageChops.difference( base_image, compare_image ) - if diff.getbbox(): - return False - else: - return True + return not diff.getbbox() From 643d17044cbdf875710ef1685ba182b4e973b4ea Mon Sep 17 00:00:00 2001 From: Jean Fechter Date: Wed, 4 Jun 2025 09:47:42 +0200 Subject: [PATCH 10/16] docs: remove duplicate CONTRIBUTING and update README --- geos-trame/CONTRIBUTING.rst | 19 ------------------- geos-trame/README.rst | 9 +++++++++ geos-trame/pyproject.toml | 1 - 3 files changed, 9 insertions(+), 20 deletions(-) delete mode 100644 geos-trame/CONTRIBUTING.rst diff --git a/geos-trame/CONTRIBUTING.rst b/geos-trame/CONTRIBUTING.rst deleted file mode 100644 index 6d5a9c236..000000000 --- a/geos-trame/CONTRIBUTING.rst +++ /dev/null @@ -1,19 +0,0 @@ -========================== -Contributing to geos-trame -========================== - -#. Clone the repository using ``git clone`` -#. Install pre-commit via ``pip install pre-commit`` -#. Run ``pre-commit install`` to set up pre-commit hooks -#. Make changes to the code, and commit your changes to a separate branch -#. Create a fork of the repository on GitHub -#. Push your branch to your fork, and open a pull request - -Tips -#### - -#. When first creating a new project, it is helpful to run ``pre-commit run --all-files`` to ensure all files pass the pre-commit checks. -#. A quick way to fix ``black`` issues is by installing black (``pip install black``) and running the ``black`` command at the root of your repository. -#. Sometimes, ``black`` and ``flake8`` do not agree. Add options to your ``.flake8`` file to fix these things. See the `flake8 configuration docs `_ for more details. -#. A quick way to fix ``codespell`` issues is by installing codespell (``pip install codespell``) and running the ``codespell -w`` command at the root of your directory. -#. The `.codespellrc file `_ can be used fix any other codespell issues, such as ignoring certain files, directories, words, or regular expressions. diff --git a/geos-trame/README.rst b/geos-trame/README.rst index 797f1948f..4cf5be310 100644 --- a/geos-trame/README.rst +++ b/geos-trame/README.rst @@ -47,6 +47,15 @@ To be able to run the test suite, make sure to install the additionals dependenc Then you can run the test with `pytest .` +Optional +-------- + +To use pre-commit hooks (ruff, mypy, yapf,...), make sure to install the dev dependencies: + +.. code-block:: console + + pip install -e .[dev] + Regarding GEOS -------------- diff --git a/geos-trame/pyproject.toml b/geos-trame/pyproject.toml index aef468b2b..ed816565f 100644 --- a/geos-trame/pyproject.toml +++ b/geos-trame/pyproject.toml @@ -56,7 +56,6 @@ build = [ dev = [ "pylint", "mypy", - "mypy", "ruff", "pre-commit" ] From 04aa683c5a9462616d5bc48367ae137082e732a0 Mon Sep 17 00:00:00 2001 From: Jean Fechter Date: Thu, 5 Jun 2025 15:26:38 +0200 Subject: [PATCH 11/16] feat: load data upon check fields --- .../app/components/properties_checker.py | 18 +++++++++++++----- geos-trame/geos_trame/app/io/data_loader.py | 11 +++++++++++ geos-trame/tests/test_properties_checker.py | 6 ++++++ 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/geos-trame/geos_trame/app/components/properties_checker.py b/geos-trame/geos_trame/app/components/properties_checker.py index 0c351499a..69cda6ed6 100644 --- a/geos-trame/geos_trame/app/components/properties_checker.py +++ b/geos-trame/geos_trame/app/components/properties_checker.py @@ -7,6 +7,7 @@ from trame_simput import get_simput_manager from geos_trame.app.data_types.field_status import FieldStatus +from geos_trame.app.data_types.renderable import Renderable from geos_trame.app.deck.tree import DeckTree from geos_trame.app.ui.viewer.regionViewer import RegionViewer from geos_trame.app.utils.geos_utils import group_name_ref_array_to_list @@ -32,18 +33,21 @@ def check_fields( self ) -> None: Get the names of all the cell data arrays from the input of the region viewer, then check that all the attributes in `attributes_to_check` have a value corresponding to one of the array names. """ - cellData = self.region_viewer.input.GetCellData() - arrayNames = [ cellData.GetArrayName( i ) for i in range( cellData.GetNumberOfArrays() ) ] + array_names = self._get_array_names() for field in self.state.deck_tree: - self.check_field( field, arrayNames ) + self._check_field( field, array_names ) self.state.dirty( "deck_tree" ) self.state.flush() - def check_field( self, field: dict, array_names: list[ str ] ) -> None: + def _check_field( self, field: dict, array_names: list[ str ] ) -> None: """Check that all the attributes in `attributes_to_check` have a value corresponding to one of the array names. Set the `valid` property to the result of this check, and if necessary, indicate which properties are invalid. """ + if len( array_names ) == 0 and Renderable.VTKMESH.value in field[ "id" ]: + self.ctrl.load_vtkmesh_from_id( field[ "id" ] ) + array_names = self._get_array_names() + field[ "drawn" ] = True field[ "valid" ] = FieldStatus.VALID.value field[ "invalid_properties" ] = [] @@ -74,9 +78,13 @@ def check_field( self, field: dict, array_names: list[ str ] ) -> None: # Parents are only valid if all children are valid field[ "invalid_children" ] = [] for child in field[ "children" ]: - self.check_field( child, array_names ) + self._check_field( child, array_names ) if child[ "valid" ] == FieldStatus.INVALID.value: field[ "valid" ] = FieldStatus.INVALID.value field[ "invalid_children" ].append( child[ "title" ] ) if len( field[ "invalid_children" ] ) == 0: field.pop( "invalid_children", None ) + + def _get_array_names( self ) -> list[ str ]: + cellData = self.region_viewer.input.GetCellData() + return [ cellData.GetArrayName( i ) for i in range( cellData.GetNumberOfArrays() ) ] diff --git a/geos-trame/geos_trame/app/io/data_loader.py b/geos-trame/geos_trame/app/io/data_loader.py index ff27c9fcc..71f1948db 100644 --- a/geos-trame/geos_trame/app/io/data_loader.py +++ b/geos-trame/geos_trame/app/io/data_loader.py @@ -38,6 +38,14 @@ def __init__( self.well_viewer = well_viewer self.state.change( "object_state" )( self._update_object_state ) + self.ctrl.load_vtkmesh_from_id.add( self.load_vtkmesh_from_id ) + + def load_vtkmesh_from_id( self, node_id: str ) -> None: + """Load the data at the given id if none is already loaded.""" + if self.region_viewer.input.number_of_cells == 0: + active_block = self.source.decode( node_id ) + if isinstance( active_block, Vtkmesh ): + self._read_mesh( active_block ) def _update_object_state( self, object_state: tuple[ str, bool ], **_: dict ) -> None: @@ -85,6 +93,9 @@ def _update_vtkmesh( self, mesh: Vtkmesh, show: bool ) -> None: self.region_viewer.reset() return + self._read_mesh( mesh ) + + def _read_mesh( self, mesh: Vtkmesh ) -> None: unstructured_grid = read_unstructured_grid( self.source.get_abs_path( mesh.file ) ) self.region_viewer.add_mesh( unstructured_grid ) diff --git a/geos-trame/tests/test_properties_checker.py b/geos-trame/tests/test_properties_checker.py index f74cfe58f..5c99b127b 100644 --- a/geos-trame/tests/test_properties_checker.py +++ b/geos-trame/tests/test_properties_checker.py @@ -22,5 +22,11 @@ def test_properties_checker( trame_server_layout: tuple[ Server, VAppLayout ], t field = trame_state.deck_tree[ 4 ][ "children" ][ 0 ] assert field[ "valid" ] == FieldStatus.UNCHECKED.value + + geos_trame.simput_manager.proxymanager.get( "Problem/Mesh/0/VTKMesh/0" )[ "region_attribute" ] = "invalid" geos_trame.properties_checker.check_fields() assert field[ "valid" ] == FieldStatus.INVALID.value + + geos_trame.simput_manager.proxymanager.get( "Problem/Mesh/0/VTKMesh/0" )[ "region_attribute" ] = "attribute" + geos_trame.properties_checker.check_fields() + assert field[ "valid" ] == FieldStatus.VALID.value From 9d70af231c088adf8f0911c2f0420d431bac7d12 Mon Sep 17 00:00:00 2001 From: Jean Fechter Date: Fri, 6 Jun 2025 10:00:32 +0200 Subject: [PATCH 12/16] refactor: add src folder and split geos_trame into geos/trame --- geos-trame/README.rst | 4 ++-- geos-trame/pyproject.toml | 4 ++-- .../geos/trame}/__init__.py | 0 .../geos/trame}/app/__init__.py | 0 .../geos/trame}/app/components/__init__.py | 0 .../trame}/app/components/alertHandler.py | 0 .../app/components/properties_checker.py | 10 ++++---- .../geos/trame}/app/core.py | 24 +++++++++---------- .../geos/trame}/app/data_types/__init__.py | 0 .../trame}/app/data_types/field_status.py | 0 .../geos/trame}/app/data_types/renderable.py | 0 .../geos/trame}/app/data_types/tree_node.py | 0 .../geos/trame}/app/deck/__init__.py | 0 .../geos/trame}/app/deck/file.py | 10 ++++---- .../geos/trame}/app/deck/tree.py | 8 +++---- .../geos/trame}/app/geosTrameException.py | 0 .../geos/trame}/app/io/__init__.py | 0 .../geos/trame}/app/io/data_loader.py | 12 +++++----- .../geos/trame}/app/io/xml_parser.py | 2 +- .../geos/trame/app/main.py} | 4 ++-- .../geos/trame}/app/ui/__init__.py | 0 .../geos/trame}/app/ui/editor.py | 2 +- .../geos/trame}/app/ui/inspector.py | 12 +++++----- .../geos/trame}/app/ui/plotting.py | 2 +- .../geos/trame}/app/ui/timeline.py | 2 +- .../geos/trame}/app/ui/viewer/__init__.py | 0 .../trame}/app/ui/viewer/perforationViewer.py | 0 .../geos/trame}/app/ui/viewer/regionViewer.py | 0 .../geos/trame}/app/ui/viewer/viewer.py | 10 ++++---- .../geos/trame}/app/ui/viewer/wellViewer.py | 0 .../geos/trame}/app/utils/__init__.py | 0 .../geos/trame}/app/utils/dict_utils.py | 0 .../geos/trame}/app/utils/file_utils.py | 0 .../geos/trame}/app/utils/geos_utils.py | 0 .../geos/trame}/app/utils/pv_utils.py | 0 .../geos/trame}/module/.gitignore | 0 .../geos/trame}/module/__init__.py | 0 .../geos/trame}/schema_generated/README.md | 0 .../geos/trame}/schema_generated/__init__.py | 0 .../trame}/schema_generated/old_schema_mod.py | 0 .../trame}/schema_generated/schema_mod.py | 0 geos-trame/tests/test_file_handling.py | 2 +- geos-trame/tests/test_import.py | 2 +- ...st_load_and_visualize_synthetic_dataset.py | 2 +- geos-trame/tests/test_properties_checker.py | 4 ++-- .../test_saving_attribute_modification.py | 2 +- .../tests/test_saving_node_modification.py | 2 +- .../test_saving_subnode_modifications.py | 2 +- geos-trame/tests/test_well_intersection.py | 2 +- .../utils/start_geos_trame_for_testing.py | 2 +- geos-trame/vue-components/vite.config.js | 2 +- 51 files changed, 64 insertions(+), 64 deletions(-) rename geos-trame/{geos_trame => src/geos/trame}/__init__.py (100%) rename geos-trame/{geos_trame => src/geos/trame}/app/__init__.py (100%) rename geos-trame/{geos_trame => src/geos/trame}/app/components/__init__.py (100%) rename geos-trame/{geos_trame => src/geos/trame}/app/components/alertHandler.py (100%) rename geos-trame/{geos_trame => src/geos/trame}/app/components/properties_checker.py (93%) rename geos-trame/{geos_trame => src/geos/trame}/app/core.py (92%) rename geos-trame/{geos_trame => src/geos/trame}/app/data_types/__init__.py (100%) rename geos-trame/{geos_trame => src/geos/trame}/app/data_types/field_status.py (100%) rename geos-trame/{geos_trame => src/geos/trame}/app/data_types/renderable.py (100%) rename geos-trame/{geos_trame => src/geos/trame}/app/data_types/tree_node.py (100%) rename geos-trame/{geos_trame => src/geos/trame}/app/deck/__init__.py (100%) rename geos-trame/{geos_trame => src/geos/trame}/app/deck/file.py (94%) rename geos-trame/{geos_trame => src/geos/trame}/app/deck/tree.py (98%) rename geos-trame/{geos_trame => src/geos/trame}/app/geosTrameException.py (100%) rename geos-trame/{geos_trame => src/geos/trame}/app/io/__init__.py (100%) rename geos-trame/{geos_trame => src/geos/trame}/app/io/data_loader.py (94%) rename geos-trame/{geos_trame => src/geos/trame}/app/io/xml_parser.py (99%) rename geos-trame/{geos_trame/app/__main__.py => src/geos/trame/app/main.py} (91%) rename geos-trame/{geos_trame => src/geos/trame}/app/ui/__init__.py (100%) rename geos-trame/{geos_trame => src/geos/trame}/app/ui/editor.py (98%) rename geos-trame/{geos_trame => src/geos/trame}/app/ui/inspector.py (95%) rename geos-trame/{geos_trame => src/geos/trame}/app/ui/plotting.py (98%) rename geos-trame/{geos_trame => src/geos/trame}/app/ui/timeline.py (98%) rename geos-trame/{geos_trame => src/geos/trame}/app/ui/viewer/__init__.py (100%) rename geos-trame/{geos_trame => src/geos/trame}/app/ui/viewer/perforationViewer.py (100%) rename geos-trame/{geos_trame => src/geos/trame}/app/ui/viewer/regionViewer.py (100%) rename geos-trame/{geos_trame => src/geos/trame}/app/ui/viewer/viewer.py (97%) rename geos-trame/{geos_trame => src/geos/trame}/app/ui/viewer/wellViewer.py (100%) rename geos-trame/{geos_trame => src/geos/trame}/app/utils/__init__.py (100%) rename geos-trame/{geos_trame => src/geos/trame}/app/utils/dict_utils.py (100%) rename geos-trame/{geos_trame => src/geos/trame}/app/utils/file_utils.py (100%) rename geos-trame/{geos_trame => src/geos/trame}/app/utils/geos_utils.py (100%) rename geos-trame/{geos_trame => src/geos/trame}/app/utils/pv_utils.py (100%) rename geos-trame/{geos_trame => src/geos/trame}/module/.gitignore (100%) rename geos-trame/{geos_trame => src/geos/trame}/module/__init__.py (100%) rename geos-trame/{geos_trame => src/geos/trame}/schema_generated/README.md (100%) rename geos-trame/{geos_trame => src/geos/trame}/schema_generated/__init__.py (100%) rename geos-trame/{geos_trame => src/geos/trame}/schema_generated/old_schema_mod.py (100%) rename geos-trame/{geos_trame => src/geos/trame}/schema_generated/schema_mod.py (100%) diff --git a/geos-trame/README.rst b/geos-trame/README.rst index 4cf5be310..a1c54deb2 100644 --- a/geos-trame/README.rst +++ b/geos-trame/README.rst @@ -54,7 +54,7 @@ To use pre-commit hooks (ruff, mypy, yapf,...), make sure to install the dev dep .. code-block:: console - pip install -e .[dev] + pip install -e '.[dev]' Regarding GEOS -------------- @@ -63,7 +63,7 @@ This application takes an XML file from the GEOS project to load dynamically all To be able to do that, we need first to generate the corresponding python class based on a xsd schema provided by GEOS. -`For more details `_ +`For more details `_ Features -------- diff --git a/geos-trame/pyproject.toml b/geos-trame/pyproject.toml index ed816565f..9e504acdd 100644 --- a/geos-trame/pyproject.toml +++ b/geos-trame/pyproject.toml @@ -73,10 +73,10 @@ file = "README.md" content-type = "text/markdown" [project.scripts] -geos-trame = "geos_trame.app.__main__:main" +geos-trame = "geos.trame.app.main:main" [project.entry-points.jupyter_serverproxy_servers] -geos-trame = "geos_trame.app.jupyter:jupyter_proxy_info" +geos-trame = "geos.trame.app.jupyter:jupyter_proxy_info" [tool.setuptools] license-files = ["LICENSE"] diff --git a/geos-trame/geos_trame/__init__.py b/geos-trame/src/geos/trame/__init__.py similarity index 100% rename from geos-trame/geos_trame/__init__.py rename to geos-trame/src/geos/trame/__init__.py diff --git a/geos-trame/geos_trame/app/__init__.py b/geos-trame/src/geos/trame/app/__init__.py similarity index 100% rename from geos-trame/geos_trame/app/__init__.py rename to geos-trame/src/geos/trame/app/__init__.py diff --git a/geos-trame/geos_trame/app/components/__init__.py b/geos-trame/src/geos/trame/app/components/__init__.py similarity index 100% rename from geos-trame/geos_trame/app/components/__init__.py rename to geos-trame/src/geos/trame/app/components/__init__.py diff --git a/geos-trame/geos_trame/app/components/alertHandler.py b/geos-trame/src/geos/trame/app/components/alertHandler.py similarity index 100% rename from geos-trame/geos_trame/app/components/alertHandler.py rename to geos-trame/src/geos/trame/app/components/alertHandler.py diff --git a/geos-trame/geos_trame/app/components/properties_checker.py b/geos-trame/src/geos/trame/app/components/properties_checker.py similarity index 93% rename from geos-trame/geos_trame/app/components/properties_checker.py rename to geos-trame/src/geos/trame/app/components/properties_checker.py index 69cda6ed6..10157eaf2 100644 --- a/geos-trame/geos_trame/app/components/properties_checker.py +++ b/geos-trame/src/geos/trame/app/components/properties_checker.py @@ -6,11 +6,11 @@ from trame_client.widgets.core import AbstractElement from trame_simput import get_simput_manager -from geos_trame.app.data_types.field_status import FieldStatus -from geos_trame.app.data_types.renderable import Renderable -from geos_trame.app.deck.tree import DeckTree -from geos_trame.app.ui.viewer.regionViewer import RegionViewer -from geos_trame.app.utils.geos_utils import group_name_ref_array_to_list +from geos.trame.app.data_types.field_status import FieldStatus +from geos.trame.app.data_types.renderable import Renderable +from geos.trame.app.deck.tree import DeckTree +from geos.trame.app.ui.viewer.regionViewer import RegionViewer +from geos.trame.app.utils.geos_utils import group_name_ref_array_to_list # Doc reference: https://geosx-geosx.readthedocs-hosted.com/en/latest/docs/sphinx/datastructure/CompleteXMLSchema.html attributes_to_check = [ ( "region_attribute", str ), ( "fields_to_import", list ), ( "surfacicFieldsToImport", list ) ] diff --git a/geos-trame/geos_trame/app/core.py b/geos-trame/src/geos/trame/app/core.py similarity index 92% rename from geos-trame/geos_trame/app/core.py rename to geos-trame/src/geos/trame/app/core.py index 80231ed2a..1f66b96ab 100644 --- a/geos-trame/geos_trame/app/core.py +++ b/geos-trame/src/geos/trame/app/core.py @@ -11,18 +11,18 @@ from trame_server.state import State from trame_simput import get_simput_manager -from geos_trame import module -from geos_trame.app.deck.tree import DeckTree -from geos_trame.app.io.data_loader import DataLoader -from geos_trame.app.ui.viewer.regionViewer import RegionViewer -from geos_trame.app.ui.viewer.wellViewer import WellViewer -from geos_trame.app.components.properties_checker import PropertiesChecker -from geos_trame.app.ui.editor import DeckEditor -from geos_trame.app.ui.inspector import DeckInspector -from geos_trame.app.ui.plotting import DeckPlotting -from geos_trame.app.ui.timeline import TimelineEditor -from geos_trame.app.ui.viewer.viewer import DeckViewer -from geos_trame.app.components.alertHandler import AlertHandler +from geos.trame import module +from geos.trame.app.deck.tree import DeckTree +from geos.trame.app.io.data_loader import DataLoader +from geos.trame.app.ui.viewer.regionViewer import RegionViewer +from geos.trame.app.ui.viewer.wellViewer import WellViewer +from geos.trame.app.components.properties_checker import PropertiesChecker +from geos.trame.app.ui.editor import DeckEditor +from geos.trame.app.ui.inspector import DeckInspector +from geos.trame.app.ui.plotting import DeckPlotting +from geos.trame.app.ui.timeline import TimelineEditor +from geos.trame.app.ui.viewer.viewer import DeckViewer +from geos.trame.app.components.alertHandler import AlertHandler import sys diff --git a/geos-trame/geos_trame/app/data_types/__init__.py b/geos-trame/src/geos/trame/app/data_types/__init__.py similarity index 100% rename from geos-trame/geos_trame/app/data_types/__init__.py rename to geos-trame/src/geos/trame/app/data_types/__init__.py diff --git a/geos-trame/geos_trame/app/data_types/field_status.py b/geos-trame/src/geos/trame/app/data_types/field_status.py similarity index 100% rename from geos-trame/geos_trame/app/data_types/field_status.py rename to geos-trame/src/geos/trame/app/data_types/field_status.py diff --git a/geos-trame/geos_trame/app/data_types/renderable.py b/geos-trame/src/geos/trame/app/data_types/renderable.py similarity index 100% rename from geos-trame/geos_trame/app/data_types/renderable.py rename to geos-trame/src/geos/trame/app/data_types/renderable.py diff --git a/geos-trame/geos_trame/app/data_types/tree_node.py b/geos-trame/src/geos/trame/app/data_types/tree_node.py similarity index 100% rename from geos-trame/geos_trame/app/data_types/tree_node.py rename to geos-trame/src/geos/trame/app/data_types/tree_node.py diff --git a/geos-trame/geos_trame/app/deck/__init__.py b/geos-trame/src/geos/trame/app/deck/__init__.py similarity index 100% rename from geos-trame/geos_trame/app/deck/__init__.py rename to geos-trame/src/geos/trame/app/deck/__init__.py diff --git a/geos-trame/geos_trame/app/deck/file.py b/geos-trame/src/geos/trame/app/deck/file.py similarity index 94% rename from geos-trame/geos_trame/app/deck/file.py rename to geos-trame/src/geos/trame/app/deck/file.py index 2755761f9..1c3bf6e5b 100644 --- a/geos-trame/geos_trame/app/deck/file.py +++ b/geos-trame/src/geos/trame/app/deck/file.py @@ -10,11 +10,11 @@ from xsdata.utils import text from xsdata_pydantic.bindings import DictEncoder, XmlContext, XmlParser, XmlSerializer -from geos_trame.app.data_types.renderable import Renderable -from geos_trame.app.geosTrameException import GeosTrameException -from geos_trame.app.io.xml_parser import XMLParser -from geos_trame.app.utils.file_utils import normalize_path -from geos_trame.schema_generated.schema_mod import Problem +from geos.trame.app.data_types.renderable import Renderable +from geos.trame.app.geosTrameException import GeosTrameException +from geos.trame.app.io.xml_parser import XMLParser +from geos.trame.app.utils.file_utils import normalize_path +from geos.trame.schema_generated.schema_mod import Problem class DeckFile( object ): diff --git a/geos-trame/geos_trame/app/deck/tree.py b/geos-trame/src/geos/trame/app/deck/tree.py similarity index 98% rename from geos-trame/geos_trame/app/deck/tree.py rename to geos-trame/src/geos/trame/app/deck/tree.py index cadf2bf78..4979f3bec 100644 --- a/geos-trame/geos_trame/app/deck/tree.py +++ b/geos-trame/src/geos/trame/app/deck/tree.py @@ -14,10 +14,10 @@ from xsdata.utils import text from xsdata_pydantic.bindings import DictDecoder, XmlContext, XmlSerializer -from geos_trame.app.deck.file import DeckFile -from geos_trame.app.geosTrameException import GeosTrameException -from geos_trame.app.utils.file_utils import normalize_path, format_xml -from geos_trame.schema_generated.schema_mod import Problem, Included, File, Functions +from geos.trame.app.deck.file import DeckFile +from geos.trame.app.geosTrameException import GeosTrameException +from geos.trame.app.utils.file_utils import normalize_path, format_xml +from geos.trame.schema_generated.schema_mod import Problem, Included, File, Functions class DeckTree( object ): diff --git a/geos-trame/geos_trame/app/geosTrameException.py b/geos-trame/src/geos/trame/app/geosTrameException.py similarity index 100% rename from geos-trame/geos_trame/app/geosTrameException.py rename to geos-trame/src/geos/trame/app/geosTrameException.py diff --git a/geos-trame/geos_trame/app/io/__init__.py b/geos-trame/src/geos/trame/app/io/__init__.py similarity index 100% rename from geos-trame/geos_trame/app/io/__init__.py rename to geos-trame/src/geos/trame/app/io/__init__.py diff --git a/geos-trame/geos_trame/app/io/data_loader.py b/geos-trame/src/geos/trame/app/io/data_loader.py similarity index 94% rename from geos-trame/geos_trame/app/io/data_loader.py rename to geos-trame/src/geos/trame/app/io/data_loader.py index 71f1948db..d5a7bd2f1 100644 --- a/geos-trame/geos_trame/app/io/data_loader.py +++ b/geos-trame/src/geos/trame/app/io/data_loader.py @@ -7,12 +7,12 @@ from trame_client.widgets.core import AbstractElement import pyvista as pv -from geos_trame.app.deck.tree import DeckTree -from geos_trame.app.geosTrameException import GeosTrameException -from geos_trame.app.ui.viewer.regionViewer import RegionViewer -from geos_trame.app.ui.viewer.wellViewer import WellViewer -from geos_trame.app.utils.pv_utils import read_unstructured_grid -from geos_trame.schema_generated.schema_mod import ( +from geos.trame.app.deck.tree import DeckTree +from geos.trame.app.geosTrameException import GeosTrameException +from geos.trame.app.ui.viewer.regionViewer import RegionViewer +from geos.trame.app.ui.viewer.wellViewer import WellViewer +from geos.trame.app.utils.pv_utils import read_unstructured_grid +from geos.trame.schema_generated.schema_mod import ( Vtkmesh, Vtkwell, Perforation, diff --git a/geos-trame/geos_trame/app/io/xml_parser.py b/geos-trame/src/geos/trame/app/io/xml_parser.py similarity index 99% rename from geos-trame/geos_trame/app/io/xml_parser.py rename to geos-trame/src/geos/trame/app/io/xml_parser.py index 7e2edc0e9..1bf935128 100644 --- a/geos-trame/geos_trame/app/io/xml_parser.py +++ b/geos-trame/src/geos/trame/app/io/xml_parser.py @@ -11,7 +11,7 @@ from collections import defaultdict -from geos_trame.app.geosTrameException import GeosTrameException +from geos.trame.app.geosTrameException import GeosTrameException class XMLParser( object ): diff --git a/geos-trame/geos_trame/app/__main__.py b/geos-trame/src/geos/trame/app/main.py similarity index 91% rename from geos-trame/geos_trame/app/__main__.py rename to geos-trame/src/geos/trame/app/main.py index 12c2201b2..2ad3b293a 100644 --- a/geos-trame/geos_trame/app/__main__.py +++ b/geos-trame/src/geos/trame/app/main.py @@ -4,10 +4,10 @@ from pathlib import Path from typing import Any -from trame.app import get_server +from trame.app import get_server # type: ignore from trame_server import Server -from geos_trame.app.core import GeosTrame +from geos.trame.app.core import GeosTrame def main( server: Server = None, **kwargs: Any ) -> None: diff --git a/geos-trame/geos_trame/app/ui/__init__.py b/geos-trame/src/geos/trame/app/ui/__init__.py similarity index 100% rename from geos-trame/geos_trame/app/ui/__init__.py rename to geos-trame/src/geos/trame/app/ui/__init__.py diff --git a/geos-trame/geos_trame/app/ui/editor.py b/geos-trame/src/geos/trame/app/ui/editor.py similarity index 98% rename from geos-trame/geos_trame/app/ui/editor.py rename to geos-trame/src/geos/trame/app/ui/editor.py index 1757c6d36..dc5b4f5b2 100644 --- a/geos-trame/geos_trame/app/ui/editor.py +++ b/geos-trame/src/geos/trame/app/ui/editor.py @@ -7,7 +7,7 @@ from trame.widgets import vuetify3 as vuetify from trame_simput import get_simput_manager -from geos_trame.app.deck.tree import DeckTree +from geos.trame.app.deck.tree import DeckTree class DeckEditor( vuetify.VCard ): diff --git a/geos-trame/geos_trame/app/ui/inspector.py b/geos-trame/src/geos/trame/app/ui/inspector.py similarity index 95% rename from geos-trame/geos_trame/app/ui/inspector.py rename to geos-trame/src/geos/trame/app/ui/inspector.py index e7897e80a..2fad1245e 100644 --- a/geos-trame/geos_trame/app/ui/inspector.py +++ b/geos-trame/src/geos/trame/app/ui/inspector.py @@ -8,12 +8,12 @@ from trame.widgets import vuetify3 as vuetify, html from trame_simput import get_simput_manager -from geos_trame.app.data_types.field_status import FieldStatus -from geos_trame.app.data_types.renderable import Renderable -from geos_trame.app.data_types.tree_node import TreeNode -from geos_trame.app.deck.tree import DeckTree -from geos_trame.app.utils.dict_utils import iterate_nested_dict -from geos_trame.schema_generated.schema_mod import Problem +from geos.trame.app.data_types.field_status import FieldStatus +from geos.trame.app.data_types.renderable import Renderable +from geos.trame.app.data_types.tree_node import TreeNode +from geos.trame.app.deck.tree import DeckTree +from geos.trame.app.utils.dict_utils import iterate_nested_dict +from geos.trame.schema_generated.schema_mod import Problem vuetify.enable_lab() diff --git a/geos-trame/geos_trame/app/ui/plotting.py b/geos-trame/src/geos/trame/app/ui/plotting.py similarity index 98% rename from geos-trame/geos_trame/app/ui/plotting.py rename to geos-trame/src/geos/trame/app/ui/plotting.py index 3e8fd306e..8f166a043 100644 --- a/geos-trame/geos_trame/app/ui/plotting.py +++ b/geos-trame/src/geos/trame/app/ui/plotting.py @@ -9,7 +9,7 @@ from trame.widgets import matplotlib from trame.widgets import vuetify3 as vuetify -from geos_trame.app.deck.tree import DeckTree +from geos.trame.app.deck.tree import DeckTree class DeckPlotting( vuetify.VCard ): diff --git a/geos-trame/geos_trame/app/ui/timeline.py b/geos-trame/src/geos/trame/app/ui/timeline.py similarity index 98% rename from geos-trame/geos_trame/app/ui/timeline.py rename to geos-trame/src/geos/trame/app/ui/timeline.py index 9429cb347..d6961c0ed 100644 --- a/geos-trame/geos_trame/app/ui/timeline.py +++ b/geos-trame/src/geos/trame/app/ui/timeline.py @@ -7,7 +7,7 @@ from trame.widgets import vuetify3 as vuetify from trame_simput import get_simput_manager -from geos_trame.app.deck.tree import DeckTree +from geos.trame.app.deck.tree import DeckTree class TimelineEditor( vuetify.VCard ): diff --git a/geos-trame/geos_trame/app/ui/viewer/__init__.py b/geos-trame/src/geos/trame/app/ui/viewer/__init__.py similarity index 100% rename from geos-trame/geos_trame/app/ui/viewer/__init__.py rename to geos-trame/src/geos/trame/app/ui/viewer/__init__.py diff --git a/geos-trame/geos_trame/app/ui/viewer/perforationViewer.py b/geos-trame/src/geos/trame/app/ui/viewer/perforationViewer.py similarity index 100% rename from geos-trame/geos_trame/app/ui/viewer/perforationViewer.py rename to geos-trame/src/geos/trame/app/ui/viewer/perforationViewer.py diff --git a/geos-trame/geos_trame/app/ui/viewer/regionViewer.py b/geos-trame/src/geos/trame/app/ui/viewer/regionViewer.py similarity index 100% rename from geos-trame/geos_trame/app/ui/viewer/regionViewer.py rename to geos-trame/src/geos/trame/app/ui/viewer/regionViewer.py diff --git a/geos-trame/geos_trame/app/ui/viewer/viewer.py b/geos-trame/src/geos/trame/app/ui/viewer/viewer.py similarity index 97% rename from geos-trame/geos_trame/app/ui/viewer/viewer.py rename to geos-trame/src/geos/trame/app/ui/viewer/viewer.py index 16fa03df7..4b8495c03 100644 --- a/geos-trame/geos_trame/app/ui/viewer/viewer.py +++ b/geos-trame/src/geos/trame/app/ui/viewer/viewer.py @@ -10,11 +10,11 @@ from trame.widgets import vuetify3 as vuetify from vtkmodules.vtkRenderingCore import vtkActor -from geos_trame.app.deck.tree import DeckTree -from geos_trame.app.ui.viewer.perforationViewer import PerforationViewer -from geos_trame.app.ui.viewer.regionViewer import RegionViewer -from geos_trame.app.ui.viewer.wellViewer import WellViewer -from geos_trame.schema_generated.schema_mod import ( +from geos.trame.app.deck.tree import DeckTree +from geos.trame.app.ui.viewer.perforationViewer import PerforationViewer +from geos.trame.app.ui.viewer.regionViewer import RegionViewer +from geos.trame.app.ui.viewer.wellViewer import WellViewer +from geos.trame.schema_generated.schema_mod import ( Vtkmesh, Vtkwell, Perforation, diff --git a/geos-trame/geos_trame/app/ui/viewer/wellViewer.py b/geos-trame/src/geos/trame/app/ui/viewer/wellViewer.py similarity index 100% rename from geos-trame/geos_trame/app/ui/viewer/wellViewer.py rename to geos-trame/src/geos/trame/app/ui/viewer/wellViewer.py diff --git a/geos-trame/geos_trame/app/utils/__init__.py b/geos-trame/src/geos/trame/app/utils/__init__.py similarity index 100% rename from geos-trame/geos_trame/app/utils/__init__.py rename to geos-trame/src/geos/trame/app/utils/__init__.py diff --git a/geos-trame/geos_trame/app/utils/dict_utils.py b/geos-trame/src/geos/trame/app/utils/dict_utils.py similarity index 100% rename from geos-trame/geos_trame/app/utils/dict_utils.py rename to geos-trame/src/geos/trame/app/utils/dict_utils.py diff --git a/geos-trame/geos_trame/app/utils/file_utils.py b/geos-trame/src/geos/trame/app/utils/file_utils.py similarity index 100% rename from geos-trame/geos_trame/app/utils/file_utils.py rename to geos-trame/src/geos/trame/app/utils/file_utils.py diff --git a/geos-trame/geos_trame/app/utils/geos_utils.py b/geos-trame/src/geos/trame/app/utils/geos_utils.py similarity index 100% rename from geos-trame/geos_trame/app/utils/geos_utils.py rename to geos-trame/src/geos/trame/app/utils/geos_utils.py diff --git a/geos-trame/geos_trame/app/utils/pv_utils.py b/geos-trame/src/geos/trame/app/utils/pv_utils.py similarity index 100% rename from geos-trame/geos_trame/app/utils/pv_utils.py rename to geos-trame/src/geos/trame/app/utils/pv_utils.py diff --git a/geos-trame/geos_trame/module/.gitignore b/geos-trame/src/geos/trame/module/.gitignore similarity index 100% rename from geos-trame/geos_trame/module/.gitignore rename to geos-trame/src/geos/trame/module/.gitignore diff --git a/geos-trame/geos_trame/module/__init__.py b/geos-trame/src/geos/trame/module/__init__.py similarity index 100% rename from geos-trame/geos_trame/module/__init__.py rename to geos-trame/src/geos/trame/module/__init__.py diff --git a/geos-trame/geos_trame/schema_generated/README.md b/geos-trame/src/geos/trame/schema_generated/README.md similarity index 100% rename from geos-trame/geos_trame/schema_generated/README.md rename to geos-trame/src/geos/trame/schema_generated/README.md diff --git a/geos-trame/geos_trame/schema_generated/__init__.py b/geos-trame/src/geos/trame/schema_generated/__init__.py similarity index 100% rename from geos-trame/geos_trame/schema_generated/__init__.py rename to geos-trame/src/geos/trame/schema_generated/__init__.py diff --git a/geos-trame/geos_trame/schema_generated/old_schema_mod.py b/geos-trame/src/geos/trame/schema_generated/old_schema_mod.py similarity index 100% rename from geos-trame/geos_trame/schema_generated/old_schema_mod.py rename to geos-trame/src/geos/trame/schema_generated/old_schema_mod.py diff --git a/geos-trame/geos_trame/schema_generated/schema_mod.py b/geos-trame/src/geos/trame/schema_generated/schema_mod.py similarity index 100% rename from geos-trame/geos_trame/schema_generated/schema_mod.py rename to geos-trame/src/geos/trame/schema_generated/schema_mod.py diff --git a/geos-trame/tests/test_file_handling.py b/geos-trame/tests/test_file_handling.py index 081af74cc..98a4bb6e5 100644 --- a/geos-trame/tests/test_file_handling.py +++ b/geos-trame/tests/test_file_handling.py @@ -4,7 +4,7 @@ from _pytest.capture import CaptureFixture from trame.app import get_server from trame_client.utils.testing import enable_testing -from geos_trame.app.core import GeosTrame +from geos.trame.app.core import GeosTrame def test_unsupported_file( capsys: CaptureFixture[ str ] ) -> None: diff --git a/geos-trame/tests/test_import.py b/geos-trame/tests/test_import.py index e47b88163..2f8e4a491 100644 --- a/geos-trame/tests/test_import.py +++ b/geos-trame/tests/test_import.py @@ -3,4 +3,4 @@ # SPDX-FileContributor: Lionel Untereiner def test_import() -> None: """Test GeosTrame import.""" - from geos_trame.app.core import GeosTrame # noqa: F401 + from geos.trame.app.core import GeosTrame # noqa: F401 diff --git a/geos-trame/tests/test_load_and_visualize_synthetic_dataset.py b/geos-trame/tests/test_load_and_visualize_synthetic_dataset.py index 261835b3c..ace0e20ff 100644 --- a/geos-trame/tests/test_load_and_visualize_synthetic_dataset.py +++ b/geos-trame/tests/test_load_and_visualize_synthetic_dataset.py @@ -5,7 +5,7 @@ import os from trame.app import get_server -from geos_trame.app.core import GeosTrame +from geos.trame.app.core import GeosTrame from seleniumbase import SB from selenium.webdriver.common.action_chains import ActionChains diff --git a/geos-trame/tests/test_properties_checker.py b/geos-trame/tests/test_properties_checker.py index 5c99b127b..b8bc2ac12 100644 --- a/geos-trame/tests/test_properties_checker.py +++ b/geos-trame/tests/test_properties_checker.py @@ -8,8 +8,8 @@ from trame_server.state import State from trame_vuetify.ui.vuetify3 import VAppLayout -from geos_trame.app.core import GeosTrame -from geos_trame.app.data_types.field_status import FieldStatus +from geos.trame.app.core import GeosTrame +from geos.trame.app.data_types.field_status import FieldStatus from tests.trame_fixtures import trame_server_layout, trame_state diff --git a/geos-trame/tests/test_saving_attribute_modification.py b/geos-trame/tests/test_saving_attribute_modification.py index d797af007..cd744c2d9 100644 --- a/geos-trame/tests/test_saving_attribute_modification.py +++ b/geos-trame/tests/test_saving_attribute_modification.py @@ -5,7 +5,7 @@ import pytest from trame.app import get_server -from geos_trame.app.core import GeosTrame +from geos.trame.app.core import GeosTrame from trame_client.utils.testing import enable_testing from seleniumbase import SB diff --git a/geos-trame/tests/test_saving_node_modification.py b/geos-trame/tests/test_saving_node_modification.py index 23be3daed..b95325778 100644 --- a/geos-trame/tests/test_saving_node_modification.py +++ b/geos-trame/tests/test_saving_node_modification.py @@ -5,7 +5,7 @@ import pytest from trame.app import get_server -from geos_trame.app.core import GeosTrame +from geos.trame.app.core import GeosTrame from trame_client.utils.testing import enable_testing from seleniumbase import SB diff --git a/geos-trame/tests/test_saving_subnode_modifications.py b/geos-trame/tests/test_saving_subnode_modifications.py index 1f5144f4d..5db64cd29 100644 --- a/geos-trame/tests/test_saving_subnode_modifications.py +++ b/geos-trame/tests/test_saving_subnode_modifications.py @@ -5,7 +5,7 @@ import pytest from trame.app import get_server -from geos_trame.app.core import GeosTrame +from geos.trame.app.core import GeosTrame from trame_client.utils.testing import enable_testing from seleniumbase import SB diff --git a/geos-trame/tests/test_well_intersection.py b/geos-trame/tests/test_well_intersection.py index 760409f5e..f6e2e0fde 100644 --- a/geos-trame/tests/test_well_intersection.py +++ b/geos-trame/tests/test_well_intersection.py @@ -9,7 +9,7 @@ from trame_vuetify.ui.vuetify3 import VAppLayout from tests.trame_fixtures import trame_server_layout, trame_state -from geos_trame.app.core import GeosTrame +from geos.trame.app.core import GeosTrame def test_internal_well_intersection( trame_server_layout: tuple[ Server, VAppLayout ], trame_state: State ) -> None: diff --git a/geos-trame/tests/utils/start_geos_trame_for_testing.py b/geos-trame/tests/utils/start_geos_trame_for_testing.py index 7de843861..73c6f8ba9 100644 --- a/geos-trame/tests/utils/start_geos_trame_for_testing.py +++ b/geos-trame/tests/utils/start_geos_trame_for_testing.py @@ -2,7 +2,7 @@ # SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. # SPDX-FileContributor: Lionel Untereiner from trame_client.utils.testing import enable_testing -from geos_trame.app.core import GeosTrame +from geos.trame.app.core import GeosTrame from trame.app import get_server from pathlib import Path diff --git a/geos-trame/vue-components/vite.config.js b/geos-trame/vue-components/vite.config.js index e66ebfe09..776e17b00 100644 --- a/geos-trame/vue-components/vite.config.js +++ b/geos-trame/vue-components/vite.config.js @@ -15,7 +15,7 @@ export default { }, }, }, - outDir: "../src/geos_trame/module/serve", + outDir: "../src/geos/trame/module/serve", assetsDir: ".", }, }; From 93daf8d6d532216538174c12e1ce52775cbaf108 Mon Sep 17 00:00:00 2001 From: Jean Fechter Date: Fri, 6 Jun 2025 12:13:43 +0200 Subject: [PATCH 13/16] chore: update path --- .github/workflows/typing-check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/typing-check.yml b/.github/workflows/typing-check.yml index 495619a52..6d73302a1 100644 --- a/.github/workflows/typing-check.yml +++ b/.github/workflows/typing-check.yml @@ -35,7 +35,7 @@ jobs: - name: Typing check with mypy # working-directory: ./${{ matrix.package-name }} run: | - python -m mypy geos-trame/geos_trame/app/__main__.py --install-types --non-interactive + python -m mypy geos-trame/src/geos/trame/app/main.py --install-types --non-interactive python -m mypy --config-file ./.mypy.ini --check-untyped-defs ./${{ matrix.package-name }} - name: Format and linting check with ruff From d2861ce2b7268095213164c9de298c507424df52 Mon Sep 17 00:00:00 2001 From: alexbenedicto Date: Fri, 6 Jun 2025 10:52:28 -0700 Subject: [PATCH 14/16] Add type hints for YAML --- geos-trame/pyproject.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/geos-trame/pyproject.toml b/geos-trame/pyproject.toml index 9e504acdd..b0b9ffd0a 100644 --- a/geos-trame/pyproject.toml +++ b/geos-trame/pyproject.toml @@ -47,6 +47,7 @@ dependencies = [ "funcy==2.0", "typing_inspect==0.9.0", "typing_extensions>=4.12", + "PyYAML==6.0.1", ] [project.optional-dependencies] @@ -56,6 +57,7 @@ build = [ dev = [ "pylint", "mypy", + "types-PyYAML", "ruff", "pre-commit" ] From bfa7e2b67b8a67e426b36a9547c5c63767ecc913 Mon Sep 17 00:00:00 2001 From: alexbenedicto Date: Fri, 6 Jun 2025 11:44:32 -0700 Subject: [PATCH 15/16] Update typing-check config to handle PyYAML types when performing mypy checking --- .github/workflows/typing-check.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/typing-check.yml b/.github/workflows/typing-check.yml index 6d73302a1..4b53634b4 100644 --- a/.github/workflows/typing-check.yml +++ b/.github/workflows/typing-check.yml @@ -30,12 +30,11 @@ jobs: # working-directory: ./${{ matrix.package-name }} run: | python -m pip install --upgrade pip - python -m pip install mypy ruff + python -m pip install mypy ruff types-PyYAML - name: Typing check with mypy # working-directory: ./${{ matrix.package-name }} run: | - python -m mypy geos-trame/src/geos/trame/app/main.py --install-types --non-interactive python -m mypy --config-file ./.mypy.ini --check-untyped-defs ./${{ matrix.package-name }} - name: Format and linting check with ruff From 314d09bbda2f84faf6df74f01ac0328c2d2e4566 Mon Sep 17 00:00:00 2001 From: alexbenedicto Date: Fri, 6 Jun 2025 13:19:29 -0700 Subject: [PATCH 16/16] Remove package version of PyYAML to allow pip to attempt to solve the dependency conflict --- geos-trame/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/geos-trame/pyproject.toml b/geos-trame/pyproject.toml index b0b9ffd0a..44b5cec6b 100644 --- a/geos-trame/pyproject.toml +++ b/geos-trame/pyproject.toml @@ -47,7 +47,7 @@ dependencies = [ "funcy==2.0", "typing_inspect==0.9.0", "typing_extensions>=4.12", - "PyYAML==6.0.1", + "PyYAML", ] [project.optional-dependencies]