-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathPVMohrCirclePlot.py
More file actions
926 lines (767 loc) · 36.5 KB
/
PVMohrCirclePlot.py
File metadata and controls
926 lines (767 loc) · 36.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright 2023-2024 TotalEnergies.
# SPDX-FileContributor: Alexandre Benedicto, Martin Lemay, Paloma Martinez
# ruff: noqa: E402 # disable Module level import not at top of file
import sys
import logging
from pathlib import Path
from enum import Enum
from typing import Any, Union, cast
from typing_extensions import Self
import numpy as np
import numpy.typing as npt
from paraview.simple import Render # type: ignore[import-not-found]
from paraview.util.vtkAlgorithm import ( # type: ignore[import-not-found]
VTKPythonAlgorithmBase, smdomain, smhint, smproperty, smproxy )
# source: https://github.com/Kitware/ParaView/blob/master/Wrapping/Python/paraview/util/vtkAlgorithm.py
from paraview.detail.loghandler import VTKHandler # type: ignore[import-not-found]
# source: https://github.com/Kitware/ParaView/blob/master/Wrapping/Python/paraview/detail/loghandler.py
from vtkmodules.vtkCommonCore import vtkDataArraySelection as vtkDAS
from vtkmodules.vtkCommonCore import vtkInformation, vtkInformationVector, vtkStringArray, vtkIntArray
from vtkmodules.vtkCommonDataModel import vtkUnstructuredGrid
# Update sys.path to load all GEOS Python Package dependencies
geos_pv_path: Path = Path( __file__ ).parent.parent.parent.parent.parent.parent
sys.path.insert( 0, str( geos_pv_path / "src" ) )
from geos.pv.utils.config import update_paths
update_paths()
from geos.geomechanics.model.MohrCircle import MohrCircle
from geos.utils.pieceEnum import Piece
from geos.utils.Logger import CountWarningHandler
from geos.utils.enumUnits import Pressure, enumerationDomainUnit
from geos.utils.GeosOutputsConstants import ( FAILURE_ENVELOPE, GeosMeshOutputsEnum )
from geos.utils.Logger import ( getLoggerHandlerType )
from geos.utils.PhysicalConstants import ( DEFAULT_FRICTION_ANGLE_DEG, DEFAULT_FRICTION_ANGLE_RAD,
DEFAULT_ROCK_COHESION )
from geos.mesh.utils.arrayHelpers import getArrayInObject
import geos.pv.utils.mohrCircles.functionsMohrCircle as mcf
import geos.pv.utils.paraviewTreatments as pvt
from geos.pv.utils.checkboxFunction import createModifiedCallback # type: ignore[attr-defined]
from geos.pv.utils.DisplayOrganizationParaview import buildNewLayoutWithPythonView
from geos.pv.pyplotUtils.matplotlibOptions import ( FontStyleEnum, FontWeightEnum, LegendLocationEnum, LineStyleEnum,
MarkerStyleEnum, OptionSelectionEnum, optionEnumToXml )
from geos.pv.utils.mohrCircles.functionsMohrCircle import StressConventionEnum
from geos.pv.utils.details import FilterCategory
__doc__ = f"""
PVMohrCirclePlot is a ParaView plugin that allows to compute and plot
Mohr's circles of selected cells and times from effective stress attribute.
Input is a vtkMultiBlockDataSet or vtkUnstructuredGrid.
This filter results in opening a new Python View window and displaying
Mohr's circle plot.
To use it:
This plugin requires the presence of a `stressEffective` attribute in the mesh. Moreover, several timesteps should also be detected.
.. Warning::
The whole ParaView pipeline will be executed for all timesteps present in the initial PVD file. Please be aware that the number of pipeline filters and timesteps should be as limited as possible. Otherwise, please consider going to get a cup of coffee.
* Load the plugin in Paraview: Tools > Manage Plugins ... > Load New ... > .../geosPythonPackages/geos-pv/src/geos/pv/plugins/post_processing/PVMohrCirclePlot
If you start from a raw GEOS output, execute the following steps before moving on.
- First, consider removing some unnecessary timesteps manually from the PVD file in order to reduce the calculation time and resources used in the following steps.
- Load the data into ParaView, then apply the `PVGeosBLockExtractAndMerge` plugin on it.
- Select the filter output that you want to consider for the Mohr's circle plot.
* Extract a few number of cells with the `ExtractSelection` ParaView Filter, then use the `MergeBlocks` ParaView Filter
* Select the resulting mesh in the pipeline
* Select the filter: Filters > { FilterCategory.GEOS_POST_PROCESSING.value } > Plot Mohr's Circle
* Select the cell Ids and time steps you want
* (Optional) Set rock cohesion and/or friction angle
* Apply
.. Note::
After a first application, select again cells and time steps to display, then
* Apply again
* Click on `Refresh Data` (you may have to click twice to refresh the Python view correctly).
To visualize the index of the cell(s) used to calculate Mohr circle, use the ParaView tool 'Find Data':
* The attribute 'ActiveCellMask' allows to select only the right cells (equal to 1).
* The attribute 'CellId' has to be used for the 'Selection Labels'.
"""
HANDLER: logging.Handler = VTKHandler()
loggerTitle: str = "Mohr Circle"
@smproxy.filter( name="PVMohrCirclePlot", label="Plot Mohr's Circles" )
@smhint.xml( f"""
<ShowInMenu category="{ FilterCategory.GEOS_POST_PROCESSING.value }"/>
<View type="PythonView"/>
""" )
@smproperty.input( name="Input", port_index=0 )
@smdomain.datatype(
dataTypes=[ "vtkUnstructuredGrid" ],
composite_data_supported=False,
)
class PVMohrCirclePlot( VTKPythonAlgorithmBase ):
def __init__( self: Self ) -> None:
"""ParaView plugin to plot Mohr's Circles of selected cells and times.
Mohr's circles are plotted using a Python View.
"""
super().__init__( nInputPorts=1,
nOutputPorts=1,
inputType="vtkUnstructuredGrid",
outputType="vtkUnstructuredGrid" )
# Create a new PythonView
self.pythonView: Any = buildNewLayoutWithPythonView()
# List of all cell ids in the mesh
self.cellIds: list[ str ] = []
# List of all time steps
self.timeSteps: npt.NDArray[ np.float64 ] = np.array( [] )
# Cell selection object
self.cellIdsDAS: vtkDAS = vtkDAS()
self.cellIdsDAS.AddObserver( 0, createModifiedCallback( self ) )
# Time steps selection object
self.timeStepsDAS: vtkDAS = vtkDAS()
self.timeStepsDAS.AddObserver( 0, createModifiedCallback( self ) )
# Requested cell ids and time steps
self.requestedCellIds: list[ str ] = []
self.requestedTimeStepsIndexes: list[ int ] = []
# List of mohr circles
self.mohrCircles: set[ MohrCircle ] = set()
# Failure envelop parameters
self.rockCohesion: float = DEFAULT_ROCK_COHESION
self.frictionAngle: float = DEFAULT_FRICTION_ANGLE_RAD
# Stress convention (Geos: negative compression, Usual: positive)
self.useGeosStressConvention: bool = True
# Curve aspect options - the same variables are set for each selected curve
self.circleIdUsed: str = ""
self.color: tuple[ float, float, float ] = ( 0.0, 0.0, 0.0 )
self.lineStyle: str = LineStyleEnum.SOLID.optionValue
self.lineWidth: float = 1.0
self.markerStyle: str = MarkerStyleEnum.NONE.optionValue
self.markerSize: float = 1.0
# Figure user choices
self.userChoices: dict[ str, Any ] = {
"xAxis": "Normal stress",
"yAxis": "Shear stress",
"stressUnit": 0,
"annotateCircles": 1,
"displayTitle": True,
"title": "Mohr's circles",
"titleStyle": FontStyleEnum.NORMAL.optionValue,
"titleWeight": FontWeightEnum.BOLD.optionValue,
"titleSize": 12,
"displayLegend": True,
"legendPosition": LegendLocationEnum.BEST.optionValue,
"legendSize": 10,
"minorticks": False,
"curvesAspect": {},
"customAxisLim": False,
"limMinX": None,
"limMaxX": None,
"limMinY": None,
"limMaxY": None,
}
# Request data processing step - incremented each time RequestUpdateExtent is called
self.requestDataStep: int = -1
self.logger = logging.getLogger( loggerTitle )
self.logger.setLevel( logging.INFO )
self.logger.addHandler( HANDLER )
self.logger.propagate = False
counter: CountWarningHandler = CountWarningHandler()
self.counter: CountWarningHandler
self.nbWarnings: int = 0
try:
self.counter = getLoggerHandlerType( type( counter ), self.logger )
self.counter.resetWarningCount()
except ValueError:
self.counter = counter
self.counter.setLevel( logging.INFO )
self.logger.addHandler( self.counter )
@smproperty.xml( """
<Property name="Refresh Data"
command="a00RefreshData"
panel_widget="command_button"/>
<Documentation>
Recompute Mohr's circles for requested time steps and cell ids.
</Documentation>
""" )
def a00RefreshData( self: Self ) -> None:
"""Reset self.requestDataStep to reload data from all time steps."""
self.requestDataStep = -1
self.logger.info( "Recomputing data for selected time steps and cell ids." )
self.Modified()
@smproperty.dataarrayselection( name="CellIdToPlot" )
def a01GetCellIdsDAS( self: Self ) -> vtkDAS:
"""Get selected cell ids to plot.
Returns:
vtkDataArraySelection: Selected cell ids.
"""
return self.cellIdsDAS
@smproperty.dataarrayselection( name="TimeStepsToPlot" )
def a02GetTimestepsToPlot( self: Self ) -> vtkDAS:
"""Get selected time steps to plot.
Returns:
vtkDataArraySelection: Selected time steps.
"""
return self.timeStepsDAS
@smproperty.xml( """<PropertyGroup label="Circle and Time Steps To Plot"
panel_visibility="default">
<Property name="CellIdToPlot"/>
<Property name="TimeStepsToPlot"/>
</PropertyGroup>""" )
def a03GroupTimesteps( self: Self ) -> None:
"""Organize groups."""
self.Modified()
@smproperty.doublevector(
name="RockCohesion",
label="Rock Cohesion (Pa)",
default_values=DEFAULT_ROCK_COHESION,
)
def b01SetCohesion( self: Self, value: float ) -> None:
"""Set rock cohesion.
Args:
value (float): Rock cohesion (Pa).
"""
self.rockCohesion = value
self.Modified()
@smproperty.doublevector(
name="FrictionAngle",
label="Friction Angle (°)",
default_values=DEFAULT_FRICTION_ANGLE_DEG,
)
def b02SetFrictionAngle( self: Self, value: float ) -> None:
"""Set friction angle.
Args:
value (float): Friction angle (°).
"""
self.frictionAngle = value * np.pi / 180.0
self.Modified()
@smproperty.xml( """<PropertyGroup label="Mohr-Coulomb Parameters"
panel_visibility="default">
<Property name="RockCohesion"/>
<Property name="FrictionAngle"/>
</PropertyGroup>""" )
def b03GroupUnit( self: Self ) -> None:
"""Organize groups."""
self.Modified()
@smproperty.intvector( name="StressUnit", label="Stress Unit", default_values=0 )
@smdomain.xml( enumerationDomainUnit( cast( Enum, Pressure ) ) )
def b04SetStressUnit( self: Self, choice: int ) -> None:
"""Set stress unit.
Args:
choice (int): Stress unit index in Pressure enum.
"""
self.userChoices[ "stressUnit" ] = choice
self.Modified()
@smproperty.intvector(
name="StressConventionForCompression",
label="Use GEOS stress Convention",
default_values=1,
)
@smdomain.xml( """<BooleanDomain name="bool"/>""" )
def b05SetStressCompressionConvention( self: Self, useGeosConvention: bool ) -> None:
"""Set stress compression convention in plots.
Args:
useGeosConvention (bool): True is Geos convention, False is usual geomechanical convention.
"""
# Specify if data is from GEOS
self.useGeosStressConvention = useGeosConvention
self.Modified()
@smproperty.intvector( name="AnnotateCircles", label="Annotate Circles", default_values=1 )
@smdomain.xml( """<BooleanDomain name="bool"/>""" )
def b06SetAnnotateCircles( self: Self, boolean: bool ) -> None:
"""Set option to add annotations to circles.
Args:
boolean (bool): True to annotate circles, False otherwise.
Default is True.
"""
self.userChoices[ "annotateCircles" ] = boolean
self.Modified()
@smproperty.intvector( name="Minorticks", label="Minorticks", default_values=0 )
@smdomain.xml( """<BooleanDomain name="bool"/>""" )
def b07SetMinorticks( self: Self, boolean: bool ) -> None:
"""Set option to display minor ticks.
Args:
boolean (bool): True to display the minor ticks, False otherwise.
Defaults is False.
"""
self.userChoices[ "minorticks" ] = boolean
self.Modified()
@smproperty.xml( """<PropertyGroup label="Properties"
panel_visibility="default">
<Property name="StressUnit"/>
<Property name="StressConventionForCompression"/>
<Property name="AnnotateCircles"/>
<Property name="Minorticks"/>
</PropertyGroup>""" )
def b08GroupUnit( self: Self ) -> None:
"""Organize groups."""
self.Modified()
@smproperty.intvector( name="ModifyTitleAndLegend", label="Modify Title And Legend", default_values=0 )
@smdomain.xml( """<BooleanDomain name="bool"/>""" )
def c00SetModifyTitleAndLegend( self: Self, boolean: bool ) -> None:
"""Set option to modify legend and title.
Args:
boolean (bool): True to modify the title and legend, False otherwise.
Defaults is False.
"""
self.userChoices[ "displayTitle" ] = boolean
self.modifyTitleAndLegend = boolean
@smproperty.stringvector( name="Title", default_values="Mohr's circle" )
def c01SetTitlePlot( self: Self, title: str ) -> None:
"""Set title.
Args:
title (str): Requested title. Defaults is "Mohr's circle".
"""
self.userChoices[ "title" ] = title
self.Modified()
@smproperty.intvector( name="Title Style", label="Title Style", default_values=0 )
@smdomain.xml( optionEnumToXml( cast( OptionSelectionEnum, FontStyleEnum ) ) )
def c02SetTitleStyle( self: Self, value: int ) -> None:
"""Set title font style.
Args:
value (int): Title font style index in FontStyleEnum.
"""
choice = list( FontStyleEnum )[ value ]
self.userChoices[ "titleStyle" ] = choice.optionValue
self.Modified()
@smproperty.intvector( name="Title Weight", label="Title Weight", default_values=1 )
@smdomain.xml( optionEnumToXml( cast( OptionSelectionEnum, FontWeightEnum ) ) )
def c03SetTitleWeight( self: Self, value: int ) -> None:
"""Set title font weight.
Args:
value (int): Title font weight index in FontWeightEnum.
"""
choice = list( FontWeightEnum )[ value ]
self.userChoices[ "titleWeight" ] = choice.optionValue
self.Modified()
@smproperty.intvector( name="Title Size", label="Title Size", default_values=12 )
@smdomain.xml( """<IntRangeDomain name="range" min="1" max="50"/>""" )
def c04SetTitleSize( self: Self, size: float ) -> None:
"""Set title font size.
Args:
size (float): Title font size between 1 and 50.
"""
self.userChoices[ "titleSize" ] = size
self.Modified()
@smproperty.xml( """<PropertyGroup label="Title Properties" panel_visibility="advanced">
<Property name="Title"/>
<Property name="Title Style"/>
<Property name="Title Weight"/>
<Property name="Title Size"/>
<Hints><PropertyWidgetDecorator type="GenericDecorator"
mode="visibility" property="ModifyTitleAndLegend"
value="1"/></Hints>
</PropertyGroup>""" )
def c06PropertyGroup( self: Self ) -> None:
"""Organize groups."""
self.Modified()
@smproperty.intvector( name="LegendPosition", label="Legend Position", default_values=0 )
@smdomain.xml( optionEnumToXml( cast( OptionSelectionEnum, LegendLocationEnum ) ) )
def d01SetLegendPosition( self: Self, value: int ) -> None:
"""Set legend position.
Args:
value (int): Legend position index in LegendLocationEnum.
"""
choice = list( LegendLocationEnum )[ value ]
self.userChoices[ "legendPosition" ] = choice.optionValue
self.Modified()
@smproperty.intvector( name="LegendSize", label="Legend Size", default_values=10 )
@smdomain.xml( """<IntRangeDomain name="range" min="1" max="50"/>""" )
def d02SetLegendSize( self: Self, size: float ) -> None:
"""Set legend font size.
Args:
size (float): Legend font size between 1 and 50.
"""
self.userChoices[ "legendSize" ] = size
self.Modified()
@smproperty.xml( """<PropertyGroup label="Legend Properties" panel_visibility="advanced">
<Property name="LegendPosition"/>
<Property name="LegendSize"/>
<Hints><PropertyWidgetDecorator type="GenericDecorator"
mode="visibility" property="ModifyTitleAndLegend"
value="1"/></Hints>
</PropertyGroup>""" )
def d03PropertyGroup( self: Self ) -> None:
"""Organize groups."""
self.Modified()
@smproperty.intvector( name="CustomAxisLim", label="Modify Axis Limits", default_values=0 )
@smdomain.xml( """<BooleanDomain name="bool"/>""" )
def e01SetCustomAxisLim( self: Self, boolean: bool ) -> None:
"""Set option to define axis limits.
Args:
boolean (bool): True to define manually the axis limits, False otherwise.
Defaults is False.
"""
self.userChoices[ "customAxisLim" ] = boolean
self.Modified()
@smproperty.doublevector( name="LimMinX", label="X min", default_values=-1e36 )
def e02LimMinX( self: Self, value: float ) -> None:
"""Set X axis min.
Args:
value (float): X axis min.
"""
value2: Union[ float, None ] = value
if value2 == -1e36:
value2 = None
self.userChoices[ "limMinX" ] = value2
self.Modified()
@smproperty.doublevector( name="LimMaxX", label="X max", default_values=1e36 )
def e03LimMaxX( self: Self, value: float ) -> None:
"""Set X axis max.
Args:
value (float): X axis max.
"""
value2: Union[ float, None ] = value
if value2 == 1e36:
value2 = None
self.userChoices[ "limMaxX" ] = value2
self.Modified()
@smproperty.doublevector( name="LimMinY", label="Y min", default_values=-1e36 )
def e04LimMinY( self: Self, value: float ) -> None:
"""Set Y axis min.
Args:
value (float): Y axis min.
"""
value2: Union[ float, None ] = value
if value2 == -1e36:
value2 = None
self.userChoices[ "limMinY" ] = value2
self.Modified()
@smproperty.doublevector( name="LimMaxY", label="Y max", default_values=1e36 )
def e05LimMaxY( self: Self, value: float ) -> None:
"""Set Y axis max.
Args:
value (float): Y axis max.
"""
value2: Union[ float, None ] = value
if value2 == 1e36:
value2 = None
self.userChoices[ "limMaxY" ] = value2
self.Modified()
@smproperty.xml( """<PropertyGroup
panel_visibility="advanced">
<Property name="LimMinX"/>
<Property name="LimMaxX"/>
<Property name="LimMinY"/>
<Property name="LimMaxY"/>
<Hints><PropertyWidgetDecorator type="GenericDecorator"
mode="visibility" property="CustomAxisLim" value="1"/></Hints>
</PropertyGroup>""" )
def e06GroupFlow( self: Self ) -> None:
"""Organized groups."""
self.Modified()
@smproperty.intvector( name="ModifyCurvesAspect", label="Modify Curves Aspect", default_values=0 )
@smdomain.xml( """<BooleanDomain name="bool"/>""" )
def f01SetModifyCurvesAspect( self: Self, boolean: bool ) -> None:
"""Set option to modify curve aspect.
Args:
boolean (bool): True to modify curve aspect, False otherwise.
Defaults is False.
"""
self.modifyCurvesAspect = boolean
@smproperty.stringvector( name="CurvesInfo", information_only="1" )
def f02GetCurveNames( self: Self ) -> list[ str ]:
"""Get curves to modify.
Returns:
list[str]: Curves to modify
"""
circleIds: list[ str ] = self._getCircleIds()
return [ FAILURE_ENVELOPE ] + circleIds
@smproperty.stringvector( name="CurveToModify", label="Curve name", number_of_elements="1" )
@smdomain.xml( """<StringListDomain name="list">
<RequiredProperties><Property name="CurvesInfo"
function="CurvesInfo"/>
</RequiredProperties>
</StringListDomain>""" )
def f03SetCellID( self: Self, value: str ) -> None:
"""Set circle ids to use.
Args:
value (str): Circle ids.
"""
self.circleIdUsed = value
self.Modified()
@smproperty.intvector( name="LineStyle", label="Line Style", default_values=1 )
@smdomain.xml( optionEnumToXml( cast( OptionSelectionEnum, LineStyleEnum ) ) )
def f04SetLineStyle( self: Self, value: int ) -> None:
"""Set line style.
Args:
value (int): Line style index in LineStyleEnum.
"""
choice = list( LineStyleEnum )[ value ]
self.lineStyle = choice.optionValue
self.Modified()
@smproperty.doublevector( name="LineWidth", default_values=1.0 )
@smdomain.xml( """<DoubleRangeDomain min="0.1" max="10.0" name="range"/>""" )
def f05SetLineWidth( self: Self, value: float ) -> None:
"""Set line width.
Args:
value (float): Line width between 1 and 10.
"""
self.lineWidth = value
self.Modified()
@smproperty.intvector( name="MarkerStyle", label="Marker Style", default_values=0 )
@smdomain.xml( optionEnumToXml( cast( OptionSelectionEnum, MarkerStyleEnum ) ) )
def f06SetMarkerStyle( self: Self, value: int ) -> None:
"""Set marker style.
Args:
value (int): Marker style index in MarkerStyleEnum.
"""
choice = list( MarkerStyleEnum )[ value ]
self.markerStyle = choice.optionValue
self.Modified()
@smproperty.doublevector( name="MarkerSize", default_values=1.0 )
@smdomain.xml( """<DoubleRangeDomain min="0.1" max="30.0" name="range"/>""" )
def f07SetMarkerSize( self: Self, value: float ) -> None:
"""Set marker size.
Args:
value (float): Size of markers between 1 and 30.
"""
self.markerSize = value
self.Modified()
@smproperty.xml( """<PropertyGroup label="Line Edition" panel_visibility="advanced">
<Property name="CurvesInfo"/>
<Property name="CurveToModify"/>
<Property name="LineStyle"/>
<Property name="LineWidth"/>
<Property name="MarkerStyle"/>
<Property name="MarkerSize"/>
<Hints><PropertyWidgetDecorator type="GenericDecorator"
mode="visibility" property="ModifyCurvesAspect"
value="1"/></Hints>
</PropertyGroup>""" )
def f08PropertyGroup( self: Self ) -> None:
"""Organize groups."""
self.Modified()
@smproperty.doublevector( name="ColorEnvelop", default_values=[ 0, 0, 0 ], number_of_elements=3 )
@smdomain.xml( """<DoubleRangeDomain max="1" min="0" name="range"/>""" )
def f09SetColor( self: Self, value0: float, value1: float, value2: float ) -> None:
"""Set envelope color.
Args:
value0 (float): Red color between 0 and 1.
value1 (float): Green color between 0 and 1.
value2 (float): Blue color between 0 and 1.
"""
self.color = ( value0, value1, value2 )
self.Modified()
@smproperty.xml( """<PropertyGroup label="" panel_widget="FontEditor"
panel_visibility="default">
<Property name="ColorEnvelop" function="Color"/>
<Hints><PropertyWidgetDecorator type="GenericDecorator"
mode="visibility" property="ModifyCurvesAspect"
value="1"/></Hints>
</PropertyGroup>""" )
def f10PropertyGroup( self: Self ) -> None:
"""Organize groups."""
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.
"""
executive = self.GetExecutive() # noqa: F841
inInfo = inInfoVec[ 0 ]
# Only at initialization step, no change later
if self.requestDataStep < 0:
# Get cell ids
inData = self.GetInputData( inInfoVec, 0, 0 )
self.cellIds = pvt.getVtkOriginalCellIds( inData, self.logger )
# Update vtkDAS
for circleId in self.cellIds:
if not self.cellIdsDAS.ArrayExists( circleId ):
self.cellIdsDAS.AddArray( circleId )
# Get the possible timesteps of execution
self.timeSteps: float = inInfo.GetInformationObject( 0 ).Get( executive.TIME_STEPS() ) # type: ignore
for timestep in self.timeSteps:
if not self.timeStepsDAS.ArrayExists( str( timestep ) ):
self.timeStepsDAS.AddArray( str( timestep ) )
return 1
def RequestUpdateExtent(
self: Self,
request: vtkInformation, # noqa: F841
inInfoVec: list[ vtkInformationVector ],
outInfoVec: vtkInformationVector,
) -> int:
"""Inherited from VTKPythonAlgorithmBase::RequestUpdateExtent.
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 ]
# Update requestDataStep
self.requestDataStep += 1
# Update time according to requestDataStep iterator
if self.requestDataStep == 0:
self._updateRequestedTimeSteps()
if self.requestDataStep < len( self.timeSteps ):
inInfo.GetInformationObject( 0 ).Set(
executive.UPDATE_TIME_STEP(), # type: ignore[no-any-return]
self.timeSteps[ self.requestDataStep ],
)
outInfoVec.GetInformationObject( 0 ).Set(
executive.UPDATE_TIME_STEP(), # type: ignore[no-any-return]
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.
"""
inData = self.GetInputData( inInfoVec, 0, 0 )
outData = self.GetOutputData( outInfoVec, 0 )
assert inData is not None
if outData is None or ( not outData.IsA( "vtkUnstructuredGrid" ) ):
outData = vtkUnstructuredGrid()
outInfoVec.GetInformationObject( 0 ).Set( outData.DATA_OBJECT(), outData ) # type: ignore
return super().RequestDataObject( request, inInfoVec, outInfoVec ) # type: ignore[no-any-return]
def RequestData(
self: Self,
request: vtkInformation, # noqa: F841
inInfoVec: list[ vtkInformationVector ], # noqa: F841
outInfoVec: vtkInformationVector, # noqa: F841
) -> 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.
"""
try:
inputMesh: vtkUnstructuredGrid = self.GetInputData( inInfoVec, 0, 0 )
executive = self.GetExecutive()
if self.requestDataStep == 1:
self.logger.info( "Computing Mohr circles for requested time steps and cell Ids." )
if self.requestDataStep < len( self.timeSteps ):
request.Set( executive.CONTINUE_EXECUTING(), 1 ) # type: ignore[no-any-return]
currentTimeStep: float = (
inInfoVec[ 0 ].GetInformationObject( 0 ).Get(
executive.UPDATE_TIME_STEP() ) # type: ignore[no-any-return]
)
if self.requestDataStep in self.requestedTimeStepsIndexes:
self.mohrCircles.update( self._createMohrCirclesAtTimeStep( inputMesh, currentTimeStep ) )
# Plot mohr circles
else:
self.logger.info( "Displaying Mohr's Circles" )
# Displayed time step, no need to go further
request.Remove( executive.CONTINUE_EXECUTING() ) # type: ignore[no-any-return]
assert self.pythonView is not None, "No Python View was found."
self._defineCurvesAspect()
mohrCircles: list[ MohrCircle ] = self._filterMohrCircles()
self.pythonView.Script = mcf.buildPythonViewScript(
geos_pv_path,
mohrCircles,
self.rockCohesion,
self.frictionAngle,
self._getUserChoices(),
)
Render()
# Cell indexes annotation
nbCells = inputMesh.GetNumberOfCells()
outputMesh: vtkUnstructuredGrid = self.GetOutputData( outInfoVec, 0 )
outputMesh.ShallowCopy( inputMesh )
cellId = vtkStringArray()
cellId.SetName( "CellId" )
cellId.SetNumberOfValues( nbCells )
activeCellMask = vtkIntArray()
activeCellMask.SetName( "ActiveCellMask" )
activeCellMask.SetNumberOfValues( nbCells )
originalCellIds = inputMesh.GetCellData().GetArray( "vtkOriginalCellIds" )
for localCellId in range( nbCells ):
if str( originalCellIds.GetValue( localCellId ) ) in self.requestedCellIds:
cellId.SetValue( localCellId, f"{ originalCellIds.GetValue( localCellId ) }" )
activeCellMask.SetValue( localCellId, 1 )
else:
activeCellMask.SetValue( localCellId, 0 )
outputMesh.GetCellData().AddArray( cellId )
outputMesh.GetCellData().AddArray( activeCellMask )
outputMesh.Modified()
result: str = f"The plugin { self.logger.name } succeeded"
if self.counter.warningCount > 0:
self.logger.warning( f"{ result } but { self.counter.warningCount } warnings have been logged." )
else:
self.logger.info( f"{ result }." )
except Exception as e:
self.logger.error( "Mohr circles cannot be plotted due to:" )
self.logger.error( e )
return 0
self.nbWarnings = self.counter.warningCount
self.counter.resetWarningCount()
return 1
def _createMohrCirclesAtTimeStep(
self: Self,
mesh: vtkUnstructuredGrid,
currentTimeStep: float,
) -> set[ MohrCircle ]:
"""Create mohr circles of all cells at the current time step.
Args:
mesh (Union[vtkUnstructuredGrid, vtkMultiBlockDataSet]): input mesh.
currentTimeStep (float): current time step
Returns:
list[MohrCircle]: list of MohrCircles for the current time step.
"""
# Get effective stress array
stressArray: npt.NDArray[ np.float64 ] = getArrayInObject( mesh,
GeosMeshOutputsEnum.AVERAGE_STRESS.attributeName,
Piece.CELLS )
# Get stress convention
stressConvention = StressConventionEnum.GEOS_STRESS_CONVENTION if self.useGeosStressConvention else StressConventionEnum.COMMON_STRESS_CONVENTION
# Get the cell IDs requested by the user
self._updateRequestedCellIds()
return mcf.createMohrCircleAtTimeStep( stressArray, self.requestedCellIds, str( currentTimeStep ),
stressConvention )
def _filterMohrCircles( self: Self ) -> list[ MohrCircle ]:
"""Filter the list of all MohrCircle to get those to plot.
Mohr circles are sorted by cell indexes first, then by timesteps.
Returns:
list[MohrCircle]: list of MohrCircle to plot.
"""
# Circle ids to plot
circleIds: list[ str ] = self._getCircleIds()
mohrCircleToPlot: list[ MohrCircle ] = [ MohrCircle( "-1" ) for i in range( len( circleIds ) ) ]
for mohrCircle in self.mohrCircles:
try:
mohrCircleToPlot[ circleIds.index( str( mohrCircle.getCircleId() ) ) ] = mohrCircle
except ValueError:
continue
return mohrCircleToPlot
def _updateRequestedTimeSteps( self: Self ) -> None:
"""Update the requestedTimeStepsIndexes attribute from user choice."""
requestedTimeSteps: list[ str ] = pvt.getArrayChoices( self.a02GetTimestepsToPlot() )
self.requestedTimeStepsIndexes = [
pvt.getTimeStepIndex( float( ts ), self.timeSteps ) for ts in requestedTimeSteps
]
def _updateRequestedCellIds( self: Self ) -> None:
"""Update the requestedCellIds attribute from user choice."""
self.requestedCellIds = pvt.getArrayChoices( self.a01GetCellIdsDAS() )
def _getUserChoices( self: Self ) -> dict[ str, Any ]:
"""Access the userChoices attribute.
Returns:
dict[str, Any] : the user choices for the figure.
"""
return self.userChoices
def _getCircleIds( self: Self ) -> list[ str ]:
"""Get circle ids to plot.
This list of circle indexes is sorted by cell indexes first, then by timesteps
Returns:
list[str]: list of circle ids to plot.
"""
cellIds: list[ str ] = pvt.getArrayChoices( self.a01GetCellIdsDAS() )
timeSteps: list[ str ] = pvt.getArrayChoices( self.a02GetTimestepsToPlot() )
return [ mcf.getMohrCircleId( cellId, timeStep ) for cellId in cellIds for timeStep in timeSteps ]
def _defineCurvesAspect( self: Self ) -> None:
"""Add curve aspect parameters according to user choices."""
self.userChoices[ "curvesAspect" ][ self.circleIdUsed ] = {
"color": self.color,
"linestyle": self.lineStyle,
"linewidth": self.lineWidth,
"marker": self.markerStyle,
"markersize": self.markerSize,
}