From 41b780b7f876c1f173f9501d3c66f6741aec74e1 Mon Sep 17 00:00:00 2001 From: Aleks Novikov Date: Thu, 24 Apr 2025 21:03:14 +0200 Subject: [PATCH 1/5] Pass cycleNumber to execute/output/collect calls --- .../2ph_comp/main.py | 10 +++++++--- .../carbonated_water/main.py | 19 +++++++++++-------- .../examples/solvers/acoustic_modeling.py | 9 +++++++-- .../examples/solvers/elastic_modeling.py | 9 +++++++-- .../examples/solvers/geomechanics_modeling.py | 7 ++++--- .../examples/solvers/reservoir_modeling.py | 7 ++++--- .../src/geos/pygeos_tools/solvers/Solver.py | 12 ++++++++---- .../geos/pygeos_tools/solvers/WaveSolver.py | 8 +++++--- 8 files changed, 53 insertions(+), 28 deletions(-) diff --git a/pygeos-tools/examples/reactiveCompositionalMultiphaseOBL_modeling/2ph_comp/main.py b/pygeos-tools/examples/reactiveCompositionalMultiphaseOBL_modeling/2ph_comp/main.py index 2cf7cc7e..c33a4780 100644 --- a/pygeos-tools/examples/reactiveCompositionalMultiphaseOBL_modeling/2ph_comp/main.py +++ b/pygeos-tools/examples/reactiveCompositionalMultiphaseOBL_modeling/2ph_comp/main.py @@ -98,7 +98,7 @@ def run_darts_model( xml_name: str, darts_model=None ): cycle: int = 0 solver.setDt( 86400.0 ) - solver.outputVtk( time ) + while time < solver.maxTime: if time < 604800: solver.setDt( 86400.0 ) @@ -108,10 +108,14 @@ def run_darts_model( xml_name: str, darts_model=None ): if rank == 0: if solver.dt is not None: print( f"time = {time:.3f}s, dt = {solver.getDt():.4f}, iter = {cycle + 1}" ) - solver.execute( time ) + + solver.outputVtk( time, cycle ) + solver.execute( time, cycle ) + time += solver.getDt() - solver.outputVtk( time ) cycle += 1 + + solver.outputVtk( time, cycle ) solver.cleanup( time ) diff --git a/pygeos-tools/examples/reactiveCompositionalMultiphaseOBL_modeling/carbonated_water/main.py b/pygeos-tools/examples/reactiveCompositionalMultiphaseOBL_modeling/carbonated_water/main.py index 58a677ba..58290b0b 100644 --- a/pygeos-tools/examples/reactiveCompositionalMultiphaseOBL_modeling/carbonated_water/main.py +++ b/pygeos-tools/examples/reactiveCompositionalMultiphaseOBL_modeling/carbonated_water/main.py @@ -50,8 +50,7 @@ def run_darts_model( domain: str, xml_name: str, darts_model=None ): time: float = 0 cycle: int = 0 solver.setDt( 8.64 ) - - solver.outputVtk( time ) + while time < solver.maxTime: # choose new timestep if domain == '1D': @@ -84,16 +83,20 @@ def run_darts_model( domain: str, xml_name: str, darts_model=None ): solver.setDt( 100.0 ) if rank == 0: print( f"time = {time:.3f}s, dt = {solver.getDt():.4f}, step = {cycle + 1}" ) - # run simulation - solver.execute( time ) - time += solver.getDt() + if cycle % 5 == 0: - solver.outputVtk( time ) + solver.outputVtk( time, cycle ) + + solver.execute( time, cycle ) + + time += solver.getDt() cycle += 1 + + solver.outputVtk( time, cycle ) solver.cleanup( time ) if __name__ == "__main__": darts_model = Model() - # run_darts_model( domain='1D', xml_name="1d_setup.xml", darts_model=darts_model ) - run_darts_model( domain='2D', xml_name="2d_setup.xml", darts_model=darts_model ) + run_darts_model( domain='1D', xml_name="1d_setup.xml", darts_model=darts_model ) + # run_darts_model( domain='2D', xml_name="2d_setup.xml", darts_model=darts_model ) diff --git a/pygeos-tools/examples/solvers/acoustic_modeling.py b/pygeos-tools/examples/solvers/acoustic_modeling.py index 07827260..f126f950 100644 --- a/pygeos-tools/examples/solvers/acoustic_modeling.py +++ b/pygeos-tools/examples/solvers/acoustic_modeling.py @@ -149,12 +149,17 @@ def main(): while time < solver.maxTime: if rank == 0 and cycle % 100 == 0: print( f"time = {time:.3f}s, dt= {solver.dt:.4f}, iter = {cycle + 1}" ) - solver.execute( time ) + if cycle % 50 == 0: - solver.outputVtk( time ) + solver.outputVtk( time, cycle ) + + solver.execute( time, cycle ) + time += solver.dt cycle += 1 + solver.outputVtk( time, cycle ) + shot.flag = "Done" if rank == 0: print( f"Shot {shot.id} done" ) diff --git a/pygeos-tools/examples/solvers/elastic_modeling.py b/pygeos-tools/examples/solvers/elastic_modeling.py index 10de4918..32026c82 100644 --- a/pygeos-tools/examples/solvers/elastic_modeling.py +++ b/pygeos-tools/examples/solvers/elastic_modeling.py @@ -82,12 +82,17 @@ def main(): while t < solver.maxTime: if rank == 0 and cycle % 100 == 0: print( f"time = {t:.3f}s, dt = {solver.dt:.4f}, iter = {cycle + 1}" ) - solver.execute( t ) + if cycle % 100 == 0: - solver.outputVtk( t ) + solver.outputVtk( t, cycle ) + + solver.execute( t, cycle ) + t += solver.dt cycle += 1 + solver.outputVtk( t, cycle ) + shot.flag = "Done" if rank == 0: print( f"Shot {shot.id} done" ) diff --git a/pygeos-tools/examples/solvers/geomechanics_modeling.py b/pygeos-tools/examples/solvers/geomechanics_modeling.py index a3a288d7..69c5515e 100644 --- a/pygeos-tools/examples/solvers/geomechanics_modeling.py +++ b/pygeos-tools/examples/solvers/geomechanics_modeling.py @@ -53,15 +53,16 @@ def main(): time: float = 0.0 cycle: int = 0 - solver.outputVtk( time ) while time < solver.maxTime: if rank == 0: if solver.dt is not None: print( f"time = {time:.3f}s, dt = {solver.dt:.4f}, iter = {cycle + 1}" ) - solver.execute( time ) - solver.outputVtk( time ) + solver.outputVtk( time, cycle ) + solver.execute( time, cycle ) time += solver.dt cycle += 1 + + solver.outputVtk( time, cycle ) solver.cleanup( time ) diff --git a/pygeos-tools/examples/solvers/reservoir_modeling.py b/pygeos-tools/examples/solvers/reservoir_modeling.py index 1e075cf8..885cde06 100644 --- a/pygeos-tools/examples/solvers/reservoir_modeling.py +++ b/pygeos-tools/examples/solvers/reservoir_modeling.py @@ -53,17 +53,18 @@ def main(): time: float = 0.0 cycle: int = 0 - solver.outputVtk( time ) while time < solver.maxTime: if rank == 0: if solver.dt is not None: print( f"time = {time:.3f}s, dt = {solver.dt:.4f}, iter = {cycle + 1}" ) - solver.execute( time ) - solver.outputVtk( time ) + solver.outputVtk( time, cycle ) + solver.execute( time, cycle ) pressure = solver.getPressures() print( pressure ) time += solver.dt cycle += 1 + + solver.outputVtk( time, cycle ) solver.cleanup( time ) diff --git a/pygeos-tools/src/geos/pygeos_tools/solvers/Solver.py b/pygeos-tools/src/geos/pygeos_tools/solvers/Solver.py index d518496b..e562836d 100644 --- a/pygeos-tools/src/geos/pygeos_tools/solvers/Solver.py +++ b/pygeos-tools/src/geos/pygeos_tools/solvers/Solver.py @@ -701,7 +701,7 @@ def cleanup( self: Self, time: float ) -> None: self.solver.cleanup( time ) @required_attributes( "solver" ) - def execute( self: Self, time: float ) -> None: + def execute( self: Self, time: float, cycleNumber: int ) -> None: """ Do one solver iteration @@ -709,8 +709,10 @@ def execute( self: Self, time: float ) -> None: ---------- time : float Current time of simulation + cycleNumber : int + Current cycle number """ - self.solver.execute( time, self.dt ) + self.solver.execute( time, self.dt, cycleNumber ) @required_attributes( "solver" ) def reinitSolver( self: Self ) -> None: @@ -718,7 +720,7 @@ def reinitSolver( self: Self ) -> None: self.solver.reinit() @required_attributes( "vtkOutputs" ) - def outputVtk( self: Self, time: float ) -> None: + def outputVtk( self: Self, time: float, cycleNumber: int ) -> None: """ Trigger the VTK output @@ -726,9 +728,11 @@ def outputVtk( self: Self, time: float ) -> None: ---------- time : float Current time of simulation + cycleNumber : int + Current cycle number """ for vtkOutput in self.vtkOutputs: - vtkOutput.output( time, self.dt ) + vtkOutput.output( time, self.dt, cycleNumber ) """ Update methods when initializing or reinitializing the solver diff --git a/pygeos-tools/src/geos/pygeos_tools/solvers/WaveSolver.py b/pygeos-tools/src/geos/pygeos_tools/solvers/WaveSolver.py index 9daf59c0..2e05d06f 100644 --- a/pygeos-tools/src/geos/pygeos_tools/solvers/WaveSolver.py +++ b/pygeos-tools/src/geos/pygeos_tools/solvers/WaveSolver.py @@ -393,7 +393,7 @@ def filterSource( self: Self, fmax: Union[ str, float ] ) -> None: self.setGeosWrapperValueByTargetKey( "Events/minTime", self.minTimeSim ) self.sourceValue = np.real( y[ max( i1 - d, 0 ):min( i4 + d, n ), : ] ) - def outputWaveField( self: Self, time: float ) -> None: + def outputWaveField( self: Self, time: float, cycleNumber: int ) -> None: """ Trigger the wavefield output @@ -401,6 +401,8 @@ def outputWaveField( self: Self, time: float ) -> None: ---------- time : float Current time of simulation + cycleNumber : int + Current cycle number """ - self.collections[ 0 ].collect( time, self.dt ) - self.hdf5Outputs[ 0 ].output( time, self.dt ) + self.collections[ 0 ].collect( time, self.dt, cycleNumber ) + self.hdf5Outputs[ 0 ].output( time, self.dt, cycleNumber ) From e184d7a9d437e80a9ab7f4da2d8ec50236c6ee0e Mon Sep 17 00:00:00 2001 From: Aleks Novikov Date: Thu, 24 Apr 2025 23:15:37 +0200 Subject: [PATCH 2/5] Apply yapf formatting --- .../carbonated_water/main.py | 4 ++-- pygeos-tools/examples/solvers/acoustic_modeling.py | 2 +- pygeos-tools/examples/solvers/elastic_modeling.py | 6 +++--- pygeos-tools/examples/solvers/geomechanics_modeling.py | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pygeos-tools/examples/reactiveCompositionalMultiphaseOBL_modeling/carbonated_water/main.py b/pygeos-tools/examples/reactiveCompositionalMultiphaseOBL_modeling/carbonated_water/main.py index 58290b0b..4ebb62be 100644 --- a/pygeos-tools/examples/reactiveCompositionalMultiphaseOBL_modeling/carbonated_water/main.py +++ b/pygeos-tools/examples/reactiveCompositionalMultiphaseOBL_modeling/carbonated_water/main.py @@ -50,7 +50,7 @@ def run_darts_model( domain: str, xml_name: str, darts_model=None ): time: float = 0 cycle: int = 0 solver.setDt( 8.64 ) - + while time < solver.maxTime: # choose new timestep if domain == '1D': @@ -83,7 +83,7 @@ def run_darts_model( domain: str, xml_name: str, darts_model=None ): solver.setDt( 100.0 ) if rank == 0: print( f"time = {time:.3f}s, dt = {solver.getDt():.4f}, step = {cycle + 1}" ) - + if cycle % 5 == 0: solver.outputVtk( time, cycle ) diff --git a/pygeos-tools/examples/solvers/acoustic_modeling.py b/pygeos-tools/examples/solvers/acoustic_modeling.py index f126f950..92732003 100644 --- a/pygeos-tools/examples/solvers/acoustic_modeling.py +++ b/pygeos-tools/examples/solvers/acoustic_modeling.py @@ -149,7 +149,7 @@ def main(): while time < solver.maxTime: if rank == 0 and cycle % 100 == 0: print( f"time = {time:.3f}s, dt= {solver.dt:.4f}, iter = {cycle + 1}" ) - + if cycle % 50 == 0: solver.outputVtk( time, cycle ) diff --git a/pygeos-tools/examples/solvers/elastic_modeling.py b/pygeos-tools/examples/solvers/elastic_modeling.py index 32026c82..aa489623 100644 --- a/pygeos-tools/examples/solvers/elastic_modeling.py +++ b/pygeos-tools/examples/solvers/elastic_modeling.py @@ -82,12 +82,12 @@ def main(): while t < solver.maxTime: if rank == 0 and cycle % 100 == 0: print( f"time = {t:.3f}s, dt = {solver.dt:.4f}, iter = {cycle + 1}" ) - + if cycle % 100 == 0: solver.outputVtk( t, cycle ) - + solver.execute( t, cycle ) - + t += solver.dt cycle += 1 diff --git a/pygeos-tools/examples/solvers/geomechanics_modeling.py b/pygeos-tools/examples/solvers/geomechanics_modeling.py index 69c5515e..14516ae1 100644 --- a/pygeos-tools/examples/solvers/geomechanics_modeling.py +++ b/pygeos-tools/examples/solvers/geomechanics_modeling.py @@ -61,7 +61,7 @@ def main(): solver.execute( time, cycle ) time += solver.dt cycle += 1 - + solver.outputVtk( time, cycle ) solver.cleanup( time ) From e7a3ba79b42e0724162fc1ceaa00a98bcf520060 Mon Sep 17 00:00:00 2001 From: Aleks Novikov Date: Fri, 25 Apr 2025 15:04:24 +0200 Subject: [PATCH 3/5] Update OBL examples for better comparison --- .../2ph_comp/input_file_adaptive.xml | 4 +++- .../carbonated_water/1d_setup.xml | 18 ++++++++++-------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/pygeos-tools/examples/reactiveCompositionalMultiphaseOBL_modeling/2ph_comp/input_file_adaptive.xml b/pygeos-tools/examples/reactiveCompositionalMultiphaseOBL_modeling/2ph_comp/input_file_adaptive.xml index ec4e1ea2..a664970c 100644 --- a/pygeos-tools/examples/reactiveCompositionalMultiphaseOBL_modeling/2ph_comp/input_file_adaptive.xml +++ b/pygeos-tools/examples/reactiveCompositionalMultiphaseOBL_modeling/2ph_comp/input_file_adaptive.xml @@ -16,7 +16,9 @@ newtonTol="0.0001" newtonMaxIter="25"/> + solverType="fgmres" + preconditionerType="mgr" + krylovTol="1.0e-5"/> diff --git a/pygeos-tools/examples/reactiveCompositionalMultiphaseOBL_modeling/carbonated_water/1d_setup.xml b/pygeos-tools/examples/reactiveCompositionalMultiphaseOBL_modeling/carbonated_water/1d_setup.xml index a2e012dc..9e714abc 100644 --- a/pygeos-tools/examples/reactiveCompositionalMultiphaseOBL_modeling/carbonated_water/1d_setup.xml +++ b/pygeos-tools/examples/reactiveCompositionalMultiphaseOBL_modeling/carbonated_water/1d_setup.xml @@ -18,7 +18,9 @@ newtonTol="0.0001" newtonMaxIter="25"/> + solverType="fgmres" + preconditionerType="mgr" + krylovTol="1.0e-5"/> @@ -101,7 +103,7 @@ objectPath="ElementRegions/Region1" fieldName="globalCompFraction" component="0" - scale="0.7"/> + scale="0.276839"/> + scale="1e-11"/> + scale="1e-11"/> + scale="0.276839"/> + scale="1e-11"/> + scale="1e-11"/> Date: Tue, 6 May 2025 10:08:47 -0700 Subject: [PATCH 4/5] Update example doc --- docs/pygeos_tools_docs/example.rst | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/pygeos_tools_docs/example.rst b/docs/pygeos_tools_docs/example.rst index a217eb8b..dda8cb9e 100644 --- a/docs/pygeos_tools_docs/example.rst +++ b/docs/pygeos_tools_docs/example.rst @@ -117,12 +117,12 @@ This block contains a ``forceDt`` attribute that will be used later to choose as The "outputs" event triggers the output of the vtk files. The attribute "timeFrequency" has the same value as "forceDt" so we can use the same timestep for the solver and the outputs. -To start, we will set the time to 0.0 and trigger one output of the vtk files. +To start, we will set the time to 0.0 and the cycle number to 0. .. code-block:: python time = 0.0 - solver.outputVtk( time ) + cycle = 0 ------------------------------------------------------------------ @@ -135,9 +135,12 @@ Once done, the simulation is ended by calling the ``cleanup`` method. .. code-block:: python while time < solver.maxTime: - solver.execute( time ) - solver.outputVtk( time ) + solver.outputVtk( time, cycle ) + solver.execute( time, cycle ) time += solver.dt + cycle += 1 + + solver.outputVtk( time, cycle ) solver.cleanup( time ) From 1649bf8a595bc41331969b1ee56e8a11fb3c7d56 Mon Sep 17 00:00:00 2001 From: Martin Lemay <117269226+mlemayTTE@users.noreply.github.com> Date: Wed, 14 May 2025 23:10:31 +0200 Subject: [PATCH 5/5] docs: Add Paraview Proxy examples (#90) * Add Paraview Proxy examples * linting, formating, typing --- geos-pv/examples/PVproxyInputs.py | 374 +++++++++++ geos-pv/examples/PVproxyTimeStepAggregator.py | 190 ++++++ geos-pv/examples/PVproxyWidgetsDynamic.py | 564 ++++++++++++++++ geos-pv/examples/PVproxyWidgetsStatic.py | 605 ++++++++++++++++++ 4 files changed, 1733 insertions(+) create mode 100644 geos-pv/examples/PVproxyInputs.py create mode 100644 geos-pv/examples/PVproxyTimeStepAggregator.py create mode 100644 geos-pv/examples/PVproxyWidgetsDynamic.py create mode 100644 geos-pv/examples/PVproxyWidgetsStatic.py diff --git a/geos-pv/examples/PVproxyInputs.py b/geos-pv/examples/PVproxyInputs.py new file mode 100644 index 00000000..892fb12d --- /dev/null +++ b/geos-pv/examples/PVproxyInputs.py @@ -0,0 +1,374 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. +# SPDX-FileContributor: Martin Lemay +# ruff: noqa: E402 # disable Module level import not at top of file +import sys +import numpy as np +from pathlib import Path +from typing_extensions import Self + +# update sys.path to load all GEOS Python Package dependencies +geos_pv_path: Path = Path( __file__ ).parent.parent +sys.path.insert( 0, str( geos_pv_path / "src" ) ) +from geos.pv.utils.config import update_paths + +update_paths() + +from vtkmodules.util.vtkAlgorithm import VTKPythonAlgorithmBase + +from paraview.util.vtkAlgorithm import ( # type: ignore[import-not-found] + smdomain, smproperty, smproxy, smhint ) +from vtkmodules.vtkCommonCore import ( + vtkInformation, + vtkInformationVector, + vtkDoubleArray, +) +from vtkmodules.vtkCommonDataModel import ( + vtkPointSet, + vtkUnstructuredGrid, + vtkFieldData, + vtkMultiBlockDataSet, +) + +__doc__ = """ +This file defines multiple Paraview plugins with various configurations. + +Examples of Source, Reader and Writer can be found on `Paraview documentation page `_. + +Additional examples are here defined: + +* PVPreserveInputTypeFilter is an example of a Paraview plugin where output is of same type as input data. + + .. Note:: + if Input data is a composite data set, the RequestData method is applied to each part of input object. + Results are concatenated to output object. Point data and cell data are added to each block, + a new line per block is added to output Field data or output vtkTable. + +* PVCompositeDataSetFilter is an example of a Paraview plugin that treats composite data sets as a single object conversely to PVPreserveInputTypeFilter. + +* PVMultipleInputFilter is an example of a Paraview plugin using 2 inputs of different type. + + The output is here of same type as input 1. + + .. Note:: Inputs are ordered in the reverse order compared to their definition using decorators. + + + +""" + + +@smproxy.filter( name="PVPreserveInputTypeFilter", label="Preserve Input Type Filter" ) +@smhint.xml( """""" ) +@smproperty.input( name="Input", port_index=0, label="Input" ) +@smdomain.datatype( + dataTypes=[ "vtkUnstructuredGrid" ], + composite_data_supported=True, +) +class PVPreserveInputTypeFilter( VTKPythonAlgorithmBase ): + + def __init__( self: Self ) -> None: + """Map the properties of a server mesh to a client mesh.""" + super().__init__( nInputPorts=1, nOutputPorts=1, outputType="vtkUnstructuredGrid" ) + + def RequestDataObject( + self: Self, + request: vtkInformation, + inInfoVec: list[ vtkInformationVector ], + outInfoVec: vtkInformationVector, + ) -> int: + """Inherited from VTKPythonAlgorithmBase::RequestDataObject. + + Args: + request (vtkInformation): Request + inInfoVec (list[vtkInformationVector]): Input objects + outInfoVec (vtkInformationVector): Output objects + + Returns: + int: 1 if calculation successfully ended, 0 otherwise. + """ + print( "RequestDataObject" ) + inData1 = self.GetInputData( inInfoVec, 0, 0 ) + outData = self.GetOutputData( outInfoVec, 0 ) + assert inData1 is not None + if outData is None or ( not outData.IsA( inData1.GetClassName() ) ): + outData = inData1.NewInstance() + outInfoVec.GetInformationObject( 0 ).Set( outData.DATA_OBJECT(), outData ) + return super().RequestDataObject( request, inInfoVec, outInfoVec ) # type: ignore[no-any-return] + + def RequestData( + self: Self, + request: vtkInformation, # noqa: F841 + inInfoVec: list[ vtkInformationVector ], + outInfoVec: vtkInformationVector, + ) -> int: + """Inherited from VTKPythonAlgorithmBase::RequestData. + + Args: + request (vtkInformation): Request + inInfoVec (list[vtkInformationVector]): Input objects + outInfoVec (vtkInformationVector): Output objects + + Returns: + int: 1 if calculation successfully ended, 0 otherwise. + """ + print( "RequestData" ) + input: vtkUnstructuredGrid = self.GetInputData( inInfoVec, 0, 0 ) + outData: vtkPointSet = self.GetOutputData( outInfoVec, 0 ) + + assert input is not None, "input 0 server mesh is null." + assert outData is not None, "Output pipeline is null." + + # do something... + # for instance copy input and create a Field data in output object + + outData.ShallowCopy( input ) + + # add Field data attribute + nbArrays: int = 3 + fieldData: vtkFieldData = outData.GetFieldData() + fieldData.AllocateArrays( nbArrays ) + for i in range( nbArrays ): + newArray: vtkDoubleArray = vtkDoubleArray() + newArray.SetName( f"Column{i}" ) + newArray.SetNumberOfComponents( 1 ) + newArray.SetNumberOfValues( 1 ) + val: float = i + np.random.rand( 1 )[ 0 ] + newArray.SetValue( 0, val ) + fieldData.AddArray( newArray ) + fieldData.Modified() + + # add Point attribute + + # add Cell attribute + + outData.Modified() + return 1 + + +@smproxy.filter( name="PVCompositeDataSetFilter", label="Composite Data Set Filter" ) +@smhint.xml( """""" ) +@smproperty.input( name="Input", port_index=0, label="Input" ) +@smdomain.datatype( + dataTypes=[ "vtkMultiBlockDataSet" ], + composite_data_supported=True, +) +class PVCompositeDataSetFilter( VTKPythonAlgorithmBase ): + + def __init__( self: Self ) -> None: + """Map the properties of a server mesh to a client mesh.""" + super().__init__( nInputPorts=2, nOutputPorts=1, outputType="vtkMultiBlockDataSet" ) + + def RequestDataObject( + self: Self, + request: vtkInformation, + inInfoVec: list[ vtkInformationVector ], + outInfoVec: vtkInformationVector, + ) -> int: + """Inherited from VTKPythonAlgorithmBase::RequestDataObject. + + Args: + request (vtkInformation): Request + inInfoVec (list[vtkInformationVector]): Input objects + outInfoVec (vtkInformationVector): Output objects + + Returns: + int: 1 if calculation successfully ended, 0 otherwise. + """ + inData1 = self.GetInputData( inInfoVec, 0, 0 ) + outData = self.GetOutputData( outInfoVec, 0 ) + assert inData1 is not None + if outData is None or ( not outData.IsA( inData1.GetClassName() ) ): + outData = inData1.NewInstance() + outInfoVec.GetInformationObject( 0 ).Set( outData.DATA_OBJECT(), outData ) + return super().RequestDataObject( request, inInfoVec, outInfoVec ) # type: ignore[no-any-return] + + def RequestData( + self: Self, + request: vtkInformation, # noqa: F841 + inInfoVec: list[ vtkInformationVector ], + outInfoVec: vtkInformationVector, + ) -> int: + """Inherited from VTKPythonAlgorithmBase::RequestData. + + Args: + request (vtkInformation): Request + inInfoVec (list[vtkInformationVector]): Input objects + outInfoVec (vtkInformationVector): Output objects + + Returns: + int: 1 if calculation successfully ended, 0 otherwise. + """ + input: vtkMultiBlockDataSet = self.GetInputData( inInfoVec, 0, 0 ) + outData: vtkMultiBlockDataSet = self.GetOutputData( outInfoVec, 0 ) + + assert input is not None, "input 0 server mesh is null." + assert outData is not None, "Output pipeline is null." + + # do something... + # for instance copy input and create a Field data in output object + + outData.ShallowCopy( input ) + nbArrays: int = 3 + fieldData: vtkFieldData = outData.GetFieldData() + fieldData.AllocateArrays( nbArrays ) + for i in range( nbArrays ): + newArray: vtkDoubleArray = vtkDoubleArray() + newArray.SetName( f"Column{i}" ) + newArray.SetNumberOfComponents( 1 ) + newArray.SetNumberOfValues( 1 ) + val: float = i + np.random.rand( 1 )[ 0 ] + newArray.SetValue( 0, val ) + fieldData.AddArray( newArray ) + + fieldData.Modified() + outData.Modified() + return 1 + + +@smproxy.filter( name="PVMultiInputFilter", label="Multiple Input Filter" ) +@smhint.xml( """""" ) +@smproperty.input( name="Input1", port_index=1, label="Input 1" ) +@smdomain.datatype( + dataTypes=[ + "vtkPointSet", + ], + composite_data_supported=False, +) +@smproperty.input( name="Input0", port_index=0, label="Input 0" ) +@smdomain.datatype( + dataTypes=[ "vtkUnstructuredGrid" ], + composite_data_supported=False, +) +class PVMultipleInputFilter( VTKPythonAlgorithmBase ): + + def __init__( self: Self ) -> None: + """Map the properties of a server mesh to a client mesh.""" + super().__init__( nInputPorts=2, nOutputPorts=1, outputType="vtkUnstructuredGrid" ) + + def RequestDataObject( + self: Self, + request: vtkInformation, + inInfoVec: list[ vtkInformationVector ], + outInfoVec: vtkInformationVector, + ) -> int: + """Inherited from VTKPythonAlgorithmBase::RequestDataObject. + + Args: + request (vtkInformation): Request + inInfoVec (list[vtkInformationVector]): Input objects + outInfoVec (vtkInformationVector): Output objects + + Returns: + int: 1 if calculation successfully ended, 0 otherwise. + """ + # here output data is of same type as input 1 + inData1 = self.GetInputData( inInfoVec, 1, 0 ) + outData = self.GetOutputData( outInfoVec, 0 ) + assert inData1 is not None + if outData is None or ( not outData.IsA( inData1.GetClassName() ) ): + outData = inData1.NewInstance() + outInfoVec.GetInformationObject( 0 ).Set( outData.DATA_OBJECT(), outData ) + return super().RequestDataObject( request, inInfoVec, outInfoVec ) # type: ignore[no-any-return] + + def RequestData( + self: Self, + request: vtkInformation, # noqa: F841 + inInfoVec: list[ vtkInformationVector ], + outInfoVec: vtkInformationVector, + ) -> int: + """Inherited from VTKPythonAlgorithmBase::RequestData. + + Args: + request (vtkInformation): Request + inInfoVec (list[vtkInformationVector]): Input objects + outInfoVec (vtkInformationVector): Output objects + + Returns: + int: 1 if calculation successfully ended, 0 otherwise. + """ + input0: vtkUnstructuredGrid = self.GetInputData( inInfoVec, 0, 0 ) + input1: vtkPointSet = self.GetInputData( inInfoVec, 1, 0 ) + outData: vtkPointSet = self.GetOutputData( outInfoVec, 0 ) + + assert input0 is not None, "input 0 server mesh is null." + assert input1 is not None, "input 1 client mesh is null." + assert outData is not None, "Output pipeline is null." + + # do something... + + return 1 + + +@smproxy.filter( name="PVMultiOutputFilter", label="Multiple Output Filter" ) +@smhint.xml( """ + + + + """ ) +@smproperty.input( name="Input", port_index=0, label="Input" ) +@smdomain.datatype( + dataTypes=[ "vtkUnstructuredGrid" ], + composite_data_supported=False, +) +class PVMultipleOutputFilter( VTKPythonAlgorithmBase ): + + def __init__( self: Self ) -> None: + """Map the properties of a server mesh to a client mesh.""" + super().__init__( nInputPorts=1, nOutputPorts=2, outputType="vtkUnstructuredGrid" ) + + def RequestDataObject( + self: Self, + request: vtkInformation, + inInfoVec: list[ vtkInformationVector ], + outInfoVec: vtkInformationVector, + ) -> int: + """Inherited from VTKPythonAlgorithmBase::RequestDataObject. + + Args: + request (vtkInformation): Request + inInfoVec (list[vtkInformationVector]): Input objects + outInfoVec (vtkInformationVector): Output objects + + Returns: + int: 1 if calculation successfully ended, 0 otherwise. + """ + # here output data is of same type as input 1 + inData1 = self.GetInputData( inInfoVec, 1, 0 ) + outData0 = self.GetOutputData( outInfoVec, 0 ) + assert inData1 is not None + if outData0 is None or ( not outData0.IsA( inData1.GetClassName() ) ): + outData0 = inData1.NewInstance() + outInfoVec.GetInformationObject( 0 ).Set( outData0.DATA_OBJECT(), outData0 ) + outData1 = self.GetOutputData( outInfoVec, 1 ) + if outData1 is None or ( not outData1.IsA( inData1.GetClassName() ) ): + outData1 = inData1.NewInstance() + outInfoVec.GetInformationObject( 1 ).Set( outData1.DATA_OBJECT(), outData1 ) + return super().RequestDataObject( request, inInfoVec, outInfoVec ) # type: ignore[no-any-return] + + def RequestData( + self: Self, + request: vtkInformation, # noqa: F841 + inInfoVec: list[ vtkInformationVector ], + outInfoVec: vtkInformationVector, + ) -> int: + """Inherited from VTKPythonAlgorithmBase::RequestData. + + Args: + request (vtkInformation): Request + inInfoVec (list[vtkInformationVector]): Input objects + outInfoVec (vtkInformationVector): Output objects + + Returns: + int: 1 if calculation successfully ended, 0 otherwise. + """ + input: vtkUnstructuredGrid = self.GetInputData( inInfoVec, 0, 0 ) + outData0: vtkUnstructuredGrid = self.GetOutputData( outInfoVec, 0 ) + outData1: vtkUnstructuredGrid = self.GetOutputData( outInfoVec, 1 ) + + assert input is not None, "input 0 server mesh is null." + assert outData0 is not None, "Output pipeline 0 is null." + assert outData1 is not None, "Output pipeline 1 is null." + + # do something... + + return 1 diff --git a/geos-pv/examples/PVproxyTimeStepAggregator.py b/geos-pv/examples/PVproxyTimeStepAggregator.py new file mode 100644 index 00000000..4b14166c --- /dev/null +++ b/geos-pv/examples/PVproxyTimeStepAggregator.py @@ -0,0 +1,190 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. +# SPDX-FileContributor: Martin Lemay +# ruff: noqa: E402 # disable Module level import not at top of file +import sys +import numpy as np +import numpy.typing as npt +from pathlib import Path +from typing_extensions import Self + +# update sys.path to load all GEOS Python Package dependencies +geos_pv_path: Path = Path( __file__ ).parent.parent +sys.path.insert( 0, str( geos_pv_path / "src" ) ) +from geos.pv.utils.config import update_paths + +update_paths() + +from vtkmodules.util.vtkAlgorithm import VTKPythonAlgorithmBase + +from paraview.util.vtkAlgorithm import ( # type: ignore[import-not-found] + smdomain, smproperty, smproxy, smhint ) +from vtkmodules.vtkCommonCore import ( + vtkInformation, + vtkInformationVector, +) +from vtkmodules.vtkCommonDataModel import ( + vtkPointSet, + vtkUnstructuredGrid, +) + +__doc__ = """ +Example of a Paraview plugin that runs aggregate data from various time steps. + +For instance, to copy arrays from oter time steps to the current one, or to compute +array differences between successive time steps. + +""" + + +@smproxy.filter( name="PVTimeStepAggregatorFilter", label="Time Step Aggregator Filter" ) +@smhint.xml( """""" ) +@smproperty.input( name="Input", port_index=0, label="Input" ) +@smdomain.datatype( + dataTypes=[ "vtkUnstructuredGrid" ], + composite_data_supported=True, +) +class PVTimeStepAggregatorFilter( VTKPythonAlgorithmBase ): + + def __init__( self: Self ) -> None: + """Map the properties of a server mesh to a client mesh.""" + super().__init__( nInputPorts=1, nOutputPorts=1, outputType="vtkUnstructuredGrid" ) + + #: all time steps from input + self._timeSteps: npt.NDArray[ np.float64 ] = np.array( [] ) + #: displayed time step in the IHM + self._currentTime: float = 0.0 + #: time step index of displayed time step + self._currentTimeStepIndex: int = 0 + #: request data processing step - incremented each time RequestUpdateExtent is called + #: start at -1 to perform initialization when filter is selected (but not applied yet) + self._requestDataStep: int = -1 + + #: saved object at each time step + self._savedInputs: list[ vtkUnstructuredGrid ] = [] + + def RequestInformation( + self: Self, + request: vtkInformation, # noqa: F841 + inInfoVec: list[ vtkInformationVector ], + outInfoVec: vtkInformationVector, + ) -> int: + """Inherited from VTKPythonAlgorithmBase::RequestInformation. + + Args: + request (vtkInformation): Request + inInfoVec (list[vtkInformationVector]): Input objects + outInfoVec (vtkInformationVector): Output objects + + Returns: + int: 1 if calculation successfully ended, 0 otherwise. + """ + executive = self.GetExecutive() + inInfo = inInfoVec[ 0 ] + # get displayed time step info at filter intialization only + if self._requestDataStep == -1: + self._timeSteps = inInfo.GetInformationObject( 0 ).Get( executive.TIME_STEPS() # type: ignore + ) + self._currentTime = inInfo.GetInformationObject( 0 ).Get( executive.UPDATE_TIME_STEP() # type: ignore + ) + self._currentTimeStepIndex = self.getTimeStepIndex( self._currentTime, self._timeSteps ) + + self._savedInputs.clear() + + # update requestDataStep + self._requestDataStep += 1 + # update time according to requestDataStep iterator + inInfo.GetInformationObject( 0 ).Set( + executive.UPDATE_TIME_STEP(), # type: ignore[attr-defined] + self._timeSteps[ self._requestDataStep ] ) + outInfoVec.GetInformationObject( 0 ).Set( + executive.UPDATE_TIME_STEP(), # type: ignore[attr-defined] + self._timeSteps[ self._requestDataStep ] ) + # update all objects according to new time info + self.Modified() + return 1 + + def RequestDataObject( + self: Self, + request: vtkInformation, + inInfoVec: list[ vtkInformationVector ], + outInfoVec: vtkInformationVector, + ) -> int: + """Inherited from VTKPythonAlgorithmBase::RequestDataObject. + + Args: + request (vtkInformation): Request + inInfoVec (list[vtkInformationVector]): Input objects + outInfoVec (vtkInformationVector): Output objects + + Returns: + int: 1 if calculation successfully ended, 0 otherwise. + """ + inData1 = self.GetInputData( inInfoVec, 0, 0 ) + outData = self.GetOutputData( outInfoVec, 0 ) + assert inData1 is not None + if outData is None or ( not outData.IsA( inData1.GetClassName() ) ): + outData = inData1.NewInstance() + outInfoVec.GetInformationObject( 0 ).Set( outData.DATA_OBJECT(), outData ) + return super().RequestDataObject( request, inInfoVec, outInfoVec ) # type: ignore[no-any-return] + + def RequestData( + self: Self, + request: vtkInformation, # noqa: F841 + inInfoVec: list[ vtkInformationVector ], + outInfoVec: vtkInformationVector, + ) -> int: + """Inherited from VTKPythonAlgorithmBase::RequestData. + + Args: + request (vtkInformation): Request + inInfoVec (list[vtkInformationVector]): Input objects + outInfoVec (vtkInformationVector): Output objects + + Returns: + int: 1 if calculation successfully ended, 0 otherwise. + """ + input: vtkUnstructuredGrid = self.GetInputData( inInfoVec, 0, 0 ) + outData: vtkPointSet = self.GetOutputData( outInfoVec, 0 ) + + assert input is not None, "input 0 server mesh is null." + assert outData is not None, "Output pipeline is null." + + # time controller + executive = self.GetExecutive() + if self._requestDataStep <= self._currentTimeStepIndex: + + # do something repeated at each time step... + dataAtT: vtkUnstructuredGrid = vtkUnstructuredGrid() + dataAtT.ShallowCopy( input ) + self._savedInputs.append( dataAtT ) + print( f"Input data saved at time step {self._requestDataStep}" ) + # keep running through time steps + request.Set( executive.CONTINUE_EXECUTING(), 1 ) # type: ignore + if self._requestDataStep >= self._currentTimeStepIndex: + # displayed time step, stop running + request.Remove( executive.CONTINUE_EXECUTING() ) # type: ignore + + # reinitialize requestDataStep if filter is re-applied later + self._requestDataStep = -1 + + # do something to finalize process... + outData.ShallowCopy( input ) + print( "Finalization process" ) + + outData.Modified() + return 1 + + def getTimeStepIndex( self: Self, time: float, timeSteps: npt.NDArray[ np.float64 ] ) -> int: + """Get the time step index of input time from the list of time steps. + + Args: + time (float): Time + timeSteps (npt.NDArray[np.float64]): Array of time steps + + Returns: + int: Time step index + """ + indexes: npt.NDArray[ np.int64 ] = np.where( np.isclose( timeSteps, time ) )[ 0 ] + assert ( indexes.size > 0 ), f"Current time {time} does not exist in the selected object." + return int( indexes[ 0 ] ) diff --git a/geos-pv/examples/PVproxyWidgetsDynamic.py b/geos-pv/examples/PVproxyWidgetsDynamic.py new file mode 100644 index 00000000..257d05dd --- /dev/null +++ b/geos-pv/examples/PVproxyWidgetsDynamic.py @@ -0,0 +1,564 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. +# SPDX-FileContributor: Martin Lemay +# ruff: noqa: E402 # disable Module level import not at top of file +import sys +from enum import Enum +from pathlib import Path +from typing_extensions import Self + +# update sys.path to load all GEOS Python Package dependencies +geos_pv_path: Path = Path( __file__ ).parent.parent +sys.path.insert( 0, str( geos_pv_path / "src" ) ) +from geos.pv.utils.config import update_paths + +update_paths() + +from geos.pv.utils.paraviewTreatments import ( strListToEnumerationDomainXml, strEnumToEnumerationDomainXml, + getArrayChoices ) +from geos_posp.visu.PVUtils.checkboxFunction import ( # type: ignore[attr-defined] + createModifiedCallback, ) + +from vtkmodules.util.vtkAlgorithm import VTKPythonAlgorithmBase +from paraview.util.vtkAlgorithm import ( # type: ignore[import-not-found] + smdomain, smproperty, smproxy, smhint, +) +from vtkmodules.vtkCommonCore import ( + vtkInformation, + vtkInformationVector, + vtkDataArraySelection, +) +from vtkmodules.vtkCommonDataModel import ( + vtkPointSet, + vtkUnstructuredGrid, +) + +__doc__ = """ +Example of a Paraview plugin that defines various dynamic input widgets. + +Dynamic inputs vary according to input data object or context. +See `Plugin HowTo page `_ +and `property hints documentation `_ + + tag from filter @smdomain.xml allows to filter arrays: attribute_type keyword filters array +if they are supported by point, cell, or field; number_of_components keyword filters on the number of components. + + tag required 2 arguments: +* mode options are defined by `vtkSMBoundsDomain::Modes enum `_ + (may have a way to select which axis, but not found) +* scale_factor: factor by which extent is multiplied for display. + +""" + +# TODO: try this https://discourse.paraview.org/t/difficulties-to-use-propertygroup-in-python-plugin-of-a-filter/12070 + +DROP_DOWN_LIST_ELEMENTS: tuple[ str, str, str ] = ( "Choice1", "Choice2", "Choice3" ) + + +class DROP_DOWN_LIST_ENUM( Enum ): + CHOICE1 = "Enum1" + CHOICE2 = "Enum2" + CHOICE3 = "Enum3" + + +@smproxy.filter( name="PVproxyWidgetsDynamic", label="Dynamic Widget Examples" ) +@smhint.xml( """""" ) +@smproperty.input( name="Input", port_index=0, label="Input" ) +@smdomain.datatype( + dataTypes=[ "vtkUnstructuredGrid" ], + composite_data_supported=True, +) +@smdomain.xml( """ + + + + + + """ ) +class PVproxyWidgetsDynamic( VTKPythonAlgorithmBase ): + + def __init__( self: Self ) -> None: + """Map the properties of a server mesh to a client mesh.""" + super().__init__( nInputPorts=1, nOutputPorts=1, outputType="vtkUnstructuredGrid" ) + + self._strSingle: str = "" + self._intSingle: int = 0 + self._doubleSingle: float = 0.0 + + self._dropDownListSelection: int = 0 + self._dropDownListSelection2: int = 0 + + self._selectedAttributeSingle: str = "" + self._clearSelectedAttributeMulti: bool = True + self._selectedAttributeMulti: list[ str ] = [] + + # used to init data + self._initArraySelections: bool = True + self._selectedTimes: vtkDataArraySelection = vtkDataArraySelection() + self._selectedTimes.AddObserver( 0, createModifiedCallback( self ) ) + + self._extentSlider: list[ float ] = [ 0.0, 1.0 ] + + self._selectedAttributeSingleType: str = "" + self._componentIndex: int = 0 + + self.clearBlockNames: bool = True + self._blockNames: list[ str ] = [] + + @smproperty.intvector( + name="BoolSingle", + label="Show/Hide More Widgets", + default_values=0, + panel_visibility="default", + ) + @smdomain.xml( """""" ) + def a00BoolSingle( self: Self, value: bool ) -> None: + """Define boolean input. + + Args: + value (bool): Input bool. + """ + # no necessarily need to store the checkbox state + self.Modified() + + @smproperty.stringvector( + name="StringSingle", + label="String Single", + number_of_elements="1", + default_values="-", + panel_visibility="default", + ) + def a01StringSingle( self: Self, value: str ) -> None: + """Define an input string field. + + Args: + value (str): Input + """ + if value != self._strSingle: + self._strSingle = value + self.Modified() + + @smproperty.intvector( + name="IntSingle", + label="Int Single", + number_of_elements="1", + default_values=0, + panel_visibility="default", + ) + def a02IntSingle( self: Self, value: int ) -> None: + """Define an input int field. + + Args: + value (int): Input + """ + if value != self._intSingle: + self._intSingle = value + self.Modified() + + @smproperty.doublevector( + name="DoubleSingle", + label="Double Single", + number_of_elements="1", + default_values=0.0, + panel_visibility="default", + ) + def a03DoubleSingle( self: Self, value: float ) -> None: + """Define an input double field. + + Args: + value (float): Input + """ + if value != self._doubleSingle: + self._doubleSingle = value + self.Modified() + + @smproperty.xml( """ + + + + + """ ) + def a04ShowHideGroup( self: Self ) -> None: + """Create a group of widgets.""" + self.Modified() + + @smproperty.intvector( + name="DropDownListFromVariable", + number_of_elements=1, + label="Dropdown List From Variable", + default_values=0, + ) + @smdomain.xml( strListToEnumerationDomainXml( DROP_DOWN_LIST_ELEMENTS ) ) + def b01DropDownListFromVariable( self: Self, intValue: int ) -> None: + """Set selection from drop down list filled with variable elements. + + Args: + intValue (int): int value. + """ + if intValue != self._dropDownListSelection: + self._dropDownListSelection = intValue + self.Modified() + + @smproperty.intvector( + name="DropDownListFromEnum", + number_of_elements=1, + label="Dropdown List From Enum", + default_values=0, + ) + @smdomain.xml( strEnumToEnumerationDomainXml( DROP_DOWN_LIST_ENUM ) ) + def b02DropDownListFromEnum( self: Self, intValue: int ) -> None: + """Set selection from drop down list filled with enumeration elements. + + Args: + intValue (int): int value. + """ + if intValue != self._dropDownListSelection2: + self._dropDownListSelection2 = intValue + self.Modified() + + @smproperty.xml( """ + + + """ ) + def b03DropDownListGroup( self: Self ) -> None: + """Create a group of widgets.""" + self.Modified() + + @smproperty.stringvector( + name="SelectSingleAttribute", + label="Select Single Attribute", + number_of_elements="1", + element_types="2", + default_values="", + panel_visibility="default", + ) + @smdomain.xml( """ + + + + + + + Select a unique attribute from all the scalars cell attributes from input object. + Input object is defined by its name Input that must corresponds to the name in @smproperty.input + Attribute support is defined by input_domain_name: inputs_array (all arrays) or user defined + function from tag from filter @smdomain.xml. + Attribute type is defined by keyword `attribute_type`: Scalars or Vectors + + """ ) + def a01SelectSingleAttribute( self: Self, name: str ) -> None: + """Set selected attribute name. + + Args: + name (str): Input value + """ + if name != self._selectedAttributeSingle: + self._selectedAttributeSingle = name + self.Modified() + + @smproperty.stringvector( + name="SelectMultipleAttribute", + label="Select Multiple Attribute", + repeat_command=1, + number_of_elements_per_command="1", + element_types="2", + default_values="", + panel_visibility="default", + ) + @smdomain.xml( """ + + + + + + + Select a unique attribute from all the scalars cell attributes from input object. + Input object is defined by its name Input that must corresponds to the name in @smproperty.input + Attribute support is defined by input_domain_name: inputs_array (all arrays) or user defined + function from tag from filter @smdomain.xml. + Attribute type is defined by keyword `attribute_type`: Scalars or Vectors + + """ ) + def a02SelectMultipleAttribute( self: Self, name: str ) -> None: + """Set selected attribute name. + + Args: + name (str): Input value + """ + if self._clearSelectedAttributeMulti: + self._selectedAttributeMulti.clear() + self._clearSelectedAttributeMulti = False + self._selectedAttributeMulti.append( name ) + self.Modified() + + @smproperty.xml( """ + + + """ ) + def a05AttributeSelectionGroup( self: Self ) -> None: + """Create a group of widgets.""" + self.Modified() + + @smproperty.xml( """ + + """ ) + def d01ActionButton( self: Self ) -> None: + """Action button to reset self._initArraySelections.""" + self._initArraySelections = True + self.Modified() + + @smproperty.dataarrayselection( name="TimeSelection" ) + def d02GetSelectedTimes( self: Self ) -> vtkDataArraySelection: + """Get selected times. + + Returns: + vtkDataArraySelection: selected attribute names. + """ + return self._selectedTimes + + @smproperty.doublevector( name="ExtentSlider", + label="Extent Slider", + number_of_elements=2, + default_values=( 0.0, 0.0 ), + panel_visibility="default", + panel_widget="double_range" ) + @smdomain.xml( """ + + + + + + + + + + """ ) + def d05ExtentSlider( self: Self, mini: float, maxi: float ) -> None: + """Define a double slider. + + Args: + mini (float): Minimum + maxi (float): Maximum + """ + if mini != self._extentSlider[ 0 ]: + self._extentSlider[ 0 ] = mini + self.Modified() + if maxi != self._extentSlider[ 1 ]: + self._extentSlider[ 1 ] = maxi + self.Modified() + + # use mode="leaves" to display only leaves, or discard it to display the whole tree + @smproperty.intvector( name="CompositeDataSetIndex", + default_values=1, + number_of_elements=1, + number_of_elements_per_command=1, + panel_visibility="default", + repeat_command=1 ) + @smdomain.xml( """ + + + + + + + + """ ) + def e00SetBlockNames( self: Self, value: str ) -> None: + """Define component selector. + + Args: + value (int): Component index + """ + if self.clearBlockNames: + self._blockNames.clear() + self.clearBlockNames = False + self._blockNames.append( value ) + self.Modified() + + @smproperty.intvector( name="AttributeType", default_values=0, number_of_elements=1 ) + @smdomain.xml( """ + + + + """ ) + def e01SetFieldAssociation( self: Self, value: int ) -> None: + """Set attribute support for next attribute selector. + + Args: + value (int): Input value + """ + self.Modified() + + @smproperty.xml( """ + + + + + + + + + + This property indicates the name of the array to be extracted. + + + """ ) + def e02SelectSingleAttributeWithType( self: Self, v1: int, v2: int, v3: int, support: int, name: str ) -> None: + """Set selected attribute name. + + Args: + v1 (int): Input value 1 + v2 (int): Input value 2 + v3 (int): Input value 3 + support (int): Attribute support (point 0, cell 1, field 2) + name (str): Input value + """ + if name != self._selectedAttributeSingleType: + self._selectedAttributeSingleType = name + self.Modified() + + @smproperty.xml( """ + + + + + + + + This property indicates the component of the array to be extracted. + + + """ ) + def e04SetInputArrayComponent( self: Self, value: int ) -> None: + """Define component selector. + + Args: + value (int): Component index + """ + if value != self._componentIndex: + self._componentIndex = value + self.Modified() + + @smproperty.xml( """ + + + + + """ ) + def e05SelectorGroup( self: Self ) -> None: + """Create a group of widgets.""" + self.Modified() + + def RequestInformation( + self: Self, + request: vtkInformation, # noqa: F841 + inInfoVec: list[ vtkInformationVector ], # noqa: F841 + outInfoVec: vtkInformationVector, + ) -> int: + """Inherited from VTKPythonAlgorithmBase::RequestInformation. + + Args: + request (vtkInformation): Request + inInfoVec (list[vtkInformationVector]): Input objects + outInfoVec (vtkInformationVector): Output objects + + Returns: + int: 1 if calculation successfully ended, 0 otherwise. + """ + # init arrays only if self._initArraySelections is True + # self._initArraySelections is True only when filter is selected + # or after hitting `Reset Arrays button` + if self._initArraySelections: + executive = self.GetExecutive() # noqa: F841 + inInfo = inInfoVec[ 0 ] + self.m_timeSteps = inInfo.GetInformationObject( 0 ).Get( executive.TIME_STEPS() ) # type: ignore + for timestep in self.m_timeSteps: # type: ignore[attr-defined] + if not self._selectedTimes.ArrayExists( str( timestep ) ): + self._selectedTimes.AddArray( str( timestep ) ) + self._initArraySelections = False + return 1 + + def RequestDataObject( + self: Self, + request: vtkInformation, + inInfoVec: list[ vtkInformationVector ], + outInfoVec: vtkInformationVector, + ) -> int: + """Inherited from VTKPythonAlgorithmBase::RequestDataObject. + + Args: + request (vtkInformation): Request + inInfoVec (list[vtkInformationVector]): Input objects + outInfoVec (vtkInformationVector): Output objects + + Returns: + int: 1 if calculation successfully ended, 0 otherwise. + """ + inData1 = self.GetInputData( inInfoVec, 0, 0 ) + outData = self.GetOutputData( outInfoVec, 0 ) + assert inData1 is not None, "Input object is undefined" + if outData is None or ( not outData.IsA( inData1.GetClassName() ) ): + outData = inData1.NewInstance() + outInfoVec.GetInformationObject( 0 ).Set( outData.DATA_OBJECT(), outData ) + return super().RequestDataObject( request, inInfoVec, outInfoVec ) # type: ignore[no-any-return] + + def RequestData( + self: Self, + request: vtkInformation, # noqa: F841 + inInfoVec: list[ vtkInformationVector ], + outInfoVec: vtkInformationVector, + ) -> int: + """Inherited from VTKPythonAlgorithmBase::RequestData. + + Args: + request (vtkInformation): Request + inInfoVec (list[vtkInformationVector]): Input objects + outInfoVec (vtkInformationVector): Output objects + + Returns: + int: 1 if calculation successfully ended, 0 otherwise. + """ + input: vtkUnstructuredGrid = self.GetInputData( inInfoVec, 0, 0 ) + outData: vtkPointSet = self.GetOutputData( outInfoVec, 0 ) + + assert input is not None, "input 0 server mesh is null." + assert outData is not None, "Output pipeline is null." + + # do something... + print( f"Single String {self._strSingle}" ) + print( f"Single int {self._intSingle}" ) + print( f"Single double {self._doubleSingle}" ) + print( f"Single Attribute selection {self._selectedAttributeSingle}" ) + print( f"Single Attribute selection with type {self._selectedAttributeSingleType}" ) + print( f"Multiple Attribute selection {self._selectedAttributeMulti}" ) + + selectedTimes = getArrayChoices( self.d02GetSelectedTimes() ) + print( f"Selected times: {selectedTimes}" ) + + print( f"Bounds slider: {self._extentSlider}" ) + print( f"Attribute {self._selectedAttributeSingleType} component: {self._componentIndex}" ) + print( f"Selected Block names: {self._blockNames}" ) + self._clearSelectedAttributeMulti = True + self.clearBlockNames = True + return 1 diff --git a/geos-pv/examples/PVproxyWidgetsStatic.py b/geos-pv/examples/PVproxyWidgetsStatic.py new file mode 100644 index 00000000..18d6f6c9 --- /dev/null +++ b/geos-pv/examples/PVproxyWidgetsStatic.py @@ -0,0 +1,605 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies. +# SPDX-FileContributor: Martin Lemay +# ruff: noqa: E402 # disable Module level import not at top of file +import sys +from pathlib import Path +from typing_extensions import Self + +from vtkmodules.util.vtkAlgorithm import VTKPythonAlgorithmBase +from paraview.util.vtkAlgorithm import ( # type: ignore[import-not-found] + smdomain, smproperty, smproxy, smhint, +) +from vtkmodules.vtkCommonCore import ( + vtkInformation, + vtkInformationVector, +) +from vtkmodules.vtkCommonDataModel import ( + vtkPointSet, + vtkUnstructuredGrid, +) + +# update sys.path to load all GEOS Python Package dependencies +geos_pv_path: Path = Path( __file__ ).parent.parent +sys.path.insert( 0, str( geos_pv_path / "src" ) ) +from geos.pv.utils.config import update_paths + +update_paths() + +__doc__ = """ +Example of a Paraview plugin that defines various static input widgets. + +Static inputs do not depend on input data object or context but are entirely defined by the decorators. +See `Plugin HowTo page `_ +and `property hints documentation `_ +""" + + +@smproxy.filter( name="PVproxyWidgetsStatic", label="Static Widget Examples" ) +@smhint.xml( """""" ) +@smproperty.input( name="Input", port_index=0, label="Input" ) +@smdomain.datatype( + dataTypes=[ "vtkUnstructuredGrid" ], + composite_data_supported=True, +) +class PVproxyWidgetsStatic( VTKPythonAlgorithmBase ): + + def __init__( self: Self ) -> None: + """Map the properties of a server mesh to a client mesh.""" + super().__init__( nInputPorts=1, nOutputPorts=1, outputType="vtkUnstructuredGrid" ) + + self._strSingle: str = "" + self._strMultiline: str = "" + self._inputFilePath: str = "" + self._outputFilePath: str = "" + self._directoryPath: str = "" + self._intSingle: float = 0 + self._intMulti: list[ float ] = [ 0, 0, 0 ] + self._boolSingle: bool = False + self._doubleSingle: float = 0.0 + self._doubleMulti: list[ float ] = [ 0.0, 0.0, 0.0 ] + self._singleSliderValue: float = 0.0 + self._singleIntSliderValue: int = 0 + self._doubleSlider: list[ float ] = [ 0.0, 0.0 ] + self._color: list[ float ] = [ 0.0, 0.0, 0.0 ] + self._table: list[ tuple[ int, float, str ] ] = [] + self._clearTable: bool = True + self._dropDownListSelection: int = 0 + + @smproperty.xml( """ + + """ ) + def a00ActionButton( self: Self ) -> None: + """Action button example.""" + print( "Executes action" ) + self.Modified() + + @smproperty.stringvector( + name="StringSingle", + label="String Single", + number_of_elements="1", + default_values="-", + panel_visibility="default", + ) + def a01StringSingle( self: Self, value: str ) -> None: + """Define an input string field. + + Args: + value (str): Input + """ + if value != self._strSingle: + self._strSingle = value + self.Modified() + + +# may be used with panel_widget="calculator" to create a scriptable widget. +# +# +# This property contains the equation for computing the new +# array. +# +# + +# use syntax= to highlight text with language color + + @smproperty.stringvector( + name="StringMultiline", + label="MultiLine String", + number_of_elements="1", + default_values="-", + panel_visibility="default", + ) + @smdomain.xml( """ + + + + """ ) + def a02StringMultiLine( self: Self, value: str ) -> None: + """Define an input string field. + + Args: + value (str): Input + """ + if value != self._strMultiline: + self._strMultiline = value + self.Modified() + + @smproperty.xml( """ + + + """ ) + def a03StringInputsGroup( self: Self ) -> None: + """Create a group of widgets.""" + self.Modified() + + @smproperty.stringvector( + name="InputFilePath", + label="Input File Path", + number_of_elements="1", + default_values="Select Input .txt file...", + panel_visibility="default", + ) + @smdomain.filelist() + @smhint.filechooser( extensions=[ "txt" ], file_description="Input text file." ) + def b01InputFilePath( self: Self, value: str ) -> None: + """Define an input file path. + + Args: + value (str): Input + """ + if value != self._inputFilePath: + self._inputFilePath = value + self.Modified() + + # @smdomain.filelist() and @smhint.filechooser may be replaced by this @smdomain.xml + # @smdomain.xml(""" + # + # + # + # + # + # """) + @smproperty.stringvector( + name="OutputFilePath", + label="Output File Path", + number_of_elements="1", + default_values="Select output file...", + panel_visibility="default", + ) + @smdomain.filelist() + @smhint.filechooser( extensions=[ "txt" ], file_description="Output text file." ) + @smdomain.xml( """ + + + + """ ) + def b02OutputFilePath( self: Self, value: str ) -> None: + """Define an input file path. + + Args: + value (str): Input + """ + if value != self._outputFilePath: + self._outputFilePath = value + self.Modified() + + # @smdomain.filelist() and @smhint.filechooser may be replaced by this @smdomain.xml + # @smdomain.xml(""" + # + # + # + # + # + + @smproperty.stringvector( + name="DirectoryPath", + label="Directory Path", + number_of_elements="1", + default_values="Select a directory...", + panel_visibility="default", + ) + @smdomain.filelist() + @smhint.filechooser( extensions="", file_description="Output directory." ) + @smdomain.xml( """ + + + + """ ) + def b03DirectoryPath( self: Self, value: str ) -> None: + """Define an input string field. + + Args: + value (str): Input + """ + if value != self._directoryPath: + self._directoryPath = value + self.Modified() + + @smproperty.xml( """ + + + + """ ) + def b04FileInputsGroup( self: Self ) -> None: + """Create a group of widgets.""" + self.Modified() + + @smproperty.intvector( + name="IntSingle", + label="Int Single", + number_of_elements="1", + default_values=0, + panel_visibility="default", + ) + def c01IntSingle( self: Self, value: int ) -> None: + """Define an input int field. + + Args: + value (int): Input + """ + if value != self._intSingle: + self._intSingle = value + self.Modified() + + @smproperty.intvector( + name="IntMulti", + label="Int Multi", + number_of_elements="3", + default_values=( 0, 0, 0 ), + panel_visibility="default", + ) + def c02IntMulti( self: Self, value0: int, value1: int, value2: int ) -> None: + """Define an input int field. + + Args: + value0 (int): Input 0 + value1 (int): Input 1 + value2 (int): Input 2 + """ + if value0 != self._intMulti[ 0 ]: + self._intMulti[ 0 ] = value0 + self.Modified() + if value1 != self._intMulti[ 1 ]: + self._intMulti[ 1 ] = value1 + self.Modified() + if value2 != self._intMulti[ 2 ]: + self._intMulti[ 2 ] = value2 + self.Modified() + + @smproperty.xml( """ + + + """ ) + def c03IntInputsGroup( self: Self ) -> None: + """Create a group of widgets.""" + self.Modified() + + @smproperty.intvector( + name="BoolSingle", + label="Single Boolean Input", + default_values=0, + panel_visibility="default", + ) + @smdomain.xml( """""" ) + def c04BoolSingle( self: Self, value: bool ) -> None: + """Define boolean input. + + Args: + value (bool): Input bool. + """ + self._boolSingle = value + self.Modified() + + @smproperty.xml( """ + + """ ) + def c05BoolInputsGroup( self: Self ) -> None: + """Create a group of widgets.""" + self.Modified() + + @smproperty.doublevector( + name="DoubleSingle", + label="Double Single", + number_of_elements="1", + default_values=0.0, + panel_visibility="default", + ) + def d01DoubleSingle( self: Self, value: float ) -> None: + """Define an input double field. + + Args: + value (float): Input + """ + if value != self._doubleSingle: + self._doubleSingle = value + self.Modified() + + @smproperty.doublevector( + name="DoubleMulti", + label="Double Multi", + number_of_elements="3", + default_values=( 0.0, 0.0, 0.0 ), + panel_visibility="default", + ) + def d02DoubleMulti( self: Self, value0: float, value1: float, value2: float ) -> None: + """Define an input double field. + + Args: + value0 (float): Input 0 + value1 (float): Input 1 + value2 (float): Input 2 + """ + if value0 != self._doubleMulti[ 0 ]: + self._doubleMulti[ 0 ] = value0 + self.Modified() + if value1 != self._doubleMulti[ 1 ]: + self._doubleMulti[ 1 ] = value1 + self.Modified() + if value2 != self._doubleMulti[ 2 ]: + self._doubleMulti[ 2 ] = value2 + self.Modified() + + @smproperty.xml( """ + + + """ ) + def d03DoubleInputsGroup( self: Self ) -> None: + """Create a group of widgets.""" + self.Modified() + + @smproperty.intvector( name="SingleIntSlider", + label="Single Int Slider", + number_of_elements=1, + default_values=0.0, + panel_visibility="default", + panel_widget="range" ) + @smdomain.xml( """ + + """ ) + def d04SingleIntSlider( self: Self, value: int ) -> None: + """Define a slider. + + Args: + value (float): Input value + """ + if value != self._singleIntSliderValue: + self._singleIntSliderValue = value + self.Modified() + + @smproperty.xml( """ + + + + """ ) + def d05SingleFloatSlider( self: Self, value: float ) -> None: + """Define a slider. + + Args: + value (float): Input value + """ + if value != self._singleSliderValue: + self._singleSliderValue = value + self.Modified() + + # add or remove inside Hints tag + @smproperty.xml( """ + + + + + + + + """ ) + def d06DoubleSlider( self: Self, mini: float, maxi: float ) -> None: + """Define a double slider. + + Args: + mini (float): Minimum + maxi (float): Maximum + """ + if mini != self._doubleSlider[ 0 ]: + self._doubleSlider[ 0 ] = mini + self.Modified() + if maxi != self._doubleSlider[ 1 ]: + self._doubleSlider[ 1 ] = maxi + self.Modified() + + @smproperty.xml( """ + + + + """ ) + def d07SliderInputsGroup( self: Self ) -> None: + """Create a group of widgets.""" + self.Modified() + + @smproperty.doublevector( name="Color", + label="Color", + number_of_elements="3", + default_values=( 0.0, 0.0, 0.0 ), + panel_visibility="default", + panel_widget="color_selector_with_palette" ) + @smdomain.xml( """ + + + + """ ) + def d07Color( self: Self, value0: float, value1: float, value2: float ) -> None: + """Define an input double field. + + Args: + value0 (float): Input 0 + value1 (float): Input 1 + value2 (float): Input 2 + """ + if value0 != self._color[ 0 ]: + self._color[ 0 ] = value0 + self.Modified() + if value1 != self._color[ 1 ]: + self._color[ 1 ] = value1 + self.Modified() + if value2 != self._color[ 2 ]: + self._color[ 2 ] = value2 + self.Modified() + + @smproperty.xml( """ + + """ ) + def d08ColorInputsGroup( self: Self ) -> None: + """Create a group of widgets.""" + self.Modified() + + @smproperty.intvector( + name="DropDownList", + label="Drop Down List", + number_of_elements=1, + default_values=0, + panel_visibility="default", + ) + @smdomain.xml( """ + + + + + + """ ) + def e01DropDownList( self: Self, intValue: int ) -> None: + """Set selection from predefined drop down list. + + Args: + intValue (int): int value. + """ + if intValue != self._dropDownListSelection: + self._dropDownListSelection = intValue + self.Modified() + + @smproperty.xml( """ + + + + + + + + + + """ ) + def f01VariableTableMultiType( self: Self, intValue: int, floatValue: float, strValue: str ) -> None: + """Set multi type table with undefined size. + + Args: + intValue (int): int value. + floatValue (float): float value. + strValue (str): string value. + """ + # clear the table the first time the method is called + if self._clearTable: + self._table.clear() + self._clearTable = False + self._table.append( ( int( intValue ), float( floatValue ), str( strValue ) ) ) + self.Modified() + + def RequestDataObject( + self: Self, + request: vtkInformation, + inInfoVec: list[ vtkInformationVector ], + outInfoVec: vtkInformationVector, + ) -> int: + """Inherited from VTKPythonAlgorithmBase::RequestDataObject. + + Args: + request (vtkInformation): Request + inInfoVec (list[vtkInformationVector]): Input objects + outInfoVec (vtkInformationVector): Output objects + + Returns: + int: 1 if calculation successfully ended, 0 otherwise. + """ + inData1 = self.GetInputData( inInfoVec, 0, 0 ) + outData = self.GetOutputData( outInfoVec, 0 ) + assert inData1 is not None + if outData is None or ( not outData.IsA( inData1.GetClassName() ) ): + outData = inData1.NewInstance() + outInfoVec.GetInformationObject( 0 ).Set( outData.DATA_OBJECT(), outData ) + return super().RequestDataObject( request, inInfoVec, outInfoVec ) # type: ignore[no-any-return] + + def RequestData( + self: Self, + request: vtkInformation, # noqa: F841 + inInfoVec: list[ vtkInformationVector ], + outInfoVec: vtkInformationVector, + ) -> int: + """Inherited from VTKPythonAlgorithmBase::RequestData. + + Args: + request (vtkInformation): Request + inInfoVec (list[vtkInformationVector]): Input objects + outInfoVec (vtkInformationVector): Output objects + + Returns: + int: 1 if calculation successfully ended, 0 otherwise. + """ + input: vtkUnstructuredGrid = self.GetInputData( inInfoVec, 0, 0 ) + outData: vtkPointSet = self.GetOutputData( outInfoVec, 0 ) + + assert input is not None, "input 0 server mesh is null." + assert outData is not None, "Output pipeline is null." + + # do something... + print( f"Single String {self._strSingle}" ) + print( f"Multiline String {self._strMultiline}" ) + print( f"Input file path {self._inputFilePath}" ) + print( f"Output file path {self._outputFilePath}" ) + print( f"Directory path {self._directoryPath}" ) + print( f"Single int {self._intSingle}" ) + print( f"Multiple int {self._intMulti}" ) + print( f"Boolean {self._boolSingle}" ) + print( f"Single double {self._doubleSingle}" ) + print( f"Multiple double {self._doubleMulti}" ) + print( f"Single Slider {self._singleIntSliderValue}" ) + print( f"Single Slider {self._singleSliderValue}" ) + print( f"Double Slider {self._doubleSlider}" ) + print( f"Color {self._color}" ) + print( f"Variable table {self._table}" ) + + # set self._clearTable to True for the next time the table is updated + self._clearTable = True + return 1