Skip to content

feat: visualize well intersection in geos-trame #83

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
May 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions geos-trame/src/geos_trame/app/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,7 @@ def main( server=None, **kwargs ):

app = GeosTrame( server, file_name )
app.server.start( **kwargs )


if __name__ == "__main__":
main()
12 changes: 9 additions & 3 deletions geos-trame/src/geos_trame/app/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
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 import DeckViewer
from geos_trame.app.ui.viewer.viewer import DeckViewer
from geos_trame.app.ui.alertHandler import AlertHandler

import sys

Expand Down Expand Up @@ -114,6 +115,8 @@ def build_ui( self, *args, **kwargs ):
with VAppLayout( self.server ) as layout:
self.simput_widget.register_layout( layout )

self.alertHandler = AlertHandler()

def on_tab_change( tab_idx ):
pass

Expand Down Expand Up @@ -174,8 +177,11 @@ def on_tab_change( tab_idx ):
if self.tree.input_file is not None:
self.deck_ui()
else:

self.ctrl.on_add_error(
"Error",
"The file " + self.state.input_file + " cannot be parsed.",
)
print(
"Cannot build ui as the input file cannot be parse.",
"The file " + self.state.input_file + " cannot be parsed.",
file=sys.stderr,
)
1 change: 1 addition & 0 deletions geos-trame/src/geos_trame/app/deck/file.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ def _build_inspect_tree_inner( key, obj, path ) -> dict:
"VTKMesh",
"InternalMesh",
"InternalWell",
"VTKWell",
"Perforation",
]
sub_node[ "drawn" ] = False
Expand Down
6 changes: 4 additions & 2 deletions geos-trame/src/geos_trame/app/deck/tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,10 +160,12 @@ def write_files( self ):
files = self._split( pb )

for filepath, content in files.items():
includeName: str = self.input_file.xml_parser.file_to_relative_path[ filepath ]
model_loaded: BaseModel = self.decode_data( content )
model_with_changes: BaseModel = self._apply_changed_properties( model_loaded )
self._append_include_file( model_with_changes, includeName )

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 )

model_as_xml: str = self.to_xml( model_with_changes )

Expand Down
12 changes: 12 additions & 0 deletions geos-trame/src/geos_trame/app/io/xml_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,18 @@ def get_simulation_deck( self ) -> ElementTree.Element:
return
return self.simulation_deck

def contains_include_files( self ) -> bool:
"""
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 self.file_to_relative_path[ filename ]

def _read( self ) -> ElementTree.Element:
"""Reads an xml file (and recursively its included files) into memory

Expand Down
97 changes: 97 additions & 0 deletions geos-trame/src/geos_trame/app/ui/alertHandler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies.
# SPDX-FileContributor: Lucas Givord - Kitware
import asyncio

from trame.widgets import vuetify3


class AlertHandler( vuetify3.VContainer ):
"""
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 ):
super().__init__(
fluid=True,
classes="pa-0 ma-0",
)

self.__max_number_of_status = 5
self.__lifetime_of_alert = 10.0
self._status_id = 0

self.state.alerts = []

self.server.controller.on_add_error.add_task( self.add_error )
self.server.controller.on_add_warning.add_task( self.add_warning )

self.generate_alert_ui()

def generate_alert_ui( self ):
"""
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.
If there are more than 5 alerts displayed, remove the oldest.
A warning will be automatically closed after 10 seconds.
"""
self.state.alerts.append( {
"id": self._status_id,
"type": type,
"title": title,
"message": message,
} )

if len( self.state.alerts ) > self.__max_number_of_status:
self.state.alerts.pop( 0 )

alert_id = self._status_id
self._status_id += 1
self.state.dirty( "alerts" )
self.state.flush()

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"
"""
self.add_alert( "warning", title, message )

async def add_error( self, title: str, message: str ):
"""
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.
"""
self.state.alerts = list( filter( lambda i: i[ "id" ] != alert_id, self.state.alerts ) )
self.state.flush()
65 changes: 31 additions & 34 deletions geos-trame/src/geos_trame/app/ui/inspector.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import yaml
from pydantic import BaseModel
from trame.widgets import vuetify3 as vuetify
from trame.widgets import html
from trame_simput import get_simput_manager
from typing import Any

Expand All @@ -15,6 +16,7 @@ class Renderable( Enum ):
VTKMESH = "VTKMesh"
INTERNALMESH = "InternalMesh"
INTERNALWELL = "InternalWell"
VTKWELL = "VTKWell"
PERFORATION = "Perforation"


Expand Down Expand Up @@ -100,7 +102,7 @@ def get_node_dict( obj, node_id, path ):
title=node_name,
children=children if len( children ) else [],
hidden_children=[],
is_drawable=node_id in ( k for k in Renderable ),
is_drawable=node_id in ( k.value for k in Renderable ),
drawn=False,
)

Expand Down Expand Up @@ -180,28 +182,23 @@ def __init__( self, listen_to_active=True, source=None, **kwargs ):
**{
# style
"hoverable": True,
"max_width": 500,
"rounded": True,
# "dense": True,
# "density": "compact",
# "active_color": "blue",
# activation logic
# "activatable": True,
# "active_strategy": "single-independent",
# "activated": ("active_ids", ),
# "update_activated": "(active_ids) => {active_id = active_ids[0]}",
"activatable": True,
"activated": ( "active_ids", ),
"active_strategy": "single-independent",
"update_activated": ( self.change_current_id, "$event" ),
# selection logic
"selectable": True,
"select_strategy": "single-independent",
"selected": ( "active_ids", ),
"update_selected": "(active_ids) => {active_id = active_ids[0]}",
"selectable": False,
**kwargs,
},
)
self.tree = source
self._source = None
self.listen_to_active = listen_to_active

self.state.obj_path = ""
self.state.object_state = [ "", False ]

# register used types from Problem
self.simput_types = []
Expand All @@ -225,18 +222,17 @@ def on_change( topic, ids=None, **kwargs ):

with self:
with vuetify.Template( v_slot_append="{ item }" ):
with vuetify.VBtn(
v_if=( "item.is_drawable" ),
icon=True,
flat=True,
slim=True,
input_value=( "item.drawn" ),
click=( self.to_draw_change, "[item.id]" ),
):
vuetify.VIcon(
"{{ ((item.drawn)) ? 'mdi-eye' : 'mdi-eye-off' }}",
v_if=( "item.is_drawable" ),
)
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, item.id, $event ] " ) )

def to_draw_change( self, item, item_id, drawn ):
self.state.object_state = [ item_id, drawn ]

@property
def source( self ):
Expand Down Expand Up @@ -298,13 +294,14 @@ def set_source( self, v ):
debug.set_property( key, getattr( active_block, key ) )
debug.commit()

def to_draw_change( self, path ):
self.state.obj_path = path

# def on_active_change(self, **_):
# if self.listen_to_active:
# print("on_active_change")
# self.set_source_proxy(simple.GetActiveSource())
def change_current_id( self, item_id=None ):
"""
Change the current id of the tree.
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
# and this item is already selected
return

# def on_selection_change(self, node_active, **_):
# print("on_selection_change", node_active)
self.state.active_id = item_id
Loading