From 9d9ce4f6281c655239f3f7a6e5afd2fd1bd117bf Mon Sep 17 00:00:00 2001 From: crouzet Date: Mon, 8 Feb 2021 09:35:01 +0100 Subject: [PATCH 1/1] initial commit from paravisaddons --- CMakeLists.txt | 83 ++ PARAVISADDONS_version.h.in | 42 + README | 59 ++ doc/README | 4 + src/AppendAttributesOverTime/CMakeLists.txt | 49 + src/AppendAttributesOverTime/README.md | 8 + .../CMakeLists.txt | 27 + .../AppendAttributesOverTimeModule/vtk.module | 35 + .../vtkAppendAttributesOverTime.cxx | 223 ++++ .../vtkAppendAttributesOverTime.h | 103 ++ .../plugin/CMakeLists.txt | 30 + src/AppendAttributesOverTime/plugin/README.md | 8 + .../plugin/filters.xml | 33 + .../plugin/paraview.plugin | 27 + .../CMakeLists.txt | 49 + .../plugin/CMakeLists.txt | 52 + .../plugin/paraview.plugin | 29 + .../plugin/pqAutoConvertPropertiesStarter.cxx | 77 ++ .../plugin/pqAutoConvertPropertiesStarter.h | 75 ++ src/CMakeLists.txt | 77 ++ src/CellDataContour/CMakeLists.txt | 49 + src/CellDataContour/plugin/CMakeLists.txt | 31 + .../CellDataContourModule/CMakeLists.txt | 29 + .../plugin/CellDataContourModule/vtk.module | 39 + .../vtkMyCellDataToPointData.cxx | 610 +++++++++++ .../vtkMyCellDataToPointData.h | 182 ++++ .../vtkMyContourFilter.cxx | 855 ++++++++++++++++ .../vtkMyContourFilter.h | 317 ++++++ .../vtkMyPVContourFilter.cxx | 266 +++++ .../vtkMyPVContourFilter.h | 89 ++ src/CellDataContour/plugin/filters.xml | 180 ++++ src/CellDataContour/plugin/paraview.plugin | 27 + src/ComplexMode/CMakeLists.txt | 49 + src/ComplexMode/MobileMesh.xml | 93 ++ src/ComplexMode/MoveMesh.py | 62 ++ src/ComplexMode/example.med | Bin 0 -> 9475 bytes src/ComplexMode/harmo.resu.vtu | 71 ++ src/ComplexMode/plugin/CMakeLists.txt | 31 + .../plugin/ComplexModeModule/CMakeLists.txt | 31 + .../plugin/ComplexModeModule/vtk.module | 38 + .../ComplexModeModule/vtkComplexMode.cxx | 396 +++++++ .../plugin/ComplexModeModule/vtkComplexMode.h | 68 ++ src/ComplexMode/plugin/filters.xml | 106 ++ src/ComplexMode/plugin/paraview.plugin | 28 + src/ContactReader/CMakeLists.txt | 49 + src/ContactReader/plugin/CMakeLists.txt | 31 + .../plugin/ContactReaderModule/CMakeLists.txt | 27 + .../plugin/ContactReaderModule/vtk.module | 37 + .../ContactReaderModule/vtkContactReader.cxx | 225 ++++ .../ContactReaderModule/vtkContactReader.h | 52 + src/ContactReader/plugin/paraview.plugin | 28 + src/ContactReader/plugin/sources.xml | 33 + src/ContactReader/resultante_rn.rco | 16 + src/ContactReader/resultante_rn.txt | 16 + src/CustomFilters/CMakeLists.txt | 28 + src/CustomFilters/Electromagnetism.xml | 59 ++ src/CustomFilters/TemporalCSVReader.xml | 117 +++ src/CustomFilters/papbComp.cpd | 98 ++ src/CustomFilters/papbComp.xml | 105 ++ src/CustomFilters/post_R_resuir.trco | 181 ++++ src/DepthVsTime/CMakeLists.txt | 49 + src/DepthVsTime/TestCase.py | 42 + src/DepthVsTime/plugin/CMakeLists.txt | 39 + .../plugin/DepthVsTimeModule/CMakeLists.txt | 27 + .../plugin/DepthVsTimeModule/vtk.module | 32 + .../DepthVsTimeModule/vtkDepthVsTime.cxx | 681 +++++++++++++ .../plugin/DepthVsTimeModule/vtkDepthVsTime.h | 66 ++ src/DepthVsTime/plugin/filters.xml | 52 + src/DepthVsTime/plugin/paraview.plugin | 27 + src/ElectromagnetismFluxDisc/CMakeLists.txt | 49 + .../plugin/CMakeLists.txt | 33 + .../CMakeLists.txt | 27 + .../ElectromagnetismFluxDiscModule/vtk.module | 32 + .../vtkElectromagnetismFluxDisc.cxx | 413 ++++++++ .../vtkElectromagnetismFluxDisc.h | 65 ++ .../plugin/Test/CMakeLists.txt | 33 + .../plugin/Test/test_flux_disc0.py | 164 +++ .../plugin/filters.xml | 58 ++ .../plugin/paraview.plugin | 27 + src/ElectromagnetismRotation/CMakeLists.txt | 51 + .../plugin/CMakeLists.txt | 56 + .../CMakeLists.txt | 27 + .../ElectromagnetismRotationHelper.cxx | 375 +++++++ .../ElectromagnetismRotationHelper.h | 105 ++ .../MEDLoaderForPV.h | 33 + .../ElectromagnetismRotationIO/CMakeLists.txt | 34 + .../VTKMEDTraits.hxx | 81 ++ .../ElectromagnetismRotationIO/vtk.module | 29 + .../vtkElectromagnetismRotation.cxx | 454 +++++++++ .../vtkElectromagnetismRotation.h | 79 ++ .../vtkPVMetaDataInformation.cxx | 157 +++ .../vtkPVMetaDataInformation.h | 65 ++ .../plugin/ParaViewPlugin/CMakeLists.txt | 58 ++ .../Resources/ElectromagnetismRotation.xml | 75 ++ .../Resources/Icons/pqCellData16.png | Bin 0 -> 899 bytes .../Resources/Icons/pqPointData16.png | Bin 0 -> 189 bytes .../Resources/pqElectromagnetismRotation.qrc | 6 + .../pqElectroRotationAbstractFieldsWidget.cxx | 147 +++ .../pqElectroRotationAbstractFieldsWidget.h | 100 ++ .../pqElectroRotationGroupWidget.cxx | 162 +++ .../pqElectroRotationGroupWidget.h | 46 + .../plugin/paraview.plugin | 24 + .../CMakeLists.txt | 49 + .../plugin/CMakeLists.txt | 33 + .../StreamTraceurFilters/CMakeLists.txt | 27 + .../plugin/StreamTraceurFilters/vtk.module | 43 + .../vtkElectromagnetismStreamTraceur.cxx | 221 ++++ .../vtkElectromagnetismStreamTraceur.h | 124 +++ .../plugin/filters.xml | 204 ++++ .../plugin/paraview.plugin | 30 + .../scripts/generate.py | 43 + .../scripts/test_stream.py | 37 + src/ElectromagnetismVecteur/CMakeLists.txt | 49 + .../plugin/CMakeLists.txt | 31 + .../plugin/VecteurFilters/CMakeLists.txt | 27 + .../plugin/VecteurFilters/vtk.module | 42 + .../vtkElectromagnetismVecteur.cxx | 110 ++ .../vtkElectromagnetismVecteur.h | 86 ++ .../plugin/filters.xml | 163 +++ .../plugin/paraview.plugin | 27 + .../scripts/generate.py | 39 + src/ExtractComponentsPlugin/CMakeLists.txt | 49 + src/ExtractComponentsPlugin/ExtractCompo.med | Bin 0 -> 21390 bytes .../plugin/CMakeLists.txt | 49 + .../ExtractComponentsModule/CMakeLists.txt | 28 + .../plugin/ExtractComponentsModule/vtk.module | 33 + .../vtkExtractComponents.cxx | 206 ++++ .../vtkExtractComponents.h | 80 ++ .../vtkSMMyNumberOfComponentsDomain.cxx | 198 ++++ .../vtkSMMyNumberOfComponentsDomain.h | 85 ++ .../plugin/filters.xml | 80 ++ .../plugin/paraview.plugin | 29 + .../plugin/pqLinkedLineEdit.cxx | 151 +++ .../plugin/pqLinkedLineEdit.h | 87 ++ src/ExtractThreeD/CMakeLists.txt | 49 + src/ExtractThreeD/plugin/CMakeLists.txt | 65 ++ .../plugin/ExtractThreeDModule/CMakeLists.txt | 35 + .../plugin/ExtractThreeDModule/vtk.module | 37 + .../ExtractThreeDModule/vtkExtractThreeD.cxx | 231 +++++ .../ExtractThreeDModule/vtkExtractThreeD.h | 53 + src/ExtractThreeD/plugin/filters.xml | 21 + src/ExtractThreeD/plugin/paraview.plugin | 27 + src/GlyphCIH/CMakeLists.txt | 49 + src/GlyphCIH/plugin/CMakeLists.txt | 31 + .../plugin/GlyphCIHFilters/CMakeLists.txt | 27 + .../plugin/GlyphCIHFilters/vtk.module | 43 + .../plugin/GlyphCIHFilters/vtkGlyphCIH.cxx | 379 +++++++ .../plugin/GlyphCIHFilters/vtkGlyphCIH.h | 69 ++ src/GlyphCIH/plugin/filters.xml | 64 ++ src/GlyphCIH/plugin/paraview.plugin | 27 + src/MoveZCote/CMakeLists.txt | 49 + src/MoveZCote/MobileMesh.xml | 93 ++ src/MoveZCote/MoveMesh.py | 62 ++ src/MoveZCote/plugin/CMakeLists.txt | 30 + .../plugin/MoveZCoteModule/CMakeLists.txt | 27 + .../plugin/MoveZCoteModule/vtk.module | 31 + .../plugin/MoveZCoteModule/vtkMoveZCote.cxx | 241 +++++ .../plugin/MoveZCoteModule/vtkMoveZCote.h | 50 + src/MoveZCote/plugin/filters.xml | 24 + src/MoveZCote/plugin/paraview.plugin | 27 + src/ProbePointOverTime/CMakeLists.txt | 23 + src/ProbePointOverTime/HowtoTest.txt | 3 + .../ProbePointOverTimePlugin.xml | 304 ++++++ src/ProbePointOverTime/example.med | Bin 0 -> 29242 bytes src/QuadraticToLinear/CMakeLists.txt | 49 + src/QuadraticToLinear/TestCase.py | 80 ++ src/QuadraticToLinear/plugin/CMakeLists.txt | 31 + .../QuadraticToLinearModule/CMakeLists.txt | 27 + .../plugin/QuadraticToLinearModule/vtk.module | 32 + .../vtkQuadraticToLinear.cxx | 402 ++++++++ .../vtkQuadraticToLinear.h | 50 + src/QuadraticToLinear/plugin/filters.xml | 21 + src/QuadraticToLinear/plugin/paraview.plugin | 27 + src/RateOfFlowThroughSection/CMakeLists.txt | 51 + .../plugin/CMakeLists.txt | 60 ++ .../CMakeLists.txt | 38 + .../VTKMEDTraits.hxx | 81 ++ .../VTKToMEDMem.cxx | 962 ++++++++++++++++++ .../VTKToMEDMem.h | 89 ++ .../RateOfFlowThroughSectionModule/vtk.module | 41 + .../vtkExplodePolyLine.cxx | 156 +++ .../vtkExplodePolyLine.h | 82 ++ .../vtkRateOfFlowThroughSection.cxx | 567 +++++++++++ .../vtkRateOfFlowThroughSection.h | 133 +++ .../vtkSedimentDeposit.cxx | 632 ++++++++++++ .../vtkSedimentDeposit.h | 131 +++ .../plugin/filters.xml | 99 ++ .../plugin/paraview.plugin | 29 + .../script/TestCase.py | 42 + .../script/calcul_3.py | 140 +++ .../script/calcul_sediment_deposit.py | 138 +++ .../script/test_sediment_deposit.py | 224 ++++ src/RosetteCIH/CMakeLists.txt | 49 + src/RosetteCIH/plugin/CMakeLists.txt | 31 + .../plugin/RosetteCIHFilters/CMakeLists.txt | 27 + .../plugin/RosetteCIHFilters/vtk.module | 42 + .../RosetteCIHFilters/vtkRosetteCIH.cxx | 523 ++++++++++ .../plugin/RosetteCIHFilters/vtkRosetteCIH.h | 93 ++ src/RosetteCIH/plugin/filters.xml | 54 + src/RosetteCIH/plugin/paraview.plugin | 27 + src/SerafinReader/CMakeLists.txt | 49 + src/SerafinReader/geo_TW.slf | Bin 0 -> 397 bytes src/SerafinReader/plugin/CMakeLists.txt | 31 + .../plugin/SerafinReaderModule/CMakeLists.txt | 27 + .../plugin/SerafinReaderModule/FFileReader.h | 195 ++++ .../SerafinReaderModule/stdSerafinReader.h | 443 ++++++++ .../plugin/SerafinReaderModule/vtk.module | 37 + .../SerafinReaderModule/vtkSerafinReader.cxx | 666 ++++++++++++ .../SerafinReaderModule/vtkSerafinReader.h | 124 +++ src/SerafinReader/plugin/paraview.plugin | 27 + src/SerafinReader/plugin/sources.xml | 58 ++ src/SinusXReader/CMakeLists.txt | 49 + src/SinusXReader/LigneDEau.sx | 32 + src/SinusXReader/plugin/CMakeLists.txt | 31 + .../plugin/SinusXReaderModule/CMakeLists.txt | 27 + .../plugin/SinusXReaderModule/vtk.module | 31 + .../SinusXReaderModule/vtkSinusXReader.cxx | 276 +++++ .../SinusXReaderModule/vtkSinusXReader.h | 53 + src/SinusXReader/plugin/paraview.plugin | 27 + src/SinusXReader/plugin/sources.xml | 24 + src/SpatialPfl/CMakeLists.txt | 49 + src/SpatialPfl/TestCase.py | 42 + src/SpatialPfl/plugin/CMakeLists.txt | 33 + .../plugin/SpatialPflModule/CMakeLists.txt | 27 + .../plugin/SpatialPflModule/vtk.module | 32 + .../plugin/SpatialPflModule/vtkSpatialPfl.cxx | 576 +++++++++++ .../plugin/SpatialPflModule/vtkSpatialPfl.h | 74 ++ src/SpatialPfl/plugin/filters.xml | 143 +++ src/SpatialPfl/plugin/paraview.plugin | 30 + src/SphereAlongLines/CMakeLists.txt | 49 + src/SphereAlongLines/Test/test_dev_surface.py | 161 +++ .../Test/test_dev_surface2.py | 169 +++ .../Test/test_dev_surface3.py | 165 +++ src/SphereAlongLines/plugin/CMakeLists.txt | 31 + .../SphereAlongLinesModule/CMakeLists.txt | 27 + .../plugin/SphereAlongLinesModule/vtk.module | 31 + .../vtkSphereAlongLines.cxx | 714 +++++++++++++ .../vtkSphereAlongLines.h | 56 + src/SphereAlongLines/plugin/filters.xml | 51 + src/SphereAlongLines/plugin/paraview.plugin | 27 + src/TemporalOnPoint/CMakeLists.txt | 49 + src/TemporalOnPoint/TestCase.py | 42 + src/TemporalOnPoint/plugin/CMakeLists.txt | 33 + .../TemporalOnPointModule/CMakeLists.txt | 27 + .../plugin/TemporalOnPointModule/vtk.module | 31 + .../vtkTemporalOnPoint.cxx | 586 +++++++++++ .../vtkTemporalOnPoint.h | 60 ++ src/TemporalOnPoint/plugin/filters.xml | 106 ++ src/TemporalOnPoint/plugin/paraview.plugin | 27 + src/Tools/Clean.py | 29 + src/TorseurCIH/CMakeLists.txt | 49 + src/TorseurCIH/Test.py | 122 +++ src/TorseurCIH/plugin/CMakeLists.txt | 65 ++ .../plugin/TorseurCIHModule/CMakeLists.txt | 39 + .../plugin/TorseurCIHModule/vtk.module | 33 + .../plugin/TorseurCIHModule/vtkTorseurCIH.cxx | 886 ++++++++++++++++ .../plugin/TorseurCIHModule/vtkTorseurCIH.h | 49 + src/TorseurCIH/plugin/filters.xml | 24 + src/TorseurCIH/plugin/paraview.plugin | 28 + src/TorseurCIH/script/Test.py | 122 +++ src/TorseurCIH/script/slice.med | Bin 0 -> 330141 bytes src/TorseurCIH/slice.med | Bin 0 -> 330141 bytes src/View/CMakeLists.txt | 73 ++ src/View/MyDisplay.cxx | 36 + src/View/MyDisplay.h | 38 + src/View/MyView.cxx | 116 +++ src/View/MyView.h | 71 ++ src/View/MyViewActiveOptions.cxx | 117 +++ src/View/MyViewActiveOptions.h | 86 ++ src/View/MyViewOptions.cxx | 111 ++ src/View/MyViewOptions.h | 92 ++ src/View/MyViewSM.xml | 38 + .../CMakeLists.txt | 49 + .../plugin/CMakeLists.txt | 39 + .../CMakeLists.txt | 27 + .../vtk.module | 31 + .../vtkXYChartRepresentationColumns.cxx | 53 + .../vtkXYChartRepresentationColumns.h | 64 ++ .../plugin/paraview.plugin | 25 + .../plugin/views.xml | 327 ++++++ src/ZJFilter/CMakeLists.txt | 49 + src/ZJFilter/TestCase.py | 20 + src/ZJFilter/plugin/CMakeLists.txt | 65 ++ .../plugin/ZJFilterModule/CMakeLists.txt | 35 + src/ZJFilter/plugin/ZJFilterModule/vtk.module | 34 + .../plugin/ZJFilterModule/vtkZJFilter.cxx | 574 +++++++++++ .../plugin/ZJFilterModule/vtkZJFilter.h | 52 + src/ZJFilter/plugin/filters.xml | 21 + src/ZJFilter/plugin/paraview.plugin | 28 + 289 files changed, 29182 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 PARAVISADDONS_version.h.in create mode 100644 README create mode 100644 doc/README create mode 100644 src/AppendAttributesOverTime/CMakeLists.txt create mode 100644 src/AppendAttributesOverTime/README.md create mode 100644 src/AppendAttributesOverTime/plugin/AppendAttributesOverTimeModule/CMakeLists.txt create mode 100644 src/AppendAttributesOverTime/plugin/AppendAttributesOverTimeModule/vtk.module create mode 100644 src/AppendAttributesOverTime/plugin/AppendAttributesOverTimeModule/vtkAppendAttributesOverTime.cxx create mode 100644 src/AppendAttributesOverTime/plugin/AppendAttributesOverTimeModule/vtkAppendAttributesOverTime.h create mode 100644 src/AppendAttributesOverTime/plugin/CMakeLists.txt create mode 100644 src/AppendAttributesOverTime/plugin/README.md create mode 100644 src/AppendAttributesOverTime/plugin/filters.xml create mode 100644 src/AppendAttributesOverTime/plugin/paraview.plugin create mode 100644 src/AutoConvertPropertiesPlugin/CMakeLists.txt create mode 100644 src/AutoConvertPropertiesPlugin/plugin/CMakeLists.txt create mode 100644 src/AutoConvertPropertiesPlugin/plugin/paraview.plugin create mode 100644 src/AutoConvertPropertiesPlugin/plugin/pqAutoConvertPropertiesStarter.cxx create mode 100644 src/AutoConvertPropertiesPlugin/plugin/pqAutoConvertPropertiesStarter.h create mode 100644 src/CMakeLists.txt create mode 100644 src/CellDataContour/CMakeLists.txt create mode 100644 src/CellDataContour/plugin/CMakeLists.txt create mode 100644 src/CellDataContour/plugin/CellDataContourModule/CMakeLists.txt create mode 100644 src/CellDataContour/plugin/CellDataContourModule/vtk.module create mode 100644 src/CellDataContour/plugin/CellDataContourModule/vtkMyCellDataToPointData.cxx create mode 100644 src/CellDataContour/plugin/CellDataContourModule/vtkMyCellDataToPointData.h create mode 100644 src/CellDataContour/plugin/CellDataContourModule/vtkMyContourFilter.cxx create mode 100644 src/CellDataContour/plugin/CellDataContourModule/vtkMyContourFilter.h create mode 100644 src/CellDataContour/plugin/CellDataContourModule/vtkMyPVContourFilter.cxx create mode 100644 src/CellDataContour/plugin/CellDataContourModule/vtkMyPVContourFilter.h create mode 100644 src/CellDataContour/plugin/filters.xml create mode 100644 src/CellDataContour/plugin/paraview.plugin create mode 100644 src/ComplexMode/CMakeLists.txt create mode 100644 src/ComplexMode/MobileMesh.xml create mode 100644 src/ComplexMode/MoveMesh.py create mode 100644 src/ComplexMode/example.med create mode 100644 src/ComplexMode/harmo.resu.vtu create mode 100644 src/ComplexMode/plugin/CMakeLists.txt create mode 100644 src/ComplexMode/plugin/ComplexModeModule/CMakeLists.txt create mode 100644 src/ComplexMode/plugin/ComplexModeModule/vtk.module create mode 100644 src/ComplexMode/plugin/ComplexModeModule/vtkComplexMode.cxx create mode 100644 src/ComplexMode/plugin/ComplexModeModule/vtkComplexMode.h create mode 100644 src/ComplexMode/plugin/filters.xml create mode 100644 src/ComplexMode/plugin/paraview.plugin create mode 100644 src/ContactReader/CMakeLists.txt create mode 100644 src/ContactReader/plugin/CMakeLists.txt create mode 100644 src/ContactReader/plugin/ContactReaderModule/CMakeLists.txt create mode 100644 src/ContactReader/plugin/ContactReaderModule/vtk.module create mode 100644 src/ContactReader/plugin/ContactReaderModule/vtkContactReader.cxx create mode 100644 src/ContactReader/plugin/ContactReaderModule/vtkContactReader.h create mode 100644 src/ContactReader/plugin/paraview.plugin create mode 100644 src/ContactReader/plugin/sources.xml create mode 100644 src/ContactReader/resultante_rn.rco create mode 100644 src/ContactReader/resultante_rn.txt create mode 100644 src/CustomFilters/CMakeLists.txt create mode 100644 src/CustomFilters/Electromagnetism.xml create mode 100644 src/CustomFilters/TemporalCSVReader.xml create mode 100644 src/CustomFilters/papbComp.cpd create mode 100644 src/CustomFilters/papbComp.xml create mode 100644 src/CustomFilters/post_R_resuir.trco create mode 100644 src/DepthVsTime/CMakeLists.txt create mode 100644 src/DepthVsTime/TestCase.py create mode 100644 src/DepthVsTime/plugin/CMakeLists.txt create mode 100644 src/DepthVsTime/plugin/DepthVsTimeModule/CMakeLists.txt create mode 100644 src/DepthVsTime/plugin/DepthVsTimeModule/vtk.module create mode 100644 src/DepthVsTime/plugin/DepthVsTimeModule/vtkDepthVsTime.cxx create mode 100644 src/DepthVsTime/plugin/DepthVsTimeModule/vtkDepthVsTime.h create mode 100644 src/DepthVsTime/plugin/filters.xml create mode 100644 src/DepthVsTime/plugin/paraview.plugin create mode 100644 src/ElectromagnetismFluxDisc/CMakeLists.txt create mode 100644 src/ElectromagnetismFluxDisc/plugin/CMakeLists.txt create mode 100644 src/ElectromagnetismFluxDisc/plugin/ElectromagnetismFluxDiscModule/CMakeLists.txt create mode 100644 src/ElectromagnetismFluxDisc/plugin/ElectromagnetismFluxDiscModule/vtk.module create mode 100644 src/ElectromagnetismFluxDisc/plugin/ElectromagnetismFluxDiscModule/vtkElectromagnetismFluxDisc.cxx create mode 100644 src/ElectromagnetismFluxDisc/plugin/ElectromagnetismFluxDiscModule/vtkElectromagnetismFluxDisc.h create mode 100644 src/ElectromagnetismFluxDisc/plugin/Test/CMakeLists.txt create mode 100644 src/ElectromagnetismFluxDisc/plugin/Test/test_flux_disc0.py create mode 100644 src/ElectromagnetismFluxDisc/plugin/filters.xml create mode 100644 src/ElectromagnetismFluxDisc/plugin/paraview.plugin create mode 100644 src/ElectromagnetismRotation/CMakeLists.txt create mode 100644 src/ElectromagnetismRotation/plugin/CMakeLists.txt create mode 100644 src/ElectromagnetismRotation/plugin/ElectromagnetismRotationHelper/CMakeLists.txt create mode 100644 src/ElectromagnetismRotation/plugin/ElectromagnetismRotationHelper/ElectromagnetismRotationHelper.cxx create mode 100644 src/ElectromagnetismRotation/plugin/ElectromagnetismRotationHelper/ElectromagnetismRotationHelper.h create mode 100644 src/ElectromagnetismRotation/plugin/ElectromagnetismRotationHelper/MEDLoaderForPV.h create mode 100644 src/ElectromagnetismRotation/plugin/ElectromagnetismRotationIO/CMakeLists.txt create mode 100644 src/ElectromagnetismRotation/plugin/ElectromagnetismRotationIO/VTKMEDTraits.hxx create mode 100644 src/ElectromagnetismRotation/plugin/ElectromagnetismRotationIO/vtk.module create mode 100644 src/ElectromagnetismRotation/plugin/ElectromagnetismRotationIO/vtkElectromagnetismRotation.cxx create mode 100644 src/ElectromagnetismRotation/plugin/ElectromagnetismRotationIO/vtkElectromagnetismRotation.h create mode 100644 src/ElectromagnetismRotation/plugin/ElectromagnetismRotationIO/vtkPVMetaDataInformation.cxx create mode 100644 src/ElectromagnetismRotation/plugin/ElectromagnetismRotationIO/vtkPVMetaDataInformation.h create mode 100644 src/ElectromagnetismRotation/plugin/ParaViewPlugin/CMakeLists.txt create mode 100644 src/ElectromagnetismRotation/plugin/ParaViewPlugin/Resources/ElectromagnetismRotation.xml create mode 100644 src/ElectromagnetismRotation/plugin/ParaViewPlugin/Resources/Icons/pqCellData16.png create mode 100644 src/ElectromagnetismRotation/plugin/ParaViewPlugin/Resources/Icons/pqPointData16.png create mode 100644 src/ElectromagnetismRotation/plugin/ParaViewPlugin/Resources/pqElectromagnetismRotation.qrc create mode 100644 src/ElectromagnetismRotation/plugin/ParaViewPlugin/pqElectroRotationAbstractFieldsWidget.cxx create mode 100644 src/ElectromagnetismRotation/plugin/ParaViewPlugin/pqElectroRotationAbstractFieldsWidget.h create mode 100644 src/ElectromagnetismRotation/plugin/ParaViewPlugin/pqElectroRotationGroupWidget.cxx create mode 100644 src/ElectromagnetismRotation/plugin/ParaViewPlugin/pqElectroRotationGroupWidget.h create mode 100644 src/ElectromagnetismRotation/plugin/paraview.plugin create mode 100644 src/ElectromagnetismStreamTraceur/CMakeLists.txt create mode 100644 src/ElectromagnetismStreamTraceur/plugin/CMakeLists.txt create mode 100644 src/ElectromagnetismStreamTraceur/plugin/StreamTraceurFilters/CMakeLists.txt create mode 100644 src/ElectromagnetismStreamTraceur/plugin/StreamTraceurFilters/vtk.module create mode 100644 src/ElectromagnetismStreamTraceur/plugin/StreamTraceurFilters/vtkElectromagnetismStreamTraceur.cxx create mode 100644 src/ElectromagnetismStreamTraceur/plugin/StreamTraceurFilters/vtkElectromagnetismStreamTraceur.h create mode 100644 src/ElectromagnetismStreamTraceur/plugin/filters.xml create mode 100644 src/ElectromagnetismStreamTraceur/plugin/paraview.plugin create mode 100644 src/ElectromagnetismStreamTraceur/scripts/generate.py create mode 100644 src/ElectromagnetismStreamTraceur/scripts/test_stream.py create mode 100644 src/ElectromagnetismVecteur/CMakeLists.txt create mode 100644 src/ElectromagnetismVecteur/plugin/CMakeLists.txt create mode 100644 src/ElectromagnetismVecteur/plugin/VecteurFilters/CMakeLists.txt create mode 100644 src/ElectromagnetismVecteur/plugin/VecteurFilters/vtk.module create mode 100644 src/ElectromagnetismVecteur/plugin/VecteurFilters/vtkElectromagnetismVecteur.cxx create mode 100644 src/ElectromagnetismVecteur/plugin/VecteurFilters/vtkElectromagnetismVecteur.h create mode 100644 src/ElectromagnetismVecteur/plugin/filters.xml create mode 100644 src/ElectromagnetismVecteur/plugin/paraview.plugin create mode 100644 src/ElectromagnetismVecteur/scripts/generate.py create mode 100644 src/ExtractComponentsPlugin/CMakeLists.txt create mode 100644 src/ExtractComponentsPlugin/ExtractCompo.med create mode 100644 src/ExtractComponentsPlugin/plugin/CMakeLists.txt create mode 100644 src/ExtractComponentsPlugin/plugin/ExtractComponentsModule/CMakeLists.txt create mode 100644 src/ExtractComponentsPlugin/plugin/ExtractComponentsModule/vtk.module create mode 100644 src/ExtractComponentsPlugin/plugin/ExtractComponentsModule/vtkExtractComponents.cxx create mode 100644 src/ExtractComponentsPlugin/plugin/ExtractComponentsModule/vtkExtractComponents.h create mode 100644 src/ExtractComponentsPlugin/plugin/ExtractComponentsModule/vtkSMMyNumberOfComponentsDomain.cxx create mode 100644 src/ExtractComponentsPlugin/plugin/ExtractComponentsModule/vtkSMMyNumberOfComponentsDomain.h create mode 100644 src/ExtractComponentsPlugin/plugin/filters.xml create mode 100644 src/ExtractComponentsPlugin/plugin/paraview.plugin create mode 100644 src/ExtractComponentsPlugin/plugin/pqLinkedLineEdit.cxx create mode 100644 src/ExtractComponentsPlugin/plugin/pqLinkedLineEdit.h create mode 100644 src/ExtractThreeD/CMakeLists.txt create mode 100644 src/ExtractThreeD/plugin/CMakeLists.txt create mode 100644 src/ExtractThreeD/plugin/ExtractThreeDModule/CMakeLists.txt create mode 100644 src/ExtractThreeD/plugin/ExtractThreeDModule/vtk.module create mode 100644 src/ExtractThreeD/plugin/ExtractThreeDModule/vtkExtractThreeD.cxx create mode 100644 src/ExtractThreeD/plugin/ExtractThreeDModule/vtkExtractThreeD.h create mode 100644 src/ExtractThreeD/plugin/filters.xml create mode 100644 src/ExtractThreeD/plugin/paraview.plugin create mode 100644 src/GlyphCIH/CMakeLists.txt create mode 100644 src/GlyphCIH/plugin/CMakeLists.txt create mode 100644 src/GlyphCIH/plugin/GlyphCIHFilters/CMakeLists.txt create mode 100644 src/GlyphCIH/plugin/GlyphCIHFilters/vtk.module create mode 100644 src/GlyphCIH/plugin/GlyphCIHFilters/vtkGlyphCIH.cxx create mode 100644 src/GlyphCIH/plugin/GlyphCIHFilters/vtkGlyphCIH.h create mode 100644 src/GlyphCIH/plugin/filters.xml create mode 100644 src/GlyphCIH/plugin/paraview.plugin create mode 100644 src/MoveZCote/CMakeLists.txt create mode 100644 src/MoveZCote/MobileMesh.xml create mode 100644 src/MoveZCote/MoveMesh.py create mode 100644 src/MoveZCote/plugin/CMakeLists.txt create mode 100644 src/MoveZCote/plugin/MoveZCoteModule/CMakeLists.txt create mode 100644 src/MoveZCote/plugin/MoveZCoteModule/vtk.module create mode 100644 src/MoveZCote/plugin/MoveZCoteModule/vtkMoveZCote.cxx create mode 100644 src/MoveZCote/plugin/MoveZCoteModule/vtkMoveZCote.h create mode 100644 src/MoveZCote/plugin/filters.xml create mode 100644 src/MoveZCote/plugin/paraview.plugin create mode 100644 src/ProbePointOverTime/CMakeLists.txt create mode 100644 src/ProbePointOverTime/HowtoTest.txt create mode 100644 src/ProbePointOverTime/ProbePointOverTimePlugin.xml create mode 100644 src/ProbePointOverTime/example.med create mode 100644 src/QuadraticToLinear/CMakeLists.txt create mode 100644 src/QuadraticToLinear/TestCase.py create mode 100644 src/QuadraticToLinear/plugin/CMakeLists.txt create mode 100644 src/QuadraticToLinear/plugin/QuadraticToLinearModule/CMakeLists.txt create mode 100644 src/QuadraticToLinear/plugin/QuadraticToLinearModule/vtk.module create mode 100644 src/QuadraticToLinear/plugin/QuadraticToLinearModule/vtkQuadraticToLinear.cxx create mode 100644 src/QuadraticToLinear/plugin/QuadraticToLinearModule/vtkQuadraticToLinear.h create mode 100644 src/QuadraticToLinear/plugin/filters.xml create mode 100644 src/QuadraticToLinear/plugin/paraview.plugin create mode 100644 src/RateOfFlowThroughSection/CMakeLists.txt create mode 100644 src/RateOfFlowThroughSection/plugin/CMakeLists.txt create mode 100644 src/RateOfFlowThroughSection/plugin/RateOfFlowThroughSectionModule/CMakeLists.txt create mode 100644 src/RateOfFlowThroughSection/plugin/RateOfFlowThroughSectionModule/VTKMEDTraits.hxx create mode 100644 src/RateOfFlowThroughSection/plugin/RateOfFlowThroughSectionModule/VTKToMEDMem.cxx create mode 100644 src/RateOfFlowThroughSection/plugin/RateOfFlowThroughSectionModule/VTKToMEDMem.h create mode 100644 src/RateOfFlowThroughSection/plugin/RateOfFlowThroughSectionModule/vtk.module create mode 100644 src/RateOfFlowThroughSection/plugin/RateOfFlowThroughSectionModule/vtkExplodePolyLine.cxx create mode 100644 src/RateOfFlowThroughSection/plugin/RateOfFlowThroughSectionModule/vtkExplodePolyLine.h create mode 100644 src/RateOfFlowThroughSection/plugin/RateOfFlowThroughSectionModule/vtkRateOfFlowThroughSection.cxx create mode 100644 src/RateOfFlowThroughSection/plugin/RateOfFlowThroughSectionModule/vtkRateOfFlowThroughSection.h create mode 100644 src/RateOfFlowThroughSection/plugin/RateOfFlowThroughSectionModule/vtkSedimentDeposit.cxx create mode 100644 src/RateOfFlowThroughSection/plugin/RateOfFlowThroughSectionModule/vtkSedimentDeposit.h create mode 100644 src/RateOfFlowThroughSection/plugin/filters.xml create mode 100644 src/RateOfFlowThroughSection/plugin/paraview.plugin create mode 100644 src/RateOfFlowThroughSection/script/TestCase.py create mode 100644 src/RateOfFlowThroughSection/script/calcul_3.py create mode 100644 src/RateOfFlowThroughSection/script/calcul_sediment_deposit.py create mode 100644 src/RateOfFlowThroughSection/script/test_sediment_deposit.py create mode 100644 src/RosetteCIH/CMakeLists.txt create mode 100644 src/RosetteCIH/plugin/CMakeLists.txt create mode 100644 src/RosetteCIH/plugin/RosetteCIHFilters/CMakeLists.txt create mode 100644 src/RosetteCIH/plugin/RosetteCIHFilters/vtk.module create mode 100644 src/RosetteCIH/plugin/RosetteCIHFilters/vtkRosetteCIH.cxx create mode 100644 src/RosetteCIH/plugin/RosetteCIHFilters/vtkRosetteCIH.h create mode 100644 src/RosetteCIH/plugin/filters.xml create mode 100644 src/RosetteCIH/plugin/paraview.plugin create mode 100644 src/SerafinReader/CMakeLists.txt create mode 100644 src/SerafinReader/geo_TW.slf create mode 100644 src/SerafinReader/plugin/CMakeLists.txt create mode 100644 src/SerafinReader/plugin/SerafinReaderModule/CMakeLists.txt create mode 100644 src/SerafinReader/plugin/SerafinReaderModule/FFileReader.h create mode 100644 src/SerafinReader/plugin/SerafinReaderModule/stdSerafinReader.h create mode 100644 src/SerafinReader/plugin/SerafinReaderModule/vtk.module create mode 100644 src/SerafinReader/plugin/SerafinReaderModule/vtkSerafinReader.cxx create mode 100644 src/SerafinReader/plugin/SerafinReaderModule/vtkSerafinReader.h create mode 100644 src/SerafinReader/plugin/paraview.plugin create mode 100644 src/SerafinReader/plugin/sources.xml create mode 100644 src/SinusXReader/CMakeLists.txt create mode 100644 src/SinusXReader/LigneDEau.sx create mode 100644 src/SinusXReader/plugin/CMakeLists.txt create mode 100644 src/SinusXReader/plugin/SinusXReaderModule/CMakeLists.txt create mode 100644 src/SinusXReader/plugin/SinusXReaderModule/vtk.module create mode 100644 src/SinusXReader/plugin/SinusXReaderModule/vtkSinusXReader.cxx create mode 100644 src/SinusXReader/plugin/SinusXReaderModule/vtkSinusXReader.h create mode 100644 src/SinusXReader/plugin/paraview.plugin create mode 100644 src/SinusXReader/plugin/sources.xml create mode 100644 src/SpatialPfl/CMakeLists.txt create mode 100644 src/SpatialPfl/TestCase.py create mode 100644 src/SpatialPfl/plugin/CMakeLists.txt create mode 100644 src/SpatialPfl/plugin/SpatialPflModule/CMakeLists.txt create mode 100644 src/SpatialPfl/plugin/SpatialPflModule/vtk.module create mode 100644 src/SpatialPfl/plugin/SpatialPflModule/vtkSpatialPfl.cxx create mode 100644 src/SpatialPfl/plugin/SpatialPflModule/vtkSpatialPfl.h create mode 100644 src/SpatialPfl/plugin/filters.xml create mode 100644 src/SpatialPfl/plugin/paraview.plugin create mode 100644 src/SphereAlongLines/CMakeLists.txt create mode 100644 src/SphereAlongLines/Test/test_dev_surface.py create mode 100644 src/SphereAlongLines/Test/test_dev_surface2.py create mode 100644 src/SphereAlongLines/Test/test_dev_surface3.py create mode 100644 src/SphereAlongLines/plugin/CMakeLists.txt create mode 100644 src/SphereAlongLines/plugin/SphereAlongLinesModule/CMakeLists.txt create mode 100644 src/SphereAlongLines/plugin/SphereAlongLinesModule/vtk.module create mode 100644 src/SphereAlongLines/plugin/SphereAlongLinesModule/vtkSphereAlongLines.cxx create mode 100644 src/SphereAlongLines/plugin/SphereAlongLinesModule/vtkSphereAlongLines.h create mode 100644 src/SphereAlongLines/plugin/filters.xml create mode 100644 src/SphereAlongLines/plugin/paraview.plugin create mode 100644 src/TemporalOnPoint/CMakeLists.txt create mode 100644 src/TemporalOnPoint/TestCase.py create mode 100644 src/TemporalOnPoint/plugin/CMakeLists.txt create mode 100644 src/TemporalOnPoint/plugin/TemporalOnPointModule/CMakeLists.txt create mode 100644 src/TemporalOnPoint/plugin/TemporalOnPointModule/vtk.module create mode 100644 src/TemporalOnPoint/plugin/TemporalOnPointModule/vtkTemporalOnPoint.cxx create mode 100644 src/TemporalOnPoint/plugin/TemporalOnPointModule/vtkTemporalOnPoint.h create mode 100644 src/TemporalOnPoint/plugin/filters.xml create mode 100644 src/TemporalOnPoint/plugin/paraview.plugin create mode 100644 src/Tools/Clean.py create mode 100644 src/TorseurCIH/CMakeLists.txt create mode 100644 src/TorseurCIH/Test.py create mode 100644 src/TorseurCIH/plugin/CMakeLists.txt create mode 100644 src/TorseurCIH/plugin/TorseurCIHModule/CMakeLists.txt create mode 100644 src/TorseurCIH/plugin/TorseurCIHModule/vtk.module create mode 100644 src/TorseurCIH/plugin/TorseurCIHModule/vtkTorseurCIH.cxx create mode 100644 src/TorseurCIH/plugin/TorseurCIHModule/vtkTorseurCIH.h create mode 100644 src/TorseurCIH/plugin/filters.xml create mode 100644 src/TorseurCIH/plugin/paraview.plugin create mode 100644 src/TorseurCIH/script/Test.py create mode 100644 src/TorseurCIH/script/slice.med create mode 100644 src/TorseurCIH/slice.med create mode 100644 src/View/CMakeLists.txt create mode 100644 src/View/MyDisplay.cxx create mode 100644 src/View/MyDisplay.h create mode 100644 src/View/MyView.cxx create mode 100644 src/View/MyView.h create mode 100755 src/View/MyViewActiveOptions.cxx create mode 100755 src/View/MyViewActiveOptions.h create mode 100755 src/View/MyViewOptions.cxx create mode 100755 src/View/MyViewOptions.h create mode 100644 src/View/MyViewSM.xml create mode 100644 src/XYChartRepresentationColumns/CMakeLists.txt create mode 100644 src/XYChartRepresentationColumns/plugin/CMakeLists.txt create mode 100644 src/XYChartRepresentationColumns/plugin/XYChartRepresentationColumnsModule/CMakeLists.txt create mode 100644 src/XYChartRepresentationColumns/plugin/XYChartRepresentationColumnsModule/vtk.module create mode 100644 src/XYChartRepresentationColumns/plugin/XYChartRepresentationColumnsModule/vtkXYChartRepresentationColumns.cxx create mode 100644 src/XYChartRepresentationColumns/plugin/XYChartRepresentationColumnsModule/vtkXYChartRepresentationColumns.h create mode 100644 src/XYChartRepresentationColumns/plugin/paraview.plugin create mode 100644 src/XYChartRepresentationColumns/plugin/views.xml create mode 100644 src/ZJFilter/CMakeLists.txt create mode 100644 src/ZJFilter/TestCase.py create mode 100644 src/ZJFilter/plugin/CMakeLists.txt create mode 100644 src/ZJFilter/plugin/ZJFilterModule/CMakeLists.txt create mode 100644 src/ZJFilter/plugin/ZJFilterModule/vtk.module create mode 100644 src/ZJFilter/plugin/ZJFilterModule/vtkZJFilter.cxx create mode 100644 src/ZJFilter/plugin/ZJFilterModule/vtkZJFilter.h create mode 100644 src/ZJFilter/plugin/filters.xml create mode 100644 src/ZJFilter/plugin/paraview.plugin diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..f1b8780 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,83 @@ +# Copyright (C) 2010-2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +CMAKE_MINIMUM_REQUIRED(VERSION 2.8.8 FATAL_ERROR) +INCLUDE(CMakeDependentOption) +PROJECT(PARAVISADDONS C CXX) + +IF(WIN32) + STRING( REPLACE "INCREMENTAL:YES" "INCREMENTAL:NO" replacementFlags ${CMAKE_SHARED_LINKER_FLAGS_DEBUG} ) + SET( CMAKE_SHARED_LINKER_FLAGS_DEBUG "${replacementFlags}" ) +ENDIF(WIN32) + +# Versioning +# =========== +# Project name, upper case +STRING(TOUPPER ${PROJECT_NAME} PROJECT_NAME_UC) + +SET(${PROJECT_NAME_UC}_MAJOR_VERSION 9) +SET(${PROJECT_NAME_UC}_MINOR_VERSION 6) +SET(${PROJECT_NAME_UC}_PATCH_VERSION 0) +SET(${PROJECT_NAME_UC}_VERSION + ${${PROJECT_NAME_UC}_MAJOR_VERSION}.${${PROJECT_NAME_UC}_MINOR_VERSION}.${${PROJECT_NAME_UC}_PATCH_VERSION}) +SET(${PROJECT_NAME_UC}_VERSION_DEV 0) + +# Common CMake macros +# =================== +SET(CONFIGURATION_ROOT_DIR $ENV{CONFIGURATION_ROOT_DIR} CACHE PATH "Path to the Salome CMake configuration files") +IF(EXISTS ${CONFIGURATION_ROOT_DIR}) + LIST(APPEND CMAKE_MODULE_PATH "${CONFIGURATION_ROOT_DIR}/cmake") + INCLUDE(SalomeMacros NO_POLICY_SCOPE) +ELSE() + MESSAGE(FATAL_ERROR "We absolutely need the Salome CMake configuration files, please define CONFIGURATION_ROOT_DIR !") +ENDIF() + +# Find KERNEL +# =========== +SET(KERNEL_ROOT_DIR $ENV{KERNEL_ROOT_DIR} CACHE PATH "Path to the Salome KERNEL") +IF(EXISTS ${KERNEL_ROOT_DIR}) + FIND_PACKAGE(SalomeKERNEL REQUIRED) + ADD_DEFINITIONS(${KERNEL_DEFINITIONS}) + INCLUDE_DIRECTORIES(${KERNEL_INCLUDE_DIRS}) +ELSE(EXISTS ${KERNEL_ROOT_DIR}) + MESSAGE(FATAL_ERROR "We absolutely need a Salome KERNEL, please define KERNEL_ROOT_DIR") +ENDIF(EXISTS ${KERNEL_ROOT_DIR}) + +# Platform setup +# ============== +INCLUDE(SalomeSetupPlatform) # From KERNEL +# Always build libraries as shared objects: +SET(BUILD_SHARED_LIBS TRUE) + +FIND_PACKAGE(SalomeQt5 REQUIRED) + +## +## Specific to ParaViS: +## + +FIND_PACKAGE(SalomeParaView REQUIRED) + +# Header configuration +# ==================== +SALOME_XVERSION(${PROJECT_NAME}) +SALOME_CONFIGURE_FILE(PARAVISADDONS_version.h.in PARAVISADDONS_version.h INSTALL ${SALOME_INSTALL_HEADERS}) + +# Sources +# ======== +ADD_SUBDIRECTORY(src) diff --git a/PARAVISADDONS_version.h.in b/PARAVISADDONS_version.h.in new file mode 100644 index 0000000..a75a540 --- /dev/null +++ b/PARAVISADDONS_version.h.in @@ -0,0 +1,42 @@ +// Copyright (C) 2010-2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +// File : PARAVISADDONS_version.h + +#if !defined(__PARAVISADDONS_VERSION_H__) +#define __PARAVISADDONS_VERSION_H__ + +/*! + Specify version of SALOME PARAVISADDONS module, as follows + + PARAVISADDONS_VERSION_MAJOR : (integer) number identifying major version + PARAVISADDONS_VERSION_MINOR : (integer) number identifying minor version + PARAVISADDONS_VERSION_MAINTENANCE : (integer) number identifying maintenance version + PARAVISADDONS_VERSION_STR : (string) complete version number "major.minor.maintenance" + PARAVISADDONS_VERSION : (hex) complete version number (major << 16) + (minor << 8) + maintenance + PARAVISADDONS_DEVELOPMENT : (integer) indicates development version when set to 1 +*/ + +#define PARAVISADDONS_VERSION_MAJOR @SALOMEPARAVISADDONS_MAJOR_VERSION@ +#define PARAVISADDONS_VERSION_MINOR @SALOMEPARAVISADDONS_MINOR_VERSION@ +#define PARAVISADDONS_VERSION_MAINTENANCE @SALOMEPARAVISADDONS_PATCH_VERSION@ +#define PARAVISADDONS_VERSION_STR "@SALOMEPARAVISADDONS_VERSION@" +#define PARAVISADDONS_VERSION @SALOMEPARAVISADDONS_XVERSION@ +#define PARAVISADDONS_DEVELOPMENT @SALOMEPARAVISADDONS_VERSION_DEV@ + +#endif // __PARAVISADDONS_VERSION_H__ diff --git a/README b/README new file mode 100644 index 0000000..b47d93d --- /dev/null +++ b/README @@ -0,0 +1,59 @@ +************************** +About SALOME PARAVISADDONS +************************** + +======= +License +======= + + +============ +Installation +============ + +-------------- +Pre-requisites +-------------- + +------------------ +Basic Installation +------------------ + +The build procedure of the SALOME platform is implemented with CMake. +In order to build the module you have to do the following actions: + +1. Set up environment for pre-requisites (see "Pre-requisites" section above). + +2. Create a build directory: + + % mkdir PARAVISADDONS_BUILD + +3. Configure the build procedure: + + % cd PARAVISADDONS_BUILD + % cmake -DCMAKE_BUILD_TYPE= -DCMAKE_INSTALL_PREFIX= + + where + - is either Release or Debug (default: Release); + - is a destination folder to install SALOME PARAVISADDONS + module (default: /usr); + - is a path to the PARAVISADDONS sources directory. + + +4. Build and install: + + % make + % make install + + This will install SALOME PARAVISADDONS module to the + specified to cmake command on the previous step. + +============= +Documentation +============= + +=============== +Troubleshooting +=============== + +Please, send a mail to webmaster.salome@opencascade.com. diff --git a/doc/README b/doc/README new file mode 100644 index 0000000..61b249d --- /dev/null +++ b/doc/README @@ -0,0 +1,4 @@ +*********************************** +SALOME PARAVISADDONS Documentation +*********************************** + diff --git a/src/AppendAttributesOverTime/CMakeLists.txt b/src/AppendAttributesOverTime/CMakeLists.txt new file mode 100644 index 0000000..5c2aab6 --- /dev/null +++ b/src/AppendAttributesOverTime/CMakeLists.txt @@ -0,0 +1,49 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +cmake_minimum_required(VERSION 3.8) +project(AppendAttributesOverTime) +find_package(ParaView REQUIRED) + +include(GNUInstallDirs) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}") +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}") +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}") + +set("_paraview_plugin_default_${CMAKE_PROJECT_NAME}" ON) +paraview_plugin_scan( + ENABLE_BY_DEFAULT YES + PLUGIN_FILES "${CMAKE_CURRENT_SOURCE_DIR}/plugin/paraview.plugin" + PROVIDES_PLUGINS plugins + REQUIRES_MODULES required_modules) + +foreach(module IN LISTS required_modules) + if(NOT TARGET "${module}") + message("Missing required module: ${module}") + return() + endif() +endforeach() + +set(BUILD_SHARED_LIBS ON) +paraview_plugin_build( + RUNTIME_DESTINATION "${CMAKE_INSTALL_BINDIR}" + LIBRARY_DESTINATION "${CMAKE_INSTALL_LIBDIR}" + LIBRARY_SUBDIRECTORY "${PARAVIEW_PLUGIN_SUBDIR}" + PLUGINS ${plugins} + AUTOLOAD ${plugins}) diff --git a/src/AppendAttributesOverTime/README.md b/src/AppendAttributesOverTime/README.md new file mode 100644 index 0000000..74af88c --- /dev/null +++ b/src/AppendAttributesOverTime/README.md @@ -0,0 +1,8 @@ +Merge TimeSteps Plugin +====================== + +Add the filter 'Append Attributes Over Timesteps' (vtkMergeArraysAndTimeSteps) +that is based on the 'Append Attributes' (vtkMergeArrays) filter. Arrays from each +timestep from each input are append to the output, with the name +'originalName_input_#N_ts_#TS' where #N is the input index and #TS is the +timestep. diff --git a/src/AppendAttributesOverTime/plugin/AppendAttributesOverTimeModule/CMakeLists.txt b/src/AppendAttributesOverTime/plugin/AppendAttributesOverTimeModule/CMakeLists.txt new file mode 100644 index 0000000..d6634fc --- /dev/null +++ b/src/AppendAttributesOverTime/plugin/AppendAttributesOverTimeModule/CMakeLists.txt @@ -0,0 +1,27 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +set(classes + vtkAppendAttributesOverTime +) + +vtk_module_add_module(AppendAttributesOverTimeModule + FORCE_STATIC + CLASSES ${classes} +) diff --git a/src/AppendAttributesOverTime/plugin/AppendAttributesOverTimeModule/vtk.module b/src/AppendAttributesOverTime/plugin/AppendAttributesOverTimeModule/vtk.module new file mode 100644 index 0000000..2ddd1e7 --- /dev/null +++ b/src/AppendAttributesOverTime/plugin/AppendAttributesOverTimeModule/vtk.module @@ -0,0 +1,35 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +NAME + AppendAttributesOverTimeModule +DEPENDS + VTK::CommonCore + VTK::CommonDataModel + VTK::CommonExecutionModel + VTK::FiltersCore + VTK::FiltersModeling + VTK::IOCore + VTK::IOGeometry + VTK::IOXML + ParaView::VTKExtensionsFiltersGeneral +PRIVATE_DEPENDS + VTK::CommonMisc + VTK::CommonSystem + VTK::FiltersGeneral diff --git a/src/AppendAttributesOverTime/plugin/AppendAttributesOverTimeModule/vtkAppendAttributesOverTime.cxx b/src/AppendAttributesOverTime/plugin/AppendAttributesOverTimeModule/vtkAppendAttributesOverTime.cxx new file mode 100644 index 0000000..4f5a5e0 --- /dev/null +++ b/src/AppendAttributesOverTime/plugin/AppendAttributesOverTimeModule/vtkAppendAttributesOverTime.cxx @@ -0,0 +1,223 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +/*========================================================================= + + Program: ParaView + Module: vtkAppendAttributesOverTime.cxx + + Copyright (c) Kitware, Inc. + All rights reserved. + See Copyright.txt or http://www.paraview.org/HTML/Copyright.html for details. + + This software is distributed WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. See the above copyright notice for more information. + +=========================================================================*/ +#include "vtkAppendAttributesOverTime.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +vtkStandardNewMacro(vtkAppendAttributesOverTime); + +//---------------------------------------------------------------------------- +vtkAppendAttributesOverTime::vtkAppendAttributesOverTime() + :RestoreOriginalTimeStep(false) +{ +} + +//---------------------------------------------------------------------------- +namespace +{ +std::string GetPaddedValue(int value, int maxValue) +{ + int padding = 1 + static_cast(log10(maxValue)); + std::ostringstream os; + os << std::setw(padding) << std::setfill('0') << std::to_string(value); + return os.str(); +} + +//---------------------------------------------------------------------------- +void ResetAttributes(vtkDataObject* obj) +{ + vtkCompositeDataSet* cObj = vtkCompositeDataSet::SafeDownCast(obj); + if (cObj) + { + vtkSmartPointer iter; + iter.TakeReference(cObj->NewIterator()); + iter->InitTraversal(); + for (; !iter->IsDoneWithTraversal(); iter->GoToNextItem()) + { + ResetAttributes(vtkDataSet::SafeDownCast(iter->GetCurrentDataObject())); + } + } + else + { + // reset fields data, so TempDataObject contains only geometry. + for (int attr = 0; attr < vtkDataObject::NUMBER_OF_ATTRIBUTE_TYPES; attr++) + { + vtkFieldData *fd = obj->GetAttributesAsFieldData(attr); + if (fd != nullptr) + { + fd->Initialize(); + } + } + } +} +} + +//---------------------------------------------------------------------------- +bool vtkAppendAttributesOverTime::GetOutputArrayName(vtkFieldData* vtkNotUsed(arrays), + const char* arrayName, int vtkNotUsed(inputIndex), std::string& outArrayName) +{ + std::string inString = ::GetPaddedValue(this->CurrentInputIndex, this->TimeSteps.size()); + std::string tsString = ::GetPaddedValue(this->UpdateTimeIndex, this->TimeSteps[this->CurrentInputIndex].size()); + outArrayName = std::string(arrayName) + "_input_" + inString + "_ts_" + tsString; + return true; +} + +//---------------------------------------------------------------------------- +int vtkAppendAttributesOverTime::RequestUpdateExtent(vtkInformation* vtkNotUsed(request), + vtkInformationVector** inInfo, vtkInformationVector* vtkNotUsed(outInfo)) +{ + vtkInformation* info = inInfo[0]->GetInformationObject(this->CurrentInputIndex); + if (this->RestoreOriginalTimeStep) + { + info->Set(vtkStreamingDemandDrivenPipeline::UPDATE_TIME_STEP(), this->OriginalTimeStep); + } + else + { + this->OriginalTimeStep = info->Get(vtkStreamingDemandDrivenPipeline::UPDATE_TIME_STEP()); + info->Set(vtkStreamingDemandDrivenPipeline::UPDATE_TIME_STEP(), + this->TimeSteps[this->CurrentInputIndex][this->UpdateTimeIndex]); + } + return 1; +} + +//---------------------------------------------------------------------------- +int vtkAppendAttributesOverTime::RequestInformation(vtkInformation* vtkNotUsed(request), + vtkInformationVector** inInfo, vtkInformationVector* outInfoVec) +{ + this->UpdateTimeIndex = 0; + this->CurrentInputIndex = 0; + int num = inInfo[0]->GetNumberOfInformationObjects(); + + // Fill this->TimeSteps + for (int idx = 0; idx < num; idx++) + { + vtkInformation* info = inInfo[0]->GetInformationObject(idx); + int len = info->Length(vtkStreamingDemandDrivenPipeline::TIME_STEPS()); + double* timeSteps = info->Get(vtkStreamingDemandDrivenPipeline::TIME_STEPS()); + std::vector timeStepsVector; + timeStepsVector.resize(len); + std::copy(timeSteps, timeSteps + len, timeStepsVector.begin()); + this->TimeSteps.push_back(timeStepsVector); + } + + vtkInformation* outInfo = outInfoVec->GetInformationObject(0); + outInfo->Remove(vtkStreamingDemandDrivenPipeline::TIME_STEPS()); + outInfo->Remove(vtkStreamingDemandDrivenPipeline::TIME_RANGE()); + + return 1; +} + +//---------------------------------------------------------------------------- +/** + * This method calls the Superclass::RequestData to perform the real + * merge. + * Here we do the following: + * - extract a dataset from 2 internal vars: an input index and a timestep index. + * (see this->TimeSteps). + * - call the Superclass RequestData method with 2 datasets as inputs: + * buffer containing previously merged arrays and current dataset to process. + * - store the resulting dataset in the buffer + * - increment the timestep index. When last timestep of current input was + * processed, increment input index and reset timestep index + * - loop + */ +int vtkAppendAttributesOverTime::RequestData( + vtkInformation* request, vtkInformationVector** inInfo, vtkInformationVector* outInfoVec) +{ + if (this->RestoreOriginalTimeStep) + { + this->RestoreOriginalTimeStep = false; + request->Remove(vtkStreamingDemandDrivenPipeline::CONTINUE_EXECUTING()); + return 1; + } + + vtkInformation* info = inInfo[0]->GetInformationObject(this->CurrentInputIndex); + vtkInformation* outInfo = outInfoVec->GetInformationObject(0); + + if (this->UpdateTimeIndex == 0 && this->CurrentInputIndex == 0) + { + // Before looping, we create the temporary output object + vtkInformation *inputInfo = inInfo[0]->GetInformationObject(0); + vtkDataObject *inputData = vtkDataObject::GetData(inputInfo); + this->TempDataObject = vtkSmartPointer::Take(inputData->NewInstance()); + this->TempDataObject->ShallowCopy(inputData); + // Remove all arrays in the ouput + ::ResetAttributes(this->TempDataObject); + } + + this->CurrentOutInfo->Set(vtkDataObject::DATA_OBJECT(), this->TempDataObject); + vtkInformationVector* reducedInputVec = vtkInformationVector::New(); + reducedInputVec->Append(this->CurrentOutInfo); + reducedInputVec->Append(info); + + // perform the effective merge. + this->Superclass::RequestData(request, &reducedInputVec, outInfoVec); + reducedInputVec->Delete(); + // save merge result in a buffer. + this->TempDataObject->ShallowCopy(outInfo->Get(vtkDataObject::DATA_OBJECT())); + + if (this->UpdateTimeIndex < + static_cast(this->TimeSteps.at(this->CurrentInputIndex).size()) - 1) + { + this->UpdateTimeIndex++; + request->Set(vtkStreamingDemandDrivenPipeline::CONTINUE_EXECUTING(), 1); + } + else if (this->CurrentInputIndex < static_cast(this->TimeSteps.size()) - 1) + { + this->UpdateTimeIndex = 0; + this->CurrentInputIndex++; + request->Set(vtkStreamingDemandDrivenPipeline::CONTINUE_EXECUTING(), 1); + } + else + { + this->RestoreOriginalTimeStep = true; + request->Set(vtkStreamingDemandDrivenPipeline::CONTINUE_EXECUTING(), 1); + vtkDataObject* output = outInfo->Get(vtkDataObject::DATA_OBJECT()); + output->ShallowCopy(this->TempDataObject); + } + + return 1; +} diff --git a/src/AppendAttributesOverTime/plugin/AppendAttributesOverTimeModule/vtkAppendAttributesOverTime.h b/src/AppendAttributesOverTime/plugin/AppendAttributesOverTimeModule/vtkAppendAttributesOverTime.h new file mode 100644 index 0000000..0c518a1 --- /dev/null +++ b/src/AppendAttributesOverTime/plugin/AppendAttributesOverTimeModule/vtkAppendAttributesOverTime.h @@ -0,0 +1,103 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +/*========================================================================= + + Program: ParaView + Module: vtkAppendAttributesOverTime.h + + Copyright (c) Kitware, Inc. + All rights reserved. + See Copyright.txt or http://www.paraview.org/HTML/Copyright.html for details. + + This software is distributed WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. See the above copyright notice for more information. + +=========================================================================*/ +/** + * @class vtkAppendAttributesOverTime + * @brief Multiple inputs with one output. + * + * vtkAppendAttributesOverTime put all arrays from all inputs into one output, + * and this for each timesteps. + * The output data object is the same as the first data input, and has no timeteps. + * It extends vtkMergeArrays to process every available timestep. + * The new arrays will have the name mangled to be the original array name plus + * `_input__ts_` where `` is the id/index of the input filter + * that is providing that array, and `` is the timestep index of corresponing input. + */ + +#ifndef vtkAppendAttributesOverTime_h +#define vtkAppendAttributesOverTime_h + +#include + +#include +#include // vtkNew +#include // Smart Pointer + +#include + +class vtkFieldData; +class vtkDataObject; + +class VTK_EXPORT vtkAppendAttributesOverTime : public vtkMergeArrays +{ +public: + static vtkAppendAttributesOverTime* New(); + vtkTypeMacro(vtkAppendAttributesOverTime, vtkMergeArrays); + +protected: + vtkAppendAttributesOverTime(); + ~vtkAppendAttributesOverTime() override = default; + + //@{ + /** + * Given an array name, return an appropriate name to use for the output array. + * Reimplemented to add original timestep index in the name. + * Returns true. + */ + bool GetOutputArrayName(vtkFieldData* arrays, const char* inArrayName, int inputIndex, + std::string& outArrayName) override; + //@} + + int RequestInformation(vtkInformation*, vtkInformationVector**, vtkInformationVector*) override; + int RequestUpdateExtent(vtkInformation*, vtkInformationVector**, vtkInformationVector*) override; + int RequestData(vtkInformation*, vtkInformationVector**, vtkInformationVector*) override; + +private: + vtkAppendAttributesOverTime(const vtkAppendAttributesOverTime&) = delete; + void operator=(const vtkAppendAttributesOverTime&) = delete; + + int UpdateTimeIndex = 0; + int CurrentInputIndex = 0; + + /** + * this->TimeSteps contains for each input a vector of timesteps + * time = this->TimeSteps[inputIndex][tsIndex] + */ + std::vector > TimeSteps; + vtkNew CurrentOutInfo; + vtkSmartPointer TempDataObject; + double OriginalTimeStep; + bool RestoreOriginalTimeStep; +}; + +#endif // vtkAppendAttributesOverTime_h diff --git a/src/AppendAttributesOverTime/plugin/CMakeLists.txt b/src/AppendAttributesOverTime/plugin/CMakeLists.txt new file mode 100644 index 0000000..8b085d4 --- /dev/null +++ b/src/AppendAttributesOverTime/plugin/CMakeLists.txt @@ -0,0 +1,30 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +paraview_add_plugin(AppendAttributesOverTime + VERSION "1.0" + MODULES AppendAttributesOverTimeModule + MODULE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/AppendAttributesOverTimeModule/vtk.module" + SERVER_MANAGER_XML filters.xml +) +install(TARGETS AppendAttributesOverTime + RUNTIME DESTINATION lib/paraview + LIBRARY DESTINATION lib/paraview + ARCHIVE DESTINATION lib/paraview +) diff --git a/src/AppendAttributesOverTime/plugin/README.md b/src/AppendAttributesOverTime/plugin/README.md new file mode 100644 index 0000000..74af88c --- /dev/null +++ b/src/AppendAttributesOverTime/plugin/README.md @@ -0,0 +1,8 @@ +Merge TimeSteps Plugin +====================== + +Add the filter 'Append Attributes Over Timesteps' (vtkMergeArraysAndTimeSteps) +that is based on the 'Append Attributes' (vtkMergeArrays) filter. Arrays from each +timestep from each input are append to the output, with the name +'originalName_input_#N_ts_#TS' where #N is the input index and #TS is the +timestep. diff --git a/src/AppendAttributesOverTime/plugin/filters.xml b/src/AppendAttributesOverTime/plugin/filters.xml new file mode 100644 index 0000000..6332470 --- /dev/null +++ b/src/AppendAttributesOverTime/plugin/filters.xml @@ -0,0 +1,33 @@ + + + + + + The Append Attributes Over Time filter takes multiple input data + sets with the same geometry and merges their attributes to produce + a single output containing all these attributes. + Any inputs without the same number of points and cells as the first + input are ignored. + Each array of each timestep of each input is copied to the output. + Note that the output is not a temporal dataset. + + + + + + + + + This property specifies the input to the filter. + + + + + diff --git a/src/AppendAttributesOverTime/plugin/paraview.plugin b/src/AppendAttributesOverTime/plugin/paraview.plugin new file mode 100644 index 0000000..2e42ade --- /dev/null +++ b/src/AppendAttributesOverTime/plugin/paraview.plugin @@ -0,0 +1,27 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +NAME + AppendAttributesOverTime +DESCRIPTION + Provides a filter to Append attributes from each timesteps and each inputs +REQUIRES_MODULES + VTK::CommonCore + VTK::IOCore + VTK::FiltersCore diff --git a/src/AutoConvertPropertiesPlugin/CMakeLists.txt b/src/AutoConvertPropertiesPlugin/CMakeLists.txt new file mode 100644 index 0000000..346f979 --- /dev/null +++ b/src/AutoConvertPropertiesPlugin/CMakeLists.txt @@ -0,0 +1,49 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +cmake_minimum_required(VERSION 3.8) +project(AutoConvertPropertiesPlugin) +find_package(ParaView REQUIRED) + +include(GNUInstallDirs) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}") +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}") +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}") + +set("_paraview_plugin_default_${CMAKE_PROJECT_NAME}" ON) +paraview_plugin_scan( + ENABLE_BY_DEFAULT YES + PLUGIN_FILES "${CMAKE_CURRENT_SOURCE_DIR}/plugin/paraview.plugin" + PROVIDES_PLUGINS plugins + REQUIRES_MODULES required_modules) + +foreach(module IN LISTS required_modules) + if(NOT TARGET "${module}") + message("Missing required module: ${module}") + return() + endif() +endforeach() + +set(BUILD_SHARED_LIBS ON) +paraview_plugin_build( + RUNTIME_DESTINATION "${CMAKE_INSTALL_BINDIR}" + LIBRARY_DESTINATION "${CMAKE_INSTALL_LIBDIR}" + LIBRARY_SUBDIRECTORY "${PARAVIEW_PLUGIN_SUBDIR}" + PLUGINS ${plugins} + AUTOLOAD ${plugins}) diff --git a/src/AutoConvertPropertiesPlugin/plugin/CMakeLists.txt b/src/AutoConvertPropertiesPlugin/plugin/CMakeLists.txt new file mode 100644 index 0000000..158f3be --- /dev/null +++ b/src/AutoConvertPropertiesPlugin/plugin/CMakeLists.txt @@ -0,0 +1,52 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +# Create an auto-start plugin. Auto start plugins provide callbacks that get +# called when the plugin is loaded and when the application shutsdown. +paraview_plugin_add_auto_start( + CLASS_NAME pqAutoConvertPropertiesStarter + STARTUP onStartup + SHUTDOWN onShutdown + INTERFACES autostart_interface + SOURCES autostart_sources +) + +# create a plugin for this starter +paraview_add_plugin(AutoConvertProperties + VERSION "1.0" + UI_INTERFACES ${autostart_interface} + SOURCES ${autostart_sources} pqAutoConvertPropertiesStarter.cxx +) + +target_include_directories(AutoConvertProperties + PRIVATE + "${CMAKE_CURRENT_BINARY_DIR}") + +target_link_libraries(AutoConvertProperties + PRIVATE + ParaView::pqApplicationComponents + ParaView::RemotingServerManager +) + +install(TARGETS AutoConvertProperties + RUNTIME DESTINATION lib/paraview + LIBRARY DESTINATION lib/paraview + ARCHIVE DESTINATION lib/paraview +) + diff --git a/src/AutoConvertPropertiesPlugin/plugin/paraview.plugin b/src/AutoConvertPropertiesPlugin/plugin/paraview.plugin new file mode 100644 index 0000000..555e6d6 --- /dev/null +++ b/src/AutoConvertPropertiesPlugin/plugin/paraview.plugin @@ -0,0 +1,29 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +NAME + AutoConvertProperties +DESCRIPTION + Plugin that automatically turns some ParaView options at startup. +CONDITION + PARAVIEW_USE_QT +REQUIRES_MODULES + ParaView::RemotingCore + ParaView::RemotingServerManager + ParaView::pqApplicationComponents diff --git a/src/AutoConvertPropertiesPlugin/plugin/pqAutoConvertPropertiesStarter.cxx b/src/AutoConvertPropertiesPlugin/plugin/pqAutoConvertPropertiesStarter.cxx new file mode 100644 index 0000000..1b07a8c --- /dev/null +++ b/src/AutoConvertPropertiesPlugin/plugin/pqAutoConvertPropertiesStarter.cxx @@ -0,0 +1,77 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +/*========================================================================= + + Program: ParaView + Module: pqAutoConvertPropertiesStarter.cxx + + Copyright (c) 2005,2006 Sandia Corporation, Kitware Inc. + All rights reserved. + + ParaView is a free software; you can redistribute it and/or modify it + under the terms of the ParaView license version 1.2. + + See License_v1.2.txt for the full ParaView license. + A copy of this license can be obtained by contacting + Kitware Inc. + 28 Corporate Drive + Clifton Park, NY 12065 + USA + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +========================================================================*/ +#include "pqAutoConvertPropertiesStarter.h" + +#include + +//----------------------------------------------------------------------------- +pqAutoConvertPropertiesStarter::pqAutoConvertPropertiesStarter(QObject* p) + : QObject(p) +{ +} + +//----------------------------------------------------------------------------- +void pqAutoConvertPropertiesStarter::onStartup() +{ + vtkSMSettings* settings = vtkSMSettings::GetInstance(); + if (!settings->HasSetting(".settings.GeneralSettings.AutoConvertProperties")) + { + settings->SetSetting("settings.GeneralSettings.AutoConvertProperties", 1); + } + if (!settings->HasSetting(".settings.GeneralSettings.ShowAnimationShortcuts")) + { + settings->SetSetting("settings.GeneralSettings.ShowAnimationShortcuts", 1); + } + if (!settings->HasSetting(".settings.GeneralSettings.ResetDisplayEmptyViews")) + { + settings->SetSetting("settings.GeneralSettings.ResetDisplayEmptyViews", 0); + } +} diff --git a/src/AutoConvertPropertiesPlugin/plugin/pqAutoConvertPropertiesStarter.h b/src/AutoConvertPropertiesPlugin/plugin/pqAutoConvertPropertiesStarter.h new file mode 100644 index 0000000..7a54167 --- /dev/null +++ b/src/AutoConvertPropertiesPlugin/plugin/pqAutoConvertPropertiesStarter.h @@ -0,0 +1,75 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +/*========================================================================= + + Program: ParaView + Module: pqAutoConvertPropertiesStarter.h + + Copyright (c) 2005,2006 Sandia Corporation, Kitware Inc. + All rights reserved. + + ParaView is a free software; you can redistribute it and/or modify it + under the terms of the ParaView license version 1.2. + + See License_v1.2.txt for the full ParaView license. + A copy of this license can be obtained by contacting + Kitware Inc. + 28 Corporate Drive + Clifton Park, NY 12065 + USA + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +========================================================================*/ +#ifndef pqAutoConvertPropertiesStarter_h +#define pqAutoConvertPropertiesStarter_h + +#include + +class pqAutoConvertPropertiesStarter : public QObject +{ + Q_OBJECT + typedef QObject Superclass; + +public: + pqAutoConvertPropertiesStarter(QObject* p = nullptr); + ~pqAutoConvertPropertiesStarter() = default; + + // Callback for startup. + void onStartup(); + + // Callback for shutdown. Does nothing. + void onShutdown() {} + +private: + Q_DISABLE_COPY(pqAutoConvertPropertiesStarter) +}; + +#endif diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..ad9fc2c --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,77 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +CMAKE_MINIMUM_REQUIRED(VERSION 2.8) + +SET(CONFIGURATION_ROOT_DIR $ENV{CONFIGURATION_ROOT_DIR} CACHE PATH "Path to the Salome CMake files") +IF(EXISTS ${CONFIGURATION_ROOT_DIR}) + LIST(APPEND CMAKE_MODULE_PATH "${CONFIGURATION_ROOT_DIR}/cmake") + INCLUDE(SalomeMacros) +ELSE() + MESSAGE(FATAL_ERROR "We absolutely need the Salome CMake configuration files, please define CONFIGURATION_ROOT_DIR !") +ENDIF() + +INCLUDE(SalomeSetupPlatform) + +FOREACH(_Qt5_COMPONENT_ ${Qt5_FIND_COMPONENTS} ${Qt5_OPTIONAL_COMPONENTS}) + SET(_Qt5_COMPONENT Qt5${_Qt5_COMPONENT_}) + LIST(FIND Qt5_OPTIONAL_COMPONENTS ${_Qt5_COMPONENT_} idx) + IF(${idx} GREATER -1) + SET(Salome${_Qt5_COMPONENT}_FIND_QUIETLY TRUE) + ENDIF() + FIND_PACKAGE(${_Qt5_COMPONENT}) + LIST(APPEND QT_INCLUDES ${${_Qt5_COMPONENT}_INCLUDE_DIRS}) + LIST(APPEND QT_DEFINITIONS ${${_Qt5_COMPONENT}_DEFINITIONS}) + LIST(APPEND QT_LIBRARIES ${${_Qt5_COMPONENT}_LIBRARIES}) +ENDFOREACH() + +ENABLE_TESTING() + +ADD_SUBDIRECTORY(MoveZCote) +ADD_SUBDIRECTORY(ComplexMode) +ADD_SUBDIRECTORY(QuadraticToLinear) +ADD_SUBDIRECTORY(ExtractComponentsPlugin) +ADD_SUBDIRECTORY(ZJFilter) +ADD_SUBDIRECTORY(SerafinReader) +ADD_SUBDIRECTORY(SpatialPfl) +ADD_SUBDIRECTORY(SinusXReader) +ADD_SUBDIRECTORY(TemporalOnPoint) +ADD_SUBDIRECTORY(DepthVsTime) +ADD_SUBDIRECTORY(SphereAlongLines) +ADD_SUBDIRECTORY(RateOfFlowThroughSection) +ADD_SUBDIRECTORY(ProbePointOverTime) +ADD_SUBDIRECTORY(CustomFilters) +ADD_SUBDIRECTORY(CellDataContour) +ADD_SUBDIRECTORY(AutoConvertPropertiesPlugin) +ADD_SUBDIRECTORY(AppendAttributesOverTime) +ADD_SUBDIRECTORY(ExtractThreeD) +ADD_SUBDIRECTORY(ContactReader) +ADD_SUBDIRECTORY(TorseurCIH) +ADD_SUBDIRECTORY(RosetteCIH) +ADD_SUBDIRECTORY(XYChartRepresentationColumns) +ADD_SUBDIRECTORY(GlyphCIH) # EDF15785 +ADD_SUBDIRECTORY(ElectromagnetismVecteur) +ADD_SUBDIRECTORY(ElectromagnetismStreamTraceur) +ADD_SUBDIRECTORY(ElectromagnetismFluxDisc) + +IF(NOT WIN32) + # still being under investigations + MESSAGE(WARNING "ElectromagnetismRotation will be skipped") + ADD_SUBDIRECTORY(ElectromagnetismRotation) +ENDIF(NOT WIN32) diff --git a/src/CellDataContour/CMakeLists.txt b/src/CellDataContour/CMakeLists.txt new file mode 100644 index 0000000..6e4ce35 --- /dev/null +++ b/src/CellDataContour/CMakeLists.txt @@ -0,0 +1,49 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +cmake_minimum_required(VERSION 3.8) +project(MyContour) +find_package(ParaView REQUIRED) + +include(GNUInstallDirs) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}") +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}") +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}") + +set("_paraview_plugin_default_${CMAKE_PROJECT_NAME}" ON) +paraview_plugin_scan( + ENABLE_BY_DEFAULT YES + PLUGIN_FILES "${CMAKE_CURRENT_SOURCE_DIR}/plugin/paraview.plugin" + PROVIDES_PLUGINS plugins + REQUIRES_MODULES required_modules) + +foreach(module IN LISTS required_modules) + if(NOT TARGET "${module}") + message("Missing required module: ${module}") + return() + endif() +endforeach() + +set(BUILD_SHARED_LIBS ON) +paraview_plugin_build( + RUNTIME_DESTINATION "${CMAKE_INSTALL_BINDIR}" + LIBRARY_DESTINATION "${CMAKE_INSTALL_LIBDIR}" + LIBRARY_SUBDIRECTORY "${PARAVIEW_PLUGIN_SUBDIR}" + PLUGINS ${plugins} + AUTOLOAD ${plugins}) diff --git a/src/CellDataContour/plugin/CMakeLists.txt b/src/CellDataContour/plugin/CMakeLists.txt new file mode 100644 index 0000000..85cdacb --- /dev/null +++ b/src/CellDataContour/plugin/CMakeLists.txt @@ -0,0 +1,31 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +paraview_add_plugin(MyContour + VERSION "1.0" + MODULES CellDataContourModule + MODULE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/CellDataContourModule/vtk.module" + SERVER_MANAGER_XML filters.xml +) + +install(TARGETS MyContour + RUNTIME DESTINATION lib/paraview + LIBRARY DESTINATION lib/paraview + ARCHIVE DESTINATION lib/paraview +) diff --git a/src/CellDataContour/plugin/CellDataContourModule/CMakeLists.txt b/src/CellDataContour/plugin/CellDataContourModule/CMakeLists.txt new file mode 100644 index 0000000..a8427ea --- /dev/null +++ b/src/CellDataContour/plugin/CellDataContourModule/CMakeLists.txt @@ -0,0 +1,29 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +set(classes + vtkMyCellDataToPointData + vtkMyContourFilter + vtkMyPVContourFilter +) + +vtk_module_add_module(CellDataContourModule + FORCE_STATIC + CLASSES ${classes} + ) diff --git a/src/CellDataContour/plugin/CellDataContourModule/vtk.module b/src/CellDataContour/plugin/CellDataContourModule/vtk.module new file mode 100644 index 0000000..b9210a2 --- /dev/null +++ b/src/CellDataContour/plugin/CellDataContourModule/vtk.module @@ -0,0 +1,39 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +NAME + CellDataContourModule +DEPENDS + VTK::CommonCore + VTK::CommonDataModel + VTK::CommonExecutionModel + VTK::FiltersCore + VTK::FiltersModeling + VTK::FiltersSources + VTK::IOCore + VTK::IOGeometry + VTK::IOXML + ParaView::VTKExtensionsAMR +PRIVATE_DEPENDS + VTK::CommonMisc + VTK::CommonSystem + VTK::FiltersGeneral + VTK::RenderingCore + VTK::vtksys + VTK::zlib diff --git a/src/CellDataContour/plugin/CellDataContourModule/vtkMyCellDataToPointData.cxx b/src/CellDataContour/plugin/CellDataContourModule/vtkMyCellDataToPointData.cxx new file mode 100644 index 0000000..c690deb --- /dev/null +++ b/src/CellDataContour/plugin/CellDataContourModule/vtkMyCellDataToPointData.cxx @@ -0,0 +1,610 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +/*========================================================================= + + Program: Visualization Toolkit + Module: vtkMyCellDataToPointData.cxx + + Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen + All rights reserved. + See Copyright.txt or http://www.kitware.com/Copyright.htm for details. + + This software is distributed WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. See the above copyright notice for more information. + + =========================================================================*/ +#include "vtkMyCellDataToPointData.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define VTK_MAX_CELLS_PER_POINT 4096 + +vtkStandardNewMacro(vtkMyCellDataToPointData); + +namespace +{ + +//---------------------------------------------------------------------------- +// Helper template function that implement the major part of the algorighm +// which will be expanded by the vtkTemplateMacro. The template function is +// provided so that coverage test can cover this function. +struct Spread +{ + template + void operator()(SrcArrayT *const srcarray, DstArrayT *const dstarray, + vtkDataSet *const src, vtkUnsignedIntArray *const num, + vtkIdType ncells, vtkIdType npoints, vtkIdType ncomps, + int highestCellDimension, int contributingCellOption) const + { + // Both arrays will have the same value type: + using T = vtk::GetAPIType; + + // zero initialization + std::fill_n(vtk::DataArrayValueRange(dstarray).begin(), + npoints * ncomps, T(0)); + + const auto srcTuples = vtk::DataArrayTupleRange(srcarray); + auto dstTuples = vtk::DataArrayTupleRange(dstarray); + + // accumulate + if (contributingCellOption != vtkMyCellDataToPointData::Patch) + { + for (vtkIdType cid = 0; cid < ncells; ++cid) + { + vtkCell *cell = src->GetCell(cid); + if (cell->GetCellDimension() >= highestCellDimension) + { + const auto srcTuple = srcTuples[cid]; + vtkIdList *pids = cell->GetPointIds(); + for (vtkIdType i = 0, I = pids->GetNumberOfIds(); i < I; ++i) + { + const vtkIdType ptId = pids->GetId(i); + auto dstTuple = dstTuples[ptId]; + // accumulate cell data to point data <==> point_data += cell_data + std::transform(srcTuple.cbegin(), + srcTuple.cend(), + dstTuple.cbegin(), + dstTuple.begin(), + std::plus()); + } + } + } + // average + for (vtkIdType pid = 0; pid < npoints; ++pid) + { + // guard against divide by zero + if (unsigned int const denom = num->GetValue(pid)) + { + // divide point data by the number of cells using it <==> + // point_data /= denum + auto dstTuple = dstTuples[pid]; + std::transform(dstTuple.cbegin(), + dstTuple.cend(), + dstTuple.begin(), + std::bind(std::divides(), std::placeholders::_1, denom)); + } + } + } + else + { // compute over cell patches + vtkNew cellsOnPoint; + std::vector data(4 * ncomps); + for (vtkIdType pid = 0; pid < npoints; ++pid) + { + std::fill(data.begin(), data.end(), 0); + T numPointCells[4] = {0, 0, 0, 0}; + // Get all cells touching this point. + src->GetPointCells(pid, cellsOnPoint); + vtkIdType numPatchCells = cellsOnPoint->GetNumberOfIds(); + for (vtkIdType pc = 0; pc < numPatchCells; pc++) + { + vtkIdType cellId = cellsOnPoint->GetId(pc); + int cellDimension = src->GetCell(cellId)->GetCellDimension(); + numPointCells[cellDimension] += 1; + const auto srcTuple = srcTuples[cellId]; + for (int comp = 0; comp < ncomps; comp++) + { + data[comp + ncomps * cellDimension] += srcTuple[comp]; + } + } + auto dstTuple = dstTuples[pid]; + for (int dimension = 3; dimension >= 0; dimension--) + { + if (numPointCells[dimension]) + { + for (int comp = 0; comp < ncomps; comp++) + { + dstTuple[comp] = data[comp + dimension * ncomps] / numPointCells[dimension]; + } + break; + } + } + } + } + } +}; + +} // end anonymous namespace + +class vtkMyCellDataToPointData::Internals +{ +public: + std::set CellDataArrays; + + // Special traversal algorithm for vtkUniformGrid and vtkRectilinearGrid to support blanking + // points will not have more than 8 cells for either of these data sets + template + int InterpolatePointDataWithMask(vtkMyCellDataToPointData *filter, T *input, vtkDataSet *output) + { + vtkNew allCellIds; + allCellIds->Allocate(8); + vtkNew cellIds; + cellIds->Allocate(8); + + vtkIdType numPts = input->GetNumberOfPoints(); + + vtkCellData *inputInCD = input->GetCellData(); + vtkCellData *inCD; + vtkPointData *outPD = output->GetPointData(); + + if (!filter->GetProcessAllArrays()) + { + inCD = vtkCellData::New(); + + for (const auto &name : this->CellDataArrays) + { + vtkAbstractArray *arr = inputInCD->GetAbstractArray(name.c_str()); + if (arr == nullptr) + { + vtkWarningWithObjectMacro(filter, "cell data array name not found."); + continue; + } + inCD->AddArray(arr); + } + } + else + { + inCD = inputInCD; + } + + outPD->InterpolateAllocate(inCD, numPts); + + double weights[8]; + + int abort = 0; + vtkIdType progressInterval = numPts / 20 + 1; + for (vtkIdType ptId = 0; ptId < numPts && !abort; ptId++) + { + if (!(ptId % progressInterval)) + { + filter->UpdateProgress(static_cast(ptId) / numPts); + abort = filter->GetAbortExecute(); + } + input->GetPointCells(ptId, allCellIds); + cellIds->Reset(); + // Only consider cells that are not masked: + for (vtkIdType cId = 0; cId < allCellIds->GetNumberOfIds(); ++cId) + { + vtkIdType curCell = allCellIds->GetId(cId); + if (input->IsCellVisible(curCell)) + { + cellIds->InsertNextId(curCell); + } + } + + vtkIdType numCells = cellIds->GetNumberOfIds(); + + if (numCells > 0) + { + double weight = 1.0 / numCells; + for (vtkIdType cellId = 0; cellId < numCells; cellId++) + { + weights[cellId] = weight; + } + outPD->InterpolatePoint(inCD, ptId, cellIds, weights); + } + else + { + outPD->NullPoint(ptId); + } + } + + if (!filter->GetProcessAllArrays()) + { + inCD->Delete(); + } + + return 1; + } +}; + +//---------------------------------------------------------------------------- +// Instantiate object so that cell data is not passed to output. +vtkMyCellDataToPointData::vtkMyCellDataToPointData() +{ + this->PassCellData = 0; + this->ContributingCellOption = vtkMyCellDataToPointData::All; + this->ProcessAllArrays = true; + this->Implementation = new Internals(); +} + +//---------------------------------------------------------------------------- +vtkMyCellDataToPointData::~vtkMyCellDataToPointData() +{ + delete this->Implementation; +} + +//---------------------------------------------------------------------------- +void vtkMyCellDataToPointData::AddCellDataArray(const char *name) +{ + if (!name) + { + vtkErrorMacro("name cannot be null."); + return; + } + + this->Implementation->CellDataArrays.insert(std::string(name)); + this->Modified(); +} + +//---------------------------------------------------------------------------- +void vtkMyCellDataToPointData::RemoveCellDataArray(const char *name) +{ + if (!name) + { + vtkErrorMacro("name cannot be null."); + return; + } + + this->Implementation->CellDataArrays.erase(name); + this->Modified(); +} + +//---------------------------------------------------------------------------- +void vtkMyCellDataToPointData::ClearCellDataArrays() +{ + if (!this->Implementation->CellDataArrays.empty()) + { + this->Modified(); + } + this->Implementation->CellDataArrays.clear(); +} + +//---------------------------------------------------------------------------- +int vtkMyCellDataToPointData::RequestData( + vtkInformation *, vtkInformationVector **inputVector, vtkInformationVector *outputVector) +{ + vtkInformation *info = outputVector->GetInformationObject(0); + vtkDataSet *output = vtkDataSet::SafeDownCast(info->Get(vtkDataObject::DATA_OBJECT())); + + vtkInformation *inInfo = inputVector[0]->GetInformationObject(0); + vtkDataSet *input = vtkDataSet::SafeDownCast(inInfo->Get(vtkDataObject::DATA_OBJECT())); + + vtkDebugMacro(<< "Mapping cell data to point data"); + + // Special traversal algorithm for unstructured grid + if (input->IsA("vtkUnstructuredGrid") || input->IsA("vtkPolyData")) + { + return this->RequestDataForUnstructuredData(nullptr, inputVector, outputVector); + } + + vtkDebugMacro(<< "Mapping cell data to point data"); + + // First, copy the input to the output as a starting point + output->CopyStructure(input); + + // Pass the point data first. The fields and attributes + // which also exist in the cell data of the input will + // be over-written during CopyAllocate + output->GetPointData()->CopyGlobalIdsOff(); + output->GetPointData()->PassData(input->GetPointData()); + output->GetPointData()->CopyFieldOff(vtkDataSetAttributes::GhostArrayName()); + + if (input->GetNumberOfPoints() < 1) + { + vtkDebugMacro(<< "No input point data!"); + return 1; + } + + // Do the interpolation, taking care of masked cells if needed. + vtkStructuredGrid *sGrid = vtkStructuredGrid::SafeDownCast(input); + vtkUniformGrid *uniformGrid = vtkUniformGrid::SafeDownCast(input); + int result; + if (sGrid && sGrid->HasAnyBlankCells()) + { + result = this->Implementation->InterpolatePointDataWithMask(this, sGrid, output); + } + else if (uniformGrid && uniformGrid->HasAnyBlankCells()) + { + result = this->Implementation->InterpolatePointDataWithMask(this, uniformGrid, output); + } + else + { + result = this->InterpolatePointData(input, output); + } + + if (result == 0) + { + return 0; + } + + if (!this->PassCellData) + { + output->GetCellData()->CopyAllOff(); + output->GetCellData()->CopyFieldOn(vtkDataSetAttributes::GhostArrayName()); + } + output->GetCellData()->PassData(input->GetCellData()); + output->GetFieldData()->PassData(input->GetFieldData()); + + return 1; +} + +//---------------------------------------------------------------------------- +void vtkMyCellDataToPointData::PrintSelf(ostream &os, vtkIndent indent) +{ + this->Superclass::PrintSelf(os, indent); + + os << indent << "PassCellData: " << (this->PassCellData ? "On\n" : "Off\n"); + os << indent << "ContributingCellOption: " << this->ContributingCellOption << endl; +} + +//---------------------------------------------------------------------------- +int vtkMyCellDataToPointData::RequestDataForUnstructuredData( + vtkInformation *, vtkInformationVector **inputVector, vtkInformationVector *outputVector) +{ + vtkDataSet *const src = vtkDataSet::SafeDownCast( + inputVector[0]->GetInformationObject(0)->Get(vtkDataObject::DATA_OBJECT())); + vtkDataSet *const dst = vtkDataSet::SafeDownCast( + outputVector->GetInformationObject(0)->Get(vtkDataObject::DATA_OBJECT())); + + vtkIdType const ncells = src->GetNumberOfCells(); + vtkIdType const npoints = src->GetNumberOfPoints(); + if (ncells < 1 || npoints < 1) + { + vtkDebugMacro(<< "No input data!"); + return 1; + } + + // count the number of cells associated with each point. if we are doing patches + // though we will do that later on. + vtkSmartPointer num; + int highestCellDimension = 0; + if (this->ContributingCellOption != vtkMyCellDataToPointData::Patch) + { + num = vtkSmartPointer::New(); + num->SetNumberOfComponents(1); + num->SetNumberOfTuples(npoints); + std::fill_n(num->GetPointer(0), npoints, 0u); + if (this->ContributingCellOption == vtkMyCellDataToPointData::DataSetMax) + { + int maxDimension = src->IsA("vtkPolyData") == 1 ? 2 : 3; + for (vtkIdType i = 0; i < src->GetNumberOfCells(); i++) + { + int dim = src->GetCell(i)->GetCellDimension(); + if (dim > highestCellDimension) + { + highestCellDimension = dim; + if (highestCellDimension == maxDimension) + { + break; + } + } + } + } + vtkNew pids; + for (vtkIdType cid = 0; cid < ncells; ++cid) + { + if (src->GetCell(cid)->GetCellDimension() >= highestCellDimension) + { + src->GetCellPoints(cid, pids); + for (vtkIdType i = 0, I = pids->GetNumberOfIds(); i < I; ++i) + { + vtkIdType const pid = pids->GetId(i); + num->SetValue(pid, num->GetValue(pid) + 1); + } + } + } + } + + // First, copy the input to the output as a starting point + dst->CopyStructure(src); + vtkPointData *const opd = dst->GetPointData(); + + // Pass the point data first. The fields and attributes + // which also exist in the cell data of the input will + // be over-written during CopyAllocate + opd->CopyGlobalIdsOff(); + opd->PassData(src->GetPointData()); + opd->CopyFieldOff(vtkDataSetAttributes::GhostArrayName()); + + // Copy all existing cell fields into a temporary cell data array, + // unless the SelectCellDataArrays option is active + vtkSmartPointer processedCellData; + if (!this->ProcessAllArrays) + { + processedCellData = vtkSmartPointer::New(); + + vtkCellData *processedCellDataTemp = src->GetCellData(); + for (const auto &name : this->Implementation->CellDataArrays) + { + vtkAbstractArray *arr = processedCellDataTemp->GetAbstractArray(name.c_str()); + if (arr == nullptr) + { + vtkWarningMacro("cell data array name not found."); + continue; + } + processedCellData->AddArray(arr); + } + } + else + { + processedCellData = vtkSmartPointer(src->GetCellData()); + } + + // Remove all fields that are not a data array. + for (vtkIdType fid = processedCellData->GetNumberOfArrays(); fid--;) + { + if (!vtkDataArray::FastDownCast(processedCellData->GetAbstractArray(fid))) + { + processedCellData->RemoveArray(fid); + } + } + + // Cell field list constructed from the filtered cell data array + vtkDataSetAttributes::FieldList cfl(1); + cfl.InitializeFieldList(processedCellData); + opd->InterpolateAllocate(cfl, npoints, npoints); + + const auto nfields = processedCellData->GetNumberOfArrays(); + int fid = 0; + auto f = [this, &fid, nfields, npoints, src, num, ncells, highestCellDimension]( + vtkAbstractArray *aa_srcarray, vtkAbstractArray *aa_dstarray) { + // update progress and check for an abort request. + this->UpdateProgress((fid + 1.0) / nfields); + ++fid; + + if (this->GetAbortExecute()) + { + return; + } + + vtkDataArray *const srcarray = vtkDataArray::FastDownCast(aa_srcarray); + vtkDataArray *const dstarray = vtkDataArray::FastDownCast(aa_dstarray); + if (srcarray && dstarray) + { + dstarray->SetNumberOfTuples(npoints); + vtkIdType const ncomps = srcarray->GetNumberOfComponents(); + + Spread worker; + using Dispatcher = vtkArrayDispatch::Dispatch2SameValueType; + if (!Dispatcher::Execute(srcarray, dstarray, worker, src, num, + ncells, npoints, ncomps, highestCellDimension, + this->ContributingCellOption)) + { // fallback for unknown arrays: + worker(srcarray, dstarray, src, num, ncells, npoints, ncomps, + highestCellDimension, this->ContributingCellOption); + } + } + }; + + if (processedCellData != nullptr && dst->GetPointData() != nullptr) + { + cfl.TransformData(0, processedCellData, dst->GetPointData(), f); + } + + if (!this->PassCellData) + { + dst->GetCellData()->CopyAllOff(); + dst->GetCellData()->CopyFieldOn(vtkDataSetAttributes::GhostArrayName()); + } + dst->GetCellData()->PassData(src->GetCellData()); + + return 1; +} + +int vtkMyCellDataToPointData::InterpolatePointData(vtkDataSet *input, vtkDataSet *output) +{ + vtkNew cellIds; + cellIds->Allocate(VTK_MAX_CELLS_PER_POINT); + + vtkIdType numPts = input->GetNumberOfPoints(); + + vtkCellData *inputInCD = input->GetCellData(); + vtkCellData *inCD; + vtkPointData *outPD = output->GetPointData(); + + if (!this->ProcessAllArrays) + { + inCD = vtkCellData::New(); + + for (const auto &name : this->Implementation->CellDataArrays) + { + vtkAbstractArray *arr = inputInCD->GetAbstractArray(name.c_str()); + if (arr == nullptr) + { + vtkWarningMacro("cell data array name not found."); + continue; + } + inCD->AddArray(arr); + } + } + else + { + inCD = inputInCD; + } + + outPD->InterpolateAllocate(inCD, numPts); + + double weights[VTK_MAX_CELLS_PER_POINT]; + + int abort = 0; + vtkIdType progressInterval = numPts / 20 + 1; + for (vtkIdType ptId = 0; ptId < numPts && !abort; ptId++) + { + if (!(ptId % progressInterval)) + { + this->UpdateProgress(static_cast(ptId) / numPts); + abort = GetAbortExecute(); + } + + input->GetPointCells(ptId, cellIds); + vtkIdType numCells = cellIds->GetNumberOfIds(); + + if (numCells > 0 && numCells < VTK_MAX_CELLS_PER_POINT) + { + double weight = 1.0 / numCells; + for (vtkIdType cellId = 0; cellId < numCells; cellId++) + { + weights[cellId] = weight; + } + outPD->InterpolatePoint(inCD, ptId, cellIds, weights); + } + else + { + outPD->NullPoint(ptId); + } + } + + if (!this->ProcessAllArrays) + { + inCD->Delete(); + } + + return 1; +} diff --git a/src/CellDataContour/plugin/CellDataContourModule/vtkMyCellDataToPointData.h b/src/CellDataContour/plugin/CellDataContourModule/vtkMyCellDataToPointData.h new file mode 100644 index 0000000..cb008e7 --- /dev/null +++ b/src/CellDataContour/plugin/CellDataContourModule/vtkMyCellDataToPointData.h @@ -0,0 +1,182 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +/*========================================================================= + + Program: Visualization Toolkit + Module: vtkMyCellDataToPointData.h + + Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen + All rights reserved. + See Copyright.txt or http://www.kitware.com/Copyright.htm for details. + + This software is distributed WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. See the above copyright notice for more information. + +=========================================================================*/ +/** + * @class vtkMyCellDataToPointData + * @brief map cell data to point data + * + * vtkMyCellDataToPointData is a filter that transforms cell data (i.e., data + * specified per cell) into point data (i.e., data specified at cell + * points). The method of transformation is based on averaging the data + * values of all cells using a particular point. For large datasets with + * several cell data arrays, the filter optionally supports selective + * processing to speed up processing. Optionally, the input cell data can + * be passed through to the output as well. + * Unstructured grids and polydata can have cells of different dimensions. + * To handle different use cases in this situation, the user can specify + * which cells contribute to the computation. The options for this are + * All (default), Patch and DataSetMax. Patch uses only the highest dimension + * cells attached to a point. DataSetMax uses the highest cell dimension in + * the entire data set. + * + * @warning + * This filter is an abstract filter, that is, the output is an abstract type + * (i.e., vtkDataSet). Use the convenience methods (e.g., + * GetPolyDataOutput(), GetStructuredPointsOutput(), etc.) to get the type + * of output you want. + * + * @sa + * vtkPointData vtkCellData vtkPointDataToCellData +*/ + +#ifndef vtkMyCellDataToPointData_h +#define vtkMyCellDataToPointData_h + +#include + +class vtkDataSet; + +class VTK_EXPORT vtkMyCellDataToPointData : public vtkDataSetAlgorithm +{ +public: + static vtkMyCellDataToPointData *New(); + vtkTypeMacro(vtkMyCellDataToPointData,vtkDataSetAlgorithm); + void PrintSelf(ostream& os, vtkIndent indent) override; + + /// Options to choose what cells contribute to the calculation + enum ContributingCellEnum { + All=0, //!< All cells + Patch=1, //!< Highest dimension cells in the patch of cells contributing to the calculation + DataSetMax=2 //!< Highest dimension cells in the data set + }; + + //@{ + /** + * Control whether the input cell data is to be passed to the output. If + * on, then the input cell data is passed through to the output; otherwise, + * only generated point data is placed into the output. + */ + vtkSetMacro(PassCellData, bool); + vtkGetMacro(PassCellData, bool); + vtkBooleanMacro(PassCellData, bool); + //@} + + //@{ + /** + * Option to specify what cells to include in the gradient computation. + * Options are all cells (All, Patch and DataSetMax). The default is All. + */ + vtkSetClampMacro(ContributingCellOption, int, 0, 2); + vtkGetMacro(ContributingCellOption, int); + //@} + + //@{ + /** + * Activate selective processing of arrays. If inactive, only arrays selected + * by the user will be considered by this filter. The default is true. + */ + vtkSetMacro(ProcessAllArrays, bool); + vtkGetMacro(ProcessAllArrays, bool); + vtkBooleanMacro(ProcessAllArrays, bool); + //@} + + /** + * Adds an array to be processed. This only has an effect if the + * ProcessAllArrays option is turned off. If a name is already present, + * nothing happens. + */ + virtual void AddCellDataArray(const char *name); + + /** + * Removes an array to be processed. This only has an effect if the + * ProcessAllArrays option is turned off. If the specified name is not + * present, nothing happens. + */ + virtual void RemoveCellDataArray(const char *name); + + /** + * Removes all arrays to be processed from the list. This only has an effect + * if the ProcessAllArrays option is turned off. + */ + virtual void ClearCellDataArrays(); + +protected: + vtkMyCellDataToPointData(); + ~vtkMyCellDataToPointData() override; + + int RequestData(vtkInformation* request, + vtkInformationVector** inputVector, + vtkInformationVector* outputVector) override; + + //@{ + /** + * Special algorithm for unstructured grids and polydata to make sure + * that we properly take into account ContributingCellOption. + */ + int RequestDataForUnstructuredData + (vtkInformation*, vtkInformationVector**, vtkInformationVector*); + //@} + + int InterpolatePointData(vtkDataSet *input, vtkDataSet *output); + + //@{ + /** + * Option to pass cell data arrays through to the output. Default is 0/off. + */ + bool PassCellData; + //@} + + //@{ + /** + * Option to specify what cells to include in the computation. + * Options are all cells (All, Patch and DataSet). The default is All. + */ + int ContributingCellOption; + //@} + + /** + * Option to activate selective processing of arrays. + */ + bool ProcessAllArrays; + + class Internals; + Internals *Implementation; + +private: + vtkMyCellDataToPointData(const vtkMyCellDataToPointData&) = delete; + void operator=(const vtkMyCellDataToPointData&) = delete; +}; + +#endif + + diff --git a/src/CellDataContour/plugin/CellDataContourModule/vtkMyContourFilter.cxx b/src/CellDataContour/plugin/CellDataContourModule/vtkMyContourFilter.cxx new file mode 100644 index 0000000..7484f77 --- /dev/null +++ b/src/CellDataContour/plugin/CellDataContourModule/vtkMyContourFilter.cxx @@ -0,0 +1,855 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +/*========================================================================= + + Program: Visualization Toolkit + Module: vtkMyContourFilter.cxx + + Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen + All rights reserved. + See Copyright.txt or http://www.kitware.com/Copyright.htm for details. + + This software is distributed WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. See the above copyright notice for more information. + +=========================================================================*/ +#include "vtkMyContourFilter.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +vtkStandardNewMacro(vtkMyContourFilter); +vtkCxxSetObjectMacro(vtkMyContourFilter,ScalarTree,vtkScalarTree); + +//----------------------------------------------------------------------------- +// Construct object with initial range (0,1) and single contour value +// of 0.0. +vtkMyContourFilter::vtkMyContourFilter() +{ + this->ContourValues = vtkContourValues::New(); + + // -1 == uninitialized. This is so we know if ComputeNormals has been set + // by the user, so that we can preserve old (broken) behavior that ignored + // this setting for certain dataset types. + this->ComputeNormals = -1; + this->ComputeGradients = 0; + this->ComputeScalars = 1; + + this->Locator = nullptr; + + this->UseScalarTree = 0; + this->ScalarTree = nullptr; + + this->OutputPointsPrecision = DEFAULT_PRECISION; + + this->GenerateTriangles = 1; + + this->SynchronizedTemplates2D = vtkSynchronizedTemplates2D::New(); + this->SynchronizedTemplates3D = vtkSynchronizedTemplates3D::New(); + this->GridSynchronizedTemplates = vtkGridSynchronizedTemplates3D::New(); + this->RectilinearSynchronizedTemplates = vtkRectilinearSynchronizedTemplates::New(); + + this->InternalProgressCallbackCommand = vtkCallbackCommand::New(); + this->InternalProgressCallbackCommand->SetCallback( + &vtkMyContourFilter::InternalProgressCallbackFunction); + this->InternalProgressCallbackCommand->SetClientData(this); + + this->SynchronizedTemplates2D->AddObserver(vtkCommand::ProgressEvent, + this->InternalProgressCallbackCommand); + this->SynchronizedTemplates3D->AddObserver(vtkCommand::ProgressEvent, + this->InternalProgressCallbackCommand); + this->GridSynchronizedTemplates->AddObserver(vtkCommand::ProgressEvent, + this->InternalProgressCallbackCommand); + this->RectilinearSynchronizedTemplates->AddObserver(vtkCommand::ProgressEvent, + this->InternalProgressCallbackCommand); + + // by default process active point scalars + this->SetInputArrayToProcess(0,0,0,vtkDataObject::FIELD_ASSOCIATION_POINTS, + vtkDataSetAttributes::SCALARS); +} + +//----------------------------------------------------------------------------- +vtkMyContourFilter::~vtkMyContourFilter() +{ + this->ContourValues->Delete(); + if ( this->Locator ) + { + this->Locator->UnRegister(this); + this->Locator = nullptr; + } + if ( this->ScalarTree ) + { + this->ScalarTree->Delete(); + this->ScalarTree = nullptr; + } + this->SynchronizedTemplates2D->Delete(); + this->SynchronizedTemplates3D->Delete(); + this->GridSynchronizedTemplates->Delete(); + this->RectilinearSynchronizedTemplates->Delete(); + this->InternalProgressCallbackCommand->Delete(); +} + +//----------------------------------------------------------------------------- +// Overload standard modified time function. If contour values are modified, +// then this object is modified as well. +vtkMTimeType vtkMyContourFilter::GetMTime() +{ + vtkMTimeType mTime=this->Superclass::GetMTime(); + vtkMTimeType time; + + if (this->ContourValues) + { + time = this->ContourValues->GetMTime(); + mTime = ( time > mTime ? time : mTime ); + } + if (this->Locator) + { + time = this->Locator->GetMTime(); + mTime = ( time > mTime ? time : mTime ); + } + + return mTime; +} + +//----------------------------------------------------------------------------- +int vtkMyContourFilter::RequestUpdateExtent(vtkInformation* request, + vtkInformationVector** inputVector, + vtkInformationVector* outputVector) +{ + vtkInformation* inInfo = inputVector[0]->GetInformationObject(0); + vtkDataSet *input = + vtkDataSet::SafeDownCast(inInfo->Get(vtkDataObject::DATA_OBJECT())); + + int numContours=this->ContourValues->GetNumberOfContours(); + double *values=this->ContourValues->GetValues(); + + vtkInformation *fInfo = + vtkDataObject::GetActiveFieldInformation(inInfo, + vtkDataObject::FIELD_ASSOCIATION_POINTS, + vtkDataSetAttributes::SCALARS); + int sType = VTK_DOUBLE; + if (fInfo) + { + sType = fInfo->Get(vtkDataObject::FIELD_ARRAY_TYPE()); + } + + // handle 2D images + int i; + if (vtkImageData::SafeDownCast(input) && sType != VTK_BIT && + !vtkUniformGrid::SafeDownCast(input)) + { + int dim = 3; + int *uExt = inInfo->Get(vtkStreamingDemandDrivenPipeline::UPDATE_EXTENT()); + if (uExt[0] == uExt[1]) + { + --dim; + } + if (uExt[2] == uExt[3]) + { + --dim; + } + if (uExt[4] == uExt[5]) + { + --dim; + } + + if ( dim == 2 ) + { + this->SynchronizedTemplates2D->SetNumberOfContours(numContours); + for (i=0; i < numContours; i++) + { + this->SynchronizedTemplates2D->SetValue(i,values[i]); + } + this->SynchronizedTemplates2D->SetComputeScalars(this->ComputeScalars); + return this->SynchronizedTemplates2D-> + ProcessRequest(request,inputVector,outputVector); + } + else if (dim == 3) + { + this->SynchronizedTemplates3D->SetNumberOfContours(numContours); + for (i=0; i < numContours; i++) + { + this->SynchronizedTemplates3D->SetValue(i,values[i]); + } + this->SynchronizedTemplates3D->SetComputeNormals(this->ComputeNormals); + this->SynchronizedTemplates3D->SetComputeGradients(this->ComputeGradients); + this->SynchronizedTemplates3D->SetComputeScalars(this->ComputeScalars); + this->SynchronizedTemplates3D->SetGenerateTriangles(this->GenerateTriangles); + return this->SynchronizedTemplates3D-> + ProcessRequest(request,inputVector,outputVector); + } + } //if image data + + // handle 3D RGrids + if (vtkRectilinearGrid::SafeDownCast(input) && sType != VTK_BIT) + { + int *uExt = inInfo->Get(vtkStreamingDemandDrivenPipeline::UPDATE_EXTENT()); + // if 3D + if (uExt[0] < uExt[1] && uExt[2] < uExt[3] && uExt[4] < uExt[5]) + { + this->RectilinearSynchronizedTemplates->SetNumberOfContours(numContours); + for (i=0; i < numContours; i++) + { + this->RectilinearSynchronizedTemplates->SetValue(i,values[i]); + } + this->RectilinearSynchronizedTemplates->SetComputeNormals(this->ComputeNormals); + this->RectilinearSynchronizedTemplates->SetComputeGradients(this->ComputeGradients); + this->RectilinearSynchronizedTemplates->SetComputeScalars(this->ComputeScalars); + this->RectilinearSynchronizedTemplates->SetGenerateTriangles(this->GenerateTriangles); + return this->RectilinearSynchronizedTemplates-> + ProcessRequest(request,inputVector,outputVector); + } + } //if 3D RGrid + + // handle 3D SGrids + if (vtkStructuredGrid::SafeDownCast(input) && sType != VTK_BIT) + { + int *uExt = inInfo->Get(vtkStreamingDemandDrivenPipeline::UPDATE_EXTENT()); + // if 3D + if (uExt[0] < uExt[1] && uExt[2] < uExt[3] && uExt[4] < uExt[5]) + { + this->GridSynchronizedTemplates->SetNumberOfContours(numContours); + for (i=0; i < numContours; i++) + { + this->GridSynchronizedTemplates->SetValue(i,values[i]); + } + this->GridSynchronizedTemplates->SetComputeNormals(this->ComputeNormals); + this->GridSynchronizedTemplates->SetComputeGradients(this->ComputeGradients); + this->GridSynchronizedTemplates->SetComputeScalars(this->ComputeScalars); + this->GridSynchronizedTemplates->SetOutputPointsPrecision(this->OutputPointsPrecision); + this->GridSynchronizedTemplates->SetGenerateTriangles(this->GenerateTriangles); + return this->GridSynchronizedTemplates-> + ProcessRequest(request,inputVector,outputVector); + } + } //if 3D SGrid + + inInfo->Set(vtkStreamingDemandDrivenPipeline::EXACT_EXTENT(), 1); + return 1; +} + +//----------------------------------------------------------------------------- +// General contouring filter. Handles arbitrary input. +// +int vtkMyContourFilter::RequestData( + vtkInformation* request, + vtkInformationVector** inputVector, + vtkInformationVector* outputVector) +{ + // get the input + vtkInformation* inInfo = inputVector[0]->GetInformationObject(0); + vtkDataSet *input = vtkDataSet::SafeDownCast( + inInfo->Get(vtkDataObject::DATA_OBJECT())); + if (!input) + { + return 0; + } + + vtkDataArray* inScalars = this->GetInputArrayToProcess(0, inputVector); + // is the input array a cell data ? + int association = this->GetInputArrayAssociation(0, inputVector); + vtkNew cd2pd; + vtkNew cd2pdInputVector; + vtkNew arrayInfo; + if (association == vtkDataObject::FIELD_ASSOCIATION_CELLS) + { + const char* name = inScalars->GetName(); + cd2pd->SetInputData(input); + cd2pd->SetProcessAllArrays(false); + cd2pd->AddCellDataArray(name); + cd2pd->Update(); + input = cd2pd->GetOutput(); + inScalars = input->GetPointData()->GetArray(name); + + cd2pdInputVector->Copy(inputVector[0]); + inInfo = cd2pdInputVector->GetInformationObject(0); + inInfo->Set(vtkDataObject::DATA_OBJECT(), input); + vtkInformationVector* tmp = cd2pdInputVector.Get(); + inputVector = &tmp; + + arrayInfo->Copy(this->GetInputArrayInformation(0)); + arrayInfo->Set(vtkDataObject::FIELD_ASSOCIATION(), vtkDataObject::FIELD_ASSOCIATION_POINTS); + this->SetInputArrayToProcess(0, arrayInfo); + } + else if (association != vtkDataObject::FIELD_ASSOCIATION_POINTS) + { + vtkErrorMacro("Invalid Array Association"); + return 1; + } + + // get the contours + int numContours = this->ContourValues->GetNumberOfContours(); + double *values = this->ContourValues->GetValues(); + int i; + + // is there data to process? + if (!inScalars) + { + return 1; + } + + int sType = inScalars->GetDataType(); + + // handle 2D images + if (vtkImageData::SafeDownCast(input) && sType != VTK_BIT && + !vtkUniformGrid::SafeDownCast(input)) + { + int dim = 3; + int *uExt = inInfo->Get(vtkStreamingDemandDrivenPipeline::UPDATE_EXTENT()); + if (uExt[0] == uExt[1]) + { + --dim; + } + if (uExt[2] == uExt[3]) + { + --dim; + } + if (uExt[4] == uExt[5]) + { + --dim; + } + + if ( dim == 2 ) + { + this->SynchronizedTemplates2D->SetNumberOfContours(numContours); + for (i=0; i < numContours; i++) + { + this->SynchronizedTemplates2D->SetValue(i,values[i]); + } + this->SynchronizedTemplates2D-> + SetInputArrayToProcess(0,this->GetInputArrayInformation(0)); + return + this->SynchronizedTemplates2D->ProcessRequest(request,inputVector,outputVector); + } + else if ( dim == 3 ) + { + this->SynchronizedTemplates3D->SetNumberOfContours(numContours); + for (i=0; i < numContours; i++) + { + this->SynchronizedTemplates3D->SetValue(i,values[i]); + } + this->SynchronizedTemplates3D->SetComputeNormals(this->ComputeNormals); + this->SynchronizedTemplates3D->SetComputeGradients(this->ComputeGradients); + this->SynchronizedTemplates3D->SetComputeScalars(this->ComputeScalars); + this->SynchronizedTemplates3D->SetGenerateTriangles(this->GenerateTriangles); + this->SynchronizedTemplates3D-> + SetInputArrayToProcess(0,this->GetInputArrayInformation(0)); + + return this->SynchronizedTemplates3D->ProcessRequest(request,inputVector,outputVector); + } + } //if image data + + // handle 3D RGrids + if (vtkRectilinearGrid::SafeDownCast(input) && sType != VTK_BIT) + { + int *uExt = inInfo->Get(vtkStreamingDemandDrivenPipeline::UPDATE_EXTENT()); + // if 3D + if (uExt[0] < uExt[1] && uExt[2] < uExt[3] && uExt[4] < uExt[5]) + { + this->RectilinearSynchronizedTemplates->SetNumberOfContours(numContours); + for (i=0; i < numContours; i++) + { + this->RectilinearSynchronizedTemplates->SetValue(i,values[i]); + } + this->RectilinearSynchronizedTemplates->SetComputeNormals(this->ComputeNormals); + this->RectilinearSynchronizedTemplates->SetComputeGradients(this->ComputeGradients); + this->RectilinearSynchronizedTemplates->SetComputeScalars(this->ComputeScalars); + this->RectilinearSynchronizedTemplates->SetGenerateTriangles(this->GenerateTriangles); + this->RectilinearSynchronizedTemplates-> + SetInputArrayToProcess(0,this->GetInputArrayInformation(0)); + return this->RectilinearSynchronizedTemplates-> + ProcessRequest(request,inputVector,outputVector); + } + } // if 3D Rgrid + + // handle 3D SGrids + if (vtkStructuredGrid::SafeDownCast(input) && sType != VTK_BIT) + { + int *uExt = inInfo->Get(vtkStreamingDemandDrivenPipeline::UPDATE_EXTENT()); + // if 3D + if (uExt[0] < uExt[1] && uExt[2] < uExt[3] && uExt[4] < uExt[5]) + { + this->GridSynchronizedTemplates->SetNumberOfContours(numContours); + for (i=0; i < numContours; i++) + { + this->GridSynchronizedTemplates->SetValue(i,values[i]); + } + this->GridSynchronizedTemplates->SetComputeNormals(this->ComputeNormals); + this->GridSynchronizedTemplates->SetComputeGradients(this->ComputeGradients); + this->GridSynchronizedTemplates->SetComputeScalars(this->ComputeScalars); + this->GridSynchronizedTemplates->SetOutputPointsPrecision(this->OutputPointsPrecision); + this->GridSynchronizedTemplates->SetGenerateTriangles(this->GenerateTriangles); + this->GridSynchronizedTemplates-> + SetInputArrayToProcess(0,this->GetInputArrayInformation(0)); + return this->GridSynchronizedTemplates-> + ProcessRequest(request,inputVector,outputVector); + } + } //if 3D SGrid + + vtkIdType cellId; + int abortExecute=0; + vtkIdList *cellPts; + vtkCellArray *newVerts, *newLines, *newPolys; + vtkPoints *newPts; + vtkIdType numCells, estimatedSize; + vtkDataArray *cellScalars; + + vtkInformation* info = outputVector->GetInformationObject(0); + vtkPolyData *output = vtkPolyData::SafeDownCast( + info->Get(vtkDataObject::DATA_OBJECT())); + if (!output) {return 0;} + + vtkPointData *inPdOriginal = input->GetPointData(); + + // We don't want to change the active scalars in the input, but we + // need to set the active scalars to match the input array to + // process so that the point data copying works as expected. Create + // a shallow copy of point data so that we can do this without + // changing the input. + vtkSmartPointer inPd = vtkSmartPointer::New(); + inPd->ShallowCopy(inPdOriginal); + + // Keep track of the old active scalars because when we set the new + // scalars, the old scalars are removed from the point data entirely + // and we have to add them back. + vtkAbstractArray* oldScalars = inPd->GetScalars(); + inPd->SetScalars(inScalars); + if (oldScalars) + { + inPd->AddArray(oldScalars); + } + vtkPointData *outPd = output->GetPointData(); + + vtkCellData *inCd = input->GetCellData(); + vtkCellData *outCd = output->GetCellData(); + + vtkDebugMacro(<< "Executing contour filter"); + if (input->IsA("vtkUnstructuredGridBase")) + { + vtkDebugMacro(<< "Processing unstructured grid"); + vtkContourGrid *cgrid; + + cgrid = vtkContourGrid::New(); + cgrid->SetInputData(input); + // currently vtkContourGrid has a ComputeGradients option + // but this doesn't do anything and will soon be deprecated. + cgrid->SetComputeNormals(this->ComputeNormals); + cgrid->SetComputeScalars(this->ComputeScalars); + cgrid->SetOutputPointsPrecision(this->OutputPointsPrecision); + cgrid->SetGenerateTriangles(this->GenerateTriangles); + cgrid->SetUseScalarTree(this->UseScalarTree); + if ( this->UseScalarTree ) //special treatment to reuse it + { + if ( this->ScalarTree == nullptr ) + { + this->ScalarTree = vtkSpanSpace::New(); + } + this->ScalarTree->SetDataSet(input); + cgrid->SetScalarTree(this->ScalarTree); + } + if ( this->Locator ) + { + cgrid->SetLocator( this->Locator ); + } + + for (i = 0; i < numContours; i++) + { + cgrid->SetValue(i, values[i]); + } + cgrid->SetInputArrayToProcess(0,this->GetInputArrayInformation(0)); + cgrid->UpdatePiece( + info->Get(vtkStreamingDemandDrivenPipeline::UPDATE_PIECE_NUMBER()), + info->Get(vtkStreamingDemandDrivenPipeline:: UPDATE_NUMBER_OF_PIECES()), + info->Get(vtkStreamingDemandDrivenPipeline::UPDATE_NUMBER_OF_GHOST_LEVELS())); + + output->ShallowCopy(cgrid->GetOutput()); + cgrid->Delete(); + } //if type VTK_UNSTRUCTURED_GRID + else + { + numCells = input->GetNumberOfCells(); + inScalars = this->GetInputArrayToProcess(0,inputVector); + if ( ! inScalars || numCells < 1 ) + { + vtkDebugMacro(<<"No data to contour"); + return 1; + } + + // Create objects to hold output of contour operation. First estimate + // allocation size. + // + estimatedSize= + static_cast(pow(static_cast(numCells),.75)); + estimatedSize *= numContours; + estimatedSize = estimatedSize / 1024 * 1024; //multiple of 1024 + if (estimatedSize < 1024) + { + estimatedSize = 1024; + } + + newPts = vtkPoints::New(); + // set precision for the points in the output + if(this->OutputPointsPrecision == vtkAlgorithm::DEFAULT_PRECISION) + { + vtkPointSet *inputPointSet = vtkPointSet::SafeDownCast(input); + if(inputPointSet) + { + newPts->SetDataType(inputPointSet->GetPoints()->GetDataType()); + } + else + { + newPts->SetDataType(VTK_FLOAT); + } + } + else if(this->OutputPointsPrecision == vtkAlgorithm::SINGLE_PRECISION) + { + newPts->SetDataType(VTK_FLOAT); + } + else if(this->OutputPointsPrecision == vtkAlgorithm::DOUBLE_PRECISION) + { + newPts->SetDataType(VTK_DOUBLE); + } + newPts->Allocate(estimatedSize,estimatedSize); + newVerts = vtkCellArray::New(); + newVerts->Allocate(estimatedSize,estimatedSize); + newLines = vtkCellArray::New(); + newLines->Allocate(estimatedSize,estimatedSize); + newPolys = vtkCellArray::New(); + newPolys->Allocate(estimatedSize,estimatedSize); + cellScalars = inScalars->NewInstance(); + cellScalars->SetNumberOfComponents(inScalars->GetNumberOfComponents()); + cellScalars->Allocate(cellScalars->GetNumberOfComponents()*VTK_CELL_SIZE); + + // locator used to merge potentially duplicate points + if ( this->Locator == nullptr ) + { + this->CreateDefaultLocator(); + } + this->Locator->InitPointInsertion (newPts, + input->GetBounds(), + input->GetNumberOfPoints()); + + // interpolate data along edge + // if we did not ask for scalars to be computed, don't copy them + if (!this->ComputeScalars) + { + outPd->CopyScalarsOff(); + } + outPd->InterpolateAllocate(inPd,estimatedSize,estimatedSize); + outCd->CopyAllocate(inCd,estimatedSize,estimatedSize); + + vtkContourHelper helper(this->Locator, newVerts, newLines, newPolys, inPd, inCd, outPd,outCd, estimatedSize, this->GenerateTriangles!=0); + // If enabled, build a scalar tree to accelerate search + // + if ( !this->UseScalarTree ) + { + vtkGenericCell *cell = vtkGenericCell::New(); + // Three passes over the cells to process lower dimensional cells first. + // For poly data output cells need to be added in the order: + // verts, lines and then polys, or cell data gets mixed up. + // A better solution is to have an unstructured grid output. + // I create a table that maps cell type to cell dimensionality, + // because I need a fast way to get cell dimensionality. + // This assumes GetCell is slow and GetCellType is fast. + // I do not like hard coding a list of cell types here, + // but I do not want to add GetCellDimension(vtkIdType cellId) + // to the vtkDataSet API. Since I anticipate that the output + // will change to vtkUnstructuredGrid. This temporary solution + // is acceptable. + // + int cellType; + unsigned char cellTypeDimensions[VTK_NUMBER_OF_CELL_TYPES]; + vtkCutter::GetCellTypeDimensions(cellTypeDimensions); + int dimensionality; + // We skip 0d cells (points), because they cannot be cut (generate no data). + for (dimensionality = 1; dimensionality <= 3; ++dimensionality) + { + // Loop over all cells; get scalar values for all cell points + // and process each cell. + // + for (cellId=0; cellId < numCells && !abortExecute; cellId++) + { + // I assume that "GetCellType" is fast. + cellType = input->GetCellType(cellId); + if (cellType >= VTK_NUMBER_OF_CELL_TYPES) + { // Protect against new cell types added. + vtkErrorMacro("Unknown cell type " << cellType); + continue; + } + if (cellTypeDimensions[cellType] != dimensionality) + { + continue; + } + input->GetCell(cellId,cell); + cellPts = cell->GetPointIds(); + if (cellScalars->GetSize()/cellScalars->GetNumberOfComponents() < + cellPts->GetNumberOfIds()) + { + cellScalars->Allocate( + cellScalars->GetNumberOfComponents()*cellPts->GetNumberOfIds()); + } + inScalars->GetTuples(cellPts,cellScalars); + + if (dimensionality == 3 && ! (cellId % 5000) ) + { + vtkDebugMacro(<<"Contouring #" << cellId); + this->UpdateProgress (static_cast(cellId)/numCells); + abortExecute = this->GetAbortExecute(); + } + + for (i=0; i < numContours; i++) + { + helper.Contour(cell,values[i],cellScalars,cellId); + } // for all contour values + } // for all cells + } // for all dimensions + cell->Delete(); + } //if using scalar tree + else + { + vtkCell *cell; + // Note: This will have problems when input contains 2D and 3D cells. + // CellData will get scrabled because of the implicit ordering of + // verts, lines and polys in vtkPolyData. The solution + // is to convert this filter to create unstructured grid. + // + // Loop over all contour values. Then for each contour value, + // loop over all cells. + // + for (i=0; i < numContours; i++) + { + for ( this->ScalarTree->InitTraversal(values[i]); + (cell=this->ScalarTree->GetNextCell(cellId,cellPts,cellScalars)) != nullptr; ) + { + helper.Contour(cell,values[i],cellScalars,cellId); + } //for all cells + } //for all contour values + } //using scalar tree + + vtkDebugMacro(<<"Created: " + << newPts->GetNumberOfPoints() << " points, " + << newVerts->GetNumberOfCells() << " verts, " + << newLines->GetNumberOfCells() << " lines, " + << newPolys->GetNumberOfCells() << " triangles"); + + // Update ourselves. Because we don't know up front how many verts, lines, + // polys we've created, take care to reclaim memory. + // + output->SetPoints(newPts); + newPts->Delete(); + cellScalars->Delete(); + + if (newVerts->GetNumberOfCells()) + { + output->SetVerts(newVerts); + } + newVerts->Delete(); + + if (newLines->GetNumberOfCells()) + { + output->SetLines(newLines); + } + newLines->Delete(); + + if (newPolys->GetNumberOfCells()) + { + output->SetPolys(newPolys); + } + newPolys->Delete(); + + // -1 == uninitialized. This setting used to be ignored, and we preserve the + // old behavior for backward compatibility. Normals will be computed here + // if and only if the user has explicitly set the option. + if (this->ComputeNormals != 0 && this->ComputeNormals != -1) + { + vtkNew normalsFilter; + normalsFilter->SetOutputPointsPrecision(this->OutputPointsPrecision); + vtkNew tempInput; + tempInput->ShallowCopy(output); + normalsFilter->SetInputData(tempInput); + normalsFilter->SetFeatureAngle(180.); + normalsFilter->UpdatePiece( + info->Get(vtkStreamingDemandDrivenPipeline::UPDATE_PIECE_NUMBER()), + info->Get(vtkStreamingDemandDrivenPipeline::UPDATE_NUMBER_OF_PIECES()), + info->Get(vtkStreamingDemandDrivenPipeline:: + UPDATE_NUMBER_OF_GHOST_LEVELS())); + output->ShallowCopy(normalsFilter->GetOutput()); + } + + this->Locator->Initialize();//releases leftover memory + output->Squeeze(); + } //else if not vtkUnstructuredGrid + + return 1; +} + +//----------------------------------------------------------------------------- +// Specify a spatial locator for merging points. By default, +// an instance of vtkMergePoints is used. +void vtkMyContourFilter::SetLocator(vtkIncrementalPointLocator *locator) +{ + if ( this->Locator == locator ) + { + return; + } + if ( this->Locator ) + { + this->Locator->UnRegister(this); + this->Locator = nullptr; + } + if ( locator ) + { + locator->Register(this); + } + this->Locator = locator; + this->Modified(); +} + +//----------------------------------------------------------------------------- +void vtkMyContourFilter::CreateDefaultLocator() +{ + if ( this->Locator == nullptr ) + { + this->Locator = vtkMergePoints::New(); + this->Locator->Register(this); + this->Locator->Delete(); + } +} + +//----------------------------------------------------------------------------- +void vtkMyContourFilter::SetArrayComponent( int comp ) +{ + this->SynchronizedTemplates2D->SetArrayComponent( comp ); + this->SynchronizedTemplates3D->SetArrayComponent( comp ); + this->RectilinearSynchronizedTemplates->SetArrayComponent( comp ); +} + +//----------------------------------------------------------------------------- +int vtkMyContourFilter::GetArrayComponent() +{ + return( this->SynchronizedTemplates2D->GetArrayComponent() ); +} + +//----------------------------------------------------------------------------- +void vtkMyContourFilter::SetOutputPointsPrecision(int precision) +{ + this->OutputPointsPrecision = precision; + this->Modified(); +} + +//----------------------------------------------------------------------------- +int vtkMyContourFilter::GetOutputPointsPrecision() const +{ + return this->OutputPointsPrecision; +} + +//----------------------------------------------------------------------------- +int vtkMyContourFilter::FillInputPortInformation(int, vtkInformation *info) +{ + info->Set(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkDataSet"); + return 1; +} + +void vtkMyContourFilter::PrintSelf(ostream& os, vtkIndent indent) +{ + this->Superclass::PrintSelf(os,indent); + + os << indent << "Compute Gradients: " + << (this->ComputeGradients ? "On\n" : "Off\n"); + os << indent << "Compute Normals: " + << (this->ComputeNormals ? "On\n" : "Off\n"); + os << indent << "Compute Scalars: " + << (this->ComputeScalars ? "On\n" : "Off\n"); + + this->ContourValues->PrintSelf(os,indent.GetNextIndent()); + + os << indent << "Use Scalar Tree: " + << (this->UseScalarTree ? "On\n" : "Off\n"); + if ( this->ScalarTree ) + { + os << indent << "Scalar Tree: " << this->ScalarTree << "\n"; + } + else + { + os << indent << "Scalar Tree: (none)\n"; + } + + if ( this->Locator ) + { + os << indent << "Locator: " << this->Locator << "\n"; + } + else + { + os << indent << "Locator: (none)\n"; + } + + os << indent << "Precision of the output points: " + << this->OutputPointsPrecision << "\n"; +} + +//---------------------------------------------------------------------------- +void vtkMyContourFilter::ReportReferences(vtkGarbageCollector* collector) +{ + this->Superclass::ReportReferences(collector); + // These filters share our input and are therefore involved in a + // reference loop. + vtkGarbageCollectorReport(collector, this->ScalarTree, "ScalarTree"); +} + +//---------------------------------------------------------------------------- +void vtkMyContourFilter::InternalProgressCallbackFunction(vtkObject *vtkNotUsed(caller), + unsigned long vtkNotUsed(eid), + void *clientData, + void *callData) +{ + vtkMyContourFilter *contourFilter = static_cast(clientData); + double progress = *static_cast(callData); + contourFilter->UpdateProgress(progress); +} diff --git a/src/CellDataContour/plugin/CellDataContourModule/vtkMyContourFilter.h b/src/CellDataContour/plugin/CellDataContourModule/vtkMyContourFilter.h new file mode 100644 index 0000000..995969c --- /dev/null +++ b/src/CellDataContour/plugin/CellDataContourModule/vtkMyContourFilter.h @@ -0,0 +1,317 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +/*========================================================================= + + Program: Visualization Toolkit + Module: vtkMyContourFilter.h + + Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen + All rights reserved. + See Copyright.txt or http://www.kitware.com/Copyright.htm for details. + + This software is distributed WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. See the above copyright notice for more information. + +=========================================================================*/ +/** + * @class vtkMyContourFilter + * @brief generate isosurfaces/isolines from scalar values + * + * vtkMyContourFilter is a filter that takes as input any dataset and + * generates on output isosurfaces and/or isolines. The exact form + * of the output depends upon the dimensionality of the input data. + * Data consisting of 3D cells will generate isosurfaces, data + * consisting of 2D cells will generate isolines, and data with 1D + * or 0D cells will generate isopoints. Combinations of output type + * are possible if the input dimension is mixed. + * + * To use this filter you must specify one or more contour values. + * You can either use the method SetValue() to specify each contour + * value, or use GenerateValues() to generate a series of evenly + * spaced contours. It is also possible to accelerate the operation of + * this filter (at the cost of extra memory) by using a + * vtkScalarTree. A scalar tree is used to quickly locate cells that + * contain a contour surface. This is especially effective if multiple + * contours are being extracted. If you want to use a scalar tree, + * invoke the method UseScalarTreeOn(). + * + * @warning + * For unstructured data or structured grids, normals and gradients + * are not computed. Use vtkPolyDataNormals to compute the surface + * normals. + * + * @sa + * vtkFlyingEdges3D vtkFlyingEdges2D vtkDiscreteFlyingEdges3D + * vtkDiscreteFlyingEdges2D vtkMarchingContourFilter vtkMarchingCubes + * vtkSliceCubes vtkMarchingSquares vtkImageMarchingCubes +*/ + +#ifndef vtkMyContourFilter_h +#define vtkMyContourFilter_h + +#include + +#include "vtkContourValues.h" // Needed for inline methods + +#include "vtkMyCellDataToPointData.h" + +class vtkIncrementalPointLocator; +class vtkScalarTree; +class vtkSynchronizedTemplates2D; +class vtkSynchronizedTemplates3D; +class vtkGridSynchronizedTemplates3D; +class vtkRectilinearSynchronizedTemplates; +class vtkCallbackCommand; + +class VTK_EXPORT vtkMyContourFilter : public vtkPolyDataAlgorithm +{ +public: + vtkTypeMacro(vtkMyContourFilter,vtkPolyDataAlgorithm); + void PrintSelf(ostream& os, vtkIndent indent) override; + + /** + * Construct object with initial range (0,1) and single contour value + * of 0.0. + */ + static vtkMyContourFilter *New(); + + //@{ + /** + * Methods to set / get contour values. + */ + void SetValue(int i, double value); + double GetValue(int i); + double *GetValues(); + void GetValues(double *contourValues); + void SetNumberOfContours(int number); + int GetNumberOfContours(); + void GenerateValues(int numContours, double range[2]); + void GenerateValues(int numContours, double rangeStart, double rangeEnd); + //@} + + /** + * Modified GetMTime Because we delegate to vtkContourValues + */ + vtkMTimeType GetMTime() override; + + //@{ + /** + * Set/Get the computation of normals. Normal computation is fairly + * expensive in both time and storage. If the output data will be + * processed by filters that modify topology or geometry, it may be + * wise to turn Normals and Gradients off. + * This setting defaults to On for vtkImageData, vtkRectilinearGrid, + * vtkStructuredGrid, and vtkUnstructuredGrid inputs, and Off for all others. + * This default behavior is to preserve the behavior of an older version of + * this filter, which would ignore this setting for certain inputs. + */ + vtkSetMacro(ComputeNormals, bool); + vtkGetMacro(ComputeNormals, bool); + vtkBooleanMacro(ComputeNormals, bool); + //@} + + //@{ + /** + * Set/Get the computation of gradients. Gradient computation is + * fairly expensive in both time and storage. Note that if + * ComputeNormals is on, gradients will have to be calculated, but + * will not be stored in the output dataset. If the output data + * will be processed by filters that modify topology or geometry, it + * may be wise to turn Normals and Gradients off. + */ + vtkSetMacro(ComputeGradients, bool); + vtkGetMacro(ComputeGradients, bool); + vtkBooleanMacro(ComputeGradients, bool); + //@} + + //@{ + /** + * Set/Get the computation of scalars. + */ + vtkSetMacro(ComputeScalars, bool); + vtkGetMacro(ComputeScalars, bool); + vtkBooleanMacro(ComputeScalars, bool); + //@} + + //@{ + /** + * Enable the use of a scalar tree to accelerate contour extraction. + */ + vtkSetMacro(UseScalarTree, bool); + vtkGetMacro(UseScalarTree, bool); + vtkBooleanMacro(UseScalarTree, bool); + //@} + + //@{ + /** + * Enable the use of a scalar tree to accelerate contour extraction. + */ + virtual void SetScalarTree(vtkScalarTree*); + vtkGetObjectMacro(ScalarTree,vtkScalarTree); + //@} + + //@{ + /** + * Set / get a spatial locator for merging points. By default, + * an instance of vtkMergePoints is used. + */ + void SetLocator(vtkIncrementalPointLocator *locator); + vtkGetObjectMacro(Locator,vtkIncrementalPointLocator); + //@} + + /** + * Create default locator. Used to create one when none is + * specified. The locator is used to merge coincident points. + */ + void CreateDefaultLocator(); + + //@{ + /** + * Set/get which component of the scalar array to contour on; defaults to 0. + * Currently this feature only works if the input is a vtkImageData. + */ + void SetArrayComponent(int); + int GetArrayComponent(); + //@} + + + //@{ + /** + * If this is enabled (by default), the output will be triangles + * otherwise, the output will be the intersection polygon + * WARNING: if the contour surface is not planar, the output + * polygon will not be planar, which might be nice to look at but hard + * to compute with downstream. + */ + vtkSetMacro(GenerateTriangles, bool); + vtkGetMacro(GenerateTriangles, bool); + vtkBooleanMacro(GenerateTriangles, bool); + //@} + + //@{ + /** + * Set/get the desired precision for the output types. See the documentation + * for the vtkAlgorithm::Precision enum for an explanation of the available + * precision settings. + */ + void SetOutputPointsPrecision(int precision); + int GetOutputPointsPrecision() const; + //@} + +protected: + vtkMyContourFilter(); + ~vtkMyContourFilter() override; + + void ReportReferences(vtkGarbageCollector*) override; + + int RequestData(vtkInformation* request, + vtkInformationVector** inputVector, + vtkInformationVector* outputVector) override; + int RequestUpdateExtent(vtkInformation*, + vtkInformationVector**, + vtkInformationVector*) override; + int FillInputPortInformation(int port, vtkInformation *info) override; + + vtkContourValues *ContourValues; + bool ComputeNormals; + bool ComputeGradients; + bool ComputeScalars; + vtkIncrementalPointLocator *Locator; + bool UseScalarTree; + vtkScalarTree *ScalarTree; + int OutputPointsPrecision; + bool GenerateTriangles; + + vtkSynchronizedTemplates2D *SynchronizedTemplates2D; + vtkSynchronizedTemplates3D *SynchronizedTemplates3D; + vtkGridSynchronizedTemplates3D *GridSynchronizedTemplates; + vtkRectilinearSynchronizedTemplates *RectilinearSynchronizedTemplates; + vtkCallbackCommand *InternalProgressCallbackCommand; + + static void InternalProgressCallbackFunction(vtkObject *caller, + unsigned long eid, + void *clientData, + void *callData); + +private: + vtkMyContourFilter(const vtkMyContourFilter&) = delete; + void operator=(const vtkMyContourFilter&) = delete; +}; + +/** + * Set a particular contour value at contour number i. The index i ranges + * between 0<=iContourValues->SetValue(i,value);} + +/** + * Get the ith contour value. + */ +inline double vtkMyContourFilter::GetValue(int i) +{return this->ContourValues->GetValue(i);} + +/** + * Get a pointer to an array of contour values. There will be + * GetNumberOfContours() values in the list. + */ +inline double *vtkMyContourFilter::GetValues() +{return this->ContourValues->GetValues();} + +/** + * Fill a supplied list with contour values. There will be + * GetNumberOfContours() values in the list. Make sure you allocate + * enough memory to hold the list. + */ +inline void vtkMyContourFilter::GetValues(double *contourValues) +{this->ContourValues->GetValues(contourValues);} + +/** + * Set the number of contours to place into the list. You only really + * need to use this method to reduce list size. The method SetValue() + * will automatically increase list size as needed. + */ +inline void vtkMyContourFilter::SetNumberOfContours(int number) +{this->ContourValues->SetNumberOfContours(number);} + +/** + * Get the number of contours in the list of contour values. + */ +inline int vtkMyContourFilter::GetNumberOfContours() +{return this->ContourValues->GetNumberOfContours();} + +/** + * Generate numContours equally spaced contour values between specified + * range. Contour values will include min/max range values. + */ +inline void vtkMyContourFilter::GenerateValues(int numContours, double range[2]) +{this->ContourValues->GenerateValues(numContours, range);} + +/** + * Generate numContours equally spaced contour values between specified + * range. Contour values will include min/max range values. + */ +inline void vtkMyContourFilter::GenerateValues(int numContours, double + rangeStart, double rangeEnd) +{this->ContourValues->GenerateValues(numContours, rangeStart, rangeEnd);} + + +#endif diff --git a/src/CellDataContour/plugin/CellDataContourModule/vtkMyPVContourFilter.cxx b/src/CellDataContour/plugin/CellDataContourModule/vtkMyPVContourFilter.cxx new file mode 100644 index 0000000..2205421 --- /dev/null +++ b/src/CellDataContour/plugin/CellDataContourModule/vtkMyPVContourFilter.cxx @@ -0,0 +1,266 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +/*========================================================================= + + Program: Visualization Toolkit + Module: vtkMyContourFilter.h + + Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen + All rights reserved. + See Copyright.txt or http://www.kitware.com/Copyright.htm for details. + + This software is distributed WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. See the above copyright notice for more information. + +=========================================================================*/ + +#include "vtkMyPVContourFilter.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +vtkStandardNewMacro(vtkMyPVContourFilter); + +//----------------------------------------------------------------------------- +vtkMyPVContourFilter::vtkMyPVContourFilter() + : vtkMyContourFilter() +{ + this->SetComputeNormals(true); +} + +//----------------------------------------------------------------------------- +vtkMyPVContourFilter::~vtkMyPVContourFilter() +{ +} + +//----------------------------------------------------------------------------- +void vtkMyPVContourFilter::PrintSelf(ostream& os, vtkIndent indent) +{ + this->Superclass::PrintSelf(os, indent); +} + +//----------------------------------------------------------------------------- +int vtkMyPVContourFilter::ProcessRequest( + vtkInformation* request, vtkInformationVector** inputVector, vtkInformationVector* outputVector) +{ + // create the output + if (request->Has(vtkDemandDrivenPipeline::REQUEST_DATA_OBJECT())) + { + return this->RequestDataObject(request, inputVector, outputVector); + } + + return this->Superclass::ProcessRequest(request, inputVector, outputVector); +} + +//----------------------------------------------------------------------------- +int vtkMyPVContourFilter::RequestData( + vtkInformation* request, vtkInformationVector** inputVector, vtkInformationVector* outputVector) +{ + vtkInformation* inInfo = inputVector[0]->GetInformationObject(0); + if (!inInfo) + { + vtkErrorMacro("Failed to get input information."); + return 1; + } + + vtkDataObject* inDataObj = inInfo->Get(vtkDataObject::DATA_OBJECT()); + if (!inDataObj) + { + vtkErrorMacro("Failed to get input data object."); + return 1; + } + + vtkInformation* outInfo = outputVector->GetInformationObject(0); + if (!outInfo) + { + vtkErrorMacro("Failed to get output information."); + return 1; + } + + vtkDataObject* outDataObj = outInfo->Get(vtkDataObject::DATA_OBJECT()); + if (!outDataObj) + { + vtkErrorMacro("Failed get output data object."); + return 1; + } + + // Check if input is AMR data. + if (vtkHierarchicalBoxDataSet::SafeDownCast(inDataObj)) + { + // This is a lot to go through to get the name of the array to process. + vtkInformation* inArrayInfo = this->GetInputArrayInformation(0); + if (!inArrayInfo) + { + vtkErrorMacro("Problem getting name of array to process."); + return 0; + } + int fieldAssociation = -1; + if (!inArrayInfo->Has(vtkDataObject::FIELD_ASSOCIATION())) + { + vtkErrorMacro("Unable to query field association for the scalar."); + return 0; + } + fieldAssociation = inArrayInfo->Get(vtkDataObject::FIELD_ASSOCIATION()); + if (fieldAssociation == vtkDataObject::FIELD_ASSOCIATION_CELLS) + { + vtkSmartPointer amrDC(vtkSmartPointer::New()); + + amrDC->SetInputData(0, inDataObj); + amrDC->SetInputArrayToProcess(0, inArrayInfo); + amrDC->SetEnableCapping(1); + amrDC->SetEnableDegenerateCells(1); + amrDC->SetEnableMultiProcessCommunication(1); + amrDC->SetSkipGhostCopy(1); + amrDC->SetTriangulateCap(1); + amrDC->SetEnableMergePoints(1); + + for (int i = 0; i < this->GetNumberOfContours(); ++i) + { + vtkSmartPointer out(vtkSmartPointer::New()); + amrDC->SetIsoValue(this->GetValue(i)); + amrDC->Update(); + out->ShallowCopy(amrDC->GetOutput(0)); + vtkMultiBlockDataSet::SafeDownCast(outDataObj)->SetBlock(i, out); + } + return 1; + } + } + + return this->ContourUsingSuperclass(request, inputVector, outputVector); +} + +//----------------------------------------------------------------------------- +int vtkMyPVContourFilter::RequestDataObject(vtkInformation* vtkNotUsed(request), + vtkInformationVector** inputVector, vtkInformationVector* outputVector) +{ + vtkInformation* inInfo = inputVector[0]->GetInformationObject(0); + if (!inInfo) + { + return 0; + } + + vtkHierarchicalBoxDataSet* input = vtkHierarchicalBoxDataSet::GetData(inInfo); + vtkInformation* outInfo = outputVector->GetInformationObject(0); + + if (input) + { + vtkMultiBlockDataSet* output = vtkMultiBlockDataSet::GetData(outInfo); + if (!output) + { + output = vtkMultiBlockDataSet::New(); + outInfo->Set(vtkDataObject::DATA_OBJECT(), output); + this->GetOutputPortInformation(0)->Set( + vtkDataObject::DATA_EXTENT_TYPE(), output->GetExtentType()); + output->Delete(); + } + return 1; + } + else + { + vtkDataSet* output = vtkDataSet::GetData(outInfo); + if (!output) + { + output = vtkPolyData::New(); + outInfo->Set(vtkDataObject::DATA_OBJECT(), output); + this->GetOutputPortInformation(0)->Set( + vtkDataObject::DATA_EXTENT_TYPE(), output->GetExtentType()); + output->Delete(); + } + return 1; + } +} + +//---------------------------------------------------------------------------- +int vtkMyPVContourFilter::ContourUsingSuperclass( + vtkInformation* request, vtkInformationVector** inputVector, vtkInformationVector* outputVector) +{ + vtkDataObject* inputDO = vtkDataObject::GetData(inputVector[0], 0); + vtkDataObject* outputDO = vtkDataObject::GetData(outputVector, 0); + + vtkCompositeDataSet* inputCD = vtkCompositeDataSet::SafeDownCast(inputDO); + if (!inputCD) + { + return this->Superclass::RequestData(request, inputVector, outputVector); + } + + vtkCompositeDataSet* outputCD = vtkCompositeDataSet::SafeDownCast(outputDO); + outputCD->CopyStructure(inputCD); + + vtkSmartPointer iter; + iter.TakeReference(inputCD->NewIterator()); + + // for input. + vtkSmartPointer newInInfoVec = vtkSmartPointer::New(); + vtkSmartPointer newInInfo = vtkSmartPointer::New(); + newInInfoVec->SetInformationObject(0, newInInfo); + + // for output. + vtkSmartPointer newOutInfoVec = + vtkSmartPointer::New(); + vtkSmartPointer newOutInfo = vtkSmartPointer::New(); + newOutInfoVec->SetInformationObject(0, newOutInfo); + + // Loop over all the datasets. + for (iter->InitTraversal(); !iter->IsDoneWithTraversal(); iter->GoToNextItem()) + { + newInInfo->Set(vtkDataObject::DATA_OBJECT(), iter->GetCurrentDataObject()); + vtkPolyData* polydata = vtkPolyData::New(); + newOutInfo->Set(vtkDataObject::DATA_OBJECT(), polydata); + polydata->FastDelete(); + + vtkInformationVector* newInInfoVecPtr = newInInfoVec.GetPointer(); + if (!this->Superclass::RequestData(request, &newInInfoVecPtr, newOutInfoVec.GetPointer())) + { + return 0; + } + outputCD->SetDataSet(iter, polydata); + } + + return 1; +} + +//----------------------------------------------------------------------------- +int vtkMyPVContourFilter::FillOutputPortInformation(int vtkNotUsed(port), vtkInformation* info) +{ + info->Set(vtkDataObject::DATA_TYPE_NAME(), "vtkDataObject"); + return 1; +} + +//----------------------------------------------------------------------------- +int vtkMyPVContourFilter::FillInputPortInformation(int port, vtkInformation* info) +{ + this->Superclass::FillInputPortInformation(port, info); + + // According to the documentation this is the way to append additional + // input data set type since VTK 5.2. + info->Append(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkHierarchicalBoxDataSet"); + return 1; +} diff --git a/src/CellDataContour/plugin/CellDataContourModule/vtkMyPVContourFilter.h b/src/CellDataContour/plugin/CellDataContourModule/vtkMyPVContourFilter.h new file mode 100644 index 0000000..65eaa7a --- /dev/null +++ b/src/CellDataContour/plugin/CellDataContourModule/vtkMyPVContourFilter.h @@ -0,0 +1,89 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +/*========================================================================= + + Program: Visualization Toolkit + Module: vtkMyContourFilter.h + + Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen + All rights reserved. + See Copyright.txt or http://www.kitware.com/Copyright.htm for details. + + This software is distributed WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. See the above copyright notice for more information. + +=========================================================================*/ +/** + * @class vtkMyPVContourFilter + * @brief generate isosurfaces/isolines from scalar values + * + * vtkMyPVContourFilter is an extension to vtkMyContourFilter. It adds the + * ability to generate isosurfaces / isolines for AMR dataset. + * + * @warning + * Certain flags in vtkAMRDualContour are assumed to be ON. + * + * @sa + * vtkMyContourFilter vtkAMRDualContour +*/ + +#ifndef vtkMyPVContourFilter_h +#define vtkMyPVContourFilter_h + +#include "vtkMyContourFilter.h" + +class VTK_EXPORT vtkMyPVContourFilter : public vtkMyContourFilter +{ +public: + vtkTypeMacro(vtkMyPVContourFilter, vtkMyContourFilter); + + void PrintSelf(ostream &os, vtkIndent indent) override; + + static vtkMyPVContourFilter* New(); + + int ProcessRequest(vtkInformation *, vtkInformationVector **, vtkInformationVector *) override; + +protected: + vtkMyPVContourFilter(); + ~vtkMyPVContourFilter() override; + + int RequestData(vtkInformation *request, vtkInformationVector **inputVector, + vtkInformationVector *outputVector) override; + + virtual int RequestDataObject(vtkInformation* request, vtkInformationVector** inputVector, + vtkInformationVector* outputVector); + + int FillInputPortInformation(int port, vtkInformation* info) override; + int FillOutputPortInformation(int port, vtkInformation *info) override; + + /** + * Class superclass request data. Also handles iterating over + * vtkHierarchicalBoxDataSet. + */ + int ContourUsingSuperclass(vtkInformation* request, vtkInformationVector** inputVector, + vtkInformationVector* outputVector); + +private: + vtkMyPVContourFilter(const vtkMyPVContourFilter&) = delete; + void operator=(const vtkMyPVContourFilter&) = delete; +}; + +#endif // vtkMyPVContourFilter_h diff --git a/src/CellDataContour/plugin/filters.xml b/src/CellDataContour/plugin/filters.xml new file mode 100644 index 0000000..b44a2b1 --- /dev/null +++ b/src/CellDataContour/plugin/filters.xml @@ -0,0 +1,180 @@ + + + + The Contour + filter computes isolines or isosurfaces using a selected + point-centered scalar array. The Contour filter operates + on any type of data set, but the input is required to have + at least one point-centered scalar (single-component) + array. The output of this filter is + polygonal. + + + + + + + + + + This property specifies the input dataset to be used by + the contour filter. + + + + + + + + This property specifies the name of the scalar array + from which the contour filter will compute isolines and/or + isosurfaces. + + + + + + + If this property is set to 1, a scalar array containing + a normal value at each point in the isosurface or isoline will be + created by the contour filter; otherwise an array of normals will not + be computed. This operation is fairly expensive both in terms of + computation time and memory required, so if the output dataset produced + by the contour filter will be processed by filters that modify the + dataset's topology or geometry, it may be wise to set the value of this + property to 0. Select whether to compute normals. + + + + + + + If this property is set to 1, a scalar array containing + a gradient value at each point in the isosurface or isoline will be + created by this filter; otherwise an array of gradients will not be + computed. This operation is fairly expensive both in terms of + computation time and memory required, so if the output dataset produced + by the contour filter will be processed by filters that modify the + dataset's topology or geometry, it may be wise to set the value of this + property to 0. Not that if ComputeNormals is set to 1, then gradients + will have to be calculated, but they will only be stored in the output + dataset if ComputeGradients is also set to 1. + + + + If this property is set to 1, an array of scalars + (containing the contour value) will be added to the output dataset. If + set to 0, the output will not contain this array. + + + + + + + + +Select the output precision of the coordinates. **Single** sets the +output to single-precision floating-point (i.e., float), **Double** +sets it to double-precision floating-point (i.e., double), and +**Default** sets it to the same precision as the precision of the +points in the input. Defaults to ***Single***. + + + + + This parameter controls whether to produce triangles in the output. + Warning: Many filters do not properly handle non-triangular polygons. + + + + + + + + + + This property specifies the values at which to compute + isosurfaces/isolines and also the number of such + values. + + + + + + + + + + + + + + + + This property specifies an incremental point locator for + merging duplicate / coincident points. + + + + + + + + + + + + + diff --git a/src/CellDataContour/plugin/paraview.plugin b/src/CellDataContour/plugin/paraview.plugin new file mode 100644 index 0000000..c983c18 --- /dev/null +++ b/src/CellDataContour/plugin/paraview.plugin @@ -0,0 +1,27 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +NAME + MyContour +DESCRIPTION + This plugin provides a customized Contour filter +REQUIRES_MODULES + VTK::CommonCore + VTK::IOCore + VTK::FiltersCore diff --git a/src/ComplexMode/CMakeLists.txt b/src/ComplexMode/CMakeLists.txt new file mode 100644 index 0000000..63bf4ff --- /dev/null +++ b/src/ComplexMode/CMakeLists.txt @@ -0,0 +1,49 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +cmake_minimum_required(VERSION 3.8) +project(ComplexModePlugin) +find_package(ParaView REQUIRED) + +include(GNUInstallDirs) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}") +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}") +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}") + +set("_paraview_plugin_default_${CMAKE_PROJECT_NAME}" ON) +paraview_plugin_scan( + ENABLE_BY_DEFAULT YES + PLUGIN_FILES "${CMAKE_CURRENT_SOURCE_DIR}/plugin/paraview.plugin" + PROVIDES_PLUGINS plugins + REQUIRES_MODULES required_modules) + +foreach(module IN LISTS required_modules) + if(NOT TARGET "${module}") + message("Missing required module: ${module}") + return() + endif() +endforeach() + +set(BUILD_SHARED_LIBS ON) +paraview_plugin_build( + RUNTIME_DESTINATION "${CMAKE_INSTALL_BINDIR}" + LIBRARY_DESTINATION "${CMAKE_INSTALL_LIBDIR}" + LIBRARY_SUBDIRECTORY "${PARAVIEW_PLUGIN_SUBDIR}" + PLUGINS ${plugins} + AUTOLOAD ${plugins}) diff --git a/src/ComplexMode/MobileMesh.xml b/src/ComplexMode/MobileMesh.xml new file mode 100644 index 0000000..01747ac --- /dev/null +++ b/src/ComplexMode/MobileMesh.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/ComplexMode/MoveMesh.py b/src/ComplexMode/MoveMesh.py new file mode 100644 index 0000000..0966452 --- /dev/null +++ b/src/ComplexMode/MoveMesh.py @@ -0,0 +1,62 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +#### import the simple module from the paraview +from paraview.simple import * +#### disable automatic camera reset on 'Show' +paraview.simple._DisableFirstRenderCameraReset() + +# create a new 'MED Reader' +#f3d_gouttedomed = MEDReader(FileName='/home/H87074/TMP52/f3d_gouttedo.med') +#f3d_gouttedomed.AllArrays = ['TS0/MESH/ComSup0/COTE Z@@][@@P1', 'TS0/MESH/ComSup0/VITESSE U@@][@@P1', 'TS0/MESH/ComSup0/VITESSE V@@][@@P1', 'TS0/MESH/ComSup0/VITESSE W@@][@@P1'] +#f3d_gouttedomed.AllTimeSteps = ['0000', '0001', '0002', '0003', '0004', '0005', '0006', '0007', '0008', '0009', '00010'] + +source = GetActiveSource() +renderView1 = GetActiveViewOrCreate('RenderView') +# get animation scene +animationScene1 = GetAnimationScene() + +# update animation scene based on data timesteps +animationScene1.UpdateAnimationUsingDataTimeSteps() + +# create a new 'Calculator' +calculator1 = Calculator(Input=source) + +# Properties modified on calculator1 +calculator1.ResultArrayName = 'DisplacementsZ' +calculator1.Function = 'COTE Z-coordsZ' + +# get color transfer function/color map for 'DisplacementsZ' +displacementsZLUT = GetColorTransferFunction('DisplacementsZ') + +# show data in view +#calculator1Display = Show(calculator1, renderView1) + +# hide data in view +Hide(source, renderView1) + +# show color bar/color legend +#calculator1Display.SetScalarBarVisibility(renderView1, True) + +# get opacity transfer function/opacity map for 'DisplacementsZ' + +# create a new 'Warp By Scalar' +warpByScalar1 = WarpByScalar(Input=calculator1) +warpByScalar1.Scalars = ['POINTS', 'DisplacementsZ'] + diff --git a/src/ComplexMode/example.med b/src/ComplexMode/example.med new file mode 100644 index 0000000000000000000000000000000000000000..3fee74f5f531030911199a56adc182fec993f6be GIT binary patch literal 9475 zcmeHMYitx%6h5=tZcCZcQh8_v+=2=f2wP|=#VBldXS-W=XO@{Q@(3xKwzg5!q}J3J zVuC-wh%r7A{UJt*iI}v8eSWH)+R+&^k7^uPH>?<@}>kuifXhbg2CqrKQ`Yi?Da;1 z#-(|do=`M6w1D;C@6vA%oMS4XI_B%pL@Ld#fC9U^autA=Jdp8WBG?S&1QT*gnjbvm z*Gn!dQ%tonA)|#!SK>A%!UG+BJ6?6xePTGv5SoJB?cJRfI2JaAk(_@|?d$xRs#Il$ z67PGY%y=F~d`PQfn1OE%YPxJ?z}9M<`abuu*P)m@0sng|lipn;DVuU1OJZ#Gd9lBk-b?jYrL1QW!CK?g%n@zggmJRSX zWU_QvE9x3lBL?9Dp`Wm+ph8{&B%zm#fv$pWfP2B6^dEqrEq!OJ8&mNJ8TSR`+=%NJ zp#MTcAzVS;X4Y|V^Y%hGfgDw=Bc*&O!vJ#dj>hiYi=f`rQUB<19bU%{5~)z=#&b{g zz_X@~?!fW=aMJY3<=vr|p#ewX!(M#rm(y_GH0+XQ`6AHR#fSa!ujSWZt4YR7BWJF^ zKlsk~*p+YDAP_MO1%#tZ#Ip)%0Q~jP@V(Aat3xU$OXe)!+QdG_wxulg0Cj817P)cV zS;D)zHI|tvxg`rp$t_t(Qo}BiRJsbN z&NH+cqtR{*w48;>)fh=L3R83c-QUk~1ckh-8^oO(MaM9^n;T!gKaW5jfjk0v1o8;v z5%~WifVq@h!*a}Gnqx-dfLQ^48RABtP{OUM)|%9{NGz_j#?+LetHF47S@J?mIQkhGE2Sk)@+ZJkQpRv_)5t)k#8)J8f_i0;%*8Qt!n=)27n!xQ zrWS*%4hb*jj}|6Zi_mXsKG`WIAH0_=of^k)QZ$=ScVs)oaX8pl2ZHrus`2Ygc4S{` zqSQhb^8|K$nA=fQgQl|`k@sF?m9`P(DvUzzIYh=zX`@%{~!nPWy^0`i7|Jy_RBGGUo zh*~t2`qinbu5@8vF_N~iab{_TaLle8E}S$&YMWgGj)M$zYYZy}-5T*N8Zy&QTGW*N z2|aHxbbE0{2&D|Vl3`R;OHkQllcuBkZ?cW)-=fB*OwtxAX(`t!x~@J@U+YixwELTT zp75`(@z<{>)UG0yzsZX}GcP!+{ZOk3P!-Lg5Y+p93gZG^h3#(#Rb`Gkeqb#gqZnx(yrq52U zmL)MzL-rH{1Dl1(DRyBwQ*JiJubd-^MOx0$L?*{F?h#a8WO_`;{p94oTFi^hCjV^Vs?#VMr}o+sIwGuM#2RPRrl2j$Lfrs{%K|?* z$HUwXT88dsdIT5&I4W+Y4=oBuZ%p8PrD$JINPC7m)zWJp?VUc8`)@TBCstRFkJsZL D*E|?q literal 0 HcmV?d00001 diff --git a/src/ComplexMode/harmo.resu.vtu b/src/ComplexMode/harmo.resu.vtu new file mode 100644 index 0000000..c2911b9 --- /dev/null +++ b/src/ComplexMode/harmo.resu.vtu @@ -0,0 +1,71 @@ + + + + + + 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COgqPVZdzLfWuNcdeiQfv7W1MyjpJQG/I8R7csRk6DfOv5+CLvgTv+UNV1P1hg2/oEdAVTjX+De+GRdjL4whO4XT67AhLy07AAAAAAAAAABVXh9tMRr3vqT8hp+fqhe//C1BGL5+8DcLjV53verzPhCgg2pBsR6/vIF4YUwmxbe+j5xFn08GP9cMzAmNYiG/RbxywL+8H7j2KRU6PMINPxlwv7b7nCK/SS94a+DmLbghwnSSZNscPzsg/3DTOye/VhZdmkX9IrhD5aPbr20lP0AhxbZ52yu/HL5MHz4rH7g= + + + + + 6AUAAAAAAAD//////v////3////8////+/////r////5////+P////f////2////9f////T////z////8v////H////w////7////+7////t////7P///+v////q////6f///+j////n////5v///+X////k////4////+L////h////4P///9/////e////3f///9z////b////2v///9n////Y////1////9b////V////1P///9P////S////0f///9D////P////zv///83////M////y////8r////J////yP///8f////G////xf///8T////D////wv///8H////A////v////77///+9////vP///7v///+6////uf///7j///+3////tv///7X///+0////s////7L///+x////sP///6////+u////rf///6z///+r////qv///6n///+o////p////6b///+l////pP///6P///+i////of///6D///+f////nv///53///+c////m////5r///+Z////mP///5f///+W////lf///5T///+T////kv///5H///+Q////j////47///+N////jP///4v///+K////if///4j///+H////hv///4X///+E////g////4L///+B////gP///3////9+////ff///3z///97////ev///3n///94////d////3b///91////dP///3P///9y////cf///3D///9v////bv///23///9s////a////2r///9p////aP///2f///9m////Zf///2T///9j////Yv///2H///9g////X////17///9d////XP///1v///9a////Wf///1j///9X////Vv///1X///9U////U////1L///9R////UP///0////9O////Tf///0z///9L////Sv///0n///9I////R////0b///9F////RP///0P///9C////Qf///0D///8/////Pv///z3///88////O////zr///85////OP///zf///82////Nf///zT///8z////Mv///zH///8w////L////y7///8t////LP///yv///8q////Kf///yj///8n////Jv///yX///8k////I////yL///8h////IP///x////8e////Hf///xz///8b////Gv///xn///8Y////F////xb///8V////FP///xP///8S////Ef///xD///8P////Dv///w3///8M////C////wr///8J////CP///wf///8G////Bf///wT///8D////Av///wH///8A//////7///7+///9/v///P7///v+///6/v//+f7///j+///3/v//9v7///X+///0/v//8/7///L+///x/v//8P7//+/+///u/v//7f7//+z+///r/v//6v7//+n+///o/v//5/7//+b+///l/v//5P7//+P+///i/v//4f7//+D+///f/v//3v7//93+///c/v//2/7//9r+///Z/v//2P7//9f+///W/v//1f7//9T+///T/v//0v7//9H+///Q/v//z/7//87+///N/v//zP7//8v+///K/v//yf7//8j+///H/v//xv7//8X+///E/v//w/7//8L+///B/v//wP7//7/+//++/v//vf7//7z+//+7/v//uv7//7n+//+4/v//t/7//7b+//+1/v//tP7//7P+//+y/v//sf7//7D+//+v/v//rv7//63+//+s/v//q/7//6r+//+p/v//qP7//6f+//+m/v//pf7//6T+//+j/v//ov7//6H+//+g/v//n/7//57+//+d/v//nP7//5v+//+a/v//mf7//5j+//+X/v//lv7//5X+//+U/v//k/7//5L+//+R/v//kP7//4/+//+O/v//jf7//4z+//+L/v//iv7//4n+//+I/v//h/7//4b+//8= + + + 6AUAAAAAAAABAAAAAgAAAAMAAAAEAAAABQAAAAYAAAAHAAAACAAAAAkAAAAKAAAACwAAAAwAAAANAAAADgAAAA8AAAAQAAAAEQAAABIAAAATAAAAFAAAABUAAAAWAAAAFwAAABgAAAAZAAAAGgAAABsAAAAcAAAAHQAAAB4AAAAfAAAAIAAAACEAAAAiAAAAIwAAACQAAAAlAAAAJgAAACcAAAAoAAAAKQAAACoAAAArAAAALAAAAC0AAAAuAAAALwAAADAAAAAxAAAAMgAAADMAAAA0AAAANQAAADYAAAA3AAAAOAAAADkAAAA6AAAAOwAAADwAAAA9AAAAPgAAAD8AAABAAAAAQQAAAEIAAABDAAAARAAAAEUAAABGAAAARwAAAEgAAABJAAAASgAAAEsAAABMAAAATQAAAE4AAABPAAAAUAAAAFEAAABSAAAAUwAAAFQAAABVAAAAVgAAAFcAAABYAAAAWQAAAFoAAABbAAAAXAAAAF0AAABeAAAAXwAAAGAAAABhAAAAYgAAAGMAAABkAAAAZQAAAGYAAABnAAAAaAAAAGkAAABqAAAAawAAAGwAAABtAAAAbgAAAG8AAABwAAAAcQAAAHIAAABzAAAAdAAAAHUAAAB2AAAAdwAAAHgAAAB5AAAAegAAAHsAAAB8AAAAfQAAAH4AAAB/AAAAgAAAAIEAAACCAAAAgwAAAIQAAACFAAAAhgAAAIcAAACIAAAAiQAAAIoAAACLAAAAjAAAAI0AAACOAAAAjwAAAJAAAACRAAAAkgAAAJMAAACUAAAAlQAAAJYAAACXAAAAmAAAAJkAAACaAAAAmwAAAJwAAACdAAAAngAAAJ8AAACgAAAAoQAAAKIAAACjAAAApAAAAKUAAACmAAAApwAAAKgAAACpAAAAqgAAAKsAAACsAAAArQAAAK4AAACvAAAAsAAAALEAAACyAAAAswAAALQAAAC1AAAAtgAAALcAAAC4AAAAuQAAALoAAAC7AAAAvAAAAL0AAAC+AAAAvwAAAMAAAADBAAAAwgAAAMMAAADEAAAAxQAAAMYAAADHAAAAyAAAAMkAAADKAAAAywAAAMwAAADNAAAAzgAAAM8AAADQAAAA0QAAANIAAADTAAAA1AAAANUAAADWAAAA1wAAANgAAADZAAAA2gAAANsAAADcAAAA3QAAAN4AAADfAAAA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dQAAAHUAAAB2AAAAdgAAAHcAAAB3AAAAeAAAAHgAAAB5AAAAeQAAAHoAAAB6AAAAewAAAHsAAAB8AAAAfAAAAH0AAAB9AAAAfgAAAH4AAAB/AAAAfwAAAIAAAACAAAAAgQAAAIEAAACCAAAAggAAAIMAAACDAAAAhAAAAIQAAACFAAAAhQAAAIYAAACGAAAAhwAAAIcAAACIAAAAiAAAAIkAAACJAAAAigAAAIoAAACLAAAAiwAAAIwAAACMAAAAjQAAAI0AAACOAAAAjgAAAI8AAACPAAAAkAAAAJAAAACRAAAAkQAAAJIAAACSAAAAkwAAAJMAAACUAAAAlAAAAJUAAACVAAAAlgAAAJYAAACXAAAAlwAAAJgAAACYAAAAmQAAAJkAAACaAAAAmgAAAJsAAACbAAAAnAAAAJwAAACdAAAAnQAAAJ4AAACeAAAAnwAAAJ8AAACgAAAAoAAAAKEAAAChAAAAogAAAKIAAACjAAAAowAAAKQAAACkAAAApQAAAKUAAACmAAAApgAAAKcAAACnAAAAqAAAAKgAAACpAAAAqQAAAKoAAACqAAAAqwAAAKsAAACsAAAArAAAAK0AAACtAAAArgAAAK4AAACvAAAArwAAALAAAACwAAAAsQAAALEAAACyAAAAsgAAALMAAACzAAAAtAAAALQAAAC1AAAAtQAAALYAAAC2AAAAtwAAALcAAAC4AAAAuAAAALkAAAC7AAAAvAAAALwAAAC9AAAAvQAAAL4AAAC+AAAAvwAAAL8AAADAAAAAwAAAAMEAAADBAAAAwgAAAMIAAADDAAAAwwAAAMQAAADEAAAAxQAAAMUAAADGAAAAxgAAAMcAAADHAAAAyAAAAMgAAADJAAAAyQAAAMoAAADKAAAAywAAAMsAAADMAAAAzAAAAM0AAADNAAAAzgAAAM4AAADPAAAAzwAAANAAAADQAAAA0QAAANEAAADSAAAA0gAAANMAAADTAAAA1AAAANQAAADVAAAA1QAAANYAAADWAAAA1wAAANcAAADYAAAA2AAAANkAAADZAAAA2gAAANoAAADbAAAA2wAAANwAAADcAAAA3QAAAN0AAADeAAAA3gAAAN8AAADfAAAA4AAAAOAAAADhAAAA4QAAAOIAAADiAAAA4wAAAOMAAADkAAAA5AAAAOUAAADlAAAA5gAAAOYAAADnAAAA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wAEAAMIBAADEAQAAxgEAAMgBAADKAQAAzAEAAM4BAADQAQAA0gEAANQBAADWAQAA2AEAANoBAADcAQAA3gEAAOABAADiAQAA5AEAAOYBAADoAQAA6gEAAOwBAADuAQAA8AEAAPIBAAD0AQAA9gEAAPgBAAD6AQAA/AEAAP4BAAAAAgAAAgIAAAQCAAAGAgAACAIAAAoCAAAMAgAADgIAABACAAASAgAAFAIAABYCAAAYAgAAGgIAABwCAAAeAgAAIAIAACICAAAkAgAAJgIAACgCAAAqAgAALAIAAC4CAAAwAgAAMgIAADQCAAA2AgAAOAIAADoCAAA8AgAAPgIAAEACAABCAgAARAIAAEYCAABIAgAASgIAAEwCAABOAgAAUAIAAFICAABUAgAAVgIAAFgCAABaAgAAXAIAAF4CAABgAgAAYQIAAGICAABjAgAAZAIAAGUCAABmAgAAZwIAAGgCAABpAgAAagIAAGsCAABsAgAAbQIAAG4CAABvAgAAcAIAAHECAAByAgAAcwIAAHQCAAB1AgAAdgIAAHcCAAB4AgAAeQIAAHoCAAB7AgAAfAIAAH0CAAB+AgAAfwIAAIACAACBAgAAggIAAIMCAACEAgAAhQIAAIYCAACHAgAAiAIAAIkCAACKAgAAiwIAAIwCAACNAgAAjgIAAI8CAACQAgAAkQIAAJICAACTAgAAlAIAAJUCAACWAgAAlwIAAJgCAACZAgAAmgIAAJsCAACcAgAAnQIAAJ4CAACfAgAAoAIAAKECAACiAgAAowIAAKQCAAClAgAApgIAAKcCAACoAgAAqQIAAKoCAAA= + + + egEAAAAAAAADAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE= + + + + + diff --git a/src/ComplexMode/plugin/CMakeLists.txt b/src/ComplexMode/plugin/CMakeLists.txt new file mode 100644 index 0000000..0cc63e0 --- /dev/null +++ b/src/ComplexMode/plugin/CMakeLists.txt @@ -0,0 +1,31 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +paraview_add_plugin(ComplexModePlugin + VERSION "1.0" + MODULES ComplexModeModule + MODULE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/ComplexModeModule/vtk.module" + SERVER_MANAGER_XML filters.xml +) + +install(TARGETS ComplexModePlugin + RUNTIME DESTINATION lib/paraview + LIBRARY DESTINATION lib/paraview + ARCHIVE DESTINATION lib/paraview +) diff --git a/src/ComplexMode/plugin/ComplexModeModule/CMakeLists.txt b/src/ComplexMode/plugin/ComplexModeModule/CMakeLists.txt new file mode 100644 index 0000000..e0f0b1e --- /dev/null +++ b/src/ComplexMode/plugin/ComplexModeModule/CMakeLists.txt @@ -0,0 +1,31 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +set(classes + vtkComplexMode +) +# Visual C++ 2017 bug: defining _USE_MATH_DEFINES inside .cpp/.h file is inoperant +IF(WIN32) +ADD_COMPILE_DEFINITIONS(_USE_MATH_DEFINES) +ENDIF(WIN32) + +vtk_module_add_module(ComplexModeModule + FORCE_STATIC + CLASSES ${classes} + ) diff --git a/src/ComplexMode/plugin/ComplexModeModule/vtk.module b/src/ComplexMode/plugin/ComplexModeModule/vtk.module new file mode 100644 index 0000000..c906fa4 --- /dev/null +++ b/src/ComplexMode/plugin/ComplexModeModule/vtk.module @@ -0,0 +1,38 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +NAME + ComplexModeModule +DEPENDS + VTK::CommonCore + VTK::CommonDataModel + VTK::CommonExecutionModel + VTK::FiltersCore + VTK::FiltersModeling + VTK::IOCore + VTK::IOGeometry + VTK::IOXML + ParaView::VTKExtensionsFiltersRendering + ParaView::VTKExtensionsMisc +PRIVATE_DEPENDS + VTK::CommonMisc + VTK::CommonSystem + VTK::FiltersGeneral + VTK::vtksys + VTK::zlib diff --git a/src/ComplexMode/plugin/ComplexModeModule/vtkComplexMode.cxx b/src/ComplexMode/plugin/ComplexModeModule/vtkComplexMode.cxx new file mode 100644 index 0000000..911eed6 --- /dev/null +++ b/src/ComplexMode/plugin/ComplexModeModule/vtkComplexMode.cxx @@ -0,0 +1,396 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +// Author : Anthony Geay (EDF R&D) + +#include "vtkComplexMode.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +vtkStandardNewMacro(vtkComplexMode); + +static const char ZE_DISPLACEMENT_NAME1[]="@@ForReal?@@"; + +static const char ZE_DISPLACEMENT_NAME2[]="@@ForImag?@@"; + +static const char ZE_DISPLACEMENT_NAME3[]="MagnitudeOfCpxDisp"; + +static const double EPS=1e-12; + +/////////////////// + +class MZCException : public std::exception +{ +public: + MZCException(const std::string& s):_reason(s) { } + virtual const char *what() const throw() { return _reason.c_str(); } + virtual ~MZCException() throw() { } +private: + std::string _reason; +}; + +vtkSmartPointer ForceTo3Compo(vtkDoubleArray *arr) +{ + if(!arr) + return vtkSmartPointer(); + int nbCompo(arr->GetNumberOfComponents()),nbTuples(arr->GetNumberOfTuples()); + if(nbCompo==3) + { + vtkSmartPointer ret(arr); + arr->Register(0); + return ret; + } + if(nbCompo==6) + { + vtkSmartPointer ret(vtkSmartPointer::New()); + ret->SetNumberOfComponents(3); + ret->SetNumberOfTuples(nbTuples); + const double *srcPt(arr->Begin()); + double *destPt(ret->Begin()); + for(int i=0;i GetPossibleArrayNames(vtkDataSet *dataset) +{ + if(!dataset) + throw MZCException("The input dataset is null !"); + std::vector< std::string > ret; + vtkPointData *att(dataset->GetPointData()); + for(int i=0;iGetNumberOfArrays();i++) + { + vtkDataArray *locArr(att->GetArray(i)); + int nbComp(locArr->GetNumberOfComponents()); + if(nbComp!=3 && nbComp!=6) + continue; + std::string s(locArr->GetName()); + ret.push_back(s); + } + return ret; +} + +std::string FindTheBest(const std::vector& arrNames, const std::string& key0, const std::string& key1) +{ + std::string ret; + char points(0); + if(arrNames.empty()) + return ret; + for(std::vector::const_iterator it=arrNames.begin();it!=arrNames.end();it++) + { + char curNbPts(1); + if((*it).find(key0,0)!=std::string::npos) + curNbPts++; + if((*it).find(key1,0)!=std::string::npos) + curNbPts++; + if(curNbPts>points) + { + points=curNbPts; + ret=*it; + } + } + return ret; +} + +std::string FindBestRealAmong(const std::vector& arrNames) +{ + static const char KEY1[]="DEPL"; + static const char KEY2[]="REEL"; + return FindTheBest(arrNames,KEY1,KEY2); +} + +std::string FindBestImagAmong(const std::vector& arrNames) +{ + static const char KEY1[]="DEPL"; + static const char KEY2[]="IMAG"; + return FindTheBest(arrNames,KEY1,KEY2); +} + +vtkUnstructuredGrid *ExtractInfo1(vtkInformationVector *inputVector) +{ + vtkInformation *inputInfo(inputVector->GetInformationObject(0)); + vtkDataSet *input(0); + vtkDataSet *input0(vtkDataSet::SafeDownCast(inputInfo->Get(vtkDataObject::DATA_OBJECT()))); + vtkMultiBlockDataSet *input1(vtkMultiBlockDataSet::SafeDownCast(inputInfo->Get(vtkDataObject::DATA_OBJECT()))); + if(input0) + input=input0; + else + { + if(!input1) + throw MZCException("Input dataSet must be a DataSet or single elt multi block dataset expected !"); + if(input1->GetNumberOfBlocks()!=1) + throw MZCException("Input dataSet is a multiblock dataset with not exactly one block ! Use MergeBlocks or ExtractBlocks filter before calling this filter !"); + vtkDataObject *input2(input1->GetBlock(0)); + if(!input2) + throw MZCException("Input dataSet is a multiblock dataset with exactly one block but this single element is NULL !"); + vtkDataSet *input2c(vtkDataSet::SafeDownCast(input2)); + if(!input2c) + throw MZCException("Input dataSet is a multiblock dataset with exactly one block but this single element is not a dataset ! Use MergeBlocks or ExtractBlocks filter before calling this filter !"); + input=input2c; + } + if(!input) + throw MZCException("Input data set is NULL !"); + vtkUnstructuredGrid *usgIn(vtkUnstructuredGrid::SafeDownCast(input)); + if(!usgIn) + throw MZCException("Input data set is not an unstructured mesh ! This filter works only on unstructured meshes !"); + return usgIn; +} + +void ExtractInfo(vtkInformationVector *inputVector, vtkUnstructuredGrid *& usgIn, const std::string& arrName, vtkDoubleArray *& arr) +{ + usgIn=ExtractInfo1(inputVector); + vtkPointData *att(usgIn->GetPointData()); + if(!att) + throw MZCException("Input dataset has no point data attribute ! Impossible to move mesh !"); + vtkDataArray *zeArr(0); + for(int i=0;iGetNumberOfArrays();i++) + { + vtkDataArray *locArr(att->GetArray(i)); + std::string s(locArr->GetName()); + if(s==arrName) + { + zeArr=locArr; + break; + } + } + if(!zeArr) + { + std::ostringstream oss; + oss << "Impossible to locate the array called \"" << arrName << "\" used to move mesh !"; + throw MZCException(oss.str()); + } + arr=vtkDoubleArray::SafeDownCast(zeArr); + if(!arr) + { + std::ostringstream oss; + oss << "Array called \"" << arrName << "\" has been located but this is NOT a float64 array !"; + throw MZCException(oss.str()); + } + if(arr->GetNumberOfComponents()!=3 && arr->GetNumberOfComponents()!=6) + { + std::ostringstream oss; + oss << "Float64 array called \"" << arrName << "\" has been located but this array has not exactly 3 or 6 components as it should !"; + throw MZCException(oss.str()); + } + if(arr->GetNumberOfTuples()!=usgIn->GetNumberOfPoints()) + { + std::ostringstream oss; + oss << "Float64-1 components array called \"" << arrName << "\" has been located but the number of tuples is invalid ! Should be " << usgIn->GetNumberOfPoints() << " instead of " << arr->GetNumberOfTuples() << " !"; + throw MZCException(oss.str()); + } +} + +//////////////////// + +class vtkComplexMode::vtkComplexModeInternal +{ +public: + void setFieldForReal(const std::string& st) { _real=st; } + void setFieldForImagin(const std::string& st) { _imag=st; } + std::string getFieldForReal() const { return _real; } + std::string getFieldForImag() const { return _imag; } +private: + std::string _real; + std::string _imag; +}; + +vtkComplexMode::vtkComplexMode():Factor(1.),Phase(90.),AnimationTime(0.),Internal(new vtkComplexMode::vtkComplexModeInternal) +{ + //this->SetInputArrayToProcess(0,0,0,vtkDataObject::FIELD_ASSOCIATION_POINTS,vtkDataSetAttributes::VECTORS); +} + +vtkComplexMode::~vtkComplexMode() +{ + delete this->Internal; +} + +void vtkComplexMode::SetInputArrayToProcess(int idx, int port, int connection, int ff, const char *name) +{ + if(idx==0) + this->Internal->setFieldForReal(name); + if(idx==1) + this->Internal->setFieldForImagin(name); + vtkUnstructuredGridAlgorithm::SetInputArrayToProcess(idx,port,connection,ff,name); +} + +double GetOptimalRatioFrom(vtkUnstructuredGrid *dataset, vtkDoubleArray *array) +{ + if(!dataset || !array) + throw MZCException("The input dataset and or array is null !"); + vtkDataArray *coords(dataset->GetPoints()->GetData()); + vtkDoubleArray *coords2(vtkDoubleArray::SafeDownCast(coords)); + if(!coords2) + throw MZCException("Input coordinates are not float64 !"); + int nbCompo(array->GetNumberOfComponents()); + if(coords2->GetNumberOfComponents()!=3 || (nbCompo!=3 && nbCompo!=6)) + throw MZCException("Input coordinates do not have 3 components as it should !"); + int nbPts(dataset->GetNumberOfPoints()); + const double *srcPt1(array->Begin()); + dataset->ComputeBounds(); + double *minmax1(dataset->GetBounds()); + double minmax2[3]={0.,0.,0.}; + for(int i=0;iInternal->getFieldForReal().empty()) + return 1; + vtkUnstructuredGrid *usgIn(0); + vtkDoubleArray *arr(0); + /*ExtractInfo(inputVector[0],usgIn,this->Internal->getFieldForReal(),arr); + std::vector candidatesArrName(GetPossibleArrayNames(usgIn)); + // + double ratio(GetOptimalRatioFrom(usgIn,arr)); + std::string optArrNameForReal(FindBestRealAmong(candidatesArrName)); + std::string optArrNameForImag(FindBestImagAmong(candidatesArrName));*/ + //std::cerr << ratio << std::endl; + //std::cerr << optArrNameForReal << " * " << optArrNameForImag << std::endl; + } + catch(MZCException& e) + { + std::ostringstream oss; + oss << "Exception has been thrown in vtkComplexMode::RequestInformation : " << e.what() << std::endl; + if(this->HasObserver("ErrorEvent") ) + this->InvokeEvent("ErrorEvent",const_cast(oss.str().c_str())); + else + vtkOutputWindowDisplayErrorText(const_cast(oss.str().c_str())); + vtkObject::BreakOnError(); + return 0; + } + return 1; +} + +int vtkComplexMode::RequestData(vtkInformation *request, vtkInformationVector **inputVector, vtkInformationVector *outputVector) +{ + //std::cerr << "########################################## vtkComplexMode::RequestData ##########################################" << std::endl; + try + { + vtkUnstructuredGrid *usgIn(0); + vtkDoubleArray *arrRealBase(0),*arrImagBase(0); + ExtractInfo(inputVector[0],usgIn,this->Internal->getFieldForReal(),arrRealBase); + ExtractInfo(inputVector[0],usgIn,this->Internal->getFieldForImag(),arrImagBase); + vtkSmartPointer arrReal(ForceTo3Compo(arrRealBase)); + vtkSmartPointer arrImag(ForceTo3Compo(arrImagBase)); + // + int nbPts(usgIn->GetNumberOfPoints()); + vtkSmartPointer step1(vtkSmartPointer::New()); + step1->DeepCopy(usgIn); + vtkSmartPointer arr1(vtkSmartPointer::New()),arr2(vtkSmartPointer::New()),zearr(vtkSmartPointer::New()); + arr1->SetName(ZE_DISPLACEMENT_NAME1); arr2->SetName(ZE_DISPLACEMENT_NAME2); zearr->SetName(ZE_DISPLACEMENT_NAME3); + arr1->SetNumberOfComponents(3); arr2->SetNumberOfComponents(3); zearr->SetNumberOfComponents(1); + arr1->SetNumberOfTuples(nbPts); arr2->SetNumberOfTuples(nbPts); zearr->SetNumberOfTuples(nbPts); + double *ptToFeed1(arr1->Begin()),*ptToFeed2(arr2->Begin()),*ptToFeed3(zearr->Begin()); + const double *srcPt1(arrReal->Begin()),*srcPt2(arrImag->Begin()); + double cst1(Factor*sin(AnimationTime*2*M_PI)),cst2(Factor*sin(AnimationTime*2*M_PI+Phase*M_PI/180.)); + std::transform(srcPt1,srcPt1+3*nbPts,ptToFeed1,std::bind2nd(std::multiplies(),cst1)); + std::transform(srcPt2,srcPt2+3*nbPts,ptToFeed2,std::bind2nd(std::multiplies(),cst2)); + std::transform(ptToFeed1,ptToFeed1+3*nbPts,ptToFeed2,ptToFeed1,std::plus()); + { + for(int i=0;iGetPointData()->AddArray(arr1)); + step1->GetPointData()->SetActiveAttribute(idx1,vtkDataSetAttributes::VECTORS); + // + vtkSmartPointer ws(vtkSmartPointer::New());//vtkNew + ws->SetInputData(step1); + ws->SetScaleFactor(1.); + ws->SetInputArrayToProcess(idx1,0,0,"vtkDataObject::FIELD_ASSOCIATION_POINTS",ZE_DISPLACEMENT_NAME1); + ws->Update(); + vtkSmartPointer ds(ws->GetOutput()); + ds->GetPointData()->RemoveArray(idx1); + int idx3(ds->GetPointData()->AddArray(zearr)); + ds->GetPointData()->SetActiveAttribute(idx3,vtkDataSetAttributes::SCALARS); + vtkInformation *outInfo(outputVector->GetInformationObject(0)); + vtkUnstructuredGrid *output(vtkUnstructuredGrid::SafeDownCast(outInfo->Get(vtkDataObject::DATA_OBJECT()))); + output->ShallowCopy(ds); + } + catch(MZCException& e) + { + std::ostringstream oss; + oss << "Exception has been thrown in vtkComplexMode::RequestInformation : " << e.what() << std::endl; + if(this->HasObserver("ErrorEvent") ) + this->InvokeEvent("ErrorEvent",const_cast(oss.str().c_str())); + else + vtkOutputWindowDisplayErrorText(const_cast(oss.str().c_str())); + vtkObject::BreakOnError(); + return 0; + } + return 1; +} + +void vtkComplexMode::PrintSelf(ostream& os, vtkIndent indent) +{ + this->Superclass::PrintSelf(os, indent); +} diff --git a/src/ComplexMode/plugin/ComplexModeModule/vtkComplexMode.h b/src/ComplexMode/plugin/ComplexModeModule/vtkComplexMode.h new file mode 100644 index 0000000..1c5230c --- /dev/null +++ b/src/ComplexMode/plugin/ComplexModeModule/vtkComplexMode.h @@ -0,0 +1,68 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +// Author : Anthony Geay (EDF R&D) + +#ifndef vtkComplexMode_h__ +#define vtkComplexMode_h__ + +#include + +class vtkMutableDirectedGraph; + +class VTK_EXPORT vtkComplexMode : public vtkUnstructuredGridAlgorithm +{ +public: + static vtkComplexMode* New(); + vtkTypeMacro(vtkComplexMode, vtkUnstructuredGridAlgorithm) + void PrintSelf(ostream& os, vtkIndent indent); + + vtkGetMacro(Factor,double); + vtkSetClampMacro(Factor,double,0.,VTK_DOUBLE_MAX); + + vtkGetMacro(Phase,double); + vtkSetClampMacro(Phase,double,-180.,180.); + + vtkGetMacro(AnimationTime,double); + vtkSetClampMacro(AnimationTime,double,0.,1.); + + void SetInputArrayToProcess(int idx, int port, int connection, int fieldAssociation, const char *name); + +protected: + vtkComplexMode(); + ~vtkComplexMode(); + + int RequestInformation(vtkInformation *request, + vtkInformationVector **inputVector, vtkInformationVector *outputVector); + + int RequestData(vtkInformation *request, vtkInformationVector **inputVector, + vtkInformationVector *outputVector); + +private: + vtkComplexMode(const vtkComplexMode&); + void operator=(const vtkComplexMode&); // Not implemented. + +protected: + double Factor; + double Phase; + double AnimationTime; + class vtkComplexModeInternal; + vtkComplexModeInternal* Internal; +}; + +#endif diff --git a/src/ComplexMode/plugin/filters.xml b/src/ComplexMode/plugin/filters.xml new file mode 100644 index 0000000..00d2311 --- /dev/null +++ b/src/ComplexMode/plugin/filters.xml @@ -0,0 +1,106 @@ + + + + + + + + + + + + + This property specifies the input to the Level Scalars filter. + + + + + + + + + + + + + Select the array that represent the real part of the complex mode. + + + + + + + + + + + + Select the array that represent the imaginary part of the complex mode. + + + + + + + + + + + + The value of this property sets the scale factor applied for all nodes displacement. + + + + + + + The value of phase between real and imaginary. + + + + + + + The value of this property sets the scale factor applied for all nodes displacement. + + + + + + + + diff --git a/src/ComplexMode/plugin/paraview.plugin b/src/ComplexMode/plugin/paraview.plugin new file mode 100644 index 0000000..48dae84 --- /dev/null +++ b/src/ComplexMode/plugin/paraview.plugin @@ -0,0 +1,28 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +NAME + ComplexModePlugin +DESCRIPTION + This plugin provides ... +REQUIRES_MODULES + VTK::CommonCore + VTK::IOCore + VTK::FiltersCore + ParaView::VTKExtensionsFiltersRendering diff --git a/src/ContactReader/CMakeLists.txt b/src/ContactReader/CMakeLists.txt new file mode 100644 index 0000000..11dbaf6 --- /dev/null +++ b/src/ContactReader/CMakeLists.txt @@ -0,0 +1,49 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +cmake_minimum_required(VERSION 3.8) +project(ContactReader) +find_package(ParaView REQUIRED) + +include(GNUInstallDirs) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}") +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}") +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}") + +set("_paraview_plugin_default_${CMAKE_PROJECT_NAME}" ON) +paraview_plugin_scan( + ENABLE_BY_DEFAULT YES + PLUGIN_FILES "${CMAKE_CURRENT_SOURCE_DIR}/plugin/paraview.plugin" + PROVIDES_PLUGINS plugins + REQUIRES_MODULES required_modules) + +foreach(module IN LISTS required_modules) + if(NOT TARGET "${module}") + message("Missing required module: ${module}") + return() + endif() +endforeach() + +set(BUILD_SHARED_LIBS ON) +paraview_plugin_build( + RUNTIME_DESTINATION "${CMAKE_INSTALL_BINDIR}" + LIBRARY_DESTINATION "${CMAKE_INSTALL_LIBDIR}" + LIBRARY_SUBDIRECTORY "${PARAVIEW_PLUGIN_SUBDIR}" + PLUGINS ${plugins} + AUTOLOAD ${plugins}) diff --git a/src/ContactReader/plugin/CMakeLists.txt b/src/ContactReader/plugin/CMakeLists.txt new file mode 100644 index 0000000..b8b86d3 --- /dev/null +++ b/src/ContactReader/plugin/CMakeLists.txt @@ -0,0 +1,31 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +paraview_add_plugin(ContactReader + VERSION "1.0" + MODULES ContactReaderModule + MODULE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/ContactReaderModule/vtk.module" + SERVER_MANAGER_XML sources.xml +) + +install(TARGETS ContactReader + RUNTIME DESTINATION lib/paraview + LIBRARY DESTINATION lib/paraview + ARCHIVE DESTINATION lib/paraview +) diff --git a/src/ContactReader/plugin/ContactReaderModule/CMakeLists.txt b/src/ContactReader/plugin/ContactReaderModule/CMakeLists.txt new file mode 100644 index 0000000..b938966 --- /dev/null +++ b/src/ContactReader/plugin/ContactReaderModule/CMakeLists.txt @@ -0,0 +1,27 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +set(classes + vtkContactReader +) + +vtk_module_add_module(ContactReaderModule + FORCE_STATIC + CLASSES ${classes} + ) diff --git a/src/ContactReader/plugin/ContactReaderModule/vtk.module b/src/ContactReader/plugin/ContactReaderModule/vtk.module new file mode 100644 index 0000000..e129733 --- /dev/null +++ b/src/ContactReader/plugin/ContactReaderModule/vtk.module @@ -0,0 +1,37 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +NAME + ContactReaderModule +DEPENDS + VTK::CommonCore + VTK::CommonDataModel + VTK::CommonExecutionModel + VTK::FiltersCore + VTK::FiltersModeling + VTK::FiltersSources + VTK::IOCore + VTK::IOGeometry + VTK::IOInfovis + ParaView::VTKExtensionsFiltersGeneral +PRIVATE_DEPENDS + VTK::CommonMisc + VTK::CommonSystem + VTK::FiltersGeneral + VTK::vtksys diff --git a/src/ContactReader/plugin/ContactReaderModule/vtkContactReader.cxx b/src/ContactReader/plugin/ContactReaderModule/vtkContactReader.cxx new file mode 100644 index 0000000..e3839d3 --- /dev/null +++ b/src/ContactReader/plugin/ContactReaderModule/vtkContactReader.cxx @@ -0,0 +1,225 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +#include "vtkContactReader.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +class MyException : public std::exception +{ +public: + MyException(const char *what) : _what(what) {} + MyException(const std::string &what) : _what(what) {} + ~MyException() throw() {} + const char *what() const throw() { return _what.c_str(); } + +private: + std::string _what; +}; + +template +class AutoPtr +{ +public: + AutoPtr(T *ptr = 0) : _ptr(ptr) {} + ~AutoPtr() { destroyPtr(); } + bool isNull() const { return _ptr == 0; } + bool isNotNull() const { return !isNull(); } + AutoPtr &operator=(T *ptr) + { + if (_ptr != ptr) + { + destroyPtr(); + _ptr = ptr; + } + return *this; + } + T *operator->() { return _ptr; } + const T *operator->() const { return _ptr; } + T &operator*() { return *_ptr; } + const T &operator*() const { return *_ptr; } + operator T *() { return _ptr; } + operator const T *() const { return _ptr; } + +private: + void destroyPtr() { delete[] _ptr; } + +private: + T *_ptr; +}; + +vtkIdType PosOf(const std::vector &arr, const std::string &what) +{ + auto pos = std::find(arr.begin(), arr.end(), what); + if (pos == arr.end()) + { + std::ostringstream oss; + oss << "vtkContactReader::PosOf : Fail to locate \"" << what << "\" in array !"; + throw MyException(oss.str()); + } + return std::distance(arr.begin(), pos); +} + +vtkStandardNewMacro(vtkContactReader); + +vtkContactReader::vtkContactReader() : FileName(NULL), ScaleFactor(0.02) +{ + this->SetNumberOfInputPorts(0); +} + +vtkContactReader::~vtkContactReader() +{ +} + +int vtkContactReader::RequestInformation(vtkInformation *vtkNotUsed(request), + vtkInformationVector **vtkNotUsed(inputVector), + vtkInformationVector *outputVector) +{ + return 1; +} + +void FillValue(vtkVariantArray *row, double *ptToFeed, std::size_t ipos, vtkIdType pos) +{ + bool isOK(false); + vtkVariant *elt(row->GetPointer(pos)); + ptToFeed[ipos] = elt->ToDouble(&isOK); + if (!isOK) + { + std::ostringstream oss; + oss << "vtkContactReader::FillValue : Error during analyze content of file ! Float64 expected !"; + throw MyException(oss.str()); + } +} + +int vtkContactReader::RequestData(vtkInformation *vtkNotUsed(request), + vtkInformationVector **vtkNotUsed(inputVector), + vtkInformationVector *outputVector) +{ + vtkInformation *outInfo(outputVector->GetInformationObject(0)); + vtkPolyData *output(vtkPolyData::SafeDownCast(outInfo->Get(vtkDataObject::DATA_OBJECT()))); + // + try + { + vtkNew reader; + reader->SetFileName(this->FileName); + reader->SetDetectNumericColumns(true); + reader->SetUseStringDelimiter(true); + reader->SetHaveHeaders(true); + reader->SetFieldDelimiterCharacters(" "); + reader->SetAddTabFieldDelimiter(true); + reader->SetMergeConsecutiveDelimiters(true); + reader->Update(); + vtkTable *table(reader->GetOutput()); + vtkIdType nbRows(table->GetNumberOfRows()), nbCols(table->GetNumberOfColumns()); + std::vector colNames(nbCols); + for (vtkIdType iCol = 0; iCol < nbCols; iCol++) + { + colNames[iCol] = table->GetColumnName(iCol); + } + vtkIdType XPos(PosOf(colNames, "X")), YPos(PosOf(colNames, "Y")), ZPos(PosOf(colNames, "Z")), DXPos(PosOf(colNames, "DX")), DYPos(PosOf(colNames, "DY")), DZPos(PosOf(colNames, "DZ")); + // + vtkSmartPointer coords(vtkSmartPointer::New()), vectArr(vtkSmartPointer::New()); + vectArr->SetNumberOfComponents(3); + coords->SetNumberOfComponents(3); + coords->SetNumberOfTuples(nbRows); + vectArr->SetNumberOfTuples(nbRows); + double *ptToFeed1(coords->Begin()), *ptToFeed2(vectArr->Begin()); + const vtkIdType POS[3] = {XPos, YPos, ZPos}, DX[3] = {DXPos, DYPos, DZPos}; + for (vtkIdType iRow = 0; iRow < nbRows; iRow++, ptToFeed1 += 3, ptToFeed2 += 3) + { + vtkVariantArray *row(table->GetRow(iRow)); + for (std::size_t ipos = 0; ipos < 3; ipos++) + { + FillValue(row, ptToFeed1, ipos, POS[ipos]); + FillValue(row, ptToFeed2, ipos, DX[ipos]); + } + std::for_each(ptToFeed2, ptToFeed2 + 3, [](double &v) { v = -v; }); + } + vectArr->SetName("Resultante"); + vtkNew ret; + vtkSmartPointer pts(vtkSmartPointer::New()); + pts->SetData(coords); + ret->SetPoints(pts); + ret->GetPointData()->AddArray(vectArr); + // + vtkNew glyph; + glyph->SetInputData(ret); + glyph->SetGlyphMode(0); //vtkPVGlyphFilter::ALL_POINTS + glyph->SetVectorScaleMode(0); //vtkPVGlyphFilter::SCALE_BY_MAGNITUDE + // + vtkNew arrow; + arrow->SetTipResolution(6); + arrow->SetTipRadius(0.1); + arrow->SetTipLength(0.35); + arrow->SetShaftResolution(6); + arrow->SetShaftRadius(0.03); + glyph->SetSourceConnection(arrow->GetOutputPort()); + //idx,port,connection,fieldAssociation,name + glyph->SetInputArrayToProcess(0, 0, 0, vtkDataObject::FIELD_ASSOCIATION_POINTS, "Resultante"); //idx==0 -> scaleArray + glyph->SetInputArrayToProcess(1, 0, 0, vtkDataObject::FIELD_ASSOCIATION_POINTS, "Resultante"); //idx==1 -> orientationArray + glyph->SetScaleFactor(this->ScaleFactor); + glyph->Update(); + output->ShallowCopy(glyph->GetOutput()); + output->GetPointData()->SetActiveAttribute(0, vtkDataSetAttributes::SCALARS); + //output->ShallowCopy(ret); + } + catch (MyException &e) + { + vtkErrorMacro(<< "vtkContactReader::RequestData : during read of " << this->FileName << " : " << e.what()); + return 0; + } + return 1; +} + +void vtkContactReader::SetScaleFactor(double newScaleFactor) +{ + if (this->ScaleFactor != newScaleFactor) + { + this->ScaleFactor = newScaleFactor; + this->Modified(); + } +} + +void vtkContactReader::PrintSelf(ostream &os, vtkIndent indent) +{ + this->Superclass::PrintSelf(os, indent); +} diff --git a/src/ContactReader/plugin/ContactReaderModule/vtkContactReader.h b/src/ContactReader/plugin/ContactReaderModule/vtkContactReader.h new file mode 100644 index 0000000..1195eeb --- /dev/null +++ b/src/ContactReader/plugin/ContactReaderModule/vtkContactReader.h @@ -0,0 +1,52 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +#ifndef __vtkContactReader_h__ +#define __vtkContactReader_h__ + +#include + +class VTK_EXPORT vtkContactReader : public vtkPolyDataAlgorithm +{ +public: + static vtkContactReader *New(); + vtkTypeMacro(vtkContactReader, vtkPolyDataAlgorithm); + void PrintSelf(ostream &os, vtkIndent indent) override; + + vtkSetStringMacro(FileName); + vtkGetStringMacro(FileName); + + void SetScaleFactor(double newScaleFactor); + +protected: + vtkContactReader(); + ~vtkContactReader() override; + + int RequestInformation(vtkInformation *, vtkInformationVector **, vtkInformationVector *); + int RequestData(vtkInformation *, vtkInformationVector **, vtkInformationVector *); + + char *FileName; + double ScaleFactor; + +private: + vtkContactReader(const vtkContactReader &) = delete; + void operator=(const vtkContactReader &) = delete; +}; + +#endif diff --git a/src/ContactReader/plugin/paraview.plugin b/src/ContactReader/plugin/paraview.plugin new file mode 100644 index 0000000..2320b30 --- /dev/null +++ b/src/ContactReader/plugin/paraview.plugin @@ -0,0 +1,28 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +NAME + ContactReader +DESCRIPTION + This plugin provides the ContactReader reader. +REQUIRES_MODULES + VTK::CommonCore + VTK::IOCore + VTK::IOInfovis + VTK::FiltersCore diff --git a/src/ContactReader/plugin/sources.xml b/src/ContactReader/plugin/sources.xml new file mode 100644 index 0000000..ef82977 --- /dev/null +++ b/src/ContactReader/plugin/sources.xml @@ -0,0 +1,33 @@ + + + + + + + + + This property specifies the file name for this reader. + + + + + This property specifies the scale factor applied to the size of arrows. + + + + + + + + diff --git a/src/ContactReader/resultante_rn.rco b/src/ContactReader/resultante_rn.rco new file mode 100644 index 0000000..d3de61f --- /dev/null +++ b/src/ContactReader/resultante_rn.rco @@ -0,0 +1,16 @@ +X Y Z DX DY DZ +31.1904 -77.7735 925.000 -8.35353E+07 9.40967E+07 6.90162E+07 +22.1834 -61.2353 908.778 -7.45578E+07 7.06419E+07 9.90851E+07 +13.0107 -37.3614 891.655 -1.12622E+08 6.34184E+07 1.44383E+08 +7.51714 -13.1788 878.021 -2.56908E+08 1.12063E+08 3.62862E+08 +7.33547 15.1584 875.000 -2.30530E+08 -5.64037E+07 2.79925E+08 +17.3523 38.7410 892.437 -1.50812E+08 -7.11926E+07 1.93785E+08 +23.1342 61.0311 902.140 -9.96464E+07 -6.47382E+07 1.24483E+08 +36.1858 80.8956 916.589 -7.26821E+07 -5.30730E+07 8.41775E+07 +50.9030 94.6827 932.711 -6.46772E+07 -5.50817E+07 6.21764E+07 + + + + + + diff --git a/src/ContactReader/resultante_rn.txt b/src/ContactReader/resultante_rn.txt new file mode 100644 index 0000000..d3de61f --- /dev/null +++ b/src/ContactReader/resultante_rn.txt @@ -0,0 +1,16 @@ +X Y Z DX DY DZ +31.1904 -77.7735 925.000 -8.35353E+07 9.40967E+07 6.90162E+07 +22.1834 -61.2353 908.778 -7.45578E+07 7.06419E+07 9.90851E+07 +13.0107 -37.3614 891.655 -1.12622E+08 6.34184E+07 1.44383E+08 +7.51714 -13.1788 878.021 -2.56908E+08 1.12063E+08 3.62862E+08 +7.33547 15.1584 875.000 -2.30530E+08 -5.64037E+07 2.79925E+08 +17.3523 38.7410 892.437 -1.50812E+08 -7.11926E+07 1.93785E+08 +23.1342 61.0311 902.140 -9.96464E+07 -6.47382E+07 1.24483E+08 +36.1858 80.8956 916.589 -7.26821E+07 -5.30730E+07 8.41775E+07 +50.9030 94.6827 932.711 -6.46772E+07 -5.50817E+07 6.21764E+07 + + + + + + diff --git a/src/CustomFilters/CMakeLists.txt b/src/CustomFilters/CMakeLists.txt new file mode 100644 index 0000000..6526e4b --- /dev/null +++ b/src/CustomFilters/CMakeLists.txt @@ -0,0 +1,28 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +# +# Author : Anthony Geay (EDF R&D) + +project(CustomFilters) +cmake_minimum_required(VERSION 2.8) +install(FILES papbComp.xml DESTINATION lib/paraview) +install(FILES TemporalCSVReader.xml DESTINATION lib/paraview) +# make testMEDReader3.py fail +#install(FILES Electromagnetism.xml DESTINATION lib/paraview) diff --git a/src/CustomFilters/Electromagnetism.xml b/src/CustomFilters/Electromagnetism.xml new file mode 100644 index 0000000..c5c80fb --- /dev/null +++ b/src/CustomFilters/Electromagnetism.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/CustomFilters/TemporalCSVReader.xml b/src/CustomFilters/TemporalCSVReader.xml new file mode 100644 index 0000000..761c2ff --- /dev/null +++ b/src/CustomFilters/TemporalCSVReader.xml @@ -0,0 +1,117 @@ + + + + The + temporal Delimited text reader reads a Delimited Text values + file into a 1D rectilinear grid. A user defined column is + used as time step indicator. On a given time step s, only + the lines having this column at the value s are kept. The + default file extensions are .csv, .tcsv, .txt. + + + + + This property specifies the file name for the temporal CSV + (Command Separated Values) reader. + + + + This property lists the characters that may be used to + separate fields. For example, a value of "," indicates a + comma-separated value file. A value of ".:;" indicates that columns + may be separated by a period, colon or semicolon. The order of the + characters in the text string does not matter. + + + + + This property indicates whether to add the tab character as a + field delimiter to the list of other delimiter characters. This is needed + since in the GUI the user can't enter a tab character. + + + + + Whether to merge successive delimiters. Use this if (for + example) your fields are separated by spaces but you don't know exactly + how many. + + + + + If the value of this property is 1, treat the first line + of the file as headers. Otherwise, column are named using their + position with the following pattern: "Field [num]" starting at 0. Use + this name to define the temporal indicator. + + + + + + This property specifies the name of the column + to use as time indicator. + + + + + This property specifies wether or not to keep + the column chosen as time step indicator in the output. + + + + + + Available timestep values. + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/CustomFilters/papbComp.cpd b/src/CustomFilters/papbComp.cpd new file mode 100644 index 0000000..520260a --- /dev/null +++ b/src/CustomFilters/papbComp.cpd @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/CustomFilters/papbComp.xml b/src/CustomFilters/papbComp.xml new file mode 100644 index 0000000..cf70eaf --- /dev/null +++ b/src/CustomFilters/papbComp.xml @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/CustomFilters/post_R_resuir.trco b/src/CustomFilters/post_R_resuir.trco new file mode 100644 index 0000000..7379706 --- /dev/null +++ b/src/CustomFilters/post_R_resuir.trco @@ -0,0 +1,181 @@ +INST;DX;DY;DZ;X;Y;Z +1.0;16.9;-13.0;-20.5;50.9;-84.7;798.4 +2.0;32.3;-17.1;-21.4;50.9;-84.7;798.4 +3.0;33.7;-16.8;-21.5;50.9;-84.7;798.4 +4.0;34.2;-16.7;-21.5;50.9;-84.7;798.4 +5.0;35.9;-16.3;-21.6;50.9;-84.7;798.4 +6.0;40.0;-19.0;-22.0;50.9;-84.7;798.4 +7.0;67.0;-44.2;-19.2;50.9;-84.7;798.4 +8.0;68.5;-43.9;-19.3;50.9;-84.7;798.4 +9.0;68.9;-43.8;-19.3;50.9;-84.7;798.4 +10.0;-5.3;12.1;-23.4;50.9;-84.7;798.4 +11.0;-3.9;12.4;-23.5;50.9;-84.7;798.4 +12.0;-3.4;12.5;-23.5;50.9;-84.7;798.4 +1.0;0.6;-5.4;-9.7;35.5;-73.5;795.2 +2.0;11.4;-9.3;-11.0;35.5;-73.5;795.2 +3.0;12.3;-9.3;-11.0;35.5;-73.5;795.2 +4.0;12.6;-9.3;-11.1;35.5;-73.5;795.2 +5.0;13.7;-9.2;-11.2;35.5;-73.5;795.2 +6.0;16.1;-11.5;-11.6;35.5;-73.5;795.2 +7.0;11.9;-19.8;-7.1;35.5;-73.5;795.2 +8.0;12.8;-19.7;-7.2;35.5;-73.5;795.2 +9.0;13.1;-19.7;-7.3;35.5;-73.5;795.2 +10.0;9.8;3.0;-15.0;35.5;-73.5;795.2 +11.0;10.7;3.0;-15.0;35.5;-73.5;795.2 +12.0;11.0;3.0;-15.1;35.5;-73.5;795.2 +1.0;2.8;-11.6;-11.1;26.2;-63.2;791.4 +2.0;27.8;-24.6;-13.7;26.2;-63.2;791.4 +3.0;29.4;-25.1;-13.9;26.2;-63.2;791.4 +4.0;29.9;-25.3;-14.0;26.2;-63.2;791.4 +5.0;31.8;-25.8;-14.2;26.2;-63.2;791.4 +6.0;36.9;-29.9;-15.5;26.2;-63.2;791.4 +7.0;29.7;-44.2;-3.3;26.2;-63.2;791.4 +8.0;31.3;-44.7;-3.5;26.2;-63.2;791.4 +9.0;31.8;-44.8;-3.5;26.2;-63.2;791.4 +10.0;23.6;-1.0;-25.0;26.2;-63.2;791.4 +11.0;25.2;-1.4;-25.2;26.2;-63.2;791.4 +12.0;25.8;-1.6;-25.3;26.2;-63.2;791.4 +1.0;-0.3;-12.0;-27.8;19.2;-51.6;785.2 +2.0;43.8;-33.9;-34.3;19.2;-51.6;785.2 +3.0;45.9;-34.8;-34.7;19.2;-51.6;785.2 +4.0;46.6;-35.1;-34.8;19.2;-51.6;785.2 +5.0;49.1;-36.1;-35.3;19.2;-51.6;785.2 +6.0;58.9;-42.1;-37.9;19.2;-51.6;785.2 +7.0;41.2;-58.8;-21.5;19.2;-51.6;785.2 +8.0;43.3;-59.7;-21.9;19.2;-51.6;785.2 +9.0;44.0;-60.0;-22.0;19.2;-51.6;785.2 +10.0;44.0;-2.5;-49.3;19.2;-51.6;785.2 +11.0;46.1;-3.4;-49.7;19.2;-51.6;785.2 +12.0;46.8;-3.7;-49.8;19.2;-51.6;785.2 +1.0;-6.0;-3.5;-53.0;14.0;-39.1;777.7 +2.0;57.3;-31.5;-64.9;14.0;-39.1;777.7 +3.0;59.6;-32.5;-65.4;14.0;-39.1;777.7 +4.0;60.4;-32.9;-65.6;14.0;-39.1;777.7 +5.0;63.0;-34.0;-66.2;14.0;-39.1;777.7 +6.0;78.0;-40.7;-69.9;14.0;-39.1;777.7 +7.0;48.9;-58.9;-54.9;14.0;-39.1;777.7 +8.0;51.2;-59.9;-55.4;14.0;-39.1;777.7 +9.0;52.0;-60.3;-55.6;14.0;-39.1;777.7 +10.0;64.1;5.6;-77.9;14.0;-39.1;777.7 +11.0;66.4;4.6;-78.4;14.0;-39.1;777.7 +12.0;67.1;4.2;-78.6;14.0;-39.1;777.7 +1.0;-8.0;3.1;-75.7;10.8;-25.8;770.0 +2.0;67.0;-27.1;-87.7;10.8;-25.8;770.0 +3.0;69.2;-28.1;-88.1;10.8;-25.8;770.0 +4.0;69.9;-28.4;-88.2;10.8;-25.8;770.0 +5.0;72.4;-29.5;-88.6;10.8;-25.8;770.0 +6.0;91.0;-36.0;-92.2;10.8;-25.8;770.0 +7.0;64.1;-63.4;-85.0;10.8;-25.8;770.0 +8.0;66.3;-64.3;-85.3;10.8;-25.8;770.0 +9.0;67.0;-64.6;-85.4;10.8;-25.8;770.0 +10.0;69.9;18.9;-93.0;10.8;-25.8;770.0 +11.0;72.1;17.9;-93.3;10.8;-25.8;770.0 +12.0;72.8;17.6;-93.5;10.8;-25.8;770.0 +1.0;-4.6;-0.7;-102.4;7.7;-13.2;763.3 +2.0;81.5;-3.3;-76.2;7.7;-13.2;763.3 +3.0;83.4;-3.4;-75.1;7.7;-13.2;763.3 +4.0;84.0;-3.4;-74.7;7.7;-13.2;763.3 +5.0;86.2;-3.5;-73.4;7.7;-13.2;763.3 +6.0;104.7;-4.4;-66.9;7.7;-13.2;763.3 +7.0;67.2;-42.3;-109.3;7.7;-13.2;763.3 +8.0;69.1;-42.4;-108.2;7.7;-13.2;763.3 +9.0;69.7;-42.4;-107.8;7.7;-13.2;763.3 +10.0;101.3;51.7;-37.6;7.7;-13.2;763.3 +11.0;103.1;51.6;-36.4;7.7;-13.2;763.3 +12.0;103.7;51.6;-36.1;7.7;-13.2;763.3 +1.0;-1.7;-1.2;-111.9;6.3;0.0;760.2 +2.0;66.7;0.3;-79.6;6.3;0.0;760.2 +3.0;68.0;0.2;-78.2;6.3;0.0;760.2 +4.0;68.4;0.2;-77.8;6.3;0.0;760.2 +5.0;69.8;0.1;-76.1;6.3;0.0;760.2 +6.0;85.0;-1.4;-66.0;6.3;0.0;760.2 +7.0;52.5;-7.5;-122.3;6.3;0.0;760.2 +8.0;53.7;-7.6;-120.9;6.3;0.0;760.2 +9.0;54.1;-7.6;-120.4;6.3;0.0;760.2 +10.0;88.3;11.3;-24.7;6.3;0.0;760.2 +11.0;89.5;11.2;-23.3;6.3;0.0;760.2 +12.0;90.0;11.2;-22.8;6.3;0.0;760.2 +1.0;-5.1;-2.6;-100.2;7.7;13.2;763.3 +2.0;81.3;7.5;-89.2;7.7;13.2;763.3 +3.0;83.1;7.7;-88.5;7.7;13.2;763.3 +4.0;83.7;7.8;-88.3;7.7;13.2;763.3 +5.0;85.9;8.1;-87.6;7.7;13.2;763.3 +6.0;105.4;8.0;-82.7;7.7;13.2;763.3 +7.0;64.4;22.3;-107.6;7.7;13.2;763.3 +8.0;66.2;22.6;-106.9;7.7;13.2;763.3 +9.0;66.8;22.6;-106.7;7.7;13.2;763.3 +10.0;104.9;-16.1;-67.8;7.7;13.2;763.3 +11.0;106.8;-15.9;-67.2;7.7;13.2;763.3 +12.0;107.4;-15.8;-67.0;7.7;13.2;763.3 +1.0;-9.0;-4.1;-81.6;11.0;25.3;768.6 +2.0;79.8;13.3;-84.4;11.0;25.3;768.6 +3.0;82.2;13.9;-84.4;11.0;25.3;768.6 +4.0;83.0;14.1;-84.4;11.0;25.3;768.6 +5.0;85.8;14.9;-84.4;11.0;25.3;768.6 +6.0;106.1;18.5;-84.2;11.0;25.3;768.6 +7.0;63.3;32.3;-87.8;11.0;25.3;768.6 +8.0;65.7;33.0;-87.8;11.0;25.3;768.6 +9.0;66.5;33.2;-87.8;11.0;25.3;768.6 +10.0;100.2;-15.9;-83.8;11.0;25.3;768.6 +11.0;102.6;-15.3;-83.8;11.0;25.3;768.6 +12.0;103.4;-15.1;-83.8;11.0;25.3;768.6 +1.0;-9.4;0.9;-58.8;15.0;38.7;775.6 +2.0;71.3;29.7;-71.1;15.0;38.7;775.6 +3.0;74.1;30.9;-71.6;15.0;38.7;775.6 +4.0;75.0;31.2;-71.7;15.0;38.7;775.6 +5.0;78.3;32.6;-72.3;15.0;38.7;775.6 +6.0;96.9;40.8;-75.7;15.0;38.7;775.6 +7.0;63.5;75.7;-53.8;15.0;38.7;775.6 +8.0;66.3;76.9;-54.3;15.0;38.7;775.6 +9.0;67.2;77.3;-54.5;15.0;38.7;775.6 +10.0;77.2;-30.6;-93.7;15.0;38.7;775.6 +11.0;80.0;-29.4;-94.2;15.0;38.7;775.6 +12.0;80.9;-29.0;-94.4;15.0;38.7;775.6 +1.0;-3.0;10.4;-34.6;19.9;51.1;782.9 +2.0;46.1;30.2;-43.1;19.9;51.1;782.9 +3.0;48.5;31.1;-43.5;19.9;51.1;782.9 +4.0;49.2;31.4;-43.6;19.9;51.1;782.9 +5.0;52.0;32.3;-44.1;19.9;51.1;782.9 +6.0;63.3;39.6;-46.8;19.9;51.1;782.9 +7.0;40.9;61.2;-26.7;19.9;51.1;782.9 +8.0;43.2;62.1;-27.1;19.9;51.1;782.9 +9.0;44.0;62.3;-27.3;19.9;51.1;782.9 +10.0;48.6;-9.4;-62.5;19.9;51.1;782.9 +11.0;50.9;-8.6;-63.0;19.9;51.1;782.9 +12.0;51.7;-8.3;-63.1;19.9;51.1;782.9 +1.0;4.7;16.1;-15.5;26.3;62.2;788.8 +2.0;33.4;28.5;-21.6;26.3;62.2;788.8 +3.0;35.1;29.0;-21.9;26.3;62.2;788.8 +4.0;35.7;29.1;-22.0;26.3;62.2;788.8 +5.0;37.8;29.7;-22.4;26.3;62.2;788.8 +6.0;44.6;35.1;-24.4;26.3;62.2;788.8 +7.0;39.7;60.3;-9.5;26.3;62.2;788.8 +8.0;41.5;60.8;-9.8;26.3;62.2;788.8 +9.0;42.1;60.9;-9.9;26.3;62.2;788.8 +10.0;23.3;-9.9;-34.7;26.3;62.2;788.8 +11.0;25.1;-9.4;-35.1;26.3;62.2;788.8 +12.0;25.6;-9.3;-35.2;26.3;62.2;788.8 +1.0;5.0;10.2;-10.3;35.6;73.3;794.5 +2.0;19.6;15.1;-14.0;35.6;73.3;794.5 +3.0;20.9;15.2;-14.3;35.6;73.3;794.5 +4.0;21.3;15.2;-14.3;35.6;73.3;794.5 +5.0;22.7;15.3;-14.6;35.6;73.3;794.5 +6.0;26.2;18.2;-15.7;35.6;73.3;794.5 +7.0;28.3;35.5;-9.4;35.6;73.3;794.5 +8.0;29.5;35.6;-9.7;35.6;73.3;794.5 +9.0;29.9;35.6;-9.8;35.6;73.3;794.5 +10.0;8.5;-8.4;-18.4;35.6;73.3;794.5 +11.0;9.8;-8.3;-18.7;35.6;73.3;794.5 +12.0;10.2;-8.3;-18.8;35.6;73.3;794.5 +1.0;17.1;13.5;-15.7;51.8;85.5;799.0 +2.0;23.8;15.5;-16.7;51.8;85.5;799.0 +3.0;24.4;15.5;-16.8;51.8;85.5;799.0 +4.0;24.7;15.5;-16.8;51.8;85.5;799.0 +5.0;25.5;15.4;-16.9;51.8;85.5;799.0 +6.0;29.0;16.8;-17.4;51.8;85.5;799.0 +7.0;60.6;44.8;-11.4;51.8;85.5;799.0 +8.0;61.2;44.8;-11.5;51.8;85.5;799.0 +9.0;61.5;44.8;-11.5;51.8;85.5;799.0 +10.0;-15.3;-15.5;-21.9;51.8;85.5;799.0 +11.0;-14.6;-15.6;-22.0;51.8;85.5;799.0 +12.0;-14.4;-15.6;-22.0;51.8;85.5;799.0 diff --git a/src/DepthVsTime/CMakeLists.txt b/src/DepthVsTime/CMakeLists.txt new file mode 100644 index 0000000..d18fcb4 --- /dev/null +++ b/src/DepthVsTime/CMakeLists.txt @@ -0,0 +1,49 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +cmake_minimum_required(VERSION 3.8) +project(DepthVsTimePlugin) +find_package(ParaView REQUIRED) + +include(GNUInstallDirs) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}") +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}") +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}") + +set("_paraview_plugin_default_${CMAKE_PROJECT_NAME}" ON) +paraview_plugin_scan( + ENABLE_BY_DEFAULT YES + PLUGIN_FILES "${CMAKE_CURRENT_SOURCE_DIR}/plugin/paraview.plugin" + PROVIDES_PLUGINS plugins + REQUIRES_MODULES required_modules) + +foreach(module IN LISTS required_modules) + if(NOT TARGET "${module}") + message("Missing required module: ${module}") + return() + endif() +endforeach() + +set(BUILD_SHARED_LIBS ON) +paraview_plugin_build( + RUNTIME_DESTINATION "${CMAKE_INSTALL_BINDIR}" + LIBRARY_DESTINATION "${CMAKE_INSTALL_LIBDIR}" + LIBRARY_SUBDIRECTORY "${PARAVIEW_PLUGIN_SUBDIR}" + PLUGINS ${plugins} + AUTOLOAD ${plugins}) diff --git a/src/DepthVsTime/TestCase.py b/src/DepthVsTime/TestCase.py new file mode 100644 index 0000000..ef022a5 --- /dev/null +++ b/src/DepthVsTime/TestCase.py @@ -0,0 +1,42 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +from MEDLoader import * + +fname="hydrau_test4.med" +meshName="mesh" +arr=DataArrayDouble([0,1,2,3,4,5]) +m=MEDCouplingCMesh() +m.setCoords(arr,arr) +m=m.buildUnstructured() +m.setName(meshName) +m.simplexize(0) +WriteMesh(fname,m,True) +# +f=MEDCouplingFieldDouble(ON_NODES) +f.setMesh(m) +f.setName("Field") +arr=m.getCoords().magnitude() +f.setArray(arr) +for i in range(10): + arr+=0.1 + f.setTime(float(i)+0.2,i,0) + WriteFieldUsingAlreadyWrittenMesh(fname,f) + pass + diff --git a/src/DepthVsTime/plugin/CMakeLists.txt b/src/DepthVsTime/plugin/CMakeLists.txt new file mode 100644 index 0000000..f9ef0ae --- /dev/null +++ b/src/DepthVsTime/plugin/CMakeLists.txt @@ -0,0 +1,39 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +paraview_add_plugin(DepthVsTimePlugin + VERSION "1.0" + MODULES DepthVsTimeModule + MODULE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/DepthVsTimeModule/vtk.module" + SERVER_MANAGER_XML filters.xml + ) + +install(TARGETS DepthVsTimePlugin + RUNTIME DESTINATION lib/paraview + LIBRARY DESTINATION lib/paraview + ARCHIVE DESTINATION lib/paraview +) diff --git a/src/DepthVsTime/plugin/DepthVsTimeModule/CMakeLists.txt b/src/DepthVsTime/plugin/DepthVsTimeModule/CMakeLists.txt new file mode 100644 index 0000000..156a14a --- /dev/null +++ b/src/DepthVsTime/plugin/DepthVsTimeModule/CMakeLists.txt @@ -0,0 +1,27 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +set(classes + vtkDepthVsTime +) + +vtk_module_add_module(DepthVsTimeModule + FORCE_STATIC + CLASSES ${classes} +) diff --git a/src/DepthVsTime/plugin/DepthVsTimeModule/vtk.module b/src/DepthVsTime/plugin/DepthVsTimeModule/vtk.module new file mode 100644 index 0000000..d120ded --- /dev/null +++ b/src/DepthVsTime/plugin/DepthVsTimeModule/vtk.module @@ -0,0 +1,32 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +NAME + DepthVsTimeModule +DEPENDS + VTK::CommonCore + VTK::CommonDataModel + VTK::CommonExecutionModel + VTK::FiltersCore + VTK::FiltersModeling +PRIVATE_DEPENDS + VTK::CommonMisc + VTK::CommonSystem + VTK::FiltersGeneral + VTK::RenderingCore diff --git a/src/DepthVsTime/plugin/DepthVsTimeModule/vtkDepthVsTime.cxx b/src/DepthVsTime/plugin/DepthVsTimeModule/vtkDepthVsTime.cxx new file mode 100644 index 0000000..5afd50e --- /dev/null +++ b/src/DepthVsTime/plugin/DepthVsTimeModule/vtkDepthVsTime.cxx @@ -0,0 +1,681 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +// Author : Anthony Geay (EDF R&D) + +#include "vtkDepthVsTime.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +vtkStandardNewMacro(vtkDepthVsTime); + +constexpr int NB_OF_DISCR_TO_DEDUCE_START_STOP = 1001; + +/////////////////// + +template +class AutoPtr +{ +public: + AutoPtr(T *ptr = 0) : _ptr(ptr) {} + ~AutoPtr() { destroyPtr(); } + AutoPtr &operator=(T *ptr) + { + if (_ptr != ptr) + { + destroyPtr(); + _ptr = ptr; + } + return *this; + } + T *operator->() { return _ptr; } + const T *operator->() const { return _ptr; } + T &operator*() { return *_ptr; } + const T &operator*() const { return *_ptr; } + operator T *() { return _ptr; } + operator const T *() const { return _ptr; } + +private: + void destroyPtr() { delete[] _ptr; } + +private: + T *_ptr; +}; + +class MZCException : public std::exception +{ +public: + MZCException(const std::string &s) : _reason(s) {} + virtual const char *what() const throw() { return _reason.c_str(); } + virtual ~MZCException() throw() {} + +private: + std::string _reason; +}; + +class vtkDepthVsTime::vtkInternal +{ +public: + vtkInternal() : _isInit(true) { _nbItems = std::numeric_limits::max(); } + void operate(double timeStep, vtkUnstructuredGrid *usgIn); + void pushData(double timeStep, vtkPolyData *data); + void fillGrid(vtkRectilinearGrid *grid) const; + void scanCoordsOfDS(vtkUnstructuredGrid *usg, vtkPolyData *zePoint, int nbOfExpectedDiscr); + static std::size_t CheckPts(vtkPointSet *usg, vtkDataArray *&arr); + +private: + void pushDataInit(double timeStep, vtkDataSetAttributes *dsa); + void pushDataStd(double timeStep, vtkDataSetAttributes *dsa); + +private: + bool _isInit; + std::size_t _nbItems; + std::vector _columnNames; + std::vector _time; + // sizeof(_data)==sizeof(_columnNames) + std::vector> _data; + vtkSmartPointer _ladder; +}; + +void ExtractInfo(vtkInformationVector *inputVector, vtkUnstructuredGrid *&usgIn) +{ + vtkInformation *inputInfo(inputVector->GetInformationObject(0)); + vtkDataSet *input(0); + vtkDataSet *input0(vtkDataSet::SafeDownCast(inputInfo->Get(vtkDataObject::DATA_OBJECT()))); + vtkMultiBlockDataSet *input1(vtkMultiBlockDataSet::SafeDownCast(inputInfo->Get(vtkDataObject::DATA_OBJECT()))); + if (input0) + input = input0; + else + { + if (!input1) + throw MZCException("Input dataSet must be a DataSet or single elt multi block dataset expected !"); + if (input1->GetNumberOfBlocks() != 1) + throw MZCException("Input dataSet is a multiblock dataset with not exactly one block ! Use MergeBlocks or ExtractBlocks filter before calling this filter !"); + vtkDataObject *input2(input1->GetBlock(0)); + if (!input2) + throw MZCException("Input dataSet is a multiblock dataset with exactly one block but this single element is NULL !"); + vtkDataSet *input2c(vtkDataSet::SafeDownCast(input2)); + if (!input2c) + throw MZCException("Input dataSet is a multiblock dataset with exactly one block but this single element is not a dataset ! Use MergeBlocks or ExtractBlocks filter before calling this filter !"); + input = input2c; + } + if (!input) + throw MZCException("Input data set is NULL !"); + usgIn = vtkUnstructuredGrid::SafeDownCast(input); + if (!usgIn) + throw MZCException("Input data set is not an unstructured mesh ! This filter works only on unstructured meshes !"); +} + +//////////////////// + +vtkDepthVsTime::vtkDepthVsTime() : NbDiscrPtsAlongZ(10), NumberOfTimeSteps(0), IsExecuting(false), CurrentTimeIndex(0), Internal(NULL) +{ + this->SetNumberOfInputPorts(2); + this->SetNumberOfOutputPorts(1); +} + +vtkDepthVsTime::~vtkDepthVsTime() +{ + delete this->Internal; + this->Internal = NULL; +} + +int vtkDepthVsTime::RequestInformation(vtkInformation *request, vtkInformationVector **inputVector, vtkInformationVector *outputVector) +{ + //std::cerr << "########################################## vtkDepthVsTime::RequestInformation ##########################################" << std::endl; + try + { + vtkUnstructuredGrid *usgIn(0); + ExtractInfo(inputVector[0], usgIn); + vtkInformation *inInfo(inputVector[0]->GetInformationObject(0)); + if (inInfo->Has(vtkStreamingDemandDrivenPipeline::TIME_STEPS())) + { + this->NumberOfTimeSteps = inInfo->Length(vtkStreamingDemandDrivenPipeline::TIME_STEPS()); + } + else + { + this->NumberOfTimeSteps = 0; + } + // The output of this filter does not contain a specific time, rather + // it contains a collection of time steps. Also, this filter does not + // respond to time requests. Therefore, we remove all time information + // from the output. + vtkInformation *outInfo(outputVector->GetInformationObject(0)); + if (outInfo->Has(vtkStreamingDemandDrivenPipeline::TIME_STEPS())) + { + outInfo->Remove(vtkStreamingDemandDrivenPipeline::TIME_STEPS()); + } + if (outInfo->Has(vtkStreamingDemandDrivenPipeline::TIME_RANGE())) + { + outInfo->Remove(vtkStreamingDemandDrivenPipeline::TIME_RANGE()); + } + return 1; + } + catch (MZCException &e) + { + vtkErrorMacro(<< "Exception has been thrown in vtkDepthVsTime::RequestInformation : " << e.what()); + return 0; + } + return 1; +} + +int vtkDepthVsTime::RequestUpdateExtent(vtkInformation *, vtkInformationVector **inputVector, vtkInformationVector *vtkNotUsed(outputVector)) +{ + // vtkInformation* outInfo = outputVector->GetInformationObject(0); + vtkInformation *inInfo1 = inputVector[0]->GetInformationObject(0); + + // get the requested update extent + double *inTimes = inInfo1->Get(vtkStreamingDemandDrivenPipeline::TIME_STEPS()); + if (inTimes) + { + double timeReq = inTimes[this->CurrentTimeIndex]; + inInfo1->Set(vtkStreamingDemandDrivenPipeline::UPDATE_TIME_STEP(), timeReq); + } + + return 1; +} + +std::string buildNameOfEntryFrom(const std::string &name, std::size_t id, std::size_t nbOfElt) +{ + if (nbOfElt == 0) + throw MZCException("buildNameOfEntryFrom : nbElt == 0 !"); + if (nbOfElt == 1) + return name; + std::ostringstream oss; + oss << name << "_" << id; + return oss.str(); +} + +void fillFieldInGrid(vtkDataSet *ds, const std::vector> &valuesByColumn, std::size_t szX, std::size_t szY, const std::vector &columnNames) +{ + vtkPointData *pd(ds->GetPointData()); + if (!pd) + throw MZCException("fillFieldInGrid : internal error 1 !"); + std::size_t nbFields(columnNames.size()); + for (std::size_t i = 0; i < nbFields; i++) + { + vtkNew arr; + arr->SetName(columnNames[i].c_str()); + arr->SetNumberOfTuples(szX * szY); + const std::vector &data(valuesByColumn[i]); + double *pt(arr->GetPointer(0)); + if (data.size() != szX * szY) + { + std::ostringstream oss; + oss << "fillFieldInGrid : fatal internal error !" + << "Expect " << szX << "*" << szY << "=" << szX * szY << " But having " << data.size() << " ! mail to anthony.geay@edf.fr !"; + throw MZCException(oss.str()); + } + // transpose + for (std::size_t i = 0; i < szX; i++) + for (std::size_t j = 0; j < szY; j++) + pt[j * szX + i] = data[i * szY + j]; + // + pd->AddArray(arr); + } +} + +void vtkDepthVsTime::vtkInternal::operate(double timeStep, vtkUnstructuredGrid *usgIn) +{ + vtkNew sourceCpy; + sourceCpy->DeepCopy(_ladder); + vtkNew probeFilter; + probeFilter->SetInputData(sourceCpy); + probeFilter->SetSourceData(usgIn); + probeFilter->Update(); + vtkDataObject *res(probeFilter->GetOutput()); + vtkPolyData *res2(vtkPolyData::SafeDownCast(res)); + if (!res2) + { + std::ostringstream oss; + oss << "Internal error ! unexpected returned of resample filter !"; + throw MZCException(oss.str()); + } + pushData(timeStep, res2); +} + +void vtkDepthVsTime::vtkInternal::pushData(double timeStep, vtkPolyData *ds) +{ + if (!ds) + throw MZCException("pushData : no data !"); + vtkDataSetAttributes *dsa(ds->GetPointData()); + if (!dsa) + throw MZCException("pushData : no point data !"); + _time.push_back(timeStep); + if (_isInit) + pushDataInit(timeStep, dsa); + else + pushDataStd(timeStep, dsa); + _isInit = false; +} + +void vtkDepthVsTime::vtkInternal::pushDataInit(double timeStep, vtkDataSetAttributes *dsa) +{ + int nba(dsa->GetNumberOfArrays()); + for (int i = 0; i < nba; i++) + { + vtkDataArray *arr(dsa->GetArray(i)); + if (!arr) + continue; + if (arr->GetNumberOfComponents() != 1) + continue; + std::size_t tmp(arr->GetNumberOfTuples()); + if (tmp == 0) + continue; + if (_nbItems == std::numeric_limits::max()) + _nbItems = tmp; + if (tmp != _nbItems) + continue; + const char *name(arr->GetName()); + if (!name) + continue; + vtkDoubleArray *arr1(vtkDoubleArray::SafeDownCast(arr)); + vtkFloatArray *arr2(vtkFloatArray::SafeDownCast(arr)); + if (!arr1 && !arr2) + continue; + _columnNames.push_back(name); + if (arr1) + { + const double *pt(arr1->GetPointer(0)); + _data.resize(_columnNames.size()); + std::vector &data(_data[_columnNames.size() - 1]); + data.insert(data.end(), pt, pt + _nbItems); + continue; + } + if (arr2) + { + const float *pt(arr2->GetPointer(0)); + _data.resize(_columnNames.size()); + std::vector &data(_data[_columnNames.size() - 1]); + data.insert(data.end(), pt, pt + _nbItems); + continue; + } + } +} + +void vtkDepthVsTime::vtkInternal::pushDataStd(double timeStep, vtkDataSetAttributes *dsa) +{ + std::set cnsRef(_columnNames.begin(), _columnNames.end()), cns; + int nba(dsa->GetNumberOfArrays()); + for (int i = 0; i < nba; i++) + { + vtkDataArray *arr(dsa->GetArray(i)); + if (!arr) + continue; + if (arr->GetNumberOfComponents() != 1) + continue; + if (arr->GetNumberOfTuples() != _nbItems) + continue; + const char *name(arr->GetName()); + if (!name) + continue; + vtkDoubleArray *arr1(vtkDoubleArray::SafeDownCast(arr)); + vtkFloatArray *arr2(vtkFloatArray::SafeDownCast(arr)); + if (!arr1 && !arr2) + continue; + std::string nameCpp(name); + std::vector::iterator it(std::find(_columnNames.begin(), _columnNames.end(), nameCpp)); + if (it == _columnNames.end()) + continue; + std::size_t columnId(std::distance(_columnNames.begin(), it)); + if (cns.find(nameCpp) != cns.end()) + throw MZCException("pushDataStd : internal error 1 !"); + cns.insert(nameCpp); + std::vector &data(_data[columnId]); + if (arr1) + { + const double *pt(arr1->GetPointer(0)); + data.insert(data.end(), pt, pt + _nbItems); + continue; + } + if (arr2) + { + const float *pt(arr2->GetPointer(0)); + data.insert(data.end(), pt, pt + _nbItems); + continue; + } + } + if (cnsRef != cns) + throw MZCException("Some float arrays are not present along time !"); +} + +void vtkDepthVsTime::vtkInternal::fillGrid(vtkRectilinearGrid *grid) const +{ + grid->SetDimensions(_time.size(), _nbItems, 1); + { + vtkNew arrX; + arrX->SetNumberOfTuples(_time.size()); + std::copy(_time.begin(), _time.end(), arrX->GetPointer(0)); + grid->SetXCoordinates(arrX); + } + { + vtkNew arrY; + arrY->SetNumberOfTuples(_nbItems); + double *arrYPt(arrY->GetPointer(0)); + vtkDoubleArray *data(vtkDoubleArray::SafeDownCast(_ladder->GetPoints()->GetData())); + if (!data) + throw MZCException("fillGrid : internal error 1 !"); + if (data->GetNumberOfTuples() != _nbItems) + throw MZCException("fillGrid : internal error 2 !"); + const double *pt(data->GetPointer(0)); + for (std::size_t i = 0; i < _nbItems; i++) + arrYPt[i] = pt[3 * i + 2]; + grid->SetYCoordinates(arrY); + } + fillFieldInGrid(grid, _data, _time.size(), _nbItems, _columnNames); +} + +std::size_t vtkDepthVsTime::vtkInternal::CheckPts(vtkPointSet *usg, vtkDataArray *&arr) +{ + if (!usg) + throw MZCException("CheckPts : expect an unstucturedgrid !"); + vtkPoints *pts(usg->GetPoints()); + if (!pts) + throw MZCException("CheckPts : no points in grid !"); + arr = pts->GetData(); + if (!arr) + throw MZCException("CheckPts : no data in points in grid !"); + if (arr->GetNumberOfComponents() != 3) + throw MZCException("CheckPts : 3D expected !"); + std::size_t nbPts(arr->GetNumberOfTuples()); + if (nbPts < 1) + throw MZCException("CheckPts : no input point !"); + vtkDoubleArray *arr1(vtkDoubleArray::SafeDownCast(arr)); + vtkFloatArray *arr2(vtkFloatArray::SafeDownCast(arr)); + if (!arr1 && !arr2) + throw MZCException("scanCoordsOfDS : for coords expected FLOAT32 or FLOAT64 !"); + return nbPts; +} + +void fillLader(vtkPolyData *source, double xpos, double ypos, double zmin, double zmax, int nbOfDiscr, const double *&ptOnInputDiscr) +{ + vtkNew arr; + arr->SetNumberOfComponents(3); + arr->SetNumberOfTuples(nbOfDiscr); + double *pt(arr->GetPointer(0)), delta((zmax - zmin) / ((double)(nbOfDiscr - 1))); + for (int i = 0; i < nbOfDiscr; i++) + { + pt[3 * i] = xpos; + pt[3 * i + 1] = ypos; + pt[3 * i + 2] = ((double)i) * delta + zmin; + } + ptOnInputDiscr = pt; + vtkNew pts; + pts->SetData(arr); + source->SetPoints(pts); + vtkNew verts; + { + vtkNew conn; + conn->SetNumberOfComponents(1); + conn->SetNumberOfTuples(2 * nbOfDiscr); + vtkIdType *pt(conn->GetPointer(0)); + for (vtkIdType i = 0; i < nbOfDiscr; i++) + { + pt[2 * i] = 1; + pt[2 * i + 1] = i; + } + verts->SetCells(nbOfDiscr, conn); + } + source->SetVerts(verts); +} + +void vtkDepthVsTime::vtkInternal::scanCoordsOfDS(vtkUnstructuredGrid *usg, vtkPolyData *zePoint, int nbOfExpectedDiscr) +{ + vtkDataArray *arr(0); + std::size_t nbPts(CheckPts(usg, arr)); + double Zmin(std::numeric_limits::max()), Zmax(std::numeric_limits::max()), LocZmin(std::numeric_limits::max()), LocZmax(std::numeric_limits::max()); + { + double tmp[6]; + usg->GetBounds(tmp); + Zmin = tmp[4]; + Zmax = tmp[5]; + } + if (Zmin == Zmax) + throw MZCException("scanCoordsOfDS : Zmin == Zmax ! Looks bad !"); + vtkNew usgCpy; + usgCpy->DeepCopy(usg); + vtkPointData *pd(usgCpy->GetPointData()); + if (!pd) + throw MZCException("scanCoordsOfDS : unexpected case ! send mail to anthony.geay@edf.fr !"); + int nbArr(pd->GetNumberOfArrays()); + if (nbArr >= 1) + { + for (int i = nbArr - 1; i >= 0; i--) + pd->RemoveArray(i); + } + { + vtkNew fakeArr; + fakeArr->SetName("a"); + std::size_t nbPts(usgCpy->GetNumberOfPoints()); + fakeArr->SetNumberOfTuples(nbPts); + double *pt(fakeArr->GetPointer(0)); + std::fill(pt, pt + nbPts, 1.); + pd->AddArray(fakeArr); + } + // + if (zePoint->GetNumberOfPoints() != 1) + throw MZCException("scanCoordsOfDS : source has to have exactly one point !"); + vtkDataArray *pts(zePoint->GetPoints()->GetData()); + if (!pts) + throw MZCException("scanCoordsOfDS : internal error ! send mail to anthony.geay@edf.fr !"); + vtkDoubleArray *pts1(vtkDoubleArray::SafeDownCast(pts)); + vtkFloatArray *pts2(vtkFloatArray::SafeDownCast(pts)); + if (!pts1 && !pts2) + throw MZCException("scanCoordsOfDS : internal error 2 ! send mail to anthony.geay@edf.fr !"); + double Xpos(std::numeric_limits::max()), Ypos(std::numeric_limits::max()); + if (pts1) + { + Xpos = pts1->GetTypedComponent(0, 0); + Ypos = pts1->GetTypedComponent(0, 1); + } + else + { + Xpos = (double)pts2->GetTypedComponent(0, 0); + Ypos = (double)pts2->GetTypedComponent(0, 1); + } + // + vtkNew source; + const double *ptOnInputDiscr(NULL); + { + fillLader(source, Xpos, Ypos, Zmin, Zmax, NB_OF_DISCR_TO_DEDUCE_START_STOP, ptOnInputDiscr); + } + // + vtkNew probeFilter; + probeFilter->SetInputData(source); + probeFilter->SetSourceData(usgCpy); + probeFilter->Update(); + vtkDataObject *res(probeFilter->GetOutput()); + vtkPolyData *res2(vtkPolyData::SafeDownCast(res)); + if (!res2) + throw MZCException("scanCoordsOfDS : Internal error ! unexpected returned of resample filter !"); + // + { + vtkPointData *pd(res2->GetPointData()); + if (!pd) + throw MZCException("scanCoordsOfDS : internal error 3 ! send mail to anthony.geay@edf.fr !"); + vtkDataArray *pts(pd->GetArray("a")); + if (!pts) + throw MZCException("scanCoordsOfDS : internal error 4 ! send mail to anthony.geay@edf.fr !"); + vtkDoubleArray *pts1(vtkDoubleArray::SafeDownCast(pts)); + if (!pts1) + throw MZCException("scanCoordsOfDS : internal error 5 ! send mail to anthony.geay@edf.fr !"); + const double *vals(pts1->GetPointer(0)); + int is(std::numeric_limits::max()), ie(std::numeric_limits::max()); + for (int i = 0; i < NB_OF_DISCR_TO_DEDUCE_START_STOP; i++) + { + if (vals[i] == 1.) + { + is = i; + break; + } + } + if (is == std::numeric_limits::max()) + throw MZCException("scanCoordsOfDS : selected point seems to be outside of domain !"); + for (int i = NB_OF_DISCR_TO_DEDUCE_START_STOP - 1; i >= 0; i--) + { + if (vals[i] == 1.) + { + ie = i; + break; + } + } + if (is == ie) + throw MZCException("scanCoordsOfDS : internal error 6 ! send mail to anthony.geay@edf.fr !"); + LocZmin = ptOnInputDiscr[3 * is + 2]; + LocZmax = ptOnInputDiscr[3 * ie + 2]; + } + // + //std::cerr << "mmmmmmm " << Xpos << " " << Ypos << std::endl; + //std::cerr << "-> " << Zmin << " " << Zmax << std::endl; + //std::cerr << "-> " << LocZmin << " " << LocZmax << std::endl; + // + { + _ladder.TakeReference(vtkPolyData::New()); + fillLader(_ladder, Xpos, Ypos, LocZmin, LocZmax, nbOfExpectedDiscr, ptOnInputDiscr); + } +} + +int vtkDepthVsTime::RequestData(vtkInformation *request, vtkInformationVector **inputVector, vtkInformationVector *outputVector) +{ + //std::cerr << "########################################## vtkDepthVsTime::RequestData ##########################################" << std::endl; + try + { + // + if (this->NumberOfTimeSteps == 0) + { + vtkErrorMacro("No time steps in input data!"); + return 0; + } + vtkInformation *outInfo(outputVector->GetInformationObject(0)); + vtkUnstructuredGrid *usgIn(0); + ExtractInfo(inputVector[0], usgIn); + vtkInformation *sourceInfo(inputVector[1]->GetInformationObject(0)); + vtkDataObject *source(sourceInfo->Get(vtkDataObject::DATA_OBJECT())); + vtkPolyData *source2(vtkPolyData::SafeDownCast(source)); + if (!source2) + throw MZCException("vtkPolyData expected as source !"); + // is this the first request + if (!this->IsExecuting) + { + request->Set(vtkStreamingDemandDrivenPipeline::CONTINUE_EXECUTING(), 1); + this->IsExecuting = true; + delete this->Internal; + this->Internal = new vtkInternal; + this->Internal->scanCoordsOfDS(usgIn, source2, this->NbDiscrPtsAlongZ); + } + // + // do something + { + double timeStep; + { + vtkInformation *inInfo(inputVector[0]->GetInformationObject(0)); + vtkDataObject *input(vtkDataObject::GetData(inInfo)); + timeStep = input->GetInformation()->Get(vtkDataObject::DATA_TIME_STEP()); + } + this->Internal->operate(timeStep, usgIn); + } + this->UpdateProgress(double(this->CurrentTimeIndex) / double(this->NumberOfTimeSteps)); + // + this->CurrentTimeIndex++; + if (this->CurrentTimeIndex == this->NumberOfTimeSteps) + { + request->Remove(vtkStreamingDemandDrivenPipeline::CONTINUE_EXECUTING()); + this->CurrentTimeIndex = 0; + this->IsExecuting = false; + vtkInformation *outInfo(outputVector->GetInformationObject(0)); + vtkRectilinearGrid *output(vtkRectilinearGrid::SafeDownCast(outInfo->Get(vtkDataObject::DATA_OBJECT()))); + vtkNew grid; + this->Internal->fillGrid(grid); + output->ShallowCopy(grid); + } + } + catch (MZCException &e) + { + if (this->IsExecuting) + { + request->Remove(vtkStreamingDemandDrivenPipeline::CONTINUE_EXECUTING()); + this->CurrentTimeIndex = 0; + this->IsExecuting = false; + } + vtkErrorMacro(<< "Exception has been thrown in vtkDepthVsTime::RequestData : " << e.what()); + return 0; + } + return 1; +} + +void vtkDepthVsTime::PrintSelf(ostream &os, vtkIndent indent) +{ + this->Superclass::PrintSelf(os, indent); +} + +void vtkDepthVsTime::SetSourceData(vtkDataObject *input) +{ + this->SetInputData(1, input); +} + +void vtkDepthVsTime::SetSourceConnection(vtkAlgorithmOutput *algOutput) +{ + this->SetInputConnection(1, algOutput); +} + +int vtkDepthVsTime::FillOutputPortInformation(int vtkNotUsed(port), vtkInformation *info) +{ + info->Set(vtkDataObject::DATA_TYPE_NAME(), "vtkRectilinearGrid"); + return 1; +} diff --git a/src/DepthVsTime/plugin/DepthVsTimeModule/vtkDepthVsTime.h b/src/DepthVsTime/plugin/DepthVsTimeModule/vtkDepthVsTime.h new file mode 100644 index 0000000..863a4e7 --- /dev/null +++ b/src/DepthVsTime/plugin/DepthVsTimeModule/vtkDepthVsTime.h @@ -0,0 +1,66 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +// Author : Anthony Geay (EDF R&D) + +#ifndef vtkDepthVsTime_h__ +#define vtkDepthVsTime_h__ + +#include + +class vtkMutableDirectedGraph; + +class VTK_EXPORT vtkDepthVsTime : public vtkDataObjectAlgorithm +{ +public: + static vtkDepthVsTime *New(); + vtkTypeMacro(vtkDepthVsTime, vtkDataObjectAlgorithm); + void PrintSelf(ostream &os, vtkIndent indent) override; + + void SetSourceData(vtkDataObject *input); + + void SetSourceConnection(vtkAlgorithmOutput *algOutput); + + vtkSetMacro(NbDiscrPtsAlongZ, int); + vtkGetMacro(NbDiscrPtsAlongZ, int); + + int FillOutputPortInformation(int vtkNotUsed(port), vtkInformation *info) override; + +protected: + vtkDepthVsTime(); + ~vtkDepthVsTime(); + + int RequestInformation(vtkInformation *request, + vtkInformationVector **inputVector, vtkInformationVector *outputVector) override; + int RequestUpdateExtent(vtkInformation *, vtkInformationVector **inputVector, vtkInformationVector *vtkNotUsed(outputVector)) override; + int RequestData(vtkInformation *request, vtkInformationVector **inputVector, + vtkInformationVector *outputVector) override; + + int NbDiscrPtsAlongZ; + int NumberOfTimeSteps; + bool IsExecuting; + int CurrentTimeIndex; + class vtkInternal; + vtkInternal *Internal; + +private: + vtkDepthVsTime(const vtkDepthVsTime &) = delete; + void operator=(const vtkDepthVsTime &) = delete; +}; + +#endif diff --git a/src/DepthVsTime/plugin/filters.xml b/src/DepthVsTime/plugin/filters.xml new file mode 100644 index 0000000..bd3cc0f --- /dev/null +++ b/src/DepthVsTime/plugin/filters.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + This property specifies the input to the Level Scalars filter. + + + + + + + + + + + This property specifies the dataset whose geometry will + be used in determining positions to probe. + + + + + + This property specifies nb of samples equaly spaced expected along Z in depth. + In other words, nb of points along Y-axis expected in output dataset. + + + + + + + + + diff --git a/src/DepthVsTime/plugin/paraview.plugin b/src/DepthVsTime/plugin/paraview.plugin new file mode 100644 index 0000000..7fb7915 --- /dev/null +++ b/src/DepthVsTime/plugin/paraview.plugin @@ -0,0 +1,27 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +NAME + DepthVsTimePlugin +DESCRIPTION + This plugin provides the DepthVsTime filter. +REQUIRES_MODULES + VTK::CommonCore + VTK::IOCore + VTK::FiltersCore diff --git a/src/ElectromagnetismFluxDisc/CMakeLists.txt b/src/ElectromagnetismFluxDisc/CMakeLists.txt new file mode 100644 index 0000000..300abc5 --- /dev/null +++ b/src/ElectromagnetismFluxDisc/CMakeLists.txt @@ -0,0 +1,49 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +cmake_minimum_required(VERSION 3.8) +project(ElectromagnetismFluxDiscPlugin) +find_package(ParaView REQUIRED) + +include(GNUInstallDirs) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}") +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}") +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}") + +set("_paraview_plugin_default_${CMAKE_PROJECT_NAME}" ON) +paraview_plugin_scan( + ENABLE_BY_DEFAULT YES + PLUGIN_FILES "${CMAKE_CURRENT_SOURCE_DIR}/plugin/paraview.plugin" + PROVIDES_PLUGINS plugins + REQUIRES_MODULES required_modules) + +foreach(module IN LISTS required_modules) + if(NOT TARGET "${module}") + message("Missing required module: ${module}") + return() + endif() +endforeach() + +set(BUILD_SHARED_LIBS ON) +paraview_plugin_build( + RUNTIME_DESTINATION "${CMAKE_INSTALL_BINDIR}" + LIBRARY_DESTINATION "${CMAKE_INSTALL_LIBDIR}" + LIBRARY_SUBDIRECTORY "${PARAVIEW_PLUGIN_SUBDIR}" + PLUGINS ${plugins} + AUTOLOAD ${plugins}) diff --git a/src/ElectromagnetismFluxDisc/plugin/CMakeLists.txt b/src/ElectromagnetismFluxDisc/plugin/CMakeLists.txt new file mode 100644 index 0000000..8fd5190 --- /dev/null +++ b/src/ElectromagnetismFluxDisc/plugin/CMakeLists.txt @@ -0,0 +1,33 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +set(BUILD_SHARED_LIBS TRUE) + +paraview_add_plugin(ElectromagnetismFluxDiscPlugin + VERSION "1.0" + MODULES ElectromagnetismFluxDiscModule + MODULE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/ElectromagnetismFluxDiscModule/vtk.module" + SERVER_MANAGER_XML filters.xml + ) + +install(TARGETS ElectromagnetismFluxDiscPlugin + RUNTIME DESTINATION lib/paraview + LIBRARY DESTINATION lib/paraview + ARCHIVE DESTINATION lib/paraview +) diff --git a/src/ElectromagnetismFluxDisc/plugin/ElectromagnetismFluxDiscModule/CMakeLists.txt b/src/ElectromagnetismFluxDisc/plugin/ElectromagnetismFluxDiscModule/CMakeLists.txt new file mode 100644 index 0000000..82eec71 --- /dev/null +++ b/src/ElectromagnetismFluxDisc/plugin/ElectromagnetismFluxDiscModule/CMakeLists.txt @@ -0,0 +1,27 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +set(classes + vtkElectromagnetismFluxDisc +) + +vtk_module_add_module(ElectromagnetismFluxDiscModule + FORCE_STATIC + CLASSES ${classes} +) diff --git a/src/ElectromagnetismFluxDisc/plugin/ElectromagnetismFluxDiscModule/vtk.module b/src/ElectromagnetismFluxDisc/plugin/ElectromagnetismFluxDiscModule/vtk.module new file mode 100644 index 0000000..2835d1b --- /dev/null +++ b/src/ElectromagnetismFluxDisc/plugin/ElectromagnetismFluxDiscModule/vtk.module @@ -0,0 +1,32 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +NAME + ElectromagnetismFluxDiscModule +DEPENDS + VTK::CommonCore + VTK::CommonDataModel + VTK::CommonExecutionModel + VTK::FiltersCore + VTK::FiltersModeling + VTK::FiltersVerdict +PRIVATE_DEPENDS + VTK::CommonMisc + VTK::CommonSystem + VTK::FiltersGeneral diff --git a/src/ElectromagnetismFluxDisc/plugin/ElectromagnetismFluxDiscModule/vtkElectromagnetismFluxDisc.cxx b/src/ElectromagnetismFluxDisc/plugin/ElectromagnetismFluxDiscModule/vtkElectromagnetismFluxDisc.cxx new file mode 100644 index 0000000..288e964 --- /dev/null +++ b/src/ElectromagnetismFluxDisc/plugin/ElectromagnetismFluxDiscModule/vtkElectromagnetismFluxDisc.cxx @@ -0,0 +1,413 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +// Author : Anthony Geay (EDF R&D) + +#include "vtkElectromagnetismFluxDisc.h" + +#include "vtkAdjacentVertexIterator.h" +#include "vtkIntArray.h" +#include "vtkLongArray.h" +#include "vtkCellData.h" +#include "vtkPointData.h" +#include "vtkCylinder.h" +#include "vtkNew.h" +#include "vtkCutter.h" +#include "vtkTransform.h" + +#include "vtkStreamingDemandDrivenPipeline.h" +#include "vtkUnstructuredGrid.h" +#include "vtkMultiBlockDataSet.h" + +#include "vtkInformationStringKey.h" +#include "vtkAlgorithmOutput.h" +#include "vtkObjectFactory.h" +#include "vtkMutableDirectedGraph.h" +#include "vtkMultiBlockDataSet.h" +#include "vtkDataSet.h" +#include "vtkInformationVector.h" +#include "vtkInformation.h" +#include "vtkDataArraySelection.h" +#include "vtkTimeStamp.h" +#include "vtkInEdgeIterator.h" +#include "vtkInformationDataObjectKey.h" +#include "vtkExecutive.h" +#include "vtkVariantArray.h" +#include "vtkStringArray.h" +#include "vtkDoubleArray.h" +#include "vtkFloatArray.h" +#include "vtkCharArray.h" +#include "vtkUnsignedCharArray.h" +#include "vtkDataSetAttributes.h" +#include "vtkDemandDrivenPipeline.h" +#include "vtkDataObjectTreeIterator.h" +#include "vtkWarpScalar.h" +#include "vtkDiskSource.h" +#include "vtkTransform.h" +#include "vtkTransformPolyDataFilter.h" +#include "vtkResampleWithDataSet.h" +#include "vtkPointDataToCellData.h" +#include "vtkMeshQuality.h" + +#include + + +#ifdef WIN32 +#define _USE_MATH_DEFINES +#endif +#include + +#include +#include +#include +#include + +vtkStandardNewMacro(vtkElectromagnetismFluxDisc); + +class VTK_EXPORT MZCException : public std::exception +{ +public: + MZCException(const std::string& s):_reason(s) { } + virtual const char *what() const throw() { return _reason.c_str(); } + virtual ~MZCException() throw() { } +private: + std::string _reason; +}; + + +void ExtractInfo(vtkInformationVector *inputVector, vtkDataSet *& usgIn) +{ + vtkInformation *inputInfo(inputVector->GetInformationObject(0)); + vtkDataSet *input(0); + vtkDataSet *input0(vtkDataSet::SafeDownCast(inputInfo->Get(vtkDataObject::DATA_OBJECT()))); + vtkMultiBlockDataSet *input1(vtkMultiBlockDataSet::SafeDownCast(inputInfo->Get(vtkDataObject::DATA_OBJECT()))); + if(input0) + input=input0; + else + { + if(!input1) + throw MZCException("Input dataSet must be a DataSet or single elt multi block dataset expected !"); + if(input1->GetNumberOfBlocks()!=1) + { + std::cerr << "**** " << input1->GetNumberOfBlocks() << std::endl; + throw MZCException("Input dataSet is a multiblock dataset with not exactly one block ! Use MergeBlocks or ExtractBlocks filter before calling this filter !"); + } + vtkDataObject *input2(input1->GetBlock(0)); + if(!input2) + throw MZCException("Input dataSet is a multiblock dataset with exactly one block but this single element is NULL !"); + vtkDataSet *input2c(vtkDataSet::SafeDownCast(input2)); + if(!input2c) + throw MZCException("Input dataSet is a multiblock dataset with exactly one block but this single element is not a dataset ! Use MergeBlocks or ExtractBlocks filter before calling this filter !"); + input=input2c; + } + if(!input) + throw MZCException("Input data set is NULL !"); + vtkPointData *att(input->GetPointData()); + if(!att) + throw MZCException("Input dataset has no point data attribute ! Impossible to deduce a developed surface on it !"); + usgIn=input; +} + +class vtkElectromagnetismFluxDisc::vtkInternals +{ +public: + vtkNew Cutter; +}; + +//////////////////// + +vtkElectromagnetismFluxDisc::vtkElectromagnetismFluxDisc():_cyl(nullptr),Internal(new vtkInternals),RadialResolution(80),CircumferentialResolution(80) +{ +} + +vtkElectromagnetismFluxDisc::~vtkElectromagnetismFluxDisc() +{ + delete this->Internal; +} + +int vtkElectromagnetismFluxDisc::RequestInformation(vtkInformation *request, vtkInformationVector **inputVector, vtkInformationVector *outputVector) +{ + //std::cerr << "########################################## vtkElectromagnetismFluxDisc::RequestInformation ##########################################" << std::endl; + try + { + vtkDataSet *usgIn(0); + ExtractInfo(inputVector[0],usgIn); + } + catch(MZCException& e) + { + vtkErrorMacro("Exception has been thrown in vtkElectromagnetismFluxDisc::RequestInformation : " << e.what()); + return 0; + } + return 1; +} + +double ComputeFlux(vtkIdType nbOfTuples, const double *area, const double *vector3Field, const double axis[3]) +{ + double ret(0.0); + for( vtkIdType i = 0 ; i < nbOfTuples ; ++i ) + ret += area[i] * vtkMath::Dot(vector3Field+i*3,axis); + return ret; +} + +template +void Rotate3DAlg(const double *center, const double *vect, double angle, vtkIdType nbNodes, const T *coordsIn, T *coordsOut) +{ + double sina(sin(angle)); + double cosa(cos(angle)); + double vectorNorm[3]; + T matrix[9]; + T matrixTmp[9]; + double norm(sqrt(vect[0]*vect[0]+vect[1]*vect[1]+vect[2]*vect[2])); + if(norm::min()) + throw MZCException("Rotate3DAlg : magnitude of input vector is too close of 0. !"); + std::transform(vect,vect+3,vectorNorm,std::bind2nd(std::multiplies(),1/norm)); + //rotation matrix computation + matrix[0]=cosa; matrix[1]=0.; matrix[2]=0.; matrix[3]=0.; matrix[4]=cosa; matrix[5]=0.; matrix[6]=0.; matrix[7]=0.; matrix[8]=cosa; + matrixTmp[0]=vectorNorm[0]*vectorNorm[0]; matrixTmp[1]=vectorNorm[0]*vectorNorm[1]; matrixTmp[2]=vectorNorm[0]*vectorNorm[2]; + matrixTmp[3]=vectorNorm[1]*vectorNorm[0]; matrixTmp[4]=vectorNorm[1]*vectorNorm[1]; matrixTmp[5]=vectorNorm[1]*vectorNorm[2]; + matrixTmp[6]=vectorNorm[2]*vectorNorm[0]; matrixTmp[7]=vectorNorm[2]*vectorNorm[1]; matrixTmp[8]=vectorNorm[2]*vectorNorm[2]; + std::transform(matrixTmp,matrixTmp+9,matrixTmp,std::bind2nd(std::multiplies(),1-cosa)); + std::transform(matrix,matrix+9,matrixTmp,matrix,std::plus()); + matrixTmp[0]=0.; matrixTmp[1]=-vectorNorm[2]; matrixTmp[2]=vectorNorm[1]; + matrixTmp[3]=vectorNorm[2]; matrixTmp[4]=0.; matrixTmp[5]=-vectorNorm[0]; + matrixTmp[6]=-vectorNorm[1]; matrixTmp[7]=vectorNorm[0]; matrixTmp[8]=0.; + std::transform(matrixTmp,matrixTmp+9,matrixTmp,std::bind2nd(std::multiplies(),sina)); + std::transform(matrix,matrix+9,matrixTmp,matrix,std::plus()); + //rotation matrix computed. + T tmp[3]; + for(vtkIdType i=0; i()); + coordsOut[i*3]=matrix[0]*tmp[0]+matrix[1]*tmp[1]+matrix[2]*tmp[2]+(T)center[0]; + coordsOut[i*3+1]=matrix[3]*tmp[0]+matrix[4]*tmp[1]+matrix[5]*tmp[2]+(T)center[1]; + coordsOut[i*3+2]=matrix[6]*tmp[0]+matrix[7]*tmp[1]+matrix[8]*tmp[2]+(T)center[2]; + } +} + +int vtkElectromagnetismFluxDisc::RequestData(vtkInformation *request, vtkInformationVector **inputVector, vtkInformationVector *outputVector) +{ + //std::cerr << "########################################## vtkElectromagnetismFluxDisc::RequestData ##########################################" << std::endl; + try + { + if(!_cyl) + throw MZCException("No cylinder object as cut function !"); + double center[3],axis[3],radius,orthoAxis[3]; + const double ZVec[3] = {0.,0.,1.}; + vtkAbstractTransform* trf(_cyl->GetTransform()); + { + _cyl->GetCenter(center); + _cyl->GetAxis(axis[0],axis[1],axis[2]); + radius=_cyl->GetRadius(); + } + if(trf) + { + double axis3[3]={center[0]+0.,center[1]+1.,center[2]+0.},axis4[3]; + trf->TransformPoint(axis3,axis4); + std::transform(axis4,axis4+3,center,axis,[](double a, double b) { return b-a; }); + axis[1]=-axis[1]; + if(std::isnan(axis[0]) && std::isnan(axis[1]) && std::isnan(axis[2])) + { axis[0]=0.; axis[1]=-1.; axis[2]=0.; } + } + + //std::cerr << trf << " jjj " << axis[0] << " " << axis[1] << " " << axis[2] << " : " << center[0] << " " << center[1] << " " << center[2] << " " " " << " -> " << radius << std::endl; + vtkDataSet *usgIn(0); + ExtractInfo(inputVector[0],usgIn); + // + vtkNew outputMesh; + { + vtkIdType nbPoints(this->RadialResolution*this->CircumferentialResolution+1); + vtkNew coords; + coords->SetNumberOfComponents(3); coords->SetNumberOfTuples(nbPoints); + double *coordsPtr(coords->GetPointer(0)); + coordsPtr[0] = 0; coordsPtr[1] = 0; coordsPtr[2] = 0; coordsPtr+=3; + for(int circI = 0 ; circI < this->CircumferentialResolution ; ++circI) + { + double a(2*M_PI*(double(circI)/double(this->CircumferentialResolution))); + double c(cos(a)),s(sin(a)); + for(int radI = 0 ; radI < this->RadialResolution ; ++radI) + { + coordsPtr[0] = c*(double(radI+1)/double(this->RadialResolution))*radius; + coordsPtr[1] = s*(double(radI+1)/double(this->RadialResolution))*radius; + coordsPtr[2] = 0; + coordsPtr+=3; + } + } + vtkNew pts; + pts->SetData(coords); + outputMesh->SetPoints(pts); + // + vtkIdType nbOfCells(this->CircumferentialResolution*this->RadialResolution); + vtkNew cells; + vtkNew cellsData; + vtkNew cellLocations; + vtkNew cellTypes; + // + cellTypes->SetNumberOfComponents(1); cellTypes->SetNumberOfTuples(nbOfCells); + cellLocations->SetNumberOfComponents(1); cellLocations->SetNumberOfTuples(nbOfCells); + cellsData->SetNumberOfComponents(1); cellsData->SetNumberOfTuples( ( 5*(this->RadialResolution)-1 ) * this->CircumferentialResolution ); + vtkIdType *clPtr(cellLocations->GetPointer(0)),*cdPtr(cellsData->GetPointer(0)); + vtkIdType offset(0),deltaPt(this->RadialResolution); + unsigned char *ctPtr(cellTypes->GetPointer(0)); + for( int iCirc = 0 ; iCirc < this->CircumferentialResolution - 1; ++iCirc ) + { + vtkIdType zeDelta(iCirc*deltaPt); + for( int iRadial = 0 ; iRadial < this->RadialResolution ; ++iRadial ) + { + *clPtr++ = offset; + if(iRadial!=0) + { + *ctPtr++ = VTK_QUAD; + cdPtr[0] = 4 ; cdPtr[1] = zeDelta + iRadial ; cdPtr[2] = zeDelta + iRadial+1; + cdPtr[3] = (zeDelta + deltaPt + iRadial+1); cdPtr[4] = ( zeDelta + deltaPt + iRadial); + cdPtr+=5; + offset += 4; + } + else + { + *ctPtr++ = VTK_TRIANGLE; + cdPtr[0] = 3 ; cdPtr[1] = 0 ; cdPtr[2] = zeDelta + 1; cdPtr[3] = (zeDelta + deltaPt +1); + cdPtr += 4; + offset += 3; + } + } + } + vtkIdType zeDelta((this->CircumferentialResolution - 1)*deltaPt); + for( int iRadial = 0 ; iRadial < this->RadialResolution ; ++iRadial ) + { + *clPtr++ = offset; + if(iRadial!=0) + { + *ctPtr++ = VTK_QUAD; + cdPtr[0] = 4 ; cdPtr[1] = zeDelta + iRadial ; cdPtr[2] = zeDelta + iRadial+1; + cdPtr[3] = iRadial+1; cdPtr[4] = iRadial; + cdPtr+=5; + offset += 4; + } + else + { + *ctPtr++ = VTK_TRIANGLE; + cdPtr[0] = 3 ; cdPtr[1] = 0 ; cdPtr[2] = zeDelta + 1; cdPtr[3] = 1; + cdPtr += 4; + offset += 3; + } + } + // + cells->SetCells(nbOfCells,cellsData); + outputMesh->SetCells(cellTypes,cellLocations,cells); + } + // Rotation + { + vtkIdType nbPoints(outputMesh->GetNumberOfPoints()); + vtkMath::Cross(ZVec,axis,orthoAxis); + double normOrthoAxis( vtkMath::Norm(orthoAxis) ); + if(normOrthoAxis > 1e-5) + { + //std::cerr << "ortho : " << normOrthoAxis << " X = " << orthoAxis[0] << " Y = " << orthoAxis[1] << " Z = " << orthoAxis[2] << std::endl; + orthoAxis[0] *= normOrthoAxis; orthoAxis[1] *= normOrthoAxis; orthoAxis[2] *= normOrthoAxis; + const double Center[3] = {0.,0.,0.}; + vtkNew newArray; + newArray->SetNumberOfComponents(3); newArray->SetNumberOfTuples(nbPoints); + vtkDoubleArray *oldPts( vtkDoubleArray::SafeDownCast( outputMesh->GetPoints()->GetData() ) ); + double angle(asin(normOrthoAxis)); + if( vtkMath::Dot(ZVec,axis) < 0. ) + angle = M_PI - angle; + Rotate3DAlg(Center,orthoAxis,angle,nbPoints,oldPts->GetPointer(0),newArray->GetPointer(0)); + outputMesh->GetPoints()->SetData(newArray); + } + } + // Translation + { + vtkDoubleArray *coords(vtkDoubleArray::SafeDownCast( outputMesh->GetPoints()->GetData())); + vtkIdType nbPts(coords->GetNumberOfTuples()); + double *coordsPtr(coords->GetPointer(0)); + for(vtkIdType i = 0 ; i < nbPts ; ++i) + { coordsPtr[3*i] += center[0]; coordsPtr[3*i+1] += center[1]; coordsPtr[3*i+2] += center[2]; } + } + // + vtkNew probeFilter; + probeFilter->SetInputData(outputMesh); + probeFilter->SetSourceData(usgIn); + // + vtkNew pd2cd; + pd2cd->SetInputConnection(probeFilter->GetOutputPort()); + pd2cd->Update(); + // + vtkNew mq; + mq->SetInputData(outputMesh); + mq->SetTriangleQualityMeasureToArea(); + mq->SetQuadQualityMeasureToArea(); + mq->Update(); + double *area( ( vtkDoubleArray::SafeDownCast(mq->GetOutput()->GetCellData()->GetArray("Quality")) )->GetPointer(0) ); + // + vtkInformation *outInfo(outputVector->GetInformationObject(0)); + vtkUnstructuredGrid *output(vtkUnstructuredGrid::SafeDownCast(outInfo->Get(vtkDataObject::DATA_OBJECT()))); + output->ShallowCopy(outputMesh); + // + vtkIdType nbOfCells(output->GetNumberOfCells()); + vtkFieldData* dsa(pd2cd->GetOutput()->GetCellData()); + int nbOfArrays(dsa->GetNumberOfArrays()); + for(int i = 0 ; i < nbOfArrays ; ++i ) + { + vtkDoubleArray *arr( vtkDoubleArray::SafeDownCast(dsa->GetArray(i)) ); + if( arr && arr->GetNumberOfComponents() == 3 ) + { + vtkNew arr2; + arr2->ShallowCopy(arr); + output->GetCellData()->AddArray(arr2); + double flux(ComputeFlux(nbOfCells,area,arr->GetPointer(0),axis)); + std::ostringstream oss; oss << dsa->GetArrayName(i) << "_flux"; + vtkNew arrFlux; + arrFlux->SetName(oss.str().c_str()); + arrFlux->SetNumberOfComponents(1); arrFlux->SetNumberOfTuples(nbOfCells); + std::for_each(arrFlux->GetPointer(0),arrFlux->GetPointer(nbOfCells),[flux](double& elt) { elt = flux; }); + output->GetCellData()->AddArray(arrFlux); + } + } + } + catch(MZCException& e) + { + vtkErrorMacro("Exception has been thrown in vtkElectromagnetismFluxDisc::RequestInformation : " << e.what()); + return 0; + } + return 1; +} + +void vtkElectromagnetismFluxDisc::PrintSelf(ostream& os, vtkIndent indent) +{ + this->Superclass::PrintSelf(os, indent); +} + +void vtkElectromagnetismFluxDisc::SetCutFunction(vtkImplicitFunction* func) +{ + vtkCylinder *cyl(vtkCylinder::SafeDownCast(func)); + if(cyl) + { + _cyl=cyl; + this->Modified(); + } +} + +vtkMTimeType vtkElectromagnetismFluxDisc::GetMTime() +{ + vtkMTimeType maxMTime = this->Superclass::GetMTime(); // My MTime + if(_cyl) + { + maxMTime=std::max(maxMTime,_cyl->GetMTime()); + } + return maxMTime; +} diff --git a/src/ElectromagnetismFluxDisc/plugin/ElectromagnetismFluxDiscModule/vtkElectromagnetismFluxDisc.h b/src/ElectromagnetismFluxDisc/plugin/ElectromagnetismFluxDiscModule/vtkElectromagnetismFluxDisc.h new file mode 100644 index 0000000..ffad69c --- /dev/null +++ b/src/ElectromagnetismFluxDisc/plugin/ElectromagnetismFluxDiscModule/vtkElectromagnetismFluxDisc.h @@ -0,0 +1,65 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +// Author : Anthony Geay (EDF R&D) + +#pragma once + +#include +//#include + +class vtkMutableDirectedGraph; +class vtkImplicitFunction; +class vtkCylinder; + +class VTK_EXPORT vtkElectromagnetismFluxDisc : public vtkUnstructuredGridAlgorithm +{ +public: + static vtkElectromagnetismFluxDisc* New(); + vtkTypeMacro(vtkElectromagnetismFluxDisc, vtkUnstructuredGridAlgorithm); + void PrintSelf(ostream& os, vtkIndent indent) override; + + void SetCutFunction(vtkImplicitFunction* func); + + vtkMTimeType GetMTime(); + + vtkSetClampMacro(RadialResolution, int, 1, VTK_INT_MAX); + vtkGetMacro(RadialResolution, int); + + vtkSetClampMacro(CircumferentialResolution, int, 3, VTK_INT_MAX); + vtkGetMacro(CircumferentialResolution, int); + +protected: + vtkElectromagnetismFluxDisc(); + ~vtkElectromagnetismFluxDisc(); + + + int RequestInformation(vtkInformation*, vtkInformationVector**, vtkInformationVector*) override; + int RequestData(vtkInformation*, vtkInformationVector**, vtkInformationVector*) override; + + vtkCylinder* _cyl; + class vtkInternals; + vtkInternals* Internal; + +private: + vtkElectromagnetismFluxDisc(const vtkElectromagnetismFluxDisc&) = delete; + void operator=(const vtkElectromagnetismFluxDisc&) = delete; + + int RadialResolution; + int CircumferentialResolution; +}; diff --git a/src/ElectromagnetismFluxDisc/plugin/Test/CMakeLists.txt b/src/ElectromagnetismFluxDisc/plugin/Test/CMakeLists.txt new file mode 100644 index 0000000..cd37495 --- /dev/null +++ b/src/ElectromagnetismFluxDisc/plugin/Test/CMakeLists.txt @@ -0,0 +1,33 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +SET(TEMP_DIR "${CMAKE_CURRENT_BINARY_DIR}/Testing/Temporary") + +IF(NOT EXISTS ${TEMP_DIR}) + FILE(MAKE_DIRECTORY ${TEMP_DIR}) +ENDIF(NOT EXISTS ${TEMP_DIR}) + +#SET(DEV_SURFACE_TESTS test_dev_surface2 test_dev_surface3) + +#IF(NOT SALOME_PARAVIS_NO_VISU_TESTS) +# FOREACH(tfile ${DEV_SURFACE_TESTS}) +# ADD_TEST(${tfile} ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/${tfile}.py ) +# SET_TESTS_PROPERTIES(${tfile} PROPERTIES LABELS "PVS_ADD_ONS") +# ENDFOREACH(tfile ${DEV_SURFACE_TESTS}) +#ENDIF() diff --git a/src/ElectromagnetismFluxDisc/plugin/Test/test_flux_disc0.py b/src/ElectromagnetismFluxDisc/plugin/Test/test_flux_disc0.py new file mode 100644 index 0000000..da991ae --- /dev/null +++ b/src/ElectromagnetismFluxDisc/plugin/Test/test_flux_disc0.py @@ -0,0 +1,164 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +# -*- coding: utf-8 -*- + +from paraview.simple import * +from vtk.util import numpy_support +import numpy as np +import vtk + +from medcoupling import * + +def MyAssert(clue): + if not clue: + raise RuntimeError("Assertion failed !") + +def Write(ds,fn): + writer = vtk.vtkUnstructuredGridWriter() + writer.SetInputData(ds) + writer.SetFileName(fn) + writer.Update() + +def MCField(fn,fieldName): + mm = MEDFileMesh.New(fn) + fs = MEDFileFields.New(fn) + return fs[fieldName][0].field(mm) + +def ComputeFlux(mc_f): + area = mc_f.getMesh().getMeasureField(True).getArray() + mc_vector = DataArrayDouble(vector,1,3) + mc_vector /= mc_vector.magnitude()[0] + mc_vector = DataArrayDouble.Aggregate(mc_f.getNumberOfTuples()*[mc_vector]) + flux_per_cell = DataArrayDouble.Dot(mc_f.getArray(),mc_vector)*area + return flux_per_cell.accumulate()[0] + +ref_flux_b_a = -60.47 +ref_flux_h_a = -127446 +ref_flux_hsomega = 1896.991081794051 +vector = [0.13,-0.47,0.87] +center = [0.0, 0.0, 3900.0] + +mr = MEDReader(FileName='/home/H87074/TMP81/paravistests_new/FilesForTests/Electromagnetism/mesh_benjamin_8_sept_2020.med') +mr.AllArrays = ['TS0/Mesh_1/ComSup0/B_A@@][@@P0', 'TS0/Mesh_1/ComSup0/B_HsOmega@@][@@P0', 'TS0/Mesh_1/ComSup0/H_A@@][@@P0', 'TS0/Mesh_1/ComSup0/H_HsOmega@@][@@P0'] +mr.AllTimeSteps = ['0000', '0001', '0002', '0003', '0004', '0005', '0006', '0007', '0008', '0009'] +mr.UpdatePipeline() + +resRadial = 200 +resCircum = 200 +fluxDisc1 = FluxDisc(Input=mr) +fluxDisc1.SliceType = 'Cylinder' +fluxDisc1.SliceType.Center = center +fluxDisc1.SliceType.Axis = vector +fluxDisc1.SliceType.Radius = 293 +fluxDisc1.RadialResolution = resRadial +fluxDisc1.CircumferentialResolution = resCircum + +mw = MEDWriter(FileName="Base.med",Input=fluxDisc1) +mw.UpdatePipeline() + +ds0 = servermanager.Fetch(fluxDisc1) +MyAssert(ds0.GetNumberOfCells()==resRadial*resCircum) +MyAssert(ds0.GetNumberOfPoints()==resRadial*resCircum+1) + +flux_b_a = ds0.GetBlock(0).GetCellData().GetArray("B_A_flux").GetValue(0) +MyAssert(np.isclose(ref_flux_b_a,flux_b_a,0.1,0)) + +flux_h_a = ds0.GetBlock(0).GetCellData().GetArray("H_A_flux").GetValue(0) +#MyAssert(np.isclose(ref_flux_h_a,flux_h_a,0.01,0)) + +flux_hsomega = ds0.GetBlock(0).GetCellData().GetArray("H_HsOmega_flux").GetValue(0) +#MyAssert(np.isclose(ref_flux_hsomega,flux_hsomega,0.01,0)) + +Write(ds0.GetBlock(0),"Base.vtu") +mc_f_base = MCField("Base.med","H_HsOmega") + +# On inverse l'axe et on verifie que le flux est inversé + +fluxDisc1.SliceType.Axis = [-elt for elt in vector] +mw = MEDWriter(FileName="Invert.med",Input=fluxDisc1) +mw.UpdatePipeline() + +ds1 = servermanager.Fetch(fluxDisc1) + +MyAssert(ds1.GetNumberOfCells()==resRadial*resCircum) +MyAssert(ds1.GetNumberOfPoints()==resRadial*resCircum+1) + +flux_b_a = ds1.GetBlock(0).GetCellData().GetArray("B_A_flux").GetValue(0) +#MyAssert(np.isclose(-ref_flux_b_a,flux_b_a,0.1,0)) + +flux_h_a = ds1.GetBlock(0).GetCellData().GetArray("H_A_flux").GetValue(0) +#MyAssert(np.isclose(-ref_flux_h_a,flux_h_a,0.1,0)) + +flux_hsomega = ds1.GetBlock(0).GetCellData().GetArray("H_HsOmega_flux").GetValue(0) +#MyAssert(np.isclose(-ref_flux_hsomega,flux_hsomega,0.1,0)) + + +Write(ds0.GetBlock(0),"Invert.vtu") +mc_f_invert = MCField("Invert.med","H_HsOmega") + +### + +ComputeFlux(mc_f_base) +ComputeFlux(mc_f_invert) + +# debug : pourquoi une telle difference de flux sur H_HsOmega ? + +a = mc_f_base.getMesh().getMeasureField(True).getArray() +b = mc_f_invert.getMesh().getMeasureField(True).getArray() +assert(a.isEqual(b,1e-10)) + +# OK pour l'area + +m_base = mc_f_base.getMesh() +m_invert = mc_f_invert.getMesh() +base_base = DataArrayDouble.GiveBaseForPlane(vector) + +newX = DataArrayDouble(resRadial*resCircum+1,3) ; newX[:] = base_base[0] +newY = DataArrayDouble(resRadial*resCircum+1,3) ; newY[:] = base_base[1] +newZ = DataArrayDouble(resRadial*resCircum+1,3) ; newZ[:] = 0 + +base_new_coords=DataArrayDouble.Meld([DataArrayDouble.Dot(m_base.getCoords(),newX),DataArrayDouble.Dot(m_base.getCoords(),newY),newZ]) +invert_new_coords=DataArrayDouble.Meld([DataArrayDouble.Dot(m_invert.getCoords(),newX),DataArrayDouble.Dot(m_invert.getCoords(),newY),newZ]) + +m_base.setCoords(base_new_coords) +m_invert.setCoords(invert_new_coords) + +mc_f_base.write("base_dbg.vtu") +mc_f_invert.write("invert_dbg.vtu") + +m_base.changeSpaceDimension(2,0.) +m_invert.changeSpaceDimension(2,0.) +a,b= m_base.getCellsContainingPoints(m_invert.computeCellCenterOfMass(),1e-12) +assert(b.isIota(len(b))) + +a2 = a[:] ; a2.sort() ; assert( a2.isIota(len(a2) ) ) + +array_base_in_invert_ref = mc_f_base.getArray()[a] + +not_of_ids = (array_base_in_invert_ref-mc_f_invert.getArray()).magnitude().findIdsGreaterThan(1.) +ok_ids = (array_base_in_invert_ref-mc_f_invert.getArray()).magnitude().findIdsLowerThan(1.) + +ze_ids = ok_ids +ze_ids = not_of_ids +print(ComputeFlux(mc_f_invert[ze_ids])) +ze_ids_base_ref = a.findIdForEach(ze_ids) +print(ComputeFlux(mc_f_base[ze_ids_base_ref])) + +mc_f_invert[ze_ids].write("invert_dbg_pb.vtu") diff --git a/src/ElectromagnetismFluxDisc/plugin/filters.xml b/src/ElectromagnetismFluxDisc/plugin/filters.xml new file mode 100644 index 0000000..f9dffe5 --- /dev/null +++ b/src/ElectromagnetismFluxDisc/plugin/filters.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + This property specifies the input to the Level Scalars filter. + + + + + + + + + + + This property sets the parameters of cylinder used for slice. + + + + + + Set the number of points in radial direction. + + + + Set the number of points in circumferential direction. + + + + + + + + diff --git a/src/ElectromagnetismFluxDisc/plugin/paraview.plugin b/src/ElectromagnetismFluxDisc/plugin/paraview.plugin new file mode 100644 index 0000000..eb9343d --- /dev/null +++ b/src/ElectromagnetismFluxDisc/plugin/paraview.plugin @@ -0,0 +1,27 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +NAME + ElectromagnetismFluxDiscPlugin +DESCRIPTION + This plugin provides the ElectromagnetismFluxDisc filter. +REQUIRES_MODULES + VTK::CommonCore + VTK::IOCore + VTK::FiltersCore diff --git a/src/ElectromagnetismRotation/CMakeLists.txt b/src/ElectromagnetismRotation/CMakeLists.txt new file mode 100644 index 0000000..195e2ad --- /dev/null +++ b/src/ElectromagnetismRotation/CMakeLists.txt @@ -0,0 +1,51 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +cmake_minimum_required(VERSION 3.8) +project(ElectromagnetismRotation) + +find_package(ParaView REQUIRED) + +option(BUILD_SHARED_LIBS "Build shared libraries" ON) +enable_testing() + +include(GNUInstallDirs) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}") +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}") +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}") + +set("_paraview_plugin_default_${CMAKE_PROJECT_NAME}" ON) +paraview_plugin_scan( + ENABLE_BY_DEFAULT YES + PLUGIN_FILES "${CMAKE_CURRENT_SOURCE_DIR}/plugin/paraview.plugin" + PROVIDES_PLUGINS plugins + REQUIRES_MODULES required_modules) + +foreach (module IN LISTS required_modules) + if (NOT TARGET "${module}") + message("Missing required module: ${module}") + return () + endif () +endforeach () + +paraview_plugin_build( + RUNTIME_DESTINATION "${CMAKE_INSTALL_BINDIR}" + LIBRARY_DESTINATION "${CMAKE_INSTALL_LIBDIR}" + LIBRARY_SUBDIRECTORY "${PARAVIEW_PLUGIN_SUBDIR}" + PLUGINS ${plugins}) diff --git a/src/ElectromagnetismRotation/plugin/CMakeLists.txt b/src/ElectromagnetismRotation/plugin/CMakeLists.txt new file mode 100644 index 0000000..442308a --- /dev/null +++ b/src/ElectromagnetismRotation/plugin/CMakeLists.txt @@ -0,0 +1,56 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +# Common CMake macros +# =================== +set(TMP_CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH}) +unset(CMAKE_MODULE_PATH) +set(CONFIGURATION_ROOT_DIR $ENV{CONFIGURATION_ROOT_DIR} CACHE PATH "Path to the Salome CMake configuration files") +if(EXISTS ${CONFIGURATION_ROOT_DIR}) + list(APPEND CMAKE_MODULE_PATH "${CONFIGURATION_ROOT_DIR}/cmake") + include(SalomeMacros) +else() + message(FATAL_ERROR "We absolutely need the Salome CMake configuration files, please define CONFIGURATION_ROOT_DIR !") +endif() + +set(MEDCOUPLING_ROOT_DIR $ENV{MEDCOUPLING_ROOT_DIR} CACHE PATH "Path to the MEDCoupling tool") +if(EXISTS ${MEDCOUPLING_ROOT_DIR}) + list(APPEND CMAKE_MODULE_PATH "${MEDCOUPLING_ROOT_DIR}/cmake_files") +endif() +list(APPEND CMAKE_MODULE_PATH "${CMAKE_ROOT}/Modules") +list(APPEND CMAKE_MODULE_PATH ${TMP_CMAKE_MODULE_PATH}) + +include(SalomeSetupPlatform) +set(BUILD_SHARED_LIBS TRUE) + +find_package(SalomePythonInterp REQUIRED) +find_package(SalomePythonLibs REQUIRED) +find_package(SalomeHDF5 REQUIRED) +find_package(SalomeMEDCoupling REQUIRED) +find_package(SalomeMEDFile REQUIRED) + +SALOME_ACCUMULATE_ENVIRONMENT(PYTHONPATH NOCHECK ${CMAKE_INSTALL_PREFIX}/${SALOME_INSTALL_BINS} + ${CMAKE_INSTALL_PREFIX}/${SALOME_INSTALL_PYTHON}) +SALOME_ACCUMULATE_ENVIRONMENT(LD_LIBRARY_PATH NOCHECK ${CMAKE_INSTALL_PREFIX}/${SALOME_INSTALL_LIBS}) +SALOME_ACCUMULATE_ENVIRONMENT(PV_PLUGIN_PATH NOCHECK ${CMAKE_INSTALL_PREFIX}/lib/paraview) + +add_subdirectory(ElectromagnetismRotationHelper) +add_subdirectory(ParaViewPlugin) + + diff --git a/src/ElectromagnetismRotation/plugin/ElectromagnetismRotationHelper/CMakeLists.txt b/src/ElectromagnetismRotation/plugin/ElectromagnetismRotationHelper/CMakeLists.txt new file mode 100644 index 0000000..f7aed0a --- /dev/null +++ b/src/ElectromagnetismRotation/plugin/ElectromagnetismRotationHelper/CMakeLists.txt @@ -0,0 +1,27 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +add_library(ElectromagnetismRotationHelper SHARED ElectromagnetismRotationHelper.cxx) +target_include_directories(ElectromagnetismRotationHelper PRIVATE . ${MEDCOUPLING_INCLUDE_DIRS}) + +target_link_libraries(ElectromagnetismRotationHelper VTK::CommonCore VTK::CommonDataModel VTK::IOXML ${MEDFILE_C_LIBRARIES}) + +TARGET_LINK_LIBRARIES(ElectromagnetismRotationHelper ${MEDCoupling_medloader}) + +install(TARGETS ElectromagnetismRotationHelper DESTINATION lib/paraview) diff --git a/src/ElectromagnetismRotation/plugin/ElectromagnetismRotationHelper/ElectromagnetismRotationHelper.cxx b/src/ElectromagnetismRotation/plugin/ElectromagnetismRotationHelper/ElectromagnetismRotationHelper.cxx new file mode 100644 index 0000000..b7c3032 --- /dev/null +++ b/src/ElectromagnetismRotation/plugin/ElectromagnetismRotationHelper/ElectromagnetismRotationHelper.cxx @@ -0,0 +1,375 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +// Author : Anthony Geay (EDF R&D) + +#include "ElectromagnetismRotationHelper.h" + +#include "InterpKernelException.hxx" + +#include "vtkInformation.h" +#include "vtkInformationDataObjectMetaDataKey.h" +#include "vtkAdjacentVertexIterator.h" +#include "vtkMutableDirectedGraph.h" +#include "vtkDataSetAttributes.h" +#include "vtkStringArray.h" + +#include +#include +#include + +const char ZE_SEPP[]="@@][@@"; + +const char ElectromagnetismRotationGrp::START[]="GRP_"; + +const char ElectromagnetismRotationFam::START[]="FAM_"; + +ElectromagnetismRotationStatus::ElectromagnetismRotationStatus(const char *name):_status(false),_name(name) +{ +} + +void ElectromagnetismRotationStatus::printMySelf(std::ostream& os) const +{ + os << " -" << _ze_key_name << "("; + if(_status) + os << "X"; + else + os << " "; + os << ")" << std::endl; +} + +bool ElectromagnetismRotationStatus::isSameAs(const ElectromagnetismRotationStatus& other) const +{ + return _name==other._name && _ze_key_name==other._ze_key_name; +} + +bool ElectromagnetismRotationGrp::isSameAs(const ElectromagnetismRotationGrp& other) const +{ + bool ret(ElectromagnetismRotationStatus::isSameAs(other)); + if(ret) + return _fams==other._fams; + else + return false; +} + +ElectromagnetismRotationFam::ElectromagnetismRotationFam(const char *name):ElectromagnetismRotationStatus(name),_id(0) +{ + std::size_t pos(_name.find(ZE_SEPP)); + std::string name0(_name.substr(0,pos)),name1(_name.substr(pos+strlen(ZE_SEPP))); + std::istringstream iss(name1); + iss >> _id; + std::ostringstream oss; oss << START << name; _ze_key_name=oss.str(); _name=name0; +} + +bool ElectromagnetismRotationFam::isSameAs(const ElectromagnetismRotationFam& other) const +{ + bool ret(ElectromagnetismRotationStatus::isSameAs(other)); + if(ret) + return _id==other._id; + else + return false; +} + +void ElectromagnetismRotationFam::printMySelf(std::ostream& os) const +{ + os << " -" << _ze_key_name << " famName : \"" << _name << "\" id : " << _id << " ("; + if(_status) + os << "X"; + else + os << " "; + os << ")" << std::endl; +} + +void ElectromagnetismRotationFam::fillIdsToKeep(std::set& s) const +{ + s.insert(_id); +} +/////////////////// + +bool ElectromagnetismRotationInternal::IndependantIsInformationOK(vtkInformationDataObjectMetaDataKey *medReaderMetaData, vtkInformation *info) +{ + // Check the information contain meta data key + if(!info->Has(medReaderMetaData)) + return false; + + // Recover Meta Data + vtkMutableDirectedGraph *sil(vtkMutableDirectedGraph::SafeDownCast(info->Get(medReaderMetaData))); + if(!sil) + return false; + int idNames(0); + vtkAbstractArray *verticesNames(sil->GetVertexData()->GetAbstractArray("Names",idNames)); + vtkStringArray *verticesNames2(vtkStringArray::SafeDownCast(verticesNames)); + if(!verticesNames2) + return false; + for(int i=0;iGetNumberOfValues();i++) + { + vtkStdString &st(verticesNames2->GetValue(i)); + if(st=="MeshesFamsGrps") + return true; + } + return false; +} + +const char *ElectromagnetismRotationInternal::getMeshName() const +{ + return this->_mesh_name.c_str(); +} + +void ElectromagnetismRotationInternal::loadFrom(vtkMutableDirectedGraph *sil) +{ + std::vector oldGrps(_groups); _groups.clear(); + std::vector oldFams(_fams); _fams.clear(); + int idNames(0); + vtkAbstractArray *verticesNames(sil->GetVertexData()->GetAbstractArray("Names",idNames)); + vtkStringArray *verticesNames2(vtkStringArray::SafeDownCast(verticesNames)); + vtkIdType id0; + bool found(false); + for(int i=0;iGetNumberOfValues();i++) + { + vtkStdString &st(verticesNames2->GetValue(i)); + if(st=="MeshesFamsGrps") + { + id0=i; + found=true; + } + } + if(!found) + throw INTERP_KERNEL::Exception("There is an internal error ! The tree on server side has not the expected look !"); + vtkAdjacentVertexIterator *it0(vtkAdjacentVertexIterator::New()); + sil->GetAdjacentVertices(id0,it0); + int kk(0),ll(0); + while(it0->HasNext()) + { + vtkIdType id1(it0->Next()); + std::string meshName((const char *)verticesNames2->GetValue(id1)); + this->_mesh_name=meshName; + vtkAdjacentVertexIterator *it1(vtkAdjacentVertexIterator::New()); + sil->GetAdjacentVertices(id1,it1); + vtkIdType idZeGrps(it1->Next());//zeGroups + vtkAdjacentVertexIterator *itGrps(vtkAdjacentVertexIterator::New()); + sil->GetAdjacentVertices(idZeGrps,itGrps); + while(itGrps->HasNext()) + { + vtkIdType idg(itGrps->Next()); + ElectromagnetismRotationGrp grp((const char *)verticesNames2->GetValue(idg)); + vtkAdjacentVertexIterator *itGrps2(vtkAdjacentVertexIterator::New()); + sil->GetAdjacentVertices(idg,itGrps2); + std::vector famsOnGroup; + while(itGrps2->HasNext()) + { + vtkIdType idgf(itGrps2->Next()); + famsOnGroup.push_back(std::string((const char *)verticesNames2->GetValue(idgf))); + } + grp.setFamilies(famsOnGroup); + itGrps2->Delete(); + _groups.push_back(grp); + } + itGrps->Delete(); + vtkIdType idZeFams(it1->Next());//zeFams + it1->Delete(); + vtkAdjacentVertexIterator *itFams(vtkAdjacentVertexIterator::New()); + sil->GetAdjacentVertices(idZeFams,itFams); + while(itFams->HasNext()) + { + vtkIdType idf(itFams->Next()); + ElectromagnetismRotationFam fam((const char *)verticesNames2->GetValue(idf)); + _fams.push_back(fam); + } + itFams->Delete(); + } + it0->Delete(); + // filter groups on cells + std::vector groupsToKeep; + std::size_t ii(0); + for(auto grp : _groups) + { + std::vector famIds(this->getFamiliesIdsOnGroup(grp.getName())); + if ( std::all_of(famIds.begin(), famIds.end(), [](int i){ return i<0; }) ) + groupsToKeep.emplace_back(std::move(grp)); + } + _groups = std::move(groupsToKeep); + // + std::size_t szg(_groups.size()),szf(_fams.size()); + if(szg==oldGrps.size() && szf==oldFams.size()) + { + bool isSame(true); + for(std::size_t i=0;i::const_iterator it0=_groups.begin();it0!=_groups.end();it0++) + if(entryCpp==(*it0).getKeyOfEntry()) + return *it0; + std::ostringstream oss; oss << "vtkElectromagnetismRotationInternal::getEntry : no such entry \"" << entry << "\"!"; + throw INTERP_KERNEL::Exception(oss.str().c_str()); +} + +ElectromagnetismRotationStatus& ElectromagnetismRotationInternal::getEntry(const char *entry) +{ + std::string entryCpp(entry); + for(std::vector::iterator it0=_groups.begin();it0!=_groups.end();it0++) + if(entryCpp==(*it0).getKeyOfEntry()) + return *it0; + std::ostringstream oss; oss << "vtkElectromagnetismRotationInternal::getEntry : no such entry \"" << entry << "\"!"; + throw INTERP_KERNEL::Exception(oss.str().c_str()); +} + +void ElectromagnetismRotationInternal::printMySelf(std::ostream& os) const +{ + os << "Groups :" << std::endl; + for(std::vector::const_iterator it0=_groups.begin();it0!=_groups.end();it0++) + (*it0).printMySelf(os); +} + +std::vector ElectromagnetismRotationInternal::getFamiliesIdsOnGroup(const std::string& groupName) const +{ + for(auto grp : _groups) + { + if(grp.getName() == groupName) + { + std::vector fams(grp.getFamiliesLyingOn()); + auto sz(fams.size()); + std::vector famIds(sz); + for(auto i = 0 ; i < sz ; ++i) + famIds[i] = this->getIdOfFamily(fams[i]); + return famIds; + } + } + std::ostringstream oss; oss << "vtkElectromagnetismRotationInternal::getFamiliesIdsOnGroup : no such group \"" << groupName << "\"!"; + throw INTERP_KERNEL::Exception(oss.str().c_str()); +} + +int ElectromagnetismRotationInternal::getIdOfFamily(const std::string& famName) const +{ + for(std::vector::const_iterator it=_fams.begin();it!=_fams.end();it++) + { + if((*it).getName()==famName) + return (*it).getId(); + } + return std::numeric_limits::max(); +} + +std::set ElectromagnetismRotationInternal::getIdsToKeep() const +{ + for(auto it: _selection) + { + const ElectromagnetismRotationStatus& elt(getEntry(it.first.c_str())); + elt.setStatus(it.second); + } + std::map m(this->computeFamStrIdMap()); + std::set s; + for(std::vector::const_iterator it0=_groups.begin();it0!=_groups.end();it0++) + { + if((*it0).getStatus()) + { + const std::vector& fams((*it0).getFamiliesLyingOn()); + for(std::vector::const_iterator it1=fams.begin();it1!=fams.end();it1++) + { + std::map::iterator it2(m.find((*it1))); + if(it2!=m.end()) + s.insert((*it2).second); + } + } + } + for(std::vector::const_iterator it0=_fams.begin();it0!=_fams.end();it0++) + if((*it0).getStatus()) + (*it0).fillIdsToKeep(s); + return s; +} + +// see reference : https://en.cppreference.com/w/cpp/iterator/iterator +class FamilyIterator : public std::iterator< std::input_iterator_tag, long, long, int*, int > +{ + long _num = 0; + const ElectromagnetismRotationInternal *_egi = nullptr; + const std::vector *_fams = nullptr; +public: + explicit FamilyIterator(long num , const ElectromagnetismRotationInternal *egi, const std::vector& fams) : _num(num),_egi(egi),_fams(&fams) {} + FamilyIterator& operator++() { ++_num; return *this;} + bool operator==(const FamilyIterator& other) const {return _num == other._num;} + bool operator!=(const FamilyIterator& other) const {return !(*this == other);} + reference operator*() const {return _egi->getIdOfFamily((*_fams)[_num]);} +}; + +std::vector< std::pair > > ElectromagnetismRotationInternal::getAllGroups() const +{ + std::vector< std::pair > > ret; + for(const auto& grp : _groups) + { + const std::vector& fams(grp.getFamiliesLyingOn()); + std::vector famIds(FamilyIterator(0,this,fams),FamilyIterator(fams.size(),this,fams)); + if ( std::all_of(famIds.begin(), famIds.end(), [](int i){ return i<0; }) )// only groups on cells considered here + { + std::pair > elt(grp.getName(),std::move(famIds)); + ret.emplace_back(std::move(elt)); + } + } + return ret; +} + +void ElectromagnetismRotationInternal::clearSelection() const +{ + _selection.clear(); + for(auto it : _groups) + it.resetStatus(); + for(auto it : _fams) + it.resetStatus(); +} + +std::map ElectromagnetismRotationInternal::computeFamStrIdMap() const +{ + std::map ret; + for(std::vector::const_iterator it0=_fams.begin();it0!=_fams.end();it0++) + ret[(*it0).getName()]=(*it0).getId(); + return ret; +} diff --git a/src/ElectromagnetismRotation/plugin/ElectromagnetismRotationHelper/ElectromagnetismRotationHelper.h b/src/ElectromagnetismRotation/plugin/ElectromagnetismRotationHelper/ElectromagnetismRotationHelper.h new file mode 100644 index 0000000..aade392 --- /dev/null +++ b/src/ElectromagnetismRotation/plugin/ElectromagnetismRotationHelper/ElectromagnetismRotationHelper.h @@ -0,0 +1,105 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +// Author : Anthony Geay (EDF R&D) + +#pragma once + +#include +#include +#include +#include + +#include "MEDLoaderForPV.h" + +class MEDLOADERFORPV_EXPORT ElectromagnetismRotationStatus +{ +public: + ElectromagnetismRotationStatus():_status(false) { } + ElectromagnetismRotationStatus(const char *name); + bool getStatus() const { return _status; } + void setStatus(bool status) const { _status=status; } + void cpyStatusFrom(const ElectromagnetismRotationStatus& other) { _status=other._status; } + std::string getName() const { return _name; } + void resetStatus() const { _status=false; } + const char *getKeyOfEntry() const { return _ze_key_name.c_str(); } + virtual void printMySelf(std::ostream& os) const; + virtual bool isSameAs(const ElectromagnetismRotationStatus& other) const; +protected: +mutable bool _status; +std::string _name; +std::string _ze_key_name; +}; + +class MEDLOADERFORPV_EXPORT ElectromagnetismRotationGrp : public ElectromagnetismRotationStatus +{ +public: + ElectromagnetismRotationGrp(const char *name):ElectromagnetismRotationStatus(name) { std::ostringstream oss; oss << START << name; _ze_key_name=oss.str(); } + void setFamilies(const std::vector& fams) { _fams=fams; } + const std::vector& getFamiliesLyingOn() const { return _fams; } + bool isSameAs(const ElectromagnetismRotationGrp& other) const; +public: + static const char START[]; + std::vector _fams; +}; + +class MEDLOADERFORPV_EXPORT ElectromagnetismRotationFam : public ElectromagnetismRotationStatus +{ +public: + ElectromagnetismRotationFam(const char *name); + void printMySelf(std::ostream& os) const; + void fillIdsToKeep(std::set& s) const; + int getId() const { return _id; } + bool isSameAs(const ElectromagnetismRotationFam& other) const; +public: + static const char START[]; +private: + int _id; +}; + +class vtkInformationDataObjectMetaDataKey; +class vtkMutableDirectedGraph; +class vtkInformation; + +class MEDLOADERFORPV_EXPORT ElectromagnetismRotationInternal +{ +public: + void loadFrom(vtkMutableDirectedGraph *sil); + int getNumberOfEntries() const; + const char *getMeshName() const; + const char *getKeyOfEntry(int i) const; + bool getStatusOfEntryStr(const char *entry) const; + void setStatusOfEntryStr(const char *entry, bool status); + void printMySelf(std::ostream& os) const; + std::vector getFamiliesIdsOnGroup(const std::string& groupName) const; + std::set getIdsToKeep() const; + std::vector< std::pair > > getAllGroups() const; + void clearSelection() const; + int getIdOfFamily(const std::string& famName) const; + static bool IndependantIsInformationOK(vtkInformationDataObjectMetaDataKey *medReaderMetaData, vtkInformation *info); +private: + std::map computeFamStrIdMap() const; + const ElectromagnetismRotationStatus& getEntry(const char *entry) const; + ElectromagnetismRotationStatus& getEntry(const char *entry); +private: + std::vector _groups; + std::vector _fams; + mutable std::vector< std::pair > _selection; + std::string _mesh_name; +}; + diff --git a/src/ElectromagnetismRotation/plugin/ElectromagnetismRotationHelper/MEDLoaderForPV.h b/src/ElectromagnetismRotation/plugin/ElectromagnetismRotationHelper/MEDLoaderForPV.h new file mode 100644 index 0000000..a40ce84 --- /dev/null +++ b/src/ElectromagnetismRotation/plugin/ElectromagnetismRotationHelper/MEDLoaderForPV.h @@ -0,0 +1,33 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +#ifndef __MEDLOADERFORPV_HXX__ +#define __MEDLOADERFORPV_HXX__ + +#ifdef WIN32 +# if defined MEDLoaderForPV_EXPORTS || defined MEDLOADERFORPV_EXPORTS +# define MEDLOADERFORPV_EXPORT __declspec( dllexport ) +# else +# define MEDLOADERFORPV_EXPORT __declspec( dllimport ) +# endif +#else + #define MEDLOADERFORPV_EXPORT +#endif + +#endif diff --git a/src/ElectromagnetismRotation/plugin/ElectromagnetismRotationIO/CMakeLists.txt b/src/ElectromagnetismRotation/plugin/ElectromagnetismRotationIO/CMakeLists.txt new file mode 100644 index 0000000..d5c5fc4 --- /dev/null +++ b/src/ElectromagnetismRotation/plugin/ElectromagnetismRotationIO/CMakeLists.txt @@ -0,0 +1,34 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +set(classes + vtkElectromagnetismRotation + vtkPVMetaDataInformation +) + +vtk_module_add_module(ElectromagnetismRotationIO + FORCE_STATIC + CLASSES ${classes} +) + +target_include_directories(ElectromagnetismRotationIO PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}/../ElectromagnetismRotationHelper" + ${MEDCOUPLING_INCLUDE_DIRS}) + +target_link_libraries(ElectromagnetismRotationIO PUBLIC ElectromagnetismRotationHelper) diff --git a/src/ElectromagnetismRotation/plugin/ElectromagnetismRotationIO/VTKMEDTraits.hxx b/src/ElectromagnetismRotation/plugin/ElectromagnetismRotationIO/VTKMEDTraits.hxx new file mode 100644 index 0000000..4c7832d --- /dev/null +++ b/src/ElectromagnetismRotation/plugin/ElectromagnetismRotationIO/VTKMEDTraits.hxx @@ -0,0 +1,81 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +// Author : Anthony Geay (EDF R&D) + +#ifndef __VTKMEDTRAITS_HXX__ +#define __VTKMEDTRAITS_HXX__ + +class vtkIntArray; +class vtkLongArray; +#ifdef WIN32 +class vtkLongLongArray; +#endif +class vtkFloatArray; +class vtkDoubleArray; + +template +class MEDFileVTKTraits +{ +public: + typedef void VtkType; + typedef void MCType; +}; + +template<> +class MEDFileVTKTraits +{ +public: + typedef vtkIntArray VtkType; + typedef MEDCoupling::DataArrayInt32 MCType; +}; + +template<> +#ifdef WIN32 +class MEDFileVTKTraits +#else +class MEDFileVTKTraits +#endif +# +{ +public: +#ifdef WIN32 + typedef vtkLongLongArray VtkType; +#else + typedef vtkLongArray VtkType; +#endif + typedef MEDCoupling::DataArrayInt64 MCType; +}; + +template<> +class MEDFileVTKTraits +{ +public: + typedef vtkFloatArray VtkType; + typedef MEDCoupling::DataArrayFloat MCType; +}; + +template<> +class MEDFileVTKTraits +{ +public: + typedef vtkDoubleArray VtkType; + typedef MEDCoupling::DataArrayDouble MCType; +}; + +#endif diff --git a/src/ElectromagnetismRotation/plugin/ElectromagnetismRotationIO/vtk.module b/src/ElectromagnetismRotation/plugin/ElectromagnetismRotationIO/vtk.module new file mode 100644 index 0000000..4f21a46 --- /dev/null +++ b/src/ElectromagnetismRotation/plugin/ElectromagnetismRotationIO/vtk.module @@ -0,0 +1,29 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +NAME + ElectromagnetismRotationIO +DEPENDS + VTK::FiltersGeneral + VTK::IOLegacy + ParaView::RemotingCore +PRIVATE_DEPENDS + VTK::IOLegacy + ParaView::VTKExtensionsFiltersRendering + ParaView::VTKExtensionsMisc diff --git a/src/ElectromagnetismRotation/plugin/ElectromagnetismRotationIO/vtkElectromagnetismRotation.cxx b/src/ElectromagnetismRotation/plugin/ElectromagnetismRotationIO/vtkElectromagnetismRotation.cxx new file mode 100644 index 0000000..ce20795 --- /dev/null +++ b/src/ElectromagnetismRotation/plugin/ElectromagnetismRotationIO/vtkElectromagnetismRotation.cxx @@ -0,0 +1,454 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +// Author : Anthony Geay + +#include "vtkElectromagnetismRotation.h" + +#include "MEDCouplingMemArray.hxx" + +#include "ElectromagnetismRotationHelper.h" +#include "VTKMEDTraits.hxx" + +#include "vtkAdjacentVertexIterator.h" +#include "vtkDataArrayTemplate.h" +#include "vtkIntArray.h" +#include "vtkLongArray.h" +#ifdef WIN32 +#include "vtkLongLongArray.h" +#endif +#include "vtkCellData.h" +#include "vtkPointData.h" + +#include "vtkStreamingDemandDrivenPipeline.h" +#include "vtkUnstructuredGrid.h" +#include "vtkMultiBlockDataSet.h" + +#include "vtkInformationStringKey.h" +#include "vtkAlgorithmOutput.h" +#include "vtkObjectFactory.h" +#include "vtkMutableDirectedGraph.h" +#include "vtkMultiBlockDataSet.h" +#include "vtkDataSet.h" +#include "vtkInformationVector.h" +#include "vtkInformation.h" +#include "vtkDataArraySelection.h" +#include "vtkTimeStamp.h" +#include "vtkInEdgeIterator.h" +#include "vtkInformationDataObjectKey.h" +#include "vtkExecutive.h" +#include "vtkVariantArray.h" +#include "vtkStringArray.h" +#include "vtkDoubleArray.h" +#include "vtkCharArray.h" +#include "vtkUnsignedCharArray.h" +#include "vtkDataSetAttributes.h" +#include "vtkDemandDrivenPipeline.h" +#include "vtkDataObjectTreeIterator.h" +#include "vtkThreshold.h" +#include "vtkMultiBlockDataGroupFilter.h" +#include "vtkCompositeDataToUnstructuredGridFilter.h" +#include "vtkInformationDataObjectMetaDataKey.h" +#include "vtkTransform.h" +#include "vtkFunctionParser.h" + +#include +#include +#include +#include + +const char ZE_SEP[]="@@][@@"; + +const char TS_STR[]="TS"; + +const char COM_SUP_STR[]="ComSup"; + +const char FAMILY_ID_CELL_NAME[]="FamilyIdCell"; + +const char NUM_ID_CELL_NAME[]="NumIdCell"; + +const char FAMILY_ID_NODE_NAME[]="FamilyIdNode"; + +const char NUM_ID_NODE_NAME[]="NumIdNode"; + +const char GLOBAL_NODE_ID_NAME[]="GlobalNodeIds"; + +vtkStandardNewMacro(vtkElectromagnetismRotation); + +vtkInformationDataObjectMetaDataKey* GetMEDReaderMetaDataIfAny() +{ + static const char ZE_KEY[] = "vtkMEDReader::META_DATA"; + MEDCoupling::GlobalDict* gd(MEDCoupling::GlobalDict::GetInstance()); + if (!gd->hasKey(ZE_KEY)) + return 0; + std::string ptSt(gd->value(ZE_KEY)); + void* pt(0); + std::istringstream iss(ptSt); + iss >> pt; + return reinterpret_cast(pt); +} + +bool IsInformationOK(vtkInformation* info) +{ + vtkInformationDataObjectMetaDataKey* key(GetMEDReaderMetaDataIfAny()); + if (!key) + return false; + // Check the information contain meta data key + if (!info->Has(key)) + return false; + // Recover Meta Data + vtkMutableDirectedGraph* sil(vtkMutableDirectedGraph::SafeDownCast(info->Get(key))); + if (!sil) + return false; + int idNames(0); + vtkAbstractArray* verticesNames(sil->GetVertexData()->GetAbstractArray("Names", idNames)); + vtkStringArray* verticesNames2(vtkStringArray::SafeDownCast(verticesNames)); + if (!verticesNames2) + return false; + for (int i = 0; i < verticesNames2->GetNumberOfValues(); i++) + { + vtkStdString& st(verticesNames2->GetValue(i)); + if (st == "MeshesFamsGrps") + return true; + } + return false; +} + +class vtkElectromagnetismRotation::vtkElectromagnetismRotationInternal : public ElectromagnetismRotationInternal +{ +}; + +//////////////////// + +vtkElectromagnetismRotation::vtkElectromagnetismRotation():SIL(NULL),Internal(new vtkElectromagnetismRotationInternal),InsideOut(0),Axis(2),RotationRotor(1e300) +{ +} + +vtkElectromagnetismRotation::~vtkElectromagnetismRotation() +{ + delete this->Internal; +} + +void vtkElectromagnetismRotation::SetInsideOut(int val) +{ + if(this->InsideOut!=val) + { + this->InsideOut=val; + this->Modified(); + } +} + +void vtkElectromagnetismRotation::SetAxis(int axis) +{ + if(this->Axis!=axis) + { + this->Axis=axis; + this->Modified(); + } +} + +void vtkElectromagnetismRotation::SetAngularStep(char *angStep) +{ + this->AngularStep = angStep; + this->Modified(); +} + +int vtkElectromagnetismRotation::RequestInformation(vtkInformation *request, vtkInformationVector **inputVector, vtkInformationVector *outputVector) +{ +// vtkUnstructuredGridAlgorithm::RequestInformation(request,inputVector,outputVector); + try + { +// std::cerr << "########################################## vtkElectromagnetismRotation::RequestInformation ##########################################" << std::endl; +// request->Print(cout); + vtkInformation *outInfo(outputVector->GetInformationObject(0)); + vtkInformation *inputInfo(inputVector[0]->GetInformationObject(0)); + if(!ElectromagnetismRotationInternal::IndependantIsInformationOK(GetMEDReaderMetaDataIfAny(),inputInfo)) + { + vtkErrorMacro("No SIL Data available ! The source of this filter must be MEDReader !"); + return 0; + } + + this->SetSIL(vtkMutableDirectedGraph::SafeDownCast(inputInfo->Get(GetMEDReaderMetaDataIfAny()))); + this->Internal->loadFrom(this->SIL); + //this->Internal->printMySelf(std::cerr); + } + catch(INTERP_KERNEL::Exception& e) + { + std::cerr << "Exception has been thrown in vtkElectromagnetismRotation::RequestInformation : " << e.what() << std::endl; + return 0; + } + return 1; +} + +/*! + * Do not use vtkCxxSetObjectMacro macro because input mdg comes from an already managed in the pipeline just a ref on it. + */ +void vtkElectromagnetismRotation::SetSIL(vtkMutableDirectedGraph *mdg) +{ + if(this->SIL==mdg) + return ; + this->SIL=mdg; +} + +template +vtkDataSet *FilterFamilies(vtkThreshold *thres, + vtkDataSet *input, const std::set& idsToKeep, bool insideOut, const char *arrNameOfFamilyField, + const char *associationForThreshold, bool& catchAll, bool& catchSmth) +{ + const int VTK_DATA_ARRAY_DELETE=vtkDataArrayTemplate::VTK_DATA_ARRAY_DELETE; + const char ZE_SELECTION_ARR_NAME[]="@@ZeSelection@@"; + vtkDataSet *output(input->NewInstance()); + output->ShallowCopy(input); + thres->SetInputData(output); + vtkDataSetAttributes *dscIn(input->GetCellData()),*dscIn2(input->GetPointData()); + vtkDataSetAttributes *dscOut(output->GetCellData()),*dscOut2(output->GetPointData()); + // + double vMin(insideOut==0?1.:0.),vMax(insideOut==0?2.:1.); + thres->ThresholdBetween(vMin,vMax); + // OK for the output + // + CellPointExtractor cpe2(input); + vtkDataArray *da(cpe2.Get()->GetScalars(arrNameOfFamilyField)); + if(!da) + return 0; + std::string daName(da->GetName()); + typedef MEDFileVTKTraits::VtkType vtkMCIdTypeArray; + vtkMCIdTypeArray *dai(vtkMCIdTypeArray::SafeDownCast(da)); + if(daName!=arrNameOfFamilyField || !dai) + return 0; + // + int nbOfTuples(dai->GetNumberOfTuples()); + vtkCharArray *zeSelection(vtkCharArray::New()); + zeSelection->SetName(ZE_SELECTION_ARR_NAME); + zeSelection->SetNumberOfComponents(1); + char *pt(new char[nbOfTuples]); + zeSelection->SetArray(pt,nbOfTuples,0,VTK_DATA_ARRAY_DELETE); + const mcIdType *inPtr(dai->GetPointer(0)); + std::fill(pt,pt+nbOfTuples,0); + catchAll=true; catchSmth=false; + std::vector pt2(nbOfTuples,false); + for(std::set::const_iterator it=idsToKeep.begin();it!=idsToKeep.end();it++) + { + bool catchFid(false); + for(int i=0;iAddArray(zeSelection)); + cpe3.Get()->SetActiveAttribute(idx,vtkDataSetAttributes::SCALARS); + cpe3.Get()->CopyScalarsOff(); + zeSelection->Delete(); + // + thres->SetInputArrayToProcess(idx,0,0,associationForThreshold,ZE_SELECTION_ARR_NAME); + thres->Update(); + vtkUnstructuredGrid *zeComputedOutput(thres->GetOutput()); + CellPointExtractor cpe(zeComputedOutput); + cpe.Get()->RemoveArray(idx); + output->Delete(); + zeComputedOutput->Register(0); + return zeComputedOutput; +} + +class CellExtractor +{ +public: + CellExtractor(vtkDataSet *ds):_ds(ds) { } + vtkDataSetAttributes *Get() { return _ds->GetCellData(); } +private: + vtkDataSet *_ds; +}; + +class PointExtractor +{ +public: + PointExtractor(vtkDataSet *ds):_ds(ds) { } + vtkDataSetAttributes *Get() { return _ds->GetPointData(); } +private: + vtkDataSet *_ds; +}; + +int vtkElectromagnetismRotation::RequestData(vtkInformation *request, vtkInformationVector **inputVector, vtkInformationVector *outputVector) +{ + try + { + // std::cerr << "########################################## vtkElectromagnetismRotation::RequestData ##########################################" << std::endl; + // request->Print(cout); + vtkInformation* inputInfo=inputVector[0]->GetInformationObject(0); + vtkMultiBlockDataSet *inputMB(vtkMultiBlockDataSet::SafeDownCast(inputInfo->Get(vtkDataObject::DATA_OBJECT()))); + if(inputMB->GetNumberOfBlocks()!=1) + { + vtkErrorMacro(<< "vtkElectromagnetismRotation::RequestData : input has not the right number of parts ! Expected 1 !" ) ; + return 0; + } + vtkDataSet *input(vtkDataSet::SafeDownCast(inputMB->GetBlock(0))); + vtkInformation *info(input->GetInformation()); + vtkInformation *outInfo(outputVector->GetInformationObject(0)); + vtkMultiBlockDataSet *output(vtkMultiBlockDataSet::SafeDownCast(outInfo->Get(vtkDataObject::DATA_OBJECT()))); + output->SetNumberOfBlocks(2); + std::set idsToKeep(this->Internal->getIdsToKeep()); + this->Internal->clearSelection(); + // first shrink the input + bool catchAll,catchSmth; + vtkNew thres1,thres2; + vtkSmartPointer rotor(FilterFamilies(thres1,input,idsToKeep,0,FAMILY_ID_CELL_NAME,"vtkDataObject::FIELD_ASSOCIATION_CELLS",catchAll,catchSmth)); + vtkSmartPointer stator(FilterFamilies(thres2,input,idsToKeep,1,FAMILY_ID_CELL_NAME,"vtkDataObject::FIELD_ASSOCIATION_CELLS",catchAll,catchSmth)); + // + double reqTS(0.); + int nbOfSteps(-1); + std::unique_ptr timeSteps; + + if(outInfo->Has(vtkStreamingDemandDrivenPipeline::UPDATE_TIME_STEP())) + reqTS=outInfo->Get(vtkStreamingDemandDrivenPipeline::UPDATE_TIME_STEP()); + if(outInfo->Has(vtkStreamingDemandDrivenPipeline::TIME_STEPS())) + { + nbOfSteps = outInfo->Length(vtkStreamingDemandDrivenPipeline::TIME_STEPS()); + timeSteps.reset(new double[ nbOfSteps ]); + outInfo->Get(vtkStreamingDemandDrivenPipeline::TIME_STEPS(),timeSteps.get()); + //std::cerr << "nb : " << nbOfSteps << std::endl; + //std::for_each(timeSteps.get(),timeSteps.get()+nbOfSteps,[](double v) { std::cerr << v << std::endl; }); + } + if(nbOfSteps<2 || !timeSteps.get()) + { + vtkErrorMacro(<< "vtkElectromagnetismRotation::RequestData : A temporal dataset is expected ! Here < 2 time steps found !" ) ; + return 0; + } + // Calcul de l angle effectif de rotation + double minTime(timeSteps[0]),maxTime(timeSteps[nbOfSteps-1]); + if(minTime == maxTime) + { + vtkErrorMacro(<< "vtkElectromagnetismRotation::RequestData : minTime == maxTime !" ) ; + return 0; + } + double angularStepD = 0; + { + vtkNew fp; + fp->SetFunction(this->AngularStep.c_str()); + angularStepD = fp->GetScalarResult(); + } + double effectiveTime(reqTS); effectiveTime = std::max(effectiveTime,minTime); effectiveTime = std::min(effectiveTime,maxTime); + this->RotationRotor = (angularStepD*((effectiveTime-minTime)/(maxTime-minTime))); + //std::cout << "*** " << effectiveTime << " " << minTime << " " << maxTime << " " << angleDegree << std::endl << std::flush; + //std::cout << "*** " << this->RotationRotor << std::endl << std::flush; + // + if(rotor) + { + if(catchAll) + { + vtkNew transformR; + switch(this->Axis) + { + case 0: + { + transformR->RotateX(this->RotationRotor); + break; + } + case 1: + { + transformR->RotateY(this->RotationRotor); + break; + } + case 2: + { + transformR->RotateZ(this->RotationRotor); + break; + } + default: + { + vtkErrorMacro(<< "vtkElectromagnetismRotation::RequestData : not recognized axis !" ) ; + return 0; + } + } + vtkNew newCoords; + transformR->TransformPoints(vtkPointSet::SafeDownCast(rotor)->GetPoints(),newCoords); + vtkPointSet::SafeDownCast(rotor)->SetPoints(newCoords); + output->SetBlock(0,rotor); + output->SetBlock(1,stator); + return 1; + } + else + return 0; + } + } + catch(INTERP_KERNEL::Exception& e) + { + vtkErrorMacro(<< "Exception has been thrown in vtkElectromagnetismRotation::RequestData : " << e.what()); + return 0; + } +} + +int vtkElectromagnetismRotation::GetSILUpdateStamp() +{ + return (int)this->SILTime; +} + +const char* vtkElectromagnetismRotation::GetGrpStart() +{ + return ElectromagnetismRotationGrp::START; +} + +const char* vtkElectromagnetismRotation::GetFamStart() +{ + return ElectromagnetismRotationFam::START; +} + +void vtkElectromagnetismRotation::PrintSelf(ostream& os, vtkIndent indent) +{ + this->Superclass::PrintSelf(os, indent); +} + +int vtkElectromagnetismRotation::GetNumberOfGroupsFlagsArrays() +{ + int ret(this->Internal->getNumberOfEntries()); + //std::cerr << "vtkElectromagnetismRotation::GetNumberOfFieldsTreeArrays() -> " << ret << std::endl; + return ret; +} + +const char *vtkElectromagnetismRotation::GetGroupsFlagsArrayName(int index) +{ + const char *ret(this->Internal->getKeyOfEntry(index)); +// std::cerr << "vtkElectromagnetismRotation::GetFieldsTreeArrayName(" << index << ") -> " << ret << std::endl; + return ret; +} + +int vtkElectromagnetismRotation::GetGroupsFlagsArrayStatus(const char *name) +{ + int ret((int)this->Internal->getStatusOfEntryStr(name)); +// std::cerr << "vtkElectromagnetismRotation::GetGroupsFlagsArrayStatus(" << name << ") -> " << ret << std::endl; + return ret; +} + +void vtkElectromagnetismRotation::SetGroupsFlagsStatus(const char *name, int status) +{ + //std::cerr << "vtkElectromagnetismRotation::SetFieldsStatus(" << name << "," << status << ")" << std::endl; + this->Internal->setStatusOfEntryStr(name,(bool)status); + this->Modified(); + //this->Internal->printMySelf(std::cerr); +} + +const char *vtkElectromagnetismRotation::GetMeshName() +{ + return this->Internal->getMeshName(); +} diff --git a/src/ElectromagnetismRotation/plugin/ElectromagnetismRotationIO/vtkElectromagnetismRotation.h b/src/ElectromagnetismRotation/plugin/ElectromagnetismRotationIO/vtkElectromagnetismRotation.h new file mode 100644 index 0000000..e272e2a --- /dev/null +++ b/src/ElectromagnetismRotation/plugin/ElectromagnetismRotationIO/vtkElectromagnetismRotation.h @@ -0,0 +1,79 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +// Author : Anthony Geay + +#pragma once + +#include "vtkMultiBlockDataSetAlgorithm.h" + +#include + +class vtkMutableDirectedGraph; + +class VTK_EXPORT vtkElectromagnetismRotation : public vtkMultiBlockDataSetAlgorithm +{ +public: + static vtkElectromagnetismRotation* New(); + vtkTypeMacro(vtkElectromagnetismRotation, vtkMultiBlockDataSetAlgorithm) + void PrintSelf(ostream& os, vtkIndent indent); + virtual int GetNumberOfGroupsFlagsArrays(); + const char *GetGroupsFlagsArrayName(int index); + int GetGroupsFlagsArrayStatus(const char *name); + virtual void SetGroupsFlagsStatus(const char *name, int status); + void SetInsideOut(int val); + void SetAxis(int axis); + void SetAngularStep(char *angStep); + + // Description: + // Every time the SIL is updated a this will return a different value. + virtual int GetSILUpdateStamp(); + const char *GetMeshName(); + static const char* GetGrpStart(); + static const char* GetFamStart(); +protected: + vtkElectromagnetismRotation(); + ~vtkElectromagnetismRotation(); + + int RequestInformation(vtkInformation *request, + vtkInformationVector **inputVector, vtkInformationVector *outputVector); + + int RequestData(vtkInformation *request, vtkInformationVector **inputVector, + vtkInformationVector *outputVector); + + // Description: + // This SIL stores the structure of the mesh/groups/cell types + // that can be selected. + virtual void SetSIL(vtkMutableDirectedGraph*); + vtkGetObjectMacro(SIL, vtkMutableDirectedGraph); +protected: + vtkMutableDirectedGraph *SIL; + vtkTimeStamp SILTime; +private: + vtkElectromagnetismRotation(const vtkElectromagnetismRotation&); + void operator=(const vtkElectromagnetismRotation&); // Not implemented. + private: + //BTX + //ETX + class vtkElectromagnetismRotationInternal; + vtkElectromagnetismRotationInternal *Internal; + int InsideOut; + int Axis; + std::string AngularStep; + mutable double RotationRotor; +}; diff --git a/src/ElectromagnetismRotation/plugin/ElectromagnetismRotationIO/vtkPVMetaDataInformation.cxx b/src/ElectromagnetismRotation/plugin/ElectromagnetismRotationIO/vtkPVMetaDataInformation.cxx new file mode 100644 index 0000000..975fa12 --- /dev/null +++ b/src/ElectromagnetismRotation/plugin/ElectromagnetismRotationIO/vtkPVMetaDataInformation.cxx @@ -0,0 +1,157 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +// Author : Anthony Geay + +#include "vtkPVMetaDataInformation.h" + +#include "vtkAlgorithm.h" +#include "vtkAlgorithmOutput.h" +#include "vtkClientServerStream.h" +#include "vtkExecutive.h" +#include "vtkDataObject.h" +#include "vtkGenericDataObjectReader.h" +#include "vtkGenericDataObjectWriter.h" +#include "vtkInformationDataObjectMetaDataKey.h" +#include "vtkInformation.h" +#include "vtkObjectFactory.h" + +#include "MEDCouplingRefCountObject.hxx" + +#include + +vtkStandardNewMacro(vtkPVMetaDataInformation); +vtkCxxSetObjectMacro(vtkPVMetaDataInformation, InformationData, vtkDataObject); + +static vtkInformationDataObjectMetaDataKey* GetMEDReaderMetaDataIfAny() +{ + static const char ZE_KEY[] = "vtkMEDReader::META_DATA"; + MEDCoupling::GlobalDict* gd(MEDCoupling::GlobalDict::GetInstance()); + if (!gd->hasKey(ZE_KEY)) + return 0; + std::string ptSt(gd->value(ZE_KEY)); + void* pt(0); + std::istringstream iss(ptSt); + iss >> pt; + return reinterpret_cast(pt); +} + +//---------------------------------------------------------------------------- +vtkPVMetaDataInformation::vtkPVMetaDataInformation() +{ + this->InformationData = NULL; +} + +//---------------------------------------------------------------------------- +vtkPVMetaDataInformation::~vtkPVMetaDataInformation() +{ + this->SetInformationData(NULL); +} + +//---------------------------------------------------------------------------- +void vtkPVMetaDataInformation::CopyFromObject(vtkObject* obj) +{ + this->SetInformationData(NULL); + + vtkAlgorithmOutput* algOutput = vtkAlgorithmOutput::SafeDownCast(obj); + if (!algOutput) + { + vtkAlgorithm* alg = vtkAlgorithm::SafeDownCast(obj); + if (alg) + { + algOutput = alg->GetOutputPort(0); + } + + } + if (!algOutput) + { + vtkErrorMacro("Information can only be gathered from a vtkAlgorithmOutput."); + return; + } + + vtkAlgorithm* reader = algOutput->GetProducer(); + vtkInformation* info = reader->GetExecutive()->GetOutputInformation( + algOutput->GetIndex()); + + if (info && info->Has(GetMEDReaderMetaDataIfAny())) + { + this->SetInformationData(vtkDataObject::SafeDownCast(info->Get(GetMEDReaderMetaDataIfAny()))); + } +} + +//---------------------------------------------------------------------------- +void vtkPVMetaDataInformation::CopyToStream(vtkClientServerStream* css) +{ + css->Reset(); + if (!this->InformationData) + { + *css << vtkClientServerStream::Reply + << vtkClientServerStream::InsertArray( + static_cast(NULL), 0) + << vtkClientServerStream::End; + return; + } + + vtkDataObject* clone = this->InformationData->NewInstance(); + clone->ShallowCopy(this->InformationData); + + vtkGenericDataObjectWriter* writer = vtkGenericDataObjectWriter::New(); + writer->SetFileTypeToBinary(); + writer->WriteToOutputStringOn(); + writer->SetInputData(clone); + writer->Write(); + + *css << vtkClientServerStream::Reply + << vtkClientServerStream::InsertArray( + writer->GetBinaryOutputString(), + writer->GetOutputStringLength()) + << vtkClientServerStream::End; + writer->RemoveAllInputs(); + writer->Delete(); + clone->Delete(); +} + +//---------------------------------------------------------------------------- +void vtkPVMetaDataInformation::CopyFromStream(const vtkClientServerStream* css) +{ + this->SetInformationData(0); + vtkTypeUInt32 length; + if (css->GetArgumentLength(0, 0, &length) && length > 0) + { + unsigned char* raw_data = new unsigned char[length]; + css->GetArgument(0, 0, raw_data, length); + vtkGenericDataObjectReader* reader = vtkGenericDataObjectReader::New(); + reader->SetBinaryInputString(reinterpret_cast(raw_data), length); + reader->ReadFromInputStringOn(); + delete []raw_data; + reader->Update(); + this->SetInformationData(reader->GetOutput()); + reader->Delete(); + } +} + +void vtkPVMetaDataInformation::AddInformation(vtkPVInformation*) +{ +} + +//---------------------------------------------------------------------------- +void vtkPVMetaDataInformation::PrintSelf(ostream& os, vtkIndent indent) +{ + this->Superclass::PrintSelf(os, indent); + os << indent << "InformationData: " << this->InformationData << endl; +} diff --git a/src/ElectromagnetismRotation/plugin/ElectromagnetismRotationIO/vtkPVMetaDataInformation.h b/src/ElectromagnetismRotation/plugin/ElectromagnetismRotationIO/vtkPVMetaDataInformation.h new file mode 100644 index 0000000..b2f3133 --- /dev/null +++ b/src/ElectromagnetismRotation/plugin/ElectromagnetismRotationIO/vtkPVMetaDataInformation.h @@ -0,0 +1,65 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +// Author : Anthony Geay + +#ifndef __vtkPVMetaDataInformation_h +#define __vtkPVMetaDataInformation_h + +#include "vtkPVInformation.h" + +class vtkDataObject; +class vtkInformationDataObjectKey; + +class VTK_EXPORT vtkPVMetaDataInformation : public vtkPVInformation +{ +public: + static vtkPVMetaDataInformation* New(); + vtkTypeMacro(vtkPVMetaDataInformation, vtkPVInformation); + void PrintSelf(ostream& os, vtkIndent indent); + + // Description: + // Transfer information about a single object into this object. + virtual void CopyFromObject(vtkObject*); + + //BTX + // Description: + // Manage a serialized version of the information. + virtual void CopyToStream(vtkClientServerStream*); + virtual void CopyFromStream(const vtkClientServerStream*); + virtual void AddInformation(vtkPVInformation*); + //ETX + + // Description: + // Returns the Information Data. + vtkGetObjectMacro(InformationData, vtkDataObject); + +//BTX +protected: + vtkPVMetaDataInformation(); + ~vtkPVMetaDataInformation(); + void SetInformationData(vtkDataObject*); + vtkDataObject* InformationData; + +private: + vtkPVMetaDataInformation(const vtkPVMetaDataInformation&); // Not implemented + void operator=(const vtkPVMetaDataInformation&); // Not implemented +//ETX +}; + +#endif diff --git a/src/ElectromagnetismRotation/plugin/ParaViewPlugin/CMakeLists.txt b/src/ElectromagnetismRotation/plugin/ParaViewPlugin/CMakeLists.txt new file mode 100644 index 0000000..f248280 --- /dev/null +++ b/src/ElectromagnetismRotation/plugin/ParaViewPlugin/CMakeLists.txt @@ -0,0 +1,58 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +set(interfaces) +set(sources) + +cmake_policy(SET CMP0071 OLD) # bug in ParaViewPlugin.cmake? + +if(PARAVIEW_USE_QT) + + set(sources + pqElectroRotationAbstractFieldsWidget.cxx + pqElectroRotationGroupWidget.cxx) + + paraview_plugin_add_property_widget( + KIND WIDGET + TYPE "ElectroRotationGroupWidgetType" + CLASS_NAME pqElectroRotationGroupWidget + INTERFACES property_interfaces + SOURCES property_sources) + list(APPEND interfaces + ${property_interfaces}) + list(APPEND sources + ${property_sources}) + +endif(PARAVIEW_USE_QT) + +paraview_add_plugin(ElectromagnetismRotation + VERSION "5.0" + UI_INTERFACES ${interfaces} + SOURCES ${sources} + UI_RESOURCES Resources/pqElectromagnetismRotation.qrc + SERVER_MANAGER_XML Resources/ElectromagnetismRotation.xml + MODULES ElectromagnetismRotationIO + MODULE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/../ElectromagnetismRotationIO/vtk.module" + ) + +install(TARGETS ElectromagnetismRotation + RUNTIME DESTINATION lib/paraview + LIBRARY DESTINATION lib/paraview + ARCHIVE DESTINATION lib/paraview +) diff --git a/src/ElectromagnetismRotation/plugin/ParaViewPlugin/Resources/ElectromagnetismRotation.xml b/src/ElectromagnetismRotation/plugin/ParaViewPlugin/Resources/ElectromagnetismRotation.xml new file mode 100644 index 0000000..a6dae23 --- /dev/null +++ b/src/ElectromagnetismRotation/plugin/ParaViewPlugin/Resources/ElectromagnetismRotation.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + This property specifies the input to the Level Scalars filter. + + + + + + + + + + + + + + + This property lists all groups and families to select. + + + + + + + + + + This property determines the axis (X, Y or Z) along which rotation is performed. + + + + Pas angulaire en degre + + + + + This property returns the name of the mesh. + + + + + + + + + + + + diff --git a/src/ElectromagnetismRotation/plugin/ParaViewPlugin/Resources/Icons/pqCellData16.png b/src/ElectromagnetismRotation/plugin/ParaViewPlugin/Resources/Icons/pqCellData16.png new file mode 100644 index 0000000000000000000000000000000000000000..8a6f2a6cc9a441e1fb4a1dfc63d8060ea93dab78 GIT binary patch literal 899 zcmV-}1AP36P)s~7#$$(SWFU8GbZ8({Xk{QrNlj4iWF>9@ z00P%ZL_t(|+MSWjYg}a%#((d7Z>BS8(j-HSPLkFnZ2}qSA_jF~l~ScP;zAIlxDprs z11<{P>dJ-d=+=!&Kv2YjNEM5%Np01(aYAj9*6GLGr0JcRoBQ?N`yLlpiyIMq;J|?g zeuwisiht;px~^3F+*;YOZj#B1V*}31eq=uxtmyN(74smH4U{e^#8xY$?|fs#1voaX z_tpATz2xxf=wNAKex^2cX#a4jH}%y0NFBYi-HCIPuZ zKkj@mJ6#*EADq&oj}B9vIigihg{0l0-2EMM_lh{_17%H25Tz3Bos_RHT$49HjmO+t zzZ^YMADMVzwt_zVJSxi}@W}72u=(o>GyNUPS`+{i$0*gFQCSkIsnyE~5V{v{(|G&f zEY3@(!Ed7c4Mbbip#>(YhjGg#BnqH^7;7zN{VZFJ@7WHu0A1IQ>DVL?Y^GG+h3iWw zf0Jl8q&>bs?_&p0<8#O#KM}4h5*kQz2vQV)TXL{@vTKR99d3RL-c^$87xrtn;i0nlrnc=zsi zj07#n!s5CWRz28>;D?)*puIz58#>8DfZW(oK7b3f;y8xj4idMy+7h;Q6_4x{>LbEV z1d<>&kwORZ^T?FMZD&kda=VE{-7;w~`YiSVfrKm_GgITzNG8 z!2kc%t0X0WpikEJP(p0I!0IYBr&*G7q9xX>$(86cyR~>X$9l$svr}3a1LGE02)Hwf aGBD&WkYK!*@x=saDubu1pUXO@geCwL{x@v^ literal 0 HcmV?d00001 diff --git a/src/ElectromagnetismRotation/plugin/ParaViewPlugin/Resources/pqElectromagnetismRotation.qrc b/src/ElectromagnetismRotation/plugin/ParaViewPlugin/Resources/pqElectromagnetismRotation.qrc new file mode 100644 index 0000000..d1fe75a --- /dev/null +++ b/src/ElectromagnetismRotation/plugin/ParaViewPlugin/Resources/pqElectromagnetismRotation.qrc @@ -0,0 +1,6 @@ + + + Icons/pqCellData16.png + Icons/pqPointData16.png + + diff --git a/src/ElectromagnetismRotation/plugin/ParaViewPlugin/pqElectroRotationAbstractFieldsWidget.cxx b/src/ElectromagnetismRotation/plugin/ParaViewPlugin/pqElectroRotationAbstractFieldsWidget.cxx new file mode 100644 index 0000000..f6307a8 --- /dev/null +++ b/src/ElectromagnetismRotation/plugin/ParaViewPlugin/pqElectroRotationAbstractFieldsWidget.cxx @@ -0,0 +1,147 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +// Author : Anthony Geay + +#include "pqElectroRotationAbstractFieldsWidget.h" + +#include "pqArrayListDomain.h" +#include "pqTreeWidget.h" +#include "pqTreeWidgetItemObject.h" + +#include +#include +//----------------------------------------------------------------------------- +pqElectroRotationAbstractFieldsWidget::pqElectroRotationAbstractFieldsWidget( + vtkSMProxy *smproxy, vtkSMProperty *smproperty, QWidget *parentObject) +: Superclass(smproxy, parentObject) +{ + this->NItems = 0; + this->visibleHeader = true; + this->setShowLabel(false); + + // Grid Layout + QGridLayout* gridLayout = new QGridLayout(this); + + // Tree widget + this->TreeWidget = new pqTreeWidget(this); + gridLayout->addWidget(this->TreeWidget); +} + +//----------------------------------------------------------------------------- +pqElectroRotationAbstractFieldsWidget::~pqElectroRotationAbstractFieldsWidget() +{ + delete this->TreeWidget; +} + +void pqElectroRotationAbstractFieldsWidget::initializeTreeWidget(vtkSMProxy *smproxy, vtkSMProperty *smproperty) +{ + // Load Tree Widget Items + this->loadTreeWidgetItems(); + + // Connect Property Domain to the fieldDomain property, + // so setFieldDomain is called when the domain changes. + vtkSMDomain* arraySelectionDomain = smproperty->GetDomain("array_list"); + new pqArrayListDomain(this,"fieldsDomain", smproxy, smproperty, arraySelectionDomain); + + // Connect property to field QProperty using a biderectionnal property link + this->addPropertyLink(this, "fields", SIGNAL(fieldsChanged()), + smproxy, smproperty); + + // Call slot when the tree is changed + QObject::connect(this->TreeWidget, SIGNAL(itemChanged(QTreeWidgetItem*, int)), + this, SLOT(onItemChanged(QTreeWidgetItem*, int))); + +} + +//----------------------------------------------------------------------------- +QSize pqElectroRotationAbstractFieldsWidget::sizeHint() const +{ + // TreeWidget sizeHintForRow is too low, correcting to +3. + int pix = (this->TreeWidget->sizeHintForRow(0) + 3) * this->NItems; + int margin[4]; + this->TreeWidget->getContentsMargins(margin, margin + 1, margin + 2, margin + 3); + int h = pix + margin[1] + margin[3]; + if (this->visibleHeader) + { + h += this->TreeWidget->header()->frameSize().height(); + } + h = std::min(300, h); + return QSize(156, h); +} + +//----------------------------------------------------------------------------- +void pqElectroRotationAbstractFieldsWidget::onItemChanged(QTreeWidgetItem* item, int column) const +{ + if (column != 0) + { + return; + } + emit fieldsChanged(); +} + +//----------------------------------------------------------------------------- +QList< QList< QVariant> > pqElectroRotationAbstractFieldsWidget::getFields() const +{ + // Put together a Field list, using ItemMap + QList< QList< QVariant> > ret; + QList< QVariant > field; + QMap::const_iterator it; + for (it = this->ItemMap.begin(); it != this->ItemMap.end(); it++) + { + field.clear(); + field.append(it.key()); + field.append(it.value()->isChecked()); + ret.append(field); + } + return ret; +} + +//----------------------------------------------------------------------------- +void pqElectroRotationAbstractFieldsWidget::setFields(QList< QList< QVariant> > fields) +{ + // Update each item checkboxes, using fields names and ItemMap + QMap::iterator it; + foreach (QList< QVariant> field, fields) + { + it = this->ItemMap.find(field[0].toString()); + if (it == this->ItemMap.end()) + { + qDebug("Found an unknow Field in pqElectroRotationAbstractFieldsWidget::setFields, ignoring"); + continue; + } + it.value()->setChecked(field[1].toBool()); + } +} + +//----------------------------------------------------------------------------- +void pqElectroRotationAbstractFieldsWidget::setFieldsDomain(QList< QList< QVariant> > fields) +{ + // Block signals so the reloading does not trigger + // UncheckPropertyModified event + this->blockSignals(true); + + // Load the tree widget + this->loadTreeWidgetItems(); + + // Set fields checkboxes + this->setFields(fields); + + // Restore signals + this->blockSignals(false); +} diff --git a/src/ElectromagnetismRotation/plugin/ParaViewPlugin/pqElectroRotationAbstractFieldsWidget.h b/src/ElectromagnetismRotation/plugin/ParaViewPlugin/pqElectroRotationAbstractFieldsWidget.h new file mode 100644 index 0000000..8496862 --- /dev/null +++ b/src/ElectromagnetismRotation/plugin/ParaViewPlugin/pqElectroRotationAbstractFieldsWidget.h @@ -0,0 +1,100 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +// Author : Anthony Geay + +#pragma once + +#include "pqPropertyWidget.h" + +class pqTreeWidget; +class pqTreeWidgetItemObject; +class QTreeWidgetItem; +class vtkSMProxy; +class vtkSMProperty; + +class pqElectroRotationAbstractFieldsWidget : public pqPropertyWidget +{ + typedef pqPropertyWidget Superclass; + Q_OBJECT + + // Description + // Property Qt used to set/get the fields with the property link + Q_PROPERTY(QList< QList< QVariant> > fields READ getFields WRITE setFields) + + // Description + // Property Qt used to set the gorup fields domain with the property link + Q_PROPERTY(QList< QList< QVariant> > fieldsDomain READ getFields WRITE setFieldsDomain) +public: + pqElectroRotationAbstractFieldsWidget( + vtkSMProxy *smproxy, vtkSMProperty *smproperty, QWidget *parentObject = 0); + virtual ~pqElectroRotationAbstractFieldsWidget(); + + // Description + // Corrected size hint, +3 pixel on each line + virtual QSize sizeHint() const; + +signals: + // Description + // Signal emited when selected field changed + void fieldsChanged() const; + +protected: + // Description + // bidrectionnal property link setter/getter + virtual QList< QList< QVariant> > getFields() const; + virtual void setFields(QList< QList< QVariant> > fields); + + // Description + // Update the domain, eg: reload + virtual void setFieldsDomain(QList< QList< QVariant> > fields); + + + // Description + // Initialize the widget items and connect it to property + // To be called by subclasses in constructor + virtual void initializeTreeWidget(vtkSMProxy *smproxy, vtkSMProperty *smproperty); + + // Description + // (Re)Load the tree widget items using recovered meta data graph + virtual void loadTreeWidgetItems() = 0; + + // Description + // Tree widget + pqTreeWidget* TreeWidget; + + // Description + // Number of items, for graphic purpose + int NItems; + + // Description + // Map of ItemPropertyName -> Item Pointer, contains only leaf. + QMap< QString, pqTreeWidgetItemObject*> ItemMap; + + // Description + // Bug in qt, header->isVisible always return false, storing this info here + // https://bugreports.qt.io/browse/QTBUG-12939 + bool visibleHeader; +protected slots: + // Description + // Slot called when the tree widget changed + virtual void onItemChanged(QTreeWidgetItem* itemOrig, int column) const; + +private: + Q_DISABLE_COPY(pqElectroRotationAbstractFieldsWidget); +}; diff --git a/src/ElectromagnetismRotation/plugin/ParaViewPlugin/pqElectroRotationGroupWidget.cxx b/src/ElectromagnetismRotation/plugin/ParaViewPlugin/pqElectroRotationGroupWidget.cxx new file mode 100644 index 0000000..0ee85e5 --- /dev/null +++ b/src/ElectromagnetismRotation/plugin/ParaViewPlugin/pqElectroRotationGroupWidget.cxx @@ -0,0 +1,162 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +// Author : Anthony Geay + +#include "pqElectroRotationGroupWidget.h" + +#include "vtkElectromagnetismRotation.h" +#include "vtkPVMetaDataInformation.h" + +#include "pqTreeWidget.h" +#include "pqTreeWidgetItemObject.h" +#include "vtkGraph.h" +#include "vtkNew.h" +#include "vtkStringArray.h" +#include "vtkDataSetAttributes.h" +#include "vtkTree.h" + +#include + +//----------------------------------------------------------------------------- +pqElectroRotationGroupWidget::pqElectroRotationGroupWidget( + vtkSMProxy *smproxy, vtkSMProperty *smproperty, QWidget *parentObject) +: Superclass(smproxy, smproperty, parentObject) +{ + this->TreeWidget->setHeaderLabel("Groups"); + this->initializeTreeWidget(smproxy, smproperty); +} + +//----------------------------------------------------------------------------- +pqElectroRotationGroupWidget::~pqElectroRotationGroupWidget() +{ +} + +//----------------------------------------------------------------------------- +void pqElectroRotationGroupWidget::loadTreeWidgetItems() +{ + // Recover Graph + vtkPVMetaDataInformation *info(vtkPVMetaDataInformation::New()); + this->proxy()->GatherInformation(info); + vtkGraph* graph = vtkGraph::SafeDownCast(info->GetInformationData()); + if(!graph) + { + return; + } + + // Clear Tree Widget + this->TreeWidget->clear(); + + // Clear Item Map + this->ItemMap.clear(); + + // Create a tree + vtkNew tree; + tree->CheckedShallowCopy(graph); + vtkStringArray* names = vtkStringArray::SafeDownCast(tree->GetVertexData()->GetAbstractArray("Names")); + + vtkIdType root = tree->GetRoot(); + vtkIdType mfg = tree->GetChild(root, 1); // MeshesFamsGrps + + vtkIdType mesh = tree->GetChild(mfg, 0); // mesh + QString meshName = QString(names->GetValue(mesh)); + + this->NItems = 0; + + vtkIdType grps = tree->GetChild(mesh, 0); // grps + pqTreeWidgetItemObject* grpsItem = new pqTreeWidgetItemObject(this->TreeWidget, QStringList()); + this->NItems++; + grpsItem->setText(0, QString("Groups of \"" + meshName + "\"")); + + vtkIdType fams = tree->GetChild(mesh, 1); // fams + std::map famDataTypeMap; + for (int i = 0; i < tree->GetNumberOfChildren(fams); i++) + { + vtkIdType fam = tree->GetChild(fams, i); + // Familly name + std::string str = names->GetValue(fam); + const char separator[]= "@@][@@"; + size_t pos = str.find(separator); + std::string name = str.substr(0, pos); + // Datatype flag + int dataTypeFlag = atoi(str.substr(pos + strlen(separator)).c_str()); + famDataTypeMap[name] = dataTypeFlag; + } + + for (int i = 0; i < tree->GetNumberOfChildren(grps); i++) + { + // Grp Item + vtkIdType grp = tree->GetChild(grps, i); + + // Datatype flag + bool hasPoint = false; + bool hasCell = false; + int dataTypeFlag = 0; + for (int j = 0; j < tree->GetNumberOfChildren(grp); j++) + { + dataTypeFlag = famDataTypeMap[names->GetValue(tree->GetChild(grp, j))]; + if (dataTypeFlag > 0) + { + hasPoint = true; + } + else if (dataTypeFlag < 0) + { + hasCell = true; + } + else + { + dataTypeFlag = 0; + break; + } + + if (hasPoint && hasCell) + { + dataTypeFlag = 0; + break; + } + } + + // + + if (dataTypeFlag<0) // if group on cells + { + pqTreeWidgetItemObject *grpItem = new pqTreeWidgetItemObject(grpsItem, QStringList()); + this->NItems++; + + // Group name + QString name = QString(names->GetValue(grp)); + grpItem->setText(0, name); + + // Property Name + QString propertyName = QString(vtkElectromagnetismRotation::GetGrpStart()) + name; + this->ItemMap[propertyName] = grpItem; + + // Checkbox + grpItem->setFlags(grpItem->flags() | Qt::ItemIsUserCheckable | Qt::ItemIsSelectable); + grpItem->setChecked(true); + + // Tooltip + grpItem->setData(0, Qt::ToolTipRole, name); + + grpItem->setData(0, Qt::DecorationRole, QPixmap(":/ParaViewResources/Icons/pqCellData16.png")); + } + } + + // Expand Widget + this->TreeWidget->expandAll(); +} diff --git a/src/ElectromagnetismRotation/plugin/ParaViewPlugin/pqElectroRotationGroupWidget.h b/src/ElectromagnetismRotation/plugin/ParaViewPlugin/pqElectroRotationGroupWidget.h new file mode 100644 index 0000000..9fe4dfc --- /dev/null +++ b/src/ElectromagnetismRotation/plugin/ParaViewPlugin/pqElectroRotationGroupWidget.h @@ -0,0 +1,46 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +// Author : Anthony Geay + +#pragma once + +#include "pqElectroRotationAbstractFieldsWidget.h" + +class vtkSMProxy; +class vtkSMProperty; + +class pqElectroRotationGroupWidget : public pqElectroRotationAbstractFieldsWidget +{ + typedef pqElectroRotationAbstractFieldsWidget Superclass; + Q_OBJECT + +public: + pqElectroRotationGroupWidget( + vtkSMProxy *smproxy, vtkSMProperty *smproperty, QWidget *parentObject = 0); + virtual ~pqElectroRotationGroupWidget(); + +protected: + // Description + // Load the tree widget using recovered meta data graph + void loadTreeWidgetItems(); + +private: + Q_DISABLE_COPY(pqElectroRotationGroupWidget); +}; + diff --git a/src/ElectromagnetismRotation/plugin/paraview.plugin b/src/ElectromagnetismRotation/plugin/paraview.plugin new file mode 100644 index 0000000..d583b66 --- /dev/null +++ b/src/ElectromagnetismRotation/plugin/paraview.plugin @@ -0,0 +1,24 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +NAME + ElectromagnetismRotation +DESCRIPTION + Rotation d une partie d un dataset +REQUIRES_MODULES diff --git a/src/ElectromagnetismStreamTraceur/CMakeLists.txt b/src/ElectromagnetismStreamTraceur/CMakeLists.txt new file mode 100644 index 0000000..9374ac2 --- /dev/null +++ b/src/ElectromagnetismStreamTraceur/CMakeLists.txt @@ -0,0 +1,49 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +cmake_minimum_required(VERSION 3.8) +project(ElectromagnetismStreamTraceur) +find_package(ParaView REQUIRED) + +include(GNUInstallDirs) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}") +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}") +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}") + +set("_paraview_plugin_default_${CMAKE_PROJECT_NAME}" ON) +paraview_plugin_scan( + ENABLE_BY_DEFAULT YES + PLUGIN_FILES "${CMAKE_CURRENT_SOURCE_DIR}/plugin/paraview.plugin" + PROVIDES_PLUGINS plugins + REQUIRES_MODULES required_modules) + +foreach(module IN LISTS required_modules) + if(NOT TARGET "${module}") + message("Missing required module: ${module}") + return() + endif() +endforeach() + +set(BUILD_SHARED_LIBS ON) +paraview_plugin_build( + RUNTIME_DESTINATION "${CMAKE_INSTALL_BINDIR}" + LIBRARY_DESTINATION "${CMAKE_INSTALL_LIBDIR}" + LIBRARY_SUBDIRECTORY "${PARAVIEW_PLUGIN_SUBDIR}" + PLUGINS ${plugins} + AUTOLOAD ${plugins}) diff --git a/src/ElectromagnetismStreamTraceur/plugin/CMakeLists.txt b/src/ElectromagnetismStreamTraceur/plugin/CMakeLists.txt new file mode 100644 index 0000000..4c90358 --- /dev/null +++ b/src/ElectromagnetismStreamTraceur/plugin/CMakeLists.txt @@ -0,0 +1,33 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# +# Create an auto-start plugin. Auto start plugins provide callbacks that get +# called when the plugin is loaded and when the application shutsdown. + +paraview_add_plugin(ElectromagnetismStreamTraceur + VERSION "1.0" + MODULES ElectromagnetismStreamTraceurFilters + MODULE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/StreamTraceurFilters/vtk.module" + SERVER_MANAGER_XML filters.xml +) + +install(TARGETS ElectromagnetismStreamTraceur + RUNTIME DESTINATION lib/paraview + LIBRARY DESTINATION lib/paraview + ARCHIVE DESTINATION lib/paraview +) diff --git a/src/ElectromagnetismStreamTraceur/plugin/StreamTraceurFilters/CMakeLists.txt b/src/ElectromagnetismStreamTraceur/plugin/StreamTraceurFilters/CMakeLists.txt new file mode 100644 index 0000000..7cac3e1 --- /dev/null +++ b/src/ElectromagnetismStreamTraceur/plugin/StreamTraceurFilters/CMakeLists.txt @@ -0,0 +1,27 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +set(classes + vtkElectromagnetismStreamTraceur +) + +vtk_module_add_module(ElectromagnetismStreamTraceurFilters + FORCE_STATIC + CLASSES ${classes} +) diff --git a/src/ElectromagnetismStreamTraceur/plugin/StreamTraceurFilters/vtk.module b/src/ElectromagnetismStreamTraceur/plugin/StreamTraceurFilters/vtk.module new file mode 100644 index 0000000..fce280f --- /dev/null +++ b/src/ElectromagnetismStreamTraceur/plugin/StreamTraceurFilters/vtk.module @@ -0,0 +1,43 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +NAME + ElectromagnetismStreamTraceurFilters +DEPENDS + VTK::CommonCore + VTK::CommonDataModel + VTK::CommonExecutionModel + VTK::FiltersCore + VTK::FiltersGeometry + VTK::FiltersModeling + VTK::FiltersSources + VTK::FiltersFlowPaths + VTK::IOCore + VTK::IOGeometry + VTK::IOXML + ParaView::VTKExtensionsFiltersGeneral + ParaView::VTKExtensionsMisc +PRIVATE_DEPENDS + VTK::CommonMisc + VTK::CommonSystem + VTK::FiltersGeneral + VTK::RenderingCore + VTK::vtksys + VTK::zlib + VTK::IOInfovis diff --git a/src/ElectromagnetismStreamTraceur/plugin/StreamTraceurFilters/vtkElectromagnetismStreamTraceur.cxx b/src/ElectromagnetismStreamTraceur/plugin/StreamTraceurFilters/vtkElectromagnetismStreamTraceur.cxx new file mode 100644 index 0000000..7618934 --- /dev/null +++ b/src/ElectromagnetismStreamTraceur/plugin/StreamTraceurFilters/vtkElectromagnetismStreamTraceur.cxx @@ -0,0 +1,221 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +#include "vtkElectromagnetismStreamTraceur.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "vtkStreamTracer.h" +#include "vtkPointSource.h" +#include "vtkPCellDataToPointData.h" + +#include "vtkCompositeDataIterator.h" +#include "vtkCompositeInterpolatedVelocityField.h" +#include "vtkCompositeDataPipeline.h" +#include "vtkInterpolatedVelocityField.h" + +#include + +vtkObjectFactoryNewMacro(vtkElectromagnetismStreamTraceur); + +vtkElectromagnetismStreamTraceur::vtkElectromagnetismStreamTraceur():IntegrationDirection(BOTH),IntegratorType(RUNGE_KUTTA45),IntegrationStepUnit(2) +,InitialIntegrationStep(0.2),TerminalSpeed(1e-12),MaximumError(1e-6),MaximumNumberOfSteps(2000) +{ + this->SetNumberOfInputPorts(2); + this->SetInputArrayToProcess(0, 0, 0, vtkDataObject::FIELD_ASSOCIATION_CELLS, vtkDataSetAttributes::VECTORS); +} + +void vtkElectromagnetismStreamTraceur::SetSourceConnection(vtkAlgorithmOutput* algOutput) +{ + this->SetInputConnection(1, algOutput); +} + +void vtkElectromagnetismStreamTraceur::SetSourceData(vtkDataSet* source) +{ + this->SetInputData(1, source); +} + +vtkDataSet* vtkElectromagnetismStreamTraceur::GetSource() +{ + if (this->GetNumberOfInputConnections(1) < 1) + { + return nullptr; + } + return vtkDataSet::SafeDownCast(this->GetExecutive()->GetInputData(1, 0)); +} + +int vtkElectromagnetismStreamTraceur::SetupOutput(vtkInformation* inInfo, vtkInformation* outInfo) +{ + int piece = outInfo->Get(vtkStreamingDemandDrivenPipeline::UPDATE_PIECE_NUMBER()); + int numPieces = outInfo->Get(vtkStreamingDemandDrivenPipeline::UPDATE_NUMBER_OF_PIECES()); + + vtkDataObject* input = inInfo->Get(vtkDataObject::DATA_OBJECT()); + vtkDataObject* output = outInfo->Get(vtkDataObject::DATA_OBJECT()); + + // Pass through field data + output->GetFieldData()->PassData(input->GetFieldData()); + + vtkCompositeDataSet* hdInput = vtkCompositeDataSet::SafeDownCast(input); + vtkDataSet* dsInput = vtkDataSet::SafeDownCast(input); + if (hdInput) + { + this->InputData = hdInput; + hdInput->Register(this); + return 1; + } + else if (dsInput) + { + vtkNew mb; + mb->SetNumberOfBlocks(numPieces); + mb->SetBlock(piece, dsInput); + this->InputData = mb; + mb->Register(this); + return 1; + } + else + { + vtkErrorMacro( + "This filter cannot handle input of type: " << (input ? input->GetClassName() : "(none)")); + return 0; + } +} + +int vtkElectromagnetismStreamTraceur::RequestData(vtkInformation* vtkNotUsed(request), vtkInformationVector** inputVector, vtkInformationVector* outputVector) +{ + vtkInformation* inInfo = inputVector[0]->GetInformationObject(0); + vtkInformation* outInfo = outputVector->GetInformationObject(0); + vtkDataArray* arr = nullptr; + vtkDataSet* input0 = nullptr; + if (!this->SetupOutput(inInfo, outInfo)) + { + return 0; + } + + vtkInformation* sourceInfo = inputVector[1]->GetInformationObject(0); + vtkDataSet* source = nullptr; + if (sourceInfo) + { + source = vtkDataSet::SafeDownCast(sourceInfo->Get(vtkDataObject::DATA_OBJECT())); + } + + vtkPolyData* output = vtkPolyData::SafeDownCast(outInfo->Get(vtkDataObject::DATA_OBJECT())); + + vtkSmartPointer iterP; + iterP.TakeReference(this->InputData->NewIterator()); + + iterP->GoToFirstItem(); + if (!iterP->IsDoneWithTraversal() && !input0) + { + input0 = vtkDataSet::SafeDownCast(iterP->GetCurrentDataObject()); + iterP->GoToNextItem(); + } + + int vecType(0); + arr = this->GetInputArrayToProcess(0, input0, vecType); + if(!arr) + { + vtkErrorMacro("No vector field selected in input !"); + return 0; + } + + vtkDataSet *input( vtkDataSet::SafeDownCast(inInfo->Get(vtkDataObject::DATA_OBJECT())) ); + const char *ArrayForGlyph(arr->GetName()); + // + vtkNew cc; + cc->SetInputData(input0); + cc->SetProcessAllArrays(1); + cc->SetPassCellData(0); + cc->SetPieceInvariant(0); + cc->Update(); + // Compute default Maximum Propagation + input0->ComputeBounds(); + double james[6]; + input0->GetBounds(james); + double dftMaxProp(std::min(std::min(james[1]-james[0],james[3]-james[2]),james[5]-james[4])); + // + vtkNew streamTracer; + streamTracer->SetInputConnection(cc->GetOutputPort()); + streamTracer->SetInterpolatorTypeToDataSetPointLocator(); + streamTracer->SetIntegrationDirection(this->IntegrationDirection); + streamTracer->SetIntegratorType(this->IntegratorType); + streamTracer->SetIntegrationStepUnit(this->IntegrationStepUnit);// 2 <=> Cell Length + streamTracer->SetInitialIntegrationStep(this->InitialIntegrationStep);//initial step length + streamTracer->SetMinimumIntegrationStep(this->MinimumIntegrationStep);//Minimum Step Length + streamTracer->SetMaximumIntegrationStep(this->MaximumIntegrationStep);//Maximum Step Length + streamTracer->SetMaximumNumberOfSteps(this->MaximumNumberOfSteps); + streamTracer->SetMaximumError(this->MaximumError); + streamTracer->SetTerminalSpeed(this->TerminalSpeed); + streamTracer->SetMaximumPropagation(dftMaxProp); + streamTracer->SetSourceConnection(this->GetInputConnection(1,0)); + streamTracer->SetInputArrayToProcess(0, 0, 0, vtkDataObject::FIELD_ASSOCIATION_POINTS, ArrayForGlyph); // idx==0 -> Vector selected + streamTracer->Update(); + output->ShallowCopy(streamTracer->GetOutput()); + // + vtkDataArray *arrToBeUsedToColor(output->GetPointData()->GetArray(ArrayForGlyph)); + vtkSmartPointer arrColor(arrToBeUsedToColor->NewInstance()); + arrColor->ShallowCopy(arrToBeUsedToColor); + arrColor->SetName(GetColorArrayName()); + int idx(output->GetPointData()->AddArray(arrColor)); + output->GetPointData()->SetActiveAttribute(idx,vtkDataSetAttributes::SCALARS); + return 1; +} + +const char vtkElectromagnetismStreamTraceur::NAME_COLOR_ARRAY[] = "Quantity To Display"; + +const char *vtkElectromagnetismStreamTraceur::GetColorArrayName() +{ + return NAME_COLOR_ARRAY; +} + +//------------------------------------------------------------------------------ +int vtkElectromagnetismStreamTraceur::FillInputPortInformation(int port, vtkInformation* info) +{ + if (port == 0) + { + info->Set(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkDataObject"); + } + else if (port == 1) + { + info->Set(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkDataSet"); + info->Set(vtkAlgorithm::INPUT_IS_OPTIONAL(), 1); + } + return 1; +} + +//------------------------------------------------------------------------------ +void vtkElectromagnetismStreamTraceur::PrintSelf(ostream& os, vtkIndent indent) +{ + this->Superclass::PrintSelf(os, indent); +} + +//------------------------------------------------------------------------------ +vtkExecutive* vtkElectromagnetismStreamTraceur::CreateDefaultExecutive() +{ + return vtkCompositeDataPipeline::New(); +} diff --git a/src/ElectromagnetismStreamTraceur/plugin/StreamTraceurFilters/vtkElectromagnetismStreamTraceur.h b/src/ElectromagnetismStreamTraceur/plugin/StreamTraceurFilters/vtkElectromagnetismStreamTraceur.h new file mode 100644 index 0000000..d59e1b0 --- /dev/null +++ b/src/ElectromagnetismStreamTraceur/plugin/StreamTraceurFilters/vtkElectromagnetismStreamTraceur.h @@ -0,0 +1,124 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +#pragma once + +#include "vtkFiltersFlowPathsModule.h" // For export macro +#include "vtkPolyDataAlgorithm.h" + +#include "vtkInitialValueProblemSolver.h" // Needed for constants + +class vtkAbstractInterpolatedVelocityField; +class vtkCompositeDataSet; +class vtkDataArray; +class vtkDataSetAttributes; +class vtkDoubleArray; +class vtkExecutive; +class vtkGenericCell; +class vtkIdList; +class vtkIntArray; +class vtkPoints; + +#include + +class VTK_EXPORT vtkElectromagnetismStreamTraceur : public vtkPolyDataAlgorithm +{ +public: + enum + { + FORWARD, + BACKWARD, + BOTH + }; + + enum Solvers + { + RUNGE_KUTTA2, + RUNGE_KUTTA4, + RUNGE_KUTTA45, + NONE, + UNKNOWN + }; + + + vtkTypeMacro(vtkElectromagnetismStreamTraceur, vtkPolyDataAlgorithm); + void PrintSelf(ostream& os, vtkIndent indent) override; + + static vtkElectromagnetismStreamTraceur* New(); + + + void SetSourceData(vtkDataSet* source); + vtkDataSet* GetSource(); + void SetSourceConnection(vtkAlgorithmOutput* algOutput); + + vtkSetClampMacro(IntegrationDirection, int, FORWARD, BOTH); + vtkGetMacro(IntegrationDirection, int); + + void SetIntegratorType(int type) { IntegratorType=type; } + + void SetIntegrationStepUnit(int unit) { IntegrationStepUnit=unit; } + + vtkSetMacro(InitialIntegrationStep, double); + vtkGetMacro(InitialIntegrationStep, double); + + vtkSetMacro(MinimumIntegrationStep, double); + vtkGetMacro(MinimumIntegrationStep, double); + + vtkSetMacro(MaximumIntegrationStep, double); + vtkGetMacro(MaximumIntegrationStep, double); + + vtkSetMacro(TerminalSpeed, double); + vtkGetMacro(TerminalSpeed, double); + + vtkSetMacro(MaximumError, double); + vtkGetMacro(MaximumError, double); + + vtkSetMacro(MaximumNumberOfSteps, vtkIdType); + vtkGetMacro(MaximumNumberOfSteps, vtkIdType); + +protected: + vtkElectromagnetismStreamTraceur(); + ~vtkElectromagnetismStreamTraceur() override = default; + // Create a default executive. + vtkExecutive* CreateDefaultExecutive() override; + static const char *GetColorArrayName(); + // hide the superclass' AddInput() from the user and the compiler + void AddInput(vtkDataObject*) { vtkErrorMacro(<< "AddInput() must be called with a vtkDataSet not a vtkDataObject."); } + + int RequestData(vtkInformation*, vtkInformationVector**, vtkInformationVector*) override; + int FillInputPortInformation(int, vtkInformation*) override; + + int SetupOutput(vtkInformation* inInfo, vtkInformation* outInfo); + + vtkCompositeDataSet* InputData; +private: + vtkElectromagnetismStreamTraceur(const vtkElectromagnetismStreamTraceur&) = delete; + void operator=(const vtkElectromagnetismStreamTraceur&) = delete; + static const char NAME_COLOR_ARRAY[]; +public: + int IntegrationDirection; + int IntegratorType; + int IntegrationStepUnit; + double InitialIntegrationStep; + double MinimumIntegrationStep; + double MaximumIntegrationStep; + double TerminalSpeed; + double MaximumError; + vtkIdType MaximumNumberOfSteps; +}; diff --git a/src/ElectromagnetismStreamTraceur/plugin/filters.xml b/src/ElectromagnetismStreamTraceur/plugin/filters.xml new file mode 100644 index 0000000..e3cbb89 --- /dev/null +++ b/src/ElectromagnetismStreamTraceur/plugin/filters.xml @@ -0,0 +1,204 @@ + + + + + + The + Stream Tracer filter generates streamlines in a vector + field from a colleces. + + + + + + + + + + + + This property specifies the input to the Stream Tracer + filter. + + + + + + + + This property contains the name of the vector array from + which to generate streamlines. + + + + + + + + + + The value of this property determines how the seeds for + the streamlines will be generated. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + This property determines in which direction(s) a + streamline is generated. + + + + + + + + This property determines which integrator (with + increasing accuracy) to use for creating streamlines. + + + + + + + This property specifies the unit for + Minimum/Initial/Maximum integration step size. The Length unit refers + to the arc length that a particle travels/advects within a single step. + The Cell Length unit represents the step size as a number of + cells. + + + + This property specifies the initial integration step + size. For non-adaptive integrators (Runge-Kutta 2 and Runge-Kutta 4), + it is fixed (always equal to this initial value) throughout the + integration. For an adaptive integrator (Runge-Kutta 4-5), the actual + step size varies such that the numerical error is less than a specified + threshold. + + + + When using the Runge-Kutta 4-5 integrator, this property + specifies the minimum integration step size. + + + + When using the Runge-Kutta 4-5 integrator, this property + specifies the maximum integration step size. + + + + This property specifies the maximum number of steps, + beyond which streamline integration is terminated. + + + + This property specifies the terminal speed, below which + particle advection/integration is terminated. + + + + This property specifies the maximum error (for + Runge-Kutta 4-5) tolerated throughout streamline integration. The + Runge-Kutta 4-5 integrator tries to adjust the step size such that the + estimated error is less than this threshold. + + + + + + + + + + diff --git a/src/ElectromagnetismStreamTraceur/plugin/paraview.plugin b/src/ElectromagnetismStreamTraceur/plugin/paraview.plugin new file mode 100644 index 0000000..019d0a5 --- /dev/null +++ b/src/ElectromagnetismStreamTraceur/plugin/paraview.plugin @@ -0,0 +1,30 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +NAME + ElectromagnetismStreamTraceur +DESCRIPTION + This plugin provides ... +REQUIRES_MODULES + VTK::CommonCore + VTK::IOCore + VTK::FiltersCore + VTK::FiltersGeneral + VTK::FiltersFlowPaths + VTK::FiltersFlowPaths \ No newline at end of file diff --git a/src/ElectromagnetismStreamTraceur/scripts/generate.py b/src/ElectromagnetismStreamTraceur/scripts/generate.py new file mode 100644 index 0000000..e340760 --- /dev/null +++ b/src/ElectromagnetismStreamTraceur/scripts/generate.py @@ -0,0 +1,43 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +from medcoupling import * + +arr = DataArrayDouble([0,1,2,3,4,5,6,7,8,9,10]) +m = MEDCouplingCMesh() +m.setCoords(arr,arr,arr) +m = m.buildUnstructured() +#m.simplexize(0) +m.changeSpaceDimension(3,0.) +m.setName("mesh") +f = MEDCouplingFieldDouble(ON_CELLS) +f.setMesh(m) +f.setName("field") +arrf = DataArrayDouble(10*10*10,3) +arrf[:,0] = 1 ; arrf[:,1] = 0 ; arrf[:,2] = 0 +f.setArray( arrf ) +f.getArray().setInfoOnComponents(["X","Y","Z"]) +f.checkConsistencyLight() +f.write("test.med") +f2 = f.deepCopy() +arrf2 = DataArrayDouble(10*10*10,3) +arrf2[:,0] = 0 ; arrf2[:,1] = 1 ; arrf2[:,2] = 0 +f2.setArray( arrf2 ) +f2.setName("field2") +WriteFieldUsingAlreadyWrittenMesh("test.med",f2) diff --git a/src/ElectromagnetismStreamTraceur/scripts/test_stream.py b/src/ElectromagnetismStreamTraceur/scripts/test_stream.py new file mode 100644 index 0000000..7d76a70 --- /dev/null +++ b/src/ElectromagnetismStreamTraceur/scripts/test_stream.py @@ -0,0 +1,37 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +from paraview.simple import * +#### disable automatic camera reset on 'Show' +paraview.simple._DisableFirstRenderCameraReset() + +def MyAssert(clue): + if not clue: + raise RuntimeError("Assertion failed !") + +testmed = MEDReader(FileName='test.med') +testmed.AllArrays = ['TS0/mesh/ComSup0/field@@][@@P0', 'TS0/mesh/ComSup0/field2@@][@@P0', 'TS0/mesh/ComSup0/mesh@@][@@P0'] +testmed.AllTimeSteps = ['0000'] +streamTraceur1 = LigneDeChamp(Input=testmed,SeedType='Point Source') +streamTraceur1.SeedType.Radius = 1 +streamTraceur1.SeedType.Center = [ 7.23,7.26,3.42 ] +streamTraceur1.Vectors = ['CELLS', "field"] #OrientationArray +streamTraceur1.UpdatePipeline() +ds0 = servermanager.Fetch(streamTraceur1) +MyAssert(ds0.GetNumberOfCells()==200) diff --git a/src/ElectromagnetismVecteur/CMakeLists.txt b/src/ElectromagnetismVecteur/CMakeLists.txt new file mode 100644 index 0000000..fc12318 --- /dev/null +++ b/src/ElectromagnetismVecteur/CMakeLists.txt @@ -0,0 +1,49 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +cmake_minimum_required(VERSION 3.8) +project(ElectromagnetismVecteur) +find_package(ParaView REQUIRED) + +include(GNUInstallDirs) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}") +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}") +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}") + +set("_paraview_plugin_default_${CMAKE_PROJECT_NAME}" ON) +paraview_plugin_scan( + ENABLE_BY_DEFAULT YES + PLUGIN_FILES "${CMAKE_CURRENT_SOURCE_DIR}/plugin/paraview.plugin" + PROVIDES_PLUGINS plugins + REQUIRES_MODULES required_modules) + +foreach(module IN LISTS required_modules) + if(NOT TARGET "${module}") + message("Missing required module: ${module}") + return() + endif() +endforeach() + +set(BUILD_SHARED_LIBS ON) +paraview_plugin_build( + RUNTIME_DESTINATION "${CMAKE_INSTALL_BINDIR}" + LIBRARY_DESTINATION "${CMAKE_INSTALL_LIBDIR}" + LIBRARY_SUBDIRECTORY "${PARAVIEW_PLUGIN_SUBDIR}" + PLUGINS ${plugins} + AUTOLOAD ${plugins}) diff --git a/src/ElectromagnetismVecteur/plugin/CMakeLists.txt b/src/ElectromagnetismVecteur/plugin/CMakeLists.txt new file mode 100644 index 0000000..76ef1e2 --- /dev/null +++ b/src/ElectromagnetismVecteur/plugin/CMakeLists.txt @@ -0,0 +1,31 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +paraview_add_plugin(ElectromagnetismVecteur + VERSION "1.0" + MODULES ElectromagnetismVecteurFilters + MODULE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/VecteurFilters/vtk.module" + SERVER_MANAGER_XML filters.xml +) + +install(TARGETS ElectromagnetismVecteur + RUNTIME DESTINATION lib/paraview + LIBRARY DESTINATION lib/paraview + ARCHIVE DESTINATION lib/paraview +) diff --git a/src/ElectromagnetismVecteur/plugin/VecteurFilters/CMakeLists.txt b/src/ElectromagnetismVecteur/plugin/VecteurFilters/CMakeLists.txt new file mode 100644 index 0000000..c6b5b87 --- /dev/null +++ b/src/ElectromagnetismVecteur/plugin/VecteurFilters/CMakeLists.txt @@ -0,0 +1,27 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +set(classes + vtkElectromagnetismVecteur +) + +vtk_module_add_module(ElectromagnetismVecteurFilters + FORCE_STATIC + CLASSES ${classes} +) diff --git a/src/ElectromagnetismVecteur/plugin/VecteurFilters/vtk.module b/src/ElectromagnetismVecteur/plugin/VecteurFilters/vtk.module new file mode 100644 index 0000000..7a18930 --- /dev/null +++ b/src/ElectromagnetismVecteur/plugin/VecteurFilters/vtk.module @@ -0,0 +1,42 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +NAME + ElectromagnetismVecteurFilters +DEPENDS + VTK::CommonCore + VTK::CommonDataModel + VTK::CommonExecutionModel + VTK::FiltersCore + VTK::FiltersGeometry + VTK::FiltersModeling + VTK::FiltersSources + VTK::IOCore + VTK::IOGeometry + VTK::IOXML + ParaView::VTKExtensionsFiltersGeneral + ParaView::VTKExtensionsMisc +PRIVATE_DEPENDS + VTK::CommonMisc + VTK::CommonSystem + VTK::FiltersGeneral + VTK::RenderingCore + VTK::vtksys + VTK::zlib + VTK::IOInfovis diff --git a/src/ElectromagnetismVecteur/plugin/VecteurFilters/vtkElectromagnetismVecteur.cxx b/src/ElectromagnetismVecteur/plugin/VecteurFilters/vtkElectromagnetismVecteur.cxx new file mode 100644 index 0000000..3590898 --- /dev/null +++ b/src/ElectromagnetismVecteur/plugin/VecteurFilters/vtkElectromagnetismVecteur.cxx @@ -0,0 +1,110 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +#include "vtkElectromagnetismVecteur.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//----------------------------------------------------------------------------- +vtkStandardNewMacro(vtkElectromagnetismVecteur); + +const char vtkElectromagnetismVecteur::NAME_COLOR_ARRAY[] = "Quantity To Display"; + +const char *vtkElectromagnetismVecteur::GetColorArrayName() +{ + return NAME_COLOR_ARRAY; +} + + vtkElectromagnetismVecteur::vtkElectromagnetismVecteur():GlyphMode(ALL_POINTS) + , MaximumNumberOfSamplePoints(5000) + , Seed(1) + , Stride(1) + { + } + +// general_filters.xml : 1670 +//----------------------------------------------------------------------------- +int vtkElectromagnetismVecteur::RequestData(vtkInformation* vtkNotUsed(request), vtkInformationVector** inputVector, vtkInformationVector* outputVector) +{ + + vtkDataArray *arr(this->GetInputArrayToProcess(1,inputVector)); + if(!arr) + { + vtkErrorMacro("No vector field selected in input !"); + return 0; + } + vtkInformation *inInfo( inputVector[0]->GetInformationObject(0) ); + vtkDataSet *input( vtkDataSet::SafeDownCast(inInfo->Get(vtkDataObject::DATA_OBJECT())) ); + const char *ArrayForGlyph(arr->GetName()); + vtkInformation *outInfo(outputVector->GetInformationObject(0)); + vtkPolyData *output(vtkPolyData::SafeDownCast(outInfo->Get(vtkDataObject::DATA_OBJECT()))); + // + vtkNew cc; + cc->SetInputData(input); + cc->Update(); + // + vtkNew glyph; + glyph->SetInputConnection(cc->GetOutputPort()); + glyph->SetGlyphMode(this->GlyphMode); // vtkPVGlyphFilter::ALL_POINTS + glyph->SetMaximumNumberOfSamplePoints(this->MaximumNumberOfSamplePoints); + glyph->SetSeed(this->Seed); + glyph->SetStride(this->Stride); + glyph->SetVectorScaleMode(0); // vtkPVGlyphFilter::SCALE_BY_MAGNITUDE + // + vtkNew arrow; + arrow->SetGlyphTypeToArrow(); + arrow->SetFilled(false); + glyph->SetSourceConnection(arrow->GetOutputPort()); + // idx,port,connection,fieldAssociation,name + glyph->SetInputArrayToProcess(0, 0, 0, vtkDataObject::FIELD_ASSOCIATION_POINTS, ""); // idx==0 -> scaleArray. "" means no scale array + glyph->SetInputArrayToProcess(1, 0, 0, vtkDataObject::FIELD_ASSOCIATION_POINTS, ArrayForGlyph); // idx==1 -> orientationArray + glyph->SetScaleFactor(this->ScaleFactor); + // + glyph->Update(); + // + output->ShallowCopy(glyph->GetOutput()); + // + vtkDataArray *arrToBeUsedToColor(output->GetPointData()->GetArray(ArrayForGlyph)); + vtkSmartPointer arrColor(arrToBeUsedToColor->NewInstance()); + arrColor->ShallowCopy(arrToBeUsedToColor); + arrColor->SetName(GetColorArrayName()); + int idx(output->GetPointData()->AddArray(arrColor)); + output->GetPointData()->SetActiveAttribute(idx,vtkDataSetAttributes::SCALARS); + return 1; +} + +//----------------------------------------------------------------------------- +void vtkElectromagnetismVecteur::PrintSelf(ostream& os, vtkIndent indent) +{ + this->Superclass::PrintSelf(os, indent); +} diff --git a/src/ElectromagnetismVecteur/plugin/VecteurFilters/vtkElectromagnetismVecteur.h b/src/ElectromagnetismVecteur/plugin/VecteurFilters/vtkElectromagnetismVecteur.h new file mode 100644 index 0000000..47f9824 --- /dev/null +++ b/src/ElectromagnetismVecteur/plugin/VecteurFilters/vtkElectromagnetismVecteur.h @@ -0,0 +1,86 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +#ifndef __vtkElectromagnetismVecteur_h__ +#define __vtkElectromagnetismVecteur_h__ + +#include + +#include + +#include +#include + +class vtkDoubleArray; +class vtkUnstructuredGrid; + +class VTK_EXPORT vtkElectromagnetismVecteur : public vtkPolyDataAlgorithm +{ +public: + enum GlyphModeType + { + ALL_POINTS, + EVERY_NTH_POINT, + SPATIALLY_UNIFORM_DISTRIBUTION, + SPATIALLY_UNIFORM_INVERSE_TRANSFORM_SAMPLING_SURFACE, + SPATIALLY_UNIFORM_INVERSE_TRANSFORM_SAMPLING_VOLUME + }; +public: + static vtkElectromagnetismVecteur* New(); + vtkTypeMacro(vtkElectromagnetismVecteur, vtkPolyDataAlgorithm); + void PrintSelf(ostream& os, vtkIndent indent) override; + + vtkGetMacro(ScaleFactor, double); + vtkSetMacro(ScaleFactor, double); + + vtkSetClampMacro(GlyphMode, int, ALL_POINTS, SPATIALLY_UNIFORM_INVERSE_TRANSFORM_SAMPLING_VOLUME); + vtkGetMacro(GlyphMode, int); + + vtkSetClampMacro(Stride, int, 1, VTK_INT_MAX); + vtkGetMacro(Stride, int); + + vtkSetMacro(Seed, int); + vtkGetMacro(Seed, int); + + vtkSetClampMacro(MaximumNumberOfSamplePoints, int, 1, VTK_INT_MAX); + vtkGetMacro(MaximumNumberOfSamplePoints, int); + +protected: + vtkElectromagnetismVecteur(); + ~vtkElectromagnetismVecteur() override = default; + + int RequestData(vtkInformation*, vtkInformationVector**, vtkInformationVector*) override; + + static const char *GetColorArrayName(); + + double ScaleFactor; + + int GlyphMode; + int MaximumNumberOfSamplePoints; + int Seed; + int Stride; + +private: + vtkElectromagnetismVecteur(const vtkElectromagnetismVecteur&) = delete; + void operator=(const vtkElectromagnetismVecteur&) = delete; + + static const char NAME_COLOR_ARRAY[]; +}; + +#endif diff --git a/src/ElectromagnetismVecteur/plugin/filters.xml b/src/ElectromagnetismVecteur/plugin/filters.xml new file mode 100644 index 0000000..9877144 --- /dev/null +++ b/src/ElectromagnetismVecteur/plugin/filters.xml @@ -0,0 +1,163 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + This property specifies the scale factor applied to the length of arrow. + + + + + + + + + + + + +This property indicates the mode that will be used to generate +glyphs from the dataset. + + + + + +This property specifies the maximum number of sample points to use +when sampling the space when Uniform Spatial Distribution is used. + + + + + + + + + + + + + + + +This property specifies the seed that will be used for generating a +uniform distribution of glyph points when a Uniform Spatial +Distribution is used. + + + + + + + + + + + + + + + + +This property specifies the stride that will be used when glyphing by +Every Nth Point. + + + + + + + + + + + + + + + +Select the input array to use for orienting the glyphs. + + + + + + This readonly property gives the name of array used to color the default active array for auto selection for Color in render view. + + + + + + + + + + diff --git a/src/ElectromagnetismVecteur/plugin/paraview.plugin b/src/ElectromagnetismVecteur/plugin/paraview.plugin new file mode 100644 index 0000000..55390f7 --- /dev/null +++ b/src/ElectromagnetismVecteur/plugin/paraview.plugin @@ -0,0 +1,27 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +NAME + ElectromagnetismVecteur +DESCRIPTION + This plugin provides ... +REQUIRES_MODULES + VTK::CommonCore + VTK::IOCore + VTK::FiltersCore diff --git a/src/ElectromagnetismVecteur/scripts/generate.py b/src/ElectromagnetismVecteur/scripts/generate.py new file mode 100644 index 0000000..bf04b7c --- /dev/null +++ b/src/ElectromagnetismVecteur/scripts/generate.py @@ -0,0 +1,39 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +from medcoupling import * + +arr = DataArrayDouble([0,1,2]) +m = MEDCouplingCMesh() +m.setCoords(arr,arr) +m = m.buildUnstructured() +#m.simplexize(0) +m.changeSpaceDimension(3,0.) +m.setName("mesh") +f = MEDCouplingFieldDouble(ON_CELLS) +f.setMesh(m) +f.setName("field") +f.setArray( DataArrayDouble([(1,1,0), (2,-2,0), (-3,3,0), (-4,-4,0)]) ) +f.getArray().setInfoOnComponents(["X","Y","Z"]) +f.checkConsistencyLight() +f.write("test.med") +f2 = f.deepCopy() +f2.setArray( DataArrayDouble([(1,0,0), (0,-2,0), (-3,0,0), (0,4,0)]) ) +f2.setName("field2") +WriteFieldUsingAlreadyWrittenMesh("test.med",f2) diff --git a/src/ExtractComponentsPlugin/CMakeLists.txt b/src/ExtractComponentsPlugin/CMakeLists.txt new file mode 100644 index 0000000..b7e553a --- /dev/null +++ b/src/ExtractComponentsPlugin/CMakeLists.txt @@ -0,0 +1,49 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +cmake_minimum_required(VERSION 3.8) +project(ExtractComponents) +find_package(ParaView REQUIRED) + +include(GNUInstallDirs) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}") +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}") +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}") + +set("_paraview_plugin_default_${CMAKE_PROJECT_NAME}" ON) +paraview_plugin_scan( + ENABLE_BY_DEFAULT YES + PLUGIN_FILES "${CMAKE_CURRENT_SOURCE_DIR}/plugin/paraview.plugin" + PROVIDES_PLUGINS plugins + REQUIRES_MODULES required_modules) + +foreach(module IN LISTS required_modules) + if(NOT TARGET "${module}") + message("Missing required module: ${module}") + return() + endif() +endforeach() + +set(BUILD_SHARED_LIBS ON) +paraview_plugin_build( + RUNTIME_DESTINATION "${CMAKE_INSTALL_BINDIR}" + LIBRARY_DESTINATION "${CMAKE_INSTALL_LIBDIR}" + LIBRARY_SUBDIRECTORY "${PARAVIEW_PLUGIN_SUBDIR}" + PLUGINS ${plugins} + AUTOLOAD ${plugins}) diff --git a/src/ExtractComponentsPlugin/ExtractCompo.med b/src/ExtractComponentsPlugin/ExtractCompo.med new file mode 100644 index 0000000000000000000000000000000000000000..48a5f4a64ebf2a345901b46b5d1af68c9a96c51a GIT binary patch literal 21390 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 literal 0 HcmV?d00001 diff --git a/src/ExtractComponentsPlugin/plugin/CMakeLists.txt b/src/ExtractComponentsPlugin/plugin/CMakeLists.txt new file mode 100644 index 0000000..02352c6 --- /dev/null +++ b/src/ExtractComponentsPlugin/plugin/CMakeLists.txt @@ -0,0 +1,49 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +set(interfaces) +set(sources) +if(PARAVIEW_USE_QT) + paraview_plugin_add_property_widget( + KIND WIDGET + TYPE LinkedLineEdit + CLASS_NAME pqLinkedLineEdit + INTERFACES interfaces + SOURCES sources + ) + list(APPEND sources + pqLinkedLineEdit.cxx + pqLinkedLineEdit.h + ) +endif() + +paraview_add_plugin(ExtractComponents + VERSION "1.0" + UI_INTERFACES ${interfaces} + SOURCES ${sources} + MODULES ExtractComponentsModule + MODULE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/ExtractComponentsModule/vtk.module" + SERVER_MANAGER_XML filters.xml +) + +install(TARGETS ExtractComponents + RUNTIME DESTINATION lib/paraview + LIBRARY DESTINATION lib/paraview + ARCHIVE DESTINATION lib/paraview +) diff --git a/src/ExtractComponentsPlugin/plugin/ExtractComponentsModule/CMakeLists.txt b/src/ExtractComponentsPlugin/plugin/ExtractComponentsModule/CMakeLists.txt new file mode 100644 index 0000000..016b7ae --- /dev/null +++ b/src/ExtractComponentsPlugin/plugin/ExtractComponentsModule/CMakeLists.txt @@ -0,0 +1,28 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +set(classes + vtkExtractComponents + vtkSMMyNumberOfComponentsDomain +) + +vtk_module_add_module(ExtractComponentsModule + FORCE_STATIC + CLASSES ${classes} +) diff --git a/src/ExtractComponentsPlugin/plugin/ExtractComponentsModule/vtk.module b/src/ExtractComponentsPlugin/plugin/ExtractComponentsModule/vtk.module new file mode 100644 index 0000000..ccec980 --- /dev/null +++ b/src/ExtractComponentsPlugin/plugin/ExtractComponentsModule/vtk.module @@ -0,0 +1,33 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +NAME + ExtractComponentsModule +DEPENDS + VTK::CommonCore + VTK::CommonDataModel + VTK::CommonExecutionModel + VTK::FiltersCore + VTK::FiltersModeling + ParaView::RemotingCore + ParaView::RemotingServerManager +PRIVATE_DEPENDS + VTK::CommonMisc + VTK::CommonSystem + VTK::FiltersGeneral diff --git a/src/ExtractComponentsPlugin/plugin/ExtractComponentsModule/vtkExtractComponents.cxx b/src/ExtractComponentsPlugin/plugin/ExtractComponentsModule/vtkExtractComponents.cxx new file mode 100644 index 0000000..b253bde --- /dev/null +++ b/src/ExtractComponentsPlugin/plugin/ExtractComponentsModule/vtkExtractComponents.cxx @@ -0,0 +1,206 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +/*========================================================================= + + Program: ParaView + Module: vtkExtractComponents.cxx + + Copyright (c) Kitware, Inc. + All rights reserved. + See Copyright.txt or http://www.paraview.org/HTML/Copyright.html for details. + + This software is distributed WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. See the above copyright notice for more information. + +=========================================================================*/ +#include "vtkExtractComponents.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef WIN32 + #define NOMINMAX + #include +#endif + +vtkStandardNewMacro(vtkExtractComponents); + +//---------------------------------------------------------------------------- +vtkExtractComponents::vtkExtractComponents() +{ +} + +//---------------------------------------------------------------------------- +vtkExtractComponents::~vtkExtractComponents() +{ + this->SetOutputArrayName(nullptr); + this->ClearComponents(); +} + +//---------------------------------------------------------------------------- +int vtkExtractComponents::FillInputPortInformation(int vtkNotUsed(port), vtkInformation *info) +{ + info->Set(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkDataSet"); + return 1; +} + +void vtkExtractComponents::SetGenerateVector(bool gvs) +{ + if (_gvs != gvs) + { + _gvs = gvs; + this->Modified(); + } +} + +//---------------------------------------------------------------------------- +int vtkExtractComponents::RequestData(vtkInformation *vtkNotUsed(request), + vtkInformationVector **inputVector, vtkInformationVector *outputVector) +{ + // get the output info object + vtkInformation *outInfo = outputVector->GetInformationObject(0); + vtkDataSet *output = vtkDataSet::SafeDownCast(outInfo->Get(vtkDataObject::DATA_OBJECT())); + + vtkInformation *inInfo = inputVector[0]->GetInformationObject(0); + vtkDataSet *input = vtkDataSet::SafeDownCast(inInfo->Get(vtkDataObject::DATA_OBJECT())); + + output->ShallowCopy(input); + + vtkDataArray *inputArray = this->GetInputArrayToProcess(0, inputVector); + vtkInformation *info = this->GetInputArrayInformation(0); + int fieldAssociation = vtkDataObject::FIELD_ASSOCIATION_POINTS; + if (info && info->Has(vtkDataObject::FIELD_ASSOCIATION())) + { + fieldAssociation = info->Get(vtkDataObject::FIELD_ASSOCIATION()); + } + + if (!inputArray) + { + vtkErrorMacro(<< "No data to extract"); + return 0; + } + + if (this->InputArrayComponents.size() == 0) + { + return 1; + } + + std::set::const_iterator it = this->InputArrayComponents.begin(); + for (; it != this->InputArrayComponents.end(); ++it) + { + if (*it >= inputArray->GetNumberOfComponents() || *it < 0) + { + vtkErrorMacro(<< "Invalid component"); + return 0; + } + } + + if (!this->OutputArrayName) + { + vtkErrorMacro(<< "No output array name"); + return 0; + } + + vtkSmartPointer outputArray = inputArray->NewInstance(); + outputArray->SetName(this->OutputArrayName); + int nbCompo(_gvs ? 3 : this->InputArrayComponents.size()); + outputArray->SetNumberOfComponents(nbCompo); + outputArray->SetNumberOfTuples(inputArray->GetNumberOfTuples()); + outputArray->CopyInformation(input->GetInformation()); + + it = this->InputArrayComponents.begin(); + int imax(-1); + for (int i = 0; it != this->InputArrayComponents.end(); ++it, i++) + { + if (!_gvs || i < 3) + { + imax = i; + outputArray->CopyComponent(i, inputArray, *it); + outputArray->SetComponentName(i, inputArray->GetComponentName(*it)); + } + } + if (_gvs) + { + for (int i = imax + 1; i < 3; i++) + { + vtkSmartPointer tmpArray = inputArray->NewInstance(); + tmpArray->SetNumberOfComponents(1); + tmpArray->SetNumberOfTuples(inputArray->GetNumberOfTuples()); + tmpArray->Fill(0.); + outputArray->CopyComponent(i, tmpArray, 0); + } + imax = 3; + } + imax = std::max(0, imax); + if (fieldAssociation == vtkDataObject::FIELD_ASSOCIATION_POINTS) + { + std::cerr << output->GetPointData()->GetNumberOfArrays() << std::endl; + int idx(output->GetPointData()->AddArray(outputArray)); + output->GetPointData()->SetActiveAttribute(idx, imax > 1 ? vtkDataSetAttributes::VECTORS : vtkDataSetAttributes::SCALARS); + } + else if (fieldAssociation == vtkDataObject::FIELD_ASSOCIATION_CELLS) + { + int idx(output->GetCellData()->AddArray(outputArray)); + output->GetCellData()->SetActiveAttribute(idx, imax > 1 ? vtkDataSetAttributes::VECTORS : vtkDataSetAttributes::SCALARS); + } + else if (fieldAssociation == vtkDataObject::FIELD_ASSOCIATION_NONE) + { + output->GetFieldData()->AddArray(outputArray); + } + + return 1; +} + +//---------------------------------------------------------------------------- +void vtkExtractComponents::PrintSelf(ostream &os, vtkIndent indent) +{ + this->Superclass::PrintSelf(os, indent); + + os << indent << "InputArrayComponents: "; + std::set::iterator it; + for (it = this->InputArrayComponents.begin(); it != this->InputArrayComponents.end(); ++it) + { + os << *it << " "; + } + os << std::endl; + + os << indent << "OutputArrayName: " << this->OutputArrayName << endl; +} + +//---------------------------------------------------------------------------- +void vtkExtractComponents::AddComponent(int component) +{ + this->InputArrayComponents.insert(component); + this->Modified(); +} + +//---------------------------------------------------------------------------- +void vtkExtractComponents::ClearComponents() +{ + this->InputArrayComponents.clear(); + this->Modified(); +} diff --git a/src/ExtractComponentsPlugin/plugin/ExtractComponentsModule/vtkExtractComponents.h b/src/ExtractComponentsPlugin/plugin/ExtractComponentsModule/vtkExtractComponents.h new file mode 100644 index 0000000..8260935 --- /dev/null +++ b/src/ExtractComponentsPlugin/plugin/ExtractComponentsModule/vtkExtractComponents.h @@ -0,0 +1,80 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +/*========================================================================= + + Program: ParaView + Module: vtkExtractComponents.h + + Copyright (c) Kitware, Inc. + All rights reserved. + See Copyright.txt or http://www.paraview.org/HTML/Copyright.html for details. + + This software is distributed WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. See the above copyright notice for more information. + +=========================================================================*/ +/** + * @class vtkExtractComponents + * @brief Extract a component of an attribute. + * + * vtkExtractComponents Extract a component of an attribute. +*/ + +#ifndef vtkExtractComponents_h +#define vtkExtractComponents_h + +#include + +#include + +class VTK_EXPORT vtkExtractComponents : public vtkDataSetAlgorithm +{ +public: + static vtkExtractComponents *New(); + vtkTypeMacro(vtkExtractComponents, vtkDataSetAlgorithm); + void PrintSelf(ostream &os, vtkIndent indent) override; + + void AddComponent(int); + void ClearComponents(); + + vtkSetStringMacro(OutputArrayName); + vtkGetStringMacro(OutputArrayName); + + void SetGenerateVector(bool gvs); + +protected: + vtkExtractComponents(); + ~vtkExtractComponents() override; + + virtual int RequestData(vtkInformation *, vtkInformationVector **, vtkInformationVector *) override; + + virtual int FillInputPortInformation(int port, vtkInformation *info) override; + + std::set InputArrayComponents; + char *OutputArrayName = nullptr; + bool _gvs = false; + +private: + vtkExtractComponents(const vtkExtractComponents &) = delete; + void operator=(const vtkExtractComponents &) = delete; +}; + +#endif diff --git a/src/ExtractComponentsPlugin/plugin/ExtractComponentsModule/vtkSMMyNumberOfComponentsDomain.cxx b/src/ExtractComponentsPlugin/plugin/ExtractComponentsModule/vtkSMMyNumberOfComponentsDomain.cxx new file mode 100644 index 0000000..c24bc34 --- /dev/null +++ b/src/ExtractComponentsPlugin/plugin/ExtractComponentsModule/vtkSMMyNumberOfComponentsDomain.cxx @@ -0,0 +1,198 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +/*========================================================================= + + Program: ParaView + Module: vtkSMMyNumberOfComponentsDomain.cxx + + Copyright (c) Kitware, Inc. + All rights reserved. + See Copyright.txt or http://www.paraview.org/HTML/Copyright.html for details. + + This software is distributed WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. See the above copyright notice for more information. + +=========================================================================*/ +#include "vtkSMMyNumberOfComponentsDomain.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +vtkStandardNewMacro(vtkSMMyNumberOfComponentsDomain); +//---------------------------------------------------------------------------- +vtkSMMyNumberOfComponentsDomain::vtkSMMyNumberOfComponentsDomain() +{ +} + +//---------------------------------------------------------------------------- +vtkSMMyNumberOfComponentsDomain::~vtkSMMyNumberOfComponentsDomain() +{ +} + +//---------------------------------------------------------------------------- +void vtkSMMyNumberOfComponentsDomain::Update(vtkSMProperty*) +{ + vtkSMProxyProperty* ip = vtkSMProxyProperty::SafeDownCast(this->GetRequiredProperty("Input")); + vtkSMStringVectorProperty* svp = + vtkSMStringVectorProperty::SafeDownCast(this->GetRequiredProperty("ArraySelection")); + if (!ip || !svp) + { + // Missing required properties. + this->RemoveAllEntries(); + this->DomainModified(); + return; + } + + /* if (svp->GetNumberOfUncheckedElements() != 5 && svp->GetNumberOfUncheckedElements() != 2 && + svp->GetNumberOfUncheckedElements() != 1) + { + // We can only handle array selection properties with 5, 2 or 1 elements. + // For 5 elements the array name is at indices [4]; for 2 + // elements it's at [1], while for 1 elements, it's at [0]. + this->RemoveAllEntries(); + return; + }*/ + + int index = svp->GetNumberOfUncheckedElements() - 1; + const char* arrayName = svp->GetUncheckedElement(index); + int arrayType = atoi(svp->GetUncheckedElement(index - 1)); + if (!arrayName || arrayName[0] == 0) + { + // No array choosen. + this->RemoveAllEntries(); + this->DomainModified(); + return; + } + + vtkSMInputArrayDomain* iad = nullptr; + vtkSMDomainIterator* di = ip->NewDomainIterator(); + di->Begin(); + while (!di->IsAtEnd()) + { + // We have to figure out whether we are working with cell data, + // point data or both. + iad = vtkSMInputArrayDomain::SafeDownCast(di->GetDomain()); + if (iad) + { + break; + } + di->Next(); + } + di->Delete(); + if (!iad) + { + // Failed to locate a vtkSMInputArrayDomain on the input property, which is + // required. + this->RemoveAllEntries(); + this->DomainModified(); + return; + } + + vtkSMInputProperty* inputProp = vtkSMInputProperty::SafeDownCast(ip); + unsigned int numProxs = ip->GetNumberOfUncheckedProxies(); + for (unsigned int i = 0; i < numProxs; i++) + { + // Use the first input + vtkSMSourceProxy* source = vtkSMSourceProxy::SafeDownCast(ip->GetUncheckedProxy(i)); + if (source) + { + this->Update(arrayName, arrayType, source, iad, + (inputProp ? inputProp->GetUncheckedOutputPortForConnection(i) : 0)); + return; + } + } +} + +//--------------------------------------------------------------------------- +void vtkSMMyNumberOfComponentsDomain::Update( + const char* arrayName, int arrayType, vtkSMSourceProxy* sp, + vtkSMInputArrayDomain* iad, int outputport) +{ + // Make sure the outputs are created. + sp->CreateOutputPorts(); + this->RemoveAllEntries(); + vtkPVDataInformation* info = sp->GetDataInformation(outputport); + if (!info) + { + this->DomainModified(); + return; + } + + vtkPVArrayInformation* ai = 0; + if (arrayType == vtkDataObject::FIELD_ASSOCIATION_POINTS) + { + ai = info->GetPointDataInformation()->GetArrayInformation(arrayName); + } + else if (arrayType == vtkDataObject::FIELD_ASSOCIATION_CELLS) + { + ai = info->GetCellDataInformation()->GetArrayInformation(arrayName); + } + else if (arrayType == vtkDataObject::FIELD_ASSOCIATION_VERTICES) + { + ai = info->GetVertexDataInformation()->GetArrayInformation(arrayName); + } + else if (arrayType == vtkDataObject::FIELD_ASSOCIATION_EDGES) + { + ai = info->GetEdgeDataInformation()->GetArrayInformation(arrayName); + } + else if (arrayType == vtkDataObject::FIELD_ASSOCIATION_ROWS) + { + ai = info->GetRowDataInformation()->GetArrayInformation(arrayName); + } + else if (arrayType == vtkDataObject::FIELD_ASSOCIATION_NONE) + { + ai = info->GetFieldDataInformation()->GetArrayInformation(arrayName); + } + + if (ai) + { + for (int i = 0; i < ai->GetNumberOfComponents(); i++) + { + const char* name = ai->GetComponentName(i); + std::ostringstream cname; + if (!name || name[0] == 0) + { + cname << i; + } + else + { + cname << name; + } + this->AddEntry(cname.str().c_str(), i); + } + } + this->DomainModified(); +} + +//---------------------------------------------------------------------------- +void vtkSMMyNumberOfComponentsDomain::PrintSelf(ostream& os, vtkIndent indent) +{ + this->Superclass::PrintSelf(os, indent); +} diff --git a/src/ExtractComponentsPlugin/plugin/ExtractComponentsModule/vtkSMMyNumberOfComponentsDomain.h b/src/ExtractComponentsPlugin/plugin/ExtractComponentsModule/vtkSMMyNumberOfComponentsDomain.h new file mode 100644 index 0000000..1b7db9b --- /dev/null +++ b/src/ExtractComponentsPlugin/plugin/ExtractComponentsModule/vtkSMMyNumberOfComponentsDomain.h @@ -0,0 +1,85 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +/*========================================================================= + + Program: ParaView + Module: vtkSMMyNumberOfComponentsDomain.h + + Copyright (c) Kitware, Inc. + All rights reserved. + See Copyright.txt or http://www.paraview.org/HTML/Copyright.html for details. + + This software is distributed WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. See the above copyright notice for more information. + +=========================================================================*/ +/** + * @class vtkSMMyNumberOfComponentsDomain + * @brief int range domain based on the number of + * components available in a particular data array. + * + * vtkSMMyNumberOfComponentsDomain is used for properties that allow the user to + * choose the component number (or associated name) to process for the choosen array. + * It needs two required properties with following functions: + * * Input -- input property for the filter. + * * ArraySelection -- string vector property used to select the array. + * This domain will not work if either of the required properties is missing. +*/ + +#ifndef vtkSMMyNumberOfComponentsDomain_h +#define vtkSMMyNumberOfComponentsDomain_h + +#include + +class vtkSMSourceProxy; +class vtkSMInputArrayDomain; + +class VTK_EXPORT vtkSMMyNumberOfComponentsDomain : public vtkSMEnumerationDomain +{ +public: + static vtkSMMyNumberOfComponentsDomain* New(); + vtkTypeMacro(vtkSMMyNumberOfComponentsDomain, vtkSMEnumerationDomain); + void PrintSelf(ostream& os, vtkIndent indent) override; + + /** + * Updates the range based on the scalar range of the currently selected + * array. This requires Input (vtkSMProxyProperty) and ArraySelection + * (vtkSMStringVectorProperty) properties. Currently, this uses + * only the first component of the array. + */ + virtual void Update(vtkSMProperty* prop); + +protected: + vtkSMMyNumberOfComponentsDomain(); + ~vtkSMMyNumberOfComponentsDomain() override; + + /** + * Internal update method doing the actual work. + */ + void Update( + const char* arrayname, int arrayType, vtkSMSourceProxy* sp, vtkSMInputArrayDomain* iad, int outputport); + +private: + vtkSMMyNumberOfComponentsDomain(const vtkSMMyNumberOfComponentsDomain&) = delete; + void operator=(const vtkSMMyNumberOfComponentsDomain&) = delete; +}; + +#endif diff --git a/src/ExtractComponentsPlugin/plugin/filters.xml b/src/ExtractComponentsPlugin/plugin/filters.xml new file mode 100644 index 0000000..bfe85bc --- /dev/null +++ b/src/ExtractComponentsPlugin/plugin/filters.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + This property specifies the input of the Extract Component filter. + + + + + + + + + + This property indicates the name of the array to be extracted. + + + + + This property indicates the name of the output scalar array. + + + + + + + + + + + This property indicates the components of the array to be extracted. + + + + + Specify if output array is clamped/ to 3 components array in order to be connected downstream to WrapByVector filter for example. Disabled by default. + + + + + + diff --git a/src/ExtractComponentsPlugin/plugin/paraview.plugin b/src/ExtractComponentsPlugin/plugin/paraview.plugin new file mode 100644 index 0000000..3e98e9d --- /dev/null +++ b/src/ExtractComponentsPlugin/plugin/paraview.plugin @@ -0,0 +1,29 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +NAME + ExtractComponents +DESCRIPTION + This plugin provides the ExtractComponents filter. +REQUIRES_MODULES + VTK::CommonCore + VTK::IOCore + VTK::FiltersCore + ParaView::RemotingCore + ParaView::RemotingServerManager diff --git a/src/ExtractComponentsPlugin/plugin/pqLinkedLineEdit.cxx b/src/ExtractComponentsPlugin/plugin/pqLinkedLineEdit.cxx new file mode 100644 index 0000000..0e564fb --- /dev/null +++ b/src/ExtractComponentsPlugin/plugin/pqLinkedLineEdit.cxx @@ -0,0 +1,151 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +/*========================================================================= + + Program: ParaView + Module: $RCSfile$ + + Copyright (c) 2005,2006 Sandia Corporation, Kitware Inc. + All rights reserved. + + ParaView is a free software; you can redistribute it and/or modify it + under the terms of the ParaView license version 1.2. + + See License_v1.2.txt for the full ParaView license. + A copy of this license can be obtained by contacting + Kitware Inc. + 28 Corporate Drive + Clifton Park, NY 12065 + USA + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +========================================================================*/ +#include "pqLinkedLineEdit.h" + +#include +#include +#include + +#include +#include + +#include +#include + +#include + +//----------------------------------------------------------------------------- +pqLinkedLineEdit::pqLinkedLineEdit( + vtkSMProxy* smproxy, vtkSMProperty* smproperty, QWidget* parentObject) + : Superclass(smproxy, parentObject) +{ + this->setProperty(smproperty); + this->setShowLabel(false); + this->setChangeAvailableAsChangeFinished(true); + + // Creating the QLineEdit + this->LineEdit = new pqLineEdit(this); + this->LineEdit->setObjectName(smproxy->GetPropertyName(smproperty)); + this->addPropertyLink(this->LineEdit, "text", SIGNAL(textChanged(const QString&)), smproperty); + this->connect( + this->LineEdit, SIGNAL(textChangedAndEditingFinished()), this, SIGNAL(changeFinished())); + + // Creating the QLabel + QLabel* label = new QLabel(this); + label->setObjectName(QString("_labelFor") + smproxy->GetPropertyName(smproperty)); + label->setText(smproperty->GetXMLLabel()); + label->setWordWrap(true); + + QHBoxLayout* hbox = new QHBoxLayout(this); + hbox->setMargin(0); + hbox->setSpacing(0); + + // Adding everything to the layout + hbox->addWidget(label); + hbox->addWidget(this->LineEdit); + this->setLayout(hbox); + + this->PreviousArrayName = ""; + + // Listen for changes of the input array property + this->Connect = vtkEventQtSlotConnect::New(); + this->Connect->Connect( + smproxy->GetProperty("SelectInputArray"), vtkCommand::UncheckedPropertyModifiedEvent, + this, SLOT(onInputArrayModified())); + + vtkSMPropertyHelper helper(this->property(), true); + helper.SetUseUnchecked(true); + QString arrayName = QString::fromLocal8Bit(helper.GetAsString()); + if (arrayName == "") + { + // If property is not initialized yet (eg. when loading a PVSM), + // set its value to the input array name + this->onInputArrayModified(); + } +} + +//----------------------------------------------------------------------------- +pqLinkedLineEdit::~pqLinkedLineEdit() +{ + this->Connect->Delete(); +} + +//----------------------------------------------------------------------------- +void pqLinkedLineEdit::updateWidget(bool showing_advanced_properties) +{ + // The property changed, let's rebuild the UI + // TODO: this code seems useless after all + vtkSMPropertyHelper helper(this->property(), true); + helper.SetUseUnchecked(true); + QString arrayName = QString::fromLocal8Bit(helper.GetAsString()); + if (this->LineEdit->text() != arrayName) + { + this->LineEdit->setText(arrayName); + emit changeAvailable(); + } +} + +//----------------------------------------------------------------------------- +void pqLinkedLineEdit::onInputArrayModified() +{ + // Input array property changed, let's update our line edit if property value + // has not yet been considered. + vtkSMPropertyHelper helper(this->proxy(), "SelectInputArray", true); + helper.SetUseUnchecked(true); + QString arrayName = QString::fromLocal8Bit(helper.GetAsString(4)); + if (arrayName != this->PreviousArrayName) + { + this->LineEdit->setText(arrayName); + this->PreviousArrayName = arrayName; + + emit changeAvailable(); + } +} diff --git a/src/ExtractComponentsPlugin/plugin/pqLinkedLineEdit.h b/src/ExtractComponentsPlugin/plugin/pqLinkedLineEdit.h new file mode 100644 index 0000000..c936f87 --- /dev/null +++ b/src/ExtractComponentsPlugin/plugin/pqLinkedLineEdit.h @@ -0,0 +1,87 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +/*========================================================================= + + Program: ParaView + Module: $RCSfile$ + + Copyright (c) 2005,2006 Sandia Corporation, Kitware Inc. + All rights reserved. + + ParaView is a free software; you can redistribute it and/or modify it + under the terms of the ParaView license version 1.2. + + See License_v1.2.txt for the full ParaView license. + A copy of this license can be obtained by contacting + Kitware Inc. + 28 Corporate Drive + Clifton Park, NY 12065 + USA + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +========================================================================*/ +#ifndef pqLinkedLineEdit_h +#define pqLinkedLineEdit_h + +#include + +class QLineEdit; +class vtkEventQtSlotConnect; + +class pqLinkedLineEdit : public pqPropertyWidget +{ + Q_OBJECT + typedef pqPropertyWidget Superclass; + +public: + pqLinkedLineEdit( + vtkSMProxy* smproxy, vtkSMProperty* smproperty, QWidget* parentObject = nullptr); + virtual ~pqLinkedLineEdit(); + + void updateWidget(bool showing_advanced_properties = false); + +public slots: + void onInputArrayModified(); + +signals: + void textChanged(const QString&); + void inputArrayModified(); + +protected: + QLineEdit* LineEdit; + QString PreviousArrayName; + vtkEventQtSlotConnect* Connect; + +private: + Q_DISABLE_COPY(pqLinkedLineEdit) +}; + +#endif diff --git a/src/ExtractThreeD/CMakeLists.txt b/src/ExtractThreeD/CMakeLists.txt new file mode 100644 index 0000000..28a4565 --- /dev/null +++ b/src/ExtractThreeD/CMakeLists.txt @@ -0,0 +1,49 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +cmake_minimum_required(VERSION 3.8) +project(ExtractThreeDimPlugin) +find_package(ParaView REQUIRED) + +include(GNUInstallDirs) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}") +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}") +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}") + +set("_paraview_plugin_default_${CMAKE_PROJECT_NAME}" ON) +paraview_plugin_scan( + ENABLE_BY_DEFAULT YES + PLUGIN_FILES "${CMAKE_CURRENT_SOURCE_DIR}/plugin/paraview.plugin" + PROVIDES_PLUGINS plugins + REQUIRES_MODULES required_modules) + +foreach(module IN LISTS required_modules) + if(NOT TARGET "${module}") + message("Missing required module: ${module}") + return() + endif() +endforeach() + +set(BUILD_SHARED_LIBS ON) +paraview_plugin_build( + RUNTIME_DESTINATION "${CMAKE_INSTALL_BINDIR}" + LIBRARY_DESTINATION "${CMAKE_INSTALL_LIBDIR}" + LIBRARY_SUBDIRECTORY "${PARAVIEW_PLUGIN_SUBDIR}" + PLUGINS ${plugins} + AUTOLOAD ${plugins}) diff --git a/src/ExtractThreeD/plugin/CMakeLists.txt b/src/ExtractThreeD/plugin/CMakeLists.txt new file mode 100644 index 0000000..443eb1b --- /dev/null +++ b/src/ExtractThreeD/plugin/CMakeLists.txt @@ -0,0 +1,65 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +CMAKE_POLICY(SET CMP0071 NEW) +SET(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) +SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib") + +# Common CMake macros +# =================== +SET(TMP_CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH}) +unset(CMAKE_MODULE_PATH) +SET(CONFIGURATION_ROOT_DIR $ENV{CONFIGURATION_ROOT_DIR} CACHE PATH "Path to the Salome CMake configuration files") +IF(EXISTS ${CONFIGURATION_ROOT_DIR}) + LIST(APPEND CMAKE_MODULE_PATH "${CONFIGURATION_ROOT_DIR}/cmake") + INCLUDE(SalomeMacros) +ELSE() + MESSAGE(FATAL_ERROR "We absolutely need the Salome CMake configuration files, please define CONFIGURATION_ROOT_DIR !") +ENDIF() + +SET(MEDCOUPLING_ROOT_DIR $ENV{MEDCOUPLING_ROOT_DIR} CACHE PATH "Path to the MEDCoupling tool") +IF(EXISTS ${MEDCOUPLING_ROOT_DIR}) + LIST(APPEND CMAKE_MODULE_PATH "${MEDCOUPLING_ROOT_DIR}/cmake_files") +ENDIF() +LIST(APPEND CMAKE_MODULE_PATH "${CMAKE_ROOT}/Modules") +LIST(APPEND CMAKE_MODULE_PATH ${TMP_CMAKE_MODULE_PATH}) + +INCLUDE(SalomeSetupPlatform) +SET(BUILD_SHARED_LIBS TRUE) + +FIND_PACKAGE(SalomeHDF5 REQUIRED) +FIND_PACKAGE(SalomeMEDCoupling REQUIRED) + +SALOME_ACCUMULATE_ENVIRONMENT(PYTHONPATH NOCHECK ${CMAKE_INSTALL_PREFIX}/${SALOME_INSTALL_BINS} + ${CMAKE_INSTALL_PREFIX}/${SALOME_INSTALL_PYTHON}) +SALOME_ACCUMULATE_ENVIRONMENT(LD_LIBRARY_PATH NOCHECK ${CMAKE_INSTALL_PREFIX}/${SALOME_INSTALL_LIBS}) +SALOME_ACCUMULATE_ENVIRONMENT(PV_PLUGIN_PATH NOCHECK ${CMAKE_INSTALL_PREFIX}/lib/paraview) + +paraview_add_plugin(ExtractThreeDimPlugin + VERSION "1.0" + MODULES ExtractThreeDModule + MODULE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/ExtractThreeDModule/vtk.module" + SERVER_MANAGER_XML filters.xml +) + +install(TARGETS ExtractThreeDimPlugin + RUNTIME DESTINATION lib/paraview + LIBRARY DESTINATION lib/paraview + ARCHIVE DESTINATION lib/paraview +) diff --git a/src/ExtractThreeD/plugin/ExtractThreeDModule/CMakeLists.txt b/src/ExtractThreeD/plugin/ExtractThreeDModule/CMakeLists.txt new file mode 100644 index 0000000..acd980d --- /dev/null +++ b/src/ExtractThreeD/plugin/ExtractThreeDModule/CMakeLists.txt @@ -0,0 +1,35 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +set(classes + vtkExtractThreeD +) + +vtk_module_add_module(ExtractThreeDModule + FORCE_STATIC + CLASSES ${classes} +) + +target_include_directories(ExtractThreeDModule PRIVATE ${MEDCOUPLING_INCLUDE_DIRS}) + +if(HDF5_IS_PARALLEL) + target_link_libraries(ExtractThreeDModule PRIVATE ${MEDCoupling_paramedloader}) +else() + target_link_libraries(ExtractThreeDModule PRIVATE ${MEDCoupling_medloader}) +endif() diff --git a/src/ExtractThreeD/plugin/ExtractThreeDModule/vtk.module b/src/ExtractThreeD/plugin/ExtractThreeDModule/vtk.module new file mode 100644 index 0000000..827952d --- /dev/null +++ b/src/ExtractThreeD/plugin/ExtractThreeDModule/vtk.module @@ -0,0 +1,37 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +NAME + ExtractThreeDModule +DEPENDS + VTK::CommonCore + VTK::CommonDataModel + VTK::CommonExecutionModel + VTK::FiltersCore + VTK::FiltersModeling + VTK::IOCore + VTK::IOGeometry + VTK::IOXML +PRIVATE_DEPENDS + VTK::CommonMisc + VTK::CommonSystem + VTK::FiltersGeneral + VTK::RenderingCore + VTK::vtksys + VTK::zlib diff --git a/src/ExtractThreeD/plugin/ExtractThreeDModule/vtkExtractThreeD.cxx b/src/ExtractThreeD/plugin/ExtractThreeDModule/vtkExtractThreeD.cxx new file mode 100644 index 0000000..208c6f3 --- /dev/null +++ b/src/ExtractThreeD/plugin/ExtractThreeDModule/vtkExtractThreeD.cxx @@ -0,0 +1,231 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +// Author : Anthony Geay + +#include "vtkExtractThreeD.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +vtkStandardNewMacro(vtkExtractThreeD); + +/////////////////// + +class vtkExtractThreeD::vtkExtractThreeDInternal +{ +public: + vtkExtractThreeDInternal() {} + std::vector getIdsToKeep() const { return _types; } + void setIdsToKeep(const std::vector &types) { _types = types; } + +private: + std::vector _types; +}; + +vtkExtractThreeD::vtkExtractThreeD() : Internal(new vtkExtractThreeDInternal) +{ +} + +vtkExtractThreeD::~vtkExtractThreeD() +{ + delete this->Internal; +} + +int vtkExtractThreeD::RequestInformation(vtkInformation *request, vtkInformationVector **inputVector, vtkInformationVector *outputVector) +{ + try + { + //std::cerr << "########################################## vtkExtractThreeD::RequestInformation ##########################################" << std::endl; + vtkInformation *outInfo(outputVector->GetInformationObject(0)); + vtkInformation *inputInfo(inputVector[0]->GetInformationObject(0)); + vtkDataSet *input(0); + { + vtkDataObject *inp(inputInfo->Get(vtkDataObject::DATA_OBJECT())); + if (vtkDataSet::SafeDownCast(inp)) + input = vtkDataSet::SafeDownCast(inp); + else + { + vtkMultiBlockDataSet *inputTmp(vtkMultiBlockDataSet::SafeDownCast(inp)); + if (inputTmp) + { + if (inputTmp->GetNumberOfBlocks() != 1) + { + vtkDebugMacro("vtkExtractThreeD::RequestInformation : input vtkMultiBlockDataSet must contain exactly 1 block !"); + return 0; + } + vtkDataSet *blk0(vtkDataSet::SafeDownCast(inputTmp->GetBlock(0))); + if (!blk0) + { + vtkDebugMacro("vtkExtractThreeD::RequestInformation : the single block in input vtkMultiBlockDataSet must be a vtkDataSet instance !"); + return 0; + } + input = blk0; + } + else + { + vtkDebugMacro("vtkExtractThreeD::RequestInformation : supported input are vtkDataSet or vtkMultiBlockDataSet !"); + return 0; + } + } + } + { + vtkIdType nbOfCells(input->GetNumberOfCells()); + std::map m; + std::set typesToKeep; + for (vtkIdType cellId = 0; cellId < nbOfCells; cellId++) + { + int vtkCt(input->GetCellType(cellId)); + const std::map::const_iterator it(m.find(vtkCt)); + if (it == m.end()) + { + const unsigned char *pos(std::find(MEDCoupling::MEDMeshMultiLev::PARAMEDMEM_2_VTKTYPE, MEDCoupling::MEDMeshMultiLev::PARAMEDMEM_2_VTKTYPE + MEDCoupling::MEDMeshMultiLev::PARAMEDMEM_2_VTKTYPE_LGTH, vtkCt)); + if (pos == MEDCoupling::MEDMeshMultiLev::PARAMEDMEM_2_VTKTYPE + MEDCoupling::MEDMeshMultiLev::PARAMEDMEM_2_VTKTYPE_LGTH) + { + vtkDebugMacro("vtkExtractThreeD::RequestInformation : cell #" << cellId << " has unrecognized type !"); + return 0; + } + INTERP_KERNEL::NormalizedCellType mcCtype((INTERP_KERNEL::NormalizedCellType)std::distance(MEDCoupling::MEDMeshMultiLev::PARAMEDMEM_2_VTKTYPE, pos)); + const INTERP_KERNEL::CellModel &cm(INTERP_KERNEL::CellModel::GetCellModel(mcCtype)); + if (cm.getDimension() == 3) + typesToKeep.insert(vtkCt); + } + } + std::vector typesToKeep2(typesToKeep.begin(), typesToKeep.end()); + this->Internal->setIdsToKeep(typesToKeep2); + } + } + catch (INTERP_KERNEL::Exception &e) + { + std::cerr << "Exception has been thrown in vtkExtractThreeD::RequestInformation : " << e.what() << std::endl; + return 0; + } + return 1; +} + +vtkDataSet *FilterFamilies(vtkDataSet *input, const std::vector &idsToKeep) +{ + const int VTK_DATA_ARRAY_DELETE = vtkDataArrayTemplate::VTK_DATA_ARRAY_DELETE; + const char ZE_SELECTION_ARR_NAME[] = "@@ZeSelection@@"; + vtkDataSet *output(input->NewInstance()); + output->ShallowCopy(input); + vtkSmartPointer thres(vtkSmartPointer::New()); + thres->SetInputData(output); + vtkDataSetAttributes *dscIn(input->GetCellData()), *dscIn2(input->GetPointData()); + vtkDataSetAttributes *dscOut(output->GetCellData()), *dscOut2(output->GetPointData()); + // + double vMin(1.), vMax(2.); + thres->ThresholdBetween(vMin, vMax); + // OK for the output + vtkIdType nbOfCells(input->GetNumberOfCells()); + vtkCharArray *zeSelection(vtkCharArray::New()); + zeSelection->SetName(ZE_SELECTION_ARR_NAME); + zeSelection->SetNumberOfComponents(1); + char *pt(new char[nbOfCells]); + zeSelection->SetArray(pt, nbOfCells, 0, VTK_DATA_ARRAY_DELETE); + std::fill(pt, pt + nbOfCells, 0); + std::vector pt2(nbOfCells, false); + for (std::vector::const_iterator it = idsToKeep.begin(); it != idsToKeep.end(); it++) + { + for (vtkIdType ii = 0; ii < nbOfCells; ii++) + { + if (input->GetCellType(ii) == *it) + pt2[ii] = true; + } + } + for (int ii = 0; ii < nbOfCells; ii++) + if (pt2[ii]) + pt[ii] = 2; + int idx(output->GetCellData()->AddArray(zeSelection)); + output->GetCellData()->SetActiveAttribute(idx, vtkDataSetAttributes::SCALARS); + output->GetCellData()->CopyScalarsOff(); + zeSelection->Delete(); + // + thres->SetInputArrayToProcess(idx, 0, 0, "vtkDataObject::FIELD_ASSOCIATION_CELLS", ZE_SELECTION_ARR_NAME); + thres->Update(); + vtkUnstructuredGrid *zeComputedOutput(thres->GetOutput()); + zeComputedOutput->GetCellData()->RemoveArray(idx); + output->Delete(); + zeComputedOutput->Register(0); + return zeComputedOutput; +} + +int vtkExtractThreeD::RequestData(vtkInformation *request, vtkInformationVector **inputVector, vtkInformationVector *outputVector) +{ + try + { + //std::cerr << "########################################## vtkExtractThreeD::RequestData ##########################################" << std::endl; + vtkInformation *inputInfo = inputVector[0]->GetInformationObject(0); + vtkDataSet *input(vtkDataSet::SafeDownCast(inputInfo->Get(vtkDataObject::DATA_OBJECT()))); + vtkInformation *info(input->GetInformation()); + vtkInformation *outInfo(outputVector->GetInformationObject(0)); + vtkDataSet *output(vtkDataSet::SafeDownCast(outInfo->Get(vtkDataObject::DATA_OBJECT()))); + std::vector idsToKeep(this->Internal->getIdsToKeep()); + vtkDataSet *tryOnCell(FilterFamilies(input, idsToKeep)); + // first shrink the input + output->ShallowCopy(tryOnCell); + tryOnCell->Delete(); + } + catch (INTERP_KERNEL::Exception &e) + { + std::cerr << "Exception has been thrown in vtkExtractThreeD::RequestData : " << e.what() << std::endl; + return 0; + } + return 1; +} + +void vtkExtractThreeD::PrintSelf(ostream &os, vtkIndent indent) +{ + this->Superclass::PrintSelf(os, indent); +} diff --git a/src/ExtractThreeD/plugin/ExtractThreeDModule/vtkExtractThreeD.h b/src/ExtractThreeD/plugin/ExtractThreeDModule/vtkExtractThreeD.h new file mode 100644 index 0000000..ed442d8 --- /dev/null +++ b/src/ExtractThreeD/plugin/ExtractThreeDModule/vtkExtractThreeD.h @@ -0,0 +1,53 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +// Author : Anthony Geay + +#ifndef vtkExtractThreeD_h__ +#define vtkExtractThreeD_h__ + +#include + +class vtkMutableDirectedGraph; + +class VTK_EXPORT vtkExtractThreeD : public vtkDataSetAlgorithm +{ +public: + static vtkExtractThreeD *New(); + vtkTypeMacro(vtkExtractThreeD, vtkDataSetAlgorithm); + void PrintSelf(ostream &os, vtkIndent indent) override; + +protected: + vtkExtractThreeD(); + ~vtkExtractThreeD(); + + int RequestInformation(vtkInformation *request, + vtkInformationVector **inputVector, vtkInformationVector *outputVector) override; + + int RequestData(vtkInformation *request, vtkInformationVector **inputVector, + vtkInformationVector *outputVector) override; + + class vtkExtractThreeDInternal; + vtkExtractThreeDInternal *Internal; + +private: + vtkExtractThreeD(const vtkExtractThreeD &) = delete; + void operator=(const vtkExtractThreeD &) = delete; +}; + +#endif diff --git a/src/ExtractThreeD/plugin/filters.xml b/src/ExtractThreeD/plugin/filters.xml new file mode 100644 index 0000000..0b7f791 --- /dev/null +++ b/src/ExtractThreeD/plugin/filters.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + This property specifies the input to the Level Scalars filter. + + + + + diff --git a/src/ExtractThreeD/plugin/paraview.plugin b/src/ExtractThreeD/plugin/paraview.plugin new file mode 100644 index 0000000..d0d9f3a --- /dev/null +++ b/src/ExtractThreeD/plugin/paraview.plugin @@ -0,0 +1,27 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +NAME + ExtractThreeDimPlugin +DESCRIPTION + This plugin provides the ExtractThreeDim filter. +REQUIRES_MODULES + VTK::CommonCore + VTK::IOCore + VTK::FiltersCore diff --git a/src/GlyphCIH/CMakeLists.txt b/src/GlyphCIH/CMakeLists.txt new file mode 100644 index 0000000..67bf2c7 --- /dev/null +++ b/src/GlyphCIH/CMakeLists.txt @@ -0,0 +1,49 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +cmake_minimum_required(VERSION 3.8) +project(GlyphCIH) +find_package(ParaView REQUIRED) + +include(GNUInstallDirs) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}") +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}") +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}") + +set("_paraview_plugin_default_${CMAKE_PROJECT_NAME}" ON) +paraview_plugin_scan( + ENABLE_BY_DEFAULT YES + PLUGIN_FILES "${CMAKE_CURRENT_SOURCE_DIR}/plugin/paraview.plugin" + PROVIDES_PLUGINS plugins + REQUIRES_MODULES required_modules) + +foreach(module IN LISTS required_modules) + if(NOT TARGET "${module}") + message("Missing required module: ${module}") + return() + endif() +endforeach() + +set(BUILD_SHARED_LIBS ON) +paraview_plugin_build( + RUNTIME_DESTINATION "${CMAKE_INSTALL_BINDIR}" + LIBRARY_DESTINATION "${CMAKE_INSTALL_LIBDIR}" + LIBRARY_SUBDIRECTORY "${PARAVIEW_PLUGIN_SUBDIR}" + PLUGINS ${plugins} + AUTOLOAD ${plugins}) diff --git a/src/GlyphCIH/plugin/CMakeLists.txt b/src/GlyphCIH/plugin/CMakeLists.txt new file mode 100644 index 0000000..2f52875 --- /dev/null +++ b/src/GlyphCIH/plugin/CMakeLists.txt @@ -0,0 +1,31 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +paraview_add_plugin(GlyphCIH + VERSION "1.0" + MODULES GlyphCIHFilters + MODULE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/GlyphCIHFilters/vtk.module" + SERVER_MANAGER_XML filters.xml +) + +install(TARGETS GlyphCIH + RUNTIME DESTINATION lib/paraview + LIBRARY DESTINATION lib/paraview + ARCHIVE DESTINATION lib/paraview +) diff --git a/src/GlyphCIH/plugin/GlyphCIHFilters/CMakeLists.txt b/src/GlyphCIH/plugin/GlyphCIHFilters/CMakeLists.txt new file mode 100644 index 0000000..06cf636 --- /dev/null +++ b/src/GlyphCIH/plugin/GlyphCIHFilters/CMakeLists.txt @@ -0,0 +1,27 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +set(classes + vtkGlyphCIH +) + +vtk_module_add_module(GlyphCIHFilters + FORCE_STATIC + CLASSES ${classes} +) diff --git a/src/GlyphCIH/plugin/GlyphCIHFilters/vtk.module b/src/GlyphCIH/plugin/GlyphCIHFilters/vtk.module new file mode 100644 index 0000000..bf9c08f --- /dev/null +++ b/src/GlyphCIH/plugin/GlyphCIHFilters/vtk.module @@ -0,0 +1,43 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +NAME + GlyphCIHFilters +DEPENDS + VTK::CommonCore + VTK::CommonDataModel + VTK::CommonExecutionModel + VTK::FiltersCore + VTK::FiltersGeometry + VTK::FiltersModeling + VTK::FiltersSources + VTK::IOCore + VTK::IOGeometry + VTK::IOXML + VTK::FiltersVerdict + ParaView::VTKExtensionsFiltersGeneral + ParaView::VTKExtensionsMisc +PRIVATE_DEPENDS + VTK::CommonMisc + VTK::CommonSystem + VTK::FiltersGeneral + VTK::RenderingCore + VTK::vtksys + VTK::zlib + VTK::IOInfovis diff --git a/src/GlyphCIH/plugin/GlyphCIHFilters/vtkGlyphCIH.cxx b/src/GlyphCIH/plugin/GlyphCIHFilters/vtkGlyphCIH.cxx new file mode 100644 index 0000000..5a9b025 --- /dev/null +++ b/src/GlyphCIH/plugin/GlyphCIHFilters/vtkGlyphCIH.cxx @@ -0,0 +1,379 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +#include "vtkGlyphCIH.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +class MyGlyphException : public std::exception +{ +public: + MyGlyphException(const std::string& s):_reason(s) { } + virtual const char *what() const throw() { return _reason.c_str(); } + virtual ~MyGlyphException() throw() { } +private: + std::string _reason; +}; + +//----------------------------------------------------------------------------- +void vtkGlyphCIH::ExtractInfo( + vtkInformationVector* inputVector, vtkSmartPointer& usgIn) +{ + vtkInformation* inputInfo(inputVector->GetInformationObject(0)); + vtkDataSet* input(0); + vtkDataSet* input0(vtkDataSet::SafeDownCast(inputInfo->Get(vtkDataObject::DATA_OBJECT()))); + vtkMultiBlockDataSet* input1( + vtkMultiBlockDataSet::SafeDownCast(inputInfo->Get(vtkDataObject::DATA_OBJECT()))); + if (input0) + { + input = input0; + } + else + { + if (!input1) + { + vtkErrorMacro("Input dataSet must be a DataSet or single elt multi block dataset expected !"); + return; + } + if (input1->GetNumberOfBlocks() != 1) + { + vtkErrorMacro("Input dataSet is a multiblock dataset with not exactly one block ! Use " + "MergeBlocks or ExtractBlocks filter before calling this filter !"); + return; + } + vtkDataObject* input2(input1->GetBlock(0)); + if (!input2) + { + vtkErrorMacro("Input dataSet is a multiblock dataset with exactly one block but this single " + "element is NULL !"); + return; + } + vtkDataSet* input2c(vtkDataSet::SafeDownCast(input2)); + if (!input2c) + { + vtkErrorMacro( + "Input dataSet is a multiblock dataset with exactly one block but this single element is " + "not a dataset ! Use MergeBlocks or ExtractBlocks filter before calling this filter !"); + return; + } + input = input2c; + } + + if (!input) + { + vtkErrorMacro("Input data set is NULL !"); + return; + } + + usgIn = vtkUnstructuredGrid::SafeDownCast(input); + if (!usgIn) + { + if (!input1) + { + vtkNew mb; + vtkNew cd; + mb->AddInputData(input); + cd->SetInputConnection(mb->GetOutputPort()); + cd->SetMergePoints(0); + cd->Update(); + usgIn = cd->GetOutput(); + } + else + { + vtkNew filter; + filter->SetMergePoints(0); + filter->SetInputData(input1); + filter->Update(); + usgIn = filter->GetOutput(); + } + } +} + +//----------------------------------------------------------------------------- +vtkStandardNewMacro(vtkGlyphCIH); + +int vtkGlyphCIH::FillInputPortInformation(int vtkNotUsed(port), vtkInformation *info) +{ + info->Set(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkDataSet"); + return 1; +} + +int vtkGlyphCIH::FillOutputPortInformation(int vtkNotUsed(port), vtkInformation* info) +{ + info->Set(vtkDataObject::DATA_TYPE_NAME(), "vtkPolyData"); + return 1; +} + +constexpr double radius_base = 0.03; +constexpr double radius_pointe = 0.1; +constexpr double z_base_pointe = 0.65; +constexpr double hauteur_pointe = 1.0 - z_base_pointe; +const double sqrt3Over2 = sqrt(3.) / 2; +const std::vector fleche_base= { + 0.0,1.0, + -sqrt3Over2,0.5, + -sqrt3Over2,-0.5, + 0.0,-1.0, + sqrt3Over2,-0.5, + sqrt3Over2,0.5 + }; +constexpr std::size_t N_CONN = 44; +constexpr std::size_t N_COORDS = 12; +constexpr std::size_t NB_CELL = 8; +constexpr std::size_t N_CONN_EFF = N_CONN-NB_CELL; +constexpr vtkIdType N_ACTIVE_CONN[N_CONN_EFF]={ + 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13,// 2 hexa + 15, 16, 17, 18, 20, 21, 22, 23, 25, 26, 27, 28, 30, 31, 32, 33, 35, 36, 37, 38, 40, 41, 42, 43// 6 quad4 +}; +constexpr vtkIdType N_CONN_STATIC[NB_CELL]={0, 7, 14, 19, 24, 29, 34, 39}; +constexpr vtkIdType ref_conn[N_CONN]={6,0,1,2,3,4,5, 6,6,7,8,9,10,11, 4,0,1,7,6, 4,1,2,8,7, 4,2,3,9,8, 4,3,4,10,9, 4,4,5,11,10, 4,5,0,6,11}; + +double signOf(double val) { return val>=0?1.:-1.; } + +template +void Algo(double widthFactor, vtkIdType inNbOfPts, double inNormMax, double scaleFactor, const double *inPts, double *coordsPtr, const T *inFieldDataPtr, vtkIdType *connPtr, T *fieldDataPtr) +{ + double sign(signOf(scaleFactor)); + scaleFactor = std::abs(scaleFactor); + for(vtkIdType iPt = 0 ; iPt < inNbOfPts ; ++iPt, coordsPtr+=3*N_COORDS, connPtr+=N_CONN, fieldDataPtr+=3*NB_CELL) + { + for(auto i = 0 ; i < NB_CELL ; ++i) + connPtr[N_CONN_STATIC[i]] = ref_conn[N_CONN_STATIC[i]]; + for(auto i = 0 ; i < N_CONN_EFF ; ++i) + connPtr[N_ACTIVE_CONN[i]] = iPt*N_COORDS + ref_conn[N_ACTIVE_CONN[i]]; + T norm(vtkMath::Norm(inFieldDataPtr+3*iPt,3)); + // + double mainZ[3] = {inFieldDataPtr[3*iPt+0]/norm,inFieldDataPtr[3*iPt+1]/norm,inFieldDataPtr[3*iPt+2]/norm}; + double mainX[3],mainY[3]; + vtkMath::Perpendiculars(mainZ,mainY,mainX,0.); + double radiusBase(widthFactor*radius_base); + double normRel(norm/inNormMax); + // normRel entre 0. et 1.. 0 -> Pas de base de fleche. 1 fleche au taquet. + /* + double zPosBasePointe(normRel*scaleFactor*inNormMax-widthFactor*hauteur_pointe); + double radiusPointe(widthFactor*radius_pointe); + if(zPosBasePointe < 0.) + { + radiusPointe = (normRel*scaleFactor*inNormMax)/hauteur_pointe*radius_pointe; + radiusBase = std::min(radiusBase,radiusPointe); + zPosBasePointe = 0.0; + }*/ + for(int i = 0 ; i < 6 ; ++i) + { + coordsPtr[3*i+0] = radiusBase*fleche_base[2*i+0]; coordsPtr[3*i+1] = radiusBase*fleche_base[2*i+1]; coordsPtr[3*i+2] = 0.0; + } + for(int i = 0 ; i < 6 ; ++i) + { + coordsPtr[6*3+3*i+0] = radiusBase*fleche_base[2*i+0]; coordsPtr[6*3+3*i+1] = radiusBase*fleche_base[2*i+1]; coordsPtr[6*3+3*i+2] = normRel*scaleFactor*inNormMax;//zPosBasePointe; + } + /*for(int i = 0 ; i < 6 ; ++i) + { + coordsPtr[12*3+3*i+0] = radiusPointe*fleche_base[2*i+0]; coordsPtr[12*3+3*i+1] = radiusPointe*fleche_base[2*i+1]; coordsPtr[12*3+3*i+2] = zPosBasePointe; + } + coordsPtr[18*3+0] = 0.; coordsPtr[18*3+1] = 0.; coordsPtr[18*3+2] = normRel*scaleFactor*inNormMax;*/ + // rotation + for(auto i = 0 ; i < N_COORDS ; ++i ) + { + double tmp[3] = { + sign*(coordsPtr[i*3]*mainX[0]+coordsPtr[i*3+1]*mainY[0]+coordsPtr[i*3+2]*mainZ[0]), + sign*(coordsPtr[i*3]*mainX[1]+coordsPtr[i*3+1]*mainY[1]+coordsPtr[i*3+2]*mainZ[1]), + sign*(coordsPtr[i*3]*mainX[2]+coordsPtr[i*3+1]*mainY[2]+coordsPtr[i*3+2]*mainZ[2]) + }; + std::copy(tmp,tmp+3,coordsPtr+i*3); + } + // translation + for(auto i = 0 ; i < N_COORDS ; ++i ) + { coordsPtr[i*3+0] += inPts[3*iPt+0]; coordsPtr[i*3+1] += inPts[3*iPt+1]; coordsPtr[i*3+2] += inPts[3*iPt+2]; } + // field + for(auto i = 0 ; i < NB_CELL ; ++i) + { std::copy(inFieldDataPtr+3*iPt,inFieldDataPtr+3*(iPt+1),fieldDataPtr+i*3); } + } +} + +template +struct Traits +{ + using EltType = T; +}; + +template<> +struct Traits +{ + using ArrayType = vtkDoubleArray; +}; + +template<> +struct Traits +{ + using ArrayType = vtkFloatArray; +}; + + +template +void Algo2(double widthFactor, double scaleFactor, vtkIdType inNbOfPts, const double *inPts, double *coordsPtr, vtkIdType *connPtr, typename Traits::ArrayType *inFieldData, vtkDataArray *fieldData) +{ + + const T *inFieldDataPtr(inFieldData->GetPointer(0)); + T *fieldDataPtr(reinterpret_cast(fieldData->GetVoidPointer(0))); + // + double inNormMin(std::numeric_limits::max()),inNormMax(-std::numeric_limits::max()); + for(vtkIdType i = 0 ; i < inNbOfPts ; ++i) + { + double norm(vtkMath::Norm(inFieldDataPtr+3*i,3)); + inNormMin = std::min(inNormMin,norm); + inNormMax = std::max(inNormMax,norm); + } + // + if(inNormMin == 0.) + throw MyGlyphException("Norm min is null !"); + // + Algo(widthFactor,inNbOfPts,inNormMax,scaleFactor,inPts,coordsPtr,inFieldDataPtr,connPtr,fieldDataPtr); +} + +void vtkGlyphCIH::SetInputArrayToProcess(int idx, int port, int connection, int ff, const char* name) +{ + if (idx == 0) + this->FieldName = name; + vtkPointSetAlgorithm::SetInputArrayToProcess(idx, port, connection, ff, name); +} + +//----------------------------------------------------------------------------- +int vtkGlyphCIH::RequestData(vtkInformation* vtkNotUsed(request),vtkInformationVector** inputVector, vtkInformationVector* outputVector) +{ + try + { + vtkInformation* outInfo(outputVector->GetInformationObject(0)); + vtkPointSet* output(vtkPointSet::SafeDownCast(outInfo->Get(vtkDataObject::DATA_OBJECT()))); + vtkSmartPointer usgIn; + this->ExtractInfo(inputVector[0], usgIn); + // + vtkNew cc; + cc->SetInputData(usgIn); + cc->Update(); + vtkDataArray *inFieldDataGen(cc->GetOutput()->GetPointData()->GetArray(this->FieldName.c_str())); + vtkIdType inNbOfPts(cc->GetOutput()->GetNumberOfPoints()); + const double *inPts(static_cast(cc->GetOutput()->GetPoints()->GetData()->GetVoidPointer(0))); + // + if(!inFieldDataGen) + { + std::ostringstream oss; oss << "No such point field with name \"" << this->FieldName << "\" !"; + throw MyGlyphException(oss.str()); + } + vtkSmartPointer fieldData; + fieldData.TakeReference(inFieldDataGen->NewInstance()); + fieldData->SetName(this->FieldName.c_str()); + for(int i=0;i<3;i++) + fieldData->SetComponentName(i,inFieldDataGen->GetComponentName(i)); + fieldData->SetNumberOfComponents(3); + fieldData->SetNumberOfTuples(inNbOfPts*NB_CELL); + // + vtkNew coords; + coords->SetNumberOfComponents(3); + coords->SetNumberOfTuples(inNbOfPts*N_COORDS); + vtkNew conn; + conn->SetNumberOfComponents(1); + conn->SetNumberOfTuples(inNbOfPts*N_CONN); + double *coordsPtr(coords->GetPointer(0)); + vtkIdType *connPtr(conn->GetPointer(0)); + // + /*double inMaxCellSize(0); + { + vtkNew mq; + mq->SetTriangleQualityMeasureToArea(); + mq->SetQuadQualityMeasureToArea(); + mq->SetInputData(usgIn); + mq->Update(); + vtkDoubleArray *area(vtkDoubleArray::SafeDownCast(mq->GetOutput()->GetCellData()->GetArray("Quality"))); + inMaxCellSize = area->GetMaxNorm(); + }*/ + // + if(vtkDoubleArray::SafeDownCast(inFieldDataGen)) + { + vtkDoubleArray *inFieldData = vtkDoubleArray::SafeDownCast(inFieldDataGen); + Algo2(this->WidthFactor,this->ScaleFactor,inNbOfPts,inPts,coordsPtr,connPtr,inFieldData,fieldData); + } + else if(vtkFloatArray::SafeDownCast(inFieldDataGen)) + { + vtkFloatArray *inFieldData = vtkFloatArray::SafeDownCast(inFieldDataGen); + Algo2(this->WidthFactor,this->ScaleFactor,inNbOfPts,inPts,coordsPtr,connPtr,inFieldData,fieldData); + } + else + { + throw MyGlyphException("Only float64 and float32 managed for input point field !"); + } + // + vtkNew pd; + vtkNew cb; + cb->SetCells(inNbOfPts*NB_CELL,conn); + pd->SetPolys(cb); + // + vtkNew pts; + pts->SetData(coords); + pd->SetPoints(pts); + // on ajoute tous les pointsarrays + vtkCellData *pdc(pd->GetCellData()); + pdc->AddArray(fieldData); + // + output->ShallowCopy(pd); + } + catch(MyGlyphException& e) + { + std::ostringstream oss; + oss << "Exception has been thrown in vtkComplexMode::RequestInformation : " << e.what() << std::endl; + if(this->HasObserver("ErrorEvent") ) + this->InvokeEvent("ErrorEvent",const_cast(oss.str().c_str())); + else + vtkOutputWindowDisplayErrorText(const_cast(oss.str().c_str())); + vtkObject::BreakOnError(); + } + return 1; +} + +//----------------------------------------------------------------------------- +void vtkGlyphCIH::PrintSelf(ostream& os, vtkIndent indent) +{ + this->Superclass::PrintSelf(os, indent); +} diff --git a/src/GlyphCIH/plugin/GlyphCIHFilters/vtkGlyphCIH.h b/src/GlyphCIH/plugin/GlyphCIHFilters/vtkGlyphCIH.h new file mode 100644 index 0000000..8a5fce2 --- /dev/null +++ b/src/GlyphCIH/plugin/GlyphCIHFilters/vtkGlyphCIH.h @@ -0,0 +1,69 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +#ifndef __vtkGlyphCIH_h__ +#define __vtkGlyphCIH_h__ + +#include + +#include +#include + +#include +#include + +class vtkDoubleArray; + +class VTK_EXPORT vtkGlyphCIH : public vtkPointSetAlgorithm +{ +public: + static vtkGlyphCIH* New(); + vtkTypeMacro(vtkGlyphCIH, vtkPointSetAlgorithm); + void PrintSelf(ostream& os, vtkIndent indent) override; + + vtkGetMacro(ScaleFactor, double); + vtkSetMacro(ScaleFactor, double); + + vtkGetMacro(WidthFactor, double); + vtkSetMacro(WidthFactor, double); + + int FillInputPortInformation(int vtkNotUsed(port), vtkInformation *info) override; + int FillOutputPortInformation(int vtkNotUsed(port), vtkInformation* info) override; + + void SetInputArrayToProcess(int idx, int port, int connection, int fieldAssociation, const char* name) override; + +protected: + vtkGlyphCIH() = default; + ~vtkGlyphCIH() override = default; + + int RequestData(vtkInformation*, vtkInformationVector**, vtkInformationVector*) override; + + void ExtractInfo(vtkInformationVector* inputVector, vtkSmartPointer& usgIn); + + double ScaleFactor; + double WidthFactor; + int TypeOfDisplay; + std::string FieldName; + +private: + vtkGlyphCIH(const vtkGlyphCIH&) = delete; + void operator=(const vtkGlyphCIH&) = delete; +}; + +#endif diff --git a/src/GlyphCIH/plugin/filters.xml b/src/GlyphCIH/plugin/filters.xml new file mode 100644 index 0000000..e388b48 --- /dev/null +++ b/src/GlyphCIH/plugin/filters.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + This property specifies the scale factor applied to the length of arrows. + + + + + This property specifies the width of arrows. + + + + + + + + + + + + Select the array that represents the requested mode. + + + + + + + + + + diff --git a/src/GlyphCIH/plugin/paraview.plugin b/src/GlyphCIH/plugin/paraview.plugin new file mode 100644 index 0000000..d734433 --- /dev/null +++ b/src/GlyphCIH/plugin/paraview.plugin @@ -0,0 +1,27 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +NAME + GlyphCIH +DESCRIPTION + This plugin provides Glyph custom +REQUIRES_MODULES + VTK::CommonCore + VTK::IOCore + VTK::FiltersCore diff --git a/src/MoveZCote/CMakeLists.txt b/src/MoveZCote/CMakeLists.txt new file mode 100644 index 0000000..af2acba --- /dev/null +++ b/src/MoveZCote/CMakeLists.txt @@ -0,0 +1,49 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +cmake_minimum_required(VERSION 3.8) +project(MoveZCotePlugin) +find_package(ParaView REQUIRED) + +include(GNUInstallDirs) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}") +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}") +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}") + +set("_paraview_plugin_default_${CMAKE_PROJECT_NAME}" ON) +paraview_plugin_scan( + ENABLE_BY_DEFAULT YES + PLUGIN_FILES "${CMAKE_CURRENT_SOURCE_DIR}/plugin/paraview.plugin" + PROVIDES_PLUGINS plugins + REQUIRES_MODULES required_modules) + +foreach(module IN LISTS required_modules) + if(NOT TARGET "${module}") + message("Missing required module: ${module}") + return() + endif() +endforeach() + +set(BUILD_SHARED_LIBS ON) +paraview_plugin_build( + RUNTIME_DESTINATION "${CMAKE_INSTALL_BINDIR}" + LIBRARY_DESTINATION "${CMAKE_INSTALL_LIBDIR}" + LIBRARY_SUBDIRECTORY "${PARAVIEW_PLUGIN_SUBDIR}" + PLUGINS ${plugins} + AUTOLOAD ${plugins}) diff --git a/src/MoveZCote/MobileMesh.xml b/src/MoveZCote/MobileMesh.xml new file mode 100644 index 0000000..01747ac --- /dev/null +++ b/src/MoveZCote/MobileMesh.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/MoveZCote/MoveMesh.py b/src/MoveZCote/MoveMesh.py new file mode 100644 index 0000000..0966452 --- /dev/null +++ b/src/MoveZCote/MoveMesh.py @@ -0,0 +1,62 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +#### import the simple module from the paraview +from paraview.simple import * +#### disable automatic camera reset on 'Show' +paraview.simple._DisableFirstRenderCameraReset() + +# create a new 'MED Reader' +#f3d_gouttedomed = MEDReader(FileName='/home/H87074/TMP52/f3d_gouttedo.med') +#f3d_gouttedomed.AllArrays = ['TS0/MESH/ComSup0/COTE Z@@][@@P1', 'TS0/MESH/ComSup0/VITESSE U@@][@@P1', 'TS0/MESH/ComSup0/VITESSE V@@][@@P1', 'TS0/MESH/ComSup0/VITESSE W@@][@@P1'] +#f3d_gouttedomed.AllTimeSteps = ['0000', '0001', '0002', '0003', '0004', '0005', '0006', '0007', '0008', '0009', '00010'] + +source = GetActiveSource() +renderView1 = GetActiveViewOrCreate('RenderView') +# get animation scene +animationScene1 = GetAnimationScene() + +# update animation scene based on data timesteps +animationScene1.UpdateAnimationUsingDataTimeSteps() + +# create a new 'Calculator' +calculator1 = Calculator(Input=source) + +# Properties modified on calculator1 +calculator1.ResultArrayName = 'DisplacementsZ' +calculator1.Function = 'COTE Z-coordsZ' + +# get color transfer function/color map for 'DisplacementsZ' +displacementsZLUT = GetColorTransferFunction('DisplacementsZ') + +# show data in view +#calculator1Display = Show(calculator1, renderView1) + +# hide data in view +Hide(source, renderView1) + +# show color bar/color legend +#calculator1Display.SetScalarBarVisibility(renderView1, True) + +# get opacity transfer function/opacity map for 'DisplacementsZ' + +# create a new 'Warp By Scalar' +warpByScalar1 = WarpByScalar(Input=calculator1) +warpByScalar1.Scalars = ['POINTS', 'DisplacementsZ'] + diff --git a/src/MoveZCote/plugin/CMakeLists.txt b/src/MoveZCote/plugin/CMakeLists.txt new file mode 100644 index 0000000..e26d260 --- /dev/null +++ b/src/MoveZCote/plugin/CMakeLists.txt @@ -0,0 +1,30 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +paraview_add_plugin(MoveZCotePlugin + VERSION "1.0" + MODULES MoveZCoteModule + MODULE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/MoveZCoteModule/vtk.module" + SERVER_MANAGER_XML filters.xml +) +install(TARGETS MoveZCotePlugin + RUNTIME DESTINATION lib/paraview + LIBRARY DESTINATION lib/paraview + ARCHIVE DESTINATION lib/paraview +) diff --git a/src/MoveZCote/plugin/MoveZCoteModule/CMakeLists.txt b/src/MoveZCote/plugin/MoveZCoteModule/CMakeLists.txt new file mode 100644 index 0000000..f782371 --- /dev/null +++ b/src/MoveZCote/plugin/MoveZCoteModule/CMakeLists.txt @@ -0,0 +1,27 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +set(classes + vtkMoveZCote +) + +vtk_module_add_module(MoveZCoteModule + FORCE_STATIC + CLASSES ${classes} +) diff --git a/src/MoveZCote/plugin/MoveZCoteModule/vtk.module b/src/MoveZCote/plugin/MoveZCoteModule/vtk.module new file mode 100644 index 0000000..28ff90b --- /dev/null +++ b/src/MoveZCote/plugin/MoveZCoteModule/vtk.module @@ -0,0 +1,31 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +NAME + MoveZCoteModule +DEPENDS + VTK::CommonCore + VTK::CommonDataModel + VTK::CommonExecutionModel + VTK::FiltersCore + VTK::FiltersModeling +PRIVATE_DEPENDS + VTK::CommonMisc + VTK::CommonSystem + VTK::FiltersGeneral diff --git a/src/MoveZCote/plugin/MoveZCoteModule/vtkMoveZCote.cxx b/src/MoveZCote/plugin/MoveZCoteModule/vtkMoveZCote.cxx new file mode 100644 index 0000000..6c6a23b --- /dev/null +++ b/src/MoveZCote/plugin/MoveZCoteModule/vtkMoveZCote.cxx @@ -0,0 +1,241 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +// Author : Anthony Geay (EDF R&D) + +#include "vtkMoveZCote.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +vtkStandardNewMacro(vtkMoveZCote); + +static const char ZE_ARRAY_NAME[] = "COTE Z"; + +static const char ZE_DISPLACEMENT_NAME[] = "@@DisplacementZ?@@"; + +/////////////////// + +class MZCException : public std::exception +{ +public: + MZCException(const std::string &s) : _reason(s) {} + virtual const char *what() const throw() { return _reason.c_str(); } + virtual ~MZCException() throw() {} + +private: + std::string _reason; +}; + +void ExtractInfo(vtkInformationVector *inputVector, vtkUnstructuredGrid *&usgIn, vtkDoubleArray *&arr) +{ + vtkInformation *inputInfo(inputVector->GetInformationObject(0)); + vtkDataSet *input(0); + vtkDataSet *input0(vtkDataSet::SafeDownCast(inputInfo->Get(vtkDataObject::DATA_OBJECT()))); + vtkMultiBlockDataSet *input1(vtkMultiBlockDataSet::SafeDownCast(inputInfo->Get(vtkDataObject::DATA_OBJECT()))); + if (input0) + input = input0; + else + { + if (!input1) + throw MZCException("Input dataSet must be a DataSet or single elt multi block dataset expected !"); + if (input1->GetNumberOfBlocks() != 1) + throw MZCException("Input dataSet is a multiblock dataset with not exactly one block ! Use MergeBlocks or ExtractBlocks filter before calling this filter !"); + vtkDataObject *input2(input1->GetBlock(0)); + if (!input2) + throw MZCException("Input dataSet is a multiblock dataset with exactly one block but this single element is NULL !"); + vtkDataSet *input2c(vtkDataSet::SafeDownCast(input2)); + if (!input2c) + throw MZCException("Input dataSet is a multiblock dataset with exactly one block but this single element is not a dataset ! Use MergeBlocks or ExtractBlocks filter before calling this filter !"); + input = input2c; + } + if (!input) + throw MZCException("Input data set is NULL !"); + usgIn = vtkUnstructuredGrid::SafeDownCast(input); + if (!usgIn) + throw MZCException("Input data set is not an unstructured mesh ! This filter works only on unstructured meshes !"); + vtkPointData *att(usgIn->GetPointData()); + if (!att) + throw MZCException("Input dataset has no point data attribute ! Impossible to move mesh !"); + vtkDataArray *zeArr(0); + for (int i = 0; i < att->GetNumberOfArrays(); i++) + { + vtkDataArray *locArr(att->GetArray(i)); + std::string s(locArr->GetName()); + if (s == ZE_ARRAY_NAME) + { + zeArr = locArr; + break; + } + } + if (!zeArr) + { + std::ostringstream oss; + oss << "Impossible to locate the array called \"" << ZE_ARRAY_NAME << "\" used to move mesh !"; + throw MZCException(oss.str()); + } + arr = vtkDoubleArray::SafeDownCast(zeArr); + if (!arr) + { + std::ostringstream oss; + oss << "Array called \"" << ZE_ARRAY_NAME << "\" has been located but this is NOT a float64 array !"; + throw MZCException(oss.str()); + } + if (arr->GetNumberOfComponents() != 1) + { + std::ostringstream oss; + oss << "Float64 array called \"" << ZE_ARRAY_NAME << "\" has been located but this array has not exactly 1 components as it should !"; + throw MZCException(oss.str()); + } + if (arr->GetNumberOfTuples() != usgIn->GetNumberOfPoints()) + { + std::ostringstream oss; + oss << "Float64-1 components array called \"" << ZE_ARRAY_NAME << "\" has been located but the number of tuples is invalid ! Should be " << usgIn->GetNumberOfPoints() << " instead of " << arr->GetNumberOfTuples() << " !"; + throw MZCException(oss.str()); + } +} + +//////////////////// +int vtkMoveZCote::RequestInformation(vtkInformation *request, vtkInformationVector **inputVector, vtkInformationVector *outputVector) +{ + //std::cerr << "########################################## vtkMoveZCote::RequestInformation ##########################################" << std::endl; + try + { + vtkUnstructuredGrid *usgIn(0); + vtkDoubleArray *arr(0); + ExtractInfo(inputVector[0], usgIn, arr); + } + catch (MZCException &e) + { + std::ostringstream oss; + oss << "Exception has been thrown in vtkMoveZCote::RequestInformation : " << e.what() << std::endl; + if (this->HasObserver("ErrorEvent")) + { + this->InvokeEvent("ErrorEvent", const_cast(oss.str().c_str())); + } + else + { + vtkOutputWindowDisplayErrorText(const_cast(oss.str().c_str())); + } + vtkObject::BreakOnError(); + return 0; + } + return 1; +} + +int vtkMoveZCote::RequestData(vtkInformation *request, vtkInformationVector **inputVector, vtkInformationVector *outputVector) +{ + //std::cerr << "########################################## vtkMoveZCote::RequestData ##########################################" << std::endl; + try + { + vtkUnstructuredGrid *usgIn(0); + vtkDoubleArray *arr(0); + ExtractInfo(inputVector[0], usgIn, arr); + // + int nbPts(usgIn->GetNumberOfPoints()); + vtkSmartPointer step1(vtkSmartPointer::New()); + step1->DeepCopy(usgIn); + vtkSmartPointer arr1(vtkSmartPointer::New()); + arr1->SetName(ZE_DISPLACEMENT_NAME); + arr1->SetNumberOfComponents(1); + arr1->SetNumberOfTuples(nbPts); + double *ptToFeed(arr1->Begin()); + vtkDataArray *coords(usgIn->GetPoints()->GetData()); + vtkDoubleArray *coords2(vtkDoubleArray::SafeDownCast(coords)); + if (!coords2) + { + throw MZCException("Input coordinates are not float64 !"); + } + if (coords2->GetNumberOfComponents() != 3) + { + throw MZCException("Input coordinates do not have 3 components as it should !"); + } + const double *srcPt1(arr->Begin()), *srcPt2(coords2->Begin()); + for (int i = 0; i < nbPts; i++, ptToFeed++) + { + *ptToFeed = srcPt1[i] - srcPt2[3 * i + 2]; + } + int idx(step1->GetPointData()->AddArray(arr1)); + step1->GetPointData()->SetActiveAttribute(idx, vtkDataSetAttributes::SCALARS); + // + vtkSmartPointer ws(vtkSmartPointer::New()); + ws->SetInputData(step1); + ws->SetScaleFactor(1); + ws->SetInputArrayToProcess(idx, 0, 0, "vtkDataObject::FIELD_ASSOCIATION_POINTS", ZE_DISPLACEMENT_NAME); + ws->Update(); + vtkSmartPointer ds(ws->GetOutput()); + // + ds->GetPointData()->RemoveArray(idx); + // + vtkInformation *outInfo(outputVector->GetInformationObject(0)); + vtkUnstructuredGrid *output(vtkUnstructuredGrid::SafeDownCast(outInfo->Get(vtkDataObject::DATA_OBJECT()))); + // + output->ShallowCopy(ds); + } + catch (MZCException &e) + { + std::ostringstream oss; + oss << "Exception has been thrown in vtkMoveZCote::RequestInformation : " << e.what() << std::endl; + if (this->HasObserver("ErrorEvent")) + { + this->InvokeEvent("ErrorEvent", const_cast(oss.str().c_str())); + } + else + { + vtkOutputWindowDisplayErrorText(const_cast(oss.str().c_str())); + } + vtkObject::BreakOnError(); + return 0; + } + return 1; +} + +void vtkMoveZCote::PrintSelf(ostream &os, vtkIndent indent) +{ + this->Superclass::PrintSelf(os, indent); +} diff --git a/src/MoveZCote/plugin/MoveZCoteModule/vtkMoveZCote.h b/src/MoveZCote/plugin/MoveZCoteModule/vtkMoveZCote.h new file mode 100644 index 0000000..8e55e1b --- /dev/null +++ b/src/MoveZCote/plugin/MoveZCoteModule/vtkMoveZCote.h @@ -0,0 +1,50 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +// Author : Anthony Geay (EDF R&D) + +#ifndef vtkMoveZCote_h__ +#define vtkMoveZCote_h__ + +#include + +class vtkMutableDirectedGraph; + +class VTK_EXPORT vtkMoveZCote : public vtkUnstructuredGridAlgorithm +{ +public: + static vtkMoveZCote *New(); + vtkTypeMacro(vtkMoveZCote, vtkUnstructuredGridAlgorithm); + void PrintSelf(ostream &os, vtkIndent indent) override; + +protected: + vtkMoveZCote() = default; + ~vtkMoveZCote() override = default; + + int RequestInformation(vtkInformation *request, + vtkInformationVector **inputVector, vtkInformationVector *outputVector) override; + + int RequestData(vtkInformation *request, vtkInformationVector **inputVector, + vtkInformationVector *outputVector) override; + +private: + vtkMoveZCote(const vtkMoveZCote &) = delete; + void operator=(const vtkMoveZCote &) = delete; +}; + +#endif diff --git a/src/MoveZCote/plugin/filters.xml b/src/MoveZCote/plugin/filters.xml new file mode 100644 index 0000000..d75e846 --- /dev/null +++ b/src/MoveZCote/plugin/filters.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + This property specifies the input to the Level Scalars filter. + + + + + + + + diff --git a/src/MoveZCote/plugin/paraview.plugin b/src/MoveZCote/plugin/paraview.plugin new file mode 100644 index 0000000..a07759e --- /dev/null +++ b/src/MoveZCote/plugin/paraview.plugin @@ -0,0 +1,27 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +NAME + MoveZCotePlugin +DESCRIPTION + This plugin provides the MoveZCote filter. +REQUIRES_MODULES + VTK::CommonCore + VTK::IOCore + VTK::FiltersCore diff --git a/src/ProbePointOverTime/CMakeLists.txt b/src/ProbePointOverTime/CMakeLists.txt new file mode 100644 index 0000000..ee42308 --- /dev/null +++ b/src/ProbePointOverTime/CMakeLists.txt @@ -0,0 +1,23 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# +# +# Author : Anthony Geay (EDF R&D) +cmake_minimum_required(VERSION 3.8) +project(ProbePointOverTime) +install(FILES ProbePointOverTimePlugin.xml DESTINATION lib/paraview ) diff --git a/src/ProbePointOverTime/HowtoTest.txt b/src/ProbePointOverTime/HowtoTest.txt new file mode 100644 index 0000000..a05f88e --- /dev/null +++ b/src/ProbePointOverTime/HowtoTest.txt @@ -0,0 +1,3 @@ +1 - Read example.med +2 - choose a cell +3 - Probe Point Over Time diff --git a/src/ProbePointOverTime/ProbePointOverTimePlugin.xml b/src/ProbePointOverTime/ProbePointOverTimePlugin.xml new file mode 100644 index 0000000..2d684ce --- /dev/null +++ b/src/ProbePointOverTime/ProbePointOverTimePlugin.xml @@ -0,0 +1,304 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + This filter takes two inputs - Input and Source, and samples the + point and cell values of Input on to the point locations of Source. + The output has the same structure as Source but its point data + have the resampled values from Input." + + + + + + + + + + + This property specifies the dataset from which to obtain + probe values. The data attributes come from this dataset. + + + + + + + + + + + + + The value of this property determines how the seeds for + the streamlines will be generated. + + + + + Control whether the source point data is to be + treated as categorical. If the data is categorical, then the + resultant data will be determined by a nearest neighbor + interpolation scheme rather than by linear interpolation. + + + + When set the input's cell data arrays are shallow copied to the output. + + + + + + When set the input's point data arrays are shallow copied to the output. + + + + + + + Set whether to pass the field-data arrays from the Input i.e. the input + providing the geometry to the output. On by default. + + + + + + + Set whether to compute the tolerance or to use a user provided + value. On by default. + + + + + + + + + + + Set the tolerance to use for + vtkDataSet::FindCell + + + + + When set, points that did not get valid values during resampling, and + cells containing such points, are marked as blank. + + + + + + + + + + + + + + + The cell locator to use for finding cells for probing. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + **Plot Selection Over Time** filter needs to process all timesteps + available in your dataset and can potentially take a long time to complete. + Do you want to continue? + + + + + + diff --git a/src/ProbePointOverTime/example.med b/src/ProbePointOverTime/example.med new file mode 100644 index 0000000000000000000000000000000000000000..2384856a487ebef76ac2884e6e7733e61daa10d7 GIT binary patch literal 29242 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 QN+1d(pbszNpnKZ?3o?n`jsO4v literal 0 HcmV?d00001 diff --git a/src/QuadraticToLinear/CMakeLists.txt b/src/QuadraticToLinear/CMakeLists.txt new file mode 100644 index 0000000..b9a9435 --- /dev/null +++ b/src/QuadraticToLinear/CMakeLists.txt @@ -0,0 +1,49 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +cmake_minimum_required(VERSION 3.8) +project(QuadraticToLinearPlugin) +find_package(ParaView REQUIRED) + +include(GNUInstallDirs) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}") +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}") +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}") + +set("_paraview_plugin_default_${CMAKE_PROJECT_NAME}" ON) +paraview_plugin_scan( + ENABLE_BY_DEFAULT YES + PLUGIN_FILES "${CMAKE_CURRENT_SOURCE_DIR}/plugin/paraview.plugin" + PROVIDES_PLUGINS plugins + REQUIRES_MODULES required_modules) + +foreach(module IN LISTS required_modules) + if(NOT TARGET "${module}") + message("Missing required module: ${module}") + return() + endif() +endforeach() + +set(BUILD_SHARED_LIBS ON) +paraview_plugin_build( + RUNTIME_DESTINATION "${CMAKE_INSTALL_BINDIR}" + LIBRARY_DESTINATION "${CMAKE_INSTALL_LIBDIR}" + LIBRARY_SUBDIRECTORY "${PARAVIEW_PLUGIN_SUBDIR}" + PLUGINS ${plugins} + AUTOLOAD ${plugins}) diff --git a/src/QuadraticToLinear/TestCase.py b/src/QuadraticToLinear/TestCase.py new file mode 100644 index 0000000..b02b1e6 --- /dev/null +++ b/src/QuadraticToLinear/TestCase.py @@ -0,0 +1,80 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +from MEDLoader import * + +fname="quadratic.med" +meshName="mesh" +coo=DataArrayDouble([(0,-1,0),(1,-1,0),(2,-1,0),#0->3 + (-1,0,0),(0,0,0),(1,0,0),(2,0,0),#3->7 + (2,1,0),(1,1,0),(0,1,0),#7->10 + (-1,0,1),(0,0,1),(1,0,1),#10->13 + (0,1,1),(1,1,1)# + ]) +m0=MEDCouplingUMesh(meshName,3) ; m0.setCoords(coo) +m0.allocateCells() +m0.insertNextCell(NORM_TETRA4,[5,6,7,12]) +m0.insertNextCell(NORM_PENTA6,[3,9,4,10,13,11]) +m0.insertNextCell(NORM_HEXA8,[4,9,8,5,11,13,14,12]) ; m0.zipCoords() +m0.convertLinearCellsToQuadratic() +# +m1=MEDCouplingUMesh(meshName,2) ; m1.setCoords(coo) +m1.allocateCells() +m1.insertNextCell(NORM_TRI3,[3,4,0]) +m1.insertNextCell(NORM_QUAD4,[0,4,5,1]) ; m1.zipCoords() +m1.convertLinearCellsToQuadratic() +# +m2=MEDCouplingUMesh(meshName,1) ; m2.setCoords(coo) +m2.allocateCells() +m2.insertNextCell(NORM_SEG2,[1,2]) ; m2.zipCoords() +m2.convertLinearCellsToQuadratic() +# +coos=[m0.getCoords(),m1.getCoords(),m2.getCoords()] +ncoos=[len(cooElt) for cooElt in coos] +arr=DataArrayDouble.Aggregate(coos) +a,b=arr.findCommonTuples(1e-12) +o2n,newNbNodes=DataArrayInt.ConvertIndexArrayToO2N(len(arr),a,b) +newArr=arr[o2n.invertArrayO2N2N2O(newNbNodes)] +# +m0.renumberNodesInConn(o2n) ; m0.setCoords(newArr) +m1.renumberNodesInConn(o2n[sum(ncoos[:1]):]) ; m1.setCoords(newArr) +m2.renumberNodesInConn(o2n[sum(ncoos[:2]):]) ; m2.setCoords(newArr) +a,b=newArr.areIncludedInMe(coo,1e-12) +assert(a) ; b.sort() +nodeIdsNotLinear=b.buildComplement(len(newArr)) +# +mm=MEDFileUMesh() +mm[0]=m0 ; mm[-1]=m1 ; mm[-2]=m2 +mm.write(fname,2) + +# +centerOfCloud=[newArr[:,i].accumulate(0)/len(newArr) for i in xrange(newArr.getNumberOfComponents())] +farr=(newArr-centerOfCloud).magnitude() +farr[nodeIdsNotLinear]=0. +zeMax=farr.getMaxValue()[0] +# +fieldName="Field" +ff=MEDFileField1TS() +f=MEDCouplingFieldDouble(ON_NODES) ; f.setMesh(m0) ; f.setArray(farr) ; f.setName(fieldName) ; f.setTime(0.,0,0) +ff.setFieldNoProfileSBT(f) +ff.write(fname,0) +farr-=zeMax ; farr.abs() ; farr[nodeIdsNotLinear]=0. +farr.reverse() ; f.setTime(1.,1,0) +ff.setFieldNoProfileSBT(f) +ff.write(fname,0) diff --git a/src/QuadraticToLinear/plugin/CMakeLists.txt b/src/QuadraticToLinear/plugin/CMakeLists.txt new file mode 100644 index 0000000..a041af6 --- /dev/null +++ b/src/QuadraticToLinear/plugin/CMakeLists.txt @@ -0,0 +1,31 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +paraview_add_plugin(QuadraticToLinearPlugin + VERSION "1.0" + MODULES QuadraticToLinearModule + MODULE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/QuadraticToLinearModule/vtk.module" + SERVER_MANAGER_XML filters.xml +) + +install(TARGETS QuadraticToLinearPlugin + RUNTIME DESTINATION lib/paraview + LIBRARY DESTINATION lib/paraview + ARCHIVE DESTINATION lib/paraview +) diff --git a/src/QuadraticToLinear/plugin/QuadraticToLinearModule/CMakeLists.txt b/src/QuadraticToLinear/plugin/QuadraticToLinearModule/CMakeLists.txt new file mode 100644 index 0000000..fe7965e --- /dev/null +++ b/src/QuadraticToLinear/plugin/QuadraticToLinearModule/CMakeLists.txt @@ -0,0 +1,27 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +set(classes + vtkQuadraticToLinear +) + +vtk_module_add_module(QuadraticToLinearModule + FORCE_STATIC + CLASSES ${classes} +) diff --git a/src/QuadraticToLinear/plugin/QuadraticToLinearModule/vtk.module b/src/QuadraticToLinear/plugin/QuadraticToLinearModule/vtk.module new file mode 100644 index 0000000..1213ba9 --- /dev/null +++ b/src/QuadraticToLinear/plugin/QuadraticToLinearModule/vtk.module @@ -0,0 +1,32 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +NAME + QuadraticToLinearModule +DEPENDS + VTK::CommonCore + VTK::CommonDataModel + VTK::CommonExecutionModel + VTK::FiltersCore + VTK::FiltersModeling +PRIVATE_DEPENDS + VTK::CommonMisc + VTK::CommonSystem + VTK::FiltersGeneral + VTK::RenderingCore diff --git a/src/QuadraticToLinear/plugin/QuadraticToLinearModule/vtkQuadraticToLinear.cxx b/src/QuadraticToLinear/plugin/QuadraticToLinearModule/vtkQuadraticToLinear.cxx new file mode 100644 index 0000000..4f86402 --- /dev/null +++ b/src/QuadraticToLinear/plugin/QuadraticToLinearModule/vtkQuadraticToLinear.cxx @@ -0,0 +1,402 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +// Author : Anthony Geay (EDF R&D) + +#include "vtkQuadraticToLinear.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +vtkStandardNewMacro(vtkQuadraticToLinear); + +constexpr int NB_QUAD_TO_LINEAR_1 = 7; + +constexpr VTKCellType QUAD_TO_LINEAR_QUAD1[NB_QUAD_TO_LINEAR_1] = {VTK_QUADRATIC_EDGE, VTK_QUADRATIC_TRIANGLE, VTK_QUADRATIC_QUAD, VTK_QUADRATIC_TETRA, VTK_QUADRATIC_WEDGE, VTK_QUADRATIC_PYRAMID, VTK_QUADRATIC_HEXAHEDRON}; + +constexpr VTKCellType QUAD_TO_LINEAR_LIN1[NB_QUAD_TO_LINEAR_1] = {VTK_LINE, VTK_TRIANGLE, VTK_QUAD, VTK_TETRA, VTK_WEDGE, VTK_PYRAMID, VTK_HEXAHEDRON}; + +constexpr int NB_QUAD_TO_LINEAR_2 = 4; + +constexpr VTKCellType QUAD_TO_LINEAR_QUAD2[NB_QUAD_TO_LINEAR_2] = {VTK_BIQUADRATIC_TRIANGLE, VTK_BIQUADRATIC_QUAD, VTK_BIQUADRATIC_QUADRATIC_WEDGE, VTK_BIQUADRATIC_QUADRATIC_HEXAHEDRON}; + +constexpr VTKCellType QUAD_TO_LINEAR_LIN2[NB_QUAD_TO_LINEAR_2] = {VTK_TRIANGLE, VTK_QUAD, VTK_WEDGE, VTK_HEXAHEDRON}; + +constexpr int NB_NB_NODES_PER_CELL = 7; + +constexpr VTKCellType NB_NODES_PER_CELL_1[NB_NB_NODES_PER_CELL] = {VTK_LINE, VTK_TRIANGLE, VTK_QUAD, VTK_TETRA, VTK_WEDGE, VTK_PYRAMID, VTK_HEXAHEDRON}; + +constexpr int NB_NODES_PER_CELL_2[NB_NB_NODES_PER_CELL] = {2, 3, 4, 4, 6, 5, 8}; + +/////////////////// + +template +class AutoPtr +{ +public: + AutoPtr(T *ptr = 0) : _ptr(ptr) {} + ~AutoPtr() { destroyPtr(); } + AutoPtr &operator=(T *ptr) + { + if (_ptr != ptr) + { + destroyPtr(); + _ptr = ptr; + } + return *this; + } + T *operator->() { return _ptr; } + const T *operator->() const { return _ptr; } + T &operator*() { return *_ptr; } + const T &operator*() const { return *_ptr; } + operator T *() { return _ptr; } + operator const T *() const { return _ptr; } + +private: + void destroyPtr() { delete[] _ptr; } + +private: + T *_ptr; +}; + +class MZCException : public std::exception +{ +public: + MZCException(const std::string &s) : _reason(s) {} + virtual const char *what() const throw() { return _reason.c_str(); } + virtual ~MZCException() throw() {} + +private: + std::string _reason; +}; + +void ExtractInfo(vtkInformationVector *inputVector, vtkUnstructuredGrid *&usgIn) +{ + vtkInformation *inputInfo(inputVector->GetInformationObject(0)); + vtkDataSet *input(0); + vtkDataSet *input0(vtkDataSet::SafeDownCast(inputInfo->Get(vtkDataObject::DATA_OBJECT()))); + vtkMultiBlockDataSet *input1(vtkMultiBlockDataSet::SafeDownCast(inputInfo->Get(vtkDataObject::DATA_OBJECT()))); + if (input0) + input = input0; + else + { + if (!input1) + throw MZCException("Input dataSet must be a DataSet or single elt multi block dataset expected !"); + if (input1->GetNumberOfBlocks() != 1) + throw MZCException("Input dataSet is a multiblock dataset with not exactly one block ! Use MergeBlocks or ExtractBlocks filter before calling this filter !"); + vtkDataObject *input2(input1->GetBlock(0)); + if (!input2) + throw MZCException("Input dataSet is a multiblock dataset with exactly one block but this single element is NULL !"); + vtkDataSet *input2c(vtkDataSet::SafeDownCast(input2)); + if (!input2c) + throw MZCException("Input dataSet is a multiblock dataset with exactly one block but this single element is not a dataset ! Use MergeBlocks or ExtractBlocks filter before calling this filter !"); + input = input2c; + } + if (!input) + throw MZCException("Input data set is NULL !"); + usgIn = vtkUnstructuredGrid::SafeDownCast(input); + if (!usgIn) + throw MZCException("Input data set is not an unstructured mesh ! This filter works only on unstructured meshes !"); +} + +vtkSmartPointer Reduce(const int *new2Old, int newNbPts, vtkDataArray *array) +{ + if (!array) + throw MZCException("Reduce : null input vector !"); + int nbOfCompo(array->GetNumberOfComponents()); + vtkSmartPointer zeRet; + if (vtkDoubleArray::SafeDownCast(array)) + { + vtkSmartPointer ret(vtkSmartPointer::New()); + zeRet = ret; + ret->SetNumberOfComponents(nbOfCompo); + ret->SetNumberOfTuples(newNbPts); + vtkDoubleArray *array1(vtkDoubleArray::SafeDownCast(array)); + if (array1) + { + const double *inpCoords(array1->GetPointer(0)); + float *outCoords(ret->GetPointer(0)); + for (int i = 0; i < newNbPts; i++, outCoords += nbOfCompo) + std::copy(inpCoords + new2Old[i] * nbOfCompo, inpCoords + (new2Old[i] + 1) * nbOfCompo, outCoords); + } + else + { + std::ostringstream oss; + oss << "Only Double array managed for the moment in input !" << array->GetName(); + throw MZCException(oss.str()); + } + } + else if (vtkIntArray::SafeDownCast(array)) + { + vtkSmartPointer ret(vtkSmartPointer::New()); + zeRet = ret; + ret->SetNumberOfComponents(nbOfCompo); + ret->SetNumberOfTuples(newNbPts); + vtkIntArray *array1(vtkIntArray::SafeDownCast(array)); + if (array1) + { + const int *inpCoords(array1->GetPointer(0)); + int *outCoords(ret->GetPointer(0)); + for (int i = 0; i < newNbPts; i++, outCoords += nbOfCompo) + std::copy(inpCoords + new2Old[i] * nbOfCompo, inpCoords + (new2Old[i] + 1) * nbOfCompo, outCoords); + } + else + { + std::ostringstream oss; + oss << "Only int32 array managed for the moment in input !" << array->GetName(); + throw MZCException(oss.str()); + } + } + else + throw MZCException("Reduce : unmanaged type !"); + for (int i = 0; i < nbOfCompo; i++) + { + const char *compoName(array->GetComponentName(i)); + zeRet->SetComponentName(i, compoName); + } + return zeRet; +} + +vtkSmartPointer ComputeQuadToLinear(vtkUnstructuredGrid *usg) +{ + std::map linToQuad; + std::map nbNodesPerType; + for (int i = 0; i < NB_QUAD_TO_LINEAR_1; i++) + linToQuad[QUAD_TO_LINEAR_QUAD1[i]] = QUAD_TO_LINEAR_LIN1[i]; + for (int i = 0; i < NB_QUAD_TO_LINEAR_2; i++) + linToQuad[QUAD_TO_LINEAR_QUAD2[i]] = QUAD_TO_LINEAR_LIN2[i]; + for (int i = 0; i < NB_NB_NODES_PER_CELL; i++) + nbNodesPerType[NB_NODES_PER_CELL_1[i]] = NB_NODES_PER_CELL_2[i]; + if (!usg) + throw MZCException("ComputeQuadToLinear : null input pointer !"); + vtkIdType nbPts = usg->GetNumberOfPoints(); + vtkIdType nbOfCells = usg->GetNumberOfCells(); + vtkIdType maxNbOfNodesPerCell = 0; + std::vector old2NewVB(nbPts, false); + for (vtkIdType i = 0; i < nbOfCells; i++) + { + vtkCell *cell(usg->GetCell(i)); + VTKCellType ct((VTKCellType)cell->GetCellType()); + std::map::const_iterator it(linToQuad.find(ct)); + if (it != linToQuad.end()) + { + std::map::const_iterator it2(nbNodesPerType.find((*it).second)); + maxNbOfNodesPerCell = std::max(it2->second, maxNbOfNodesPerCell); + for (vtkIdType j = 0; j < (*it2).second; j++) + { + vtkIdType ptId(cell->GetPointId(j)); + old2NewVB[ptId] = true; + } + } + else + { + if (ct != VTK_POLYHEDRON) + { + vtkIdType nbPtsOfCell = cell->GetNumberOfPoints(); + maxNbOfNodesPerCell = std::max(nbPtsOfCell, maxNbOfNodesPerCell); + for (vtkIdType j = 0; j < nbPtsOfCell; j++) + { + vtkIdType ptId = cell->GetPointId(j); + old2NewVB[ptId] = true; + } + } + else + throw MZCException("ComputeQuadToLinear : polyhedrons are not managed yet !"); + } + } + int newNbPts(std::count(old2NewVB.begin(), old2NewVB.end(), true)); + AutoPtr new2Old(new int[newNbPts]), old2New(new int[nbPts]); + struct Renumberer + { + Renumberer(int *pt) : _cnt(0), _pt(pt) {} + void operator()(bool v) + { + if (v) + { + *(_pt++) = _cnt; + } + _cnt++; + } + + private: + int _cnt; + int *_pt; + }; + struct RenumbererR + { + RenumbererR(int *pt) : _cnt(0), _pt(pt) {} + void operator()(bool v) + { + *_pt++ = _cnt; + if (v) + _cnt++; + } + + private: + int _cnt; + int *_pt; + }; + std::for_each(old2NewVB.begin(), old2NewVB.end(), Renumberer(new2Old)); + std::for_each(old2NewVB.begin(), old2NewVB.end(), RenumbererR(old2New)); + // + vtkNew ret; + vtkNew pts; + // deal with coordinates + vtkSmartPointer newCoords(Reduce(new2Old, newNbPts, usg->GetPoints()->GetData())); + // + ret->Initialize(); + ret->Allocate(nbOfCells); + { // deal with connectivity + AutoPtr connOfCellTmp(new vtkIdType[maxNbOfNodesPerCell]); + for (vtkIdType i = 0; i < nbOfCells; i++) + { + vtkCell *cell(usg->GetCell(i)); + VTKCellType ct((VTKCellType)cell->GetCellType()); + std::map::const_iterator it(linToQuad.find(ct)); + if (it != linToQuad.end()) + { + std::map::const_iterator it2(nbNodesPerType.find(it->second)); + for (vtkIdType j = 0; j < it2->second; j++) + connOfCellTmp[j] = old2New[cell->GetPointId(j)]; + ret->InsertNextCell(it->second, it2->second, connOfCellTmp); + } + else + { + vtkIdType nbPtsOfCell(cell->GetNumberOfPoints()); + for (vtkIdType j = 0; j < nbPtsOfCell; j++) + connOfCellTmp[j] = old2New[cell->GetPointId(j)]; + ret->InsertNextCell(ct, nbPtsOfCell, connOfCellTmp); + } + } + } + ret->SetPoints(pts); + pts->SetData(newCoords); + // Deal with cell fields + if (usg->GetCellData()) + { + ret->GetCellData()->ShallowCopy(usg->GetCellData()); + } + if (usg->GetPointData()) + { + vtkPointData *pd(usg->GetPointData()); + for (int i = 0; i < pd->GetNumberOfArrays(); i++) + { + vtkSmartPointer arr(Reduce(new2Old, newNbPts, pd->GetArray(i))); + arr->SetName(pd->GetArray(i)->GetName()); + ret->GetPointData()->AddArray(arr); + } + } + // + return ret; +} + +//////////////////// + +int vtkQuadraticToLinear::RequestInformation(vtkInformation *request, vtkInformationVector **inputVector, vtkInformationVector *outputVector) +{ + //std::cerr << "########################################## vtkQuadraticToLinear::RequestInformation ##########################################" << std::endl; + try + { + vtkUnstructuredGrid *usgIn(0); + ExtractInfo(inputVector[0], usgIn); + } + catch (MZCException &e) + { + std::ostringstream oss; + oss << "Exception has been thrown in vtkQuadraticToLinear::RequestInformation : " << e.what() << std::endl; + if (this->HasObserver("ErrorEvent")) + this->InvokeEvent("ErrorEvent", const_cast(oss.str().c_str())); + else + vtkOutputWindowDisplayErrorText(const_cast(oss.str().c_str())); + vtkObject::BreakOnError(); + return 0; + } + return 1; +} + +int vtkQuadraticToLinear::RequestData(vtkInformation *request, vtkInformationVector **inputVector, vtkInformationVector *outputVector) +{ + //std::cerr << "########################################## vtkQuadraticToLinear::RequestData ##########################################" << std::endl; + try + { + vtkUnstructuredGrid *usgIn(0); + ExtractInfo(inputVector[0], usgIn); + vtkSmartPointer ret(ComputeQuadToLinear(usgIn)); + vtkInformation *outInfo(outputVector->GetInformationObject(0)); + vtkUnstructuredGrid *output(vtkUnstructuredGrid::SafeDownCast(outInfo->Get(vtkDataObject::DATA_OBJECT()))); + output->ShallowCopy(ret); + } + catch (MZCException &e) + { + std::ostringstream oss; + oss << "Exception has been thrown in vtkQuadraticToLinear::RequestInformation : " << e.what() << std::endl; + if (this->HasObserver("ErrorEvent")) + this->InvokeEvent("ErrorEvent", const_cast(oss.str().c_str())); + else + vtkOutputWindowDisplayErrorText(const_cast(oss.str().c_str())); + vtkObject::BreakOnError(); + return 0; + } + return 1; +} + +void vtkQuadraticToLinear::PrintSelf(ostream &os, vtkIndent indent) +{ + this->Superclass::PrintSelf(os, indent); +} +/* +(cd /home/H87074/salome/DEV/tools/build/Paravisaddons-master-cm302-pv501 ; export CURRENT_SOFTWARE_SRC_DIR=/home/H87074/salome/DEV/tools/src/PARAVISADDONS ; export CURRENT_SOFTWARE_BUILD_DIR=/home/H87074/salome/DEV/tools/build/Paravisaddons-master-cm302-pv501 ; export CURRENT_SOFTWARE_INSTALL_DIR=/home/H87074/salome/DEV/tools/install/Paravisaddons-master-cm302-pv501 ; export PYTHON_VERSION="2.7" ; . /home/H87074/salome/DEV/salome_modules.sh >/dev/null 2>&1 ; . /home/H87074/salome/DEV/tools/build/Paravisaddons-master-cm302-pv501/.yamm/env_build.sh >/dev/null 2>&1 ; . /home/H87074/salome/DEV/salome_prerequisites.sh >/dev/null 2>&1 ; cmake -DCMAKE_INSTALL_PREFIX=/home/H87074/salome/DEV/tools/install/Paravisaddons-master-cm302-pv501 -DCMAKE_MODULE_PATH=${CONFIGURATION_CMAKE_DIR} -DCMAKE_BUILD_TYPE=Debug /home/H87074/salome/DEV/tools/src/PARAVISADDONS ) + +(cd /home/H87074/salome/DEV/tools/build/Paravisaddons-master-cm302-pv501/QuadraticToLinear ; export CURRENT_SOFTWARE_SRC_DIR=/home/H87074/salome/DEV/tools/src/PARAVISADDONS ; export CURRENT_SOFTWARE_BUILD_DIR=/home/H87074/salome/DEV/tools/build/Paravisaddons-master-cm302-pv501 ; export CURRENT_SOFTWARE_INSTALL_DIR=/home/H87074/salome/DEV/tools/install/Paravisaddons-master-cm302-pv501 ; export PYTHON_VERSION="2.7" ; . /home/H87074/salome/DEV/salome_modules.sh >/dev/null 2>&1 ; . /home/H87074/salome/DEV/tools/build/Paravisaddons-master-cm302-pv501/.yamm/env_build.sh >/dev/null 2>&1 ; . /home/H87074/salome/DEV/salome_prerequisites.sh >/dev/null 2>&1 ; make -j8 install ) + +/home/H87074/salome/prerequisites/src/ParaView/VTK/Common/DataModel/vtkCellType.h:87 +*/ diff --git a/src/QuadraticToLinear/plugin/QuadraticToLinearModule/vtkQuadraticToLinear.h b/src/QuadraticToLinear/plugin/QuadraticToLinearModule/vtkQuadraticToLinear.h new file mode 100644 index 0000000..6f732ea --- /dev/null +++ b/src/QuadraticToLinear/plugin/QuadraticToLinearModule/vtkQuadraticToLinear.h @@ -0,0 +1,50 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +// Author : Anthony Geay (EDF R&D) + +#ifndef vtkQuadraticToLinear_h__ +#define vtkQuadraticToLinear_h__ + +#include + +class vtkMutableDirectedGraph; + +class VTK_EXPORT vtkQuadraticToLinear : public vtkUnstructuredGridAlgorithm +{ +public: + static vtkQuadraticToLinear *New(); + vtkTypeMacro(vtkQuadraticToLinear, vtkUnstructuredGridAlgorithm); + void PrintSelf(ostream &os, vtkIndent indent) override; + +protected: + vtkQuadraticToLinear() = default; + ~vtkQuadraticToLinear() override = default; + + int RequestInformation(vtkInformation *request, + vtkInformationVector **inputVector, vtkInformationVector *outputVector) override; + + int RequestData(vtkInformation *request, vtkInformationVector **inputVector, + vtkInformationVector *outputVector) override; + +private: + vtkQuadraticToLinear(const vtkQuadraticToLinear &) = delete; + void operator=(const vtkQuadraticToLinear &) = delete; +}; + +#endif diff --git a/src/QuadraticToLinear/plugin/filters.xml b/src/QuadraticToLinear/plugin/filters.xml new file mode 100644 index 0000000..8897072 --- /dev/null +++ b/src/QuadraticToLinear/plugin/filters.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + This property specifies the input to the Level Scalars filter. + + + + + diff --git a/src/QuadraticToLinear/plugin/paraview.plugin b/src/QuadraticToLinear/plugin/paraview.plugin new file mode 100644 index 0000000..c52e04b --- /dev/null +++ b/src/QuadraticToLinear/plugin/paraview.plugin @@ -0,0 +1,27 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +NAME + QuadraticToLinearPlugin +DESCRIPTION + This plugin provides the QuadraticToLinear filter. +REQUIRES_MODULES + VTK::CommonCore + VTK::IOCore + VTK::FiltersCore diff --git a/src/RateOfFlowThroughSection/CMakeLists.txt b/src/RateOfFlowThroughSection/CMakeLists.txt new file mode 100644 index 0000000..f8b150e --- /dev/null +++ b/src/RateOfFlowThroughSection/CMakeLists.txt @@ -0,0 +1,51 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +cmake_minimum_required(VERSION 3.8) +project(RateOfFlowThroughSection) +find_package(ParaView REQUIRED) + +include(GNUInstallDirs) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}") +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}") +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}") + +set(PARAVIEW_PLUGIN_ENABLE_RateOfFlowThroughSection TRUE) + +set("_paraview_plugin_default_${CMAKE_PROJECT_NAME}" ON) +paraview_plugin_scan( + ENABLE_BY_DEFAULT YES + PLUGIN_FILES "${CMAKE_CURRENT_SOURCE_DIR}/plugin/paraview.plugin" + PROVIDES_PLUGINS plugins + REQUIRES_MODULES required_modules) + +foreach(module IN LISTS required_modules) + if(NOT TARGET "${module}") + message("Missing required module: ${module}") + return() + endif() +endforeach() + +set(BUILD_SHARED_LIBS ON) +paraview_plugin_build( + RUNTIME_DESTINATION "${CMAKE_INSTALL_BINDIR}" + LIBRARY_DESTINATION "${CMAKE_INSTALL_LIBDIR}" + LIBRARY_SUBDIRECTORY "${PARAVIEW_PLUGIN_SUBDIR}" + PLUGINS ${plugins} + AUTOLOAD ${plugins}) diff --git a/src/RateOfFlowThroughSection/plugin/CMakeLists.txt b/src/RateOfFlowThroughSection/plugin/CMakeLists.txt new file mode 100644 index 0000000..bb43260 --- /dev/null +++ b/src/RateOfFlowThroughSection/plugin/CMakeLists.txt @@ -0,0 +1,60 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +# Common CMake macros +# =================== +SET(TMP_CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH}) +unset(CMAKE_MODULE_PATH) +SET(CONFIGURATION_ROOT_DIR $ENV{CONFIGURATION_ROOT_DIR} CACHE PATH "Path to the Salome CMake configuration files") +IF(EXISTS ${CONFIGURATION_ROOT_DIR}) + LIST(APPEND CMAKE_MODULE_PATH "${CONFIGURATION_ROOT_DIR}/cmake") + INCLUDE(SalomeMacros) +ELSE() + MESSAGE(FATAL_ERROR "We absolutely need the Salome CMake configuration files, please define CONFIGURATION_ROOT_DIR !") +ENDIF() + +SET(MEDCOUPLING_ROOT_DIR $ENV{MEDCOUPLING_ROOT_DIR} CACHE PATH "Path to the MEDCoupling tool") +IF(EXISTS ${MEDCOUPLING_ROOT_DIR}) + LIST(APPEND CMAKE_MODULE_PATH "${MEDCOUPLING_ROOT_DIR}/cmake_files") +ENDIF() +LIST(APPEND CMAKE_MODULE_PATH "${CMAKE_ROOT}/Modules") +LIST(APPEND CMAKE_MODULE_PATH ${TMP_CMAKE_MODULE_PATH}) + +INCLUDE(SalomeSetupPlatform) +SET(BUILD_SHARED_LIBS TRUE) + +FIND_PACKAGE(SalomeHDF5 REQUIRED) +FIND_PACKAGE(SalomeMEDCoupling REQUIRED) + +SALOME_ACCUMULATE_ENVIRONMENT(PYTHONPATH NOCHECK ${CMAKE_INSTALL_PREFIX}/${SALOME_INSTALL_BINS} + ${CMAKE_INSTALL_PREFIX}/${SALOME_INSTALL_PYTHON}) +SALOME_ACCUMULATE_ENVIRONMENT(LD_LIBRARY_PATH NOCHECK ${CMAKE_INSTALL_PREFIX}/${SALOME_INSTALL_LIBS}) +SALOME_ACCUMULATE_ENVIRONMENT(PV_PLUGIN_PATH NOCHECK ${CMAKE_INSTALL_PREFIX}/lib/paraview) + +paraview_add_plugin(RateOfFlowThroughSectionPlugin + VERSION "1.0" + MODULES RateOfFlowThroughSectionModule + MODULE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/RateOfFlowThroughSectionModule/vtk.module" + SERVER_MANAGER_XML filters.xml +) +install(TARGETS RateOfFlowThroughSectionPlugin + RUNTIME DESTINATION lib/paraview + LIBRARY DESTINATION lib/paraview + ARCHIVE DESTINATION lib/paraview +) diff --git a/src/RateOfFlowThroughSection/plugin/RateOfFlowThroughSectionModule/CMakeLists.txt b/src/RateOfFlowThroughSection/plugin/RateOfFlowThroughSectionModule/CMakeLists.txt new file mode 100644 index 0000000..4a38fcb --- /dev/null +++ b/src/RateOfFlowThroughSection/plugin/RateOfFlowThroughSectionModule/CMakeLists.txt @@ -0,0 +1,38 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +set(classes + vtkRateOfFlowThroughSection + vtkExplodePolyLine + vtkSedimentDeposit + VTKToMEDMem +) + +vtk_module_add_module(RateOfFlowThroughSectionModule + FORCE_STATIC + CLASSES ${classes} +) + +target_include_directories(RateOfFlowThroughSectionModule PUBLIC ${MEDCOUPLING_INCLUDE_DIRS}) + +if(HDF5_IS_PARALLEL) + target_link_libraries(RateOfFlowThroughSectionModule PRIVATE ${MEDCoupling_paramedloader}) +else() + target_link_libraries(RateOfFlowThroughSectionModule PRIVATE ${MEDCoupling_medloader}) +endif() diff --git a/src/RateOfFlowThroughSection/plugin/RateOfFlowThroughSectionModule/VTKMEDTraits.hxx b/src/RateOfFlowThroughSection/plugin/RateOfFlowThroughSectionModule/VTKMEDTraits.hxx new file mode 100644 index 0000000..4c7832d --- /dev/null +++ b/src/RateOfFlowThroughSection/plugin/RateOfFlowThroughSectionModule/VTKMEDTraits.hxx @@ -0,0 +1,81 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +// Author : Anthony Geay (EDF R&D) + +#ifndef __VTKMEDTRAITS_HXX__ +#define __VTKMEDTRAITS_HXX__ + +class vtkIntArray; +class vtkLongArray; +#ifdef WIN32 +class vtkLongLongArray; +#endif +class vtkFloatArray; +class vtkDoubleArray; + +template +class MEDFileVTKTraits +{ +public: + typedef void VtkType; + typedef void MCType; +}; + +template<> +class MEDFileVTKTraits +{ +public: + typedef vtkIntArray VtkType; + typedef MEDCoupling::DataArrayInt32 MCType; +}; + +template<> +#ifdef WIN32 +class MEDFileVTKTraits +#else +class MEDFileVTKTraits +#endif +# +{ +public: +#ifdef WIN32 + typedef vtkLongLongArray VtkType; +#else + typedef vtkLongArray VtkType; +#endif + typedef MEDCoupling::DataArrayInt64 MCType; +}; + +template<> +class MEDFileVTKTraits +{ +public: + typedef vtkFloatArray VtkType; + typedef MEDCoupling::DataArrayFloat MCType; +}; + +template<> +class MEDFileVTKTraits +{ +public: + typedef vtkDoubleArray VtkType; + typedef MEDCoupling::DataArrayDouble MCType; +}; + +#endif diff --git a/src/RateOfFlowThroughSection/plugin/RateOfFlowThroughSectionModule/VTKToMEDMem.cxx b/src/RateOfFlowThroughSection/plugin/RateOfFlowThroughSectionModule/VTKToMEDMem.cxx new file mode 100644 index 0000000..5792221 --- /dev/null +++ b/src/RateOfFlowThroughSection/plugin/RateOfFlowThroughSectionModule/VTKToMEDMem.cxx @@ -0,0 +1,962 @@ +// Copyright (C) 2017-2020 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +// Author : Anthony Geay (EDF R&D) + +#include "VTKToMEDMem.h" + +#include "vtkAdjacentVertexIterator.h" +#include "vtkIntArray.h" +#include "vtkLongArray.h" +#include "vtkCellData.h" +#include "vtkPointData.h" +#include "vtkFloatArray.h" +#include "vtkCellArray.h" + +#include "vtkStreamingDemandDrivenPipeline.h" +#include "vtkInformationDataObjectMetaDataKey.h" +#include "vtkUnstructuredGrid.h" +#include "vtkMultiBlockDataSet.h" +#include "vtkRectilinearGrid.h" +#include "vtkInformationStringKey.h" +#include "vtkAlgorithmOutput.h" +#include "vtkObjectFactory.h" +#include "vtkMutableDirectedGraph.h" +#include "vtkMultiBlockDataSet.h" +#include "vtkPolyData.h" +#include "vtkDataSet.h" +#include "vtkInformationVector.h" +#include "vtkInformation.h" +#include "vtkDataArraySelection.h" +#include "vtkTimeStamp.h" +#include "vtkInEdgeIterator.h" +#include "vtkInformationDataObjectKey.h" +#include "vtkExecutive.h" +#include "vtkVariantArray.h" +#include "vtkStringArray.h" +#include "vtkDoubleArray.h" +#include "vtkCharArray.h" +#include "vtkUnsignedCharArray.h" +#include "vtkDataSetAttributes.h" +#include "vtkDemandDrivenPipeline.h" +#include "vtkDataObjectTreeIterator.h" +#include "vtkWarpScalar.h" + +#include +#include +#include +#include + +using VTKToMEDMem::Grp; +using VTKToMEDMem::Fam; + +using MEDCoupling::MEDFileData; +using MEDCoupling::MEDFileMesh; +using MEDCoupling::MEDFileCMesh; +using MEDCoupling::MEDFileUMesh; +using MEDCoupling::MEDFileFields; +using MEDCoupling::MEDFileMeshes; + +using MEDCoupling::MEDFileInt32Field1TS; +using MEDCoupling::MEDFileInt64Field1TS; +using MEDCoupling::MEDFileField1TS; +using MEDCoupling::MEDFileIntFieldMultiTS; +using MEDCoupling::MEDFileFieldMultiTS; +using MEDCoupling::MEDFileAnyTypeFieldMultiTS; +using MEDCoupling::DataArray; +using MEDCoupling::DataArrayInt32; +using MEDCoupling::DataArrayInt64; +using MEDCoupling::DataArrayFloat; +using MEDCoupling::DataArrayDouble; +using MEDCoupling::MEDCouplingMesh; +using MEDCoupling::MEDCouplingUMesh; +using MEDCoupling::MEDCouplingCMesh; +using MEDCoupling::MEDCouplingFieldDouble; +using MEDCoupling::MEDCouplingFieldFloat; +using MEDCoupling::MEDCouplingFieldInt; +using MEDCoupling::MEDCouplingFieldInt64; +using MEDCoupling::MCAuto; +using MEDCoupling::Traits; +using MEDCoupling::MLFieldTraits; + +/////////////////// + +Fam::Fam(const std::string& name) +{ + static const char ZE_SEP[]="@@][@@"; + std::size_t pos(name.find(ZE_SEP)); + std::string name0(name.substr(0,pos)),name1(name.substr(pos+strlen(ZE_SEP))); + std::istringstream iss(name1); + iss >> _id; + _name=name0; +} + +/////////////////// + +#include "VTKMEDTraits.hxx" + +std::map ComputeMapOfType() +{ + std::map ret; + int nbOfTypesInMC(sizeof(MEDCOUPLING2VTKTYPETRADUCER)/sizeof( decltype(MEDCOUPLING2VTKTYPETRADUCER[0]) )); + for(int i=0;i& context) +{ + static const char DFT_MESH_NAME[]="Mesh"; + if(context.empty()) + return DFT_MESH_NAME; + std::ostringstream oss; oss << DFT_MESH_NAME; + for(std::vector::const_iterator it=context.begin();it!=context.end();it++) + oss << "_" << *it; + return oss.str(); +} + +DataArrayIdType *ConvertVTKArrayToMCArrayInt(vtkDataArray *data) +{ + if(!data) + throw MZCException("ConvertVTKArrayToMCArrayInt : internal error !"); + int nbTuples(data->GetNumberOfTuples()),nbComp(data->GetNumberOfComponents()); + std::size_t nbElts(nbTuples*nbComp); + MCAuto ret(DataArrayIdType::New()); + ret->alloc(nbTuples,nbComp); + for(int i=0;iGetComponentName(i)); + if(comp) + ret->setInfoOnComponent(i,comp); + } + mcIdType *ptOut(ret->getPointer()); + vtkIntArray *d0(vtkIntArray::SafeDownCast(data)); + if(d0) + { + const int *pt(d0->GetPointer(0)); + std::copy(pt,pt+nbElts,ptOut); + return ret.retn(); + } + vtkLongArray *d1(vtkLongArray::SafeDownCast(data)); + if(d1) + { + const long *pt(d1->GetPointer(0)); + std::copy(pt,pt+nbElts,ptOut); + return ret.retn(); + } + vtkIdTypeArray *d3(vtkIdTypeArray::SafeDownCast(data)); + if(d3) + { + const vtkIdType *pt(d3->GetPointer(0)); + std::copy(pt,pt+nbElts,ptOut); + return ret.retn(); + } + vtkUnsignedCharArray *d2(vtkUnsignedCharArray::SafeDownCast(data)); + if(d2) + { + const unsigned char *pt(d2->GetPointer(0)); + std::copy(pt,pt+nbElts,ptOut); + return ret.retn(); + } + std::ostringstream oss; + oss << "ConvertVTKArrayToMCArrayInt : unrecognized array \"" << typeid(*data).name() << "\" type !"; + throw MZCException(oss.str()); +} + +template +typename Traits::ArrayType *ConvertVTKArrayToMCArrayDouble(vtkDataArray *data) +{ + if(!data) + throw MZCException("ConvertVTKArrayToMCArrayDouble : internal error !"); + int nbTuples(data->GetNumberOfTuples()),nbComp(data->GetNumberOfComponents()); + std::size_t nbElts(nbTuples*nbComp); + MCAuto< typename Traits::ArrayType > ret(Traits::ArrayType::New()); + ret->alloc(nbTuples,nbComp); + for(int i=0;iGetComponentName(i)); + if(comp) + ret->setInfoOnComponent(i,comp); + else + { + if(nbComp>1 && nbComp<=3) + { + char tmp[2]; + tmp[0]=(char)('X'+i); tmp[1]='\0'; + ret->setInfoOnComponent(i,tmp); + } + } + } + T *ptOut(ret->getPointer()); + typename MEDFileVTKTraits::VtkType *d0(MEDFileVTKTraits::VtkType::SafeDownCast(data)); + if(d0) + { + const T *pt(d0->GetPointer(0)); + for(std::size_t i=0;iGetClassName() << "\" type !"; + throw MZCException(oss.str()); +} + +DataArrayDouble *ConvertVTKArrayToMCArrayDoubleForced(vtkDataArray *data) +{ + if(!data) + throw MZCException("ConvertVTKArrayToMCArrayDoubleForced : internal error 0 !"); + vtkFloatArray *d0(vtkFloatArray::SafeDownCast(data)); + if(d0) + { + MCAuto ret(ConvertVTKArrayToMCArrayDouble(data)); + MCAuto ret2(ret->convertToDblArr()); + return ret2.retn(); + } + vtkDoubleArray *d1(vtkDoubleArray::SafeDownCast(data)); + if(d1) + return ConvertVTKArrayToMCArrayDouble(data); + throw MZCException("ConvertVTKArrayToMCArrayDoubleForced : unrecognized type of data for double !"); +} + +DataArray *ConvertVTKArrayToMCArray(vtkDataArray *data) +{ + if(!data) + throw MZCException("ConvertVTKArrayToMCArray : internal error !"); + vtkFloatArray *d0(vtkFloatArray::SafeDownCast(data)); + if(d0) + return ConvertVTKArrayToMCArrayDouble(data); + vtkDoubleArray *d1(vtkDoubleArray::SafeDownCast(data)); + if(d1) + return ConvertVTKArrayToMCArrayDouble(data); + vtkIntArray *d2(vtkIntArray::SafeDownCast(data)); + vtkLongArray *d3(vtkLongArray::SafeDownCast(data)); + vtkUnsignedCharArray *d4(vtkUnsignedCharArray::SafeDownCast(data)); + vtkIdTypeArray *d5(vtkIdTypeArray::SafeDownCast(data)); + if(d2 || d3 || d4 || d5) + return ConvertVTKArrayToMCArrayInt(data); + std::ostringstream oss; + oss << "ConvertVTKArrayToMCArray : unrecognized array \"" << typeid(*data).name() << "\" type !"; + throw MZCException(oss.str()); +} + +MEDCouplingUMesh *BuildMeshFromCellArray(vtkCellArray *ca, DataArrayDouble *coords, int meshDim, INTERP_KERNEL::NormalizedCellType type) +{ + MCAuto subMesh(MEDCouplingUMesh::New("",meshDim)); + subMesh->setCoords(coords); subMesh->allocateCells(); + int nbCells(ca->GetNumberOfCells()); + if(nbCells==0) + return 0; + //vtkIdType nbEntries(ca->GetNumberOfConnectivityEntries()); // todo: unused + const vtkIdType *conn(ca->GetData()->GetPointer(0)); + for(int i=0;i conn2(sz); + for(int jj=0;jjinsertNextCell(type,sz,&conn2[0]); + conn+=sz; + } + return subMesh.retn(); +} + +MEDCouplingUMesh *BuildMeshFromCellArrayTriangleStrip(vtkCellArray *ca, DataArrayDouble *coords, MCAuto& ids) +{ + MCAuto subMesh(MEDCouplingUMesh::New("",2)); + subMesh->setCoords(coords); subMesh->allocateCells(); + int nbCells(ca->GetNumberOfCells()); + if(nbCells==0) + return 0; + //vtkIdType nbEntries(ca->GetNumberOfConnectivityEntries()); // todo: unused + const vtkIdType *conn(ca->GetData()->GetPointer(0)); + ids=DataArrayIdType::New() ; ids->alloc(0,1); + for(int i=0;i0) + { + for(int j=0;jinsertNextCell(INTERP_KERNEL::NORM_TRI3,3,conn2); + ids->pushBackSilent(i); + } + } + else + { + std::ostringstream oss; oss << "BuildMeshFromCellArrayTriangleStrip : on cell #" << i << " the triangle stip looks bab !"; + throw MZCException(oss.str()); + } + conn+=sz; + } + return subMesh.retn(); +} + +class MicroField +{ +public: + MicroField(const MCAuto& m, const std::vector >& cellFs):_m(m),_cellFs(cellFs) { } + MicroField(const std::vector< MicroField >& vs); + void setNodeFields(const std::vector >& nf) { _nodeFs=nf; } + MCAuto getMesh() const { return _m; } + std::vector > getCellFields() const { return _cellFs; } +private: + MCAuto _m; + std::vector > _cellFs; + std::vector > _nodeFs; +}; + +MicroField::MicroField(const std::vector< MicroField >& vs) +{ + std::size_t sz(vs.size()); + std::vector vs2(sz); + std::vector< std::vector< MCAuto > > arrs2(sz); + int nbElts(-1); + for(std::size_t ii=0;ii arrsTmp(sz); + for(std::size_t jj=0;jj +void AppendToFields(MEDCoupling::TypeOfField tf, MEDCouplingMesh *mesh, const DataArrayIdType *n2oPtr, typename MEDFileVTKTraits::MCType *dadPtr, MEDFileFields *fs, double timeStep, int tsId) +{ + std::string fieldName(dadPtr->getName()); + MCAuto< typename Traits::FieldType > f(Traits::FieldType::New(tf)); + f->setTime(timeStep,tsId,0); + { + std::string fieldNameForChuckNorris(MEDCoupling::MEDFileAnyTypeField1TSWithoutSDA::FieldNameToMEDFileConvention(fieldName)); + f->setName(fieldNameForChuckNorris); + } + if(!n2oPtr) + f->setArray(dadPtr); + else + { + MCAuto< typename Traits::ArrayType > dad2(dadPtr->selectByTupleId(n2oPtr->begin(),n2oPtr->end())); + f->setArray(dad2); + } + f->setMesh(mesh); + MCAuto< typename MLFieldTraits::FMTSType > fmts(MLFieldTraits::FMTSType::New()); + MCAuto< typename MLFieldTraits::F1TSType > f1ts(MLFieldTraits::F1TSType::New()); + f1ts->setFieldNoProfileSBT(f); + fmts->pushBackTimeStep(f1ts); + fs->pushField(fmts); +} + +void AppendMCFieldFrom(MEDCoupling::TypeOfField tf, MEDCouplingMesh *mesh, MEDFileData *mfd, MCAuto da, const DataArrayIdType *n2oPtr, double timeStep, int tsId) +{ + static const char FAMFIELD_FOR_CELLS[]="FamilyIdCell"; + static const char FAMFIELD_FOR_NODES[]="FamilyIdNode"; + if(!da || !mesh || !mfd) + throw MZCException("AppendMCFieldFrom : internal error !"); + MEDFileFields *fs(mfd->getFields()); + MEDFileMeshes *ms(mfd->getMeshes()); + if(!fs || !ms) + throw MZCException("AppendMCFieldFrom : internal error 2 !"); + MCAuto dad(MEDCoupling::DynamicCast(da)); + if(dad.isNotNull()) + { + AppendToFields(tf,mesh,n2oPtr,dad,fs,timeStep,tsId); + return ; + } + MCAuto daf(MEDCoupling::DynamicCast(da)); + if(daf.isNotNull()) + { + AppendToFields(tf,mesh,n2oPtr,daf,fs,timeStep,tsId); + return ; + } + MCAuto dai(MEDCoupling::DynamicCast(da)); + MCAuto daId(MEDCoupling::DynamicCast(da)); + if(dai.isNotNull() || daId.isNotNull()) + { + std::string fieldName(da->getName()); + if((fieldName!=FAMFIELD_FOR_CELLS || tf!=MEDCoupling::ON_CELLS) && (fieldName!=FAMFIELD_FOR_NODES || tf!=MEDCoupling::ON_NODES)) + { + if(!dai) + AppendToFields(tf,mesh,n2oPtr,daId,fs,timeStep,tsId); + else + AppendToFields(tf,mesh,n2oPtr,dai,fs,timeStep,tsId); + return ; + } + else if(fieldName==FAMFIELD_FOR_CELLS && tf==MEDCoupling::ON_CELLS) + { + MEDFileMesh *mm(ms->getMeshWithName(mesh->getName())); + if(!mm) + throw MZCException("AppendMCFieldFrom : internal error 3 !"); + if(!daId) + throw MZCException("AppendMCFieldFrom : internal error 3 (not mcIdType) !"); + if(!n2oPtr) + mm->setFamilyFieldArr(mesh->getMeshDimension()-mm->getMeshDimension(),daId); + else + { + MCAuto dai2(daId->selectByTupleId(n2oPtr->begin(),n2oPtr->end())); + mm->setFamilyFieldArr(mesh->getMeshDimension()-mm->getMeshDimension(),dai2); + } + } + else if(fieldName==FAMFIELD_FOR_NODES || tf==MEDCoupling::ON_NODES) + { + MEDFileMesh *mm(ms->getMeshWithName(mesh->getName())); + if(!mm) + throw MZCException("AppendMCFieldFrom : internal error 4 !"); + if(!daId) + throw MZCException("AppendMCFieldFrom : internal error 4 (not mcIdType) !"); + if(!n2oPtr) + mm->setFamilyFieldArr(1,daId); + else + { + MCAuto dai2(daId->selectByTupleId(n2oPtr->begin(),n2oPtr->end())); + mm->setFamilyFieldArr(1,dai2); + } + } + } +} + +void PutAtLevelDealOrder(MEDFileData *mfd, int meshDimRel, const MicroField& mf, double timeStep, int tsId) +{ + if(!mfd) + throw MZCException("PutAtLevelDealOrder : internal error !"); + MEDFileMesh *mm(mfd->getMeshes()->getMeshAtPos(0)); + MEDFileUMesh *mmu(dynamic_cast(mm)); + if(!mmu) + throw MZCException("PutAtLevelDealOrder : internal error 2 !"); + MCAuto mesh(mf.getMesh()); + mesh->setName(mfd->getMeshes()->getMeshAtPos(0)->getName()); + MCAuto o2n(mesh->sortCellsInMEDFileFrmt()); + //const DataArrayIdType *o2nPtr(o2n); // todo: unused + MCAuto n2o; + mmu->setMeshAtLevel(meshDimRel,mesh); + const DataArrayIdType *n2oPtr(0); + if(o2n) + { + n2o=o2n->invertArrayO2N2N2O(mesh->getNumberOfCells()); + n2oPtr=n2o; + if(n2oPtr && n2oPtr->isIota(mesh->getNumberOfCells())) + n2oPtr=0; + if(n2oPtr) + mm->setRenumFieldArr(meshDimRel,n2o); + } + // + std::vector > cells(mf.getCellFields()); + for(std::vector >::const_iterator it=cells.begin();it!=cells.end();it++) + { + MCAuto da(*it); + AppendMCFieldFrom(MEDCoupling::ON_CELLS,mesh,mfd,da,n2oPtr,timeStep,tsId); + } +} + +void AssignSingleGTMeshes(MEDFileData *mfd, const std::vector< MicroField >& ms, double timeStep, int tsId) +{ + if(!mfd) + throw MZCException("AssignSingleGTMeshes : internal error !"); + MEDFileMesh *mm0(mfd->getMeshes()->getMeshAtPos(0)); + MEDFileUMesh *mm(dynamic_cast(mm0)); + if(!mm) + throw MZCException("AssignSingleGTMeshes : internal error 2 !"); + int meshDim(-std::numeric_limits::max()); + std::map > ms2; + for(std::vector< MicroField >::const_iterator it=ms.begin();it!=ms.end();it++) + { + const MEDCouplingUMesh *elt((*it).getMesh()); + if(elt) + { + int myMeshDim(elt->getMeshDimension()); + meshDim=std::max(meshDim,myMeshDim); + ms2[myMeshDim].push_back(*it); + } + } + if(ms2.empty()) + return ; + for(std::map >::const_iterator it=ms2.begin();it!=ms2.end();it++) + { + const std::vector< MicroField >& vs((*it).second); + if(vs.size()==1) + { + PutAtLevelDealOrder(mfd,(*it).first-meshDim,vs[0],timeStep,tsId); + } + else + { + MicroField merge(vs); + PutAtLevelDealOrder(mfd,(*it).first-meshDim,merge,timeStep,tsId); + } + } +} + +DataArrayDouble *BuildCoordsFrom(vtkPointSet *ds) +{ + if(!ds) + throw MZCException("BuildCoordsFrom : internal error !"); + vtkPoints *pts(ds->GetPoints()); + if(!pts) + throw MZCException("BuildCoordsFrom : internal error 2 !"); + vtkDataArray *data(pts->GetData()); + if(!data) + throw MZCException("BuildCoordsFrom : internal error 3 !"); + return ConvertVTKArrayToMCArrayDoubleForced(data); +} + +void AddNodeFields(MEDFileData *mfd, vtkDataSetAttributes *dsa, double timeStep, int tsId) +{ + if(!mfd || !dsa) + throw MZCException("AddNodeFields : internal error !"); + MEDFileMesh *mm(mfd->getMeshes()->getMeshAtPos(0)); + MEDFileUMesh *mmu(dynamic_cast(mm)); + if(!mmu) + throw MZCException("AddNodeFields : internal error 2 !"); + MCAuto mesh; + if(!mmu->getNonEmptyLevels().empty()) + mesh=mmu->getMeshAtLevel(0); + else + { + mesh=MEDCouplingUMesh::Build0DMeshFromCoords(mmu->getCoords()); + mesh->setName(mmu->getName()); + } + int nba(dsa->GetNumberOfArrays()); + for(int i=0;iGetArray(i)); + const char *name(arr->GetName()); + if(!arr) + continue; + MCAuto da(ConvertVTKArrayToMCArray(arr)); + da->setName(name); + AppendMCFieldFrom(MEDCoupling::ON_NODES,mesh,mfd,da,NULL,timeStep,tsId); + } +} + +std::vector > AddPartFields(const DataArrayIdType *part, vtkDataSetAttributes *dsa) +{ + std::vector< MCAuto > ret; + if(!dsa) + return ret; + int nba(dsa->GetNumberOfArrays()); + for(int i=0;iGetArray(i)); + if(!arr) + continue; + const char *name(arr->GetName()); + //int nbCompo(arr->GetNumberOfComponents()); // todo: unused + //vtkIdType nbTuples(arr->GetNumberOfTuples()); // todo: unused + MCAuto mcarr(ConvertVTKArrayToMCArray(arr)); + if(part) + mcarr=mcarr->selectByTupleId(part->begin(),part->end()); + mcarr->setName(name); + ret.push_back(mcarr); + } + return ret; +} + +std::vector > AddPartFields2(int bg, int end, vtkDataSetAttributes *dsa) +{ + std::vector< MCAuto > ret; + if(!dsa) + return ret; + int nba(dsa->GetNumberOfArrays()); + for(int i=0;iGetArray(i)); + if(!arr) + continue; + const char *name(arr->GetName()); + //int nbCompo(arr->GetNumberOfComponents()); // todo: unused + //vtkIdType nbTuples(arr->GetNumberOfTuples()); // todo: unused + MCAuto mcarr(ConvertVTKArrayToMCArray(arr)); + mcarr=mcarr->selectByTupleIdSafeSlice(bg,end,1); + mcarr->setName(name); + ret.push_back(mcarr); + } + return ret; +} + +void ConvertFromRectilinearGrid(MEDFileData *ret, vtkRectilinearGrid *ds, const std::vector& context, double timeStep, int tsId) +{ + if(!ds || !ret) + throw MZCException("ConvertFromRectilinearGrid : internal error !"); + // + MCAuto meshes(MEDFileMeshes::New()); + ret->setMeshes(meshes); + MCAuto fields(MEDFileFields::New()); + ret->setFields(fields); + // + MCAuto cmesh(MEDFileCMesh::New()); + meshes->pushMesh(cmesh); + MCAuto cmeshmc(MEDCouplingCMesh::New()); + vtkDataArray *cx(ds->GetXCoordinates()),*cy(ds->GetYCoordinates()),*cz(ds->GetZCoordinates()); + if(cx) + { + MCAuto arr(ConvertVTKArrayToMCArrayDoubleForced(cx)); + cmeshmc->setCoordsAt(0,arr); + } + if(cy) + { + MCAuto arr(ConvertVTKArrayToMCArrayDoubleForced(cy)); + cmeshmc->setCoordsAt(1,arr); + } + if(cz) + { + MCAuto arr(ConvertVTKArrayToMCArrayDoubleForced(cz)); + cmeshmc->setCoordsAt(2,arr); + } + std::string meshName(GetMeshNameWithContext(context)); + cmeshmc->setName(meshName); + cmesh->setMesh(cmeshmc); + std::vector > cellFs(AddPartFields(0,ds->GetCellData())); + for(std::vector >::const_iterator it=cellFs.begin();it!=cellFs.end();it++) + { + MCAuto da(*it); + AppendMCFieldFrom(MEDCoupling::ON_CELLS,cmeshmc,ret,da,NULL,timeStep,tsId); + } + std::vector > nodeFs(AddPartFields(0,ds->GetPointData())); + for(std::vector >::const_iterator it=nodeFs.begin();it!=nodeFs.end();it++) + { + MCAuto da(*it); + AppendMCFieldFrom(MEDCoupling::ON_NODES,cmeshmc,ret,da,NULL,timeStep,tsId); + } +} + +void ConvertFromPolyData(MEDFileData *ret, vtkPolyData *ds, const std::vector& context, double timeStep, int tsId) +{ + if(!ds || !ret) + throw MZCException("ConvertFromPolyData : internal error !"); + // + MCAuto meshes(MEDFileMeshes::New()); + ret->setMeshes(meshes); + MCAuto fields(MEDFileFields::New()); + ret->setFields(fields); + // + MCAuto umesh(MEDFileUMesh::New()); + meshes->pushMesh(umesh); + MCAuto coords(BuildCoordsFrom(ds)); + umesh->setCoords(coords); + umesh->setName(GetMeshNameWithContext(context)); + // + int offset(0); + std::vector< MicroField > ms; + vtkCellArray *cd(ds->GetVerts()); + if(cd) + { + MCAuto subMesh(BuildMeshFromCellArray(cd,coords,0,INTERP_KERNEL::NORM_POINT1)); + if((const MEDCouplingUMesh *)subMesh) + { + std::vector > cellFs(AddPartFields2(offset,offset+subMesh->getNumberOfCells(),ds->GetCellData())); + offset+=subMesh->getNumberOfCells(); + ms.push_back(MicroField(subMesh,cellFs)); + } + } + vtkCellArray *cc(ds->GetLines()); + if(cc) + { + MCAuto subMesh; + try + { + subMesh=BuildMeshFromCellArray(cc,coords,1,INTERP_KERNEL::NORM_SEG2); + } + catch(INTERP_KERNEL::Exception& e) + { + std::ostringstream oss; oss << "MEDWriter does not manage polyline cell type because MED file format does not support it ! Maybe it is the source of the problem ? The cause of this exception was " << e.what() << std::endl; + throw INTERP_KERNEL::Exception(oss.str()); + } + if((const MEDCouplingUMesh *)subMesh) + { + std::vector > cellFs(AddPartFields2(offset,offset+subMesh->getNumberOfCells(),ds->GetCellData())); + offset+=subMesh->getNumberOfCells(); + ms.push_back(MicroField(subMesh,cellFs)); + } + } + vtkCellArray *cb(ds->GetPolys()); + if(cb) + { + MCAuto subMesh(BuildMeshFromCellArray(cb,coords,2,INTERP_KERNEL::NORM_POLYGON)); + if((const MEDCouplingUMesh *)subMesh) + { + std::vector > cellFs(AddPartFields2(offset,offset+subMesh->getNumberOfCells(),ds->GetCellData())); + offset+=subMesh->getNumberOfCells(); + ms.push_back(MicroField(subMesh,cellFs)); + } + } + vtkCellArray *ca(ds->GetStrips()); + if(ca) + { + MCAuto ids; + MCAuto subMesh(BuildMeshFromCellArrayTriangleStrip(ca,coords,ids)); + if((const MEDCouplingUMesh *)subMesh) + { + std::vector > cellFs(AddPartFields(ids,ds->GetCellData())); + offset+=subMesh->getNumberOfCells(); + ms.push_back(MicroField(subMesh,cellFs)); + } + } + AssignSingleGTMeshes(ret,ms,timeStep,tsId); + AddNodeFields(ret,ds->GetPointData(),timeStep,tsId); +} + +void ConvertFromUnstructuredGrid(MEDFileData *ret, vtkUnstructuredGrid *ds, const std::vector& context, double timeStep, int tsId) +{ + if(!ds || !ret) + throw MZCException("ConvertFromUnstructuredGrid : internal error !"); + // + MCAuto meshes(MEDFileMeshes::New()); + ret->setMeshes(meshes); + MCAuto fields(MEDFileFields::New()); + ret->setFields(fields); + // + MCAuto umesh(MEDFileUMesh::New()); + meshes->pushMesh(umesh); + MCAuto coords(BuildCoordsFrom(ds)); + umesh->setCoords(coords); + umesh->setName(GetMeshNameWithContext(context)); + vtkIdType nbCells(ds->GetNumberOfCells()); + vtkCellArray *ca(ds->GetCells()); + if(!ca) + return ; + //vtkIdType nbEnt(ca->GetNumberOfConnectivityEntries()); // todo: unused + //vtkIdType *caPtr(ca->GetData()->GetPointer(0)); // todo: unused + vtkUnsignedCharArray *ct(ds->GetCellTypesArray()); + if(!ct) + throw MZCException("ConvertFromUnstructuredGrid : internal error"); + vtkIdTypeArray *cla(ds->GetCellLocationsArray()); + //const vtkIdType *claPtr(cla->GetPointer(0)); // todo: unused + if(!cla) + throw MZCException("ConvertFromUnstructuredGrid : internal error 2"); + const unsigned char *ctPtr(ct->GetPointer(0)); + std::map m(ComputeMapOfType()); + MCAuto lev(DataArrayInt::New()) ; lev->alloc(nbCells,1); + int *levPtr(lev->getPointer()); + for(vtkIdType i=0;i::iterator it(m.find(ctPtr[i])); + if(it!=m.end()) + { + const INTERP_KERNEL::CellModel& cm(INTERP_KERNEL::CellModel::GetCellModel((INTERP_KERNEL::NormalizedCellType)(*it).second)); + levPtr[i]=cm.getDimension(); + } + else + { + if(ctPtr[i]==VTK_POLY_VERTEX) + { + const INTERP_KERNEL::CellModel& cm(INTERP_KERNEL::CellModel::GetCellModel(INTERP_KERNEL::NORM_POINT1)); + levPtr[i]=cm.getDimension(); + } + else + { + std::ostringstream oss; oss << "ConvertFromUnstructuredGrid : at pos #" << i << " unrecognized VTK cell with type =" << ctPtr[i]; + throw MZCException(oss.str()); + } + } + } + MCAuto levs(lev->getDifferentValues()); + std::vector< MicroField > ms; + //vtkIdTypeArray *faces(ds->GetFaces()),*faceLoc(ds->GetFaceLocations()); // todo: unused + for(const int *curLev=levs->begin();curLev!=levs->end();curLev++) + { + MCAuto m0(MEDCouplingUMesh::New("",*curLev)); + m0->setCoords(coords); m0->allocateCells(); + MCAuto cellIdsCurLev(lev->findIdsEqual(*curLev)); + for(const mcIdType *cellId=cellIdsCurLev->begin();cellId!=cellIdsCurLev->end();cellId++) + { + int vtkType(ds->GetCellType(*cellId)); + std::map::iterator it(m.find(vtkType)); + INTERP_KERNEL::NormalizedCellType ct=it!=m.end()?(INTERP_KERNEL::NormalizedCellType)((*it).second):INTERP_KERNEL::NORM_POINT1; + if(ct!=INTERP_KERNEL::NORM_POLYHED && vtkType!=VTK_POLY_VERTEX) + { + vtkIdType sz(0); + const vtkIdType *pts(nullptr); + ds->GetCellPoints(*cellId, sz, pts); + std::vector conn2(pts,pts+sz); + m0->insertNextCell(ct,sz,conn2.data()); + } + else if(ct==INTERP_KERNEL::NORM_POLYHED) + { + // # de faces du polyèdre + vtkIdType nbOfFaces(0); + // connectivé des faces (numFace0Pts, id1, id2, id3, numFace1Pts,id1, id2, id3, ...) + const vtkIdType *facPtr(nullptr); + ds->GetFaceStream(*cellId, nbOfFaces, facPtr); + std::vector conn; + for(vtkIdType k=0;kinsertNextCell(ct,ToIdType(conn.size()),&conn[0]); + } + else + { + vtkIdType sz(0); + const vtkIdType *pts(nullptr); + ds->GetCellPoints(*cellId, sz, pts); + if(sz!=1) + throw MZCException("ConvertFromUnstructuredGrid : non single poly vertex not managed by MED !"); + m0->insertNextCell(ct,1,(const mcIdType*)pts); + } + } + std::vector > cellFs(AddPartFields(cellIdsCurLev,ds->GetCellData())); + ms.push_back(MicroField(m0,cellFs)); + } + AssignSingleGTMeshes(ret,ms,timeStep,tsId); + AddNodeFields(ret,ds->GetPointData(),timeStep,tsId); +} + +/////////////////// + +void WriteMEDFileFromVTKDataSet(MEDFileData *mfd, vtkDataSet *ds, const std::vector& context, double timeStep, int tsId) +{ + if(!ds || !mfd) + throw MZCException("Internal error in WriteMEDFileFromVTKDataSet."); + vtkPolyData *ds2(vtkPolyData::SafeDownCast(ds)); + vtkUnstructuredGrid *ds3(vtkUnstructuredGrid::SafeDownCast(ds)); + vtkRectilinearGrid *ds4(vtkRectilinearGrid::SafeDownCast(ds)); + if(ds2) + { + ConvertFromPolyData(mfd,ds2,context,timeStep,tsId); + } + else if(ds3) + { + ConvertFromUnstructuredGrid(mfd,ds3,context,timeStep,tsId); + } + else if(ds4) + { + ConvertFromRectilinearGrid(mfd,ds4,context,timeStep,tsId); + } + else + throw MZCException("Unrecognized vtkDataSet ! Sorry ! Try to convert it to UnstructuredGrid to be able to write it !"); +} + +void WriteMEDFileFromVTKMultiBlock(MEDFileData *mfd, vtkMultiBlockDataSet *ds, const std::vector& context, double timeStep, int tsId) +{ + if(!ds || !mfd) + throw MZCException("Internal error in WriteMEDFileFromVTKMultiBlock."); + int nbBlocks(ds->GetNumberOfBlocks()); + if(nbBlocks==1 && context.empty()) + { + vtkDataObject *uniqueElt(ds->GetBlock(0)); + if(!uniqueElt) + throw MZCException("Unique elt in multiblock is NULL !"); + vtkDataSet *uniqueEltc(vtkDataSet::SafeDownCast(uniqueElt)); + if(uniqueEltc) + { + WriteMEDFileFromVTKDataSet(mfd,uniqueEltc,context,timeStep,tsId); + return ; + } + } + for(int i=0;iGetBlock(i)); + std::vector context2; + context2.push_back(i); + if(!elt) + { + std::ostringstream oss; oss << "In context "; + std::copy(context.begin(),context.end(),std::ostream_iterator(oss," ")); + oss << " at pos #" << i << " elt is NULL !"; + throw MZCException(oss.str()); + } + vtkDataSet *elt1(vtkDataSet::SafeDownCast(elt)); + if(elt1) + { + WriteMEDFileFromVTKDataSet(mfd,elt1,context,timeStep,tsId); + continue; + } + vtkMultiBlockDataSet *elt2(vtkMultiBlockDataSet::SafeDownCast(elt)); + if(elt2) + { + WriteMEDFileFromVTKMultiBlock(mfd,elt2,context,timeStep,tsId); + continue; + } + std::ostringstream oss; oss << "In context "; + std::copy(context.begin(),context.end(),std::ostream_iterator(oss," ")); + oss << " at pos #" << i << " elt not recognized data type !"; + throw MZCException(oss.str()); + } +} + +void WriteMEDFileFromVTKGDS(MEDFileData *mfd, vtkDataObject *input, double timeStep, int tsId) +{ + if(!input || !mfd) + throw MZCException("WriteMEDFileFromVTKGDS : internal error !"); + std::vector context; + vtkDataSet *input1(vtkDataSet::SafeDownCast(input)); + if(input1) + { + WriteMEDFileFromVTKDataSet(mfd,input1,context,timeStep,tsId); + return ; + } + vtkMultiBlockDataSet *input2(vtkMultiBlockDataSet::SafeDownCast(input)); + if(input2) + { + WriteMEDFileFromVTKMultiBlock(mfd,input2,context,timeStep,tsId); + return ; + } + throw MZCException("WriteMEDFileFromVTKGDS : not recognized data type !"); +} + +void PutFamGrpInfoIfAny(MEDFileData *mfd, const std::string& meshName, const std::vector& groups, const std::vector& fams) +{ + if(!mfd) + return ; + if(meshName.empty()) + return ; + MEDFileMeshes *meshes(mfd->getMeshes()); + if(!meshes) + return ; + if(meshes->getNumberOfMeshes()!=1) + return ; + MEDFileMesh *mm(meshes->getMeshAtPos(0)); + if(!mm) + return ; + mm->setName(meshName); + for(std::vector::const_iterator it=fams.begin();it!=fams.end();it++) + mm->setFamilyId((*it).getName(),(*it).getID()); + for(std::vector::const_iterator it=groups.begin();it!=groups.end();it++) + mm->setFamiliesOnGroup((*it).getName(),(*it).getFamilies()); + MEDFileFields *fields(mfd->getFields()); + if(!fields) + return ; + for(int i=0;igetNumberOfFields();i++) + { + MEDFileAnyTypeFieldMultiTS *fmts(fields->getFieldAtPos(i)); + if(!fmts) + continue; + fmts->setMeshName(meshName); + } +} diff --git a/src/RateOfFlowThroughSection/plugin/RateOfFlowThroughSectionModule/VTKToMEDMem.h b/src/RateOfFlowThroughSection/plugin/RateOfFlowThroughSectionModule/VTKToMEDMem.h new file mode 100644 index 0000000..7cf1544 --- /dev/null +++ b/src/RateOfFlowThroughSection/plugin/RateOfFlowThroughSectionModule/VTKToMEDMem.h @@ -0,0 +1,89 @@ +// Copyright (C) 2017-2020 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +// Author : Anthony Geay (EDF R&D) + +#ifndef __VTKTOMEDMEM_HXX__ +#define __VTKTOMEDMEM_HXX__ + +#include "vtkSystemIncludes.h" //needed for exports + +#include "MEDCouplingRefCountObject.hxx" +#include "MEDCouplingMemArray.hxx" +#include "MEDCouplingFieldDouble.hxx" +#include "MEDCouplingFieldFloat.hxx" +#include "MEDCouplingFieldInt.hxx" +#include "MEDCouplingFieldInt64.hxx" +#include "MEDFileData.hxx" +#include "MEDFileField.hxx" +#include "MEDFileMesh.hxx" +#include "MEDLoaderTraits.hxx" + +#include +#include + +/////////////////// + +class vtkDataSet; + +class VTK_EXPORT MZCException : public std::exception +{ +public: + MZCException(const std::string& s):_reason(s) { } + virtual const char *what() const noexcept { return _reason.c_str(); } + virtual ~MZCException() noexcept { } +private: + std::string _reason; +}; + +namespace VTKToMEDMem +{ + class VTK_EXPORT Grp + { + public: + Grp(const std::string& name):_name(name) { } + void setFamilies(const std::vector& fams) { _fams=fams; } + std::string getName() const { return _name; } + std::vector getFamilies() const { return _fams; } + private: + std::string _name; + std::vector _fams; + }; + + class VTK_EXPORT Fam + { + public: + Fam(const std::string& name); + std::string getName() const { return _name; } + int getID() const { return _id; } + private: + std::string _name; + int _id; + }; +} + +class vtkDataObject; + +void VTK_EXPORT WriteMEDFileFromVTKDataSet(MEDCoupling::MEDFileData *mfd, vtkDataSet *ds, const std::vector& context, double timeStep, int tsId); + +void VTK_EXPORT WriteMEDFileFromVTKGDS(MEDCoupling::MEDFileData *mfd, vtkDataObject *input, double timeStep, int tsId); + +void VTK_EXPORT PutFamGrpInfoIfAny(MEDCoupling::MEDFileData *mfd, const std::string& meshName, const std::vector& groups, const std::vector& fams); + +#endif + diff --git a/src/RateOfFlowThroughSection/plugin/RateOfFlowThroughSectionModule/vtk.module b/src/RateOfFlowThroughSection/plugin/RateOfFlowThroughSectionModule/vtk.module new file mode 100644 index 0000000..3d39df7 --- /dev/null +++ b/src/RateOfFlowThroughSection/plugin/RateOfFlowThroughSectionModule/vtk.module @@ -0,0 +1,41 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +NAME + RateOfFlowThroughSectionModule +DEPENDS + VTK::CommonCore + VTK::CommonDataModel + VTK::CommonExecutionModel + VTK::FiltersCore + VTK::FiltersModeling + VTK::IOCore + VTK::IOGeometry + VTK::IOXML +PRIVATE_DEPENDS + VTK::CommonMisc + VTK::CommonSystem + VTK::FiltersGeneral + VTK::FiltersSources + VTK::FiltersGeometry + VTK::RenderingCore + VTK::vtksys + VTK::zlib + ParaView::VTKExtensionsMisc + ParaView::VTKExtensionsFiltersRendering diff --git a/src/RateOfFlowThroughSection/plugin/RateOfFlowThroughSectionModule/vtkExplodePolyLine.cxx b/src/RateOfFlowThroughSection/plugin/RateOfFlowThroughSectionModule/vtkExplodePolyLine.cxx new file mode 100644 index 0000000..a1a2060 --- /dev/null +++ b/src/RateOfFlowThroughSection/plugin/RateOfFlowThroughSectionModule/vtkExplodePolyLine.cxx @@ -0,0 +1,156 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +#include "vtkExplodePolyLine.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +vtkStandardNewMacro(vtkExplodePolyLine); + +class MyException : public std::exception +{ +public: + MyException(const std::string& s):_reason(s) { } + virtual const char *what() const throw() { return _reason.c_str(); } + virtual ~MyException() throw() { } +private: + std::string _reason; +}; + +//----------------------------------------------------------------------------- +void vtkExplodePolyLine::ExtractInfo(vtkInformationVector* inputVector, vtkSmartPointer& usgIn) +{ + vtkInformation* inputInfo(inputVector->GetInformationObject(0)); + vtkPolyData* input(vtkPolyData::SafeDownCast(inputInfo->Get(vtkDataObject::DATA_OBJECT()))); + if (!input) + { + vtkUnstructuredGrid* input2(vtkUnstructuredGrid::SafeDownCast(inputInfo->Get(vtkDataObject::DATA_OBJECT()))); + if(!input2) + vtkErrorMacro("Input data set is not vtkPolyData !"); + + vtkNew surface; + + surface->SetNonlinearSubdivisionLevel(0); + surface->SetInputData(input2); + surface->Update(); + usgIn = surface->GetOutput(); + return; + } + usgIn = vtkPolyData::SafeDownCast(input); +} + + +int vtkExplodePolyLine::FillInputPortInformation(int vtkNotUsed(port), vtkInformation *info) +{ + info->Set(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkDataSet"); + return 1; +} + +int vtkExplodePolyLine::FillOutputPortInformation(int vtkNotUsed(port), vtkInformation* info) +{ + info->Set(vtkDataObject::DATA_TYPE_NAME(), "vtkPolyData"); + return 1; +} + +int vtkExplodePolyLine::RequestData(vtkInformation* vtkNotUsed(request),vtkInformationVector** inputVector, vtkInformationVector* outputVector) +{ + try + { + vtkInformation* outInfo(outputVector->GetInformationObject(0)); + vtkPointSet* output(vtkPointSet::SafeDownCast(outInfo->Get(vtkDataObject::DATA_OBJECT()))); + vtkSmartPointer usgIn; + this->ExtractInfo(inputVector[0], usgIn); + vtkCellArray *cc(usgIn->GetLines()); + vtkIdType nbEntries(cc->GetNumberOfConnectivityEntries()); + const vtkIdType *conn(cc->GetData()->GetPointer(0)); + vtkNew pd; + vtkNew cb; + if(nbEntries<1) + { + throw MyException("Input PolyData looks empty !"); + } + if(conn[0] == nbEntries-1) + { + vtkIdType nbCells(nbEntries-2); + vtkNew connOut; + connOut->SetNumberOfComponents(1); + connOut->SetNumberOfTuples(3*nbCells); + vtkIdType *connOutPtr(connOut->GetPointer(0)); + for( auto iCell = 0 ; iCell < nbCells ; ++iCell, connOutPtr+=3 ) + { + connOutPtr[0] = 2; + connOutPtr[1] = conn[1+iCell]; + connOutPtr[2] = conn[1+iCell+1]; + } + cb->SetCells(nbCells,connOut); + } + else if(nbEntries == 3*cc->GetNumberOfCells()) + { + output->ShallowCopy(usgIn); + return 1; + } + else + { + throw MyException("Input PolyData is not containing only lines as required as precondition !"); + } + pd->SetLines(cb); + vtkNew pts; + // here DeepCopy is required, because GetMeshMTime of input PolyData is modified and generate useless computation afterwards in the pipeline. Bug ? + pts->DeepCopy(usgIn->GetPoints()); + pd->SetPoints(pts); + output->ShallowCopy(pd); + } + catch(MyException& e) + { + vtkErrorMacro("Exception has been thrown in vtkComplexMode::RequestInformation : " << e.what()); + } + return 1; +} + +//----------------------------------------------------------------------------- +void vtkExplodePolyLine::PrintSelf(ostream& os, vtkIndent indent) +{ + this->Superclass::PrintSelf(os, indent); +} diff --git a/src/RateOfFlowThroughSection/plugin/RateOfFlowThroughSectionModule/vtkExplodePolyLine.h b/src/RateOfFlowThroughSection/plugin/RateOfFlowThroughSectionModule/vtkExplodePolyLine.h new file mode 100644 index 0000000..74882d9 --- /dev/null +++ b/src/RateOfFlowThroughSection/plugin/RateOfFlowThroughSectionModule/vtkExplodePolyLine.h @@ -0,0 +1,82 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +#ifndef __vtkExplodePolyLine_h__ +#define __vtkExplodePolyLine_h__ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +class vtkDoubleArray; + +class VTK_EXPORT vtkExplodePolyLine : public vtkPointSetAlgorithm +{ +public: + static vtkExplodePolyLine* New(); + vtkTypeMacro(vtkExplodePolyLine, vtkPointSetAlgorithm); + void PrintSelf(ostream& os, vtkIndent indent) override; + + int FillInputPortInformation(int vtkNotUsed(port), vtkInformation *info) override; + int FillOutputPortInformation(int vtkNotUsed(port), vtkInformation* info) override; + +protected: + vtkExplodePolyLine() = default; + ~vtkExplodePolyLine() override = default; + + int RequestData(vtkInformation*, vtkInformationVector**, vtkInformationVector*) override; + + void ExtractInfo(vtkInformationVector* inputVector, vtkSmartPointer& usgIn); + +private: + vtkExplodePolyLine(const vtkExplodePolyLine&) = delete; + void operator=(const vtkExplodePolyLine&) = delete; +}; + +#endif diff --git a/src/RateOfFlowThroughSection/plugin/RateOfFlowThroughSectionModule/vtkRateOfFlowThroughSection.cxx b/src/RateOfFlowThroughSection/plugin/RateOfFlowThroughSectionModule/vtkRateOfFlowThroughSection.cxx new file mode 100644 index 0000000..7fbdb89 --- /dev/null +++ b/src/RateOfFlowThroughSection/plugin/RateOfFlowThroughSectionModule/vtkRateOfFlowThroughSection.cxx @@ -0,0 +1,567 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +// Author : Anthony Geay (EDF R&D) + +#include "vtkRateOfFlowThroughSection.h" +#include "vtkExplodePolyLine.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "VTKToMEDMem.h" + +#include +#include +#include + +vtkStandardNewMacro(vtkRateOfFlowThroughSection); + +/////////////////// + +static vtkDataSet *SplitSingleMultiBloc(vtkDataObject *ds) +{ + if(!ds) + throw INTERP_KERNEL::Exception("vtkSedimentDeposit SplitSingleMultiBloc : nullptr !"); + vtkMultiBlockDataSet *ds0(vtkMultiBlockDataSet::SafeDownCast(ds)); + if(!ds0) + { + vtkDataSet *ds00(vtkDataSet::SafeDownCast(ds)); + if(!ds00) + throw INTERP_KERNEL::Exception("vtkSedimentDeposit SplitSingleMultiBloc : neither a vtkMultiBlockDataSet nor a vtkDataSet !"); + return ds00; + } + if(ds0->GetNumberOfBlocks() != 1) + { + std::ostringstream oss; oss << "vtkSedimentDeposit SplitSingleMultiBloc : presence of multiblock dataset with not exactly one dataset in it ! (" << ds0->GetNumberOfBlocks() << ") !"; + throw INTERP_KERNEL::Exception(oss.str()); + } + vtkDataObject *ds1(ds0->GetBlock(0)); + vtkDataSet *ds1c(vtkDataSet::SafeDownCast(ds1)); + if(!ds1c) + throw INTERP_KERNEL::Exception("vtkSedimentDeposit SplitSingleMultiBloc : nullptr inside single multiblock element !"); + return ds1c; +} + +static void ExtractInfo(vtkInformationVector *inputVector, vtkUnstructuredGrid *&usgIn) +{ + vtkInformation *inputInfo(inputVector->GetInformationObject(0)); + vtkDataSet *input(0); + vtkDataSet *input0(vtkDataSet::SafeDownCast(inputInfo->Get(vtkDataObject::DATA_OBJECT()))); + vtkMultiBlockDataSet *input1(vtkMultiBlockDataSet::SafeDownCast(inputInfo->Get(vtkDataObject::DATA_OBJECT()))); + if (input0) + input = input0; + else + { + if (!input1) + throw INTERP_KERNEL::Exception("Input dataSet must be a DataSet or single elt multi block dataset expected !"); + if (input1->GetNumberOfBlocks() != 1) + throw INTERP_KERNEL::Exception("Input dataSet is a multiblock dataset with not exactly one block ! Use MergeBlocks or ExtractBlocks filter before calling this filter !"); + vtkDataObject *input2(input1->GetBlock(0)); + if (!input2) + throw INTERP_KERNEL::Exception("Input dataSet is a multiblock dataset with exactly one block but this single element is NULL !"); + vtkDataSet *input2c(vtkDataSet::SafeDownCast(input2)); + if (!input2c) + throw INTERP_KERNEL::Exception("Input dataSet is a multiblock dataset with exactly one block but this single element is not a dataset ! Use MergeBlocks or ExtractBlocks filter before calling this filter !"); + input = input2c; + } + if (!input) + throw INTERP_KERNEL::Exception("Input data set is NULL !"); + usgIn = vtkUnstructuredGrid::SafeDownCast(input); + if (!usgIn) + throw INTERP_KERNEL::Exception("Input data set is not an unstructured mesh ! This filter works only on unstructured meshes !"); +} + +//////////////////// + +void vtkRateOfFlowThroughSection::vtkInternal::fillTable(vtkTable *table) const +{ + { + vtkNew timeArr; + timeArr->SetName("Time"); + timeArr->SetNumberOfTuples(_data.size()); + double *pt(timeArr->GetPointer(0)); + { + std::size_t tmp(0); + std::for_each(pt, pt + _data.size(), [this, &tmp](double &val) { val = this->_data[tmp++].first; }); + } + table->AddColumn(timeArr); + } + { + vtkNew timeArr; + timeArr->SetName("Rate of flow"); + timeArr->SetNumberOfTuples(_data.size()); + double *pt(timeArr->GetPointer(0)); + { + std::size_t tmp(0); + std::for_each(pt, pt + _data.size(), [this, &tmp](double &val) { val = this->_data[tmp++].second; }); + } + table->AddColumn(timeArr); + } +} + +void vtkRateOfFlowThroughSection::vtkInternal::analyzeInputDataSets(vtkUnstructuredGrid *ds1, vtkDataSet *ds2) +{ + _recomputationOfMatrixNeeded = false; + if (_mt1 != ds1->GetMeshMTime()) + { + _mt1 = ds1->GetMeshMTime(); + _recomputationOfMatrixNeeded = true; + } + vtkUnstructuredGrid *ds2_0(vtkUnstructuredGrid::SafeDownCast(ds2)); + vtkPolyData *ds2_1(vtkPolyData::SafeDownCast(ds2)); + if (!ds2_0 && !ds2_1) + throw INTERP_KERNEL::Exception("analyzeInputDataSets : unexpected source !"); + if (ds2_0) + if (_mt2 != ds2_0->GetMeshMTime()) + { + _mt2 = ds2_0->GetMeshMTime(); + _recomputationOfMatrixNeeded = true; + } + if (ds2_1) + if (_mt2 != ds2_1->GetMeshMTime()) + { + _mt2 = ds2_1->GetMeshMTime(); + _recomputationOfMatrixNeeded = true; + } +} + +//////////////////// + +vtkRateOfFlowThroughSection::vtkRateOfFlowThroughSection() : NumberOfTimeSteps(0), CurrentTimeIndex(0), IsExecuting(false), Internal(nullptr) +{ + this->SetNumberOfInputPorts(2); + this->SetNumberOfOutputPorts(1); +} + +vtkRateOfFlowThroughSection::~vtkRateOfFlowThroughSection() +{ +} + +int vtkRateOfFlowThroughSection::RequestUpdateExtent(vtkInformation *, vtkInformationVector **inputVector, vtkInformationVector *vtkNotUsed(outputVector)) +{ + // vtkInformation* outInfo = outputVector->GetInformationObject(0); + vtkInformation *inInfo1 = inputVector[0]->GetInformationObject(0); + + // get the requested update extent + double *inTimes = inInfo1->Get(vtkStreamingDemandDrivenPipeline::TIME_STEPS()); + if (inTimes) + { + double timeReq = inTimes[this->CurrentTimeIndex]; + inInfo1->Set(vtkStreamingDemandDrivenPipeline::UPDATE_TIME_STEP(), timeReq); + } + + return 1; +} + +int vtkRateOfFlowThroughSection::RequestInformation(vtkInformation *request, vtkInformationVector **inputVector, vtkInformationVector *outputVector) +{ + //std::cerr << "########################################## vtkRateOfFlowThroughSection::RequestInformation ##########################################" << std::endl; + try + { + vtkUnstructuredGrid *usgIn(0); + ExtractInfo(inputVector[0], usgIn); + vtkInformation *info(outputVector->GetInformationObject(0)); + vtkInformation *inInfo(inputVector[0]->GetInformationObject(0)); + if (inInfo->Has(vtkStreamingDemandDrivenPipeline::TIME_STEPS())) + { + this->NumberOfTimeSteps = inInfo->Length(vtkStreamingDemandDrivenPipeline::TIME_STEPS()); + } + else + { + this->NumberOfTimeSteps = 0; + } + // The output of this filter does not contain a specific time, rather + // it contains a collection of time steps. Also, this filter does not + // respond to time requests. Therefore, we remove all time information + // from the output. + vtkInformation *outInfo(outputVector->GetInformationObject(0)); + if (outInfo->Has(vtkStreamingDemandDrivenPipeline::TIME_STEPS())) + { + outInfo->Remove(vtkStreamingDemandDrivenPipeline::TIME_STEPS()); + } + if (outInfo->Has(vtkStreamingDemandDrivenPipeline::TIME_RANGE())) + { + outInfo->Remove(vtkStreamingDemandDrivenPipeline::TIME_RANGE()); + } + } + catch (INTERP_KERNEL::Exception &e) + { + std::ostringstream oss; + oss << "Exception has been thrown in vtkRateOfFlowThroughSection::RequestInformation : " << e.what() << std::endl; + if (this->HasObserver("ErrorEvent")) + this->InvokeEvent("ErrorEvent", const_cast(oss.str().c_str())); + else + vtkOutputWindowDisplayErrorText(const_cast(oss.str().c_str())); + vtkObject::BreakOnError(); + return 0; + } + return 1; +} + +static MEDCoupling::MCAuto ToMedcoupling(MEDCoupling::MCAuto &mfd, vtkDataSet *usgIn) +{ + WriteMEDFileFromVTKDataSet(mfd, usgIn, {}, 0., 0); + MEDCoupling::MEDFileMeshes *ms(mfd->getMeshes()); + if (ms->getNumberOfMeshes() != 1) + throw INTERP_KERNEL::Exception("Unexpected number of meshes !"); + MEDCoupling::MEDFileMesh *mm(ms->getMeshAtPos(0)); + MEDCoupling::MEDFileUMesh *mmu(dynamic_cast(mm)); + if (!mmu) + throw INTERP_KERNEL::Exception("Expecting unstructured one !"); + return mmu->getMeshAtLevel(0); +} + +static void MyAssert(bool status, const std::string &message) +{ + if (!status) + throw INTERP_KERNEL::Exception(message); +} + +bool IsNameIn(const std::string& name, const std::vector& namesPossible) +{ + for(auto np : namesPossible) + { + std::size_t pos( name.find(np) ); + if(pos==std::string::npos) + continue; + std::string nameCpy(name); + std::string tmp(nameCpy.replace(pos,np.length(),std::string())); + if( tmp.find_first_not_of(" \t") == std::string::npos ) + return true; + } + return false; +} + +vtkDataArray *FindArrayHavingNameIn(vtkPointData *pd, const std::vector& namesPossible, std::function func) +{ + vtkDataArray *ret(nullptr); + for(auto i = 0; i < pd->GetNumberOfArrays() ; ++i ) + { + vtkDataArray *arr(pd->GetArray(i)); + std::string name(arr->GetName()); + if(IsNameIn(name,namesPossible)) + { + if( func(arr) ) + { + ret = arr; + break; + } + } + } + return ret; +} + +int vtkRateOfFlowThroughSection::RequestData(vtkInformation *request, vtkInformationVector **inputVector, vtkInformationVector *outputVector) +{ + //std::cerr << "########################################## vtkRateOfFlowThroughSection::RequestData ##########################################" << std::endl; + try + { + vtkUnstructuredGrid *usgIn(nullptr); + ExtractInfo(inputVector[0], usgIn); + // is this the first request + if (!this->IsExecuting) + { + request->Set(vtkStreamingDemandDrivenPipeline::CONTINUE_EXECUTING(), 1); + this->IsExecuting = true; + delete this->Internal; + this->Internal = new vtkInternal; + } + // + vtkInformation *sourceInfo(inputVector[1]->GetInformationObject(0)); + vtkDataObject *source(sourceInfo->Get(vtkDataObject::DATA_OBJECT())); + vtkDataSet *source1(SplitSingleMultiBloc(source)); + // + vtkNew epl; + epl->SetInputData(source1); + epl->Update(); + vtkDataSet *source2(epl->GetOutput()); + // + this->Internal->analyzeInputDataSets(usgIn, source1); + /////////////////////// + ////////////////////// + ///////////////////// + // + std::vector> &matrix(this->Internal->getMatrix()); + if (this->Internal->computationNeeded()) + { + MEDCoupling::MCAuto m, sec; + MEDCoupling::MCAuto mfd(MEDCoupling::MEDFileData::New()); + m = ToMedcoupling(mfd, usgIn); + { + MEDCoupling::MCAuto mfdSec(MEDCoupling::MEDFileData::New()); + sec = ToMedcoupling(mfd, source2); + } + { + MEDCoupling::MCAuto arr(m->getCoords()->keepSelectedComponents({2})); + MyAssert(arr->isUniform(0, 1e-12), "Expected coords array equal to 0 for Z axis."); + } + m->changeSpaceDimension(2, 0.); + { + MEDCoupling::MCAuto arr(sec->getCoords()->keepSelectedComponents({2})); + MyAssert(arr->isUniform(0, 1e-12), "Expected coords array equal to 0 for Z axis."); + } + sec->changeSpaceDimension(2, 0.); + // sec peut etre completement merdique avec des pts dupliques alors on filtre + { + bool tmp; + mcIdType tmpp; + MEDCoupling::MCAuto tmppp(sec->mergeNodes(1e-12, tmp, tmpp)); + } + sec->zipCoords(); + sec->removeDegenerated1DCells(); + // + MEDCoupling::MCAuto line_inter; + MEDCoupling::MCAuto cellid_in_2d, cellid_in1d; + { + MEDCoupling::MEDCouplingUMesh *tmp(nullptr), *tmp2(nullptr); + MEDCoupling::DataArrayIdType *tmp3(nullptr), *tmp4(nullptr); + MEDCoupling::MEDCouplingUMesh::Intersect2DMeshWith1DLine(m, sec, 1e-12, tmp, tmp2, tmp3, tmp4); + tmp->decrRef(); + line_inter = tmp2; + cellid_in_2d = tmp3; + cellid_in1d = tmp4; + } + line_inter->zipCoords(); + MEDCoupling::MCAuto TwoDcells(MEDCoupling::DataArrayIdType::New()); + TwoDcells->alloc(line_inter->getNumberOfCells(), 1); + { + auto t(cellid_in1d->begin()); + auto TwoDcellsPtr(TwoDcells->getPointer()); + for (std::size_t i = 0; i < cellid_in1d->getNumberOfTuples(); i++, t += 2, TwoDcellsPtr++) + { + int zeValue(-1); + std::for_each(t, t + 2, [&zeValue](const int &v) { if(v!=-1 && zeValue==-1) zeValue=v; }); + *TwoDcellsPtr = zeValue; + } + } + MEDCoupling::MCAuto notFreeStyle1DCells(TwoDcells->findIdsNotEqual(-1)); + MEDCoupling::MCAuto n2oCells(TwoDcells->selectByTupleId(notFreeStyle1DCells->begin(), notFreeStyle1DCells->end())); + TwoDcells = cellid_in_2d->selectByTupleId(n2oCells->begin(), n2oCells->end()); + MEDCoupling::MCAuto effective_line1d(line_inter->buildPartOfMySelf(notFreeStyle1DCells->begin(), notFreeStyle1DCells->end())); + MEDCoupling::MCAuto effective_2d_cells(m->buildPartOfMySelf(TwoDcells->begin(), TwoDcells->end())); + MEDCoupling::MCAuto o2n(effective_2d_cells->zipCoordsTraducer()); + MEDCoupling::MCAuto n2o(o2n->invertArrayO2N2N2O(effective_2d_cells->getNumberOfNodes())); + MEDCoupling::MCAuto effective_line1d_2(MEDCoupling::MEDCoupling1SGTUMesh::New(effective_line1d)); // change format of umesh to ease alg + MEDCoupling::MCAuto effective_2d_cells_2(MEDCoupling::MEDCoupling1SGTUMesh::New(effective_2d_cells)); // change format of umesh to ease alg + MyAssert(effective_2d_cells_2->getCellModelEnum() == INTERP_KERNEL::NORM_TRI3, "Only TRI3 are expected as input"); + + MEDCoupling::MCAuto conn1d(effective_line1d_2->getNodalConnectivity()->deepCopy()); + conn1d->rearrange(2); + MEDCoupling::MCAuto conn2d(effective_2d_cells_2->getNodalConnectivity()->deepCopy()); + conn2d->rearrange(3); + const MEDCoupling::DataArrayDouble *coo1d(effective_line1d->getCoords()), *coo2d(effective_2d_cells->getCoords()); + MyAssert(conn2d->getNumberOfTuples() == conn1d->getNumberOfTuples(), "Internal error !"); + + matrix.resize(effective_line1d->getNumberOfCells()); + { + const mcIdType *t1(conn1d->begin()), *t2(conn2d->begin()); + const double *coo1dPtr(coo1d->begin()), *coo2dPtr(coo2d->begin()); + double seg2[4], tri3[6]; + for (std::size_t i = 0; i < effective_line1d->getNumberOfCells(); i++, t1 += 2, t2 += 3) + { + double baryInfo[3]; + double length; + seg2[0] = coo1dPtr[2 * t1[0]]; + seg2[1] = coo1dPtr[2 * t1[0] + 1]; + seg2[2] = coo1dPtr[2 * t1[1]]; + seg2[3] = coo1dPtr[2 * t1[1] + 1]; + tri3[0] = coo2dPtr[2 * t2[0]]; + tri3[1] = coo2dPtr[2 * t2[0] + 1]; + tri3[2] = coo2dPtr[2 * t2[1]]; + tri3[3] = coo2dPtr[2 * t2[1] + 1]; + tri3[4] = coo2dPtr[2 * t2[2]]; + tri3[5] = coo2dPtr[2 * t2[2] + 1]; + MEDCoupling::DataArrayDouble::ComputeIntegralOfSeg2IntoTri3(seg2, tri3, baryInfo, length); + std::map &row(matrix[i]); + row[n2o->getIJ(t2[0], 0)] = baryInfo[0]; + row[n2o->getIJ(t2[1], 0)] = baryInfo[1]; + row[n2o->getIJ(t2[2], 0)] = baryInfo[2]; + } + } + MEDCoupling::MCAuto orthoField; + const MEDCoupling::DataArrayDouble *ortho(nullptr); + { + MEDCoupling::MCAuto tmp(effective_line1d->buildUnstructured()); + orthoField = tmp->buildOrthogonalField(); + this->Internal->setOrtho(orthoField->getArray()); + } + { + MEDCoupling::MCAuto measure(effective_line1d->getMeasureField(true)); + this->Internal->setMeasure(measure->getArray()); + } + } + const MEDCoupling::DataArrayDouble *ortho(this->Internal->getOrtho()), *measure_arr(this->Internal->getMeasure()); + /////////////////////// + ////////////////////// + ///////////////////// + constexpr char SEARCHED_FIELD_HAUTEUR[]="HAUTEUR D'EAU"; + constexpr char SEARCHED_FIELD_HAUTEUR2[]="WATER DEPTH"; + constexpr char SEARCHED_FIELD_SPEED[]="VITESSE *"; + constexpr char SEARCHED_FIELD_SPEED2[]="VELOCITY *"; + + vtkPointData *pd(usgIn->GetPointData()); + vtkDataArray *h_water_tmp(FindArrayHavingNameIn(pd,{ SEARCHED_FIELD_HAUTEUR, SEARCHED_FIELD_HAUTEUR2 },[](vtkDataArray *arr) { return arr->GetNumberOfComponents()==1; })); + vtkDataArray *speed_tmp(FindArrayHavingNameIn(pd,{ "VITESSE *", "VELOCITY *" },[](vtkDataArray *arr) { return arr->GetNumberOfComponents()>1; })); + vtkDoubleArray *h_water(vtkDoubleArray::SafeDownCast(h_water_tmp)), *speed(vtkDoubleArray::SafeDownCast(speed_tmp)); + std::ostringstream oss; + oss << "Expecting presence of float32 following fields : \"" << SEARCHED_FIELD_HAUTEUR << "\" or \"" << SEARCHED_FIELD_HAUTEUR2 << "\"and \"" << SEARCHED_FIELD_SPEED << "\" or \"" << SEARCHED_FIELD_SPEED2 << " \""; + MyAssert(h_water && speed, oss.str()); + MEDCoupling::MCAuto speed2(MEDCoupling::DataArrayFloat::New()); + speed2->alloc(speed->GetNumberOfTuples(), speed->GetNumberOfComponents()); + std::copy(speed->GetPointer(0), speed->GetPointer(0) + speed2->getNbOfElems(), speed2->rwBegin()); + MEDCoupling::MCAuto speed_arr(speed2->convertToDblArr()); + MEDCoupling::MCAuto h_water_arrf(MEDCoupling::DataArrayFloat::New()); + h_water_arrf->alloc(h_water->GetNumberOfTuples(), 1); + std::copy(h_water->GetPointer(0), h_water->GetPointer(0) + h_water_arrf->getNbOfElems(), h_water_arrf->rwBegin()); + /*MEDCoupling::MEDFileFloatField1TS *speed(nullptr),*h_water(nullptr); + { + MEDCoupling::MEDFileFields *fields(mfd->getFields()); + MyAssert(fields,"No arrays found in the input dataset !"); + MEDCoupling::MEDFileAnyTypeFieldMultiTS *speed_mts(fields->getFieldWithName(SEARCHED_FIELD_SPEED)); + MEDCoupling::MEDFileAnyTypeFieldMultiTS *h_water_mts(fields->getFieldWithName(SEARCHED_FIELD_HAUTEUR)); + std::ostringstream oss; oss << "Expecting single time step for following fields : \"" << SEARCHED_FIELD_HAUTEUR << "\" and \"" << SEARCHED_FIELD_SPEED << "\""; + MyAssert(speed_mts->getNumberOfTS()==1 && h_water_mts->getNumberOfTS()==1,oss.str()); + MEDCoupling::MEDFileFloatFieldMultiTS *speed_mts_2(dynamic_cast(speed_mts)); + MEDCoupling::MEDFileFloatFieldMultiTS *h_water_mts_2(dynamic_cast(h_water_mts)); + std::ostringstream oss2; oss2 << "Expecting presence of float32 following fields : \"" << SEARCHED_FIELD_HAUTEUR << "\" and \"" << SEARCHED_FIELD_SPEED << "\""; + MyAssert(!speed_mts_2 || !h_water_mts_2,oss2.str()); + speed=speed_mts_2->getTimeStepAtPos(0); h_water=h_water_mts_2->getTimeStepAtPos(0); + } + MEDCoupling::MCAuto speed_arr(speed->getUndergroundDataArray()->convertToDblArr());*/ + { + MEDCoupling::MCAuto arr(speed_arr->keepSelectedComponents({2})); + MyAssert(arr->isUniform(0, 1e-12), "Expected speed array equal to 0 for Z axis."); + } + speed_arr = speed_arr->keepSelectedComponents({0, 1}); + MEDCoupling::MCAuto h_water_arr(h_water_arrf->convertToDblArr()); + const double *speed_ptr(speed_arr->begin()), *ortho_ptr(ortho->begin()); + MEDCoupling::MCAuto h_out(MEDCoupling::DataArrayDouble::New()); + h_out->alloc(matrix.size(), 1); + h_out->fillWithZero(); + MEDCoupling::MCAuto v_out(MEDCoupling::DataArrayDouble::New()); + v_out->alloc(matrix.size(), 1); + v_out->fillWithZero(); + const double *orthoPtr(ortho->begin()); + for (std::size_t i = 0; i < matrix.size(); i++, speed_ptr += 2, ortho_ptr += 2) + { + const std::map &row(matrix[i]); + double h_out_value(0.), speed[2] = {0., 0.}; + for (auto it : row) + { + h_out_value += it.second * h_water_arr->getIJ(it.first, 0); + speed[0] += it.second * speed_arr->getIJ(it.first, 0); + speed[1] += it.second * speed_arr->getIJ(it.first, 1); + } + h_out->setIJ(i, 0, h_out_value); + v_out->setIJ(i, 0, speed[0]*orthoPtr[2*i+0] +speed[1]*orthoPtr[2*i+1] ); + } + double zeValue(0.); + { + MEDCoupling::MCAuto tmp1(MEDCoupling::DataArrayDouble::Multiply(h_out, v_out)); + tmp1 = MEDCoupling::DataArrayDouble::Multiply(tmp1, measure_arr); + zeValue = std::abs(tmp1->accumulate((std::size_t)0)); + } + double timeStep; + { + vtkInformation *inInfo(inputVector[0]->GetInformationObject(0)); + vtkDataObject *input(vtkDataObject::GetData(inInfo)); + timeStep = input->GetInformation()->Get(vtkDataObject::DATA_TIME_STEP()); + } + this->Internal->pushData(timeStep, zeValue); + this->CurrentTimeIndex++; + if (this->CurrentTimeIndex == this->NumberOfTimeSteps) + { + request->Remove(vtkStreamingDemandDrivenPipeline::CONTINUE_EXECUTING()); + this->CurrentTimeIndex = 0; + this->IsExecuting = false; + vtkInformation *outInfo(outputVector->GetInformationObject(0)); + vtkTable *output(vtkTable::SafeDownCast(outInfo->Get(vtkDataObject::DATA_OBJECT()))); + vtkNew table; + this->Internal->fillTable(table); + output->ShallowCopy(table); + } + } + catch (INTERP_KERNEL::Exception& e) + { + if (this->IsExecuting) + { + request->Remove(vtkStreamingDemandDrivenPipeline::CONTINUE_EXECUTING()); + this->CurrentTimeIndex = 0; + this->IsExecuting = false; + } + std::ostringstream oss; + oss << "Exception has been thrown in vtkRateOfFlowThroughSection::RequestData : " << e.what() << std::endl; + vtkErrorMacro(<< oss.str()); + return 0; + } + return 1; +} + +void vtkRateOfFlowThroughSection::PrintSelf(ostream &os, vtkIndent indent) +{ + this->Superclass::PrintSelf(os, indent); +} + +void vtkRateOfFlowThroughSection::SetSourceData(vtkDataObject *input) +{ + this->SetInputData(1, input); +} + +void vtkRateOfFlowThroughSection::SetSourceConnection(vtkAlgorithmOutput *algOutput) +{ + this->SetInputConnection(1, algOutput); +} + +int vtkRateOfFlowThroughSection::FillOutputPortInformation(int vtkNotUsed(port), vtkInformation *info) +{ + info->Set(vtkDataObject::DATA_TYPE_NAME(), "vtkTable"); + return 1; +} diff --git a/src/RateOfFlowThroughSection/plugin/RateOfFlowThroughSectionModule/vtkRateOfFlowThroughSection.h b/src/RateOfFlowThroughSection/plugin/RateOfFlowThroughSectionModule/vtkRateOfFlowThroughSection.h new file mode 100644 index 0000000..f9b45e8 --- /dev/null +++ b/src/RateOfFlowThroughSection/plugin/RateOfFlowThroughSectionModule/vtkRateOfFlowThroughSection.h @@ -0,0 +1,133 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +// Author : Anthony Geay (EDF R&D) + +#ifndef vtkRateOfFlowThroughSection_h__ +#define vtkRateOfFlowThroughSection_h__ + +#include +#include "vtkExplodePolyLine.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "VTKToMEDMem.h" + +#include +#include +#include + +class vtkMutableDirectedGraph; + +class VTK_EXPORT vtkRateOfFlowThroughSection : public vtkDataObjectAlgorithm +{ +public: + static vtkRateOfFlowThroughSection *New(); + vtkTypeMacro(vtkRateOfFlowThroughSection, vtkDataObjectAlgorithm); + void PrintSelf(ostream &os, vtkIndent indent) override; + + void SetSourceData(vtkDataObject *input); + + void SetSourceConnection(vtkAlgorithmOutput *algOutput); + + int FillOutputPortInformation(int, vtkInformation *) override; + +protected: + vtkRateOfFlowThroughSection(); + ~vtkRateOfFlowThroughSection() override; + + int RequestInformation(vtkInformation *, vtkInformationVector **, vtkInformationVector *) override; + int RequestData(vtkInformation *, vtkInformationVector **, vtkInformationVector *) override; + int RequestUpdateExtent(vtkInformation *, vtkInformationVector **, vtkInformationVector *) override; + + int NumberOfTimeSteps; + int CurrentTimeIndex; + bool IsExecuting; + class VTK_EXPORT vtkInternal { + public: + vtkInternal() {}; + void pushData(double timeStep, double value) { _data.emplace_back(timeStep, value); } + void fillTable(vtkTable *table) const; + void analyzeInputDataSets(vtkUnstructuredGrid *ds1, vtkDataSet *ds2); + bool computationNeeded() const + { + if (_recomputationOfMatrixNeeded) + { + _matrix.clear(); + } + return _recomputationOfMatrixNeeded; + }; + std::vector> &getMatrix() { return _matrix; } + void setOrtho(const MEDCoupling::DataArrayDouble *ortho) { _ortho.takeRef(ortho); } + const MEDCoupling::DataArrayDouble *getOrtho() const { return (const MEDCoupling::DataArrayDouble *)_ortho; } + void setMeasure(const MEDCoupling::DataArrayDouble *measure) { _measure.takeRef(measure); } + const MEDCoupling::DataArrayDouble *getMeasure() { return (const MEDCoupling::DataArrayDouble *)_measure; } + + private: + std::vector> _data; + vtkMTimeType _mt1 = 0; + vtkMTimeType _mt2 = 0; + mutable std::vector> _matrix; + bool _recomputationOfMatrixNeeded = true; + MEDCoupling::MCConstAuto _ortho; + MEDCoupling::MCConstAuto _measure; + }; + + vtkInternal *Internal; + +private: + vtkRateOfFlowThroughSection(const vtkRateOfFlowThroughSection &) = delete; + void operator=(const vtkRateOfFlowThroughSection &) = delete; +}; + +#endif diff --git a/src/RateOfFlowThroughSection/plugin/RateOfFlowThroughSectionModule/vtkSedimentDeposit.cxx b/src/RateOfFlowThroughSection/plugin/RateOfFlowThroughSectionModule/vtkSedimentDeposit.cxx new file mode 100644 index 0000000..7395c10 --- /dev/null +++ b/src/RateOfFlowThroughSection/plugin/RateOfFlowThroughSectionModule/vtkSedimentDeposit.cxx @@ -0,0 +1,632 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +// Author : Anthony Geay (EDF R&D) + +#include "vtkSedimentDeposit.h" +#include "vtkExplodePolyLine.h" +#include + + +vtkStandardNewMacro(vtkSedimentDeposit); + +/////////////////// + +static int GetNumberOfBlocs(vtkDataObject *ds) +{ + if(!ds) + throw INTERP_KERNEL::Exception("vtkSedimentDeposit SplitSingleMultiBloc : nullptr !"); + vtkMultiBlockDataSet *ds0(vtkMultiBlockDataSet::SafeDownCast(ds)); + if(!ds0) + { + vtkDataSet *ds00(vtkDataSet::SafeDownCast(ds)); + if(!ds00) + throw INTERP_KERNEL::Exception("vtkSedimentDeposit SplitSingleMultiBloc : neither a vtkMultiBlockDataSet nor a vtkDataSet !"); + return 1; + } + return ds0->GetNumberOfBlocks(); +} + +static vtkDataSet *SplitSingleMultiBloc(vtkDataObject *ds, int blockId) +{ + if(!ds) + throw INTERP_KERNEL::Exception("vtkSedimentDeposit SplitSingleMultiBloc : nullptr !"); + vtkMultiBlockDataSet *ds0(vtkMultiBlockDataSet::SafeDownCast(ds)); + if(!ds0) + { + vtkDataSet *ds00(vtkDataSet::SafeDownCast(ds)); + if(!ds00) + throw INTERP_KERNEL::Exception("vtkSedimentDeposit SplitSingleMultiBloc : neither a vtkMultiBlockDataSet nor a vtkDataSet !"); + if(blockId != 0) + throw INTERP_KERNEL::Exception("vtkSedimentDeposit SplitSingleMultiBloc : 0 expected !"); + return ds00; + } + if( blockId >= ds0->GetNumberOfBlocks() ) + { + std::ostringstream oss; oss << "vtkSedimentDeposit SplitSingleMultiBloc : presence of multiblock dataset with not exactly one dataset in it ! (" << ds0->GetNumberOfBlocks() << ") !"; + throw INTERP_KERNEL::Exception(oss.str()); + } + vtkDataObject *ds1(ds0->GetBlock(blockId)); + vtkDataSet *ds1c(vtkDataSet::SafeDownCast(ds1)); + if(!ds1c) + throw INTERP_KERNEL::Exception("vtkSedimentDeposit SplitSingleMultiBloc : nullptr inside single multiblock element !"); + return ds1c; +} + +static void ExtractInfo(vtkInformationVector *inputVector, vtkUnstructuredGrid *&usgIn) +{ + vtkInformation *inputInfo(inputVector->GetInformationObject(0)); + vtkDataSet *input = nullptr; + vtkDataSet *input0(vtkDataSet::SafeDownCast(inputInfo->Get(vtkDataObject::DATA_OBJECT()))); + vtkMultiBlockDataSet *input1(vtkMultiBlockDataSet::SafeDownCast(inputInfo->Get(vtkDataObject::DATA_OBJECT()))); + if (input0) + { + input = input0; + } + else + { + if (!input1) + { + throw INTERP_KERNEL::Exception("Input dataSet must be a DataSet or single elt multi block dataset expected !"); + } + if (input1->GetNumberOfBlocks() != 1) + { + throw INTERP_KERNEL::Exception("Input dataSet is a multiblock dataset with not exactly one block ! Use MergeBlocks or ExtractBlocks filter before calling this filter !"); + } + vtkDataObject *input2(input1->GetBlock(0)); + if (!input2) + { + throw INTERP_KERNEL::Exception("Input dataSet is a multiblock dataset with exactly one block but this single element is NULL !"); + } + vtkDataSet *input2c(vtkDataSet::SafeDownCast(input2)); + if (!input2c) + { + throw INTERP_KERNEL::Exception("Input dataSet is a multiblock dataset with exactly one block but this single element is not a dataset ! Use MergeBlocks or ExtractBlocks filter before calling this filter !"); + } + input = input2c; + } + if (!input) + { + throw INTERP_KERNEL::Exception("Input data set is NULL !"); + } + usgIn = vtkUnstructuredGrid::SafeDownCast(input); + if (!usgIn) + { + throw INTERP_KERNEL::Exception("Input data set is not an unstructured mesh ! This filter works only on unstructured meshes !"); + } +} + +std::string vtkSedimentDeposit::vtkInternal::getReprDependingPos(const std::string& origName) const +{ + if( _nb_of_curves == 1 ) + return origName; + std::ostringstream oss; + oss << origName << "_" << _curve_id; + return oss.str(); +} + +void vtkSedimentDeposit::vtkInternal::fillTable(vtkTable *table) const +{ + if( _curve_id == 0 ) + { + vtkNew timeArr; + std::string name(getReprDependingPos("Time")); + timeArr->SetName(name.c_str()); + timeArr->SetNumberOfTuples(_data.size()); + double *pt(timeArr->GetPointer(0)); + { + std::size_t tmp(0); + std::for_each(pt, pt + _data.size(), [this, &tmp](double &val) { val = std::get<0>(this->_data[tmp++]); }); + } + table->AddColumn(timeArr); + } + { + vtkNew timeArr; + std::string name(getReprDependingPos("Total")); + timeArr->SetName(name.c_str()); + timeArr->SetNumberOfTuples(_data.size()); + double *pt(timeArr->GetPointer(0)); + { + std::size_t tmp(0); + std::for_each(pt, pt + _data.size(), [this, &tmp](double &val) { val=std::get<1>(this->_data[tmp])+std::get<2>(this->_data[tmp]); tmp++; }); + } + table->AddColumn(timeArr); + } + { + vtkNew timeArr; + std::string name(getReprDependingPos("Positif")); + timeArr->SetName(name.c_str()); + timeArr->SetNumberOfTuples(_data.size()); + double *pt(timeArr->GetPointer(0)); + { + std::size_t tmp(0); + std::for_each(pt, pt + _data.size(), [this, &tmp](double &val) { val = std::get<1>(this->_data[tmp++]); }); + } + table->AddColumn(timeArr); + } + { + vtkNew timeArr; + std::string name(getReprDependingPos("Negatif")); + timeArr->SetName(name.c_str()); + timeArr->SetNumberOfTuples(_data.size()); + double *pt(timeArr->GetPointer(0)); + { + std::size_t tmp(0); + std::for_each(pt, pt + _data.size(), [this, &tmp](double &val) { val = std::get<2>(this->_data[tmp++]); }); + } + table->AddColumn(timeArr); + } +} + +void vtkSedimentDeposit::vtkInternal::analyzeInputDataSets(vtkUnstructuredGrid *ds1, vtkDataSet *ds2) +{ + _recomputationOfMatrixNeeded = false; + if (_mt1 != ds1->GetMeshMTime()) + { + _mt1 = ds1->GetMeshMTime(); + _recomputationOfMatrixNeeded = true; + } + vtkUnstructuredGrid *ds2_0(vtkUnstructuredGrid::SafeDownCast(ds2)); + vtkPolyData *ds2_1(vtkPolyData::SafeDownCast(ds2)); + if (!ds2_0 && !ds2_1) + throw INTERP_KERNEL::Exception("analyzeInputDataSets : unexpected source !"); + if (ds2_0) + if (_mt2 != ds2_0->GetMeshMTime()) + { + _mt2 = ds2_0->GetMeshMTime(); + _recomputationOfMatrixNeeded = true; + } + if (ds2_1) + if (_mt2 != ds2_1->GetMeshMTime()) + { + _mt2 = ds2_1->GetMeshMTime(); + _recomputationOfMatrixNeeded = true; + } +} + +static std::string Strip(const std::string& ins) +{ + std::string::size_type pos(ins.find_last_not_of(" \t")); + return ins.substr(0,pos+1); +} + +static vtkDataArray *FindFieldWithNameStripped(vtkFieldData *fd, const char *fieldNameToSearch) +{ + std::string keyToSearch(fieldNameToSearch); + std::vector candidates; + if(!fd) + throw INTERP_KERNEL::Exception("FindFieldWithNameStripped : nullptr instance !"); + auto nbArrays(fd->GetNumberOfArrays()); + for(auto i = 0 ; i < nbArrays ; ++i) + { + std::string arrName(fd->GetArrayName(i)); + if(Strip(arrName) == keyToSearch) + candidates.push_back(arrName); + } + if(candidates.size()!=1) + { + std::ostringstream oss; oss << "FindFieldWithNameStripped : not exactly one candidate for \"" << fieldNameToSearch << "\" !"; + throw INTERP_KERNEL::Exception(oss.str()); + } + return fd->GetArray(candidates[0].c_str()); +} + +//////////////////// + +vtkSedimentDeposit::vtkSedimentDeposit() : NumberOfTimeSteps(0), CurrentTimeIndex(0), IsExecuting(false) +{ + this->SetNumberOfInputPorts(2); + this->SetNumberOfOutputPorts(1); +} + +int vtkSedimentDeposit::RequestUpdateExtent(vtkInformation *info, vtkInformationVector **inputVector, vtkInformationVector *outputVector) +{ + // vtkInformation* outInfo = outputVector->GetInformationObject(0); + vtkInformation *inInfo1 = inputVector[0]->GetInformationObject(0); + + // get the requested update extent + double *inTimes = inInfo1->Get(vtkStreamingDemandDrivenPipeline::TIME_STEPS()); + if (inTimes) + { + double timeReq = inTimes[this->CurrentTimeIndex]; + inInfo1->Set(vtkStreamingDemandDrivenPipeline::UPDATE_TIME_STEP(), timeReq); + } + + return 1; + //return vtkDataObjectAlgorithm::RequestUpdateExtent(info,inputVector,outputVector); +} + +int vtkSedimentDeposit::RequestInformation(vtkInformation *request, vtkInformationVector **inputVector, vtkInformationVector *outputVector) +{ + //std::cerr << "########################################## vtkSedimentDeposit::RequestInformation ##########################################" << std::endl; + try + { + vtkUnstructuredGrid *usgIn(0); + ExtractInfo(inputVector[0], usgIn); + vtkInformation *info(outputVector->GetInformationObject(0)); + vtkInformation *inInfo(inputVector[0]->GetInformationObject(0)); + if (inInfo->Has(vtkStreamingDemandDrivenPipeline::TIME_STEPS())) + { + this->NumberOfTimeSteps = inInfo->Length(vtkStreamingDemandDrivenPipeline::TIME_STEPS()); + } + else + { + this->NumberOfTimeSteps = 0; + } + // The output of this filter does not contain a specific time, rather + // it contains a collection of time steps. Also, this filter does not + // respond to time requests. Therefore, we remove all time information + // from the output. + vtkInformation *outInfo(outputVector->GetInformationObject(0)); + if (outInfo->Has(vtkStreamingDemandDrivenPipeline::TIME_STEPS())) + { + outInfo->Remove(vtkStreamingDemandDrivenPipeline::TIME_STEPS()); + } + if (outInfo->Has(vtkStreamingDemandDrivenPipeline::TIME_RANGE())) + { + outInfo->Remove(vtkStreamingDemandDrivenPipeline::TIME_RANGE()); + } + } + catch (INTERP_KERNEL::Exception &e) + { + std::ostringstream oss; + oss << "Exception has been thrown in vtkSedimentDeposit::RequestInformation : " << e.what() << std::endl; + if (this->HasObserver("ErrorEvent")) + this->InvokeEvent("ErrorEvent", const_cast(oss.str().c_str())); + else + vtkOutputWindowDisplayErrorText(const_cast(oss.str().c_str())); + vtkObject::BreakOnError(); + return 0; + } + return 1; +} + +static MEDCoupling::MCAuto ToMedcoupling(MEDCoupling::MCAuto &mfd, vtkDataSet *usgIn) +{ + WriteMEDFileFromVTKDataSet(mfd, usgIn, {}, 0., 0); + MEDCoupling::MEDFileMeshes *ms(mfd->getMeshes()); + if (ms->getNumberOfMeshes() != 1) + throw INTERP_KERNEL::Exception("Unexpected number of meshes !"); + MEDCoupling::MEDFileMesh *mm(ms->getMeshAtPos(0)); + MEDCoupling::MEDFileUMesh *mmu(dynamic_cast(mm)); + if (!mmu) + throw INTERP_KERNEL::Exception("Expecting unstructured one !"); + return mmu->getMeshAtLevel(0); +} + +static void MyAssert(bool status, const std::string &message) +{ + if (!status) + { + throw INTERP_KERNEL::Exception(message); + } +} + +int vtkSedimentDeposit::RequestData(vtkInformation *request, vtkInformationVector **inputVector, vtkInformationVector *outputVector) +{ + //std::cerr << "########################################## vtkSedimentDeposit::RequestData ##########################################" << std::endl; + try + { + vtkUnstructuredGrid *usgIn(nullptr); + ExtractInfo(inputVector[0], usgIn); + vtkInformation *sourceInfo(inputVector[1]->GetInformationObject(0)); + vtkDataObject *source(sourceInfo->Get(vtkDataObject::DATA_OBJECT())); + int nbOfBlocks(GetNumberOfBlocs(source)); + // is this the first request + if (!this->IsExecuting) + { + request->Set(vtkStreamingDemandDrivenPipeline::CONTINUE_EXECUTING(), 1); + this->IsExecuting = true; + this->Internal2.resize(nbOfBlocks); + int rk(0); + std::for_each(this->Internal2.begin(),this->Internal2.end(),[&rk,nbOfBlocks](std::unique_ptr< vtkInternal >& elt) { elt.reset(new vtkInternal(rk++,nbOfBlocks)); }); + } + // + this->CurrentTimeIndex++; + vtkNew table; + // + for(int blockId = 0 ; blockId < nbOfBlocks ; ++blockId) + { + vtkDataSet *source1(SplitSingleMultiBloc(source,blockId)); + // + vtkNew epl; + epl->SetInputData(source1); + epl->Update(); + vtkDataSet *source2(epl->GetOutput()); + // + this->Internal2[blockId]->analyzeInputDataSets(usgIn, source1); + /////////////////////// + MEDCoupling::MCAuto &meshorig(this->Internal2[blockId]->meshOrigin()); + MEDCoupling::MCAuto &untouched_2d_cells(this->Internal2[blockId]->untouched2DCells()); + MEDCoupling::MCAuto &cells_at_boundary_origin(this->Internal2[blockId]->cellsAtBoundary()); + MEDCoupling::MCAuto ¢ers(this->Internal2[blockId]->centers()); + MEDCoupling::MCAuto &vol(this->Internal2[blockId]->measure()); + if (this->Internal2[blockId]->computationNeeded()) + { + MEDCoupling::MCAuto mesh, polygon; + MEDCoupling::MCAuto data(MEDCoupling::MEDFileData::New()); + meshorig = ToMedcoupling(data, usgIn); + meshorig->changeSpaceDimension(2, 0.); + mesh = meshorig->deepCopy(); //uncouple data and mesh + { + MEDCoupling::MCAuto tmp(MEDCoupling::MEDFileData::New()); + polygon = ToMedcoupling(tmp, source2); + } + { + MEDCoupling::MCAuto arr(polygon->getCoords()->keepSelectedComponents({2})); + if(!arr->isUniform(0, 1e-12)) + std::cerr << "Expected coords array equal to 0 for Z axis... Ignored" << std::endl; + } + polygon->changeSpaceDimension(2, 0.); + { + bool tmp; + mcIdType tmpp; + MEDCoupling::MCAuto tmppp(polygon->mergeNodes(1e-12, tmp, tmpp)); + } + MEDCoupling::MCAuto polygon_1sgt(MEDCoupling::MEDCoupling1SGTUMesh::New(polygon)); + MEDCoupling::MCAuto conn(polygon_1sgt->getNodalConnectivity()->deepCopy()); + conn->rearrange(2); + MEDCoupling::MCAuto notNullCellsPolygon; + { + MEDCoupling::MCAuto conn0(conn->keepSelectedComponents({0})), conn1(conn->keepSelectedComponents({1})); + MEDCoupling::MCAuto delta(MEDCoupling::DataArrayIdType::Substract(conn0, conn1)); + notNullCellsPolygon = delta->findIdsNotEqual(0); + } + { + MEDCoupling::MCAuto tmp(polygon_1sgt->buildUnstructured()); + polygon = tmp->buildPartOfMySelf(notNullCellsPolygon->begin(), notNullCellsPolygon->end()); + } + MEDCoupling::MCAuto polygon_2d(MEDCoupling::MEDCouplingUMesh::New("mesh", 2)); + { + MEDCoupling::MCAuto coo(polygon->getCoords()->deepCopy()); + polygon_2d->setCoords(coo); + } + polygon_2d->allocateCells(); + { + MEDCoupling::MCAuto tmp(MEDCoupling::MEDCoupling1SGTUMesh::New(polygon)); + conn.takeRef(tmp->getNodalConnectivity()); + } + conn->rearrange(2); + MEDCoupling::MCAuto conn2(conn->fromLinkedListOfPairToList()); + MyAssert(conn2->front() == conn2->back(), "Expected closed wire as input"); + conn2->popBackSilent(); + polygon_2d->insertNextCell(INTERP_KERNEL::NORM_POLYGON, conn2->getNbOfElems(), conn2->begin()); + bool clockWise(false); + // + {//false is very important to state if input polygon is clockwise (>0) or not (<0) + MEDCoupling::MCAuto area(polygon_2d->getMeasureField(false)); + clockWise = area->getIJ(0,0) > 0.0; + } + // + double bbox[4]; + mesh->getBoundingBox(bbox); + double TmpCenter[2] = {(bbox[0] + bbox[1]) / 2., (bbox[2] + bbox[3]) / 2.}; + double Tmpalpha = 1. / std::max(bbox[1] - bbox[0], bbox[3] - bbox[2]); + double MinusTmpCenter[2] = {-TmpCenter[0], -TmpCenter[1]}, Origin[2] = {0., 0.}; + mesh->translate(MinusTmpCenter); + mesh->scale(Origin, Tmpalpha); + polygon->translate(MinusTmpCenter); + polygon->scale(Origin, Tmpalpha); + MEDCoupling::MCAuto mesh2, line_inter; + MEDCoupling::MCAuto cellid_in_2d, cellid_in1d; + { + MEDCoupling::MEDCouplingUMesh *tmp(nullptr), *tmp2(nullptr); + MEDCoupling::DataArrayIdType *tmp3(nullptr), *tmp4(nullptr); + MEDCoupling::MEDCouplingUMesh::Intersect2DMeshWith1DLine(mesh, polygon, 1e-12, tmp, tmp2, tmp3, tmp4); + mesh2 = tmp; + line_inter = tmp2; + cellid_in_2d = tmp3; + cellid_in1d = tmp4; + } + MEDCoupling::MCAuto coo2(mesh2->getCoords()->deepCopy()); + coo2->applyLin(1. / Tmpalpha, 0.); + coo2->applyLin(1., TmpCenter[0], 0); + coo2->applyLin(1., TmpCenter[1], 1); + mesh2->setCoords(coo2); + + std::size_t side = clockWise?1:0; + MEDCoupling::MCAuto twodcells_to_remove(cellid_in1d->keepSelectedComponents({side})); + MEDCoupling::MCAuto twodcells_to_keep(cellid_in1d->keepSelectedComponents({(side + 1) % 2})); + MEDCoupling::MCAuto ids(twodcells_to_keep->findIdsNotEqual(-1)); + twodcells_to_keep = twodcells_to_keep->selectByTupleId(ids->begin(), ids->end()); + int hotspot(twodcells_to_keep->front()); + twodcells_to_remove = twodcells_to_remove->selectByTupleId(ids->begin(), ids->end()); + MEDCoupling::MCAuto allcells(twodcells_to_remove->buildComplement(mesh2->getNumberOfCells())); + MEDCoupling::MCAuto mesh2_without_cells_around_polygon(mesh2->buildPartOfMySelf(allcells->begin(), allcells->end())); + std::vector> grps; + { + std::vector tmp(mesh2_without_cells_around_polygon->partitionBySpreadZone()); + std::for_each(tmp.begin(), tmp.end(), [&grps](MEDCoupling::DataArrayIdType *grp) { grps.emplace_back(grp); }); + } + std::for_each(grps.begin(), grps.end(), [allcells](MEDCoupling::MCAuto &grp) { grp = allcells->selectByTupleId(grp->begin(), grp->end()); }); + MyAssert(grps.size() == 2, "Expecting 2 groups, 1 inside, 1 outside !"); + MEDCoupling::MCAuto zeGrp; + if (grps[0]->presenceOfValue(hotspot) && !grps[1]->presenceOfValue(hotspot)) + zeGrp.takeRef(grps[0]); + if (grps[1]->presenceOfValue(hotspot) && !grps[0]->presenceOfValue(hotspot)) + zeGrp.takeRef(grps[1]); + if (zeGrp.isNull()) + { + throw INTERP_KERNEL::Exception("Internal error : partitioning failed !"); + } + MEDCoupling::MCAuto mesh3(mesh2->buildPartOfMySelf(zeGrp->begin(), zeGrp->end())); + double totVol(0.), refVol(0.); + { + MEDCoupling::MCAuto tmp(mesh3->getMeasureField(true)), tmp2(polygon_2d->getMeasureField(true)); + totVol = tmp->accumulate(0); + refVol = tmp2->accumulate(0); + } + MyAssert(std::abs(totVol - refVol) / refVol < 1e-6, "The test of area conservation failed !"); + MEDCoupling::MCAuto original_cell_ids_2d(cellid_in_2d->selectByTupleId(zeGrp->begin(), zeGrp->end())); + original_cell_ids_2d->sort(); // les cells dans le referentiel original 2D + MEDCoupling::MCAuto all_cut_2d_cells(cellid_in1d->deepCopy()); + all_cut_2d_cells->rearrange(1); + all_cut_2d_cells->sort(); + all_cut_2d_cells = all_cut_2d_cells->buildUnique(); // les cells qui ont subit un split dans le referentiel de sortie 2D + MEDCoupling::MCAuto all_cut_2d_cells_origin(cellid_in_2d->selectByTupleId(all_cut_2d_cells->begin(), all_cut_2d_cells->end())); + untouched_2d_cells = original_cell_ids_2d->buildSubstraction(all_cut_2d_cells_origin); + MEDCoupling::MCAuto cells_at_boundary(all_cut_2d_cells->buildIntersection(zeGrp)); // les cellules decoupees dans le referentiel de sortie 2D + cells_at_boundary_origin = cellid_in_2d->selectByTupleId(cells_at_boundary->begin(), cells_at_boundary->end()); + mesh3 = mesh2->buildPartOfMySelf(cells_at_boundary->begin(), cells_at_boundary->end()); + { + MEDCoupling::MCAuto tmp(mesh3->getMeasureField(true)); + vol.takeRef(tmp->getArray()); + } + MEDCoupling::MCAuto tmp0(mesh->buildPartOfMySelf(untouched_2d_cells->begin(), untouched_2d_cells->end())); + mesh3->getBoundingBox(bbox); + double MinusZeCenter[2] = {-(bbox[0] + bbox[1]) / 2., -(bbox[2] + bbox[3]) / 2.}; + double alpha(1. / std::max(bbox[1] - bbox[0], bbox[3] - bbox[2])); + mesh3->translate(MinusZeCenter); + mesh3->scale(Origin, alpha); + MEDCoupling::MCAuto centers_around_zero(mesh3->computeCellCenterOfMass()); + centers = centers_around_zero->deepCopy(); + centers->applyLin(1. / alpha, -MinusZeCenter[0], 0); + centers->applyLin(1. / alpha, -MinusZeCenter[1], 1); + } + constexpr char SEARCHED_FIELD_EVOLUTION[] = "EVOLUTION"; + MEDCoupling::MCAuto f(MEDCoupling::MEDCouplingFieldDouble::New(MEDCoupling::ON_NODES)); + { + vtkPointData *pd(usgIn->GetPointData()); + vtkDataArray *evolution_tmp(FindFieldWithNameStripped(pd,SEARCHED_FIELD_EVOLUTION)); + vtkDoubleArray *evolution_tmp2(vtkDoubleArray::SafeDownCast(evolution_tmp)); + if (!evolution_tmp2) + { + std::ostringstream oss; + oss << "Internal error : " << SEARCHED_FIELD_EVOLUTION << " is expected to be of type float32 !"; + throw INTERP_KERNEL::Exception(oss.str()); + } + MyAssert(evolution_tmp2->GetNumberOfTuples() == meshorig->getNumberOfNodes(), "Mismatch of sizes !"); + MEDCoupling::MCAuto arr(MEDCoupling::DataArrayDouble::New()); + arr->alloc(meshorig->getNumberOfNodes(), 1); + std::copy(evolution_tmp2->GetPointer(0), evolution_tmp2->GetPointer(meshorig->getNumberOfNodes()), arr->getPointer()); + f->setArray(arr); + f->setMesh(meshorig); + f->checkConsistencyLight(); + } + MEDCoupling::MCAuto f_easy(f->buildSubPart(untouched_2d_cells->begin(), untouched_2d_cells->end())); + //f_easy->setName("easy"); + //f_easy->writeVTK("easy.vtu"); + MEDCoupling::MCAuto weights; + { + MEDCoupling::MCAuto tmp(f_easy->getDiscretization()->getMeasureField(f_easy->getMesh(), true)); + weights.takeRef(tmp->getArray()); + } + MEDCoupling::MCAuto positive_ids(f_easy->getArray()->findIdsGreaterThan(0.)); + MEDCoupling::MCAuto negative_ids(positive_ids->buildComplement(f_easy->getMesh()->getNumberOfNodes())); + double positive_part(0.), negative_part(0.); + { + MEDCoupling::MCAuto tmp(f_easy->getArray()->selectByTupleId(positive_ids->begin(), positive_ids->end())); + MEDCoupling::MCAuto tmp2(weights->selectByTupleId(positive_ids->begin(), positive_ids->end())); + MEDCoupling::MCAuto tmp3(MEDCoupling::DataArrayDouble::Multiply(tmp, tmp2)); + positive_part = tmp3->accumulate((std::size_t)0); + } + { + MEDCoupling::MCAuto tmp(f_easy->getArray()->selectByTupleId(negative_ids->begin(), negative_ids->end())); + MEDCoupling::MCAuto tmp2(weights->selectByTupleId(negative_ids->begin(), negative_ids->end())); + MEDCoupling::MCAuto tmp3(MEDCoupling::DataArrayDouble::Multiply(tmp, tmp2)); + negative_part = tmp3->accumulate((std::size_t)0); + } + MEDCoupling::MCAuto f_hard(f->buildSubPart(cells_at_boundary_origin->begin(), cells_at_boundary_origin->end())); + MEDCoupling::MCAuto hard_part(f_hard->getValueOnMulti(centers->begin(), centers->getNumberOfTuples())); + MEDCoupling::MCAuto positive_ids_hard(hard_part->findIdsGreaterThan(0.)); + MEDCoupling::MCAuto negative_ids_hard(positive_ids_hard->buildComplement(cells_at_boundary_origin->getNumberOfTuples())); + double positive_part_hard(0.), negative_part_hard(0.); + { + MEDCoupling::MCAuto tmp(hard_part->selectByTupleId(positive_ids_hard->begin(), positive_ids_hard->end())); + MEDCoupling::MCAuto tmp2(vol->selectByTupleId(positive_ids_hard->begin(), positive_ids_hard->end())); + MEDCoupling::MCAuto tmp3(MEDCoupling::DataArrayDouble::Multiply(tmp, tmp2)); + positive_part_hard = tmp3->accumulate((std::size_t)0); + } + { + MEDCoupling::MCAuto tmp(hard_part->selectByTupleId(negative_ids_hard->begin(), negative_ids_hard->end())); + MEDCoupling::MCAuto tmp2(vol->selectByTupleId(negative_ids_hard->begin(), negative_ids_hard->end())); + MEDCoupling::MCAuto tmp3(MEDCoupling::DataArrayDouble::Multiply(tmp, tmp2)); + negative_part_hard = tmp3->accumulate((std::size_t)0); + } + double timeStep; + { + vtkInformation *inInfo(inputVector[0]->GetInformationObject(0)); + vtkDataObject *input(vtkDataObject::GetData(inInfo)); + timeStep = input->GetInformation()->Get(vtkDataObject::DATA_TIME_STEP()); + } + this->Internal2[blockId]->pushData(timeStep, positive_part + positive_part_hard, negative_part + negative_part_hard); + if (this->CurrentTimeIndex == this->NumberOfTimeSteps) + { + this->Internal2[blockId]->fillTable(table); + } + } + if (this->CurrentTimeIndex == this->NumberOfTimeSteps) + { + request->Remove(vtkStreamingDemandDrivenPipeline::CONTINUE_EXECUTING()); + this->CurrentTimeIndex = 0; + this->IsExecuting = false; + } + vtkInformation *outInfo(outputVector->GetInformationObject(0)); + vtkTable *output(vtkTable::SafeDownCast(outInfo->Get(vtkDataObject::DATA_OBJECT()))); + output->ShallowCopy(table); + } + catch (INTERP_KERNEL::Exception &e) + { + if (this->IsExecuting) + { + request->Remove(vtkStreamingDemandDrivenPipeline::CONTINUE_EXECUTING()); + this->CurrentTimeIndex = 0; + this->IsExecuting = false; + } + std::ostringstream oss; + oss << "Exception has been thrown in vtkSedimentDeposit::RequestData : " << e.what() << std::endl; + vtkErrorMacro(<< oss.str()); + return 0; + } + return 1; +} + +void vtkSedimentDeposit::PrintSelf(ostream &os, vtkIndent indent) +{ + this->Superclass::PrintSelf(os, indent); +} + +void vtkSedimentDeposit::SetSourceData(vtkDataObject *input) +{ + this->SetInputData(1, input); +} + +void vtkSedimentDeposit::SetSourceConnection(vtkAlgorithmOutput *algOutput) +{ + this->SetInputConnection(1, algOutput); +} + +int vtkSedimentDeposit::FillOutputPortInformation(int vtkNotUsed(port), vtkInformation *info) +{ + info->Set(vtkDataObject::DATA_TYPE_NAME(), "vtkTable"); + return 1; +} + +bool vtkSedimentDeposit::vtkInternal::computationNeeded() const +{ + if (_recomputationOfMatrixNeeded) + { + _meshorig.nullify(); + _untouched_2d_cells.nullify(); + _cells_at_boundary_origin.nullify(); + _centers.nullify(); + _vol.nullify(); + } + return _recomputationOfMatrixNeeded; +} + diff --git a/src/RateOfFlowThroughSection/plugin/RateOfFlowThroughSectionModule/vtkSedimentDeposit.h b/src/RateOfFlowThroughSection/plugin/RateOfFlowThroughSectionModule/vtkSedimentDeposit.h new file mode 100644 index 0000000..0a7ec61 --- /dev/null +++ b/src/RateOfFlowThroughSection/plugin/RateOfFlowThroughSectionModule/vtkSedimentDeposit.h @@ -0,0 +1,131 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +// Author : Anthony Geay (EDF R&D) + +#ifndef vtkSedimentDeposit_h__ +#define vtkSedimentDeposit_h__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "VTKToMEDMem.h" + +#include +#include +#include +#include +#include + +class VTK_EXPORT vtkSedimentDeposit : public vtkDataObjectAlgorithm +{ +public: + static vtkSedimentDeposit *New(); + vtkTypeMacro(vtkSedimentDeposit, vtkDataObjectAlgorithm); + void PrintSelf(ostream &os, vtkIndent indent) override; + + void SetSourceData(vtkDataObject *input); + void SetSourceConnection(vtkAlgorithmOutput *algOutput); + + int FillOutputPortInformation(int, vtkInformation *) override; + +protected: + vtkSedimentDeposit(); + ~vtkSedimentDeposit() override = default; + + int RequestInformation(vtkInformation *, vtkInformationVector **, vtkInformationVector *) override; + int RequestData(vtkInformation *, vtkInformationVector **, vtkInformationVector *) override; + int RequestUpdateExtent(vtkInformation *, vtkInformationVector **, vtkInformationVector *) override; + + int NumberOfTimeSteps; + int CurrentTimeIndex; + bool IsExecuting; + class VTK_EXPORT vtkInternal + { + public: + vtkInternal(int curveId, int nbOfCurves) :_curve_id(curveId), _nb_of_curves(nbOfCurves) {} + void pushData(double timeStep, double positiveValue, double negativeValue) { _data.emplace_back(timeStep, positiveValue, negativeValue); } + void fillTable(vtkTable *table) const; + void analyzeInputDataSets(vtkUnstructuredGrid *ds1, vtkDataSet *ds2); + bool computationNeeded() const; + MEDCoupling::MCAuto &meshOrigin() { return _meshorig; } + MEDCoupling::MCAuto &untouched2DCells() { return _untouched_2d_cells; } + MEDCoupling::MCAuto &cellsAtBoundary() { return _cells_at_boundary_origin; } + MEDCoupling::MCAuto ¢ers() { return _centers; } + MEDCoupling::MCAuto &measure() { return _vol; } + std::string getReprDependingPos(const std::string& origName) const; + + private: + std::vector> _data; + vtkMTimeType _mt1 = 0; + vtkMTimeType _mt2 = 0; + int _curve_id; + int _nb_of_curves; + bool _recomputationOfMatrixNeeded = true; + mutable MEDCoupling::MCAuto _meshorig; + mutable MEDCoupling::MCAuto _untouched_2d_cells; + mutable MEDCoupling::MCAuto _cells_at_boundary_origin; + mutable MEDCoupling::MCAuto _centers; + mutable MEDCoupling::MCAuto _vol; + }; + std::vector< std::unique_ptr< vtkInternal > > Internal2; + +private: + vtkSedimentDeposit(const vtkSedimentDeposit &) = delete; + void operator=(const vtkSedimentDeposit &) = delete; +}; + +#endif diff --git a/src/RateOfFlowThroughSection/plugin/filters.xml b/src/RateOfFlowThroughSection/plugin/filters.xml new file mode 100644 index 0000000..7099009 --- /dev/null +++ b/src/RateOfFlowThroughSection/plugin/filters.xml @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + This property specifies the input to the Level Scalars filter. + + + + + + + + + The value of this property determines a polyline through which the rate of flow will be computed. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + This property specifies the input to the Level Scalars filter. + + + + + + + + + The value of this property determines a closed polyline inside which the deposite will be computed. + + + + + + + + + + + + + + + + diff --git a/src/RateOfFlowThroughSection/plugin/paraview.plugin b/src/RateOfFlowThroughSection/plugin/paraview.plugin new file mode 100644 index 0000000..6164e54 --- /dev/null +++ b/src/RateOfFlowThroughSection/plugin/paraview.plugin @@ -0,0 +1,29 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +NAME + RateOfFlowThroughSectionPlugin +DESCRIPTION + This plugin provides the RateOfFlowThroughSection filter. +REQUIRES_MODULES + VTK::CommonCore + VTK::IOCore + VTK::FiltersCore + VTK::FiltersSources + VTK::FiltersGeneral diff --git a/src/RateOfFlowThroughSection/script/TestCase.py b/src/RateOfFlowThroughSection/script/TestCase.py new file mode 100644 index 0000000..a51bada --- /dev/null +++ b/src/RateOfFlowThroughSection/script/TestCase.py @@ -0,0 +1,42 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +from MEDLoader import * + +fname="hydrau_test1.med" +meshName="mesh" +arr=DataArrayDouble([0,1,2,3,4,5]) +m=MEDCouplingCMesh() +m.setCoords(arr,arr) +m=m.buildUnstructured() +m.setName(meshName) +m.simplexize(0) +WriteMesh(fname,m,True) +# +f=MEDCouplingFieldDouble(ON_NODES) +f.setMesh(m) +f.setName("Field") +arr=m.getCoords().magnitude() +f.setArray(arr) +for i in range(10): + arr+=0.1 + f.setTime(float(i),i,0) + WriteFieldUsingAlreadyWrittenMesh(fname,f) + pass + diff --git a/src/RateOfFlowThroughSection/script/calcul_3.py b/src/RateOfFlowThroughSection/script/calcul_3.py new file mode 100644 index 0000000..ae25c85 --- /dev/null +++ b/src/RateOfFlowThroughSection/script/calcul_3.py @@ -0,0 +1,140 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +from medcoupling import * + +resu = "resu.med" +suffix_section = "5" +section = "section_{}.med".format(suffix_section) + +print("Working with {}".format(section)) +mm=MEDFileMesh.New(resu) +m=mm[0] +sec=ReadMeshFromFile(section) +assert(m.getCoords()[:,2].isUniform(0,1e-12)) +m.setCoords(m.getCoords()[:,[0,1]]) +assert(sec.getCoords()[:,2].isUniform(0,1e-12)) +sec.setCoords(sec.getCoords()[:,[0,1]]) +sec.mergeNodes(1e-12) +sec.zipCoords() +sec.removeDegenerated1DCells() +_,line_inter,cellid_in_2d,cellid_in1d = MEDCouplingUMesh.Intersect2DMeshWith1DLine(m,sec,1e-6) +line_inter.zipCoords() +# +TwoDcells=DataArrayInt(line_inter.getNumberOfCells()) +for i,t in enumerate(cellid_in1d): + candidates = [elt for elt in list(t) if elt != -1] + if len(candidates)==0: + TwoDcells[i]=-1 + TwoDcells[i]=candidates[0] + pass +notFreeStyle1DCells = TwoDcells.findIdsNotEqual(-1) +n2oCells = TwoDcells[notFreeStyle1DCells] +TwoDcells=cellid_in_2d[n2oCells] +# +effective_line1d=line_inter[notFreeStyle1DCells] +# effective_2d_cells - maillage contenant pour chaque cellule du maillage 1D coupé la cellule 2D de resu qui la contient +effective_2d_cells=m[TwoDcells] +o2n=effective_2d_cells.zipCoordsTraducer() +n2o=o2n.invertArrayO2N2N2O(effective_2d_cells.getNumberOfNodes()) +# +effective_line1d=MEDCoupling1SGTUMesh(effective_line1d) # change format of umesh to ease alg +effective_2d_cells=MEDCoupling1SGTUMesh(effective_2d_cells) # change format of umesh to ease alg +assert(effective_2d_cells.getCellModelEnum()==NORM_TRI3) +# +conn1d=effective_line1d.getNodalConnectivity()[:] ; conn1d.rearrange(2) +conn2d=effective_2d_cells.getNodalConnectivity()[:] ; conn2d.rearrange(3) +coo1d=effective_line1d.getCoords() +coo2d=effective_2d_cells.getCoords() +assert(len(conn2d)==len(conn1d)) +# coeffs coeffs_integ and n2o are elements for matrix +h_water_mts=MEDFileFloatFieldMultiTS(resu,"HAUTEUR D\'EAU",False) +speed_mts=MEDFileFloatFieldMultiTS(resu,"VITESSE",False) +assert(len(h_water_mts.getPflsReallyUsed())==0) +assert(len(speed_mts.getPflsReallyUsed())==0) + +h_out=DataArrayDouble(effective_line1d.getNumberOfCells()) ; h_out[:]=0. +v_out=DataArrayDouble(effective_line1d.getNumberOfCells()) ; v_out[:]=0. +# on calcule la matrice qui pour chaque cellule du la line 1D decoupee, donne +# la contribution de chacun des nodes de la cell 2D a laquelle elle appartient. +matrix=effective_line1d.getNumberOfCells()*[None] + +for i,(t1,t2) in enumerate(zip(conn1d,conn2d)): + seg2=coo1d[list(t1)] + tri3=coo2d[list(t2)] + baryInfo,length=DataArrayDouble.ComputeIntegralOfSeg2IntoTri3(seg2,tri3) + matrix[i]=[(n2o[i],j) for i,j in zip(list(t2),baryInfo)] + pass +ortho=effective_line1d.buildUnstructured().buildOrthogonalField().getArray() + +for ts in range(1): + coeffs_integ=DataArrayDouble(effective_2d_cells.getNumberOfNodes()) ; coeffs_integ[:]=0 + h_water=h_water_mts[ts] ; h_water.loadArrays() + speed=speed_mts[ts] ; speed.loadArrays() + h_water_arr=h_water.getUndergroundDataArray() + speed_arr=speed.getUndergroundDataArray().convertToDblArr() + assert(speed_arr[:,2].isUniform(0,1e-12)) + speed_arr=speed_arr[:,[0,1]] + for i in range(effective_line1d.getNumberOfCells()): + row=matrix[i] + h_out[i]=sum([b*h_water_arr[a] for a,b in row]) + speed=sum([b*speed_arr[a] for a,b in row]) + v_out[i] = float(DataArrayDouble.Dot(speed,ortho[i])[0]) + pass + zeValue = abs((h_out*v_out*effective_line1d.getMeasureField(True).getArray()).accumulate()[0]) + print("ts %d (%d) = %lf"%(ts,int(h_water.getTime()[-1]),zeValue)) + pass +# Bug 21733 +# Avant 1 == 1487.5 +# 5 == 1434.8 +# Apres 1 == 1600.814665 +# 5 == 1600.837195 +"""h_f=MEDCouplingFieldDouble(ON_CELLS) ; h_f.setMesh(effective_line1d) +h_f.setArray(h_out) +h_f.setName("HAUTEUR") +# +v_f=MEDCouplingFieldDouble(ON_CELLS) ; v_f.setMesh(effective_line1d) +v_f.setArray(v_out) +v_f.setName("VITESSE") +effective_line1d.write("line1d.med") +WriteFieldUsingAlreadyWrittenMesh("line1d.med",h_f) +WriteFieldUsingAlreadyWrittenMesh("line1d.med",v_f)""" + +"""# avec calcul_2 +ts 0 (0) = 1606.649455 +ts 1 (7200) = 1534.516771 +ts 2 (14400) = 1549.476531 +ts 3 (21600) = 1551.205389 +ts 4 (28800) = 1550.100327 +ts 5 (36000) = 1547.519873 +ts 6 (43200) = 1542.625840 +ts 7 (50400) = 1540.418416 +ts 8 (57600) = 1539.691491 +ts 9 (64800) = 1542.502136 +ts 10 (72000) = 1536.397618 +ts 11 (79200) = 1536.609661 +ts 12 (86400) = 1535.983922 +ts 13 (93600) = 1537.728434 +ts 14 (100800) = 1537.462885 +ts 15 (108000) = 1537.290268 +ts 16 (115200) = 1537.143315 +ts 17 (122400) = 1537.037729 +ts 18 (129600) = 1536.967132 +ts 19 (136800) = 1536.924427 +ts 20 (144000) = 1536.905037""" diff --git a/src/RateOfFlowThroughSection/script/calcul_sediment_deposit.py b/src/RateOfFlowThroughSection/script/calcul_sediment_deposit.py new file mode 100644 index 0000000..13c1709 --- /dev/null +++ b/src/RateOfFlowThroughSection/script/calcul_sediment_deposit.py @@ -0,0 +1,138 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +from medcoupling import * + +data_file="Res_sisy.med" +poly_file="contour.med" +cutoff=0. + +polygon=ReadMeshFromFile(poly_file) +data=MEDFileMesh.New(data_file) +mesh=data[0] +mesh.changeSpaceDimension(2,0.) +mesh=mesh.deepCopy() +assert(polygon.getCoords()[:,2].isUniform(0,1e-12)) +polygon.changeSpaceDimension(2,0.) +polygon.mergeNodes(1e-12) # +polygon_1sgt=MEDCoupling1SGTUMesh(polygon) +conn=polygon_1sgt.getNodalConnectivity().deepCopy() +conn.rearrange(2) +notNullCellsPolygon=(conn[:,0]-conn[:,1]).findIdsNotEqual(0) +polygon=polygon_1sgt.buildUnstructured()[notNullCellsPolygon] +polygon_2d=MEDCouplingUMesh("mesh",2) +polygon_2d.setCoords(polygon.getCoords().deepCopy()) +polygon_2d.allocateCells() +conn=MEDCoupling1SGTUMesh(polygon).getNodalConnectivity() +conn.rearrange(2) +conn2=conn.fromLinkedListOfPairToList() +assert(conn2[0]==conn2[-1]) +conn2.popBackSilent() +polygon_2d.insertNextCell(NORM_POLYGON,conn2.getValues()) +clockWise = polygon_2d.getMeasureField(False).getIJ(0,0) > 0. +# +side={True : 1 , False : 0}[clockWise] + +(Xmin,Xmax),(Ymin,Ymax)=mesh.getBoundingBox() +TmpCenter=( (Xmin+Xmax)/2., (Ymin+Ymax)/2. ) +Tmpalpha=1/max(Xmax-Xmin,Ymax-Ymin) +mesh.translate(-DataArrayDouble(TmpCenter,1,2)) +mesh.scale([0.,0.],Tmpalpha) +polygon.translate(-DataArrayDouble(TmpCenter,1,2)) +polygon.scale([0.,0.],Tmpalpha) +mesh2,line_inter,cellid_in_2d,cellid_in1d = MEDCouplingUMesh.Intersect2DMeshWith1DLine(mesh,polygon,1e-12) +coo=mesh2.getCoords().deepCopy() +coo2=coo*(1/Tmpalpha)+TmpCenter +mesh2.setCoords(coo2) + +twodcells_to_remove = cellid_in1d[:,side] +twodcells_to_keep = cellid_in1d[:,(side+1)%2] +ids=twodcells_to_keep.findIdsNotEqual(-1) +twodcells_to_keep=twodcells_to_keep[ids] +hotspot = twodcells_to_keep[0] + +twodcells_to_keep = twodcells_to_keep[ids] # les cells2D de bord du domaine dans le referentiel de sortie 2D +twodcells_to_remove.sort() +ids=twodcells_to_remove.findIdsNotEqual(-1) +twodcells_to_remove=twodcells_to_remove[ids] +allcells=twodcells_to_remove.buildComplement(mesh2.getNumberOfCells()) +mesh2_without_cells_around_polygon=mesh2[allcells] +grps=mesh2_without_cells_around_polygon.partitionBySpreadZone() +grps=[allcells[elt] for elt in grps] +assert(len(grps)==2) +zeGrp = None +if (hotspot in grps[0]) and (hotspot not in grps[1]): + zeGrp = grps[0] +if (hotspot not in grps[0]) and (hotspot in grps[1]): + zeGrp = grps[1] +if not zeGrp: + raise RuntimeError("Ooops") + pass +mesh3 = mesh2[zeGrp] +totVol = mesh3.getMeasureField(True).accumulate()[0] +refVol = polygon_2d.getMeasureField(True).accumulate()[0] +assert(abs((totVol-refVol)/refVol)<1e-6) +# +original_cell_ids_2d=cellid_in_2d[zeGrp] ; original_cell_ids_2d.sort() # les cells dans le referentiel original 2D +all_cut_2d_cells=cellid_in1d[:] +all_cut_2d_cells.rearrange(1) +all_cut_2d_cells.sort() +all_cut_2d_cells=all_cut_2d_cells.buildUnique() # les cells qui ont subit un split dans le referentiel de sortie 2D + +all_cut_2d_cells_origin=cellid_in_2d[all_cut_2d_cells] +untouched_2d_cells=original_cell_ids_2d.buildSubstraction(all_cut_2d_cells_origin) + +cells_at_boundary=all_cut_2d_cells.buildIntersection(zeGrp) # les cellules decoupées dans le referentiel de sortie 2D +cells_at_boundary_origin=cellid_in_2d[cells_at_boundary] +mesh3=mesh2[cells_at_boundary] +vol = mesh3.getMeasureField(True).getArray() +#volRef = mesh[cells_at_boundary_origin].getMeasureField(True).getArray() +#centers=mesh3.computeCellCenterOfMass() +# +tmp0=mesh[untouched_2d_cells] +(Xmin,Xmax),(Ymin,Ymax)=mesh3.getBoundingBox() +ZeCenter=( (Xmin+Xmax)/2., (Ymin+Ymax)/2. ) +alpha=1/max(Xmax-Xmin,Ymax-Ymin) +mesh3.translate(-DataArrayDouble(ZeCenter,1,2)) +mesh3.scale([0.,0.],alpha) +centers_around_zero=mesh3.computeCellCenterOfMass() +centers=centers_around_zero*(1/alpha)+ZeCenter +# +evolution_multiTS=MEDFileFloatFieldMultiTS(data_file,"EVOLUTION",False) +for ts in range(10): + evolution_1TS=evolution_multiTS[ts] + evolution_1TS.loadArrays() + f=evolution_1TS.field(data).convertToDblField() + f_easy=f[untouched_2d_cells] + f_easy.write("f_easy.med") # + weights = f_easy.getDiscretization().getMeasureField(f_easy.getMesh(),True).getArray() + positive_ids = f_easy.getArray().findIdsGreaterThan(cutoff) + negative_ids = positive_ids.buildComplement(f_easy.getMesh().getNumberOfNodes()) + positive_part = (f_easy.getArray()[positive_ids]*weights[positive_ids]).accumulate(0) + negative_part = (f_easy.getArray()[negative_ids]*weights[negative_ids]).accumulate(0) + # + f_hard = f[cells_at_boundary_origin] + hard_part = f_hard.getValueOnMulti(centers) + positive_ids_hard = hard_part.findIdsGreaterThan(cutoff) + negative_ids_hard = positive_ids_hard.buildComplement(len(cells_at_boundary_origin)) + positive_part_hard=(hard_part[positive_ids_hard]*vol[positive_ids_hard]).accumulate(0) + negative_part_hard=(hard_part[negative_ids_hard]*vol[negative_ids_hard]).accumulate(0) + print("Time step %d (%ld)"%(ts,evolution_1TS.getTime()[-1]),positive_part+positive_part_hard+negative_part+negative_part_hard) + evolution_1TS.unloadArrays() + pass diff --git a/src/RateOfFlowThroughSection/script/test_sediment_deposit.py b/src/RateOfFlowThroughSection/script/test_sediment_deposit.py new file mode 100644 index 0000000..3953b20 --- /dev/null +++ b/src/RateOfFlowThroughSection/script/test_sediment_deposit.py @@ -0,0 +1,224 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +# trace generated using paraview version 5.6.0-RC1-3-g7bafc2b + +#### import the simple module from the paraview +from paraview.simple import * +#### disable automatic camera reset on 'Show' +paraview.simple._DisableFirstRenderCameraReset() + +# create a new 'SerafinReader' +res_sisy_total_Tronqueres = SerafinReader(FileName='/home/H87074/TMP168_HYDRAU/17372_SEDIMENTS/Res_sisy_total_Tronque.res') + +# get animation scene +animationScene1 = GetAnimationScene() + +# update animation scene based on data timesteps +animationScene1.UpdateAnimationUsingDataTimeSteps() + +# get active view +renderView1 = GetActiveViewOrCreate('RenderView') +# uncomment following to set a specific view size +# renderView1.ViewSize = [1268, 607] + +# show data in view +res_sisy_total_TronqueresDisplay = Show(res_sisy_total_Tronqueres, renderView1) + +# trace defaults for the display properties. +res_sisy_total_TronqueresDisplay.Representation = 'Surface' +res_sisy_total_TronqueresDisplay.ColorArrayName = [None, ''] +res_sisy_total_TronqueresDisplay.OSPRayScaleArray = 'EVOLUTION ' +res_sisy_total_TronqueresDisplay.OSPRayScaleFunction = 'PiecewiseFunction' +res_sisy_total_TronqueresDisplay.SelectOrientationVectors = 'EVOLUTION ' +res_sisy_total_TronqueresDisplay.ScaleFactor = 466.36875000000003 +res_sisy_total_TronqueresDisplay.SelectScaleArray = 'EVOLUTION ' +res_sisy_total_TronqueresDisplay.GlyphType = 'Arrow' +res_sisy_total_TronqueresDisplay.GlyphTableIndexArray = 'EVOLUTION ' +res_sisy_total_TronqueresDisplay.GaussianRadius = 23.3184375 +res_sisy_total_TronqueresDisplay.SetScaleArray = ['POINTS', 'EVOLUTION '] +res_sisy_total_TronqueresDisplay.ScaleTransferFunction = 'PiecewiseFunction' +res_sisy_total_TronqueresDisplay.OpacityArray = ['POINTS', 'EVOLUTION '] +res_sisy_total_TronqueresDisplay.OpacityTransferFunction = 'PiecewiseFunction' +res_sisy_total_TronqueresDisplay.DataAxesGrid = 'GridAxesRepresentation' +res_sisy_total_TronqueresDisplay.SelectionCellLabelFontFile = '' +res_sisy_total_TronqueresDisplay.SelectionPointLabelFontFile = '' +res_sisy_total_TronqueresDisplay.PolarAxes = 'PolarAxesRepresentation' +res_sisy_total_TronqueresDisplay.ScalarOpacityUnitDistance = 173.44832869720128 + +# init the 'GridAxesRepresentation' selected for 'DataAxesGrid' +res_sisy_total_TronqueresDisplay.DataAxesGrid.XTitleFontFile = '' +res_sisy_total_TronqueresDisplay.DataAxesGrid.YTitleFontFile = '' +res_sisy_total_TronqueresDisplay.DataAxesGrid.ZTitleFontFile = '' +res_sisy_total_TronqueresDisplay.DataAxesGrid.XLabelFontFile = '' +res_sisy_total_TronqueresDisplay.DataAxesGrid.YLabelFontFile = '' +res_sisy_total_TronqueresDisplay.DataAxesGrid.ZLabelFontFile = '' + +# init the 'PolarAxesRepresentation' selected for 'PolarAxes' +res_sisy_total_TronqueresDisplay.PolarAxes.PolarAxisTitleFontFile = '' +res_sisy_total_TronqueresDisplay.PolarAxes.PolarAxisLabelFontFile = '' +res_sisy_total_TronqueresDisplay.PolarAxes.LastRadialAxisTextFontFile = '' +res_sisy_total_TronqueresDisplay.PolarAxes.SecondaryRadialAxesTextFontFile = '' + +# reset view to fit data +renderView1.ResetCamera() + +# update the view to ensure updated data information +renderView1.Update() + +# create a new 'ShapeReader' +contourVolume1shp = ShapeReader(FileName='/home/H87074/TMP168_HYDRAU/17372_SEDIMENTS/ContourVolume1.shp') + +# show data in view +contourVolume1shpDisplay = Show(contourVolume1shp, renderView1) + +# trace defaults for the display properties. +contourVolume1shpDisplay.Representation = 'Surface' +contourVolume1shpDisplay.ColorArrayName = [None, ''] +contourVolume1shpDisplay.OSPRayScaleFunction = 'PiecewiseFunction' +contourVolume1shpDisplay.SelectOrientationVectors = 'None' +contourVolume1shpDisplay.ScaleFactor = 28.764741869986757 +contourVolume1shpDisplay.SelectScaleArray = 'None' +contourVolume1shpDisplay.GlyphType = 'Arrow' +contourVolume1shpDisplay.GlyphTableIndexArray = 'None' +contourVolume1shpDisplay.GaussianRadius = 1.4382370934993378 +contourVolume1shpDisplay.SetScaleArray = [None, ''] +contourVolume1shpDisplay.ScaleTransferFunction = 'PiecewiseFunction' +contourVolume1shpDisplay.OpacityArray = [None, ''] +contourVolume1shpDisplay.OpacityTransferFunction = 'PiecewiseFunction' +contourVolume1shpDisplay.DataAxesGrid = 'GridAxesRepresentation' +contourVolume1shpDisplay.SelectionCellLabelFontFile = '' +contourVolume1shpDisplay.SelectionPointLabelFontFile = '' +contourVolume1shpDisplay.PolarAxes = 'PolarAxesRepresentation' + +# init the 'GridAxesRepresentation' selected for 'DataAxesGrid' +contourVolume1shpDisplay.DataAxesGrid.XTitleFontFile = '' +contourVolume1shpDisplay.DataAxesGrid.YTitleFontFile = '' +contourVolume1shpDisplay.DataAxesGrid.ZTitleFontFile = '' +contourVolume1shpDisplay.DataAxesGrid.XLabelFontFile = '' +contourVolume1shpDisplay.DataAxesGrid.YLabelFontFile = '' +contourVolume1shpDisplay.DataAxesGrid.ZLabelFontFile = '' + +# init the 'PolarAxesRepresentation' selected for 'PolarAxes' +contourVolume1shpDisplay.PolarAxes.PolarAxisTitleFontFile = '' +contourVolume1shpDisplay.PolarAxes.PolarAxisLabelFontFile = '' +contourVolume1shpDisplay.PolarAxes.LastRadialAxisTextFontFile = '' +contourVolume1shpDisplay.PolarAxes.SecondaryRadialAxesTextFontFile = '' + +# update the view to ensure updated data information +renderView1.Update() + +# create a new 'Transform' +transform1 = Transform(Input=contourVolume1shp) +transform1.Transform = 'Transform' + +# Properties modified on transform1.Transform +transform1.Transform.Translate = [800000.0, 6500000.0, 0.0] + +# Properties modified on transform1.Transform +transform1.Transform.Translate = [800000.0, 6500000.0, 0.0] + +# show data in view +transform1Display = Show(transform1, renderView1) + +# trace defaults for the display properties. +transform1Display.Representation = 'Surface' +transform1Display.ColorArrayName = [None, ''] +transform1Display.OSPRayScaleFunction = 'PiecewiseFunction' +transform1Display.SelectOrientationVectors = 'None' +transform1Display.ScaleFactor = 28.764741869986757 +transform1Display.SelectScaleArray = 'None' +transform1Display.GlyphType = 'Arrow' +transform1Display.GlyphTableIndexArray = 'None' +transform1Display.GaussianRadius = 1.4382370934993378 +transform1Display.SetScaleArray = [None, ''] +transform1Display.ScaleTransferFunction = 'PiecewiseFunction' +transform1Display.OpacityArray = [None, ''] +transform1Display.OpacityTransferFunction = 'PiecewiseFunction' +transform1Display.DataAxesGrid = 'GridAxesRepresentation' +transform1Display.SelectionCellLabelFontFile = '' +transform1Display.SelectionPointLabelFontFile = '' +transform1Display.PolarAxes = 'PolarAxesRepresentation' + +# init the 'GridAxesRepresentation' selected for 'DataAxesGrid' +transform1Display.DataAxesGrid.XTitleFontFile = '' +transform1Display.DataAxesGrid.YTitleFontFile = '' +transform1Display.DataAxesGrid.ZTitleFontFile = '' +transform1Display.DataAxesGrid.XLabelFontFile = '' +transform1Display.DataAxesGrid.YLabelFontFile = '' +transform1Display.DataAxesGrid.ZLabelFontFile = '' + +# init the 'PolarAxesRepresentation' selected for 'PolarAxes' +transform1Display.PolarAxes.PolarAxisTitleFontFile = '' +transform1Display.PolarAxes.PolarAxisLabelFontFile = '' +transform1Display.PolarAxes.LastRadialAxisTextFontFile = '' +transform1Display.PolarAxes.SecondaryRadialAxesTextFontFile = '' + +# hide data in view +Hide(contourVolume1shp, renderView1) + +# update the view to ensure updated data information +renderView1.Update() + +# set active source +SetActiveSource(res_sisy_total_Tronqueres) + +# create a new 'Sediment Deposit' +sedimentDeposit1 = SedimentDeposit(Input=res_sisy_total_Tronqueres, + Source=transform1) + +#### saving camera placements for all active views + +# current camera placement for renderView1 +renderView1.InteractionMode = '2D' +renderView1.CameraPosition = [487587.4375, 6685895.75, 9562.2955669413] +renderView1.CameraFocalPoint = [487587.4375, 6685895.75, 0.0] +renderView1.CameraParallelScale = 2474.9042076238147 + +#### uncomment the following to render all views +# RenderAllViews() +# alternatively, if you want to write images, you can use SaveScreenshot(...). + +# Create a new 'Line Chart View' +lineChartView1 = CreateView('XYChartView') +lineChartView1.ViewSize = [735, 607] +lineChartView1.ChartTitleFontFile = '' +lineChartView1.LeftAxisTitleFontFile = '' +lineChartView1.LeftAxisLabelFontFile = '' +lineChartView1.BottomAxisTitleFontFile = '' +lineChartView1.BottomAxisLabelFontFile = '' +lineChartView1.RightAxisLabelFontFile = '' +lineChartView1.TopAxisTitleFontFile = '' +lineChartView1.TopAxisLabelFontFile = '' + +# get layout +layout1 = GetLayout() + +# place view in the layout +layout1.AssignView(2, lineChartView1) + +# show data in view +rateOfFlowThroughSection1Display = Show(sedimentDeposit1, lineChartView1) + +# trace defaults for the display properties. +rateOfFlowThroughSection1Display.CompositeDataSetIndex = [0] +rateOfFlowThroughSection1Display.SeriesLabelPrefix = '' + +# update the view to ensure updated data information +lineChartView1.Update() diff --git a/src/RosetteCIH/CMakeLists.txt b/src/RosetteCIH/CMakeLists.txt new file mode 100644 index 0000000..ac9df2e --- /dev/null +++ b/src/RosetteCIH/CMakeLists.txt @@ -0,0 +1,49 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +cmake_minimum_required(VERSION 3.8) +project(RosetteCIH) +find_package(ParaView REQUIRED) + +include(GNUInstallDirs) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}") +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}") +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}") + +set("_paraview_plugin_default_${CMAKE_PROJECT_NAME}" ON) +paraview_plugin_scan( + ENABLE_BY_DEFAULT YES + PLUGIN_FILES "${CMAKE_CURRENT_SOURCE_DIR}/plugin/paraview.plugin" + PROVIDES_PLUGINS plugins + REQUIRES_MODULES required_modules) + +foreach(module IN LISTS required_modules) + if(NOT TARGET "${module}") + message("Missing required module: ${module}") + return() + endif() +endforeach() + +set(BUILD_SHARED_LIBS ON) +paraview_plugin_build( + RUNTIME_DESTINATION "${CMAKE_INSTALL_BINDIR}" + LIBRARY_DESTINATION "${CMAKE_INSTALL_LIBDIR}" + LIBRARY_SUBDIRECTORY "${PARAVIEW_PLUGIN_SUBDIR}" + PLUGINS ${plugins} + AUTOLOAD ${plugins}) diff --git a/src/RosetteCIH/plugin/CMakeLists.txt b/src/RosetteCIH/plugin/CMakeLists.txt new file mode 100644 index 0000000..b74f6e8 --- /dev/null +++ b/src/RosetteCIH/plugin/CMakeLists.txt @@ -0,0 +1,31 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +paraview_add_plugin(RosetteCIH + VERSION "1.0" + MODULES RosetteCIHFilters + MODULE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/RosetteCIHFilters/vtk.module" + SERVER_MANAGER_XML filters.xml +) + +install(TARGETS RosetteCIH + RUNTIME DESTINATION lib/paraview + LIBRARY DESTINATION lib/paraview + ARCHIVE DESTINATION lib/paraview +) diff --git a/src/RosetteCIH/plugin/RosetteCIHFilters/CMakeLists.txt b/src/RosetteCIH/plugin/RosetteCIHFilters/CMakeLists.txt new file mode 100644 index 0000000..fee6269 --- /dev/null +++ b/src/RosetteCIH/plugin/RosetteCIHFilters/CMakeLists.txt @@ -0,0 +1,27 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +set(classes + vtkRosetteCIH +) + +vtk_module_add_module(RosetteCIHFilters + FORCE_STATIC + CLASSES ${classes} +) diff --git a/src/RosetteCIH/plugin/RosetteCIHFilters/vtk.module b/src/RosetteCIH/plugin/RosetteCIHFilters/vtk.module new file mode 100644 index 0000000..8e10a07 --- /dev/null +++ b/src/RosetteCIH/plugin/RosetteCIHFilters/vtk.module @@ -0,0 +1,42 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +NAME + RosetteCIHFilters +DEPENDS + VTK::CommonCore + VTK::CommonDataModel + VTK::CommonExecutionModel + VTK::FiltersCore + VTK::FiltersGeometry + VTK::FiltersModeling + VTK::FiltersSources + VTK::IOCore + VTK::IOGeometry + VTK::IOXML + ParaView::VTKExtensionsFiltersGeneral + ParaView::VTKExtensionsMisc +PRIVATE_DEPENDS + VTK::CommonMisc + VTK::CommonSystem + VTK::FiltersGeneral + VTK::RenderingCore + VTK::vtksys + VTK::zlib + VTK::IOInfovis diff --git a/src/RosetteCIH/plugin/RosetteCIHFilters/vtkRosetteCIH.cxx b/src/RosetteCIH/plugin/RosetteCIHFilters/vtkRosetteCIH.cxx new file mode 100644 index 0000000..8f38c11 --- /dev/null +++ b/src/RosetteCIH/plugin/RosetteCIHFilters/vtkRosetteCIH.cxx @@ -0,0 +1,523 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +#include "vtkRosetteCIH.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//----------------------------------------------------------------------------- +void vtkRosetteCIH::ExtractInfo( + vtkInformationVector* inputVector, vtkSmartPointer& usgIn) +{ + vtkInformation* inputInfo(inputVector->GetInformationObject(0)); + vtkDataSet* input(0); + vtkDataSet* input0(vtkDataSet::SafeDownCast(inputInfo->Get(vtkDataObject::DATA_OBJECT()))); + vtkMultiBlockDataSet* input1( + vtkMultiBlockDataSet::SafeDownCast(inputInfo->Get(vtkDataObject::DATA_OBJECT()))); + if (input0) + { + input = input0; + } + else + { + if (!input1) + { + vtkErrorMacro("Input dataSet must be a DataSet or single elt multi block dataset expected !"); + return; + } + if (input1->GetNumberOfBlocks() != 1) + { + vtkErrorMacro("Input dataSet is a multiblock dataset with not exactly one block ! Use " + "MergeBlocks or ExtractBlocks filter before calling this filter !"); + return; + } + vtkDataObject* input2(input1->GetBlock(0)); + if (!input2) + { + vtkErrorMacro("Input dataSet is a multiblock dataset with exactly one block but this single " + "element is NULL !"); + return; + } + vtkDataSet* input2c(vtkDataSet::SafeDownCast(input2)); + if (!input2c) + { + vtkErrorMacro( + "Input dataSet is a multiblock dataset with exactly one block but this single element is " + "not a dataset ! Use MergeBlocks or ExtractBlocks filter before calling this filter !"); + return; + } + input = input2c; + } + + if (!input) + { + vtkErrorMacro("Input data set is NULL !"); + return; + } + + usgIn = vtkUnstructuredGrid::SafeDownCast(input); + if (!usgIn) + { + if (!input1) + { + vtkNew mb; + vtkNew cd; + mb->AddInputData(input); + cd->SetInputConnection(mb->GetOutputPort()); + cd->SetMergePoints(0); + cd->Update(); + usgIn = cd->GetOutput(); + } + else + { + vtkNew filter; + filter->SetMergePoints(0); + filter->SetInputData(input1); + filter->Update(); + usgIn = filter->GetOutput(); + } + } +} + +//----------------------------------------------------------------------------- +vtkStandardNewMacro(vtkRosetteCIH); + +//----------------------------------------------------------------------------- +vtkSmartPointer vtkRosetteCIH::GenerateGlyphLinesFor(vtkUnstructuredGrid* usgIn, + const char* keyPoint, const char COMPRESS_TRACTION[]) +{ + vtkFieldData* dsa(usgIn->GetCellData()); + std::string arrayForGlyph(this->RetrieveFieldForGlyph(usgIn, keyPoint)); + int compoId(-1); + vtkDoubleArray* arrayForPosNeg2(RetrieveFieldForPost(usgIn, keyPoint, compoId)); + // vtkAbstractArray *arrayForPosNeg(dsa->GetAbstractArray(FIELD_NAME_2)); + // vtkDoubleArray *arrayForPosNeg2(vtkDoubleArray::SafeDownCast(arrayForPosNeg)); + int nbCompo(arrayForPosNeg2->GetNumberOfComponents()); + vtkIdType nbTuples(arrayForPosNeg2->GetNumberOfTuples()); + vtkNew compressionOrTraction; + compressionOrTraction->SetNumberOfComponents(1); + compressionOrTraction->SetNumberOfTuples(nbTuples); + compressionOrTraction->SetName(COMPRESS_TRACTION); + const double* pt(arrayForPosNeg2->GetPointer(0)); + double* ptOut(compressionOrTraction->GetPointer(0)); + for (vtkIdType i = 0; i < nbTuples; i++) + { + if (pt[i * nbCompo + compoId] > 0.) + ptOut[i] = 1.; + else + ptOut[i] = -1.; + } + int arrId(dsa->AddArray(compressionOrTraction)); + // + vtkNew glyph; + glyph->SetInputData(usgIn); + glyph->SetGlyphMode(0); // vtkPVGlyphFilter::ALL_POINTS + glyph->SetVectorScaleMode(0); // vtkPVGlyphFilter::SCALE_BY_MAGNITUDE + + // + vtkNew arrow; + glyph->SetSourceConnection(arrow->GetOutputPort()); + // idx,port,connection,fieldAssociation,name + glyph->SetInputArrayToProcess( + 0, 0, 0, vtkDataObject::FIELD_ASSOCIATION_CELLS, arrayForGlyph.c_str()); // idx==0 -> scaleArray + glyph->SetInputArrayToProcess(1, 0, 0, vtkDataObject::FIELD_ASSOCIATION_CELLS, + arrayForGlyph.c_str()); // idx==1 -> orientationArray + glyph->SetScaleFactor(this->ScaleFactor); + glyph->Update(); + + return vtkSmartPointer(glyph->GetOutput()); +} + +//----------------------------------------------------------------------------- +void vtkRosetteCIH::PostTraitementT1etT2( + vtkUnstructuredGrid* usgIn, vtkUnstructuredGrid* output) +{ + constexpr char COMPRESS_TRACTION[] = "CompressionOrTraction"; + // "RESUNL__SIRO_ELEM_T1_Vector" , "RESUNL__SIRO_ELEM_T1" + vtkSmartPointer gl1 = + this->GenerateGlyphLinesFor(usgIn, "T1", COMPRESS_TRACTION); + vtkSmartPointer gl2 = + this->GenerateGlyphLinesFor(usgIn, "T2", COMPRESS_TRACTION); + // + vtkNew mb; + vtkNew cd; + mb->AddInputData(gl1); + mb->AddInputData(gl2); + cd->SetInputConnection(mb->GetOutputPort()); + cd->SetMergePoints(0); + cd->Update(); + // + output->ShallowCopy(cd->GetOutput()); + // + vtkFieldData* dsa(output->GetPointData()); + int nbOfArrays(dsa->GetNumberOfArrays()); + for (int i = nbOfArrays - 1; i >= 0; i--) + { + const char* arrName(dsa->GetArrayName(i)); + if (std::string(arrName) != COMPRESS_TRACTION) + { + dsa->RemoveArray(i); + } + } + output->GetPointData()->SetActiveAttribute(0, vtkDataSetAttributes::SCALARS); +} + +//----------------------------------------------------------------------------- +int vtkRosetteCIH::ComponentIdOfArray(vtkAbstractArray* array, const std::string& compoName) +{ + int nbCompo(array->GetNumberOfComponents()); + int ret(-1); + for (int i = 0; i < nbCompo; i++) + { + if (compoName == array->GetComponentName(i)) + { + if (ret != -1) + { + vtkErrorMacro("ComponentIdOfArray : already found !"); + return ret; + } + ret = i; + } + } + if (ret == -1) + { + vtkErrorMacro( + "ComponentIdOfArray : component " << compoName << " in array " << array->GetName() << " !"); + } + return ret; +} + +//----------------------------------------------------------------------------- +std::string vtkRosetteCIH::GenerateAValidFieldForGlyph( + vtkUnstructuredGrid* usgInCpy, const std::string& arrayName, const char* keyPoint) +{ + vtkFieldData* dsa(usgInCpy->GetCellData()); + vtkAbstractArray* array(dsa->GetAbstractArray(arrayName.c_str())); + vtkDoubleArray* array2(vtkDoubleArray::SafeDownCast(array)); + // + std::string ret(arrayName); + ret += std::string("_vveeccttoorr"); + int compoIds[3] = { -1, -1, -1 }; + for (int i = 0; i < 3; i++) + { + std::string compoName("SIG_"); + compoName += keyPoint; + compoName += 'X' + i; + compoIds[i] = ComponentIdOfArray(array, compoName); + } + // + vtkIdType nbTuples(array2->GetNumberOfTuples()); + int nbCompo(array2->GetNumberOfComponents()); + vtkNew vect; + vect->SetNumberOfComponents(3); + vect->SetNumberOfTuples(nbTuples); + vect->SetName(ret.c_str()); + + const double* pt(array2->GetPointer(0)); + double* ptOut(vect->GetPointer(0)); + for (vtkIdType i = 0; i < nbTuples; i++) + { + for (int j = 0; j < 3; j++) + { + ptOut[3 * i + j] = pt[nbCompo * i + compoIds[j]]; + } + } + // + dsa->AddArray(vect); + return ret; +} + +//----------------------------------------------------------------------------- +bool vtkRosetteCIH::EndWith(const std::string& arrayName, const std::string& end) +{ + std::size_t lenOfLastChance(end.length()); + if (arrayName.length() < lenOfLastChance) + { + return false; + } + + std::string endOfArrayName(arrayName.substr(arrayName.length() - lenOfLastChance)); + return endOfArrayName == end; +} + +//----------------------------------------------------------------------------- +bool vtkRosetteCIH::IsFirstChance(const std::string& arrayName, const char* keyPoint) +{ + std::string PATTERN("SIRO_ELEM"); + PATTERN += std::string("_") + keyPoint; + return this->EndWith(arrayName, PATTERN); +} + +//----------------------------------------------------------------------------- +bool vtkRosetteCIH::IsLastChanceArray(const std::string& arrayName) +{ + return this->EndWith(arrayName, "SIRO_ELEM"); +} + +//----------------------------------------------------------------------------- +std::string vtkRosetteCIH::GetFieldName(vtkUnstructuredGrid* usgInCpy, const char* keyPoint) +{ + vtkFieldData* dsa(usgInCpy->GetCellData()); + std::string arrayNameOK; + int nbOfArrays(dsa->GetNumberOfArrays()); + bool found(false); + for (int i = 0; i < nbOfArrays; ++i) + { + vtkAbstractArray* arrayAbstract(dsa->GetAbstractArray(i)); + std::string arrayName(arrayAbstract->GetName()); + if (this->IsFirstChance(arrayName, keyPoint) || this->IsLastChanceArray(arrayName)) + { + if (found) + { + vtkErrorMacro("GetFieldName : already found !"); + } + arrayNameOK = arrayName; + found = true; + } + } + if (!found) + { + vtkErrorMacro("GetFieldName : Impossible to find a valid array !"); + } + return arrayNameOK; +} + +//----------------------------------------------------------------------------- +std::string vtkRosetteCIH::RetrieveFieldForGlyph( + vtkUnstructuredGrid* usgInCpy, const char* keyPoint) +{ + std::string arrayNameOK(this->GetFieldName(usgInCpy, keyPoint)); + return this->GenerateAValidFieldForGlyph(usgInCpy, arrayNameOK, keyPoint); +} + +//----------------------------------------------------------------------------- +vtkDoubleArray* vtkRosetteCIH::RetrieveFieldForPost( + vtkUnstructuredGrid* usgInCpy, const char* keyPoint, int& compId) +{ + std::string FIELD_NAME_2(this->GetFieldName(usgInCpy, keyPoint)); + vtkFieldData* dsa(usgInCpy->GetCellData()); + vtkAbstractArray* arrayForPosNeg(dsa->GetAbstractArray(FIELD_NAME_2.c_str())); + vtkDoubleArray* arrayForPosNeg2(vtkDoubleArray::SafeDownCast(arrayForPosNeg)); + std::string compoToFind("SIG_"); + compoToFind += keyPoint; + compId = this->ComponentIdOfArray(arrayForPosNeg, compoToFind); + return arrayForPosNeg2; +} + +//----------------------------------------------------------------------------- +void vtkRosetteCIH::PostTraitementOnlyOneCompo(vtkUnstructuredGrid* usgIn, + vtkUnstructuredGrid* output, const char* keyPoint, + const char* COMPRESS_TRACTION) +{ + vtkNew usgInCpy; + usgInCpy->DeepCopy(usgIn); + // + vtkFieldData* dsa(usgInCpy->GetCellData()); + int compId(-1); + // vtkAbstractArray *arrayForPosNeg(dsa->GetAbstractArray(FIELD_NAME_2)); + std::string arrayForGlyph(this->RetrieveFieldForGlyph(usgInCpy, keyPoint)); + vtkDoubleArray* arrayForPosNeg2(this->RetrieveFieldForPost(usgInCpy, keyPoint, compId)); + // vtkDoubleArray::SafeDownCast(arrayForPosNeg); + int nbCompo(arrayForPosNeg2->GetNumberOfComponents()); + vtkIdType nbTuples(arrayForPosNeg2->GetNumberOfTuples()); + + vtkNew compressionOrTraction; + compressionOrTraction->SetNumberOfComponents(1); + compressionOrTraction->SetNumberOfTuples(nbTuples); + compressionOrTraction->SetName(COMPRESS_TRACTION); + + const double* pt(arrayForPosNeg2->GetPointer(0)); + double* ptOut(compressionOrTraction->GetPointer(0)); + double valMin(std::numeric_limits::max()); + double valMax(-std::numeric_limits::max()); + + for (vtkIdType i = 0; i < nbTuples; i++) + { + double val(pt[i * nbCompo + compId]); + valMin = std::min(valMin, val); + valMax = std::max(valMax, val); + ptOut[i] = val; + } + // + for (int i = dsa->GetNumberOfArrays() - 1; i >= 0; i--) + { + if (arrayForGlyph != dsa->GetAbstractArray(i)->GetName()) + { + dsa->RemoveArray(i); + } + } + int arrId(dsa->AddArray(compressionOrTraction)); + + vtkNew arrow; + + vtkNew surface; + surface->SetNonlinearSubdivisionLevel(0); + surface->SetInputData(usgInCpy); + + vtkNew normals; + normals->ComputeCellNormalsOn(); + normals->ComputePointNormalsOff(); + normals->SplittingOff(); + normals->SetInputConnection(surface->GetOutputPort()); + normals->Update(); + + // for some reasons, the glyph filter removes scalars and normals, we have to duplicate them + vtkDataArray* normalsArray = normals->GetOutput()->GetCellData()->GetNormals(); + + vtkSmartPointer savedNormalsArray; + savedNormalsArray.TakeReference(normalsArray->NewInstance()); + savedNormalsArray->DeepCopy(normalsArray); + savedNormalsArray->SetName("CellNormals"); + + normals->GetOutput()->GetCellData()->AddArray(savedNormalsArray); + + vtkNew glyph; + glyph->SetInputConnection(normals->GetOutputPort()); + glyph->SetGlyphMode(0); // vtkPVGlyphFilter::ALL_POINTS + glyph->SetVectorScaleMode(0); // vtkPVGlyphFilter::SCALE_BY_MAGNITUDE + glyph->SetSourceConnection(arrow->GetOutputPort()); + // idx,port,connection,fieldAssociation,name + glyph->SetInputArrayToProcess( + 0, 0, 0, vtkDataObject::FIELD_ASSOCIATION_CELLS, arrayForGlyph.c_str()); // idx==0 -> scaleArray + glyph->SetInputArrayToProcess(1, 0, 0, vtkDataObject::FIELD_ASSOCIATION_CELLS, + arrayForGlyph.c_str()); // idx==1 -> orientationArray + glyph->SetScaleFactor(this->ScaleFactor); + + vtkNew ribbon; + ribbon->SetWidth(this->WidthFactor); + ribbon->VaryWidthOff(); + ribbon->UseDefaultNormalOff(); + ribbon->SetInputArrayToProcess(1, 0, 0, vtkDataObject::FIELD_ASSOCIATION_POINTS, "CellNormals"); + ribbon->SetInputConnection(glyph->GetOutputPort()); + ribbon->Update(); + + vtkDataSet* ret = ribbon->GetOutput(); + + vtkFieldData* fieldData = ret->GetPointData(); + for (int i = fieldData->GetNumberOfArrays() - 1; i >= 0; i--) + { + fieldData->RemoveArray(i); + } + + fieldData = ret->GetCellData(); + for (int i = fieldData->GetNumberOfArrays() - 1; i >= 0; i--) + { + fieldData->RemoveArray(i); + } + + vtkNew compressionOrTractionNaN; + compressionOrTractionNaN->SetNumberOfComponents(1); + compressionOrTractionNaN->SetNumberOfTuples(ret->GetNumberOfCells()); + compressionOrTractionNaN->SetName(COMPRESS_TRACTION); + compressionOrTractionNaN->Fill(NAN); + fieldData->AddArray(compressionOrTractionNaN); + + vtkNew tesselator; + tesselator->SetOutputDimension(1); + tesselator->SetInputData(usgInCpy); + tesselator->Update(); + + vtkNew mb; + mb->AddInputData(tesselator->GetOutput()); + mb->AddInputData(ret); + + vtkNew cd; + cd->SetInputConnection(mb->GetOutputPort()); + cd->SetMergePoints(0); + cd->Update(); + + output->ShallowCopy(cd->GetOutput()); + + int arrayId; + output->GetCellData()->GetAbstractArray(COMPRESS_TRACTION, arrayId); + output->GetCellData()->SetActiveAttribute(arrayId, vtkDataSetAttributes::SCALARS); +} + +//----------------------------------------------------------------------------- +void vtkRosetteCIH::PostTraitementT1(vtkUnstructuredGrid* usgIn, vtkUnstructuredGrid* output) +{ + // constexpr char FIELD_NAME[]="RESUNL__SIRO_ELEM_T1_Vector"; + // constexpr char FIELD_NAME_2[]="RESUNL__SIRO_ELEM_T1"; + constexpr char COMPRESS_TRACTION[] = "Contrainte specifique 1"; + this->PostTraitementOnlyOneCompo(usgIn, output, "T1", COMPRESS_TRACTION); +} + +//----------------------------------------------------------------------------- +void vtkRosetteCIH::PostTraitementT2(vtkUnstructuredGrid* usgIn, vtkUnstructuredGrid* output) +{ + // constexpr char FIELD_NAME[]="RESUNL__SIRO_ELEM_T2_Vector"; + // constexpr char FIELD_NAME_2[]="RESUNL__SIRO_ELEM_T2"; + constexpr char COMPRESS_TRACTION[] = "Contrainte specifique 3"; + this->PostTraitementOnlyOneCompo(usgIn, output, "T2", COMPRESS_TRACTION); +} + +//----------------------------------------------------------------------------- +int vtkRosetteCIH::RequestData(vtkInformation* vtkNotUsed(request), + vtkInformationVector** inputVector, vtkInformationVector* outputVector) +{ + vtkInformation* outInfo(outputVector->GetInformationObject(0)); + vtkUnstructuredGrid* output( + vtkUnstructuredGrid::SafeDownCast(outInfo->Get(vtkDataObject::DATA_OBJECT()))); + // + vtkSmartPointer usgIn; + this->ExtractInfo(inputVector[0], usgIn); + switch (this->TypeOfDisplay) + { + case 0: + this->PostTraitementT1etT2(usgIn, output); + break; + case 1: + this->PostTraitementT1(usgIn, output); + break; + case 2: + this->PostTraitementT2(usgIn, output); + break; + default: + vtkErrorMacro("GetFieldName : Impossible to find a valid array !"); + } + + return 1; +} + +//----------------------------------------------------------------------------- +void vtkRosetteCIH::PrintSelf(ostream& os, vtkIndent indent) +{ + this->Superclass::PrintSelf(os, indent); +} diff --git a/src/RosetteCIH/plugin/RosetteCIHFilters/vtkRosetteCIH.h b/src/RosetteCIH/plugin/RosetteCIHFilters/vtkRosetteCIH.h new file mode 100644 index 0000000..c184e44 --- /dev/null +++ b/src/RosetteCIH/plugin/RosetteCIHFilters/vtkRosetteCIH.h @@ -0,0 +1,93 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +#ifndef __vtkRosetteCIH_h__ +#define __vtkRosetteCIH_h__ + +#include + +#include + +#include +#include + +class vtkDoubleArray; + +class VTK_EXPORT vtkRosetteCIH : public vtkUnstructuredGridAlgorithm +{ +public: + static vtkRosetteCIH* New(); + vtkTypeMacro(vtkRosetteCIH, vtkUnstructuredGridAlgorithm); + void PrintSelf(ostream& os, vtkIndent indent) override; + + vtkGetMacro(ScaleFactor, double); + vtkSetMacro(ScaleFactor, double); + + vtkGetMacro(WidthFactor, double); + vtkSetMacro(WidthFactor, double); + + vtkGetMacro(TypeOfDisplay, int); + vtkSetMacro(TypeOfDisplay, int); + +protected: + vtkRosetteCIH() = default; + ~vtkRosetteCIH() override = default; + + int RequestData(vtkInformation*, vtkInformationVector**, vtkInformationVector*) override; + + void ExtractInfo(vtkInformationVector* inputVector, vtkSmartPointer& usgIn); + + vtkSmartPointer GenerateGlyphLinesFor( + vtkUnstructuredGrid* usgIn, const char* keyPoint, const char COMPRESS_TRACTION[]); + + void PostTraitementT1etT2(vtkUnstructuredGrid* usgIn, vtkUnstructuredGrid* output); + + int ComponentIdOfArray(vtkAbstractArray* array, const std::string& compoName); + + std::string GenerateAValidFieldForGlyph( + vtkUnstructuredGrid* usgInCpy, const std::string& arrayName, const char* keyPoint); + + bool EndWith(const std::string& arrayName, const std::string& end); + + bool IsFirstChance(const std::string& arrayName, const char* keyPoint); + bool IsLastChanceArray(const std::string& arrayName); + + std::string GetFieldName(vtkUnstructuredGrid* usgInCpy, const char* keyPoint); + + std::string RetrieveFieldForGlyph(vtkUnstructuredGrid* usgInCpy, const char* keyPoint); + + vtkDoubleArray* RetrieveFieldForPost( + vtkUnstructuredGrid* usgInCpy, const char* keyPoint, int& compId); + + void PostTraitementOnlyOneCompo(vtkUnstructuredGrid* usgIn, vtkUnstructuredGrid* output, + const char* keyPoint, const char* COMPRESS_TRACTION); + + void PostTraitementT1(vtkUnstructuredGrid* usgIn, vtkUnstructuredGrid* output); + void PostTraitementT2(vtkUnstructuredGrid* usgIn, vtkUnstructuredGrid* output); + + double ScaleFactor; + double WidthFactor; + int TypeOfDisplay; + +private: + vtkRosetteCIH(const vtkRosetteCIH&) = delete; + void operator=(const vtkRosetteCIH&) = delete; +}; + +#endif diff --git a/src/RosetteCIH/plugin/filters.xml b/src/RosetteCIH/plugin/filters.xml new file mode 100644 index 0000000..1c3d6ae --- /dev/null +++ b/src/RosetteCIH/plugin/filters.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + This property specifies the scale factor applied to the length of the ribbon. + + + + This property specifies the width factor applied to the ribbon. + + + + + + + + + Property pour specifier l'une des 3 visus liées aux rosettes. + + + + + + + + + diff --git a/src/RosetteCIH/plugin/paraview.plugin b/src/RosetteCIH/plugin/paraview.plugin new file mode 100644 index 0000000..e87df62 --- /dev/null +++ b/src/RosetteCIH/plugin/paraview.plugin @@ -0,0 +1,27 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +NAME + RosetteCIH +DESCRIPTION + This plugin provides ... +REQUIRES_MODULES + VTK::CommonCore + VTK::IOCore + VTK::FiltersCore diff --git a/src/SerafinReader/CMakeLists.txt b/src/SerafinReader/CMakeLists.txt new file mode 100644 index 0000000..bbbc3e9 --- /dev/null +++ b/src/SerafinReader/CMakeLists.txt @@ -0,0 +1,49 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +cmake_minimum_required(VERSION 3.8) +project(SerafinReader) +find_package(ParaView REQUIRED) + +include(GNUInstallDirs) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}") +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}") +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}") + +set("_paraview_plugin_default_${CMAKE_PROJECT_NAME}" ON) +paraview_plugin_scan( + ENABLE_BY_DEFAULT YES + PLUGIN_FILES "${CMAKE_CURRENT_SOURCE_DIR}/plugin/paraview.plugin" + PROVIDES_PLUGINS plugins + REQUIRES_MODULES required_modules) + +foreach(module IN LISTS required_modules) + if(NOT TARGET "${module}") + message("Missing required module: ${module}") + return() + endif() +endforeach() + +set(BUILD_SHARED_LIBS ON) +paraview_plugin_build( + RUNTIME_DESTINATION "${CMAKE_INSTALL_BINDIR}" + LIBRARY_DESTINATION "${CMAKE_INSTALL_LIBDIR}" + LIBRARY_SUBDIRECTORY "${PARAVIEW_PLUGIN_SUBDIR}" + PLUGINS ${plugins} + AUTOLOAD ${plugins}) diff --git a/src/SerafinReader/geo_TW.slf b/src/SerafinReader/geo_TW.slf new file mode 100644 index 0000000000000000000000000000000000000000..058d01f8f1bd382c23accaad17c9ef6eb8e4bb52 GIT binary patch literal 397 zcma)!O9}!p5JcM_h$|O!fL^*0yh6_h31lX-h#Hw%wtRY)+n5DOluR9Cl%%y)CW zMHePLYYd}3tkcn)_jVn04pSFzgV+BhxC!=P1t@xLL&W*doyw+=9dH6=5B8_nJ%TOR zfeqj;vrG2O*4eXHI_&e$KIi;%rvX&ymR;s)wme1p41Lbitj?<$Z&7@k_9gNS^TQk9 C&m?XD literal 0 HcmV?d00001 diff --git a/src/SerafinReader/plugin/CMakeLists.txt b/src/SerafinReader/plugin/CMakeLists.txt new file mode 100644 index 0000000..2687b3b --- /dev/null +++ b/src/SerafinReader/plugin/CMakeLists.txt @@ -0,0 +1,31 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +paraview_add_plugin(SerafinReader + VERSION "1.0" + MODULES SerafinReaderModule + MODULE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/SerafinReaderModule/vtk.module" + SERVER_MANAGER_XML sources.xml +) + +install(TARGETS SerafinReader + RUNTIME DESTINATION lib/paraview + LIBRARY DESTINATION lib/paraview + ARCHIVE DESTINATION lib/paraview +) diff --git a/src/SerafinReader/plugin/SerafinReaderModule/CMakeLists.txt b/src/SerafinReader/plugin/SerafinReaderModule/CMakeLists.txt new file mode 100644 index 0000000..fc8f2c4 --- /dev/null +++ b/src/SerafinReader/plugin/SerafinReaderModule/CMakeLists.txt @@ -0,0 +1,27 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +set(classes + vtkSerafinReader +) + +vtk_module_add_module(SerafinReaderModule + FORCE_STATIC + CLASSES ${classes} +) diff --git a/src/SerafinReader/plugin/SerafinReaderModule/FFileReader.h b/src/SerafinReader/plugin/SerafinReaderModule/FFileReader.h new file mode 100644 index 0000000..d8ab149 --- /dev/null +++ b/src/SerafinReader/plugin/SerafinReaderModule/FFileReader.h @@ -0,0 +1,195 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +/*========================================================================= + + Program: Visualization Toolkit + Module: $RCSfile: FFileReader.h,v $ + + Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen + All rights reserved. + See Copyright.txt or http://www.kitware.com/Copyright.htm for details. + + This software is distributed WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. See the above copyright notice for more information. + +=========================================================================*/ + +////// Reader for files generated by The TELEMAC modelling system \\\\\ +// Module developped by herve ozdoba - Sept 2008 ( herve-externe.ozdoba at edf.fr / herve at ozdoba.fr ) +// Please address all comments to Regina Nebauer ( regina.nebauer at edf.fr ) +// >>> Test version + +#ifndef __FFileReader_h__ +#define __FFileReader_h__ + +/** -- Inclusions issues de la bibliothèque standard du C++ -- */ + +#include +#include +#include +#include +#include +#include // need 64bit file position variables + +using namespace std; + +#include "vtkStringArray.h" + +/** ********************************************************************************************************* **/ +/** -- Definition de la classe abstraite de lecture des fichiers issus du langage de programmation Fortran -- **/ +/** ********************************************************************************************************* **/ + +/// Cette classe, destinée uniquement à lire un fichier Serafin écrit par Telemac sous Fortran, permet de simplifier la lecture +/// des données binaires en prenant en charge les systèmes d'écriture big/little endian spécifiques à certaine architecture . + +// Passe de quatre octets à la lecture d'un flux +#define skipReadingHeader(stream) (stream->seekg (sizeof(int), ios_base::cur )) + +class FFileReader +{ +public: + + // Simple constructeur (flux en argument) + FFileReader(ifstream* stream); + + // Simple constructeur (nom de fichier en argument) + FFileReader(const vtkStdString& filename);// non implémentée + + // Destructeur de base + ~FFileReader(); + + // [inline] Retourne true si le fichier est écrit en big endian, cette fonction utilise la taille maximum du titre de la simu pour + // déterminer cette propriété (donc cette classe ne peut en aucun être utilisée hors de son domaine actuel d'utilisation) + // TODO Modifier le nom de cette méthode, elle n'indique pas le système d'écriture mais uniquement la nécessité d'inverser ou non l'ordre des bytes à + // la lecture du fichier . + bool IsBigEndian(){return this->BigEndian;}; + + // [inline] Déplace le pointeur de lecture du fichier vers le bloc d'écriture suivant dans le fichier fortran et retourne la position actuelle . + int GoToNextBloc() + { + FileStream->seekg (GetBlocSize() + 2*sizeof(int), ios_base::cur); + if (IsBigEndian()) s_readBlocSize ();else ns_readBlocSize (); + + return FileStream->tellg(); + }; + + // [inline] Retourne la taille du bloc d'écriture actuel . + int GetBlocSize(){return this->BlocSize;}; + // float size - allow for 32 or 64 bit floats + + // Lit et stocke des tableaux sous différents formats (la taille est spécifiée en second argument) + // these functions return a file position + int64_t (FFileReader:: *readIntArray) (int* , const int); + int64_t (FFileReader:: *readFloatArray) (double*, const int); + int64_t (FFileReader:: *readStringArray) (vtkStringArray*, const int); // nom implémentée + + // Quelques macros pour simplification d'écriture sur pointeur de fonction + // TODO Redefinir les macros, elle ne sont plus valables ... à dédéfinir du type '(*this.*readFloatArray)' pour utilisation ultérieure . + #define ReadIntArray (*readIntArray) + #define ReadFloatArray (*readFloatArray) + /*#define ReadStringArray (*ReadStringArray)*/ + + // [inline] Lit une chaîne de caractères en fonction de la taille passée en argument et se déplace sur le bloc suivant + int64_t ReadString(char* s, int size) + { + skipReadingHeader(FileStream); + FileStream->read (s, size); + skipReadingHeader(FileStream); + readBlocSize (); + // if (IsBigEndian()) s_readBlocSize ();else ns_readBlocSize (); + + return FileStream->tellg(); + }; + + // lecture de tableaux avec inversion des octets ou non + int64_t s_readInt32Array(int* arr, const int size); + int64_t ns_readInt32Array(int* arr, const int size); + int64_t g_readInt32Array(int* arr, const int size); + + int64_t s_readFloat32Array(double* arr, const int size); + int64_t ns_readFloat32Array(double* arr, const int size); + int64_t g_readFloat32Array(double* arr, const int size); + + int64_t s_readInt64Array(int64_t* arr, const int size); + int64_t ns_readInt64Array(int64_t* arr, const int size); + int64_t g_readInt64Array(int64_t* arr, const int size); + + int64_t s_readFloat64Array(double* arr, const int size); + int64_t ns_readFloat64Array(double* arr, const int size); + int64_t g_readFloat64Array(double* arr, const int size); + // Retourne la taille du fichier + // TODO A placer en protected par la suite + int64_t GetFileSize() + { + // sauvegarder la position courante + int64_t pos = FileStream->tellg(); + + // se placer en fin de fichier + FileStream->seekg( 0 , std::ios_base::end ); + + // récupérer la nouvelle position = la taille du fichier + int64_t size = FileStream->tellg() ; + + // restaurer la position initiale du fichier + FileStream->seekg( pos, std::ios_base::beg ) ; + + return size ; + }; + + +protected: + + FFileReader(); // Non-implémentée + + bool BigEndian; // Système d'écriture du fichier + int BlocSize; // Taille du bloc d'écriture suivant + + ifstream *FileStream; // Le flux d'entrée du fichier + + // [inline] Lecture d'un entête avec ou sans swap (indicateur par préfixe) + void s_readBlocSize () {ns_readBlocSize (); Swap32((char*)(&BlocSize));}; + void ns_readBlocSize () {FileStream->read ((char*)(&BlocSize), sizeof(int));FileStream->seekg ( -sizeof(int), ios_base::cur );}; + + void readBlocSize() + { + FileStream->read ((char*)(&BlocSize), sizeof(int)); + // always reposition it back to its start ..... + FileStream->seekg ( -sizeof(int), ios_base::cur ); + if ( this->BigEndian) { + Swap32((char*)(&BlocSize)); + } + + }; + +private: + //[inline] Gestion des swaps pour la prise en charge l/ge + #define Intervert(i,j) {one_byte = data[i]; data[i] = data[j]; data[j] = one_byte;} + void Swap32 (char* data) {char one_byte;Intervert(0,3);Intervert(1,2);} + void Swap32Array (const long int size, char* data) {long int indent;for(indent = 0; indent!= size; indent++) Swap32(&data[indent*4]);}; + void Swap64 (char* data) {char one_byte;Intervert(0,7);Intervert(1,6);Intervert(2,5);Intervert(3,4);} + void Swap64Array (const long int size, char* data) {long int indent;for(indent = 0; indent!= size; indent++) Swap64(&data[indent*8]);}; + + FFileReader(const FFileReader&); // Pas implémentée + void operator=(const FFileReader&); // Pas implémentée + +}; /* class_FFileReader */ + +#endif diff --git a/src/SerafinReader/plugin/SerafinReaderModule/stdSerafinReader.h b/src/SerafinReader/plugin/SerafinReaderModule/stdSerafinReader.h new file mode 100644 index 0000000..e068209 --- /dev/null +++ b/src/SerafinReader/plugin/SerafinReaderModule/stdSerafinReader.h @@ -0,0 +1,443 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +/*========================================================================= + + Program: Visualization Toolkit + Module: $RCSfile: stdSerafinReader.h,v $ + + Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen + All rights reserved. + See Copyright.txt or http://www.kitware.com/Copyright.htm for details. + + This software is distributed WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. See the above copyright notice for more information. + +=========================================================================*/ + +////// Reader for files generated by The TELEMAC modelling system \\\\\ +// Module developped by herve ozdoba - Sept 2008 ( herve-externe.ozdoba at edf.fr / herve at ozdoba.fr ) +// Please address all comments to Regina Nebauer ( regina.nebauer at edf.fr ) +// >>> Test version + +#ifndef __stdSerafinReader_h__ +#define __stdSerafinReader_h__ + +/** -- Inclusions issues de la bibliothèque standard du C++ -- */ + +#include "FFileReader.h" + +#include +#include +#include +#include +#include + +using namespace std; + +#include "vtkStringArray.h" +#include "vtkIntArray.h" +#include "vtkFloatArray.h" +#include "vtkStdString.h" +#include "vtkDoubleArray.h" +#include "vtkIntArray.h" +#include "vtkCellArray.h" + +/** ***************************************** **/ +/** ********** A définir pour test ********** **/ +/** ***************************************** **/ + +/// Veuillez décommentez la ligne suivante et compiler en mode exécutable pour effectuer des tests /// + +#define ___SIMPLE_READER_TEST_EXE___ + +/** -- Quelques définitions inhérantes au format Serafin standard -- **/ + +// Nombre maximum de caractères que peut avoir le titre d'une simulation sous Telemac. +#define TITLE_MAX_SIZE 80 + +// Taille du bloc de description des discrétisations. +#define VAR_DESC_SIZE 16 + +// Taille du bloc de définition des paramètres dans le fichier . +#define PARAM_NUMBER 10 + +// Taille du bloc de définition des dates dans le fichier . +#define DATE_NUMBER 6 + +//Taille des informations relatives aux discrétisations +#define DISC_DESC_SIZE 4 + +/** ********************************************************************************* **/ +/** -- Definition des type utilisés pour la classe de lecture des fichiers Serafin -- **/ +/** ********************************************************************************* **/ + +/// ci-dessous, voici une liste de types définis afin de faciliter le stockage des informations d'entête +/// d'un fichier Serafin en lecture . + +// Définit le type de discrétisation utilisé dans le fichier +// TODO A revoir pour la dénormination +typedef enum +{ + P0_Elem = 0, // >>> Serafin + P1_Elem = 1, // >>> Volfin + P2_Elem = 2 // Pas encore implémenté + +} SerafinDiscretizationType;/* enum_DiscretizationType */ + +// Définit les informations sur une variables ; +typedef struct +{ + char name[VAR_DESC_SIZE] ; + char unit[VAR_DESC_SIZE] ; + int ncomp; // Number of coponent + int icomp; // Id of component + +} SerafinVar;/* struct_Var */ + +// Définit les informations relatives à une date précise +typedef struct +{ + int day ; int month ; int year ; + int hour ; int min ; int sec ; + +} SerafinDate;/* struct_Date */ + +// Définit une structure standard pour les meta-données (en considérant une seule discrétisation par fichier) +typedef struct +{ + char Title[TITLE_MAX_SIZE] ; // le titre + int VarNumber ; // le nombre de variables + char* VarList ; // Un pointeur vers la liste des variables + SerafinVar* nVarList ; // Un pointeur vers la liste des variables + int IParam[PARAM_NUMBER] ; // l'information IParam + int Date[DATE_NUMBER] ; // La date du début de la simulation + int DiscretizationInfo[DISC_DESC_SIZE] ; // Le informations de discrétisation (nombre d'éléments, nombre de points, ...) + +} SerafinMetaData; /* struct_MetaData */ + +// Définit l'index général d'un fichier serafin pour faciliter le positionnement la de la lecture +typedef struct +{ + int64_t FileSize ; // taille du fichier + int64_t MetaSize ; // taille des metadonnées + int64_t DataSize ; // Taille totale des blocs de données + int64_t DataBlocSize ; // Taille d'un bloc de données + int64_t FloatSize ; // Taille des reels 4 ou 8 + int64_t IntSize ; // Taille des reels 4 ou 8 + int64_t TagSize ; // Taille des reels 4 ou 8 + int64_t FieldSize; // Taille d'un champ + int64_t TimeSize; // Taille d'un temps + + int64_t ConnectivityPosition ; // La position dans le fichier de la table de connectivité + int64_t XPosition; // La position dans le fichier de la table des valeurs de X + int64_t YPosition; // La position dans le fichier de la table des valeurs de Y + int64_t DataPosision; // La position dans le fichier des blocs de données + + int NumberOfDate ; // Information sur le temps étudié + + // TODO La variable suivante n'a rien à faire dans cette stucture à mon avis mais je la laisse en attendant + // Elle décrit le type de discrétisation qui est affecté lors de la création de l'index du fichier, d'où sa présence ici + SerafinDiscretizationType discretizationtype ; + +} SerafinIndexInfo; /* struct_IndexInfo */ + +/** ********************************************************************************************* **/ +/** -- Definition de la classe générique de traitement des fichiers externes au format Serafin -- **/ +/** ********************************************************************************************* **/ + +/// Classe de lecture de fichier Serafin utilisée par le lecteur Serafin . +/// Elle parse l'entête du fichier pour recueillir les informations le concernant puis créer une table d'index qui +/// permet la lecture des informations souhaitées . + +// TODO Normaliser l'écriture des fonction avec un premier caractère en majuscule (pb mineur) + +// Supprime les espaces à la fin d'une chaine +#define DeleteBlank(string, maxsize) \ + {int compteur = maxsize ; while(isspace(string[compteur-1])&&(compteur!= 0)) compteur --; string[compteur] = '\0';} + +class stdSerafinReader : public FFileReader +{ +public: + // Simple constructeur (flux en argument) + stdSerafinReader(ifstream* stream); + + // Destructeur de base + ~stdSerafinReader() { + // remove cache values + delete XValues, YValues; + } + + // Renvoie 1 si le fichier est un fichier serafin 3D + + int Is3D = 0; + + int Is3Dfile () + { + if (this->Is3D == 0) { + int id; + char name[VAR_DESC_SIZE+1]; + this->Is3D = 0; + for (id=0; idIs3D = 1; + return this->Is3D; + } + } + } + return this->Is3D; + }; + + // Renvoie la variable associée à un identifiant dans la liste stockée dans le fichier Serafin + void GetVarById(const int id, SerafinVar* var) + { + int icomp, ncomp; + if (id > GetNumberOfVars()) return; + strncpy(metadata->nVarList[id].name, var->name, VAR_DESC_SIZE+1); + strncpy(metadata->nVarList[id].unit, var->unit, VAR_DESC_SIZE+1); + metadata->nVarList[id].ncomp = var->ncomp; + metadata->nVarList[id].icomp = var->icomp; + }; + + // Associe le nom de la variable selon le rang dans la table (name doit avoir 17 caractères disponibles ) + void GetVarNameById(const int id, char* name) + { + if (id > GetNumberOfVars()) return; + memcpy ( name, metadata->nVarList[id].name, VAR_DESC_SIZE ); + name[VAR_DESC_SIZE] = '\0' ; + }; + + // Associe l'unité de la variable selon le rang dans la table (unit doit avoir 17 caractère disponibles ) + void GetVarUnitById(const int id, char* unit) + { + if (id > GetNumberOfVars()) return; + memcpy ( unit, metadata->nVarList[id].unit, VAR_DESC_SIZE ); + unit[VAR_DESC_SIZE] = '\0' ; + }; + + // Retourne le nombre de variables ( X e t Y exceptés ) + int GetNumberOfVars() {return this->metadata->VarNumber;}; + + // Associe le nom de la simulation à l'argument name . + void GetTitle(char* name) {strcpy(name, this->metadata->Title);}; + + // Retourne 1 si la date de début de la simulation est connue + int HasDate() {return (this->metadata->IParam[9] == 1);}; + + // Associe la date de début de simulation + void GetDate(SerafinDate* date) + { + date->day = this->metadata->Date[2]; date->month = this->metadata->Date[1]; date->year = this->metadata->Date[0]; + date->hour = this->metadata->Date[3]; date->min = this->metadata->Date[4]; date->sec = this->metadata->Date[5]; + }; + + // Simplifie la lecture des informations de discrétisation + int GetNodeByElements() {return this->metadata->DiscretizationInfo[2];}; + int GetNumberOfNodes() {return this->metadata->DiscretizationInfo[1];}; + int GetNumberOfElement() {return this->metadata->DiscretizationInfo[0];}; + + // Retourne le nombre de pas de temps + int GetTotalTime() {return this->index->NumberOfDate;}; + + // Retourne une date selon l'identifiant 'timeid' spécifié en argument + double GetTime(int timeid) + { + double value = 0; + FileStream->seekg( this->index->DataPosision + this->index->DataBlocSize * timeid , std::ios_base::beg ) ; + skipReadingHeader(FileStream); + (*this.*readFloatArray)(&(value), 1); + return value; + }; + + + // Lit les valeurs d'abscisse et les copie dans la table 'values' à partir de la position 'id' pour une taille 'size' + int GetXValues(const int id, const int size, double* values) + { + GoToXPosition (id);(*this.*readFloatArray)(values, size); + return FileStream->tellg(); + }; + + // Lit les valeurs d'ordonnée et les copie dans la table 'values' à partir de la position 'id' pour une taille 'size' + int GetYValues(const int id, const int size, double* values) + { + GoToYPosition (id);(*this.*readFloatArray)(values, size); + return FileStream->tellg(); + }; + + // Lit les valeurs de côte et les copie dans la table 'values' à partir de la position 'id' pour une taille 'size' à un temps 'time' + // Retourne 0 et ne fait rien s'il s'agit d'un fichier 2D + int GetZValues(const int id, const int size, double* values, int time) + { + if(!Is3Dfile ()) return 0; + GoToData(time, 0, id);(*this.*readFloatArray)(values, size); + return FileStream->tellg(); + } + + // cache xy values - dont change with time + double* XValues = NULL; + double* YValues = NULL; + + void WriteCoord(double *coords, const int time) + { + int i = 0; + const int size = this->GetNumberOfNodes(); + double* arr = new double[size]; + + // Ecriture des valeurs X + + if (XValues == NULL) { + XValues = new double[size]; + this->GetXValues(0, size, XValues); + //vtkDebugMacro( << "Caching XValues\n"); + } + for (i=0;iGetYValues(0, size, YValues); + //vtkDebugMacro( << "Caching YValues\n"); + } + for (i=0;iGetZValues(0, size, arr, time); + if (this->Is3Dfile ()) { + for (i=0;iGetVarValues(time, varid+i, 0, arr, size); + for (j=0;jGoToConnectivityPosition (0); + (*this.*readIntArray)(values, this->GetNodeByElements()*this->GetNumberOfElement()); + } + + // Lit les valeurs de la variable de discrétisation identifiée par 'idvar' et les copie dans la table 'values' à partir de la position 'id' + // pour une taille 'size' à un temps 'time' + int GetVarValues(const int time, const int idvar, const int id, double* values, const int size) + { + GoToData(time, idvar, id); + (*this.*readFloatArray)(values, size); + return FileStream->tellg(); + }; + + + // TODO A terme, placer ces variables en protected + SerafinMetaData* metadata ; + SerafinIndexInfo* index ; + +protected: + stdSerafinReader(); // Non implementée ; + + // Lit l'ensemble des metadonnées et retourne la position actuelle + // Cette méthode est appelée dès l'instanciation pour gérer une bonne fois pour toutes les métadonnées + int readMetaData (); + + // Créer l'index du fichier Serafin + void createIndex (); + + // Identify vector info for each variable + void ComputeVarInfo (); + ////// Ensemble de fonction de lecture de la table d'index \\\\\\ + + // [fixés] Déplace la tête de lecture sur la positon id dans la table des valeurs de X ou Y + int64_t GoToXPosition (const int id) { + FileStream->seekg( this->index->XPosition +this->index->FloatSize*id+this->index->TagSize, std::ios_base::beg ) ; + return FileStream->tellg(); + } + int64_t GoToYPosition (const int id) { + FileStream->seekg( this->index->YPosition +this->index->FloatSize*id+this->index->TagSize, std::ios_base::beg ) ; + return FileStream->tellg(); + } + + // [fixé] Déplace la tête de lecture sur l'élément N dans la table de connectivité + int64_t GoToConnectivityPosition (const int N) + { + FileStream->seekg( this->index->ConnectivityPosition +this->index->IntSize * (1+N*GetNodeByElements()), std::ios_base::beg ) ; + return FileStream->tellg(); + }; + + // TODO Améliorer les trois méthodes ci-dessous + + // Déplace la tête de lecture sur un bloc de données en fonction du temps spécifié en argument + int64_t GoToData(const int time) + { + if (time >= GetTotalTime()) return 0 ; + FileStream->seekg( this->index->DataPosision + this->index->DataBlocSize * time + this->index->TimeSize, std::ios_base::beg ) ; + return FileStream->tellg(); + }; + + // Déplace la tête de lecture sur un bloc de données en fonction du temps et de l'identifiant de variable spécifiés en argument + int64_t GoToData(const int time, const int idvar) + { + int blocNode = this->index->FloatSize * GetNumberOfNodes() + 2*this->index->TagSize ; + + GoToData(time); + if (idvar >= GetNumberOfVars()) return 0 ; + + FileStream->seekg( blocNode * idvar, std::ios_base::cur ) ; + + return FileStream->tellg(); + }; + + // Déplace la tête de lecture sur un bloc de données en fonction du temps, de l'identifiant de variable et du point spécifiés en argument + int64_t GoToData(const int time, const int idvar, const int id) + { + GoToData(time, idvar); + FileStream->seekg( this->index->FloatSize*id+this->index->TagSize, std::ios_base::cur ) ; + return FileStream->tellg(); + }; + + + +private : + + stdSerafinReader(const stdSerafinReader&); // Pas implémentée + void operator=(const stdSerafinReader&); // Pas implémentée + +}; /* class_stdSerafinReader */ + +#endif diff --git a/src/SerafinReader/plugin/SerafinReaderModule/vtk.module b/src/SerafinReader/plugin/SerafinReaderModule/vtk.module new file mode 100644 index 0000000..4cfe9b2 --- /dev/null +++ b/src/SerafinReader/plugin/SerafinReaderModule/vtk.module @@ -0,0 +1,37 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +NAME + SerafinReaderModule +DEPENDS + VTK::CommonCore + VTK::CommonDataModel + VTK::CommonExecutionModel + VTK::FiltersCore + VTK::FiltersModeling + VTK::IOCore + VTK::IOGeometry + VTK::IOXML +PRIVATE_DEPENDS + VTK::CommonMisc + VTK::CommonSystem + VTK::FiltersGeneral + VTK::RenderingCore + VTK::vtksys + VTK::zlib diff --git a/src/SerafinReader/plugin/SerafinReaderModule/vtkSerafinReader.cxx b/src/SerafinReader/plugin/SerafinReaderModule/vtkSerafinReader.cxx new file mode 100644 index 0000000..8a086ac --- /dev/null +++ b/src/SerafinReader/plugin/SerafinReaderModule/vtkSerafinReader.cxx @@ -0,0 +1,666 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +/*========================================================================= + + Program: Visualization Toolkit + Module: $RCSfile: vtkSerafinReader.cxx,v $ + + Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen + All rights reserved. + See Copyright.txt or http://www.kitware.com/Copyright.htm for details. + + This software is distributed WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. See the above copyright notice for more information. + +=========================================================================*/ + +////// Reader for files 2D/3D generated by The TELEMAC modelling system \\\\\ +// Module developped by herve ozdoba - Sept 2008 ( herve-externe.ozdoba at edf.fr / herve at ozdoba.fr ) +// Please address all comments to Regina Nebauer ( regina.nebauer at edf.fr ) +// >>> Test version + +#include "FFileReader.h" +#include "stdSerafinReader.h" +#include "vtkSerafinReader.h" + +#include "vtkErrorCode.h" +#include "vtkInformation.h" +#include "vtkInformationVector.h" +#include "vtkStreamingDemandDrivenPipeline.h" +#include "vtkUnstructuredGrid.h" +#include "vtkMultiBlockDataSet.h" +#include "vtkPointData.h" +#include "vtkCellData.h" +#include "vtkDoubleArray.h" +#include "vtkIntArray.h" +#include "vtkCellArray.h" +#include "vtkNew.h" + +#include +#include +#ifdef WIN32 +#define NOMINMAX +#endif + +class vtkSerafinReader::vtkInternal +{ +public: + vtkInternal() { } + vtkUnstructuredGrid *getPointer() const { return _usg.GetPointer(); } + void geometryHasBeenRead() { _geometry_read=true; } + bool hasGeometryAlreadyRead() const { return _geometry_read; } +private: + bool _geometry_read = false; + vtkNew _usg; +}; + +/** +++++++++++++++++ Définition des méthodes de la classe FFileReader +++++++++++++++++ **/ + +/* ******************* Constructeur ******************* + * Ce constructeur reçoit un flux de lecture de fichier en argument . + * Gloabalement, les initialisations sont effectuée ici et le premier entier est lu pour + * déterminer dans quelle configuration d'écriture on se place en association avec le fichier . + * La différenciation petit/grand boutien est faite à la lecture du premier entier + * qui doit valoir la taille maximale du titre soit 80 caractères (à l'heure où j'écris ces lignes) . + */ +FFileReader :: FFileReader(ifstream* stream) +{ + // différentes initialisations + this->BigEndian = false ; + this->BlocSize = 0 ; + this->FileStream = stream; + + // lecture de l'entête + readBlocSize (); + //vtkDebugMacro( << "BlocSize Little Endian " << BlocSize << "\n"); + if (this->BlocSize != TITLE_MAX_SIZE) // pas d'échange d'octet à la lecture + { + this->BigEndian = true ; + // Relecture de l'entête + readBlocSize (); + //vtkDebugMacro( << "BlocSize Big Endian " << BlocSize << "\n"); + } + + // The float reader will be set later when we identify if single or double + FFileReader::readIntArray = &FFileReader::g_readInt32Array ; +}; + +/* Lecture d'un tableau d'entier arr de taille size avec inversion de octets */ +int64_t FFileReader :: s_readInt32Array(int* arr, const int size) +{ + FileStream->read ((char*)(arr), sizeof(int)*size); + Swap32Array (size, (char*)(arr)); + return FileStream->tellg(); +}; + +/* Lecture d'un tableau d'entier arr de taille size sans inversion de octets */ +int64_t FFileReader :: ns_readInt32Array(int* arr, const int size) +{ + FileStream->read ((char*)(arr), sizeof(int)*size); + return FileStream->tellg(); +}; + +/* Lecture d'un tableau de flottants arr de taille size avec inversion de octets */ +int64_t FFileReader :: s_readFloat32Array(double* arr, const int size) +{ + float *tmp = new float[size]; + FileStream->read ((char*)(tmp), sizeof(float)*size); + Swap32Array (size, (char*)(tmp)); + for(int i; itellg(); +}; + +/* Lecture d'un tableau de flottants arr de taille size sans inversion de octets */ +int64_t FFileReader :: ns_readFloat32Array(double* arr, const int size) +{ + float *tmp = new float[size]; + FileStream->read ((char*)(tmp), sizeof(float)*size); + for(int i; itellg(); +}; +// generic single entry read functions +int64_t FFileReader :: g_readInt32Array(int* arr, const int size) +{ + if ( this->BigEndian ) { + return s_readInt32Array(arr, size); + } + return ns_readInt32Array(arr, size); +}; + +int64_t FFileReader :: g_readFloat32Array(double* arr, const int size) +{ + if ( this->BigEndian ) { + return s_readFloat32Array(arr, size); + } + return ns_readFloat32Array(arr, size); +}; + +/* Lecture d'un tableau d'entier arr de taille size avec inversion de octets */ +int64_t FFileReader :: s_readInt64Array(int64_t* arr, const int size) +{ + FileStream->read ((char*)(arr), sizeof(int64_t)*size); + Swap64Array (size, (char*)(arr)); + return FileStream->tellg(); +}; + +/* Lecture d'un tableau d'entier arr de taille size sans inversion de octets */ +int64_t FFileReader :: ns_readInt64Array(int64_t* arr, const int size) +{ + FileStream->read ((char*)(arr), sizeof(int64_t)*size); + return FileStream->tellg(); +}; + +/* Lecture d'un tableau de flottants arr de taille size avec inversion de octets */ +int64_t FFileReader :: s_readFloat64Array(double* arr, const int size) +{ + FileStream->read ((char*)(arr), sizeof(double)*size); + Swap64Array (size, (char*)(arr)); + return FileStream->tellg(); +}; + +/* Lecture d'un tableau de flottants arr de taille size sans inversion de octets */ +int64_t FFileReader :: ns_readFloat64Array(double* arr, const int size) +{ + FileStream->read ((char*)(arr), sizeof(double)*size); + return FileStream->tellg(); +}; +// generic single entry read functions +int64_t FFileReader :: g_readInt64Array(int64_t* arr, const int size) +{ + if ( this->BigEndian ) { + return s_readInt64Array(arr, size); + } + return ns_readInt64Array(arr, size); +}; + +int64_t FFileReader :: g_readFloat64Array(double* arr, const int size) +{ + if ( this->BigEndian ) { + return s_readFloat64Array(arr, size); + } + return ns_readFloat64Array(arr, size); +}; + +/* ******************* Destructeur ***************** */ +// TODO compléter cette méthode !!! +FFileReader :: ~FFileReader() +{ + // Ne rien faire pour le moment +}; + +/** +++++++++++++++++ Définition des méthodes de la classe stdSerafinReader +++++++++++++++++ **/ + +/* ******************* Constructeur ***************** */ +stdSerafinReader :: stdSerafinReader(ifstream* stream) : FFileReader(stream) +{ + // TODO Initialisation des variables + this->metadata = new SerafinMetaData(); + this->index = new SerafinIndexInfo(); + + // Lecture des metadonnée + //vtkDebugMacro( << "Reafin metadata" << endl); + this->readMetaData (); + + //Création de l'index + //vtkDebugMacro( << "Creating Index" << endl); + this->createIndex (); + + // Identify variable vector + //vtkDebugMacro( << "Computing Var Infor" << endl); + this->ComputeVarInfo(); +}; + +/* ******************* Destructeur ***************** */ +// TODO compléter cette méthode !!! +//stdSerafinReader :: ~stdSerafinReader() +//{ +// // Ne rien faire pour le moment, provoque une 'legere fuite memoire' maitrisee +//}; +void stdSerafinReader::ComputeVarInfo() +{ + int pos, ncomp; + int found; + + if (Is3Dfile()) + ncomp = 3; + else + ncomp = 2; + + for( int i; i < this->metadata->VarNumber ; i++) { + // must read full buffer as each varaible is a file record + string name(metadata->nVarList[i].name); + list vec0 {" U ", " X ", "QX ", "U0 "}; + list vec1 {" V ", " Y ", "QY ", "V0 "}; + list vec2 {" W ", " Z ", "QZ ", "W0 "}; + list> vec {vec0, vec1, vec2}; + + pos = name.find("COTE Z"); + if (pos != std::string::npos){ + metadata->nVarList[i].ncomp = 0; + metadata->nVarList[i].icomp = 0; + continue; + } + + found = 0; + int k = 0; + for(auto const &veci : vec){ + for(auto const &str : veci){ + pos = name.find(str); + if (pos != std::string::npos){ + metadata->nVarList[i].ncomp = ncomp; + metadata->nVarList[i].icomp = k; + metadata->nVarList[i].name[pos+1] = '*'; + if (str[0] != ' ') metadata->nVarList[i].name[pos] = '*'; + found = 1; + break; + }; + } + k++; + if (found != 0) break; + } + // Found a vector go to next variable + if (found != 0) continue; + // Default values + metadata->nVarList[i].ncomp = 0; + metadata->nVarList[i].icomp = 0; + } +}; + +/* ******************* createIndex ***************** */ +/* Cette méthode crée un index de taille et de position à partir des informations meta + * afin de faciliter la lecture du fichier serafin . + */ +void stdSerafinReader :: createIndex () +{ + int tag = 0 ; + int nnodes = GetNumberOfNodes(); + int ndp = GetNodeByElements(); + int nelem = GetNumberOfElement(); + + // TODO: Identify FloatSize (read tag of coordinates) + this->index->IntSize = sizeof(int) ; + this->index->TagSize = sizeof(int) ; + + this->index->FileSize = GetFileSize(); + this->index->MetaSize = FileStream->tellg(); + + this->index->ConnectivityPosition = this->index->MetaSize; + + this->index->XPosition = (this->index->MetaSize) + + (this->index->IntSize*nnodes+2*this->index->TagSize) + + (this->index->IntSize*ndp*nelem+2*this->index->TagSize); + + // Identifying float precision from tag of coordinates + FileStream->seekg( this->index->XPosition); + (*this.*readIntArray)(&tag, 1); + this->index->FloatSize = int(tag/nnodes) ; + //vtkDebugMacro( << "Float Size: " << this->index->FloatSize << endl); + if (this->index->FloatSize == 4){ + FFileReader::readFloatArray = &FFileReader::g_readFloat32Array ; + }else{ + FFileReader::readFloatArray = &FFileReader::g_readFloat64Array ; + } + + // Size of a time info + this->index->TimeSize = 2*this->index->TagSize + this->index->FloatSize; + // Size of a Field + this->index->FieldSize = this->index->FloatSize*nnodes+2*this->index->TagSize; + + // Size of the whole data + this->index->DataSize = (this->index->FileSize) + - (index->MetaSize) + - (this->index->IntSize*nnodes+2*this->index->TagSize) + - 2*index->FieldSize + - (this->index->IntSize*ndp*nelem+2*this->index->TagSize); + // Index to data + this->index->DataPosision = (this->index->FileSize) - (this->index->DataSize); + + // Index of Y coordinates + this->index->YPosition = this->index->XPosition + this->index->FieldSize; + // Size of data bloc for one time step + this->index->DataBlocSize = this->index->TimeSize + GetNumberOfVars()*this->index->FieldSize; + + /*............................................................................................*/ + + + this->index->NumberOfDate = (this->index->DataSize)/(this->index->DataBlocSize); + //vtkDebugMacro(<< "Number of Date: " << this->index->NumberOfDate << endl); + +}; + +/* ******************* readMetaData ***************** */ +/* Cette methode permet de de lire les métadata dans le but de recueillir les informations + * essentielles incluses dans le fichier . Globalement, la demarche sequentielle est la suivante : + * - lecture du titre et suppression des espaces en fin de chaine s'il y en a + * - lecture du nombre de variables + * - lecture du nom des variables et des leurs unités respectives + * - lecture des paramamètres + * - lecture des informations de discrétisation + */ +int stdSerafinReader :: readMetaData () +{ + + //Lecture du titre + if (ReadString(metadata->Title, TITLE_MAX_SIZE) != 88) return 0;// metadata->Title[TITLE_MAX_SIZE]='\0'; + DeleteBlank(metadata->Title, TITLE_MAX_SIZE-8); + + //lecture du nombre de variables (on passe les entete) + skipReadingHeader(FileStream); //skip reclen + // read linear varsno + if ((*this.*readIntArray)(&(metadata->VarNumber), 1) != 96) return 0; + skipReadingHeader(FileStream); // skip quad varno + skipReadingHeader(FileStream); // skip reclen + + //lecture des variables + metadata->VarList = (char *)new SerafinVar[metadata->VarNumber]; + metadata->nVarList = new SerafinVar[metadata->VarNumber]; + + //vtkDebugMacro( << "nVarList Size " << sizeof(metadata->nVarList) << "\n"); + + { + int compteur = 0 ; + char buffer[VAR_DESC_SIZE*2]; + for( compteur; compteur < metadata->VarNumber ; compteur++) { + // must read full buffer as each varaible is a file record + ReadString(&buffer[0], VAR_DESC_SIZE*2); + strncpy(metadata->nVarList[compteur].name, &buffer[0], VAR_DESC_SIZE); + strncpy(metadata->nVarList[compteur].unit, &buffer[VAR_DESC_SIZE], VAR_DESC_SIZE); + metadata->nVarList[compteur].name[VAR_DESC_SIZE]='\0'; + metadata->nVarList[compteur].unit[VAR_DESC_SIZE]='\0'; + } + }; + + // Lecture des parametres et, si necessaire, de la date de simu + skipReadingHeader(FileStream); + (*this.*readIntArray)(metadata->IParam, PARAM_NUMBER); + skipReadingHeader(FileStream); + + if (metadata->IParam[9] == 1)// Si la date est indiquée + { + skipReadingHeader(FileStream); + (*this.*readIntArray)(metadata->Date, DATE_NUMBER); + skipReadingHeader(FileStream); + }; + + //lecture des information de discrietisation + skipReadingHeader(FileStream); + (*this.*readIntArray)(metadata->DiscretizationInfo, DISC_DESC_SIZE); + skipReadingHeader(FileStream); + + // On lit l'entete du bloc de lecture pour connaitre la taille de la table de connectivite + if (IsBigEndian()) s_readBlocSize ();else ns_readBlocSize (); + + + return FileStream->tellg(); +}; + +/** +++++++++++++++++ Définition des méthodes de la classe vtkSerafinReader +++++++++++++++++ **/ + +#include "vtkObjectFactory.h" + +//vtkCxxRevisionMacro(vtkSerafinReader, "$Revision: 0.2 $"); +vtkStandardNewMacro(vtkSerafinReader); + +vtkSerafinReader::vtkSerafinReader():Internal(nullptr) +{ + + //vtkDebugMacro( << "Instanciation du lecteur Serafin"); + + this->FileName = NULL; + this->FileStream = NULL; + this->Reader = NULL; + this->TimeStep = 0; + this->Internal=new vtkInternal; + + this->SetNumberOfInputPorts(0); +}; + +vtkSerafinReader::~vtkSerafinReader() +{ + if (this->FileName) + { + this->SetFileName(0); + } + delete this->Internal; + +} +void vtkSerafinReader::SetTimeUnit(int value) +{ + cerr << "TimeUnit " << value << endl; +} + +int vtkSerafinReader::RequestInformation(vtkInformation *vtkNotUsed(request), + vtkInformationVector **vtkNotUsed(inputVector), + vtkInformationVector *outputVector) +{ + vtkInformation* outInfo = outputVector->GetInformationObject(0); + //outInfo->Set(vtkStreamingDemandDrivenPipeline::MAXIMUM_NUMBER_OF_PIECES(),1); + + if ( !this->FileName ) + { + vtkErrorMacro("No filename specified"); + return 0; + } + + this->FileStream = new ifstream(this->FileName, ifstream::binary|ifstream::in); + + if (this->FileStream->fail()) + { + this->SetErrorCode(vtkErrorCode::FileNotFoundError); + delete this->FileStream; + this->FileStream = NULL; + vtkErrorMacro("Specified filename not found"); + return 0; + } + + this->Reader = new stdSerafinReader( FileStream); + + {//Gestion du temps + const int totime = this->Reader->GetTotalTime(); + if (totime > 1) + { + int i=0; + double* TimeValues = new double[totime]; + + for (i=0; iReader->GetTime(i) ;} + + outInfo->Set(vtkStreamingDemandDrivenPipeline::TIME_STEPS(), &TimeValues[0], totime); + + double timeRange[2]; + timeRange[0] = TimeValues[0]; + timeRange[1] = TimeValues[totime-1]; + outInfo->Set(vtkStreamingDemandDrivenPipeline::TIME_RANGE(), timeRange, 2); + + }; + } + return 1; +} + +int vtkSerafinReader::RequestData(vtkInformation *vtkNotUsed(request), + vtkInformationVector **vtkNotUsed(inputVector), + vtkInformationVector *outputVector) +{ + int totime = this->Reader->GetTotalTime(); + vtkInformation *outInfo = outputVector->GetInformationObject(0); + vtkUnstructuredGrid *output = vtkUnstructuredGrid::SafeDownCast(outInfo->Get(vtkDataObject::DATA_OBJECT())); + int tsLength = outInfo->Length(vtkStreamingDemandDrivenPipeline::TIME_STEPS()); + double *steps = outInfo->Get(vtkStreamingDemandDrivenPipeline::TIME_STEPS()); + double requestedTimeSteps = outInfo->Get(vtkStreamingDemandDrivenPipeline::UPDATE_TIME_STEP()); + + if(outInfo->Has(vtkStreamingDemandDrivenPipeline::UPDATE_TIME_STEP()) && tsLength>0) + { + // Get the requested time step. We only support requests of a single time + // step in this reader right now + double requestedTimeSteps = outInfo->Get(vtkStreamingDemandDrivenPipeline::UPDATE_TIME_STEP()); + + // find the first time value larger than requested time value + // this logic could be improved + int cnt = 0; + while (cnt < tsLength-1 && steps[cnt] < requestedTimeSteps) + { + cnt++; + } + + this->TimeStep = cnt; + } + + //vtkDebugMacro( << "Serafin steps <" << steps << ">..." << requestedTimeSteps << this->TimeStep); + + if ( outInfo->Has( vtkStreamingDemandDrivenPipeline::UPDATE_TIME_STEP() ) ) + { + double* steps = outInfo->Get( vtkStreamingDemandDrivenPipeline::TIME_STEPS() ); + }; + + + if ( this->FileStream == NULL ) + { + return 0; + } + + //vtkDebugMacro( << "Reading Time " << this->TimeStep << endl); + + // Lecture de la geometrie + if(!this->Internal->hasGeometryAlreadyRead()) + this->ReadGeometry(Internal->getPointer(), this->TimeStep); + Internal->geometryHasBeenRead(); + output->ShallowCopy(Internal->getPointer()); + + // Lecture des donnees + //vtkDebugMacro( << "Reading Data " << this->TimeStep << endl); + this->ReadData(output, this->TimeStep); + //vtkDebugMacro( << "Request done" << endl); + + return 1; +} + +void vtkSerafinReader::PrintSelf(ostream& os, vtkIndent indent) +{ + this->Superclass::PrintSelf(os,indent); + + os << indent << "File Name: " << (this->FileName ? this->FileName : "(none)") << endl; + os << indent << "Number Of Nodes: " << this->Reader->GetNumberOfNodes() << endl; + os << indent << "Number Of Node Fields: " << this->Reader->GetNumberOfVars() << endl; + os << indent << "Number Of Cells: " << this->Reader->GetNumberOfElement() << endl; +} + +void vtkSerafinReader::ReadGeometry(vtkUnstructuredGrid *output, int time) +{ + vtkDoubleArray *coords = vtkDoubleArray::New(); + coords->SetNumberOfComponents(3); + coords->SetNumberOfTuples(this->Reader->GetNumberOfNodes()); + + //vtkDebugMacro( << "Reading coordinates" << endl); + this->Reader->WriteCoord(coords->GetPointer (0), time); + + //Lecture de la table de connectivite + //vtkDebugMacro( << "Reading connectivity" << endl); + { + int i = 0, k = 0, l = 0; + vtkIdType list[27]; + const int size = this->Reader->GetNodeByElements()*this->Reader->GetNumberOfElement(); + int* arr = new int[size]; + + switch(this->Reader->GetNodeByElements()) + { + case 3 : l = VTK_TRIANGLE ; break; + case 4 : l = (this->Reader->Is3Dfile())? VTK_TETRA : VTK_QUAD ; break; + case 5 : l = VTK_PYRAMID; break; + case 6 : l = VTK_WEDGE; break; + case 8 : l = VTK_HEXAHEDRON ;break; + default: + { + vtkErrorMacro( << "cell type is not supported\n");return; + } + } + + //vtkDebugMacro( << "Writting connectivity\n"); + this->Reader->WriteConnectivity(arr); + output->Allocate(this->Reader->GetNumberOfNodes(), this->Reader->GetNumberOfNodes()); + + for(i = 0; i < this->Reader->GetNumberOfElement(); i++) + { + for(k = 0; k < this->Reader->GetNodeByElements(); k++) + list[k] = arr[this->Reader->GetNodeByElements()*i+k]-1; + + output->InsertNextCell(l, this->Reader->GetNodeByElements(), list); + }; + + delete[] arr; + }; + + //vtkDebugMacro( << "Setting points\n"); + vtkPoints *points = vtkPoints::New(); + points->SetData(coords); + coords->Delete(); + + output->SetPoints(points); + points->Delete(); + //vtkDebugMacro( << "Read Geometry done\n"); + +} + + + +void vtkSerafinReader::ReadData(vtkUnstructuredGrid *output, int time) +{ + int i = 0, dim = 1;int vel =0 ; + char name[VAR_DESC_SIZE+1]; + + const int size = this->Reader->GetNumberOfNodes(); + + const int ideb = 0; + const int ifin = this->Reader->GetNumberOfVars(); + SerafinVar * var ; + + for (i = ideb ; iReader->metadata->nVarList[i]); + //vtkDebugMacro( << "ReadData varname" << endl); + //vtkDebugMacro( << " id: " << i << endl); + //vtkDebugMacro( << " name: *" << var->name << "*\n"); + //vtkDebugMacro( << " icomp/ncomp: " << var->icomp << "/" << var->ncomp << endl); + vtkDoubleArray *data = vtkDoubleArray::New(); + std::string name(var->name); + name = name.substr(0, name.find_last_not_of(" \n")+1); + + // TODO: Creating vector for VELOCITY and stuff + if (var->ncomp != 0){ + data->SetName(name.c_str()); + data->SetNumberOfComponents(3); + data->SetNumberOfTuples(size); + this->Reader->GetVarRangeValues(size, var->ncomp, i, data->GetPointer(0), time); + i+= var->ncomp-1; + + }else{ + data->SetName(name.c_str()); + data->SetNumberOfComponents(1); + data->SetNumberOfTuples(size); + this->Reader->GetVarValues(time, i, 0, data->GetPointer (0), size); + } + + {//Stockage des donnees + }; + output->GetPointData()->AddArray(data); + + data->Delete(); + + } +} diff --git a/src/SerafinReader/plugin/SerafinReaderModule/vtkSerafinReader.h b/src/SerafinReader/plugin/SerafinReaderModule/vtkSerafinReader.h new file mode 100644 index 0000000..d4f9616 --- /dev/null +++ b/src/SerafinReader/plugin/SerafinReaderModule/vtkSerafinReader.h @@ -0,0 +1,124 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +/*========================================================================= + + Program: Visualization Toolkit + Module: $RCSfile: vtkSerafinReader.h,v $ + + Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen + All rights reserved. + See Copyright.txt or http://www.kitware.com/Copyright.htm for details. + + This software is distributed WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. See the above copyright notice for more information. + +=========================================================================*/ + +////// Reader for files generated by The TELEMAC modelling system \\\\\ +// Module developped by herve ozdoba - Sept 2008 ( herve-externe.ozdoba at edf.fr / herve at ozdoba.fr ) +// Please address all comments to Regina Nebauer ( regina.nebauer at edf.fr ) +// >>> Test version + +#ifndef __vtkSerafinReader_h__ +#define __vtkSerafinReader_h__ + +/** -- Inclusions issues de la bibliotheque standard du C++ -- */ + +#include +#include +#include +#include +#include + +using namespace std; + +/** -- Inclusion des entetes de la bibliotheque vtk -- **/ + +#include "vtkUnstructuredGridAlgorithm.h" + +#include "vtkStringArray.h" + + +#include "stdSerafinReader.h" + +#include "vtkIntArray.h" +#include "vtkFloatArray.h" +#include "vtkStdString.h" +#include "vtkDoubleArray.h" +#include "vtkIntArray.h" +#include "vtkCellArray.h" + + +/** ********************************************************************************************* **/ +/** -- Definition de la classe de lecture des fichiers externes au format Serafin pour Telemac -- **/ +/** ********************************************************************************************* **/ + +class VTK_EXPORT vtkSerafinReader : public vtkUnstructuredGridAlgorithm +{ +public: + + static vtkSerafinReader *New(); + + vtkTypeMacro(vtkSerafinReader,vtkUnstructuredGridAlgorithm); + void PrintSelf(ostream& os, vtkIndent indent); + + void SetTimeUnit(int); + + vtkSetStringMacro(FileName); + vtkGetStringMacro(FileName); + + vtkSetMacro(TimeStep, int); + vtkGetMacro(TimeStep, int); + +protected: + + // Implementation du constructeur associe a la classe + vtkSerafinReader(); + + // Implementation du descructeur + ~vtkSerafinReader(); + + int RequestInformation (vtkInformation *, vtkInformationVector **, vtkInformationVector *); + int RequestData (vtkInformation *, vtkInformationVector **, vtkInformationVector *); + + // Lecture de la geometrie du maillage + void ReadGeometry (vtkUnstructuredGrid *output, int time); + + // Lecture des donnees de la simulation au niveau des noeuds et des cellules. + void ReadData (vtkUnstructuredGrid *output, int time); + + char *FileName; // Nom du fichier ouvert par le logiciel Paraview + ifstream *FileStream;// Flux de lecture du fichier + + int TimeStep; + + stdSerafinReader* Reader; /** /!\ Instance de lecture du fichier Serafin **/ + + class vtkInternal; + vtkInternal *Internal; + +private: + vtkSerafinReader(const vtkSerafinReader&); // Pas implemente + void operator=(const vtkSerafinReader&); // Pas implemente + +}; /* class_vtkSerafinReader */ + +#endif diff --git a/src/SerafinReader/plugin/paraview.plugin b/src/SerafinReader/plugin/paraview.plugin new file mode 100644 index 0000000..4b89fe1 --- /dev/null +++ b/src/SerafinReader/plugin/paraview.plugin @@ -0,0 +1,27 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +NAME + SerafinReader +DESCRIPTION + This plugin provides ... +REQUIRES_MODULES + VTK::CommonCore + VTK::IOCore + VTK::FiltersCore diff --git a/src/SerafinReader/plugin/sources.xml b/src/SerafinReader/plugin/sources.xml new file mode 100644 index 0000000..32f6b49 --- /dev/null +++ b/src/SerafinReader/plugin/sources.xml @@ -0,0 +1,58 @@ + + + + + + + The SERAFIN reader reads a binary file creating a vtkUnstructuredGrid. + The default file extension is .srf for this software. + + + + + + This property specifies the file name for the SERAFIN reader. + + + + + + + + + + + + + + + + + + + + This property indicates which transform mode will be used. + + + + + + + + + + + + diff --git a/src/SinusXReader/CMakeLists.txt b/src/SinusXReader/CMakeLists.txt new file mode 100644 index 0000000..c2567cf --- /dev/null +++ b/src/SinusXReader/CMakeLists.txt @@ -0,0 +1,49 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +cmake_minimum_required(VERSION 3.8) +project(SinusXReader) +find_package(ParaView REQUIRED) + +include(GNUInstallDirs) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}") +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}") +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}") + +set("_paraview_plugin_default_${CMAKE_PROJECT_NAME}" ON) +paraview_plugin_scan( + ENABLE_BY_DEFAULT YES + PLUGIN_FILES "${CMAKE_CURRENT_SOURCE_DIR}/plugin/paraview.plugin" + PROVIDES_PLUGINS plugins + REQUIRES_MODULES required_modules) + +foreach(module IN LISTS required_modules) + if(NOT TARGET "${module}") + message("Missing required module: ${module}") + return() + endif() +endforeach() + +set(BUILD_SHARED_LIBS ON) +paraview_plugin_build( + RUNTIME_DESTINATION "${CMAKE_INSTALL_BINDIR}" + LIBRARY_DESTINATION "${CMAKE_INSTALL_LIBDIR}" + LIBRARY_SUBDIRECTORY "${PARAVIEW_PLUGIN_SUBDIR}" + PLUGINS ${plugins} + AUTOLOAD ${plugins}) diff --git a/src/SinusXReader/LigneDEau.sx b/src/SinusXReader/LigneDEau.sx new file mode 100644 index 0000000..ddbedd7 --- /dev/null +++ b/src/SinusXReader/LigneDEau.sx @@ -0,0 +1,32 @@ +C Fichier g?n?r? par Fudaa +C Version 2.1 - Date 2017-7-13 15:48:39 +C +C +C +B N +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.000000E+00 +1.000000E+00 +1.000000E+00 1 +CN polyligne 1-1 +CP 0 1 +CP +0.000000E00 +CP 0 + +3331.001943348042 2662.8766934197047 0.0 A + +3251.6311188826576 2628.5541747319708 0.0 A + +3137.9377757295388 2572.7800818644027 0.0 A + +2897.6801449154004 2469.8125258012005 0.0 A + +2805.4383759421153 2454.796423875317 0.0 A + +2687.454717953029 2409.748118097666 0.0 A + +2472.938976154691 2308.9257194524475 0.0 A + +2331.3585865677887 2214.5387930611787 0.0 A + +2241.2619750124863 2133.0228111778106 0.0 A + +2056.7784370659156 2055.797144130409 0.0 A + +1988.1333996904477 2040.7810422045256 0.0 A + +1887.3110010452287 2045.0713570404928 0.0 A + +1724.279037278492 1961.4102177391408 0.0 A + +1674.9404166648744 1879.8942358557724 0.0 A + +1366.037748475268 1746.894475940803 0.0 A + +1145.08653442298 1637.4914476236506 0.0 A + +1018.5222467619608 1633.201132787684 0.0 A + +949.8772093864927 1618.1850308617993 0.0 A + +891.9579591009415 1583.8625121740656 0.0 A + +840.4741810693404 1500.2013728727136 0.0 A + +643.1196986148697 1418.6853909893455 0.0 A + +657.4611628222997 1424.6090392489361 0.0 A diff --git a/src/SinusXReader/plugin/CMakeLists.txt b/src/SinusXReader/plugin/CMakeLists.txt new file mode 100644 index 0000000..c018761 --- /dev/null +++ b/src/SinusXReader/plugin/CMakeLists.txt @@ -0,0 +1,31 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +paraview_add_plugin(SinusXReader + VERSION "1.0" + MODULES SinusXReaderModule + MODULE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/SinusXReaderModule/vtk.module" + SERVER_MANAGER_XML sources.xml +) + +install(TARGETS SinusXReader + RUNTIME DESTINATION lib/paraview + LIBRARY DESTINATION lib/paraview + ARCHIVE DESTINATION lib/paraview +) diff --git a/src/SinusXReader/plugin/SinusXReaderModule/CMakeLists.txt b/src/SinusXReader/plugin/SinusXReaderModule/CMakeLists.txt new file mode 100644 index 0000000..24a640a --- /dev/null +++ b/src/SinusXReader/plugin/SinusXReaderModule/CMakeLists.txt @@ -0,0 +1,27 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +set(classes + vtkSinusXReader +) + +vtk_module_add_module(SinusXReaderModule + FORCE_STATIC + CLASSES ${classes} +) diff --git a/src/SinusXReader/plugin/SinusXReaderModule/vtk.module b/src/SinusXReader/plugin/SinusXReaderModule/vtk.module new file mode 100644 index 0000000..34325d6 --- /dev/null +++ b/src/SinusXReader/plugin/SinusXReaderModule/vtk.module @@ -0,0 +1,31 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +NAME + SinusXReaderModule +DEPENDS + VTK::CommonCore + VTK::CommonDataModel + VTK::CommonExecutionModel + VTK::FiltersCore + VTK::IOCore +PRIVATE_DEPENDS + VTK::CommonMisc + VTK::CommonSystem + VTK::FiltersGeneral diff --git a/src/SinusXReader/plugin/SinusXReaderModule/vtkSinusXReader.cxx b/src/SinusXReader/plugin/SinusXReaderModule/vtkSinusXReader.cxx new file mode 100644 index 0000000..f103b6a --- /dev/null +++ b/src/SinusXReader/plugin/SinusXReaderModule/vtkSinusXReader.cxx @@ -0,0 +1,276 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +#include "vtkSinusXReader.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +class MyException : public std::exception +{ +public: + MyException(const char *what) : _what(what) {} + MyException(const std::string &what) : _what(what) {} + ~MyException() throw() {} + const char *what() const throw() { return _what.c_str(); } + +private: + std::string _what; +}; + +template +class AutoPtr +{ +public: + AutoPtr(T *ptr = nullptr) : _ptr(ptr) {} + ~AutoPtr() { destroyPtr(); } + bool isNull() const { return _ptr == 0; } + bool isNotNull() const { return !isNull(); } + AutoPtr &operator=(T *ptr) + { + if (_ptr != ptr) + { + destroyPtr(); + _ptr = ptr; + } + return *this; + } + T *operator->() { return _ptr; } + const T *operator->() const { return _ptr; } + T &operator*() { return *_ptr; } + const T &operator*() const { return *_ptr; } + operator T *() { return _ptr; } + operator const T *() const { return _ptr; } + +private: + void destroyPtr() { delete[] _ptr; } + +private: + T *_ptr; +}; + +bool isFloat(const std::string &st, double &val) +{ + if (st == "NaN") + { + val = 0.; + return true; + } + std::istringstream iss(st); + iss >> val; + return iss.eof() && !iss.fail() && !iss.bad(); +} + +bool FindPart(const std::string &st, std::size_t &work, double &val) +{ + std::string part0; + std::size_t pos0(st.find_first_not_of(" \t", work)); + if (pos0 == std::string::npos) + return false; + std::size_t pos1(st.find_first_of(" \t", pos0)); + if (pos1 == std::string::npos) + return false; + part0 = st.substr(pos0, pos1 - pos0); + if (!isFloat(part0, val)) + return false; + work = pos1; + return true; +} + +bool isDataLine(const char *line, std::streamsize szOfLine, double &val0, double &val1, double &val2) +{ + if (szOfLine < 1) + return false; + std::string st(line, szOfLine - 1); + std::size_t work(0); + if (!FindPart(st, work, val0)) + return false; + if (!FindPart(st, work, val1)) + return false; + if (!FindPart(st, work, val2)) + return false; + std::size_t pos0(st.find_first_not_of(" \t", work)); + if (pos0 == std::string::npos) + return false; + std::size_t pos1(st.find_first_of(" \t", pos0)); + if (pos1 != std::string::npos) + return false; + std::string endOfLine(st.substr(pos0)); + if (endOfLine.length() != 1) + return false; + const char c(endOfLine[0]); + if (c < 'A' || c > 'Z') + return false; + return true; +} + +std::vector readSinusX(const char *fileName) +{ + std::ifstream ifs(fileName); + if (!ifs) + { + std::ostringstream oss; + oss << "readSinusX : Error while opening \"" << fileName << "\" file !"; + throw MyException(oss.str()); + } + ifs.seekg(0, ifs.end); + std::streampos length(ifs.tellg()); + ifs.seekg(0, ifs.beg); + AutoPtr data(new char[length]); + std::vector ret; + while (!ifs.eof()) + { + ifs.getline(data, length); + std::streamsize szOfLine(ifs.gcount()); + double vals[3]; + if (isDataLine(data, szOfLine, vals[0], vals[1], vals[2])) + ret.insert(ret.end(), vals, vals + 3); + } + return ret; +} + +void performSubDiv(std::vector &data, int nbOfSubDiv) +{ + constexpr int SPACEDIM = 3; + if (nbOfSubDiv <= 1) + return; + if (data.size() % SPACEDIM != 0) + throw MyException("Internal error : invalid size of data !"); + std::size_t nbPts(data.size() / SPACEDIM); + if (nbPts <= 1) + return; + std::size_t newNbPts((nbOfSubDiv - 1) * (nbPts - 1) + nbPts); + std::vector newData(newNbPts * SPACEDIM); + const double *inPt(data.data()); + double *pt(newData.data()); + for (auto i = 0; i < nbPts - 1; i++, inPt += SPACEDIM) + { + pt = std::copy(inPt, inPt + SPACEDIM, pt); + const double *inPtNext(inPt + SPACEDIM); + for (auto j = 1; j < nbOfSubDiv; j++) + { + double ratio((double)j / (double)nbOfSubDiv); + pt = std::transform(inPt, inPt + SPACEDIM, inPtNext, pt, [ratio](const double &a, const double &b) { return a + ratio * (b - a); }); + } + } + pt = std::copy(inPt, inPt + SPACEDIM, pt); + data = std::move(newData); +} + +vtkStandardNewMacro(vtkSinusXReader); + +vtkSinusXReader::vtkSinusXReader() +{ + this->SetNumberOfInputPorts(0); +} + +int vtkSinusXReader::RequestInformation(vtkInformation *vtkNotUsed(request), + vtkInformationVector **vtkNotUsed(inputVector), + vtkInformationVector *outputVector) +{ + return 1; +} + +int vtkSinusXReader::RequestData(vtkInformation *vtkNotUsed(request), + vtkInformationVector **vtkNotUsed(inputVector), + vtkInformationVector *outputVector) +{ + vtkInformation *outInfo(outputVector->GetInformationObject(0)); + vtkPolyData *output(vtkPolyData::SafeDownCast(outInfo->Get(vtkDataObject::DATA_OBJECT()))); + // + try + { + vtkNew ret; + if (!this->FileName) + return 0; + std::vector data(readSinusX(this->FileName)); + performSubDiv(data, this->NumberOfSubdiv); + if (data.size() % 3 != 0) + throw MyException("Internal error : invalid size of data !"); + std::size_t nbPts(data.size() / 3); + vtkNew arr; + arr->SetNumberOfComponents(3); + arr->SetNumberOfTuples(nbPts); + std::copy(data.begin(), data.end(), arr->GetPointer(0)); + vtkNew pts; + pts->SetData(arr); + ret->SetPoints(pts); + vtkNew verts; + { + vtkNew conn; + conn->SetNumberOfComponents(1); + conn->SetNumberOfTuples(2 * nbPts); + vtkIdType *pt(conn->GetPointer(0)); + for (vtkIdType i = 0; i < nbPts; i++) + { + pt[2 * i] = 1; + pt[2 * i + 1] = i; + } + verts->SetCells(nbPts, conn); + } + ret->SetVerts(verts); + if (nbPts >= 1) + { + vtkNew lines; + { + vtkNew conn; + conn->SetNumberOfComponents(1); + conn->SetNumberOfTuples(3 * (nbPts - 1)); + vtkIdType *pt(conn->GetPointer(0)); + for (vtkIdType i = 0; i < nbPts - 1; i++) + { + pt[3 * i] = 2; + pt[3 * i + 1] = i; + pt[3 * i + 2] = i + 1; + } + lines->SetCells(nbPts - 1, conn); + } + ret->SetLines(lines); + } + output->ShallowCopy(ret); + } + catch (MyException &e) + { + vtkErrorMacro(<< "vtkSinusXReader::RequestData : during read of " << this->FileName << " : " << e.what()); + return 0; + } + return 1; +} + +void vtkSinusXReader::PrintSelf(ostream &os, vtkIndent indent) +{ + this->Superclass::PrintSelf(os, indent); +} diff --git a/src/SinusXReader/plugin/SinusXReaderModule/vtkSinusXReader.h b/src/SinusXReader/plugin/SinusXReaderModule/vtkSinusXReader.h new file mode 100644 index 0000000..0de1ebe --- /dev/null +++ b/src/SinusXReader/plugin/SinusXReaderModule/vtkSinusXReader.h @@ -0,0 +1,53 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +#ifndef __vtkSinusXReader_h__ +#define __vtkSinusXReader_h__ + +#include + +class VTK_EXPORT vtkSinusXReader : public vtkPolyDataAlgorithm +{ +public: + static vtkSinusXReader *New(); + vtkTypeMacro(vtkSinusXReader, vtkPolyDataAlgorithm); + void PrintSelf(ostream &os, vtkIndent indent) override; + + vtkSetStringMacro(FileName); + vtkGetStringMacro(FileName); + + vtkSetMacro(NumberOfSubdiv, int); + vtkGetMacro(NumberOfSubdiv, int); + +protected: + vtkSinusXReader(); + ~vtkSinusXReader() override = default; + + int RequestInformation(vtkInformation *, vtkInformationVector **, vtkInformationVector *) override; + int RequestData(vtkInformation *, vtkInformationVector **, vtkInformationVector *) override; + + char *FileName = nullptr; + int NumberOfSubdiv = 1; + +private: + vtkSinusXReader(const vtkSinusXReader &) = delete; + void operator=(const vtkSinusXReader &) = delete; +}; + +#endif diff --git a/src/SinusXReader/plugin/paraview.plugin b/src/SinusXReader/plugin/paraview.plugin new file mode 100644 index 0000000..58c1f0a --- /dev/null +++ b/src/SinusXReader/plugin/paraview.plugin @@ -0,0 +1,27 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +NAME + SinusXReader +DESCRIPTION + This plugin provides reader for SinusX file format. +REQUIRES_MODULES + VTK::CommonCore + VTK::IOCore + VTK::FiltersCore diff --git a/src/SinusXReader/plugin/sources.xml b/src/SinusXReader/plugin/sources.xml new file mode 100644 index 0000000..bce8322 --- /dev/null +++ b/src/SinusXReader/plugin/sources.xml @@ -0,0 +1,24 @@ + + + + + + + + + This property specifies the file name for the Sinus X reader. + + + + + + + + + + diff --git a/src/SpatialPfl/CMakeLists.txt b/src/SpatialPfl/CMakeLists.txt new file mode 100644 index 0000000..ae83916 --- /dev/null +++ b/src/SpatialPfl/CMakeLists.txt @@ -0,0 +1,49 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +cmake_minimum_required(VERSION 3.8) +project(SpatialPfl) +find_package(ParaView REQUIRED) + +include(GNUInstallDirs) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}") +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}") +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}") + +set("_paraview_plugin_default_${CMAKE_PROJECT_NAME}" ON) +paraview_plugin_scan( + ENABLE_BY_DEFAULT YES + PLUGIN_FILES "${CMAKE_CURRENT_SOURCE_DIR}/plugin/paraview.plugin" + PROVIDES_PLUGINS plugins + REQUIRES_MODULES required_modules) + +foreach(module IN LISTS required_modules) + if(NOT TARGET "${module}") + message("Missing required module: ${module}") + return() + endif() +endforeach() + +set(BUILD_SHARED_LIBS ON) +paraview_plugin_build( + RUNTIME_DESTINATION "${CMAKE_INSTALL_BINDIR}" + LIBRARY_DESTINATION "${CMAKE_INSTALL_LIBDIR}" + LIBRARY_SUBDIRECTORY "${PARAVIEW_PLUGIN_SUBDIR}" + PLUGINS ${plugins} + AUTOLOAD ${plugins}) diff --git a/src/SpatialPfl/TestCase.py b/src/SpatialPfl/TestCase.py new file mode 100644 index 0000000..a51bada --- /dev/null +++ b/src/SpatialPfl/TestCase.py @@ -0,0 +1,42 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +from MEDLoader import * + +fname="hydrau_test1.med" +meshName="mesh" +arr=DataArrayDouble([0,1,2,3,4,5]) +m=MEDCouplingCMesh() +m.setCoords(arr,arr) +m=m.buildUnstructured() +m.setName(meshName) +m.simplexize(0) +WriteMesh(fname,m,True) +# +f=MEDCouplingFieldDouble(ON_NODES) +f.setMesh(m) +f.setName("Field") +arr=m.getCoords().magnitude() +f.setArray(arr) +for i in range(10): + arr+=0.1 + f.setTime(float(i),i,0) + WriteFieldUsingAlreadyWrittenMesh(fname,f) + pass + diff --git a/src/SpatialPfl/plugin/CMakeLists.txt b/src/SpatialPfl/plugin/CMakeLists.txt new file mode 100644 index 0000000..6faaa60 --- /dev/null +++ b/src/SpatialPfl/plugin/CMakeLists.txt @@ -0,0 +1,33 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +set(BUILD_SHARED_LIBS TRUE) + +paraview_add_plugin(SpatialPfl + VERSION "1.0" + MODULES SpatialPflModule + MODULE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/SpatialPflModule/vtk.module" + SERVER_MANAGER_XML filters.xml +) + +install(TARGETS SpatialPfl + RUNTIME DESTINATION lib/paraview + LIBRARY DESTINATION lib/paraview + ARCHIVE DESTINATION lib/paraview +) diff --git a/src/SpatialPfl/plugin/SpatialPflModule/CMakeLists.txt b/src/SpatialPfl/plugin/SpatialPflModule/CMakeLists.txt new file mode 100644 index 0000000..ada5525 --- /dev/null +++ b/src/SpatialPfl/plugin/SpatialPflModule/CMakeLists.txt @@ -0,0 +1,27 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +set(classes + vtkSpatialPfl +) + +vtk_module_add_module(SpatialPflModule + FORCE_STATIC + CLASSES ${classes} +) diff --git a/src/SpatialPfl/plugin/SpatialPflModule/vtk.module b/src/SpatialPfl/plugin/SpatialPflModule/vtk.module new file mode 100644 index 0000000..b824326 --- /dev/null +++ b/src/SpatialPfl/plugin/SpatialPflModule/vtk.module @@ -0,0 +1,32 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +NAME + SpatialPflModule +DEPENDS + VTK::CommonCore + VTK::CommonDataModel + VTK::CommonExecutionModel + VTK::FiltersCore + VTK::FiltersModeling +PRIVATE_DEPENDS + VTK::CommonMisc + VTK::CommonSystem + VTK::FiltersSources + VTK::FiltersGeneral diff --git a/src/SpatialPfl/plugin/SpatialPflModule/vtkSpatialPfl.cxx b/src/SpatialPfl/plugin/SpatialPflModule/vtkSpatialPfl.cxx new file mode 100644 index 0000000..2c2233a --- /dev/null +++ b/src/SpatialPfl/plugin/SpatialPflModule/vtkSpatialPfl.cxx @@ -0,0 +1,576 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +// Author : Anthony Geay (EDF R&D) + +#include "vtkSpatialPfl.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "vtkPolyData.h" + +#include +#include +#include + +vtkStandardNewMacro(vtkSpatialPfl); + +/////////////////// + +template +class AutoPtr +{ +public: + AutoPtr(T* ptr = nullptr) + : _ptr(ptr) + { + } + ~AutoPtr() { destroyPtr(); } + AutoPtr& operator=(T* ptr) + { + if (_ptr != ptr) + { + destroyPtr(); + _ptr = ptr; + } + return *this; + } + T* operator->() { return _ptr; } + const T* operator->() const { return _ptr; } + T& operator*() { return *_ptr; } + const T& operator*() const { return *_ptr; } + operator T*() { return _ptr; } + operator const T*() const { return _ptr; } + +private: + void destroyPtr() { delete[] _ptr; } + +private: + T* _ptr; +}; + +class MZCException : public std::exception +{ +public: + MZCException(const std::string& s) + : _reason(s) + { + } + virtual const char* what() const throw() { return _reason.c_str(); } + virtual ~MZCException() throw() {} + +private: + std::string _reason; +}; + +void ExtractInfo(vtkInformationVector* inputVector, vtkUnstructuredGrid*& usgIn) +{ + vtkInformation* inputInfo(inputVector->GetInformationObject(0)); + vtkDataSet* input = nullptr; + vtkDataSet* input0 = vtkDataSet::SafeDownCast(inputInfo->Get(vtkDataObject::DATA_OBJECT())); + vtkMultiBlockDataSet* input1 = vtkMultiBlockDataSet::SafeDownCast(inputInfo->Get(vtkDataObject::DATA_OBJECT())); + if (input0) + { + input = input0; + } + else + { + if (!input1) + { + throw MZCException( + "Input dataSet must be a DataSet or single elt multi block dataset expected !"); + } + if (input1->GetNumberOfBlocks() != 1) + { + throw MZCException("Input dataSet is a multiblock dataset with not exactly one block ! Use " + "MergeBlocks or ExtractBlocks filter before calling this filter !"); + } + vtkDataObject* input2 = input1->GetBlock(0); + if (!input2) + { + throw MZCException("Input dataSet is a multiblock dataset with exactly one block but this " + "single element is NULL !"); + } + vtkDataSet* input2c = vtkDataSet::SafeDownCast(input2); + if (!input2c) + { + throw MZCException( + "Input dataSet is a multiblock dataset with exactly one block but this single element is " + "not a dataset ! Use MergeBlocks or ExtractBlocks filter before calling this filter !"); + } + input = input2c; + } + if (!input) + { + throw MZCException("Input data set is NULL !"); + } + usgIn = vtkUnstructuredGrid::SafeDownCast(input); + if (!usgIn) + { + throw MZCException("Input data set is not an unstructured mesh ! This filter works only on " + "unstructured meshes !"); + } +} + +//////////////////// + +vtkSpatialPfl::vtkSpatialPfl() +{ + this->SetNumberOfInputPorts(2); + this->SetNumberOfOutputPorts(1); +} + +int vtkSpatialPfl::RequestInformation(vtkInformation* request, vtkInformationVector** inputVector, vtkInformationVector* outputVector) +{ + // std::cerr << "########################################## vtkSpatialPfl::RequestInformation + // ##########################################" << std::endl; + try + { + vtkUnstructuredGrid* usgIn(0); + ExtractInfo(inputVector[0], usgIn); + vtkInformation* info(outputVector->GetInformationObject(0)); + } + catch (MZCException& e) + { + std::ostringstream oss; + oss << "Exception has been thrown in vtkSpatialPfl::RequestInformation : " << e.what() + << std::endl; + if (this->HasObserver("ErrorEvent")) + { + this->InvokeEvent("ErrorEvent", const_cast(oss.str().c_str())); + } + else + { + vtkOutputWindowDisplayErrorText(const_cast(oss.str().c_str())); + } + vtkObject::BreakOnError(); + return 0; + } + return 1; +} + +void buildTableFrom(vtkPolyData* table, const std::vector >& valuesByColumn, const std::vector& columnNames) +{ + vtkPointData *pd(table->GetPointData()); + std::size_t sz(valuesByColumn.size()); + if (sz != columnNames.size()) + { + throw MZCException("Sizes of vectors mismatches !"); + } + if (sz == 0) + { + return; + } + std::size_t nbSamples(valuesByColumn[0].size()); + for (int i = 0; i < sz; i++) + { + vtkSmartPointer arr(vtkSmartPointer::New()); + arr->SetName(columnNames[i].c_str()); + if (nbSamples != valuesByColumn[i].size()) + { + std::ostringstream oss; + oss << "Sizes of vectors " << i << " mismatches with size of others " << nbSamples << " !"; + throw MZCException(oss.str()); + } + arr->SetNumberOfTuples(nbSamples); + arr->SetNumberOfComponents(1); + double* pt(arr->GetPointer(0)); + std::copy(valuesByColumn[i].begin(), valuesByColumn[i].end(), pt); + pd->AddArray(arr); + } +} +template +struct VTKArrayTraits +{ +}; + +template<> +struct VTKArrayTraits +{ + using Type = vtkDoubleArray; +}; + +template<> +struct VTKArrayTraits +{ + using Type = vtkFloatArray; +}; + +template +void FromVTKArrayComputeCurvAbsInternal(typename VTKArrayTraits::Type *data, std::vector& ret, std::vector& Xcoords, std::vector& Ycoords) +{ + vtkIdType nbTuples(data->GetNumberOfTuples()), nbComp(data->GetNumberOfComponents()); + ret.resize(nbTuples); Xcoords.resize(nbTuples); Ycoords.resize(nbTuples); + const T* pt(data->GetPointer(0)); + ret[0] = 0.; Xcoords[0] = pt[0]; Ycoords[0] = pt[1]; + for (vtkIdType i = 1; i < nbTuples; i++) + { + double val(0.); + for (vtkIdType j = 0; j < nbComp; j++) + { + double delta(pt[nbComp * (i - 1) + j] - pt[nbComp * i + j]); + val += delta * delta; + } + Xcoords[i] = pt[nbComp * i + 0]; Ycoords[i] = pt[nbComp * i + 1]; + ret[i] = ret[i - 1] + sqrt(val); + } +} + +std::vector FromVTKArrayComputeCurvAbs(vtkDataArray* data, std::vector& Xcoords, std::vector& Ycoords) +{ + vtkIdType nbTuples(data->GetNumberOfTuples()), nbComp(data->GetNumberOfComponents()); + if (nbTuples < 1) + { + throw MZCException("FromVTKArrayComputeCurvAbs : internal error 1 !"); + } + std::vector ret; + vtkDoubleArray* d1(vtkDoubleArray::SafeDownCast(data)); + if (d1) + { + FromVTKArrayComputeCurvAbsInternal(d1,ret,Xcoords,Ycoords); + return ret; + } + vtkFloatArray* d2(vtkFloatArray::SafeDownCast(data)); + if (d2) + { + FromVTKArrayComputeCurvAbsInternal(d2,ret,Xcoords,Ycoords); + return ret; + } + throw MZCException("FromVTKArrayComputeCurvAbs : internal error 2 !"); +} + +static std::string GetReprDependingPos(const std::string& origName, int blockId, int nbBlocks) +{ + if( nbBlocks == 1 ) + return origName; + std::ostringstream oss; + oss << origName << "_" << blockId; + return oss.str(); +} + +void FillPolyDataInstance(vtkPolyData *ds, const std::vector& xs, const std::vector& ys) +{ + vtkNew coords; + std::size_t nbPts(xs.size()); + coords->SetNumberOfComponents(3); + coords->SetNumberOfTuples(nbPts); + double *coordsPt(coords->GetPointer(0)); + vtkNew pts; + pts->SetData(coords); + ds->SetPoints(pts); + // + vtkNew cc; + cc->AllocateExact(1,nbPts); + std::unique_ptr conn(new vtkIdType[nbPts]); + // + for(std::size_t iPt = 0 ; iPt < nbPts ; ++iPt) + { + coordsPt[3*iPt] = xs[iPt] ; coordsPt[3*iPt+1] = ys[iPt] ; coordsPt[3*iPt+2] = 0.; + conn[iPt] = iPt; + } + // + cc->InsertNextCell(nbPts,conn.get()); + ds->SetLines(cc); +} + +void buildTableFromPolyData(vtkMultiBlockDataSet* table, vtkPolyData* ds, int blockId, int nbBlocks) +{ + vtkNew eltInTable; + vtkPoints* pts(ds->GetPoints()); + if (!pts) + { + throw MZCException("buildTableFromPolyData : internal error 2 !"); + } + vtkDataArray* data(pts->GetData()); + if (!data) + { + throw MZCException("buildTableFromPolyData : internal error 3 !"); + } + // + std::vector Xcoords, Ycoords; + std::vector xs(FromVTKArrayComputeCurvAbs(data, Xcoords, Ycoords)); + // + FillPolyDataInstance(eltInTable,Xcoords,Ycoords); + // + vtkCellArray *cd(ds->GetVerts()), *cc(ds->GetLines()); + // + vtkDataSetAttributes* dsa(ds->GetPointData()); + if (!dsa) + { + throw MZCException("buildTableFromPolyData : no point data !"); + } + int nba = dsa->GetNumberOfArrays(); + // + std::vector > valuesByColumn(1); + std::vector columnNames(1); + valuesByColumn[0] = xs; + columnNames[0] = "Curv Abscissa"; + // + for (int i = 0; i < nba; i++) + { + vtkDataArray* arr(dsa->GetArray(i)); + std::vector tmp(arr->GetNumberOfTuples()); + if (arr->GetNumberOfComponents() != 1) + { + continue; + } + std::string name(GetReprDependingPos(arr->GetName(),blockId,nbBlocks)); + vtkDoubleArray* arr1(vtkDoubleArray::SafeDownCast(arr)); + if (!arr1) + { + vtkFloatArray* arr2(vtkFloatArray::SafeDownCast(arr)); + if (!arr2) + { + continue; + } + const float* pt(arr2->GetPointer(0)); + std::copy(pt, pt + arr->GetNumberOfTuples(), tmp.begin()); + } + else + { + const double* pt(arr1->GetPointer(0)); + std::copy(pt, pt + arr1->GetNumberOfTuples(), tmp.begin()); + } + valuesByColumn.push_back(tmp); + columnNames.push_back(name); + } + // EDF21757 - Ajout de X et Y + valuesByColumn.push_back(Xcoords); columnNames.push_back("X"); + valuesByColumn.push_back(Ycoords); columnNames.push_back("Y"); + // + buildTableFrom(eltInTable, valuesByColumn, columnNames); + table->SetBlock(blockId,eltInTable); +} + +vtkPolyData *ExtractTo(vtkDataObject *source) +{ + vtkMultiBlockDataSet *sourceMB(vtkMultiBlockDataSet::SafeDownCast(source)); + if(sourceMB) + { + if(sourceMB->GetNumberOfBlocks() != 1) + { + std::ostringstream oss; oss << "Internal error ! Number of blocks of MultiBlockDataSet source must be equal to 1 ! Here : " << sourceMB->GetNumberOfBlocks(); + throw MZCException(oss.str()); + } + vtkDataObject *source20(sourceMB->GetBlock(0)); + vtkPolyData *source20c(vtkPolyData::SafeDownCast(source20)); + if(!source20c) + { + throw MZCException("Internal error ! source is a mono block MultiBlockDataSet but this block is not a vtkDataSet !"); + } + return source20c; + } + vtkPolyData* source2(vtkPolyData::SafeDownCast(source)); + return source2; +} + +static int GetNumberOfBlocs(vtkDataObject *ds) +{ + if(!ds) + throw MZCException("vtkSedimentDeposit SplitSingleMultiBloc : nullptr !"); + vtkMultiBlockDataSet *ds0(vtkMultiBlockDataSet::SafeDownCast(ds)); + if(!ds0) + { + vtkPolyData *ds00(vtkPolyData::SafeDownCast(ds)); + if(!ds00) + throw MZCException("vtkSedimentDeposit SplitSingleMultiBloc : neither a vtkMultiBlockDataSet nor a vtkPolyData !"); + return 1; + } + return ds0->GetNumberOfBlocks(); +} + +static vtkPolyData *SplitSingleMultiBloc(vtkDataObject *res, int blockId) +{ + vtkPolyData* res2(vtkPolyData::SafeDownCast(res)); + if (!res2) + { + vtkMultiBlockDataSet *res2c(vtkMultiBlockDataSet::SafeDownCast(res)); + if(!res2c) + { + throw MZCException("Internal error ! unexpected returned of resample filter !"); + } + if(blockId >= res2c->GetNumberOfBlocks()) + { + std::ostringstream oss; oss << "Internal error ! Number of blocks of MultiBlockDataSet must be equal < " << blockId << " ! Here : " << res2c->GetNumberOfBlocks(); + throw MZCException(oss.str()); + } + vtkDataObject *res20(res2c->GetBlock(blockId)); + vtkPolyData *res20c(vtkPolyData::SafeDownCast(res20)); + if(!res20c) + { + throw MZCException("Internal error ! resample filter returned a mono block MultiBlockDataSet but this block is not a vtkPolyData !"); + } + return res20c; + } + if(blockId!=0) + throw MZCException("Internal error ! 0 expected !"); + return res2; +} + +int vtkSpatialPfl::RequestData(vtkInformation* request, vtkInformationVector** inputVector, vtkInformationVector* outputVector) +{ + // std::cerr << "########################################## vtkSpatialPfl::RequestData + // ##########################################" << std::endl; + try + { + vtkUnstructuredGrid* usgIn(0); + ExtractInfo(inputVector[0], usgIn); + // + vtkInformation* sourceInfo(inputVector[1]->GetInformationObject(0)); + vtkDataObject* source(sourceInfo->Get(vtkDataObject::DATA_OBJECT())); + vtkDataSet* source2(vtkDataSet::SafeDownCast(source)); + + int nbBlocks2(GetNumberOfBlocs(source)); + vtkNew sampleLineMB; + sampleLineMB->SetNumberOfBlocks(nbBlocks2); + + //vtkSmartPointer sampleLine; + if (this->ResampleInput) + { + for(int iBlock = 0 ; iBlock < nbBlocks2 ; ++iBlock) + { + vtkPolyData* pl = SplitSingleMultiBloc(source,iBlock); + if (!pl) + { + vtkErrorMacro("The second input of this filter must be of type vtkPolyData."); + return 1; + } + vtkSmartPointer sampleLine; + sampleLine.TakeReference(this->ResamplePolyLine(pl)); + sampleLineMB->SetBlock(iBlock,sampleLine); + } + } + else + { + for(int iBlock = 0 ; iBlock < nbBlocks2 ; ++iBlock) + { + //sampleLine = ExtractTo(source); + sampleLineMB->SetBlock(iBlock,SplitSingleMultiBloc(source,iBlock)); + } + } + // + vtkNew probeFilter; + probeFilter->SetInputData(sampleLineMB); + probeFilter->SetSourceData(usgIn); + probeFilter->Update(); + vtkDataObject* res(probeFilter->GetOutput()); + // + int nbBlocks(GetNumberOfBlocs(res)); + vtkNew table; + vtkInformation* outInfo = outputVector->GetInformationObject(0); + vtkMultiBlockDataSet *output ( vtkMultiBlockDataSet::SafeDownCast(outInfo->Get(vtkDataObject::DATA_OBJECT())) ); + output->SetNumberOfBlocks(nbBlocks); + for(int blockId = 0 ; blockId < nbBlocks ; ++blockId) + { + vtkPolyData *res2(SplitSingleMultiBloc(res,blockId)); + buildTableFromPolyData(output, res2, blockId, nbBlocks); + } + } + catch (MZCException& e) + { + std::ostringstream oss; + oss << "Exception has been thrown in vtkSpatialPfl::RequestData : " << e.what() << std::endl; + if (this->HasObserver("ErrorEvent")) + { + this->InvokeEvent("ErrorEvent", const_cast(oss.str().c_str())); + } + else + { + vtkOutputWindowDisplayErrorText(const_cast(oss.str().c_str())); + } + vtkObject::BreakOnError(); + return 0; + } + return 1; +} + +void vtkSpatialPfl::PrintSelf(ostream& os, vtkIndent indent) +{ + this->Superclass::PrintSelf(os, indent); +} + +void vtkSpatialPfl::SetSourceData(vtkDataObject* input) +{ + this->SetInputData(1, input); +} + +void vtkSpatialPfl::SetSourceConnection(vtkAlgorithmOutput* algOutput) +{ + this->SetInputConnection(1, algOutput); +} + +int vtkSpatialPfl::FillOutputPortInformation(int vtkNotUsed(port), vtkInformation* info) +{ + info->Set(vtkDataObject::DATA_TYPE_NAME(), "vtkMultiBlockDataSet"); + return 1; +} + +vtkPolyData* vtkSpatialPfl::ResamplePolyLine(vtkPolyData* pl) +{ + vtkPolyData* res = vtkPolyData::New(); + + vtkNew subdivision; + subdivision->SetPoints(pl->GetPoints()); + subdivision->SetResolution(this->NumberOfSamples); + subdivision->Update(); + res->ShallowCopy(subdivision->GetOutputDataObject(0)); + + return res; +} + +/* + + + */ + +// /opt/cmake/3.6.2/bin/cmake -DCMAKE_INSTALL_PREFIX=/home/H87074/TMP117_HYDRAU/SpatialPfl_install +// -DCONFIGURATION_ROOT_DIR=/opt/salome-conf/8.3.0 -DCMAKE_BUILD_TYPE=Debug +// -DPYTHON_INCLUDE_DIR=/usr/include/python2.7 +// -DPYTHON_LIBRARY=/usr/lib/x86_64-linux-gnu/libpython2.7.so ../SpatialPfl +/* +export LD_LIBRARY_PATH=/home/H87074/TMP117_HYDRAU/SpatialPfl_install/lib:${LD_LIBRARY_PATH} +export PV_PLUGIN_PATH=/home/H87074/TMP117_HYDRAU/SpatialPfl_install/lib:${PV_PLUGIN_PATH} +*/ diff --git a/src/SpatialPfl/plugin/SpatialPflModule/vtkSpatialPfl.h b/src/SpatialPfl/plugin/SpatialPflModule/vtkSpatialPfl.h new file mode 100644 index 0000000..d84a01f --- /dev/null +++ b/src/SpatialPfl/plugin/SpatialPflModule/vtkSpatialPfl.h @@ -0,0 +1,74 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +// Author : Anthony Geay (EDF R&D) + +#pragma once + +#include "vtkDataObjectAlgorithm.h" + +class vtkPolyData; + +class VTK_EXPORT vtkSpatialPfl : public vtkDataObjectAlgorithm +{ +public: + static vtkSpatialPfl* New(); + vtkTypeMacro(vtkSpatialPfl, vtkDataObjectAlgorithm); + void PrintSelf(ostream& os, vtkIndent indent) override; + + void SetSourceData(vtkDataObject* input); + + void SetSourceConnection(vtkAlgorithmOutput* algOutput); + + //@{ + /** + * Set/Get the number of points that will be considered along the polyline source + * before the probing. + */ + vtkGetMacro(ResampleInput, bool); + vtkSetMacro(ResampleInput, bool); + vtkBooleanMacro(ResampleInput, bool); + //@} + + //@{ + /** + * Set/Get the number of points that will be considered along the polyline source + * before the probing. + */ + vtkGetMacro(NumberOfSamples, int); + vtkSetMacro(NumberOfSamples, int); + //@} + + int FillOutputPortInformation(int vtkNotUsed(port), vtkInformation* info) override; + +protected: + vtkSpatialPfl(); + ~vtkSpatialPfl() override = default; + + int RequestInformation(vtkInformation*, vtkInformationVector**, vtkInformationVector*) override; + int RequestData(vtkInformation*, vtkInformationVector**, vtkInformationVector*) override; + + vtkPolyData* ResamplePolyLine(vtkPolyData* pl); + + bool ResampleInput = false; + int NumberOfSamples = 0; + +private: + vtkSpatialPfl(const vtkSpatialPfl&) = delete; + void operator=(const vtkSpatialPfl&) = delete; +}; diff --git a/src/SpatialPfl/plugin/filters.xml b/src/SpatialPfl/plugin/filters.xml new file mode 100644 index 0000000..4d87321 --- /dev/null +++ b/src/SpatialPfl/plugin/filters.xml @@ -0,0 +1,143 @@ + + + + + + + + + + + + + This property specifies the input to the Level Scalars filter. + + + + + + + + + + + The value of this property determines the points where probe will be done. + + + + + + If this entry is checked, the user can specify the level of + subdivision applied to each segment of the input polyline. + + + + + + + + + + + + The value of this property determines the level of subdivision + applied to each segment of the input polyline. + + + + + + + + + + + + + + + + + + + + + + + + This property specifies the input to the Level Scalars filter. + + + + + + + + The value of this property determines the points where probe will be done. + + + + + + If this entry is checked, the user can specify the level of + subdivision applied to each segment of the input polyline. + + + + + + + + + + + + The value of this property determines the level of subdivision + applied to each segment of the input polyline. + + + + + + + + + + + + + + + diff --git a/src/SpatialPfl/plugin/paraview.plugin b/src/SpatialPfl/plugin/paraview.plugin new file mode 100644 index 0000000..73cd445 --- /dev/null +++ b/src/SpatialPfl/plugin/paraview.plugin @@ -0,0 +1,30 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +NAME + SpatialPfl +DESCRIPTION + This plugin provides the SpatialPfl filter. +REQUIRES_MODULES + VTK::CommonCore + VTK::CommonDataModel + VTK::IOCore + VTK::FiltersCore + VTK::FiltersSources + VTK::FiltersGeneral diff --git a/src/SphereAlongLines/CMakeLists.txt b/src/SphereAlongLines/CMakeLists.txt new file mode 100644 index 0000000..01b2213 --- /dev/null +++ b/src/SphereAlongLines/CMakeLists.txt @@ -0,0 +1,49 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +cmake_minimum_required(VERSION 3.8) +project(SphereAlongLinesPlugin) +find_package(ParaView REQUIRED) + +include(GNUInstallDirs) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}") +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}") +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}") + +set("_paraview_plugin_default_${CMAKE_PROJECT_NAME}" ON) +paraview_plugin_scan( + ENABLE_BY_DEFAULT YES + PLUGIN_FILES "${CMAKE_CURRENT_SOURCE_DIR}/plugin/paraview.plugin" + PROVIDES_PLUGINS plugins + REQUIRES_MODULES required_modules) + +foreach(module IN LISTS required_modules) + if(NOT TARGET "${module}") + message("Missing required module: ${module}") + return() + endif() +endforeach() + +set(BUILD_SHARED_LIBS ON) +paraview_plugin_build( + RUNTIME_DESTINATION "${CMAKE_INSTALL_BINDIR}" + LIBRARY_DESTINATION "${CMAKE_INSTALL_LIBDIR}" + LIBRARY_SUBDIRECTORY "${PARAVIEW_PLUGIN_SUBDIR}" + PLUGINS ${plugins} + AUTOLOAD ${plugins}) diff --git a/src/SphereAlongLines/Test/test_dev_surface.py b/src/SphereAlongLines/Test/test_dev_surface.py new file mode 100644 index 0000000..1675518 --- /dev/null +++ b/src/SphereAlongLines/Test/test_dev_surface.py @@ -0,0 +1,161 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +#### import the simple module from the paraview +from paraview.simple import * +#### disable automatic camera reset on 'Show' +paraview.simple._DisableFirstRenderCameraReset() + +# create a new 'MED Reader' +multiTSmed = MEDReader(FileName='multiTS.med') +multiTSmed.AllArrays = ['TS0/Mesh/ComSup0/Pressure@@][@@P0'] +multiTSmed.AllTimeSteps = ['0000', '0001', '0002', '0003', '0004', '0005', '0006', '0007', '0008', '0009'] + +# get animation scene +animationScene1 = GetAnimationScene() + +# update animation scene based on data timesteps +animationScene1.UpdateAnimationUsingDataTimeSteps() + +# get active view +renderView1 = GetActiveViewOrCreate('RenderView') +# uncomment following to set a specific view size +# renderView1.ViewSize = [1499, 582] + +# show data in view +multiTSmedDisplay = Show(multiTSmed, renderView1) + +# trace defaults for the display properties. +multiTSmedDisplay.Representation = 'Surface' +multiTSmedDisplay.ColorArrayName = [None, ''] +multiTSmedDisplay.OSPRayScaleArray = 'FamilyIdNode' +multiTSmedDisplay.OSPRayScaleFunction = 'PiecewiseFunction' +multiTSmedDisplay.SelectOrientationVectors = 'FamilyIdNode' +multiTSmedDisplay.ScaleFactor = 0.07399989366531372 +multiTSmedDisplay.SelectScaleArray = 'FamilyIdNode' +multiTSmedDisplay.GlyphType = 'Arrow' +multiTSmedDisplay.GlyphTableIndexArray = 'FamilyIdNode' +multiTSmedDisplay.DataAxesGrid = 'GridAxesRepresentation' +multiTSmedDisplay.PolarAxes = 'PolarAxesRepresentation' +multiTSmedDisplay.ScalarOpacityUnitDistance = 0.017316274962626298 +multiTSmedDisplay.GaussianRadius = 0.03699994683265686 +multiTSmedDisplay.SetScaleArray = ['POINTS', 'FamilyIdNode'] +multiTSmedDisplay.ScaleTransferFunction = 'PiecewiseFunction' +multiTSmedDisplay.OpacityArray = ['POINTS', 'FamilyIdNode'] +multiTSmedDisplay.OpacityTransferFunction = 'PiecewiseFunction' +multiTSmedDisplay.InputVectors = [None, ''] +multiTSmedDisplay.SelectInputVectors = [None, ''] +multiTSmedDisplay.WriteLog = '' + +# init the 'PiecewiseFunction' selected for 'ScaleTransferFunction' +multiTSmedDisplay.ScaleTransferFunction.Points = [0.0, 0.0, 0.5, 0.0, 1.1757813367477812e-38, 1.0, 0.5, 0.0] + +# init the 'PiecewiseFunction' selected for 'OpacityTransferFunction' +multiTSmedDisplay.OpacityTransferFunction.Points = [0.0, 0.0, 0.5, 0.0, 1.1757813367477812e-38, 1.0, 0.5, 0.0] + +# reset view to fit data +renderView1.ResetCamera() + +# update the view to ensure updated data information +renderView1.Update() + +# create a new 'Developed Surface' +developedSurface1 = DevelopedSurface(Input=multiTSmed) +developedSurface1.SliceType = 'Cylinder' + +# init the 'Cylinder' selected for 'SliceType' +developedSurface1.SliceType.Center = [0.0, 0.0, 0.05000000074505806] +developedSurface1.SliceType.Radius = 0.3699994683265686 + +# Properties modified on developedSurface1.SliceType +developedSurface1.SliceType.Center = [0.0, 0.0, 0.05] +developedSurface1.SliceType.Axis = [0.0, 0.0, 1.0] +developedSurface1.SliceType.Radius = 0.07 + +# Properties modified on developedSurface1.SliceType +developedSurface1.SliceType.Center = [0.0, 0.0, 0.05] +developedSurface1.SliceType.Axis = [0.0, 0.0, 1.0] +developedSurface1.SliceType.Radius = 0.07 + +# show data in view +developedSurface1Display = Show(developedSurface1, renderView1) + +# trace defaults for the display properties. +developedSurface1Display.Representation = 'Surface' +developedSurface1Display.ColorArrayName = [None, ''] +developedSurface1Display.OSPRayScaleArray = 'FamilyIdNode' +developedSurface1Display.OSPRayScaleFunction = 'PiecewiseFunction' +developedSurface1Display.SelectOrientationVectors = 'FamilyIdNode' +developedSurface1Display.ScaleFactor = 0.043982297150257116 +developedSurface1Display.SelectScaleArray = 'FamilyIdNode' +developedSurface1Display.GlyphType = 'Arrow' +developedSurface1Display.GlyphTableIndexArray = 'FamilyIdNode' +developedSurface1Display.DataAxesGrid = 'GridAxesRepresentation' +developedSurface1Display.PolarAxes = 'PolarAxesRepresentation' +developedSurface1Display.GaussianRadius = 0.021991148575128558 +developedSurface1Display.SetScaleArray = ['POINTS', 'FamilyIdNode'] +developedSurface1Display.ScaleTransferFunction = 'PiecewiseFunction' +developedSurface1Display.OpacityArray = ['POINTS', 'FamilyIdNode'] +developedSurface1Display.OpacityTransferFunction = 'PiecewiseFunction' +developedSurface1Display.InputVectors = [None, ''] +developedSurface1Display.SelectInputVectors = [None, ''] +developedSurface1Display.WriteLog = '' + +# init the 'PiecewiseFunction' selected for 'ScaleTransferFunction' +developedSurface1Display.ScaleTransferFunction.Points = [0.0, 0.0, 0.5, 0.0, 1.1757813367477812e-38, 1.0, 0.5, 0.0] + +# init the 'PiecewiseFunction' selected for 'OpacityTransferFunction' +developedSurface1Display.OpacityTransferFunction.Points = [0.0, 0.0, 0.5, 0.0, 1.1757813367477812e-38, 1.0, 0.5, 0.0] + +# hide data in view +Hide(multiTSmed, renderView1) + +# update the view to ensure updated data information +renderView1.Update() + +#change interaction mode for render view +renderView1.InteractionMode = '2D' + +# toggle 3D widget visibility (only when running from the GUI) +Hide3DWidgets(proxy=developedSurface1.SliceType) + +# set scalar coloring +ColorBy(developedSurface1Display, ('CELLS', 'Pressure')) + +# rescale color and/or opacity maps used to include current data range +developedSurface1Display.RescaleTransferFunctionToDataRange(True, False) + +# show color bar/color legend +developedSurface1Display.SetScalarBarVisibility(renderView1, True) + +# get color transfer function/color map for 'Pressure' +pressureLUT = GetColorTransferFunction('Pressure') + +#### saving camera placements for all active views + +# current camera placement for renderView1 +renderView1.InteractionMode = '2D' +renderView1.CameraPosition = [0.18935662797765695, 0.01726656182167085, 2.08092363470839] +renderView1.CameraFocalPoint = [0.18935662797765695, 0.01726656182167085, 0.05000000074505806] +renderView1.CameraParallelScale = 0.16748564967020724 + +#### uncomment the following to render all views +# RenderAllViews() +# alternatively, if you want to write images, you can use SaveScreenshot(...). +Render() diff --git a/src/SphereAlongLines/Test/test_dev_surface2.py b/src/SphereAlongLines/Test/test_dev_surface2.py new file mode 100644 index 0000000..3d7e85e --- /dev/null +++ b/src/SphereAlongLines/Test/test_dev_surface2.py @@ -0,0 +1,169 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +#### import the simple module from the paraview +from paraview.simple import * +from math import pi +TMPFileName="test2.med" + +#### disable automatic camera reset on 'Show' +paraview.simple._DisableFirstRenderCameraReset() + +# create a new 'Mandelbrot' +mandelbrot1 = Mandelbrot() + +# Properties modified on mandelbrot1 +mandelbrot1.WholeExtent = [0, 50, 0, 50, 0, 50] + +# get active view +renderView1 = GetActiveViewOrCreate('RenderView') +# uncomment following to set a specific view size +# renderView1.ViewSize = [1017, 317] + +# show data in view +mandelbrot1Display = Show(mandelbrot1, renderView1) + +# trace defaults for the display properties. +mandelbrot1Display.Representation = 'Outline' +mandelbrot1Display.ColorArrayName = ['POINTS', ''] +mandelbrot1Display.OSPRayScaleArray = 'Iterations' +mandelbrot1Display.OSPRayScaleFunction = 'PiecewiseFunction' +mandelbrot1Display.SelectOrientationVectors = 'Iterations' +mandelbrot1Display.ScaleFactor = 0.25 +mandelbrot1Display.SelectScaleArray = 'Iterations' +mandelbrot1Display.GlyphType = 'Arrow' +mandelbrot1Display.GlyphTableIndexArray = 'Iterations' +mandelbrot1Display.DataAxesGrid = 'GridAxesRepresentation' +mandelbrot1Display.PolarAxes = 'PolarAxesRepresentation' +mandelbrot1Display.ScalarOpacityUnitDistance = 0.08124038404635964 +mandelbrot1Display.Slice = 25 +mandelbrot1Display.GaussianRadius = 0.125 +mandelbrot1Display.SetScaleArray = ['POINTS', 'Iterations'] +mandelbrot1Display.ScaleTransferFunction = 'PiecewiseFunction' +mandelbrot1Display.OpacityArray = ['POINTS', 'Iterations'] +mandelbrot1Display.OpacityTransferFunction = 'PiecewiseFunction' +mandelbrot1Display.InputVectors = [None, ''] +mandelbrot1Display.SelectInputVectors = [None, ''] +mandelbrot1Display.WriteLog = '' + +# init the 'PiecewiseFunction' selected for 'ScaleTransferFunction' +mandelbrot1Display.ScaleTransferFunction.Points = [1.0, 0.0, 0.5, 0.0, 100.0, 1.0, 0.5, 0.0] + +# init the 'PiecewiseFunction' selected for 'OpacityTransferFunction' +mandelbrot1Display.OpacityTransferFunction.Points = [1.0, 0.0, 0.5, 0.0, 100.0, 1.0, 0.5, 0.0] + +# reset view to fit data +renderView1.ResetCamera() + +# update the view to ensure updated data information +renderView1.Update() + +# create a new 'Developed Surface' +developedSurface1 = DevelopedSurface(Input=mandelbrot1) +developedSurface1.SliceType = 'Cylinder' + +# init the 'Cylinder' selected for 'SliceType' +developedSurface1.SliceType.Center = [-0.5, 0.0, 1.0] +developedSurface1.SliceType.Radius = 0.5 #1.25 + +# show data in view +developedSurface1Display = Show(developedSurface1, renderView1) + +# get color transfer function/color map for 'Iterations' +iterationsLUT = GetColorTransferFunction('Iterations') + +# trace defaults for the display properties. +developedSurface1Display.Representation = 'Surface' +developedSurface1Display.ColorArrayName = ['POINTS', 'Iterations'] +developedSurface1Display.LookupTable = iterationsLUT +developedSurface1Display.OSPRayScaleArray = 'Iterations' +developedSurface1Display.OSPRayScaleFunction = 'PiecewiseFunction' +developedSurface1Display.SelectOrientationVectors = 'Iterations' +developedSurface1Display.ScaleFactor = 0.7853981633974483 +developedSurface1Display.SelectScaleArray = 'Iterations' +developedSurface1Display.GlyphType = 'Arrow' +developedSurface1Display.GlyphTableIndexArray = 'Iterations' +developedSurface1Display.DataAxesGrid = 'GridAxesRepresentation' +developedSurface1Display.PolarAxes = 'PolarAxesRepresentation' +developedSurface1Display.GaussianRadius = 0.39269908169872414 +developedSurface1Display.SetScaleArray = ['POINTS', 'Iterations'] +developedSurface1Display.ScaleTransferFunction = 'PiecewiseFunction' +developedSurface1Display.OpacityArray = ['POINTS', 'Iterations'] +developedSurface1Display.OpacityTransferFunction = 'PiecewiseFunction' +developedSurface1Display.InputVectors = [None, ''] +developedSurface1Display.SelectInputVectors = [None, ''] +developedSurface1Display.WriteLog = '' + +# init the 'PiecewiseFunction' selected for 'ScaleTransferFunction' +developedSurface1Display.ScaleTransferFunction.Points = [1.0, 0.0, 0.5, 0.0, 100.0, 1.0, 0.5, 0.0] + +# init the 'PiecewiseFunction' selected for 'OpacityTransferFunction' +developedSurface1Display.OpacityTransferFunction.Points = [1.0, 0.0, 0.5, 0.0, 100.0, 1.0, 0.5, 0.0] + +# hide data in view +Hide(mandelbrot1, renderView1) + +# show color bar/color legend +developedSurface1Display.SetScalarBarVisibility(renderView1, True) + +# update the view to ensure updated data information +renderView1.Update() + +# toggle 3D widget visibility (only when running from the GUI) +Hide3DWidgets(proxy=developedSurface1.SliceType) + +#### saving camera placements for all active views + +# current camera placement for renderView1 +renderView1.CameraPosition = [4.090024784500779, -0.15919161102314858, 7.485304552729019] +renderView1.CameraFocalPoint = [4.090024784500779, -0.15919161102314858, 1.0] +renderView1.CameraParallelScale = 2.03100960115899 + +#### uncomment the following to render all views +# RenderAllViews() +# alternatively, if you want to write images, you can use SaveScreenshot(...). + +mand=servermanager.Fetch(mandelbrot1) +axisId=1 +high_out=mand.GetSpacing()[axisId]*(mand.GetExtent()[2*axisId+1]-mand.GetExtent()[2*axisId+0]) + +vtp=servermanager.Fetch(developedSurface1) +arr=vtp.GetPointData().GetArray(0) +assert(arr.GetName()=="Iterations") +a,b=arr.GetRange() +assert(a>=1 and a<=2) +assert(b==100.) +SaveData(TMPFileName, proxy=developedSurface1) +from MEDLoader import * + +mm=MEDFileMesh.New(TMPFileName) +m0=mm[0] +area=m0.getMeasureField(True).getArray().accumulate()[0] + +zeResu0=area/high_out/developedSurface1.SliceType.Radius +assert(abs(zeResu0-2*pi)<1e-5) + +fs=MEDFileFields(TMPFileName) +f=fs["Iterations"][0].field(mm) +nodeIds=f.getArray().convertToDblArr().findIdsInRange(99.,101.) +cellIds=m0.getCellIdsLyingOnNodes(nodeIds,True) +zeResu1=m0[cellIds].getMeasureField(True).getArray().accumulate()[0] + +assert(abs(zeResu1-1.1427)<1e-2) + diff --git a/src/SphereAlongLines/Test/test_dev_surface3.py b/src/SphereAlongLines/Test/test_dev_surface3.py new file mode 100644 index 0000000..c4b1b36 --- /dev/null +++ b/src/SphereAlongLines/Test/test_dev_surface3.py @@ -0,0 +1,165 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +#### import the simple module from the paraview +from paraview.simple import * +from math import pi +TMPFileName="test3.med" + +#### disable automatic camera reset on 'Show' +paraview.simple._DisableFirstRenderCameraReset() + +# create a new 'Mandelbrot' +mandelbrot1 = Mandelbrot() + +# Properties modified on mandelbrot1 +mandelbrot1.WholeExtent = [0, 50, 0, 50, 0, 50] + +# get active view +renderView1 = GetActiveViewOrCreate('RenderView') +# uncomment following to set a specific view size +# renderView1.ViewSize = [1017, 317] + +# show data in view +mandelbrot1Display = Show(mandelbrot1, renderView1) + +# trace defaults for the display properties. +mandelbrot1Display.Representation = 'Outline' +mandelbrot1Display.ColorArrayName = ['POINTS', ''] +mandelbrot1Display.OSPRayScaleArray = 'Iterations' +mandelbrot1Display.OSPRayScaleFunction = 'PiecewiseFunction' +mandelbrot1Display.SelectOrientationVectors = 'Iterations' +mandelbrot1Display.ScaleFactor = 0.25 +mandelbrot1Display.SelectScaleArray = 'Iterations' +mandelbrot1Display.GlyphType = 'Arrow' +mandelbrot1Display.GlyphTableIndexArray = 'Iterations' +mandelbrot1Display.DataAxesGrid = 'GridAxesRepresentation' +mandelbrot1Display.PolarAxes = 'PolarAxesRepresentation' +mandelbrot1Display.ScalarOpacityUnitDistance = 0.08124038404635964 +mandelbrot1Display.Slice = 25 +mandelbrot1Display.GaussianRadius = 0.125 +mandelbrot1Display.SetScaleArray = ['POINTS', 'Iterations'] +mandelbrot1Display.ScaleTransferFunction = 'PiecewiseFunction' +mandelbrot1Display.OpacityArray = ['POINTS', 'Iterations'] +mandelbrot1Display.OpacityTransferFunction = 'PiecewiseFunction' +mandelbrot1Display.InputVectors = [None, ''] +mandelbrot1Display.SelectInputVectors = [None, ''] +mandelbrot1Display.WriteLog = '' + +# init the 'PiecewiseFunction' selected for 'ScaleTransferFunction' +mandelbrot1Display.ScaleTransferFunction.Points = [1.0, 0.0, 0.5, 0.0, 100.0, 1.0, 0.5, 0.0] + +# init the 'PiecewiseFunction' selected for 'OpacityTransferFunction' +mandelbrot1Display.OpacityTransferFunction.Points = [1.0, 0.0, 0.5, 0.0, 100.0, 1.0, 0.5, 0.0] + +# reset view to fit data +renderView1.ResetCamera() + +# update the view to ensure updated data information +renderView1.Update() + +# create a new 'Developed Surface' +developedSurface1 = DevelopedSurface(Input=mandelbrot1) +developedSurface1.SliceType = 'Cylinder' + +# init the 'Cylinder' selected for 'SliceType' +developedSurface1.SliceType.Center = [-0.5, 0.0, 1.0] +developedSurface1.SliceType.Radius = 0.5 #1.25 +developedSurface1.SliceType.Axis = [-0.5065630563269753, -0.6288876685363318, -0.5898255422814533] + +# show data in view +developedSurface1Display = Show(developedSurface1, renderView1) + +# get color transfer function/color map for 'Iterations' +iterationsLUT = GetColorTransferFunction('Iterations') + +# trace defaults for the display properties. +developedSurface1Display.Representation = 'Surface' +developedSurface1Display.ColorArrayName = ['POINTS', 'Iterations'] +developedSurface1Display.LookupTable = iterationsLUT +developedSurface1Display.OSPRayScaleArray = 'Iterations' +developedSurface1Display.OSPRayScaleFunction = 'PiecewiseFunction' +developedSurface1Display.SelectOrientationVectors = 'Iterations' +developedSurface1Display.ScaleFactor = 0.7853981633974483 +developedSurface1Display.SelectScaleArray = 'Iterations' +developedSurface1Display.GlyphType = 'Arrow' +developedSurface1Display.GlyphTableIndexArray = 'Iterations' +developedSurface1Display.DataAxesGrid = 'GridAxesRepresentation' +developedSurface1Display.PolarAxes = 'PolarAxesRepresentation' +developedSurface1Display.GaussianRadius = 0.39269908169872414 +developedSurface1Display.SetScaleArray = ['POINTS', 'Iterations'] +developedSurface1Display.ScaleTransferFunction = 'PiecewiseFunction' +developedSurface1Display.OpacityArray = ['POINTS', 'Iterations'] +developedSurface1Display.OpacityTransferFunction = 'PiecewiseFunction' +developedSurface1Display.InputVectors = [None, ''] +developedSurface1Display.SelectInputVectors = [None, ''] +developedSurface1Display.WriteLog = '' + +# init the 'PiecewiseFunction' selected for 'ScaleTransferFunction' +developedSurface1Display.ScaleTransferFunction.Points = [1.0, 0.0, 0.5, 0.0, 100.0, 1.0, 0.5, 0.0] + +# init the 'PiecewiseFunction' selected for 'OpacityTransferFunction' +developedSurface1Display.OpacityTransferFunction.Points = [1.0, 0.0, 0.5, 0.0, 100.0, 1.0, 0.5, 0.0] + +# hide data in view +Hide(mandelbrot1, renderView1) + +# show color bar/color legend +developedSurface1Display.SetScalarBarVisibility(renderView1, True) + +# update the view to ensure updated data information +renderView1.Update() + +# toggle 3D widget visibility (only when running from the GUI) +Hide3DWidgets(proxy=developedSurface1.SliceType) + +#### saving camera placements for all active views + +# current camera placement for renderView1 +renderView1.CameraPosition = [4.090024784500779, -0.15919161102314858, 7.485304552729019] +renderView1.CameraFocalPoint = [4.090024784500779, -0.15919161102314858, 1.0] +renderView1.CameraParallelScale = 2.03100960115899 + +#### uncomment the following to render all views +# RenderAllViews() +# alternatively, if you want to write images, you can use SaveScreenshot(...). + + +vtp=servermanager.Fetch(developedSurface1) +arr=vtp.GetPointData().GetArray(0) +assert(arr.GetName()=="Iterations") +a,b=arr.GetRange() +assert(a>=1 and a<=2) +assert(b==100.) +SaveData(TMPFileName, proxy=developedSurface1) +from MEDLoader import * + +mm=MEDFileMesh.New(TMPFileName) +m0=mm[0] +area=m0.getMeasureField(True).getArray().accumulate()[0] + + +fs=MEDFileFields(TMPFileName) +f=fs["Iterations"][0].field(mm) +nodeIds=f.getArray().convertToDblArr().findIdsInRange(99.,101.) +cellIds=m0.getCellIdsLyingOnNodes(nodeIds,True) +zeResu1=m0[cellIds].getMeasureField(True).getArray().accumulate()[0] + +assert(abs(zeResu1-1.3564)<1e-2) + diff --git a/src/SphereAlongLines/plugin/CMakeLists.txt b/src/SphereAlongLines/plugin/CMakeLists.txt new file mode 100644 index 0000000..bbf2f2f --- /dev/null +++ b/src/SphereAlongLines/plugin/CMakeLists.txt @@ -0,0 +1,31 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +paraview_add_plugin(SphereAlongLinesPlugin + VERSION "1.0" + MODULES SphereAlongLinesModule + MODULE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/SphereAlongLinesModule/vtk.module" + SERVER_MANAGER_XML filters.xml +) + +install(TARGETS SphereAlongLinesPlugin + RUNTIME DESTINATION lib/paraview + LIBRARY DESTINATION lib/paraview + ARCHIVE DESTINATION lib/paraview +) diff --git a/src/SphereAlongLines/plugin/SphereAlongLinesModule/CMakeLists.txt b/src/SphereAlongLines/plugin/SphereAlongLinesModule/CMakeLists.txt new file mode 100644 index 0000000..75e4e10 --- /dev/null +++ b/src/SphereAlongLines/plugin/SphereAlongLinesModule/CMakeLists.txt @@ -0,0 +1,27 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +set(classes + vtkSphereAlongLines +) + +vtk_module_add_module(SphereAlongLinesModule + FORCE_STATIC + CLASSES ${classes} +) diff --git a/src/SphereAlongLines/plugin/SphereAlongLinesModule/vtk.module b/src/SphereAlongLines/plugin/SphereAlongLinesModule/vtk.module new file mode 100644 index 0000000..6d2af88 --- /dev/null +++ b/src/SphereAlongLines/plugin/SphereAlongLinesModule/vtk.module @@ -0,0 +1,31 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +NAME + SphereAlongLinesModule +DEPENDS + VTK::CommonCore + VTK::CommonDataModel + VTK::CommonExecutionModel + VTK::FiltersCore + VTK::FiltersModeling +PRIVATE_DEPENDS + VTK::CommonMisc + VTK::CommonSystem + VTK::FiltersGeneral diff --git a/src/SphereAlongLines/plugin/SphereAlongLinesModule/vtkSphereAlongLines.cxx b/src/SphereAlongLines/plugin/SphereAlongLinesModule/vtkSphereAlongLines.cxx new file mode 100644 index 0000000..f1f3887 --- /dev/null +++ b/src/SphereAlongLines/plugin/SphereAlongLinesModule/vtkSphereAlongLines.cxx @@ -0,0 +1,714 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +// Author : Anthony Geay (EDF R&D) + +#include "vtkSphereAlongLines.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +vtkStandardNewMacro(vtkSphereAlongLines); + +template +struct VTKTraits +{ +}; + +template <> +struct VTKTraits +{ + using VtkType = vtkFloatArray; +}; + +template <> +struct VTKTraits +{ + using VtkType = vtkDoubleArray; +}; + +constexpr const char INTEGRATIONTIME_ARR_NAME[] = "IntegrationTime"; + +/////////////////// + +class MZCException : public std::exception +{ +public: + MZCException(const std::string& s) + : _reason(s) + { + } + virtual const char* what() const throw() { return _reason.c_str(); } + virtual ~MZCException() throw() {} + +private: + std::string _reason; +}; + +void ExtractInfo(vtkInformationVector* inputVector, vtkPolyData*& usgIn) +{ + vtkInformation* inputInfo(inputVector->GetInformationObject(0)); + vtkDataSet* input(0); + vtkDataSet* input0(vtkDataSet::SafeDownCast(inputInfo->Get(vtkDataObject::DATA_OBJECT()))); + vtkMultiBlockDataSet* input1( + vtkMultiBlockDataSet::SafeDownCast(inputInfo->Get(vtkDataObject::DATA_OBJECT()))); + if (input0) + input = input0; + else + { + if (!input1) + throw MZCException( + "Input dataSet must be a DataSet or single elt multi block dataset expected !"); + if (input1->GetNumberOfBlocks() != 1) + throw MZCException("Input dataSet is a multiblock dataset with not exactly one block ! Use " + "MergeBlocks or ExtractBlocks filter before calling this filter !"); + vtkDataObject* input2(input1->GetBlock(0)); + if (!input2) + throw MZCException("Input dataSet is a multiblock dataset with exactly one block but this " + "single element is NULL !"); + vtkDataSet* input2c(vtkDataSet::SafeDownCast(input2)); + if (!input2c) + throw MZCException( + "Input dataSet is a multiblock dataset with exactly one block but this single element is " + "not a dataset ! Use MergeBlocks or ExtractBlocks filter before calling this filter !"); + input = input2c; + } + if (!input) + throw MZCException("Input data set is NULL !"); + vtkPointData* att(input->GetPointData()); + vtkPolyData* zeInput(vtkPolyData::SafeDownCast(input)); + if (!zeInput) + throw MZCException("Input dataSet is not a polydata as expected !"); + usgIn = zeInput; +} + +vtkDataArray* GetCoords(vtkPointSet* ds) +{ + vtkPoints* pts(ds->GetPoints()); + if (!pts) + throw MZCException("GetCoords : internal error 2 !"); + vtkDataArray* data(pts->GetData()); + if (!data) + throw MZCException("GetCoords : internal error 3 !"); + if (data->GetNumberOfComponents() != 3) + throw MZCException("GetCoords : internal error 4 !"); + return data; +} + +vtkDoubleArray* GetIntegrationTime(vtkPointSet* ds) +{ + vtkDataSetAttributes* dsa(ds->GetPointData()); + if (!dsa) + throw MZCException( + "GetIntegrationTime : no point data ! Is the input data comes from Stream Tracer !"); + int idx(0); + vtkAbstractArray* arr(dsa->GetAbstractArray(INTEGRATIONTIME_ARR_NAME, idx)); + if (!arr) + { + std::ostringstream oss; + oss << "GetIntegrationTime : no such " << INTEGRATIONTIME_ARR_NAME + << " array in input dataset !"; + throw MZCException(oss.str()); + } + vtkDoubleArray* ret(vtkDoubleArray::SafeDownCast(arr)); + if (!ret) + { + std::ostringstream oss; + oss << "GetIntegrationTime :" << INTEGRATIONTIME_ARR_NAME << " array expected to be float64 !"; + throw MZCException(oss.str()); + } + if (ret->GetNumberOfComponents() != 1) + { + std::ostringstream oss; + oss << "GetIntegrationTime :" << INTEGRATIONTIME_ARR_NAME + << " array expected to be single compo !"; + throw MZCException(oss.str()); + } + return ret; +} + +template +void RearrangeIfNecessaryImpl(typename VTKTraits::VtkType* coords, vtkDoubleArray* intTime, + std::vector >& connect, double eps) +{ + std::size_t nbCells(connect.size()); + if (nbCells % 2 != 0) + return; + std::size_t nbCellsCand(nbCells / 2); + const double* intTimePtr(intTime->GetPointer(0)); + const T* coordsPtr(coords->GetPointer(0)); + T distance[3]; + std::vector > connectOut(nbCellsCand); + for (std::size_t i = 0; i < nbCellsCand; i++) + { + const std::vector& elt0(connect[i]); + const std::vector& elt1(connect[i + nbCellsCand]); + if (elt0.empty() || elt1.empty()) + return; + vtkIdType pt0(elt0[0]), pt1(elt1[0]); + if (pt0 == pt1) + continue; + if (std::abs(intTimePtr[pt0]) > eps || std::abs(intTimePtr[pt1]) > eps) + return; + std::transform(coordsPtr + 3 * pt0, coordsPtr + 3 * (pt0 + 1), coordsPtr + 3 * pt1, distance, + [](T a, T b) { return b - a; }); + if (std::sqrt( + distance[0] * distance[0] + distance[1] * distance[1] + distance[2] * distance[2]) > eps) + return; + std::vector& tab(connectOut[i]); + tab.insert(tab.end(), elt1.rbegin(), elt1.rend()); + tab.back() = elt0[0]; + tab.insert(tab.end(), elt0.begin() + 1, elt0.end()); + } + connect = connectOut; +} + +void RearrangeIfNecessary(vtkDataArray* coords, vtkDoubleArray* intTime, + std::vector >& connect, double eps) +{ + vtkFloatArray* c0(vtkFloatArray::SafeDownCast(coords)); + if (c0) + { + RearrangeIfNecessaryImpl(c0, intTime, connect, eps); + return; + } + vtkDoubleArray* c1(vtkDoubleArray::SafeDownCast(coords)); + if (c1) + { + RearrangeIfNecessaryImpl(c1, intTime, connect, eps); + return; + } + throw MZCException("Not recognized type of data for coordinates !"); +} + +class vtkSphereAlongLines::vtkInternals +{ + friend class CurvAbsPathWalker; + + class PathWalker + { + protected: + PathWalker(const vtkSphereAlongLines::vtkInternals* intern) + : _internal(intern) + { + } + + public: + virtual double getTimeFrom(std::size_t pathId, double absTime2) const = 0; + virtual bool isInside(std::size_t pathId, double realTime, vtkIdType& a, vtkIdType& b, + double& aw, double& bw) const = 0; + + protected: + const vtkSphereAlongLines::vtkInternals* _internal; + }; + + class IntTimeGlobPathWalker : public PathWalker + { + public: + IntTimeGlobPathWalker(const vtkSphereAlongLines::vtkInternals* intern) + : PathWalker(intern) + { + } + double getTimeFrom(std::size_t pathId, double absTime2) const; + bool isInside(std::size_t pathId, double realTime, vtkIdType& a, vtkIdType& b, double& aw, + double& bw) const override; + }; + + class LocalPathWalker : public PathWalker + { + protected: + LocalPathWalker(const vtkSphereAlongLines::vtkInternals* intern) + : PathWalker(intern) + { + } + bool isInside(std::size_t pathId, double realTime, vtkIdType& a, vtkIdType& b, double& aw, + double& bw) const override; + double getTimeFrom(std::size_t pathId, double absTime2) const override; + virtual const std::vector& getRankingArray(std::size_t pathId) const = 0; + }; + + class IntTimeLocPathWalker : public LocalPathWalker + { + public: + IntTimeLocPathWalker(const vtkSphereAlongLines::vtkInternals* intern) + : LocalPathWalker(intern) + { + } + const std::vector& getRankingArray(std::size_t pathId) const override + { + return _internal->_integration_time_along_pathes[pathId]; + } + }; + + class CurvAbsPathWalker : public LocalPathWalker + { + public: + CurvAbsPathWalker(const vtkSphereAlongLines::vtkInternals* intern) + : LocalPathWalker(intern) + { + } + const std::vector& getRankingArray(std::size_t pathId) const override + { + return _internal->_curv_absc_along_pathes[pathId]; + } + }; + +public: + void initCacheIfNeeded(vtkPolyData* ds); + vtkSmartPointer dataSetAtNormalizedTime( + vtkPolyData* ds, double absTime, int walkType) const; + std::unique_ptr buildPathWalker(int walkType) const; + +private: + void initCacheForce(vtkPolyData* ds); + +private: + vtkMTimeType _input_DS_time = 0; + std::vector > _connectivity; + //! for each path it stores the integration time the corresponding time. Every array is expected + //! to be in ascending order + std::vector > _integration_time_along_pathes; + //! for each path it stores curv abscissa along pathes + std::vector > _curv_absc_along_pathes; + std::vector > _time_range_per_path; + std::vector > _ca_range_per_path; + double _abs_min = std::numeric_limits::max(); + double _abs_max = -std::numeric_limits::max(); +}; + +double vtkSphereAlongLines::vtkInternals::IntTimeGlobPathWalker::getTimeFrom( + std::size_t, double absTime2) const +{ + return _internal->_abs_min + absTime2 * (_internal->_abs_max - _internal->_abs_min); +} + +bool vtkSphereAlongLines::vtkInternals::IntTimeGlobPathWalker::isInside( + std::size_t pathId, double realTime, vtkIdType& a, vtkIdType& b, double& aw, double& bw) const +{ + const std::pair& pathRange(_internal->_time_range_per_path[pathId]); + if (realTime < pathRange.first || realTime > pathRange.second) + return false; + const std::vector& timeAlongPath(_internal->_integration_time_along_pathes[pathId]); + const std::vector& associatedNodes(_internal->_connectivity[pathId]); + std::vector::const_iterator it(std::find_if(timeAlongPath.begin(), timeAlongPath.end(), + [realTime](const double& val) -> bool { return realTime < val; })); + std::size_t pos(std::distance(timeAlongPath.begin(), it)); + pos = std::min(pos, timeAlongPath.size() - 1); + if (*it != realTime) + { + a = associatedNodes[pos - 1]; + b = associatedNodes[pos]; + aw = (timeAlongPath[pos] - realTime) / (timeAlongPath[pos] - timeAlongPath[pos - 1]); + bw = 1. - aw; + } + else + { + a = associatedNodes[pos]; + b = a; + aw = 1.; + bw = 0.; + } + return true; +} + +bool vtkSphereAlongLines::vtkInternals::LocalPathWalker::isInside( + std::size_t pathId, double realTime, vtkIdType& a, vtkIdType& b, double& aw, double& bw) const +{ + const std::vector& timeAlongPath(getRankingArray(pathId)); + const std::vector& associatedNodes(_internal->_connectivity[pathId]); + std::vector::const_iterator it(std::find_if(timeAlongPath.begin(), timeAlongPath.end(), + [realTime](const double& val) -> bool { return realTime <= val; })); + std::size_t pos(std::distance(timeAlongPath.begin(), it)); + pos = std::min(pos, timeAlongPath.size() - 1); + if (*it != realTime) + { + a = associatedNodes[pos - 1]; + b = associatedNodes[pos]; + aw = (timeAlongPath[pos] - realTime) / (timeAlongPath[pos] - timeAlongPath[pos - 1]); + bw = 1. - aw; + } + else + { + a = associatedNodes[pos]; + b = a; + aw = 1.; + bw = 0.; + } + return true; +} + +double vtkSphereAlongLines::vtkInternals::LocalPathWalker::getTimeFrom( + std::size_t pathId, double absTime2) const +{ + double realTime(std::numeric_limits::max()); + { + const std::vector& timeAlongPath(getRankingArray(pathId)); + realTime = timeAlongPath.front() + absTime2 * (timeAlongPath.back() - timeAlongPath.front()); + realTime = std::max(realTime, timeAlongPath.front()); + realTime = std::min(realTime, timeAlongPath.back()); + } + return realTime; +} + +std::unique_ptr +vtkSphereAlongLines::vtkInternals::buildPathWalker(int walkType) const +{ + switch (walkType) + { + case 0: + return std::unique_ptr( + new IntTimeGlobPathWalker(this)); + case 1: + return std::unique_ptr( + new IntTimeLocPathWalker(this)); + case 2: + return std::unique_ptr( + new CurvAbsPathWalker(this)); + default: + return std::unique_ptr( + new CurvAbsPathWalker(this)); + } +} + +void vtkSphereAlongLines::vtkInternals::initCacheIfNeeded(vtkPolyData* ds) +{ + vtkMTimeType mtime(ds->GetMTime()); + if (ds->GetMTime() == _input_DS_time) + return; + initCacheForce(ds); + _input_DS_time = mtime; +} + +void vtkSphereAlongLines::vtkInternals::initCacheForce(vtkPolyData* ds) +{ + // std::cout << "Force cache" << std::endl; + _abs_min = std::numeric_limits::max(); + _abs_max = -std::numeric_limits::max(); + _connectivity.clear(); + _integration_time_along_pathes.clear(); + _curv_absc_along_pathes.clear(); + _time_range_per_path.clear(); + _ca_range_per_path.clear(); + // to Improve + vtkDataArray* cooInBase(GetCoords(ds)); + vtkFloatArray* cooIn(vtkFloatArray::SafeDownCast(cooInBase)); + // + if ((ds->GetPolys() && ds->GetPolys()->GetNumberOfCells() > 0) || + (ds->GetStrips() && ds->GetStrips()->GetNumberOfCells() > 0) || + (ds->GetVerts() && ds->GetVerts()->GetNumberOfCells() > 0)) + throw MZCException( + "Presence of strips/vertices/polygons in input polydata ! Invalid with this type of filter!"); + vtkCellArray* cc(ds->GetLines()); + if (!cc) + throw MZCException("No polylines in input polydata ! Difficult to build something on it !"); + vtkIdType nbCells(cc->GetNumberOfCells()); + _connectivity.resize(nbCells); + vtkIdType npts; + const vtkIdType* pts; + cc->InitTraversal(); + for (vtkIdType i = 0; cc->GetNextCell(npts, pts); i++) + { + std::vector& conn2(_connectivity[i]); + conn2.insert(conn2.end(), pts, pts + npts); + } + cc->InitTraversal(); + vtkDoubleArray* ita(GetIntegrationTime(ds)); + RearrangeIfNecessary(GetCoords(ds), ita, _connectivity, 1e-5); + const double* itaPtr(ita->GetPointer(0)); + std::size_t nbCellsEff(_connectivity.size()); + _integration_time_along_pathes.resize(nbCellsEff); + _curv_absc_along_pathes.resize(nbCellsEff); + _time_range_per_path.resize(nbCellsEff); + _ca_range_per_path.resize(nbCellsEff); + for (std::size_t i = 0; i < nbCellsEff; i++) + { + const std::vector& conn2(_connectivity[i]); + std::vector& vals(_integration_time_along_pathes[i]); + std::vector& absCurv(_curv_absc_along_pathes[i]); + std::transform(conn2.begin(), conn2.end(), std::back_inserter(vals), + [itaPtr](vtkIdType nodeId) { return itaPtr[nodeId]; }); + float lastVal(0.); + const float* cooInPtr(cooIn->GetPointer(conn2[0] * 3)); + std::transform(conn2.begin(), conn2.end(), std::back_inserter(absCurv), + [cooIn, &cooInPtr, &lastVal](vtkIdType nodeId) { + const float* pt(cooIn->GetPointer(nodeId * 3)); + lastVal += sqrt((cooInPtr[0] - pt[0]) * (cooInPtr[0] - pt[0]) + + (cooInPtr[1] - pt[1]) * (cooInPtr[1] - pt[1]) + + (cooInPtr[2] - pt[2]) * (cooInPtr[2] - pt[2])); + cooInPtr = pt; + return lastVal; + }); + _time_range_per_path[i].first = vals.front(); + _time_range_per_path[i].second = vals.back(); + _ca_range_per_path[i].first = absCurv.front(); + _ca_range_per_path[i].second = absCurv.back(); + _abs_min = std::min(_abs_min, vals.front()); + _abs_max = std::max(_abs_max, vals.back()); + // std::cout << "cell " << i << " -> "; std::for_each(conn2.begin(),conn2.end(),[](vtkIdType + // elt) { std::cout << elt << " "; }); std::cout << std::endl; std::cout << "cell " << i << " -> + // "; std::for_each(absCurv.begin(),absCurv.end(),[](double elt) { std::cout << elt << " "; }); + // std::cout << std::endl; + } + // std::cerr << _abs_min << " - " << _abs_max << std::endl; +} + +template +void dealWith( + VTKDATAARRAY* arr1, vtkDataArray* arrOut, vtkIdType a, double aw, vtkIdType b, double bw) +{ + int nbCompo(arr1->GetNumberOfComponents()); + using VT = typename VTKDATAARRAY::ValueType; + VT *tupleA(arr1->GetPointer(nbCompo * a)), *tupleB(arr1->GetPointer(b * nbCompo)); + { + VT* tmpData = new VT[nbCompo]; + std::transform(tupleA, tupleA + nbCompo, tupleB, tmpData, + [aw, bw](VT a, VT b) -> VT { return VT(a) * VT(aw) + VT(b) * VT(bw); }); + arrOut->InsertNextTuple(tmpData); + delete[] tmpData; + } +} + +class CooAssign +{ +public: + virtual void apply( + vtkIdType a, double aw, vtkIdType b, double bw, vtkDoubleArray* cooOut) const = 0; +}; + +template +class CooAssignT : public CooAssign +{ +public: + using VtkType = typename VTKTraits::VtkType; + CooAssignT(VtkType* arr) + : _coo_in(arr->GetPointer(0)) + { + } + void apply(vtkIdType a, double aw, vtkIdType b, double bw, vtkDoubleArray* cooOut) const override + { + const T *pt0(_coo_in + 3 * a), *pt1(_coo_in + 3 * b); + std::transform(pt0, pt0 + 3, pt1, ptToAdd, [aw, bw](T elt0, T elt1) -> double { + return double(elt0) * double(aw) + double(elt1) * double(bw); + }); + cooOut->InsertNextTuple(ptToAdd); + } + +private: + const T* _coo_in; + T tmp[3]; + mutable double ptToAdd[3]; +}; + +vtkSmartPointer vtkSphereAlongLines::vtkInternals::dataSetAtNormalizedTime( + vtkPolyData* ds, double absTime, int walkType) const +{ + double absTime2(std::min(std::max(absTime, 0.), 1.)); + std::size_t maxNbOfPts(_integration_time_along_pathes.size()); + std::vector > outArrays; + std::vector inArrays; + { + vtkDataSetAttributes* dsa(ds->GetPointData()); + for (int i = 0; i < dsa->GetNumberOfArrays(); i++) + { + vtkDataArray* arr(dsa->GetArray(i)); + if (!arr) + continue; + vtkDoubleArray* arr1(vtkDoubleArray::SafeDownCast(arr)); + if (arr1) + { + vtkSmartPointer outArray(vtkDoubleArray::New()); + outArray->SetName(arr->GetName()); + outArray->SetNumberOfComponents(arr->GetNumberOfComponents()); + outArray->SetNumberOfTuples(0); + outArrays.push_back(outArray); + inArrays.push_back(arr); + continue; + } + vtkFloatArray* arr2(vtkFloatArray::SafeDownCast(arr)); + if (arr2) + { + vtkSmartPointer outArray(vtkFloatArray::New()); + outArray->SetName(arr->GetName()); + outArray->SetNumberOfComponents(arr->GetNumberOfComponents()); + outArray->SetNumberOfTuples(0); + outArrays.push_back(outArray); + inArrays.push_back(arr); + continue; + } + } + } + std::size_t nbArrays(outArrays.size()); + vtkDataArray* cooInBase(GetCoords(ds)); + std::unique_ptr cooInDS; + vtkFloatArray* cooIn(vtkFloatArray::SafeDownCast(cooInBase)); + if (cooIn) + { + std::unique_ptr > tmp(new CooAssignT(cooIn)); + cooInDS = std::move(tmp); + } + vtkDoubleArray* cooIn2(vtkDoubleArray::SafeDownCast(cooInBase)); + if (cooIn2) + { + std::unique_ptr > tmp(new CooAssignT(cooIn2)); + cooInDS = std::move(tmp); + } + const float* cooInPtr(cooIn->GetPointer(0)); + // TODO : improve + vtkSmartPointer outDS(vtkPolyData::New()); + vtkSmartPointer cooOut(vtkDoubleArray::New()); + cooOut->SetNumberOfComponents(3); + cooOut->SetNumberOfTuples(0); + { + vtkNew pts; + pts->SetData(cooOut); + outDS->SetPoints(pts); + } + vtkNew verts; + vtkNew conn; + conn->SetNumberOfComponents(1); + vtkIdType nbOfPts(0); + vtkIdType connToAdd[2]; + connToAdd[0] = 1; + std::vector connv; + std::unique_ptr pw(buildPathWalker(walkType)); + for (std::size_t i = 0; i < maxNbOfPts; i++) + { + vtkIdType a, b; + double aw, bw; + double realTime(pw->getTimeFrom(i, absTime2)); + if (pw->isInside(i, realTime, a, b, aw, bw)) + { + // std::cout << i << " - " << a << " - " << b << " * " << aw << " * " << bw << std::endl; + cooInDS->apply(a, aw, b, bw, cooOut); + connToAdd[1] = nbOfPts++; + connv.insert(connv.end(), connToAdd, connToAdd + 2); + for (std::size_t j = 0; j < nbArrays; j++) + { + vtkDataArray* arr(inArrays[j]); + vtkDoubleArray* arr1(vtkDoubleArray::SafeDownCast(arr)); + if (arr1) + { + dealWith(arr1, outArrays[j], a, aw, b, bw); + continue; + } + vtkFloatArray* arr2(vtkFloatArray::SafeDownCast(arr)); + if (arr2) + { + dealWith(arr2, outArrays[j], a, aw, b, bw); + continue; + } + } + } + } + conn->SetNumberOfTuples(connv.size()); + std::copy(connv.begin(), connv.end(), conn->GetPointer(0)); + verts->SetCells(nbOfPts, conn); + outDS->SetVerts(verts); + { + vtkDataSetAttributes* dsa(outDS->GetPointData()); + for (auto arr : outArrays) + { + dsa->AddArray(arr); + } + } + return outDS; +} + +//////////////////// + +vtkSphereAlongLines::vtkSphereAlongLines() + : Internal(new vtkInternals) + , AnimationTime(0.) + , WalkType(2) +{ +} + +vtkSphereAlongLines::~vtkSphereAlongLines() +{ + delete this->Internal; +} + +int vtkSphereAlongLines::RequestInformation( + vtkInformation* request, vtkInformationVector** inputVector, vtkInformationVector* outputVector) +{ + // std::cerr << "########################################## + // vtkSphereAlongLines::RequestInformation ##########################################" << + // std::endl; + try + { + vtkPolyData* ds(nullptr); + ExtractInfo(inputVector[0], ds); + } + catch (MZCException& e) + { + vtkErrorMacro(<< "Exception has been thrown in vtkSphereAlongLines::RequestInformation : " + << e.what()); + return 0; + } + return 1; +} + +int vtkSphereAlongLines::RequestData( + vtkInformation* request, vtkInformationVector** inputVector, vtkInformationVector* outputVector) +{ + // std::cerr << "########################################## vtkSphereAlongLines::RequestData + // ##########################################" << std::endl; + try + { + vtkPolyData* ds(nullptr); + ExtractInfo(inputVector[0], ds); + this->Internal->initCacheIfNeeded(ds); + vtkSmartPointer pts( + this->Internal->dataSetAtNormalizedTime(ds, this->AnimationTime, this->WalkType)); + vtkInformation* outInfo(outputVector->GetInformationObject(0)); + vtkPolyData* output(vtkPolyData::SafeDownCast(outInfo->Get(vtkDataObject::DATA_OBJECT()))); + output->ShallowCopy(pts); + } + catch (MZCException& e) + { + vtkErrorMacro(<< "Exception has been thrown in vtkSphereAlongLines::Data : " << e.what()); + return 0; + } + return 1; +} + +void vtkSphereAlongLines::PrintSelf(ostream& os, vtkIndent indent) +{ + this->Superclass::PrintSelf(os, indent); +} diff --git a/src/SphereAlongLines/plugin/SphereAlongLinesModule/vtkSphereAlongLines.h b/src/SphereAlongLines/plugin/SphereAlongLinesModule/vtkSphereAlongLines.h new file mode 100644 index 0000000..28e45a7 --- /dev/null +++ b/src/SphereAlongLines/plugin/SphereAlongLinesModule/vtkSphereAlongLines.h @@ -0,0 +1,56 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +// Author : Anthony Geay (EDF R&D) + +#ifndef vtkSphereAlongLines_h__ +#define vtkSphereAlongLines_h__ + +#include + +class VTK_EXPORT vtkSphereAlongLines : public vtkPolyDataAlgorithm +{ +public: + static vtkSphereAlongLines* New(); + vtkTypeMacro(vtkSphereAlongLines, vtkPolyDataAlgorithm); + void PrintSelf(ostream& os, vtkIndent indent) override; + + vtkGetMacro(AnimationTime, double); + vtkSetClampMacro(AnimationTime, double, 0., 1.); + + vtkGetMacro(WalkType, int); + vtkSetMacro(WalkType, int); + +protected: + vtkSphereAlongLines(); + ~vtkSphereAlongLines() override; + + int RequestInformation(vtkInformation*, vtkInformationVector**, vtkInformationVector*) override; + int RequestData(vtkInformation*, vtkInformationVector**, vtkInformationVector*) override; + + class vtkInternals; + vtkInternals* Internal; + double AnimationTime; + int WalkType; + +private: + vtkSphereAlongLines(const vtkSphereAlongLines&) = delete; + void operator=(const vtkSphereAlongLines&) = delete; +}; + +#endif diff --git a/src/SphereAlongLines/plugin/filters.xml b/src/SphereAlongLines/plugin/filters.xml new file mode 100644 index 0000000..86c7087 --- /dev/null +++ b/src/SphereAlongLines/plugin/filters.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + This property specifies the input to the Sphere Along Lines filter. + + + + + + The value of this property allows to animate spheres along input pathes. This value is expected to be inside [0,1] range. + + + + + + + + + This property determines in which direction(s) a + streamline is generated. + + + + + + + + diff --git a/src/SphereAlongLines/plugin/paraview.plugin b/src/SphereAlongLines/plugin/paraview.plugin new file mode 100644 index 0000000..e04b8c5 --- /dev/null +++ b/src/SphereAlongLines/plugin/paraview.plugin @@ -0,0 +1,27 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +NAME + SphereAlongLinesPlugin +DESCRIPTION + This plugin provides the SphereAlongLines filter. +REQUIRES_MODULES + VTK::CommonCore + VTK::IOCore + VTK::FiltersCore diff --git a/src/TemporalOnPoint/CMakeLists.txt b/src/TemporalOnPoint/CMakeLists.txt new file mode 100644 index 0000000..c54bec3 --- /dev/null +++ b/src/TemporalOnPoint/CMakeLists.txt @@ -0,0 +1,49 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +cmake_minimum_required(VERSION 3.8) +project(TemporalOnPointPlugin) +find_package(ParaView REQUIRED) + +include(GNUInstallDirs) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}") +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}") +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}") + +set("_paraview_plugin_default_${CMAKE_PROJECT_NAME}" ON) +paraview_plugin_scan( + ENABLE_BY_DEFAULT YES + PLUGIN_FILES "${CMAKE_CURRENT_SOURCE_DIR}/plugin/paraview.plugin" + PROVIDES_PLUGINS plugins + REQUIRES_MODULES required_modules) + +foreach(module IN LISTS required_modules) + if(NOT TARGET "${module}") + message("Missing required module: ${module}") + return() + endif() +endforeach() + +set(BUILD_SHARED_LIBS ON) +paraview_plugin_build( + RUNTIME_DESTINATION "${CMAKE_INSTALL_BINDIR}" + LIBRARY_DESTINATION "${CMAKE_INSTALL_LIBDIR}" + LIBRARY_SUBDIRECTORY "${PARAVIEW_PLUGIN_SUBDIR}" + PLUGINS ${plugins} + AUTOLOAD ${plugins}) diff --git a/src/TemporalOnPoint/TestCase.py b/src/TemporalOnPoint/TestCase.py new file mode 100644 index 0000000..07a9dcf --- /dev/null +++ b/src/TemporalOnPoint/TestCase.py @@ -0,0 +1,42 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +from MEDLoader import * + +fname="hydrau_test3.med" +meshName="mesh" +arr=DataArrayDouble([0,1,2,3,4,5]) +m=MEDCouplingCMesh() +m.setCoords(arr,arr) +m=m.buildUnstructured() +m.setName(meshName) +m.simplexize(0) +WriteMesh(fname,m,True) +# +f=MEDCouplingFieldDouble(ON_NODES) +f.setMesh(m) +f.setName("Field") +arr=m.getCoords().magnitude() +f.setArray(arr) +for i in range(10): + arr+=0.1 + f.setTime(float(i)+0.2,i,0) + WriteFieldUsingAlreadyWrittenMesh(fname,f) + pass + diff --git a/src/TemporalOnPoint/plugin/CMakeLists.txt b/src/TemporalOnPoint/plugin/CMakeLists.txt new file mode 100644 index 0000000..f0910b8 --- /dev/null +++ b/src/TemporalOnPoint/plugin/CMakeLists.txt @@ -0,0 +1,33 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +set(BUILD_SHARED_LIBS TRUE) + +paraview_add_plugin(TemporalOnPointPlugin + VERSION "1.0" + MODULES TemporalOnPointModule + MODULE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/TemporalOnPointModule/vtk.module" + SERVER_MANAGER_XML filters.xml +) + +install(TARGETS TemporalOnPointPlugin + RUNTIME DESTINATION lib/paraview + LIBRARY DESTINATION lib/paraview + ARCHIVE DESTINATION lib/paraview +) diff --git a/src/TemporalOnPoint/plugin/TemporalOnPointModule/CMakeLists.txt b/src/TemporalOnPoint/plugin/TemporalOnPointModule/CMakeLists.txt new file mode 100644 index 0000000..3d4dc40 --- /dev/null +++ b/src/TemporalOnPoint/plugin/TemporalOnPointModule/CMakeLists.txt @@ -0,0 +1,27 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +set(classes + vtkTemporalOnPoint +) + +vtk_module_add_module(TemporalOnPointModule + FORCE_STATIC + CLASSES ${classes} +) diff --git a/src/TemporalOnPoint/plugin/TemporalOnPointModule/vtk.module b/src/TemporalOnPoint/plugin/TemporalOnPointModule/vtk.module new file mode 100644 index 0000000..a0c1a0c --- /dev/null +++ b/src/TemporalOnPoint/plugin/TemporalOnPointModule/vtk.module @@ -0,0 +1,31 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +NAME + TemporalOnPointModule +DEPENDS + VTK::CommonCore + VTK::CommonDataModel + VTK::CommonExecutionModel + VTK::FiltersCore + VTK::FiltersModeling +PRIVATE_DEPENDS + VTK::CommonMisc + VTK::CommonSystem + VTK::FiltersGeneral diff --git a/src/TemporalOnPoint/plugin/TemporalOnPointModule/vtkTemporalOnPoint.cxx b/src/TemporalOnPoint/plugin/TemporalOnPointModule/vtkTemporalOnPoint.cxx new file mode 100644 index 0000000..ee1a9b8 --- /dev/null +++ b/src/TemporalOnPoint/plugin/TemporalOnPointModule/vtkTemporalOnPoint.cxx @@ -0,0 +1,586 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +// Author : Anthony Geay (EDF R&D) + +#include "vtkTemporalOnPoint.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +vtkStandardNewMacro(vtkTemporalOnPoint); + +/////////////////// + +template +class AutoPtr +{ +public: + AutoPtr(T* ptr = nullptr) + : _ptr(ptr) + { + } + ~AutoPtr() { destroyPtr(); } + AutoPtr& operator=(T* ptr) + { + if (_ptr != ptr) + { + destroyPtr(); + _ptr = ptr; + } + return *this; + } + T* operator->() { return _ptr; } + const T* operator->() const { return _ptr; } + T& operator*() { return *_ptr; } + const T& operator*() const { return *_ptr; } + operator T*() { return _ptr; } + operator const T*() const { return _ptr; } + +private: + void destroyPtr() { delete[] _ptr; } + +private: + T* _ptr; +}; + +class MZCException : public std::exception +{ +public: + MZCException(const std::string& s) + : _reason(s) + { + } + virtual const char* what() const throw() { return _reason.c_str(); } + virtual ~MZCException() throw() {} + +private: + std::string _reason; +}; + +class vtkTemporalOnPoint::vtkInternal +{ +public: + vtkInternal() + : _isInit(true) + { + _Z = std::numeric_limits::max(); + } + void operate(double timeStep, vtkUnstructuredGrid* usgIn, vtkPolyData* source); + void pushData(double timeStep, vtkPolyData* data); + void fillTable(vtkTable* table) const; + void scanCoordsOfDS(vtkUnstructuredGrid* usg); + static std::size_t CheckPts(vtkPointSet* usg, vtkDataArray*& arr); + +private: + void pushDataInit(double timeStep, vtkDataSetAttributes* dsa); + void pushDataStd(double timeStep, vtkDataSetAttributes* dsa); + +private: + double _Z; + bool _isInit; + std::vector _columnNames; + std::vector _time; + // First level of _data is for curves series. + // Second level of _data is for curve. Foreach i sizeof(_data[i]) must be equal to + // sizeof(_columName) Third level of _data is for time. Foreach i,j sizeof(_data[i][j]) must be + // equal to sizeof(_time) + std::vector > > _data; +}; + +void ExtractInfo(vtkInformationVector* inputVector, vtkUnstructuredGrid*& usgIn) +{ + vtkInformation* inputInfo(inputVector->GetInformationObject(0)); + vtkDataSet* input(0); + vtkDataSet* input0(vtkDataSet::SafeDownCast(inputInfo->Get(vtkDataObject::DATA_OBJECT()))); + vtkMultiBlockDataSet* input1( + vtkMultiBlockDataSet::SafeDownCast(inputInfo->Get(vtkDataObject::DATA_OBJECT()))); + if (input0) + input = input0; + else + { + if (!input1) + throw MZCException( + "Input dataSet must be a DataSet or single elt multi block dataset expected !"); + if (input1->GetNumberOfBlocks() != 1) + throw MZCException("Input dataSet is a multiblock dataset with not exactly one block ! Use " + "MergeBlocks or ExtractBlocks filter before calling this filter !"); + vtkDataObject* input2(input1->GetBlock(0)); + if (!input2) + throw MZCException("Input dataSet is a multiblock dataset with exactly one block but this " + "single element is NULL !"); + vtkDataSet* input2c(vtkDataSet::SafeDownCast(input2)); + if (!input2c) + throw MZCException( + "Input dataSet is a multiblock dataset with exactly one block but this single element is " + "not a dataset ! Use MergeBlocks or ExtractBlocks filter before calling this filter !"); + input = input2c; + } + if (!input) + throw MZCException("Input data set is NULL !"); + usgIn = vtkUnstructuredGrid::SafeDownCast(input); + if (!usgIn) + throw MZCException("Input data set is not an unstructured mesh ! This filter works only on " + "unstructured meshes !"); +} + +//////////////////// + +vtkTemporalOnPoint::vtkTemporalOnPoint() + : NumberOfTimeSteps(0) + , IsExecuting(false) + , CurrentTimeIndex(0) + , Internal(nullptr) +{ + this->SetNumberOfInputPorts(2); + this->SetNumberOfOutputPorts(1); +} + +vtkTemporalOnPoint::~vtkTemporalOnPoint() +{ + delete this->Internal; + this->Internal = nullptr; +} + +int vtkTemporalOnPoint::RequestInformation( + vtkInformation* request, vtkInformationVector** inputVector, vtkInformationVector* outputVector) +{ + // std::cerr << "########################################## vtkTemporalOnPoint::RequestInformation + // ##########################################" << std::endl; + try + { + vtkUnstructuredGrid* usgIn(0); + ExtractInfo(inputVector[0], usgIn); + vtkInformation* inInfo(inputVector[0]->GetInformationObject(0)); + if (inInfo->Has(vtkStreamingDemandDrivenPipeline::TIME_STEPS())) + { + this->NumberOfTimeSteps = inInfo->Length(vtkStreamingDemandDrivenPipeline::TIME_STEPS()); + } + else + { + this->NumberOfTimeSteps = 0; + } + // The output of this filter does not contain a specific time, rather + // it contains a collection of time steps. Also, this filter does not + // respond to time requests. Therefore, we remove all time information + // from the output. + vtkInformation* outInfo(outputVector->GetInformationObject(0)); + if (outInfo->Has(vtkStreamingDemandDrivenPipeline::TIME_STEPS())) + { + outInfo->Remove(vtkStreamingDemandDrivenPipeline::TIME_STEPS()); + } + if (outInfo->Has(vtkStreamingDemandDrivenPipeline::TIME_RANGE())) + { + outInfo->Remove(vtkStreamingDemandDrivenPipeline::TIME_RANGE()); + } + return 1; + } + catch (MZCException& e) + { + vtkErrorMacro(<< "Exception has been thrown in vtkTemporalOnPoint::RequestInformation : " + << e.what()); + return 0; + } + return 1; +} + +int vtkTemporalOnPoint::RequestUpdateExtent(vtkInformation*, vtkInformationVector** inputVector, + vtkInformationVector* vtkNotUsed(outputVector)) +{ + // vtkInformation* outInfo = outputVector->GetInformationObject(0); + vtkInformation* inInfo1 = inputVector[0]->GetInformationObject(0); + + // get the requested update extent + double* inTimes = inInfo1->Get(vtkStreamingDemandDrivenPipeline::TIME_STEPS()); + if (inTimes) + { + double timeReq = inTimes[this->CurrentTimeIndex]; + inInfo1->Set(vtkStreamingDemandDrivenPipeline::UPDATE_TIME_STEP(), timeReq); + } + + return 1; +} + +std::string buildNameOfEntryFrom(const std::string& name, std::size_t id, std::size_t nbOfElt) +{ + if (nbOfElt == 0) + throw MZCException("buildNameOfEntryFrom : nbElt == 0 !"); + if (nbOfElt == 1) + return name; + std::ostringstream oss; + oss << name << "_" << id; + return oss.str(); +} + +void buildTableFrom(vtkTable* table, + const std::vector > >& valuesByColumn, + const std::vector zeTimes, const std::vector& columnNames) +{ + { + vtkNew timeArr; + timeArr->SetName("Time"); + timeArr->SetNumberOfTuples(zeTimes.size()); + double* pt(timeArr->GetPointer(0)); + std::copy(zeTimes.begin(), zeTimes.end(), pt); + table->AddColumn(timeArr); + } + std::size_t nbOfSeries(valuesByColumn.size()), nbCols(columnNames.size()); + for (std::size_t i = 0; i < nbOfSeries; i++) + { + for (std::size_t j = 0; j < nbCols; j++) + { + vtkNew arr; + std::string name(buildNameOfEntryFrom(columnNames[j], i, nbOfSeries)); + arr->SetName(name.c_str()); + arr->SetNumberOfTuples(zeTimes.size()); + double* pt(arr->GetPointer(0)); + std::copy(valuesByColumn[i][j].begin(), valuesByColumn[i][j].end(), pt); + table->AddColumn(arr); + } + } +} + +void vtkTemporalOnPoint::vtkInternal::operate( + double timeStep, vtkUnstructuredGrid* usgIn, vtkPolyData* source) +{ + vtkNew sourceCpy; + sourceCpy->DeepCopy(source); + vtkDataArray* arr(0); + std::size_t nbPts(CheckPts(sourceCpy, arr)); + if (_Z != std::numeric_limits::max()) + { + vtkDoubleArray* arr1(vtkDoubleArray::SafeDownCast(arr)); + vtkFloatArray* arr2(vtkFloatArray::SafeDownCast(arr)); + if (arr1) + { + double* pt(arr1->GetPointer(0)); + for (std::size_t i = 0; i < nbPts; i++) + pt[3 * i + 2] = _Z; + } + else + { + float* pt(arr2->GetPointer(0)); + for (std::size_t i = 0; i < nbPts; i++) + pt[3 * i + 2] = _Z; + } + } + // + vtkNew probeFilter; + probeFilter->SetInputData(sourceCpy); + probeFilter->SetSourceData(usgIn); + probeFilter->Update(); + vtkDataObject* res(probeFilter->GetOutput()); + vtkPolyData* res2(vtkPolyData::SafeDownCast(res)); + if (!res2) + { + std::ostringstream oss; + oss << "Internal error ! unexpected returned of resample filter !"; + throw MZCException(oss.str()); + } + pushData(timeStep, res2); +} + +void vtkTemporalOnPoint::vtkInternal::pushData(double timeStep, vtkPolyData* ds) +{ + if (!ds) + throw MZCException("pushData : no data !"); + vtkDataSetAttributes* dsa(ds->GetPointData()); + if (!dsa) + throw MZCException("pushData : no point data !"); + _time.push_back(timeStep); + if (_isInit) + pushDataInit(timeStep, dsa); + else + pushDataStd(timeStep, dsa); + _isInit = false; +} + +void vtkTemporalOnPoint::vtkInternal::pushDataInit(double timeStep, vtkDataSetAttributes* dsa) +{ + std::size_t nbOfItems(std::numeric_limits::max()); + int nba(dsa->GetNumberOfArrays()); + for (int i = 0; i < nba; i++) + { + vtkDataArray* arr(dsa->GetArray(i)); + if (!arr) + continue; + if (arr->GetNumberOfComponents() != 1) + continue; + std::size_t tmp(arr->GetNumberOfTuples()); + if (tmp == 0) + continue; + if (nbOfItems == std::numeric_limits::max()) + { + nbOfItems = tmp; + _data.resize(nbOfItems); + } + if (tmp != nbOfItems) + continue; + const char* name(arr->GetName()); + if (!name) + continue; + vtkDoubleArray* arr1(vtkDoubleArray::SafeDownCast(arr)); + vtkFloatArray* arr2(vtkFloatArray::SafeDownCast(arr)); + if (!arr1 && !arr2) + continue; + _columnNames.push_back(name); + if (arr1) + { + const double* pt(arr1->GetPointer(0)); + for (std::size_t j = 0; j < nbOfItems; j++) + { + _data[j].resize(_columnNames.size()); + _data[j][_columnNames.size() - 1].push_back(pt[j]); + } + continue; + } + if (arr2) + { + const float* pt(arr2->GetPointer(0)); + for (std::size_t j = 0; j < nbOfItems; j++) + { + _data[j].resize(_columnNames.size()); + _data[j][_columnNames.size() - 1].push_back(pt[j]); + } + continue; + } + } +} + +void vtkTemporalOnPoint::vtkInternal::pushDataStd(double timeStep, vtkDataSetAttributes* dsa) +{ + std::set cnsRef(_columnNames.begin(), _columnNames.end()), cns; + std::size_t nbOfItems(_data.size()); + int nba(dsa->GetNumberOfArrays()); + for (int i = 0; i < nba; i++) + { + vtkDataArray* arr(dsa->GetArray(i)); + if (!arr) + continue; + if (arr->GetNumberOfComponents() != 1) + continue; + if (arr->GetNumberOfTuples() != nbOfItems) + continue; + const char* name(arr->GetName()); + if (!name) + continue; + vtkDoubleArray* arr1(vtkDoubleArray::SafeDownCast(arr)); + vtkFloatArray* arr2(vtkFloatArray::SafeDownCast(arr)); + if (!arr1 && !arr2) + continue; + std::string nameCpp(name); + std::vector::iterator it( + std::find(_columnNames.begin(), _columnNames.end(), nameCpp)); + if (it == _columnNames.end()) + continue; + std::size_t columnId(std::distance(_columnNames.begin(), it)); + cns.insert(nameCpp); + if (arr1) + { + const double* pt(arr1->GetPointer(0)); + for (std::size_t j = 0; j < nbOfItems; j++) + _data[j][columnId].push_back(pt[j]); + continue; + } + if (arr2) + { + const float* pt(arr2->GetPointer(0)); + for (std::size_t j = 0; j < nbOfItems; j++) + _data[j][columnId].push_back(pt[j]); + continue; + } + } + if (cnsRef != cns) + throw MZCException("Some float arrays are not present along time !"); +} + +void vtkTemporalOnPoint::vtkInternal::fillTable(vtkTable* table) const +{ + buildTableFrom(table, _data, _time, _columnNames); +} + +std::size_t vtkTemporalOnPoint::vtkInternal::CheckPts(vtkPointSet* usg, vtkDataArray*& arr) +{ + if (!usg) + throw MZCException("CheckPts : expect an unstucturedgrid !"); + vtkPoints* pts(usg->GetPoints()); + if (!pts) + throw MZCException("CheckPts : no points in grid !"); + arr = pts->GetData(); + if (!arr) + throw MZCException("CheckPts : no data in points in grid !"); + if (arr->GetNumberOfComponents() != 3) + throw MZCException("CheckPts : 3D expected !"); + std::size_t nbPts(arr->GetNumberOfTuples()); + if (nbPts < 1) + throw MZCException("CheckPts : no input point !"); + vtkDoubleArray* arr1(vtkDoubleArray::SafeDownCast(arr)); + vtkFloatArray* arr2(vtkFloatArray::SafeDownCast(arr)); + if (!arr1 && !arr2) + throw MZCException("scanCoordsOfDS : for coords expected FLOAT32 or FLOAT64 !"); + return nbPts; +} + +void vtkTemporalOnPoint::vtkInternal::scanCoordsOfDS(vtkUnstructuredGrid* usg) +{ + vtkDataArray* arr(0); + std::size_t nbPts(CheckPts(usg, arr)); + vtkDoubleArray* arr1(vtkDoubleArray::SafeDownCast(arr)); + vtkFloatArray* arr2(vtkFloatArray::SafeDownCast(arr)); + if (arr1) + { + const double* pt(arr1->GetPointer(0)); + double ref(pt[2]); + for (std::size_t i = 1; i < nbPts; i++) + if (pt[3 * i + 2] != ref) + { + _Z = std::numeric_limits::max(); + return; + } + _Z = ref; + } + else + { + const float* pt(arr2->GetPointer(0)); + float ref(pt[2]); + for (std::size_t i = 1; i < nbPts; i++) + if (pt[3 * i + 2] != ref) + { + _Z = std::numeric_limits::max(); + return; + } + _Z = ref; + } + // std::cerr << "Is 2D ? " << _Z << std::endl; +} + +int vtkTemporalOnPoint::RequestData( + vtkInformation* request, vtkInformationVector** inputVector, vtkInformationVector* outputVector) +{ + // std::cerr << "########################################## vtkTemporalOnPoint::RequestData + // ##########################################" << std::endl; + try + { + // + if (this->NumberOfTimeSteps == 0) + { + vtkErrorMacro("No time steps in input data!"); + return 0; + } + vtkInformation* outInfo(outputVector->GetInformationObject(0)); + vtkUnstructuredGrid* usgIn(0); + ExtractInfo(inputVector[0], usgIn); + // is this the first request + if (!this->IsExecuting) + { + request->Set(vtkStreamingDemandDrivenPipeline::CONTINUE_EXECUTING(), 1); + this->IsExecuting = true; + delete this->Internal; + this->Internal = new vtkInternal; + this->Internal->scanCoordsOfDS(usgIn); + } + // + // do something + { + vtkInformation* sourceInfo(inputVector[1]->GetInformationObject(0)); + vtkDataObject* source(sourceInfo->Get(vtkDataObject::DATA_OBJECT())); + vtkPolyData* source2(vtkPolyData::SafeDownCast(source)); + if (!source2) + throw MZCException("vtkPolyData expected as source !"); + double timeStep; + { + vtkInformation* inInfo(inputVector[0]->GetInformationObject(0)); + vtkDataObject* input(vtkDataObject::GetData(inInfo)); + timeStep = input->GetInformation()->Get(vtkDataObject::DATA_TIME_STEP()); + } + this->Internal->operate(timeStep, usgIn, source2); + } + // + this->CurrentTimeIndex++; + if (this->CurrentTimeIndex == this->NumberOfTimeSteps) + { + request->Remove(vtkStreamingDemandDrivenPipeline::CONTINUE_EXECUTING()); + this->CurrentTimeIndex = 0; + this->IsExecuting = false; + vtkInformation* outInfo(outputVector->GetInformationObject(0)); + vtkTable* output(vtkTable::SafeDownCast(outInfo->Get(vtkDataObject::DATA_OBJECT()))); + vtkNew table; + this->Internal->fillTable(table); + output->ShallowCopy(table); + } + } + catch (MZCException& e) + { + if (this->IsExecuting) + { + request->Remove(vtkStreamingDemandDrivenPipeline::CONTINUE_EXECUTING()); + this->CurrentTimeIndex = 0; + this->IsExecuting = false; + } + vtkErrorMacro(<< "Exception has been thrown in vtkTemporalOnPoint::RequestData : " << e.what()); + return 0; + } + return 1; +} + +void vtkTemporalOnPoint::PrintSelf(ostream& os, vtkIndent indent) +{ + this->Superclass::PrintSelf(os, indent); +} + +void vtkTemporalOnPoint::SetSourceData(vtkDataObject* input) +{ + this->SetInputData(1, input); +} + +void vtkTemporalOnPoint::SetSourceConnection(vtkAlgorithmOutput* algOutput) +{ + this->SetInputConnection(1, algOutput); +} + +int vtkTemporalOnPoint::FillOutputPortInformation(int vtkNotUsed(port), vtkInformation* info) +{ + info->Set(vtkDataObject::DATA_TYPE_NAME(), "vtkTable"); + return 1; +} diff --git a/src/TemporalOnPoint/plugin/TemporalOnPointModule/vtkTemporalOnPoint.h b/src/TemporalOnPoint/plugin/TemporalOnPointModule/vtkTemporalOnPoint.h new file mode 100644 index 0000000..2bc68af --- /dev/null +++ b/src/TemporalOnPoint/plugin/TemporalOnPointModule/vtkTemporalOnPoint.h @@ -0,0 +1,60 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +// Author : Anthony Geay (EDF R&D) + +#ifndef vtkTemporalOnPoint_h__ +#define vtkTemporalOnPoint_h__ + +#include + +class vtkMutableDirectedGraph; + +class VTK_EXPORT vtkTemporalOnPoint : public vtkDataObjectAlgorithm +{ +public: + static vtkTemporalOnPoint* New(); + vtkTypeMacro(vtkTemporalOnPoint, vtkDataObjectAlgorithm); + void PrintSelf(ostream& os, vtkIndent indent) override; + + void SetSourceData(vtkDataObject* input); + + void SetSourceConnection(vtkAlgorithmOutput* algOutput); + + int FillOutputPortInformation(int, vtkInformation*) override; + +protected: + vtkTemporalOnPoint(); + ~vtkTemporalOnPoint() override; + + int RequestInformation(vtkInformation*, vtkInformationVector**, vtkInformationVector*) override; + int RequestUpdateExtent(vtkInformation*, vtkInformationVector**, vtkInformationVector*) override; + int RequestData(vtkInformation*, vtkInformationVector**, vtkInformationVector*) override; + + int NumberOfTimeSteps; + bool IsExecuting; + int CurrentTimeIndex; + class vtkInternal; + vtkInternal* Internal; + +private: + vtkTemporalOnPoint(const vtkTemporalOnPoint&) = delete; + void operator=(const vtkTemporalOnPoint&) = delete; +}; + +#endif diff --git a/src/TemporalOnPoint/plugin/filters.xml b/src/TemporalOnPoint/plugin/filters.xml new file mode 100644 index 0000000..66b4c74 --- /dev/null +++ b/src/TemporalOnPoint/plugin/filters.xml @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + This property specifies the input to the Level Scalars filter. + + + + + + + + + + + This property specifies the dataset whose geometry will + be used in determining positions to probe. + + + + + + + + + + + + + diff --git a/src/TemporalOnPoint/plugin/paraview.plugin b/src/TemporalOnPoint/plugin/paraview.plugin new file mode 100644 index 0000000..a8b596b --- /dev/null +++ b/src/TemporalOnPoint/plugin/paraview.plugin @@ -0,0 +1,27 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +NAME + TemporalOnPointPlugin +DESCRIPTION + This plugin provides the TemporalOnPoint filter. +REQUIRES_MODULES + VTK::CommonCore + VTK::IOCore + VTK::FiltersCore diff --git a/src/Tools/Clean.py b/src/Tools/Clean.py new file mode 100644 index 0000000..85a92b3 --- /dev/null +++ b/src/Tools/Clean.py @@ -0,0 +1,29 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +from glob import glob +import os +from datetime import datetime + +fis=glob("*.txt")+glob("*.cmake") +fis2=[elt for elt in fis if datetime.fromtimestamp(os.stat(elt).st_mtime) > datetime(2018,1,12,7,33,0)] +for elt in fis2: + print(elt) + #os.remove(elt) + pass diff --git a/src/TorseurCIH/CMakeLists.txt b/src/TorseurCIH/CMakeLists.txt new file mode 100644 index 0000000..11c2b52 --- /dev/null +++ b/src/TorseurCIH/CMakeLists.txt @@ -0,0 +1,49 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +cmake_minimum_required(VERSION 3.8) +project(TorseurCIH) +find_package(ParaView REQUIRED) + +include(GNUInstallDirs) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}") +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}") +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}") + +set("_paraview_plugin_default_${CMAKE_PROJECT_NAME}" ON) +paraview_plugin_scan( + ENABLE_BY_DEFAULT YES + PLUGIN_FILES "${CMAKE_CURRENT_SOURCE_DIR}/plugin/paraview.plugin" + PROVIDES_PLUGINS plugins + REQUIRES_MODULES required_modules) + +foreach(module IN LISTS required_modules) + if(NOT TARGET "${module}") + message("Missing required module: ${module}") + return() + endif() +endforeach() + +set(BUILD_SHARED_LIBS ON) +paraview_plugin_build( + RUNTIME_DESTINATION "${CMAKE_INSTALL_BINDIR}" + LIBRARY_DESTINATION "${CMAKE_INSTALL_LIBDIR}" + LIBRARY_SUBDIRECTORY "${PARAVIEW_PLUGIN_SUBDIR}" + PLUGINS ${plugins} + AUTOLOAD ${plugins}) diff --git a/src/TorseurCIH/Test.py b/src/TorseurCIH/Test.py new file mode 100644 index 0000000..dfff8c4 --- /dev/null +++ b/src/TorseurCIH/Test.py @@ -0,0 +1,122 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +from medcoupling import * +import math + +def ReturnInertia(p,OM,area_field): + base_X = DataArrayDouble(len(OM),3) ; base_X[:]=p + dist_to_base_X = (OM-DataArrayDouble.Dot(OM,base_X)*base_X).magnitude() + inertia = (dist_to_base_X*dist_to_base_X*area_field).accumulate()[0] + return inertia + +def fffff(initialVect,normalFace,OM,area_field, posToIterate): + li=[] + for zePos in posToIterate: + p = initialVect.deepCopy() + MEDCouplingPointSet.Rotate3DAlg([0,0,0],normalFace.getValues(),zePos/float(180)*math.pi,p) + inertia = ReturnInertia(p,OM,area_field) + li.append((zePos,p.deepCopy(),inertia)) + return max(li,key=lambda x:x[2]) + +def fff(initialVect,normalFace,OM,area_field): + pos = fffff(initialVect,normalFace,OM,area_field,[i*float(10) for i in range(18)])[0] + for expo in range(5): + pos,p,v = fffff(initialVect,normalFace,OM,area_field,[pos+i*(10**-expo) for i in range(-9,10)]) + return pos,p,v + +fname = "slice.med" +mm=MEDFileMesh.New(fname) +m=mm[0] +f1ts = MEDFileField1TS(fname,"RESUME__SIEF_NOEU") +f = f1ts.field(mm) +m = f.getMesh() +area_field = m.getMeasureField(True) +area = area_field.accumulate()[0] # 1 +centerOfMassField = m.computeCellCenterOfMass() +centerOfMass = DataArrayDouble([elt/area for elt in (centerOfMassField*area_field.getArray()).accumulate()],1,3) # 2 +m.unPolyze() +tri = MEDCoupling1SGTUMesh(m) +assert(tri.getCellModelEnum()==NORM_TRI3) +# +fCell = f.nodeToCellDiscretization() +(fCell.getArray()[:,[0,1,2]]*area).accumulate() +ids = area_field.getArray().findIdsLowerThan(1e-7).buildComplement(m.getNumberOfCells()) +#fCell[ids] +eqn = m[ids].computePlaneEquationOf3DFaces()[:,:3] +eqn /= eqn.magnitude() +area_vector = eqn*area_field.getArray()[ids] +matrix = fCell[ids].getArray() +# +F_x = matrix[:,0]*eqn[:,0] + matrix[:,3]*eqn[:,1] + matrix[:,4]*eqn[:,2] +F_y = matrix[:,3]*eqn[:,0] + matrix[:,1]*eqn[:,1] + matrix[:,5]*eqn[:,2] +F_z = matrix[:,4]*eqn[:,0] + matrix[:,5]*eqn[:,1] + matrix[:,2]*eqn[:,2] +# +F = DataArrayDouble.Meld([F_x,F_y,F_z]) +# +ZeForce = DataArrayDouble(F.accumulate(),1,3) +normalFace = DataArrayDouble(eqn.accumulate(),1,3) +normalFace /= normalFace.magnitude()[0] +ForceNormale = DataArrayDouble.Dot(ZeForce,normalFace)[0]*normalFace # 3 +TangentForce = ZeForce-ForceNormale # 4 +# +bary = m[ids].computeCellCenterOfMass() +OM = bary-centerOfMass +momentum = DataArrayDouble(DataArrayDouble.CrossProduct(OM,F).accumulate(),1,3) # 5 +# Inertie +InertiaNormale = (DataArrayDouble.Dot(OM,OM)*area_field.getArray()[ids]).accumulate()[0] # 6_A +base = DataArrayDouble(DataArrayDouble.GiveBaseForPlane(normalFace),3,3) +angle, tangentPrincipal, inertiaPrincipal = fff(base[0],normalFace,OM,area_field.getArray()[ids]) +tangentOther = DataArrayDouble.CrossProduct(normalFace,tangentPrincipal) +inertiaOther = ReturnInertia(tangentOther,OM,area_field.getArray()[ids]) +""" +base_X = DataArrayDouble(len(ids),3) ; base_X[:]=base[0] +base_Y = DataArrayDouble(len(ids),3) ; base_Y[:]=base[1] +dist_to_base_X = (OM-DataArrayDouble.Dot(OM,base_X)*base_X).magnitude() +dist_to_base_Y = (OM-DataArrayDouble.Dot(OM,base_Y)*base_Y).magnitude() +inertia_mat_0 = (dist_to_base_Y*dist_to_base_Y*area_field.getArray()[ids]).accumulate()[0] +inertia_mat_1 = (dist_to_base_X*dist_to_base_X*area_field.getArray()[ids]).accumulate()[0] +inertia_mat_01 = -(dist_to_base_X*dist_to_base_Y*area_field.getArray()[ids]).accumulate()[0] +from numpy import linalg as LA +import numpy as np +mat = np.matrix([[inertia_mat_0, inertia_mat_01], [inertia_mat_01, inertia_mat_1]]) +v,w = LA.eig(mat) +pos_of_max = max([(i,elt) for (i,elt) in enumerate(v)],key=lambda x: x[1])[0] +u0 = DataArrayDouble(np.array(w[:,pos_of_max])) ; u0.rearrange(2) +v0 = DataArrayDouble(np.array(w[:,1-pos_of_max])) ; v0.rearrange(2) +# +I_new_base_0 = v[pos_of_max] # 6_B +new_base_0 = u0[0,0]*base[0]+u0[0,1]*base[1] # 6_B +#new_base_1 = v0[0,0]*base[0]+v0[0,1]*base[1] +new_base_1 = DataArrayDouble.CrossProduct(normalFace,new_base_0) +new_base_Y = DataArrayDouble(len(ids),3) ; new_base_Y[:]=new_base_1 +new_dist_to_base_Y = (OM-DataArrayDouble.Dot(OM,new_base_Y)*new_base_Y).magnitude() +I_new_base_1 = (new_dist_to_base_Y*new_dist_to_base_Y*area_field.getArray()[ids]).accumulate()[0] +""" +""" +new_base_X = DataArrayDouble(len(ids),3) ; new_base_X[:]=new_base_0 +new_dist_to_base_X = (OM-DataArrayDouble.Dot(OM,new_base_X)*new_base_X).magnitude() +I_new_base_0 = (new_dist_to_base_X*new_dist_to_base_X*area_field.getArray()[ids]).accumulate()[0] +tmp=m[ids] ; tmp.zipCoords() +f=MEDCouplingFieldDouble(ON_CELLS) +f.setMesh(tmp) +f.setArray(new_dist_to_base_X) +f.setName("dist") +f.writeVTK("distEig.vtu")""" +#mat*w[:,0] diff --git a/src/TorseurCIH/plugin/CMakeLists.txt b/src/TorseurCIH/plugin/CMakeLists.txt new file mode 100644 index 0000000..0dd36dc --- /dev/null +++ b/src/TorseurCIH/plugin/CMakeLists.txt @@ -0,0 +1,65 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +CMAKE_POLICY(SET CMP0071 NEW) +SET(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) +SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib") + +# Common CMake macros +# =================== +SET(TMP_CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH}) +unset(CMAKE_MODULE_PATH) +SET(CONFIGURATION_ROOT_DIR $ENV{CONFIGURATION_ROOT_DIR} CACHE PATH "Path to the Salome CMake configuration files") +IF(EXISTS ${CONFIGURATION_ROOT_DIR}) + LIST(APPEND CMAKE_MODULE_PATH "${CONFIGURATION_ROOT_DIR}/cmake") + INCLUDE(SalomeMacros) +ELSE() + MESSAGE(FATAL_ERROR "We absolutely need the Salome CMake configuration files, please define CONFIGURATION_ROOT_DIR !") +ENDIF() + +SET(MEDCOUPLING_ROOT_DIR $ENV{MEDCOUPLING_ROOT_DIR} CACHE PATH "Path to the MEDCoupling tool") +IF(EXISTS ${MEDCOUPLING_ROOT_DIR}) + LIST(APPEND CMAKE_MODULE_PATH "${MEDCOUPLING_ROOT_DIR}/cmake_files") +ENDIF() +LIST(APPEND CMAKE_MODULE_PATH "${CMAKE_ROOT}/Modules") +LIST(APPEND CMAKE_MODULE_PATH ${TMP_CMAKE_MODULE_PATH}) + +INCLUDE(SalomeSetupPlatform) +SET(BUILD_SHARED_LIBS TRUE) + +FIND_PACKAGE(SalomeHDF5 REQUIRED) +FIND_PACKAGE(SalomeMEDCoupling REQUIRED) + +SALOME_ACCUMULATE_ENVIRONMENT(PYTHONPATH NOCHECK ${CMAKE_INSTALL_PREFIX}/${SALOME_INSTALL_BINS} + ${CMAKE_INSTALL_PREFIX}/${SALOME_INSTALL_PYTHON}) +SALOME_ACCUMULATE_ENVIRONMENT(LD_LIBRARY_PATH NOCHECK ${CMAKE_INSTALL_PREFIX}/${SALOME_INSTALL_LIBS}) +SALOME_ACCUMULATE_ENVIRONMENT(PV_PLUGIN_PATH NOCHECK ${CMAKE_INSTALL_PREFIX}/lib/paraview) + +paraview_add_plugin(TorseurCIH + VERSION "1.0" + MODULES TorseurCIHModule + MODULE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/TorseurCIHModule/vtk.module" + SERVER_MANAGER_XML filters.xml +) + +install(TARGETS TorseurCIH + RUNTIME DESTINATION lib/paraview + LIBRARY DESTINATION lib/paraview + ARCHIVE DESTINATION lib/paraview +) diff --git a/src/TorseurCIH/plugin/TorseurCIHModule/CMakeLists.txt b/src/TorseurCIH/plugin/TorseurCIHModule/CMakeLists.txt new file mode 100644 index 0000000..3b62afe --- /dev/null +++ b/src/TorseurCIH/plugin/TorseurCIHModule/CMakeLists.txt @@ -0,0 +1,39 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +IF(WIN32) +ADD_COMPILE_DEFINITIONS(_USE_MATH_DEFINES) +ENDIF(WIN32) + +set(classes + vtkTorseurCIH +) + +vtk_module_add_module(TorseurCIHModule + FORCE_STATIC + CLASSES ${classes} +) + +target_include_directories(TorseurCIHModule PRIVATE ${MEDCOUPLING_INCLUDE_DIRS}) + +if(HDF5_IS_PARALLEL) + target_link_libraries(TorseurCIHModule PRIVATE ${MEDCoupling_paramedloader}) +else() + target_link_libraries(TorseurCIHModule PRIVATE ${MEDCoupling_medloader}) +endif() diff --git a/src/TorseurCIH/plugin/TorseurCIHModule/vtk.module b/src/TorseurCIH/plugin/TorseurCIHModule/vtk.module new file mode 100644 index 0000000..91072d1 --- /dev/null +++ b/src/TorseurCIH/plugin/TorseurCIHModule/vtk.module @@ -0,0 +1,33 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +NAME + TorseurCIHModule +DEPENDS + VTK::CommonCore + VTK::CommonDataModel + VTK::CommonExecutionModel + VTK::FiltersCore + VTK::FiltersGeneral + ParaView::RemotingCore + VTK::IOLegacy +PRIVATE_DEPENDS + ParaView::VTKExtensionsMisc + ParaView::VTKExtensionsFiltersRendering + diff --git a/src/TorseurCIH/plugin/TorseurCIHModule/vtkTorseurCIH.cxx b/src/TorseurCIH/plugin/TorseurCIHModule/vtkTorseurCIH.cxx new file mode 100644 index 0000000..9ac9f26 --- /dev/null +++ b/src/TorseurCIH/plugin/TorseurCIHModule/vtkTorseurCIH.cxx @@ -0,0 +1,886 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +// Author : Anthony Geay (EDF R&D) + +#include "vtkTorseurCIH.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "InterpKernelAutoPtr.hxx" +#include "InterpKernelGaussCoords.hxx" +#include "MEDCouplingFieldDouble.hxx" +#include "MEDCouplingMemArray.hxx" +#include "MEDCouplingUMesh.hxx" + +#include +#include +#include +#include + +using MEDCoupling::DataArray; +using MEDCoupling::DataArrayDouble; +using MEDCoupling::DataArrayInt; +using MEDCoupling::DataArrayInt64; +using MEDCoupling::DynamicCastSafe; +using MEDCoupling::MCAuto; +using MEDCoupling::MEDCouplingFieldDouble; +using MEDCoupling::MEDCouplingMesh; +using MEDCoupling::MEDCouplingUMesh; +using MEDCoupling::ON_GAUSS_PT; + +vtkStandardNewMacro(vtkTorseurCIH); +/////////////////// + +std::map ComputeMapOfType() +{ + std::map ret; + int nbOfTypesInMC(sizeof(MEDCOUPLING2VTKTYPETRADUCER) / sizeof(int)); + for (int i = 0; i < nbOfTypesInMC; i++) + { + int vtkId(MEDCOUPLING2VTKTYPETRADUCER[i]); + if (vtkId != -1) + ret[vtkId] = i; + } + return ret; +} + +std::map ComputeRevMapOfType() +{ + std::map ret; + int nbOfTypesInMC(sizeof(MEDCOUPLING2VTKTYPETRADUCER) / sizeof(int)); + for (int i = 0; i < nbOfTypesInMC; i++) + { + int vtkId(MEDCOUPLING2VTKTYPETRADUCER[i]); + if (vtkId != -1) + ret[i] = vtkId; + } + return ret; +} + +/////////////////// + +void ExtractInfo(vtkInformationVector* inputVector, vtkSmartPointer& usgIn) +{ + vtkInformation* inputInfo(inputVector->GetInformationObject(0)); + vtkDataSet* input = nullptr; + vtkDataSet* input0(vtkDataSet::SafeDownCast(inputInfo->Get(vtkDataObject::DATA_OBJECT()))); + vtkMultiBlockDataSet* input1( + vtkMultiBlockDataSet::SafeDownCast(inputInfo->Get(vtkDataObject::DATA_OBJECT()))); + if (input0) + input = input0; + else + { + if (!input1) + throw INTERP_KERNEL::Exception( + "Input dataSet must be a DataSet or single elt multi block dataset expected !"); + if (input1->GetNumberOfBlocks() != 1) + throw INTERP_KERNEL::Exception( + "Input dataSet is a multiblock dataset with not exactly one block ! Use MergeBlocks or " + "ExtractBlocks filter before calling this filter !"); + vtkDataObject* input2(input1->GetBlock(0)); + if (!input2) + throw INTERP_KERNEL::Exception("Input dataSet is a multiblock dataset with exactly one block " + "but this single element is NULL !"); + vtkDataSet* input2c(vtkDataSet::SafeDownCast(input2)); + if (!input2c) + throw INTERP_KERNEL::Exception( + "Input dataSet is a multiblock dataset with exactly one block but this single element is " + "not a dataset ! Use MergeBlocks or ExtractBlocks filter before calling this filter !"); + input = input2c; + } + if (!input) + throw INTERP_KERNEL::Exception("Input data set is NULL !"); + usgIn.TakeReference(vtkUnstructuredGrid::SafeDownCast(input)); + if (!usgIn.Get()) + { + if (!input1) + { + vtkNew mb; + vtkNew cd; + mb->AddInputData(input); + cd->SetInputConnection(mb->GetOutputPort()); + cd->SetMergePoints(0); + cd->Update(); + usgIn = cd->GetOutput(); + } + else + { + vtkNew filter; + filter->SetMergePoints(0); + filter->SetInputData(input1); + filter->Update(); + vtkUnstructuredGrid* res(filter->GetOutput()); + usgIn.TakeReference(res); + if (res) + res->Register(nullptr); + } + } + else + usgIn->Register(nullptr); +} + +DataArrayInt* ConvertVTKArrayToMCArrayInt(vtkDataArray* data) +{ + if (!data) + throw INTERP_KERNEL::Exception("ConvertVTKArrayToMCArrayInt : internal error !"); + int nbTuples(data->GetNumberOfTuples()), nbComp(data->GetNumberOfComponents()); + std::size_t nbElts(nbTuples * nbComp); + MCAuto ret(DataArrayInt::New()); + ret->alloc(nbTuples, nbComp); + for (int i = 0; i < nbComp; i++) + { + const char* comp(data->GetComponentName(i)); + if (comp) + ret->setInfoOnComponent(i, comp); + } + int* ptOut(ret->getPointer()); + vtkIntArray* d0(vtkIntArray::SafeDownCast(data)); + if (d0) + { + const int* pt(d0->GetPointer(0)); + std::copy(pt, pt + nbElts, ptOut); + return ret.retn(); + } + vtkLongArray* d1(vtkLongArray::SafeDownCast(data)); + if (d1) + { + const long* pt(d1->GetPointer(0)); + std::copy(pt, pt + nbElts, ptOut); + return ret.retn(); + } + vtkIdTypeArray* d2(vtkIdTypeArray::SafeDownCast(data)); + if (d2) + { + const vtkIdType* pt(d2->GetPointer(0)); + std::copy(pt, pt + nbElts, ptOut); + return ret.retn(); + } + std::ostringstream oss; + oss << "ConvertVTKArrayToMCArrayInt : unrecognized array \"" << typeid(*data).name() + << "\" type !"; + throw INTERP_KERNEL::Exception(oss.str()); +} + +DataArrayDouble* ConvertVTKArrayToMCArrayDouble(vtkDataArray* data) +{ + if (!data) + throw INTERP_KERNEL::Exception("ConvertVTKArrayToMCArrayDouble : internal error !"); + int nbTuples(data->GetNumberOfTuples()), nbComp(data->GetNumberOfComponents()); + std::size_t nbElts(nbTuples * nbComp); + MCAuto ret(DataArrayDouble::New()); + ret->alloc(nbTuples, nbComp); + for (int i = 0; i < nbComp; i++) + { + const char* comp(data->GetComponentName(i)); + if (comp) + ret->setInfoOnComponent(i, comp); + } + double* ptOut(ret->getPointer()); + vtkFloatArray* d0(vtkFloatArray::SafeDownCast(data)); + if (d0) + { + const float* pt(d0->GetPointer(0)); + for (std::size_t i = 0; i < nbElts; i++) + ptOut[i] = pt[i]; + return ret.retn(); + } + vtkDoubleArray* d1(vtkDoubleArray::SafeDownCast(data)); + if (d1) + { + const double* pt(d1->GetPointer(0)); + std::copy(pt, pt + nbElts, ptOut); + return ret.retn(); + } + std::ostringstream oss; + oss << "ConvertVTKArrayToMCArrayDouble : unrecognized array \"" << typeid(*data).name() + << "\" type !"; + throw INTERP_KERNEL::Exception(oss.str()); +} + +DataArray* ConvertVTKArrayToMCArray(vtkDataArray* data) +{ + if (!data) + throw INTERP_KERNEL::Exception("ConvertVTKArrayToMCArray : internal error !"); + vtkFloatArray* d0(vtkFloatArray::SafeDownCast(data)); + vtkDoubleArray* d1(vtkDoubleArray::SafeDownCast(data)); + if (d0 || d1) + return ConvertVTKArrayToMCArrayDouble(data); + vtkIntArray* d2(vtkIntArray::SafeDownCast(data)); + vtkLongArray* d3(vtkLongArray::SafeDownCast(data)); + if (d2 || d3) + return ConvertVTKArrayToMCArrayInt(data); + std::ostringstream oss; + oss << "ConvertVTKArrayToMCArray : unrecognized array \"" << typeid(*data).name() << "\" type !"; + throw INTERP_KERNEL::Exception(oss.str()); +} + +DataArrayDouble* BuildCoordsFrom(vtkPointSet* ds) +{ + if (!ds) + throw INTERP_KERNEL::Exception("BuildCoordsFrom : internal error !"); + vtkPoints* pts(ds->GetPoints()); + if (!pts) + throw INTERP_KERNEL::Exception("BuildCoordsFrom : internal error 2 !"); + vtkDataArray* data(pts->GetData()); + if (!data) + throw INTERP_KERNEL::Exception("BuildCoordsFrom : internal error 3 !"); + MCAuto coords(ConvertVTKArrayToMCArrayDouble(data)); + return coords.retn(); +} + +void ConvertFromUnstructuredGrid(vtkUnstructuredGrid* ds, + std::vector >& ms, std::vector >& ids) +{ + MCAuto coords(BuildCoordsFrom(ds)); + vtkIdType nbCells(ds->GetNumberOfCells()); + vtkUnsignedCharArray* ct(ds->GetCellTypesArray()); + if (!ct) + throw INTERP_KERNEL::Exception("ConvertFromUnstructuredGrid : internal error"); + const unsigned char* ctPtr(ct->GetPointer(0)); + std::map m(ComputeMapOfType()); + MCAuto lev(DataArrayIdType::New()); + lev->alloc(nbCells, 1); + mcIdType* levPtr(lev->getPointer()); + for (vtkIdType i = 0; i < nbCells; i++) + { + std::map::iterator it(m.find(ctPtr[i])); + if (it != m.end()) + { + const INTERP_KERNEL::CellModel& cm( + INTERP_KERNEL::CellModel::GetCellModel((INTERP_KERNEL::NormalizedCellType)(*it).second)); + levPtr[i] = cm.getDimension(); + } + else + { + std::ostringstream oss; + oss << "ConvertFromUnstructuredGrid : at pos #" << i + << " unrecognized VTK cell with type =" << ctPtr[i]; + throw INTERP_KERNEL::Exception(oss.str()); + } + } + MCAuto levs(lev->getDifferentValues()); + vtkIdTypeArray *faces(ds->GetFaces()), *faceLoc(ds->GetFaceLocations()); + for (const mcIdType* curLev = levs->begin(); curLev != levs->end(); curLev++) + { + MCAuto m0(MEDCouplingUMesh::New("", *curLev)); + m0->setCoords(coords); + m0->allocateCells(); + MCAuto cellIdsCurLev(lev->findIdsEqual(*curLev)); + for (const mcIdType* cellId = cellIdsCurLev->begin(); cellId != cellIdsCurLev->end(); cellId++) + { + std::map::iterator it(m.find(ctPtr[*cellId])); + vtkIdType sz; + vtkIdType const* pts; + ds->GetCellPoints(*cellId, sz, pts); + INTERP_KERNEL::NormalizedCellType ct((INTERP_KERNEL::NormalizedCellType)(*it).second); + if (ct != INTERP_KERNEL::NORM_POLYHED) + { + std::vector conn2(sz); + for (int kk = 0; kk < sz; kk++) + conn2[kk] = pts[kk]; + m0->insertNextCell(ct, sz, &conn2[0]); + } + else + { + if (!faces || !faceLoc) + throw INTERP_KERNEL::Exception( + "ConvertFromUnstructuredGrid : faces are expected when there are polyhedra !"); + const vtkIdType *facPtr(faces->GetPointer(0)), *facLocPtr(faceLoc->GetPointer(0)); + std::vector conn; + int off0(facLocPtr[*cellId]); + int nbOfFaces(facPtr[off0++]); + for (int k = 0; k < nbOfFaces; k++) + { + int nbOfNodesInFace(facPtr[off0++]); + std::copy(facPtr + off0, facPtr + off0 + nbOfNodesInFace, std::back_inserter(conn)); + off0 += nbOfNodesInFace; + if (k < nbOfFaces - 1) + conn.push_back(-1); + } + m0->insertNextCell(ct, conn.size(), conn.data()); + } + } + ms.push_back(m0); + ids.push_back(cellIdsCurLev); + } +} + +vtkSmartPointer ConvertUMeshFromMCToVTK(const MEDCouplingUMesh* mVor) +{ + std::map zeMapRev(ComputeRevMapOfType()); + int nbCells(mVor->getNumberOfCells()); + vtkSmartPointer ret(vtkSmartPointer::New()); + ret->Initialize(); + ret->Allocate(); + vtkSmartPointer points(vtkSmartPointer::New()); + { + const DataArrayDouble* vorCoords(mVor->getCoords()); + vtkSmartPointer da(vtkSmartPointer::New()); + da->SetNumberOfComponents(vorCoords->getNumberOfComponents()); + da->SetNumberOfTuples(vorCoords->getNumberOfTuples()); + std::copy(vorCoords->begin(), vorCoords->end(), da->GetPointer(0)); + points->SetData(da); + } + mVor->checkConsistencyLight(); + switch (mVor->getMeshDimension()) + { + case 3: + { + vtkIdType *cPtr(nullptr), *dPtr(nullptr); + unsigned char* aPtr(nullptr); + vtkSmartPointer cellTypes(vtkSmartPointer::New()); + { + cellTypes->SetNumberOfComponents(1); + cellTypes->SetNumberOfTuples(nbCells); + aPtr = cellTypes->GetPointer(0); + } + vtkSmartPointer cellLocations(vtkSmartPointer::New()); + { + cellLocations->SetNumberOfComponents(1); + cellLocations->SetNumberOfTuples(nbCells); + cPtr = cellLocations->GetPointer(0); + } + vtkSmartPointer cells(vtkSmartPointer::New()); + { + MCAuto tmp2(mVor->computeEffectiveNbOfNodesPerCell()); + cells->SetNumberOfComponents(1); + cells->SetNumberOfTuples(tmp2->accumulate((std::size_t)0) + nbCells); + dPtr = cells->GetPointer(0); + } + const mcIdType *connPtr(mVor->getNodalConnectivity()->begin()), + *connIPtr(mVor->getNodalConnectivityIndex()->begin()); + int k(0), kk(0); + std::vector ee, ff; + for (int i = 0; i < nbCells; i++, connIPtr++) + { + INTERP_KERNEL::NormalizedCellType ct( + static_cast(connPtr[connIPtr[0]])); + *aPtr++ = zeMapRev[connPtr[connIPtr[0]]]; + if (ct != INTERP_KERNEL::NORM_POLYHED) + { + int sz(connIPtr[1] - connIPtr[0] - 1); + *dPtr++ = sz; + dPtr = std::copy(connPtr + connIPtr[0] + 1, connPtr + connIPtr[1], dPtr); + *cPtr++ = k; + k += sz + 1; + ee.push_back(kk); + } + else + { + std::set s(connPtr + connIPtr[0] + 1, connPtr + connIPtr[1]); + s.erase(-1); + int nbFace(std::count(connPtr + connIPtr[0] + 1, connPtr + connIPtr[1], -1) + 1); + ff.push_back(nbFace); + const mcIdType* work(connPtr + connIPtr[0] + 1); + for (int j = 0; j < nbFace; j++) + { + const mcIdType* work2 = std::find(work, connPtr + connIPtr[1], -1); + ff.push_back(std::distance(work, work2)); + ff.insert(ff.end(), work, work2); + work = work2 + 1; + } + *dPtr++ = (int)s.size(); + dPtr = std::copy(s.begin(), s.end(), dPtr); + *cPtr++ = k; + k += (int)s.size() + 1; + ee.push_back(kk); + kk += connIPtr[1] - connIPtr[0] + 1; + } + } + // + vtkSmartPointer faceLocations(vtkSmartPointer::New()); + { + faceLocations->SetNumberOfComponents(1); + faceLocations->SetNumberOfTuples(ee.size()); + std::copy(ee.begin(), ee.end(), faceLocations->GetPointer(0)); + } + vtkSmartPointer faces(vtkSmartPointer::New()); + { + faces->SetNumberOfComponents(1); + faces->SetNumberOfTuples(ff.size()); + std::copy(ff.begin(), ff.end(), faces->GetPointer(0)); + } + vtkSmartPointer cells2(vtkSmartPointer::New()); + cells2->SetCells(nbCells, cells); + ret->SetCells(cellTypes, cellLocations, cells2, faceLocations, faces); + break; + } + case 2: + { + vtkSmartPointer cellTypes(vtkSmartPointer::New()); + { + cellTypes->SetNumberOfComponents(1); + cellTypes->SetNumberOfTuples(nbCells); + unsigned char* ptr(cellTypes->GetPointer(0)); + std::fill(ptr, ptr + nbCells, zeMapRev[(int)INTERP_KERNEL::NORM_POLYGON]); + } + vtkIdType *cPtr(nullptr), *dPtr(nullptr); + vtkSmartPointer cellLocations(vtkSmartPointer::New()); + { + cellLocations->SetNumberOfComponents(1); + cellLocations->SetNumberOfTuples(nbCells); + cPtr = cellLocations->GetPointer(0); + } + vtkSmartPointer cells(vtkSmartPointer::New()); + { + cells->SetNumberOfComponents(1); + cells->SetNumberOfTuples(mVor->getNodalConnectivity()->getNumberOfTuples()); + dPtr = cells->GetPointer(0); + } + const mcIdType *connPtr(mVor->getNodalConnectivity()->begin()), + *connIPtr(mVor->getNodalConnectivityIndex()->begin()); + mcIdType k(0); + for (mcIdType i = 0; i < nbCells; i++, connIPtr++) + { + *dPtr++ = connIPtr[1] - connIPtr[0] - 1; + dPtr = std::copy(connPtr + connIPtr[0] + 1, connPtr + connIPtr[1], dPtr); + *cPtr++ = k; + k += connIPtr[1] - connIPtr[0]; + } + vtkSmartPointer cells2(vtkSmartPointer::New()); + cells2->SetCells(nbCells, cells); + ret->SetCells(cellTypes, cellLocations, cells2); + break; + } + case 1: + { + vtkSmartPointer cellTypes(vtkSmartPointer::New()); + { + cellTypes->SetNumberOfComponents(1); + cellTypes->SetNumberOfTuples(nbCells); + unsigned char* ptr(cellTypes->GetPointer(0)); + std::fill(ptr, ptr + nbCells, zeMapRev[(int)INTERP_KERNEL::NORM_SEG2]); + } + vtkIdType *cPtr(nullptr), *dPtr(nullptr); + vtkSmartPointer cellLocations(vtkSmartPointer::New()); + { + cellLocations->SetNumberOfComponents(1); + cellLocations->SetNumberOfTuples(nbCells); + cPtr = cellLocations->GetPointer(0); + } + vtkSmartPointer cells(vtkSmartPointer::New()); + { + cells->SetNumberOfComponents(1); + cells->SetNumberOfTuples(mVor->getNodalConnectivity()->getNumberOfTuples()); + dPtr = cells->GetPointer(0); + } + const mcIdType *connPtr(mVor->getNodalConnectivity()->begin()), + *connIPtr(mVor->getNodalConnectivityIndex()->begin()); + for (int i = 0; i < nbCells; i++, connIPtr++) + { + *dPtr++ = 2; + dPtr = std::copy(connPtr + connIPtr[0] + 1, connPtr + connIPtr[1], dPtr); + *cPtr++ = 3 * i; + } + vtkSmartPointer cells2(vtkSmartPointer::New()); + cells2->SetCells(nbCells, cells); + ret->SetCells(cellTypes, cellLocations, cells2); + break; + } + default: + throw INTERP_KERNEL::Exception("Not implemented yet !"); + } + ret->SetPoints(points); + return ret; +} + +MCAuto ForceBuilder(const std::vector& TAB, const DataArrayDouble* matrix, const DataArrayDouble* eqn) +{ + MCAuto tmp0, tmp1, ret; + tmp0 = matrix->keepSelectedComponents({ TAB[0] }); + tmp1 = eqn->keepSelectedComponents({ 0 }); + MCAuto p0(DataArrayDouble::Multiply(tmp0, tmp1)); + tmp0 = matrix->keepSelectedComponents({ TAB[1] }); + tmp1 = eqn->keepSelectedComponents({ 1 }); + MCAuto p1(DataArrayDouble::Multiply(tmp0, tmp1)); + ret = DataArrayDouble::Add(p0, p1); + tmp0 = matrix->keepSelectedComponents({ TAB[2] }); + tmp1 = eqn->keepSelectedComponents({ 2 }); + MCAuto p2(DataArrayDouble::Multiply(tmp0, tmp1)); + ret = DataArrayDouble::Add(ret, p2); + return ret; +} + +double ReturnInertia( + const double pOut[3], const DataArrayDouble* OM, const DataArrayDouble* area_field_ids) +{ + MCAuto base_X(DataArrayDouble::New()); + base_X->alloc(OM->getNumberOfTuples(), 3); + base_X->setPartOfValuesSimple1(pOut[0], 0, OM->getNumberOfTuples(), 1, 0, 1, 1); + base_X->setPartOfValuesSimple1(pOut[1], 0, OM->getNumberOfTuples(), 1, 1, 2, 1); + base_X->setPartOfValuesSimple1(pOut[2], 0, OM->getNumberOfTuples(), 1, 2, 3, 1); + MCAuto dist_to_base_X; + { + MCAuto tmp(DataArrayDouble::Dot(OM, base_X)); + tmp = DataArrayDouble::Multiply(tmp, base_X); + tmp = DataArrayDouble::Substract(OM, tmp); + dist_to_base_X = tmp->magnitude(); + } + MCAuto inertiaArr; + { + MCAuto tmp(DataArrayDouble::Multiply(dist_to_base_X, dist_to_base_X)); + inertiaArr = DataArrayDouble::Multiply(tmp, area_field_ids); + } + double inertiaTmp; + inertiaArr->accumulate(&inertiaTmp); + return inertiaTmp; +} + +void FindPrincipalAxeInternal(const double startVector[3], const double normalFace[3], + const DataArrayDouble* OM, const DataArrayDouble* area_field_ids, + const std::vector& posToIterate, double& angleDegree, double outputAxis[3], + double& inertia) +{ + constexpr double CENTER[3] = { 0, 0, 0 }; + inertia = -std::numeric_limits::max(); + for (auto zePos : posToIterate) + { + double p[3] = { startVector[0], startVector[1], startVector[2] }, pOut[3]; + DataArrayDouble::Rotate3DAlg(CENTER, normalFace, zePos / 180. * M_PI, 1, p, pOut); + double inertiaTmp = ReturnInertia(pOut, OM, area_field_ids); + if (inertiaTmp > inertia) + { + inertia = inertiaTmp; + std::copy(pOut, pOut + 3, outputAxis); + angleDegree = zePos; + } + } +} + +void FindPrincipalAxe(const double startVector[3], const double normalFace[3], + const DataArrayDouble* OM, const DataArrayDouble* area_field_ids, double& angleDegree, + double outputAxis[3], double& inertia) +{ + std::vector R( + { 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160, 170 }); + FindPrincipalAxeInternal( + startVector, normalFace, OM, area_field_ids, R, angleDegree, outputAxis, inertia); + for (int i = 0; i < 5; ++i) + { + std::vector Q({ -9, -8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }); + const double CST(std::pow((double)10., (double)-i)); + std::for_each(Q.begin(), Q.end(), [angleDegree, CST](double& v) { v = CST * v + angleDegree; }); + FindPrincipalAxeInternal( + startVector, normalFace, OM, area_field_ids, Q, angleDegree, outputAxis, inertia); + } +} + +vtkSmartPointer ComputeTorseurCIH(vtkUnstructuredGrid* usgIn) +{ + std::vector > m; + { + std::vector > ids; + ConvertFromUnstructuredGrid(usgIn, m, ids); + } + vtkDataArray* sief(nullptr); + { + int nArrays(usgIn->GetPointData()->GetNumberOfArrays()); + for (int i = 0; i < nArrays; i++) + { + vtkDataArray* array(usgIn->GetPointData()->GetArray(i)); + if (!array) + continue; + std::string name(array->GetName()); + if (name.find("SIEF") != std::string::npos) + if (array->GetNumberOfComponents() == 6) + { + if (sief) + { + std::ostringstream oss; + oss << "ComputeTorseurCIH : several candidates for SIEF field !"; + throw INTERP_KERNEL::Exception(oss.str()); + } + sief = array; + } + } + } + if (!sief) + throw INTERP_KERNEL::Exception("ComputeTorseurCIH : unable to find a field for SIEF!"); + MCAuto area_field(m[0]->getMeasureField(true)); + double area; + area_field->accumulate(&area); // 1 + MCAuto centerOfMassField(m[0]->computeCellCenterOfMass()); + double centerOfMass[3]; + { + MCAuto tmp( + DataArrayDouble::Multiply(centerOfMassField, area_field->getArray())); + tmp->accumulate(centerOfMass); + std::for_each(centerOfMass, centerOfMass + 3, [area](double& v) { v /= area; }); + } // 2 + m[0]->unPolyze(); + std::set s(m[0]->getAllGeoTypes()); + if (s.size() != 1) + { + std::ostringstream oss; + oss << "ComputeTorseurCIH : Only TRI3 are supported !"; + throw INTERP_KERNEL::Exception(oss.str()); + } + if (*(s.begin()) != INTERP_KERNEL::NORM_TRI3) + { + std::ostringstream oss; + oss << "ComputeTorseurCIH : Only TRI3 are supported !"; + throw INTERP_KERNEL::Exception(oss.str()); + } + MCAuto f(MEDCouplingFieldDouble::New(MEDCoupling::ON_NODES)); + { + f->setMesh(m[0]); + MCAuto tmp(ConvertVTKArrayToMCArrayDouble(sief)); + f->setArray(tmp); + } + MCAuto fCell(f->nodeToCellDiscretization()); + MCAuto ids; + { + MCAuto tmp(area_field->getArray()->findIdsLowerThan(1e-7)); + ids = tmp->buildComplement(m[0]->getNumberOfCells()); + } + MCAuto m_ids(m[0]->buildPartOfMySelf(ids->begin(), ids->end())); + MCAuto eqn; + { + MCAuto tmp(m_ids->computePlaneEquationOf3DFaces()); + eqn = tmp->keepSelectedComponents({ 0, 1, 2 }); + tmp = eqn->magnitude(); + eqn = DataArrayDouble::Divide(eqn, tmp); + } + MCAuto area_field_ids( + area_field->getArray()->selectByTupleId(ids->begin(), ids->end())); + MCAuto area_vector(DataArrayDouble::Multiply(eqn, area_field_ids)); + MCAuto matrix(fCell->getArray()->selectByTupleId(ids->begin(), ids->end())); + MCAuto F_x, F_y, F_z; + { + F_x = ForceBuilder({ 0, 3, 4 }, matrix, eqn); + F_y = ForceBuilder({ 3, 1, 5 }, matrix, eqn); + F_z = ForceBuilder({ 4, 5, 2 }, matrix, eqn); + } + MCAuto F(DataArrayDouble::Meld({ F_x, F_y, F_z })); + double ZeForce[3], normalFace[3]; + F->accumulate(ZeForce); + eqn->accumulate(normalFace); + { + double normalFaceNorm(sqrt(normalFace[0] * normalFace[0] + normalFace[1] * normalFace[1] + + normalFace[2] * normalFace[2])); + std::for_each(normalFace, normalFace + 3, [normalFaceNorm](double& v) { v /= normalFaceNorm; }); + } + double ForceNormale[3]; // 3 + { + double tmp( + ZeForce[0] * normalFace[0] + ZeForce[1] * normalFace[1] + ZeForce[2] * normalFace[2]); + ForceNormale[0] = tmp * normalFace[0]; + ForceNormale[1] = tmp * normalFace[1]; + ForceNormale[2] = tmp * normalFace[2]; + } + double TangentForce[3] = { ZeForce[0] - ForceNormale[0], ZeForce[1] - ForceNormale[1], + ZeForce[2] - ForceNormale[2] }; // 4 + MCAuto bary(m_ids->computeCellCenterOfMass()); + MCAuto OM; + { + MCAuto centerOfMass2(DataArrayDouble::New()); + centerOfMass2->alloc(1, 3); + std::copy(centerOfMass, centerOfMass + 3, centerOfMass2->getPointer()); + OM = DataArrayDouble::Substract(bary, centerOfMass2); + } + double momentum[3]; // 5 + { + MCAuto tmp(DataArrayDouble::CrossProduct(OM, F)); + tmp->accumulate(momentum); + } + double InertiaNormale(ReturnInertia(normalFace, OM, area_field_ids)); // 6 + double base[9]; + DataArrayDouble::GiveBaseForPlane(normalFace, base); + double angleDegree, outputAxis[3], inertia; + FindPrincipalAxe(base, normalFace, OM, area_field_ids, angleDegree, outputAxis, inertia); + double tangentOther[3] = { normalFace[1] * outputAxis[2] - normalFace[2] * outputAxis[1], + normalFace[2] * outputAxis[0] - normalFace[0] * outputAxis[2], + normalFace[0] * outputAxis[1] - normalFace[1] * outputAxis[0] }; + double inertiaOther(ReturnInertia(tangentOther, OM, area_field_ids)); + vtkSmartPointer ret(vtkSmartPointer::New()); + vtkSmartPointer col0(vtkSmartPointer::New()); + constexpr int NB_ROWS = 11; + col0->SetNumberOfComponents(1); + col0->SetNumberOfTuples(NB_ROWS); + col0->SetName("Grandeur"); + // scalaire + col0->SetValue(0, strdup("Aire")); + col0->SetValue(1, strdup("Inertie Normal")); + col0->SetValue(2, strdup("Inertie Tangentielle principale")); + col0->SetValue(3, strdup("Inertie Tangentielle secondaire")); + // vectoriel + col0->SetValue(4, strdup("Position du centre de gravite")); + col0->SetValue(5, strdup("Effort Normal")); + col0->SetValue(6, strdup("Effort Tangentiel")); + col0->SetValue(7, strdup("Axe Normal")); + col0->SetValue(8, strdup("Axe Tangentiel principal")); + col0->SetValue(9, strdup("Axe Tangentiel secondaire")); + col0->SetValue(10, strdup("Moment au centre de gravite")); + ret->AddColumn(col0); + // + vtkSmartPointer col1(vtkSmartPointer::New()); + col1->SetName("X"); + col1->SetNumberOfComponents(1); + col1->SetNumberOfTuples(NB_ROWS); + col1->SetValue(0, area); + col1->SetValue(1, InertiaNormale); + col1->SetValue(2, inertia); + col1->SetValue(3, inertiaOther); + col1->SetValue(4, centerOfMass[0]); + col1->SetValue(5, ForceNormale[0]); + col1->SetValue(6, TangentForce[0]); + col1->SetValue(7, normalFace[0]); + col1->SetValue(8, outputAxis[0]); + col1->SetValue(9, tangentOther[0]); + col1->SetValue(10, momentum[0]); + ret->AddColumn(col1); + // + vtkSmartPointer col2(vtkSmartPointer::New()); + col2->SetName("Y"); + col2->SetNumberOfComponents(1); + col2->SetNumberOfTuples(NB_ROWS); + col2->SetValue(0, 0.); + col2->SetValue(1, 0.); + col2->SetValue(2, 0.); + col2->SetValue(3, 0.); + col2->SetValue(4, centerOfMass[1]); + col2->SetValue(5, ForceNormale[1]); + col2->SetValue(6, TangentForce[1]); + col2->SetValue(7, normalFace[1]); + col2->SetValue(8, outputAxis[1]); + col2->SetValue(9, tangentOther[1]); + col2->SetValue(10, momentum[1]); + ret->AddColumn(col2); + // + vtkSmartPointer col3(vtkSmartPointer::New()); + col3->SetName("Z"); + col3->SetNumberOfComponents(1); + col3->SetNumberOfTuples(NB_ROWS); + col3->SetValue(0, 0.); + col3->SetValue(1, 0.); + col3->SetValue(2, 0.); + col3->SetValue(3, 0.); + col3->SetValue(4, centerOfMass[2]); + col3->SetValue(5, ForceNormale[2]); + col3->SetValue(6, TangentForce[2]); + col3->SetValue(7, normalFace[2]); + col3->SetValue(8, outputAxis[2]); + col3->SetValue(9, tangentOther[2]); + col3->SetValue(10, momentum[2]); + ret->AddColumn(col3); + return ret; +} + +//////////////////// + +int vtkTorseurCIH::FillOutputPortInformation(int vtkNotUsed(port), vtkInformation* info) +{ + info->Set(vtkDataObject::DATA_TYPE_NAME(), "vtkTable"); + return 1; +} + +int vtkTorseurCIH::RequestInformation( + vtkInformation* request, vtkInformationVector** inputVector, vtkInformationVector* outputVector) +{ + // std::cerr << "########################################## vtkTorseurCIH::RequestInformation + // ##########################################" << std::endl; + try + { + vtkSmartPointer usgIn; + ExtractInfo(inputVector[0], usgIn); + } + catch (INTERP_KERNEL::Exception& e) + { + std::ostringstream oss; + oss << "Exception has been thrown in vtkTorseurCIH::RequestInformation : " << e.what() + << std::endl; + if (this->HasObserver("ErrorEvent")) + this->InvokeEvent("ErrorEvent", const_cast(oss.str().c_str())); + else + vtkOutputWindowDisplayErrorText(const_cast(oss.str().c_str())); + vtkObject::BreakOnError(); + return 0; + } + return 1; +} + +int vtkTorseurCIH::RequestData( + vtkInformation* request, vtkInformationVector** inputVector, vtkInformationVector* outputVector) +{ + // std::cerr << "########################################## vtkTorseurCIH::RequestData + // ##########################################" << std::endl; + try + { + vtkSmartPointer usgIn; + ExtractInfo(inputVector[0], usgIn); + // + vtkSmartPointer ret(ComputeTorseurCIH(usgIn)); + vtkInformation* inInfo(inputVector[0]->GetInformationObject(0)); + vtkInformation* outInfo(outputVector->GetInformationObject(0)); + vtkTable* output(vtkTable::SafeDownCast(outInfo->Get(vtkDataObject::DATA_OBJECT()))); + output->ShallowCopy(ret); + } + catch (INTERP_KERNEL::Exception& e) + { + std::ostringstream oss; + oss << "Exception has been thrown in vtkTorseurCIH::RequestData : " << e.what() << std::endl; + if (this->HasObserver("ErrorEvent")) + this->InvokeEvent("ErrorEvent", const_cast(oss.str().c_str())); + else + vtkOutputWindowDisplayErrorText(const_cast(oss.str().c_str())); + vtkObject::BreakOnError(); + return 0; + } + return 1; +} + +void vtkTorseurCIH::PrintSelf(ostream& os, vtkIndent indent) +{ + this->Superclass::PrintSelf(os, indent); +} diff --git a/src/TorseurCIH/plugin/TorseurCIHModule/vtkTorseurCIH.h b/src/TorseurCIH/plugin/TorseurCIHModule/vtkTorseurCIH.h new file mode 100644 index 0000000..c6b5a97 --- /dev/null +++ b/src/TorseurCIH/plugin/TorseurCIHModule/vtkTorseurCIH.h @@ -0,0 +1,49 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +// Author : Anthony Geay (EDF R&D) + +#ifndef vtkTorseurCIH_h__ +#define vtkTorseurCIH_h__ + +#include + +class vtkMutableDirectedGraph; + +class VTK_EXPORT vtkTorseurCIH : public vtkUnstructuredGridAlgorithm +{ +public: + static vtkTorseurCIH* New(); + vtkTypeMacro(vtkTorseurCIH, vtkUnstructuredGridAlgorithm); + void PrintSelf(ostream& os, vtkIndent indent); + + int FillOutputPortInformation(int port, vtkInformation* info) override; + +protected: + vtkTorseurCIH() = default; + ~vtkTorseurCIH() override = default; + + int RequestInformation(vtkInformation*, vtkInformationVector**, vtkInformationVector*) override; + int RequestData(vtkInformation*, vtkInformationVector**, vtkInformationVector*) override; + +private: + vtkTorseurCIH(const vtkTorseurCIH&) = delete; + void operator=(const vtkTorseurCIH&) = delete; +}; + +#endif diff --git a/src/TorseurCIH/plugin/filters.xml b/src/TorseurCIH/plugin/filters.xml new file mode 100644 index 0000000..4d26596 --- /dev/null +++ b/src/TorseurCIH/plugin/filters.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + This property specifies the input to the Level Scalars filter. + + + + + + + + diff --git a/src/TorseurCIH/plugin/paraview.plugin b/src/TorseurCIH/plugin/paraview.plugin new file mode 100644 index 0000000..23769d7 --- /dev/null +++ b/src/TorseurCIH/plugin/paraview.plugin @@ -0,0 +1,28 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +NAME + TorseurCIH +DESCRIPTION + This plugin provides the TorseurCIH filter. +REQUIRES_MODULES + VTK::CommonCore + VTK::IOCore + VTK::FiltersCore + VTK::FiltersGeneral diff --git a/src/TorseurCIH/script/Test.py b/src/TorseurCIH/script/Test.py new file mode 100644 index 0000000..dfff8c4 --- /dev/null +++ b/src/TorseurCIH/script/Test.py @@ -0,0 +1,122 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +from medcoupling import * +import math + +def ReturnInertia(p,OM,area_field): + base_X = DataArrayDouble(len(OM),3) ; base_X[:]=p + dist_to_base_X = (OM-DataArrayDouble.Dot(OM,base_X)*base_X).magnitude() + inertia = (dist_to_base_X*dist_to_base_X*area_field).accumulate()[0] + return inertia + +def fffff(initialVect,normalFace,OM,area_field, posToIterate): + li=[] + for zePos in posToIterate: + p = initialVect.deepCopy() + MEDCouplingPointSet.Rotate3DAlg([0,0,0],normalFace.getValues(),zePos/float(180)*math.pi,p) + inertia = ReturnInertia(p,OM,area_field) + li.append((zePos,p.deepCopy(),inertia)) + return max(li,key=lambda x:x[2]) + +def fff(initialVect,normalFace,OM,area_field): + pos = fffff(initialVect,normalFace,OM,area_field,[i*float(10) for i in range(18)])[0] + for expo in range(5): + pos,p,v = fffff(initialVect,normalFace,OM,area_field,[pos+i*(10**-expo) for i in range(-9,10)]) + return pos,p,v + +fname = "slice.med" +mm=MEDFileMesh.New(fname) +m=mm[0] +f1ts = MEDFileField1TS(fname,"RESUME__SIEF_NOEU") +f = f1ts.field(mm) +m = f.getMesh() +area_field = m.getMeasureField(True) +area = area_field.accumulate()[0] # 1 +centerOfMassField = m.computeCellCenterOfMass() +centerOfMass = DataArrayDouble([elt/area for elt in (centerOfMassField*area_field.getArray()).accumulate()],1,3) # 2 +m.unPolyze() +tri = MEDCoupling1SGTUMesh(m) +assert(tri.getCellModelEnum()==NORM_TRI3) +# +fCell = f.nodeToCellDiscretization() +(fCell.getArray()[:,[0,1,2]]*area).accumulate() +ids = area_field.getArray().findIdsLowerThan(1e-7).buildComplement(m.getNumberOfCells()) +#fCell[ids] +eqn = m[ids].computePlaneEquationOf3DFaces()[:,:3] +eqn /= eqn.magnitude() +area_vector = eqn*area_field.getArray()[ids] +matrix = fCell[ids].getArray() +# +F_x = matrix[:,0]*eqn[:,0] + matrix[:,3]*eqn[:,1] + matrix[:,4]*eqn[:,2] +F_y = matrix[:,3]*eqn[:,0] + matrix[:,1]*eqn[:,1] + matrix[:,5]*eqn[:,2] +F_z = matrix[:,4]*eqn[:,0] + matrix[:,5]*eqn[:,1] + matrix[:,2]*eqn[:,2] +# +F = DataArrayDouble.Meld([F_x,F_y,F_z]) +# +ZeForce = DataArrayDouble(F.accumulate(),1,3) +normalFace = DataArrayDouble(eqn.accumulate(),1,3) +normalFace /= normalFace.magnitude()[0] +ForceNormale = DataArrayDouble.Dot(ZeForce,normalFace)[0]*normalFace # 3 +TangentForce = ZeForce-ForceNormale # 4 +# +bary = m[ids].computeCellCenterOfMass() +OM = bary-centerOfMass +momentum = DataArrayDouble(DataArrayDouble.CrossProduct(OM,F).accumulate(),1,3) # 5 +# Inertie +InertiaNormale = (DataArrayDouble.Dot(OM,OM)*area_field.getArray()[ids]).accumulate()[0] # 6_A +base = DataArrayDouble(DataArrayDouble.GiveBaseForPlane(normalFace),3,3) +angle, tangentPrincipal, inertiaPrincipal = fff(base[0],normalFace,OM,area_field.getArray()[ids]) +tangentOther = DataArrayDouble.CrossProduct(normalFace,tangentPrincipal) +inertiaOther = ReturnInertia(tangentOther,OM,area_field.getArray()[ids]) +""" +base_X = DataArrayDouble(len(ids),3) ; base_X[:]=base[0] +base_Y = DataArrayDouble(len(ids),3) ; base_Y[:]=base[1] +dist_to_base_X = (OM-DataArrayDouble.Dot(OM,base_X)*base_X).magnitude() +dist_to_base_Y = (OM-DataArrayDouble.Dot(OM,base_Y)*base_Y).magnitude() +inertia_mat_0 = (dist_to_base_Y*dist_to_base_Y*area_field.getArray()[ids]).accumulate()[0] +inertia_mat_1 = (dist_to_base_X*dist_to_base_X*area_field.getArray()[ids]).accumulate()[0] +inertia_mat_01 = -(dist_to_base_X*dist_to_base_Y*area_field.getArray()[ids]).accumulate()[0] +from numpy import linalg as LA +import numpy as np +mat = np.matrix([[inertia_mat_0, inertia_mat_01], [inertia_mat_01, inertia_mat_1]]) +v,w = LA.eig(mat) +pos_of_max = max([(i,elt) for (i,elt) in enumerate(v)],key=lambda x: x[1])[0] +u0 = DataArrayDouble(np.array(w[:,pos_of_max])) ; u0.rearrange(2) +v0 = DataArrayDouble(np.array(w[:,1-pos_of_max])) ; v0.rearrange(2) +# +I_new_base_0 = v[pos_of_max] # 6_B +new_base_0 = u0[0,0]*base[0]+u0[0,1]*base[1] # 6_B +#new_base_1 = v0[0,0]*base[0]+v0[0,1]*base[1] +new_base_1 = DataArrayDouble.CrossProduct(normalFace,new_base_0) +new_base_Y = DataArrayDouble(len(ids),3) ; new_base_Y[:]=new_base_1 +new_dist_to_base_Y = (OM-DataArrayDouble.Dot(OM,new_base_Y)*new_base_Y).magnitude() +I_new_base_1 = (new_dist_to_base_Y*new_dist_to_base_Y*area_field.getArray()[ids]).accumulate()[0] +""" +""" +new_base_X = DataArrayDouble(len(ids),3) ; new_base_X[:]=new_base_0 +new_dist_to_base_X = (OM-DataArrayDouble.Dot(OM,new_base_X)*new_base_X).magnitude() +I_new_base_0 = (new_dist_to_base_X*new_dist_to_base_X*area_field.getArray()[ids]).accumulate()[0] +tmp=m[ids] ; tmp.zipCoords() +f=MEDCouplingFieldDouble(ON_CELLS) +f.setMesh(tmp) +f.setArray(new_dist_to_base_X) +f.setName("dist") +f.writeVTK("distEig.vtu")""" +#mat*w[:,0] diff --git a/src/TorseurCIH/script/slice.med b/src/TorseurCIH/script/slice.med new file mode 100644 index 0000000000000000000000000000000000000000..167c676212df97fe927187dde63bc17e776dd5b4 GIT binary patch literal 330141 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%0DDVhgro7xv-+4&f+HAO>e~ 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�Mi-ICMN~cR;QlQUJfdSFP`1GDmjBE_25FR4!ls@z~Mw*IF z+l;~wzd+_W1M+dzm8kWA5%}T@TpqrIb@1kq@gspz@USM{_r``O1fRMFzX|S7a`{@w z8usP2)}T^9)Lq9?yh7!rQLj(--^&NieWx@hmh~o(kQ?NZHT#c5RR0|FSlHuF*zf+e z{GiH@c&g)|>f*hx=d03cvrs@175B9JK#8S9r;|uSLQo#>%HQhg0&T_zk%MPVIQ#Ggx;Py$$)u) zty8pD5Bn2soJ#BUxL@yHys@v3@F&U>PYfhQ`xDn5Z$5qqzEi(-6?)=){fG(1I|e4W zpI_`rRE;*skGDtCx{9$c4o$j?A`X5$_A&O_UexVb#v$J?fk%&&P8-L1x2t0`<1j$J z$SgbjNg4MkDfi=#Bnq+bTl%Tc4ho^fGdg}3>&JPAd$WHPg}Bhw69zf-IGkT>$ced_+h=Y3IECEH64@q#yg^0LUJ3;Ot; z&~Z=jDAL2m(%Qg-;aUEJOsIQ{sTINwp$_;w=lV(;yi_mm(bNes)TPz(`UM2y#$aYb zhA6)NSGZ0QU$0>2*;Aec9pEX={N=B{#9nXyhOI5|FIR6g*s&{+@N)}_I4clHB#H>V z_2LU83XUsB*X9QjXE^h#qx1p@m;HC#tWn3eY8ak8hPr-p~!NPwp<)`4Xo&dg^D9cij$f zXS$S=2=OhyZvUkx6E{h}V;WfC+f)7gw_y9n2)|fv2DGH`Bdjp0aomj$warc>%f^(&zqmK>_XnZV!P`h>h+h! z?P4Nhz|+fpT+ENKk9i-iJ;m=mZ+e`sx6hk6;C$}rKJ-q*B{OvnVGEG9K&7rTuUyeT9A=S*}bqnC^8>0G|h*xgLW47J+d<{)Y z^(?FCdvo1VxPq&h=uo&3uKK|?{1d2aZVL74@!?t?bR)kj8FgXyguyL23gO!IaX>OT zfDp*52c{t6I>OZg^lTSr4b;+Smq{-{z`xyOpS)00*IQ;&% z8ZT`U&LOH27M2CB%F7f=*2C{FUF@Hoz-tkgHWjZk;6V)R9&GuA`7+6vPO3>?LOw5g zi{vzk$mFbOW>E!io$h?K9ej!CJ425?A?ySG?R>lVpszW^RCVr#FJbyf(PIVo$#rGn z@H61%Qq|jbhf$B%-p!Q!FB?7`pZ{wzq+qT>vTWBZbpCp?&l-0Ecd&^@cYoc0Zdpd% z*P|72S!J^THS!6UotA6BJM`fW=R9uY1D?(JlUMu$b8%ZwCkLaiYPfFLnc0K-pj&K1 zP6u;8Azn@o0-%>>ZO(M;^Ci}Lo{TGK_z{KX_cq@q`4M&dU+Gq%9?VgoeOFKf9g|m0 zf&%6>N-p%t>`wC~Jl-FoPILutVCp}4x${E1bk%DGJq{)FFI z>X$RO{RzcQQpf&Yh2Jx)+E+v9uybwa(jKCIe&@`qe+u>WgV+tyzpL_-(|_0;ai1&H zB=T_e{xW@^cBY{T#5TR+rH}H|L(I&Fu>nA|M(n_1oY86dgB{!0(Wogot&Sd5Pc?>Dx=iUFZeinBL#W=){pn>Qp$crO+I_K zysjTXrMf@NNf7+aD8t<=`zb`rpSE{9Ie`l{&sPd70uNjZ{(0&!>T0kQen%+;CtZs2 zGvqlL%5&D|z+aBHT&Y)Wfwx?FD#sh3117twg*EvSTW_7Nm_)u+d&=gtcEFb~;2V`-We_&H4%Yu0=Zz9YU87d~Q+U7EUtUcwK)L?MpSr?LKKwtkIEkcEz9s&Otc z4}Gft^xnpUXYd*-3@O0-dc8}xHbV+{DMj_9r7?vF^#9SooPs{ryoC8-#F>7xbw_*5 zy#+S+U189}JoPF)n=#g%odHvK6L@9aW8KH!=^{UC-`y#L`=hQj_ILoi^ewshBiWa6 zAHz0R-%^Fn|5+r72Yq65desw;`~8SJF<0Jo?C>W(yr`dQqVp#@(iQ2R{lI!+{q0LN z4xT7^V?rF~FL~xgzIM~ckzW!aL!o zoFCEgbHIfED{#Oy5vrsk*v~U|Jo)(C>3OH@Rn5?6d}d+rPd~G_$3Dv86`6>ZGVtwG zL0uJ}>$+rt{qgkP`WJ8DTd~!|AK2#%)5`TV;B(r?xEAG+Zy%j-S5T6H9zdPJdL8{0 z&nt^ej}f2a;$=lXm0l}a=VuHeK29*DPd5b; zN7>E~M6Mz~eCW9siMYEpXT3#Xj6}?)3q_}$0bj*ne%H#ALOeU0lWb*9fsgjnM`Cd} z|26rb&SB(hXP%+6kMTYl@-N5d@}llN;@+YUen@ws;CIn7-tQ3!?VqdYCzNZybLYYN zM`a0tMP1SUJdH2jU|Vwo?4N1PD88$)?YCSnh`lG)V#3CkJ%gzXCO zjoEspR}0UzEVuYk=oQQnyf>qf!@k?1eZpxTeVPIWZm2p;ZYhj!pGte>8tIukMEvFdFa+kppLYaXnHEaQB?_jCC#fiG8c z#O`Tz;x|xdhF8;U$Np>B8yhRF0iTC@<#Ye~V_$YJ$Yg&){+X6tuElyyHWH}X zrilEvZ2Wx*S0}#uJF4Kz<@*J9AG`$p(~zWwzYSZZ$?2Ebu^RoV8 z;KaG($7pR3S2>=s7XvT)mCD~b;YlVw>=L9oG=jRYsyQ_hd0geM)5Fc!H+t9K_KJPS zy4>t>oeg|es(XyhB_@0>R1-ztvVp;-!U$6CsjcFB`7;Q*+6$Cz# z81fd(e~b7KQ2s>$^(am0jLUnh+a!HrFtHbX$cnURcdReB%gu}t_&g_u0%}CCZsqPP zTG0g}KKv=2zlJ?bY|M`4I=o7l(&-dK1L!LMb)O<==nSUyxFc_)4dwP>Xt&DK#{U z`YFyqdiHBH8hNH-k$=t(@nP!TZ2<$szbc{6Hh;e1IW5Gu#vy;Ngr6N_egHqMdS<#e z_uw-U_M`29FzVykq2)Z}uL%CQ$49V^s%p}#6-v=3*&M>~B^!E-hv)623h?=Fa1DfE zes4I(xk|6um$>!j^`+g&mpQ2#>qD47KF_niRpJoN?>*uuWCLe_tjBri?^IQPeUEr4>A36%+1goA;!;la1 zgQ@ELpAgLNv%bDT0=`#XVCtg6yx!S^jP5-x+73YNd z_xR(RaL6ZYr75wb-o#r*!{fP39y){*c zb5(ghv`)}%IDCGqFMiLDP|xVfZtCzOWVMpA*}EW7W-8X3ehFio|b`ika{zp(OegCw2j*Pr5?^j zrOt`^23%?yD6;IN4PA)t9;bR*`0txKIFtcb+`U@ttQigc|ENa7^Fs8)s!JMN{4u9> zST*%WBZU|;Y})G~27Slvvemm2WI{dn)Y+Q@;9u8hu2#MFZ$^#vPUG+q2kKFRsl2slGU>c=Mo{C@RZ zWp8q~$w{1NtIhjaR}cK!B8~e)d&HCV-m5>r%l+75 zV^oT`dc(ugz@r`K6S+nF9`+0%9Lye{uzVRnqz1KaYCj%GSS#w!J#h#mxK8i?<>nGd z6g9ZlXQHnW^!D>5&fDO3pi??s^6#8+n@POSq$XQxR1O>yQaGlXV(4fbMgLi`nOdvA?s;cdzu5+_I?4cx=+i)g?hk`2+7hf-dOh|HuEb*J7NxZ_LygY8{(Nw z@C&UDYV_5bOkcO6jt_lNrxyDTc<5Q)ol82v&keu3gK+=bWk1NAy+I}{y-hs@_uxGD zWV>k13F!RKi_05JLRarvS<=6Nb6;#9z0>;PP4q3_-ed+n_}x=KMzb;J(PPvukhzFD z2FJ7V01ONJIe`2Sa$k=}|yQS13#R7=S zYPE7hz$+Q*KW}9rK5Jbx$;(CF7Io9;E5d%M9=!i48~yJ@+HkdJpWrvK#J@sU4&N?n z8|&UhTs6W+Gw?aQ@0zkcrc(;wRa$4==i@e^=cEUl0Y>rfduNd~#WDi3U&opP&{qMFsvYmpG;< z;D0iA`NW_4f_}I8u+}Q_Ur9^deF`sp*np&pcK}yW4^{0Jg3rlmqrQbq%o_%AQ{sj& z2d(@n`!9UNoQ6Mrwndz^ygWDc?h^RV0~+B*z_}@0aojQB%~nh$nx&PX_i*LTsJt9N zbUvq(`TiDjnWyB`jwj*$jM}Z+h@)>v@3uJ4ioV{k;;lf`Cr$wk9?H1>u^$*ry@LF! zM!RJA3;hjdZrOry)MJUCKUhT|PH<|+CW8N)JdwXOi2?nDUBlBifUjQn%}xmeXOC-` z6kNbHxzS(n68L$$Hv&y!66oju*7z!bx?IFn!^a2sPiD^M4*O$2Vu!nZkt%o@#}9{o zUMA45TTwQi!94VZ?0uTf_0VU~GEJuFpuSJOsm>w;JRH206g&rUxjqzD;0goV5*dvAb^kYw`!rmdAOEpC0eZw+hlutC;1URaFP>!g`2WH zZ;P;AW=E))&~Ll)i;?}REc_t<3w@LVyrPu{i7(d2KB-T1s|nCwlG?7%06(?`h5e)k zZZxkI`)VqWc%$CrzTAiBe}|3Sis!tQJ?UZQZ`21(E-mFEz_mODZ7)=zrzOYTX+S;m zeE2813;XUsnqAu$)Po{lj>@%vg^p>Hj9esm)V&oy4#;8M&-<9AiGD;J_aQHqBLAco zFE|#&lZk{@BVA1rbb?zrCbz|+zfEPo;kVu>dt21;wNwDyl%9RiOUkh@-wJosOg*81cB2czaM%M z(1ZT7*~%8tEm+^&dH1$s9{j@bxZrx^mzB&Nsc$gfbgg1K%0u3ZP!;NoW10efRVhm- z4S{~Il(n=U^Yh7`V;u)h}mUPN5t*@>KY4Wz%y1)j;>r( zN4)DYQ*S|CxXiJG{?Iz|>6v|#v&gqD0jr+;-_g%xbF<<62EH!FQ~JpZyx*gF>4p~Y z`BMC^sGMeV+Xz1t_zqO7pZl8 zZ;HOWv5-fVH2SG@_qV`P(WoerIlm{(hrdYR@kCTT8ri43G6E#G^zU_;&geKJVX0hy3sZZmmg> z*Mp8uA!}_34idVVs5e~@&X(6@d_QppIB+%;kV%HCemN^>Pz8$ zUlvaux{7n1OfoKSItyQk0-8&1ccBBA-n*s|jCl^>X`^#k?>l(8wb_&4$Do>(VFKKc zb3p7+**TowYIl?Os3Y*bS!rpoHh5v{M+JQ7V;;{BiFNr*CXCsIb-xahi7XpCmm~@F zLAN|$Ig9u4Lipx>P5j++wLhz+e}Xp{Q1WNO`)-~in~NG?e>I*Q34R29*URwRP1UHY zot{3piFoXLfv_n-9jRMdry`*MePYo|#`}mzPxS_xcOxFXYG|RI7({+M6~Gn+KbMU( z-a+_$z6pNWLZkUzGW<2& zzE+JO?jK1xzWC-X`l@eqnA9zOiSR1#!~N**$J4ZT>frNQ&tLS*PX_PWANuKwmmgv7 zckqcmE%>&V9EUFfZ&M8yT2~x`eoy4H+#TSr&o{1*EdiI=t*`eoFM|hH8PW{}ubj;B zQ=J>%Ck^q0{fleY=E$_KEWk^dJ315tXMI-ps8qR5CYbUYG?t;491Y|S5A4Faq|vIQ z1Kwk^YTu-bJfM3#;#4AV;BUS(7M?f?p)&3*HS(6f2KP6cu!vm+l@5&{F_#=Pn(9ya*If#Ct(>S@78@>u{ePUON{Ry+O>X{Aj zP^aAz+N*xyypO^bh7#b7$J!VAy=Vf68z0sO7t8#KTYMkb7smaGS4&PJolZEfWASK0 zD(YPx%C1;;GwfI9cG*_MlZ{tJHeN1R?^NG~QwIL&PL7+f)ng8#_1U97tjm<^zY;$; z!f){$%k8XD@FD?0a{(^kXDHjRWEud6Tyf~UcF2n$-hX)f1bi>|B{jWZR-B)_mPpRS zYhTv!tE-5QPOSv@S>VgQp~LY4$bZg`A$PA~AMlO`&bhb%U)>#};$rnDB6FWz&9njT z-pQpVdmR2CDj}0+qfi(4s#eRwPiuunK2r*Oh)ram?d-pJaCd(2FI6B>sC-gY7e2LL z^(8;6O5hyga{~LNp9K(O`e%tv&>fgXB<%>`!1|bK@Y{gD&xB}tYjX*OaI~Fzd>Q(p zhW#x*@=9<57Bvi(mwg;ySN<%@FfO z7Y_VXHbx)V_|EqLyf4$aV%gOw;Fw{X=NiB>W4S%wNLvw?Zca3HcA_6IGrxKhIQ7iM zDDCN!hsD%(GJ5o&INbG262v#BFjbL2n(EV#o_d4(FA?v>L9ui#P_xQ zEH_v1UR+8-U(;vsK77OIN5St19qi5%kOM!gLvA!VPbP4vOo*^>0CCYtbwnTdQl`FU zM=0?2jagZS(};_&ezEN^+X3Gr`MmipL&{0l}fFW3AGnD2OXfx<-E?K z7l(bVe9Nhq7dY=~`;*3-$WOf&SYo*J!6#(MbLEi1^9uRzdWCxVM6=VEpi1!Z#pT?R zz{fe+(bEgSZ3|tl{rZT@l25flcl?Hq@^j-J#Vz0&TLOl^KSlp>w@*ILR-9)Y$@G&6 zdE#lCb0}R3>ewUBuX!WE9M zjl^lY2j~;JZ;$!*7|Bt$yt?r{2ods=-QJzJj8v8y3MaOKOpZfQ!Teim?Lf;tUdpacTe`+uPy%({UPb> z{`_?K?<|x)t3iM0xs_|>X5co*JuMLS;O|hGKaffT{~xg9i1atq2W(bu2UjRWn)*Jw zbNr~gLv|PDETGOdIcX>$4F8(~3u)3L;PY=(59i$w*H83u|Ir~693M{RdBLd|~b!bSd7C{3F6;s|LKk;~veR_%!h4+m**cSWjE`1l`F( zzr{_|wN|(jc>9CS+ZYw}dCVVfZocYEG&$NfJ_&{I*o7Gu*9rJ6XJmbS^&aP6I66_w z7sDq#BZVWZ)Q=Es+w|YJpMJz=Grc5-yI5xql8N5HH_mfXwC7Z@9|jYj|Hge4>TS#a zPsooLyT7+u2Y5(XEBwspvM+J&rB>$@>d_OgO#6@Zp|5LqdhI*%+~;3b&-&>pMDsRw zj+9CCd*X#`%J96pwDh%q;#xeKGPsKPw(m8EyMrjS;0abzW-o%9>ZQa z=$y*jpIwE1OD^^k!#d^!%}CH3r6Uf-?(m<(bGZ}d_TvqH|3R^!!*57IeC{gr?Rf(K zcd;$YVfoOHgjww|dchK%(} z-AVh!9p|4su?lLt2|i#`zJO^7@S@w~!z}^mD-*<&)D_fOTmGYWe}MZS7`($M3%m?# z$c=gAMRNRD@spPn;*0W^q`z24fpX#WC8)2G?apVh48qqhjn=Rq{rfZT{QIZr{0N4! zq^lPk&_ClyRUJp&Omog*_Zs>m)U+>Nb(uk@f0{aE7V9pYGdPc>5pnvz0BR3>U&qgx zg=GWrtDQ-bE)wTkxg~O0qR%(vr*QSsJ>+pY+FWhir+688nLnskPH(6O_Xcws^oPVTd}{$zrdDrfx^@~hH}vQ9qs%Q!A9L99r!=+k+W6PskNmXghvoTHY2o+Ihh(*6!4JluX)2)+ z^I+wXl7Ui~JG7w@Wve_5eVd!+=0!Q^EADF!=juaOGIyJ5|5*~zd)0HNnG)u_wDRA4 z#a!*c@5|vUmLy_7E0@r_bj%+!N(Ma#XkTIe$Zd!2 z!Fu>*Jl}MAALihUzH#4ik|hy2A1{BToX7jW+fYCeBoV>97Kv$d@KxIRqJDhCli)r` zoqL-Gz6@GJ5}UV@i1={DgcjVt#UTdW$GESh2{gr;?C|Nyo(jvr>!iD*y%r24;-Fb! z)+SLBLHl3y3x>NCA~d*$trl_Tj@HWTp4${+;&;{X8vG56W~ptKpg-o&RpzTP^D)hwDs9Eloq-r+zZc6wU`F+wNQ5#@Zi?kny` zvr@%FDb)GboH8Qxfcr1fG*csQMtX{QhHnS{?HXK@7{KRI@Hn^sJDxvl$vi!HfcyFF z64Wn%lb5cRoddtb?ZxXT3=Caf}hMxV^ZA)o^Sj!mpf*7 z-i&L9hw|ai6V(1-LlwBE`lewl$4TVZlPtx*5YPGUUJI;ZB@?fwV+GRK zNQAFB%STDV7<)d+IxD{$E&E zSG)$L$1&IOr!O}=MS?_pTdfU@^n`x2hUr@`_E)*dT)#R6>vF!s?G-=#DJXxXO0SWK zP`xZW>~w-+c_hgA66S%a%-@vbx$|tK^o|!|PC{*SnRNl)*ZLRzkGQ%%tVtCt#$4W* z0#7tvOM2vuigl9+#tZxBU*YT0Ou5bJSWlWE+eYt#_v5(#mtB;JLcA(?xi1ZRlPVeh zx+7}vssC&I-O>bg{ho2o+Z(`ff;~0Uz)9aYJpH$@f}f1Y2$7dXUl^oAPaOOncBV|Z zjzCwnhuuaz47&X3oL^^`u)bcN4V%9WJrci6&&~jRzUd+(Zd|JyGZrzn!5g-PR&>{s4s#&+0k+RS)wPh63Bpy@oEY%)8B`8hs9v(xuKt^z}X{7!(F# z{>-uOa$Pow`0$OaU61ci@pn9V1O6?^WwoUz6#OSUTXQh_xXwJv(K`{xnfWaBEDmFR zkCqklNuvMHxWmS98`h=b_U?akiK^X-B)uotzpHlalt6s_VNCsa5Bk)*V+*n~CCr zyjINDWN(3AjM5!$Vi5bN$cWASBKD28VA#iC_zTUf(ksQGF6)b5YAaO)zkKN9-eb@b~al%{_hRr|1e!-9rB6%6iAmhdN~Uh~d^+{GE%REeu?--#H%2 zU%rL>JkK|`Bzg^fl*wb)_DX|yySHNSZU=P6GINfd%V#W`>T7y(fJ0RL&Ol*cs$~Uc9qGAe&mZuV!5r@s+gk)eH zRZeRb1R0Wul@uyQYQ&K=8gAQaT7Keh+K-TkLGQ|-oyh0eN#!?MXD~OZGn(9j=k@V)eIBhC@Mq(bBEw=#BbvfVWGQW3&+`&N;0z7A|=b zrU^sa`4NXaW@h)i%)$Cs$qpdlzUREw-ZJe7AIR{;O?LgbAKkSRzu=F@ezJe1WDn+X zC;Mx-fVYk}C-oR2k4)%pVmaXFOXS9={>K>QOZ0w~QmqJq{!A}`r0Da%{{*1Sdpl_=1^-RQG%=MQh(ZM2zP&^s1dh;Q0 zGVMuw(^>cx#yqoT@&Zm%6Z)m@j67CV{E7?D@$j`R{Idn%!IuYD=<(Xnx%mJ$@`sEC zPe}hK#34ql;9M5ui6N~}gR`isQymYU3MUhdiRML0Ur{gARl4LL&gn86ToBX({{PQ; zuh%Iu5t8WZ#D44_PAcJlA`2ew^4S1s4)B13g0@C!z;g^O*XZhB_)?V()7{~R zE~mxil%5!H)ahjo`ZLfsNjG_A9Ku{>@~lc=FnE4%_U0=d$iv+lj1q{$N1r-$Row(1 zab(xn91HZX+h@l|cB6jZHFUxK7vk7ym3<2v@O2!BO30+fK6Ekoq=L^s6E)CNX8RAn zRc;pn&r+AL{GJbWy3VJO88&Ph-+go>In;$;S4Z~-`2as#zY&oH?qGc$$=QSTHS_aN z;K6ICUza1LioX5JAL-J8$OG5;#cnOxLT}nD93VCgK7huw?K1d~Lz^z;@J#}@if}S^ zn<75xJM}FX0Jn&49jn1UX00jXi$Q#Tq`IQDp@h$Uab^1|){)L?rmVmM_GOprgA-I< zgu(ylipJdCsrp699Q4yPZ7e^djvOxV9H~U#>wbL?$@xBbn`dV~KU>H8yWCx-4IW7T zJ!iLmGv*koW@$g7@6%n*S+xNiWhHyh>m>S0reBzC7muP&Jz~2v4bN98_C0GJ4|stF z@?#2k^FfyP5x?G$i8fDb3*r~}U4MPG z_|6V_FKgeDLLBmeanetZt9MX`#dNbsqh7GvXK{jdKXeXZ{u%%L4BCp#9cKUSllyBl z6x{Eb64Dd#U+}w9Rn30g3*S3sf6r{xRVk&-`|G`-pI5QE8W;i|Cx5pt?{V-Gzhwsx zq3jSBr;H zH?107yr_&g#U=T|V+J_&B{RP-+YRKQ!x6I=fVT&wH5j%RW1jTCh_Len=8)(ZDI>_& z2lNEo$D1%OPvIJK!|Ps4rDxx>kmrAoH-G(%y6|u8>?+n@O# zbH)0)df(g6k3xu6+q}7H4IRk({oNYi(W<)KDt;p`cHcO-_YAIk?mW6%0^Hm2*tYVH zBXk2;4!=1guVXt8~I|$eEGh_oZCbPn=$$!Vm{tqFc(w&sbMV99Qpl_M?I>db*z7eg#)*mpWvLj9`7S>a=nNQ`Lgu+Vw?{pm67uo z{q$Vs9mVF`ah~xR$xUZ(BfhRj>MNoi`+JY=av#>mE8k-si7AK+-C{4>kPm8fU+3oC z!g_c}IOXI0OC;GfT$MqezjBnvNE~`6x9x+L!l++UiWHoHoA+%sSU=+r-KoIrqwB`t zH|sKZgeOqXo=M;L5%pb)AyM%;7=B>q*>ev7CzZIS4-Ww+h*$U2d-y9RTL zZg1Hw3-SGOtq(lNQ}>Pco-3r_{1m_KD+RvL_h>bJ=Ldi3TYtm!x+-|TjAZ>^eCX%! zrtzi|I0uczOy!~@Uh59ta`J*te?jYD%N9mZ`^ zX4EkU>8JYVKlIyz!koL(5eFtl^j<9#k%+|kTxrop_%uFhvwnniA;>HvloO0R&+tTz zAJ=HE1)(Hd^Y-&zDp5gydCT!5_SbPgXRmQMx4`GxoGUV-4gRmG3(hpa7bL1T^7H=i z%~{iQ)ImH>Uo^^}LtQ~B5%G|*0?wH{Q^f~dIKD07lyT|);hY=VI<5gVApO=am5#{UWYKD{IgoZ-E?dvD=E3USNJ z!O{b9TO(dk_+%#hrw;Nse#(H~RrW>gM>FuZ(-1KC1|CkE7z;1IiTKOH!ti$zyu$X7 z_jWrFhf}L)uVDSEsfo9ILR|W8Op_&F3Oylh)OID{mIbCb$;^uX9((~!Vi5H z`@uVTIyt=D3LmHggVb+rP(O>7QC-LWU<&`Nav3TPXCnioe^9uO;Pm|xuk$?RPiWx@lqCTz{Q@jct z#&;dGb77PHs-rQt%AUX*C zAQpZC3|Mb|zK>*5L$Q92)|@?^0v^VRP&rhKzWWVV@ok0BWxCD2%esv@qdBo4D&(1G z)ZxGR^Wcw@BG3M?$&28;mtPv`g#5Qpg7=sscq|^}p&s}HGu=o>fn2VeC30AEG9WH#$IoWJ*CdgB=8`qGGVYSp-g1h~C<6OD6!Bw3CrK&SPT zH6^hZzn@9I@=b-GLWDhGsm~We{QvX2a~k+Ry4B5Iy9o2_?&ht$m=h~oyWpdq3jN`b zzL9)7)>(|95+Bx$=WQiU@mBa6kNl+wfpgkU$?hd1t}YCiGSOl#`#GN54#TOoUqjehgKg8&vp#lj{DuJo3JPbMvPA zBF_AP4x+YsyloIVInV!!*#vO@4f~`ZyCHlIH-a_a&(> zOyziPa{aS|@OvPSmN)#tdhOz^e^8G5Bih?4T2_kwDpz6udt2a;`fp{0Sm*boJXMoX z2Q%w*dx=Y6-sksD{X$&r?1{Ad=P*~|GU2`ryx*MGqK{41KYwbm^nJL&^jY(?R( z!b(RQKLs7A*#YBLvbp5pW`f| z|AlM0JKxLSFQE5Nj;c6@c?B8jAW;`O;1GSwEde`0PDC&1)qn@CJG(39?h5#JVw0pT z@FUHJ?x}$7n8#Zm$o$xZ`+nDo+mr#mQw=1glXz~zrxZ4YnZu8Osd=Cbc`BH;Yi}f; z-&^09W=Z5V4Sue%RtMA_77{fEA>g%khD3&=UV)fk`k#*W>EUyy(!pc@SRXvRL5%_mc zwHZ(7XOivS1Uldxns@t`ERH_MoTyyfo^J}c&mT>-H1U5HxPQNMM!YxCuM`wLjn_I$$KVX+Qtxb8D?5+-q44O}IG)#a)0+j)k%tw<4~?AFfX-9aK#&gmCiGLp z`!&>432o6gCNW>pp;pw&aTxk1!JVG6z;V)}oTVpTBM)>u9X<;^2+MVg{*&O1Rk@C} zQKQaSj&Y|lg%hSe<@5BO88CEHcP7v;A}*IlxEW*(%s$Npx~W6&-Weo`M&S>9rylo#~r^jhHegLv(MgZ%{Av| zer7D{#|)Hy(1#~pi+Q-g0s1jry-l17>wM`rt^5G&BOUE`eS`^c7}M*-FU*i1Q&gCb4`&Hc5Y-r(E#@MaxE0cX@H!1+&RgJ8_>EaJ{3= zqd<_yp==Re8b-)FdRMqOObh+uIv(xBZrR}qzO?Zrp0URr5@A2md>Zmw2lRe0K|kEr z*Qq;G!9V#yx%Dg-zPFY4<%d0xTZ?eTDit%RN6KY7(E`tzqD64QY8m|0=5O}5!R}tw zE*09(0sEd5p)ra5uv+Yk}kT*D;_4BDF>H9W_P z;^JZ<@Sv@9M_>LpWQWVXl1{D;e*20T-e#r<>?=AscgdCl@b>(rj*sBCy3@6Z_jAC$ zJrXWDz0eOTN4|6S4h!sWl;Eb)_yYD>@Y!-Ux&(cvq?`XxLY;s}##KB97wGTsd~%5n z{IkrFFbC0a$TxV(iN{q6`|iDwX4{*zhy8C@#G<@l|Go~IVp8z$wH0?g>Of9$L-e?L zeg|9~b1u>DBvcZ$)xobCwt#ZXJ-cbJ>GTCDw_rV(lJc|ZyNG;2Pp(R z&jY`uRu>en4(p%#qs(XUiw5iY`r@0L*uzyYI^r8^9-uxE`ZB z@Jod{m&@UL&+q^GkRAYbglg{nk1+k36x1zS4HT90$I`{94V9^6f!iD(*LGft9e2nf}?k+G7qlCi?ry zmL8CYyip`U4Ex@FgEeLV{$}$xx|?~BN8_V;N60uA>fttB-YtL}Z)ARuE`@On4KgNW zf_!xf=luyjA?Oz!vn6e!?|>^Zub0$-d=>YiyAz9$helQ=iZAB_{)HByNHEy*=9=>> z?co0uZSr|VlY@Syt5wwmJd9?dt7-iqAM^4On}7iP-5Ijg9mv;^J<9qu8uBd)K3bd+ zOon+ClpNN{hxO(;$Z;8PQ8D$~jhi&k-w2=C>JO~9JNNTEr#&D)N$*j+`+LwsO6L-N zoWOpvSLiQ$LO<#54%r)zA%8?tHb(^fMEcGbFU%m1UEs5ALk{S3cit!6v~1x2mFk;b zmI3~EFfGzf0_qZ&O4;QBf3V?wdA$Dw@-{gePCJ6%T5kNBGny0X5A#_BZNYyO=PT^{ z2}f=cnkZ2>JDlpV>h}*_?QkR%uUxLt}?*W#Ds`cvG{pP~SwMo~<?^PIuTAck*lWrES1dV-Hi<=}!T_Rr4k33)pW5DMHRZBEWq~ha6wP zJk5&>X4?ZkVY4JuO$2_CR9QW=0Qi_@i_3ZX%#ar(FGqeo65?XG-3yw)Gc$IlrD`ui z{;TWsvs+D&H^%a!zPKH5mPFW??HJ$yzHg@qVZB}MPYB_rfqrevw&$6Zq0d;>kU_8x z*wqcOmiJ*$_tIu=o~sRY(&B6Nxp~0*7!2<1_Cp@-)ER@H_W`$#>h80F-hSIHG{^(_ z#N9*@(Y3{PxQYFN`(uWn|5P7UB+J8m^!TW~X|cl@c|YNE41ngPsgnnf5Us5WBSfoFn@*N&PN5HuS1x=O3yP_Xqk=eA=880lb^?!CqL^7vesJFs&kE=(+M-{LA--mp(VKNN|rk9h&z@I8Ud3o>bt4_o~@_7XMh zTR3avNm&c_{*W7Ds02IUG^1c^5Bd8K{a#PH0^Y0jtU2cdeXI^UsviRX@uuuPUO4ao zF>0Td^?(<0@?74|c>(;l)akIaYOu%eYb0I3-gTM$^7Y`~tRA2G)D8B}W|t&(5O9c`{Cv$a^yiq_?q%7AePPXfMVMTmeo`{vMne|V zm&l1M)x&;mt1EO(a>rr+@9x7HI9Axt^J`tIBkcc^vhl*bV-@Nk?lh6qjetL}d|Y4s zB=k#DoLLP3dm&TNa>^I*$mZi4(-X&mKj?U$`~udco2W=rijX}{y-66%Q%PS>{x zjuNJyRwOS0uBf(dE_eoZz2m_#AMp2LOb&}o!+cWQGRnPT1H9rs&x#1}g%tC80h!l8 zzu_e^iikkIongm&@&wqgPJmG1Jm7~x|8bdbcO7tgkGxDw`XH`K{w#tBcz`peR^v7> zzn;5Oo`FG-=kUrf?>rpOjqXp$27^8P`jYht;E5a7lb;!xVLYmOPBg*qZ>FB}_Vxq2 z|5;Kg;WP03Z0DE@;CMqL{x}MN_#tuPP!*hay0v%sp*!#$_n%xC06vF5wf~hm9mo^0 zy$UT25E5O@6^dgdvHG=tJ3$1F>fpz*yO8>eu$aVM= z-QmaZ{6=y0bZ`u{*1VFG3+u7}^7VFDM`^`6ypzDEP34G{G}QusPH)~z)(dqVuQTff z0heA*i3o52J2Bj=N^uePtF_UU|G)`;xQ(0pxT-GXgNMe;f_jvD6;=$rB_y^v+a0r$>D zf0cb7_No0%nodmOi0dR5HjtnP{4SanMV|-t(gR!)jKR=HvWohp_*b~uGY+qEX2AdgDo%28I}uay)&cUZi!!!4!Ha2_ME z#rdCe4iEvkC27ij#RqagKKMzoqYH4#w5hin`D#zib{`r}pD=hj8@F7kr? zsr!a|5BzopddiQ+;IHoqWUXzgI6&WZmGz^5e`wks{&~*;eUm+&i5%>2*yBvbbI@nw zn%h|yR^W?B2CUB3&_O)Uvr5{O0rE2T-S*|dF4#o0>{(uiy0_AD7V3qQ2hud&ft`!Ewle7mdLa<=)>DJ}(WP?^N=G5i zBCb<22-iz!R_$@8w8Iq>Enj&Gav3`$R&+fP@>R@s+NO%2-|C6xx2J<3{+1XqbA}l5 zew`H(zJh!^;A>Y;f&aKJVMeG6_)%Yy20MZQ&nzk)HU;viU~64N!W7S6%G60TV+v4C^t)-Ez12LEA=E}iBYFW{&iRW4J&r?-0}9!kQI_eIyN zFe~&mo%$uc90c-z)HGP{E9AG*tM#!2K%R%58O7He;9~|}lJJ8Z9N7{l4e_zZMXRLD zDh9%Sy5ETyH~NA1^2V{T6Tm*Q<*^g(%fJK0{m3kkgni||e6>hYhWuiwW0v7VfY-L7 z%+BpV9h=csEDzLMdW+5|Y9DdLWvC0gXZe7B_Z%190KFro;JGs;1$6;7E+U;`faiYQ zj$Mm}`jndywogAp?(lZo-Un&u$9U%)(-&Llb1urEwEGeItdWiC)xlAka6?lTjx4-a z&QZbqkxH-RQ^58AXuYlJi39l#JT(><5B90@P-7MF8*HnB4GUp#zO(W6d>Zgk8@BT= zfhSE$K3SOqa(VGX3LJ7re7S;-W(_(WUQB< zj?$tI$yh^O%C_C*bj>HwZ}Xl(kg+iEi(W2wi%cQ!fbG+DF0ij8(5Isi>`!-%%TYc! z9{TV|WEAd85%2e^DJk$N+FQK+pqCni3EE>)q25WTPgyz@!{>k(UCj>frGPlk^Pza|Fne4leTZWVi(RZ5f&N0+m6sXAfSZlpe)K;K zJf8(4uK@}451jgTi)ISqs+*3AHdP2WJUNiR6Q+EsYBR^^_7d=l_G65g-7 z0S;Ql-MwuH`t0NT*V|&lV2@eSlwD?_zY(jzEq0KnFE^=p(?Gr~huZ#_!*MHMK<+5S zReQ$OhObY+`d<|B);$LK;r=h5(U=1dRz?0M9r$V|GW?~p?hv2*RDGtx73#^x4i#X| z;8(oi@cZfsaZKtP5~9G@h5Bja@!WyBkPSio?t0LVhi^F8!+3TrMpz`aK_4FV>h<|R zM;wJcpQ@`8^ts?aG?KClxPA75k2CHq&Q5n@YK<29m{?{^D){$aKPyyk z!1%rw=dN)EJ*&mAuZah8G2n2FVEQ%S#OqoFue;$sl#^tgegI#bG-Y&j0N#IO%8~?J z=2f&g?@|N2wC;6G0sNtpuHKv3et_?GhoFHy^#AwXh+6>r9Yz*j(N7KY{($VR3-H|c z$(_w+pkGErd{q7})ca*D6>7I%1$(j+wDAyd-?q$~XGstr+sY03(4h!*22?BetU+FM zf1f*qLH!Q?^K<l+$7NZrS6Ok3WCODi8X3BCyP_9r_2IB28L=ahVPF^W1smW;`*-(NuXbzE~VFqra+xfDuKj4_zf5R z-ENP9J+Ikn^OH}9Jf$iFKAzWr0|E+ut%Cn^T*!(dTMO2|kkwQH0=%ELCQl^=b-kXu zxX}&3h37k`1qh*k!Dm}ur8_WAva(leuKieg8T#v!IyrFw-<{xfc)6$=)?=02l3xPgIH#%6 zhkze$GB??Of%C_HYwPk=Kz-;nJXJ&Boo?!NlUVEGUGBtsGCJ*0Nr)GeEdy{@9 zzzp=NY_`fx&;#`;x;OWLXL7!KtK>Q0=J9F^+R^u5KRDTxlZv1ZkZ5K=KExpg z=yTRgt{fY%Y=;w6GFRLHdBS_{XvL`y{FH~bqY4MC^UC9*jC!!Y31jE)wHTcxrLJ@6Lh?)a!iUdQJ;^R`1@K;#|-lMP0f_p2PK%1}Pu5aM|Mm))WUT zUqF9V%Vl#nOW2>DkLZLA=!G`xUHNAC{r=8I;d9U*Xy9D-DDW)OavW0>l+Z_-`bWn+ zkppgdk^c`d#CJ`ajxOJDhQ86#@*AIEJZEX&Z0xiH-xnzqZ^{92xDKs6E=_yfqQgn2 zizn=HYpzs@=U}}&I@wI}8gQ{Lk6imv;FT~A9}CeGdt9X0H;<48_}+@(BLxr-Q+IZ} zt{P#Bqn8wZbQ$o1yEyR)Edt0tk(!+*WC0u!-lSh}3i7tyhH7U@>~W^_%_Co69_=;q zo7^1$2Yzu7+y?*L22YK03iM*-T5aGu;J9tKDc>`2e4dNT*(P$t-M6>oSIvjOc_0t%I8hGCTo)+R~cxk*`8Npw+97)?j~_zLR&+gh0NKN4}ah z;GffTalUiqZ*enI{uXoK*UMDCz3@sJ^g3R=goF*`p$=~$P3Tvf!BdAv+ztKM_-mc+ z7{PrHrmkN}gghput_O?fppRf9!FM;{CuW|}zn7o4!Q6692Z7$odUdRL3wR`(uaegB z5U((LMd3FD<4Le#uCyHv@xnpUqe0bx(~9VKdte=JxjQj`w19Z69z8p0l><(ZTYu;w z_=!E$p?KT6V7IsLYCXH+h?_SQ>EHxAug<}KPgN57nA)eVc*A(>%CEEXg1wRJZyNOh zynjkqipE*g9!KQc_?j1Z`{_8l?r-29$O?Tc&;ffr+xlQFqyyrV^v;o)^}s8Mrj7{# zUtZlK7m{xUddP(I1b!m)S3Szftw{*+P+wMG>PV>POibHX4u&`h8+V;K@E5VYW@o}I zp?;WU$zk&{^ywX9=Mds@#BrZ+l;-XSUXt<`&H?&f$S!`Y?Swdmm-zzy35fqXm!BNX zdoGgZZ%P$p6Hv!iu%FS9lhH+NSrrT?$ zhByHxM0xKz@VFP31!`}C-s%6r`RXa?i+5N8pDXCit&cwAq=*iFh1)_ufWCA~8EW6J3|S}=~q z{+U_PF%W0pxA0a5etS0FmT1Nnc)-go^EoiD%Fg9^d*ElBjXf_@1o-bOYbi(YAn=8r zU(e)|LOi^;c1%PJ`b@8rqN@gry}WAx+KI~6nfR%k34X|k-a33u?BhjAit;cc?!m< zX{KZq{LW2{&%UY)kY^{gBd~KC>VJu4pWH2g{Ez~gGxsx~?n^{ERv7XimKh@P{Z~61VF?UwPe9FV=yh&vULbpGzUnuW7->0_5COPfNdH5bA9+ zam1b4An&+|Q%r%tclD{Bu&8#xQR4>|Fagi7DS5uE?-Jlbw-EV{{7|>4DQ)p(5csCS zPs2=BD>}bsO}P#HTqQ1a7I0)bfr)xO@S5u9 zc?O(-Zw|Dmt13GR{!1lqZ2N7%AKRe>wV=OT$OpB49|C=W*JhUqxOKFqZ|Dm?@b;ye zclh2r;7YpxPz-P$_@4@19l(1rcs~km-h)17gwF!%E`WXCSbN9_`dx`Ns{{}1QXW|+ z>CaD4Z(%z-!W0F5Qf>?PBKYHaKW22CVZ2N`=H-jMVciYx43R*5q~Hr{s3@$@yl~3f z(eRwTg|^=}4S+9uw0fpJ5Bi}6-7i`J9HRZY{m!ln_#Lrk9HC%8eH4XLQp=%^V&w55 zPq3rp6VF?;(qVrKw;vT6OAj!W0Irxe-f1g#g#I=`(NCGd9}TE?-(9kXxKcQFUcjdmyI(85Bm;gDai39z9OOxnlzJsTfH=6ApAq3bd*BzTDbL*nKb6IR zoCNe^6!Dp3y6WI}nl-u{<%B+PZ+?AZz-m|vCDL_1hlVZ=TDgwU_=%g?^Aoe;N6c{FnJ z46K)$X}-sofWPK9tHb+9Ymd{S=CtZ_gS;lHl2XP%Tbw8lzNO1!*x#Y^#a@;P_#fpr zKC6Nph~_+4j3Wa-P`lFh8VUI2JMsklpKPIzSaYKj;Jn`}q)A^x0k4M%&fhr-aRhk_ z(f2T(^ym_C7>q|Vb(u5AUBEr}IX`;{LEeZ#?t@z;4!B`zv8bD1-)5bv+?mq>j~Y@^ z`x!dmxNQxF4FE^^DZkS~9{{(^-kCe<26@T9TBgs#b9Z)E$Q{HJKd|uhUxwppZ@)A# zh<7zwjq5Lf|9_q8vc9q@;GEh=q)Jy|Upx*&Rb}v_v0kg8D2R_Asn1;Ng8O-V+w=5Y zknfJMbBswBpkJ{KtEv_QnEm*;dg@d4OY+x$N^fKwkt=a9uBl zd8p@zG727rzRXO7=3iIsa8LFNE?4Hk@xzgHi%#(GJ-L210xxA9SD=s#*ActJ@Xn1J z`j$-Ieb&DMc3WrenJ_)b2YjrNo@b_M9^_K7f zE=akvc>%6Zm#Kfe0)9_SPxq<`;xe?kT$Mf$KgFcWWHXo`pT5E-R~_m(&Y6XtzYIL~ zeg|bv7_4VEhDJXFh>O!oQu*<`1fJZ?Q(hjPdor8fTCfD-rcw3;EVXk>lS1A3O*roPG>-{B_*4Ds-@~&LEBS4)7pqF9`B~0G@7X zRhid<=k*te3y%^7e@McBJQQ%0RJt0$x!+K~ZrHiZ^$r&Sxqkc| zrzj0_pQOH^lL61ipD$FgsBe$^e4Sd05b$1D+-r|EZ(CfMSB#lH$eF4#zO4x0U)on8 ziP^A@#V0@RljT64ilOOuo>agu`@5z_(mCKxiS?>uX<(;g6&&PJp`V}Z&4PeE=&v81 z!{xbSkMn-#(xn6Zj=5F*VCXBrp{mx+;&Y&<(W7tD!O%zhoMZQdBH()biDzHIe!F%G z+~GLyh+|9`VHja|#Q91S(#rwP)Unlyd?aL#t4|)$Xe6-5o%BdIz6R^OX6?O<2-w@- zT~99fPQyNfOdZ2*5+fak=7e9#1@kK8d4Gm*qAAj~Vp&5C66W zJbY_s_Z{@tBi5N8$_Iay<5#MMHpDkGV}hi>&Y!W)Dy@cfF`1u}W+`qD^~97+vqO;A z`o+(b4%RXD)b~elm@RH$`s(BX56#h7!=$GSIP|M~?~WMs6Q-9G`w04kdf|rSqO=1} z=UIl79r!alHnc}y17Da$_BQ-!I_zghE%pyAtvWzcm*1l!M(nBHmaAcFSU4I7AMPnlc&uj`siu8QSR3o&ovE zwl-1#`?J=b^Q+hb;)i;#R@L7_UfOxKjf1{obc0;gF8lU4U%BLH?MslSd`m%u*#qqJ z#go)@pm(N~%o5Y!@AO!`-PB)$-x;^C{0RKSMR|&b59`pMtbc+a3VuIrShKKv4ET9& zabHnG@WYKxh?YMGf1lOck=qLLtXhXCn8A)!94efy%5cE3F@%qJzkD zy3@jEVO}3xXH^-3ejfF|W%Yj8;iL=N2~A-cbvij_iA$Xmpn7<`}Ou-OBl!{ol%Bd2FTfK^3wV-1ok9? z>1mB1#Epk}HPIHtVFeUNUV?l}pn#E<88|PAcu@=F^hH8rjUPNGchkLWcgW`=K3@TtSEm(%x5r;>cQt@pS`uW90*hs*2rTnR) zbHQGk-Kyfs0DPMEaQu8b;N-D-G4l6Okazm|bx7w$=+~T=yH$8;>O9 z8o>V;&p-X+Ch#1Oh@NY*`arzR?@z%mP4X4-cSp%*l6n2>R1vhU^O_uzo(ieNgQPdh-gyCtNr1X+h*tH(DTX@IfMj z!Y0%)bhlsFAcwq#1^*pKs4ZN9*h$RFdo-VZ!p+E?p# zCsW`d3U{xvW3W$DbRX4w@KY|RT+$S|`N`gj}jZ_Rf=-sX?r z56`*Vb3nGw~~qH586j59$R(ds=Js;W-Sge(Ea1_(qFf zA_V@!hO};m4)j+Zjf6hgD_b1yiMG`k+|Q(R==0Y8qaCl&Vq&KWJS#Et^L_HiI&*9UM|Ewwpi8yn(!nlknYWF)>p|W?J-;ap@K|Y#e+tb3SIVeP&gGi| z-xHypFbRI6Qv0 zt}Og&o*K-T{q;}3df}*c&gac+C-hnT(5D~`$E2InYqSCmxcH%0ad`*xTXuNc6V}Dz zi(lRsVBNiL;?8>yaV;_(_RoLHAWphHNsy-mc>vT+iq2gy?}@*kJnn@)=z9Fd}g(wu|1jZXUGe$b=z zOK}``Y&zHQNZOg^yMRL36TcgnMo*{T<;t39voA#=`&C-CR=-CK@a@B z;Qs8#Tae$H%`;!U4fT)O@!OYTAl|?-Tl*9Ahu)<}`$eEn{yes>t`7tLG;+a69OSWx z=T^A>CBSLl1ZN$=Pm7>xk*p8^JW`%H-f9iw|8^`aq8fM(Y8mzwSU+-pzpAo9UrP{+ zZ1s!*o;c=%lhp#ALYey<;|197F13H_5Zot`;tfrHkh7FK@4^yU>~U84%YK<0_PB(N zBk588_BhSi_V~eT5HH))iebMC+ zgV&$`a-#R}D30JA{0J{bJOcQi z82AG39EfrE>3%l&=Pgo9rkypH{S_}Bk>M+hbFiAqZU=Wijtg@=d#x1hlJY*+tZ zzJ&iJe@|`i2pwFU^zz?hLw)ch_u*zK8HMmb1mT77_>ST6{egQtkf8s1{rl71lmCxT z_irc3|A!$s7_)yr=|5cQ;3N&}Waj^6UI?fT?&;v9f4=^n8Db_F9XuLYaWO5qds_G9 z?@8R15!bparzkEjCoB{8ui5<1BYQA4T=)N;nn(Zj*nbbnU+-?e5VAWMy8rzB#AFBG zOel9xyysuj+g4h%_rE;C!B=_y>#Icn^;Q43u9$i7_f2s9>yv*QFgcO`IrOAQL09~9 zDcli$`0q;rbj`u*>{6TiU$f8skCky>LgufNY2k!}8UJ4f@BiT&1`cs`|L>#o?~iat z__7ARPS9`W6o#pBiosA0Jz8NW^2?Yglyv%>8 z3jAOAc!&O4&spSdKmJd7>i_@mQ`)G(&SYv9U87OBR!TLCI10Q5z7NcxfOiA$q&_Sl z66Iae%!vgw`Fbc`vwRNe`nU*Xt}LK$)919fFD@WI&fw2pPiE0d(F7BZ#wbd9PsY0- zKY>Ce4)@gaOrmc0tuwMI)2Q;&<{)GJ6k2}Xd6?<)1o}yH-~9WPNo3e@yQooZ3b||V zpW_&sK&J2Nd(5*(5C_9G0zt-J^ghD=vK2=+66+#yk51}FilT!BQ}23^k88G+%4uMW z%Iab^aWhCFBSdmEV+zqJ^B1MDOray@-(TK^@17j%AY0 zAp;+kHAn?TidA_{mkd_Xri#5#0UVF#iro8{HiB4KDBdwnk0I$9S>oZwek7e|&e?!Q zQ1Fop4{2pbk+G08J)`jyy3ZCpX5c=93dmC!Q*CDusZBy&82$pXEzl`^xv+#B!tQ6% z*)F1~K((U{M9b*vUfQp4$|cmWQbfY~atS>Oa$Q&5oJXvm(({qV0`k0}64Cr}9uZWEgTNKKy*@H4OFZX$=4Ir^xi&RU# zPSnYuetrqR59!&NP;Pe)qTi*{@lDQysO%;e+1JEjRKT-B+4gV{MM*Fia9NKbe)E$< zqMhx?rGiuVy;vVozS}~CWSY_KG{OD7omx~vJ0!_fiJ_a}8G}k54d}>4{NMF??P&aH ziH0TfgVKJ~?3Q!J{MryANzRjD6YNEF38y9at$R?nC#`Mlqb_vw zsc=F=|1x^aX&@!Tyn>$TQZDJ1EF-M{^}0^Y5}HnoiilWRMs|tKmZ@~hC_A3MjGJKz z_3RTjh@M(Oq*C`Q6sMOFF7y)T>a%6kA{hMo(Y<+eYR`3(VPqaDP81(!2D$KyE&n#~ zeFoh#x$mAkJcr1Nh_>3&XAtWRBzb~+6-^ZISl(<~M$ELz#OX{c$lX-QC@f+IMYtL~ zxQRc8mZk*AT;)cQq3Z4Gl;}|;6Ls3h02@V-m2pY4C8H>h@JBK6+i{d!;`hhNWe}+j zT)ao@KZOFX9Ys7Hc~7W0zF`dgcdjr}rDDrp4sY=dxM6$>Q{_``zL>9(armKp z2dpVr);bxN*S(w@nRRq{T=+-SDlDOTsgj!wFQarPZ_1{_92q^ zC#9c1b)fG(rStQ9UC4@7WW+hH3E8Qo6A|J1(Qua=YmH$ixKUJ=IX%EGyMq^bk2f8^v)y-xx~1UN|k}65W^w!y&$Z6Kl-3k2La)N)k4IpliM6y~RH|kzPu_gz^>8mkOnKs*R?Qq+vii)g=DWlJz)4ehd&Z>jUGqSHMmvIL&3 zqR;flOIpg7k+zX_K!e^Yx=FXX^fqS+H7A5FjEJqFgx74Vct)$}?ZYEKE)TAvt5*hm z)%RvmaVD2()%6i1P*0#6WIBtm;w9U9uTiA?B#6iG>;_WxkR&bKT}L**vcjU)*U--P z#dplgD~LYta;1dlG8z+H5&wE)14&f!DPMqj9d3JUu1UU#KACc~bQr84l-_v1<90Wq ziFEoJaC`uLWJ9toFFR58$@eO?%EKr_NkQ>->L^-_(_%<(FQHojF(1UsS+(q6X`f?kxB|1rB6H|^_^Y|hL zqdU>;=a0gpPkPZkji-g_zQd^4qDkc_(=ZY!SZMLf>Oz4%528(qy3p#0t=uouy-4pk zwX3DT0GjFKX%N91L8OK&#+1Xes7|5LnYv{b1=0?YlJw0YlC?WFq^ff$r?!82%ykSY zDHB$jGc2QILDz?2sf(y&1F)T|r{Pw+SuTeTTgE^!Fq7A%{; zq>iD)TQi~tnYD=DjidI8e+LQ~U>M_{XhfXnisRyMwISE*J<3GvU5JOqG0~X429p*x zZ9ErSi$(=&|9q`5Y^H-=Glbp*woJ5#7PuTgc?X9AU& zRG-v7KZ@)L-V#Poj-hA?wvk+dX7uS($k9Qt6KNvDKGhtB=<3(nA8xs0=y1vAteMs< zB5D?UGFV%UrTX$0w{JB@_ksOk4IrK`ST7! z4&Si{!loKN4084m42 z1pP+UcTDO~yd&?}Q-^WXKr-IelsSl${Tl8ly&gsmUmp7J?D(QNQyPUDDL-`YV;))I z6Mw|R`{{*vvOl_6&9CjS{QyblOo)nJO2Td@Q%AoJe}`T9L)ZLT5hCI1PN>jR5k&?Q@$>^7h$W8LtByau#V)9jTdsvW-fLA5Y6j()= z@bj@VCl}Gx9Br|P=4DiKG4L^w`7G*xM4fCJzJ#_ry>xyV%^*p-2Y%0CzJ2j2>kkoC zV^>g??RaVxrnDWbvBbTC-uaF`>B?P2KUfZ%vSsI^`(#2_RIe2xtC|mSUl#I_C+E0~ zQt&Vesp&z&dzsj}zWkkLY!%tszZ})aTS3$G40I=w*3e7*v2#r$tH?qAtiL2*C3fY* zJP)~XImW`rI%sKFhDG>%mgnA{N8aNL$8SzjmFQ?A8yCx~yT6Jtg42Ic+hhgS^!A6PVGf2ZJ$!m4 zku3ugpnPZC#%e+sZy>RPEJfXDC#vSJ3ZmeKw*mZ&hXNmO`CI>|zD4EZq>o4hEUL+pB7 zs_PWvXz`72qW|k+3`OVIsNFBYbb8R7ltc-3C`zMy?Rg1?&s=et?qLCzeChYGb)ikf zw?$4qIktgXngc5S;BO*=KouqmmDzC8F(37)2}| zC(5A89nf#CM(Gan0j$M2==_aI3z8clDDag?gm!N^>QDXOq5NZ2OVFLt4cN$7b|4cttZItsI>(zoeD9=8Njv>_}@k%u$qj zMuBCLJGw)m7evdkjF#teAKz78M!#y4$@>(Rk&*{fv`RJLm`9?V&DUp;fm+blZqVQL zNj+ZUU>DjalIbJYXVE_IQ)%N*^T^flR~6@F7{}a9-xAIhMBK8y;FiCR;>|6W?TyyZ z_RWw!zx@$J68=N(tm!!N8d-7{>m5PY+pm+S{~Sf?%ZIP;){mmL+cPIsizd;{+j^lS zcYdI*q=jVjKQEC;-&F;cb_L9~a!k|s-D5=lreN^ci&`vuY|)8~xej|P+pu>=w+tgb zXEiteun^0^(Haag6=1~ek-pjEm4Jr{we%+|(5d6?6XOy-NCl7ADk=v`OP;TKy#G{y zEekpm+dE`q94oJlsh+f8qeg-wos`X38pG!->CQGR_T@K^2gD=D>S`gL&E5#QS@iu_ zG2Jk#>Jy|qr8IzQuc)uPsKG?UB(*BpYcVa^@g@A# zNkrt>@vAa_9_1L{k`i(oL0^b^N*bib(ZJgvlWEd1wAwQ`M|^c0RT@X!?C2dwkzFs8 zA3OX&a`o|X(&r12eV23A59JxO7;{Tn1dhZm_oM1A*I@hPy{YLLHJB9}p%67wEw-de z2Nelb7>}y$l5=Mz_Gi@lumw*Q77;b@rem=j3zQU~O<@>D_|0EZ!hHHr;@bHAL%&9l zjoj00A^A$|xkAAYX}x76)le^uyE}u(PRMk0RF5H#2j;Nh3)ppX?Z)=JW#p6;^7IMY zJn~xXVpIxPMjw@C>})935V=$&QFZn*GS1MYC{tYre#7n&*Vk3_Tzut0rAh_1{7lTr zjJFKCn)yvT9rz8GxV+Xm)QtL9$}<^iThT1tH{;R4pXlBn2XYSnHe~2_G-v^@3GI7N zDz8i?VaF(Rc_Ou9vHCERP~3I`HWyj4RG(amIEQ3&tCun`*HYbU{gdgKEoaB*De7kI z<>2zU)Qinn9!>P)2XG_~Rvyu}sKipT)_Q9f$B{6LeX5=0AhOLi>U>i%fY?%=I#;rf zpum+UguxOG7GfIek z%NA3Fc@u}>ozg79>UkQ49C;J4B-hK{+-DOoa`89iJ!J2(x36+p=o$R6w(L_1@d?S8 zH*;7mfqfD-e)fodG5ri8|FM{_dSM2+REPDwU>!%3R3eu6JtVNx6N~TEsQc244pETi2+k8KJ|rjh8g#MQ0`6KGB}*vE%w43+&payo@^ z24&jae>~ehg!l-$UbXQLquusjzlL^(kwxKM^4VWQ$TO_yDb|pT#bn*UQcORgg-3|q zR5=M#fC9mx%mmCKLe_Y%=Q%ce=H7GXOBm|DW%R$|^3(+Z>Bbr=<$ zWYP|PJ;rDG?d7v8l^CIm*TxEeAvPjCO!%6t5WChUDtA4w2$Q?|E!JWZ;Pk z%qdWv!N4BFzD^AOVW}&^M4nzso*OB^(AR-qSW7;3(S-KQg``YOOe*i>jfk%pMPN4l zP1_{w`|3C6LPIZfvG0=0_!oa9uqfXheNh@o9kyrMvvO7Sn(OO!Dz#Q*FBC@eX{MgA4MXnZ(@oAXWwHM9YG&1%+4a7 zp3n0;5;JJ$R##A@-#B`8EnS-Z!3@In-zyAqoJCd|Nk?hy`_WT6yz%9KMPK0isHCE>Se?h81gHjg%JuhWWUc4pv9yV2s2Yrw&QfVRP%Py4qEB*!QUr_0+UF>}TSy3%rf9 zh&RLf4Yl+nDnCr0dJg>ddG2$hcO8L0OeoxsXD&eNbIJ0Zs;NjZa!q>ZvNQ6eh>JcO z6O5wveFz`72OxJ_9$D^8FO->ZNBwQgIx=5n-NV|~kUpWM2kYfEwEq6WPyAHim(Cue zu6A8TPSwP5ZH=qwuuJIJn}=&?A>a4LW8g#O-B16Hmt8~ZmJj_;F|42rhWSxZ)-|-U z+hY|5xNmbQ;_;d0MZ{MlU$-f{h)CW9GoLbQMx(l4^v)(%V^=$|d(D-cMI*U}=n}=%eKM1(f6Kvc;DUay-)h0SkBz@5E33z_hBy0l!81rrxx$j~+)Ec$VE9LQ;<&y{vETLGN44KhBzGVRdFTTz)q) zv2bc#i#3fbOo?wqJRIjTzA^qAsUvYXhj6CxC(gO~6^gLkqy?S*q<{F!# zKy~&z<|CM1T%MST_0*oUJGq&T#av;}T(dC1_&JB^xhuV}X1Bq}jRJS<{IXTnVwN{{ zyLBc0FuftV>JjwhafBMuHgg>>b@jruoTp1CGkvk&)oJY+=5gqV%dgWhM8U{CH~q2k zM}H(j`8ey$Coe2>R?>7f@FS-6;ZsU()<@9aO&N`pJ}50Ba70DT8=bFC8aI{kK^i&e z{`NCRWOmQ@<6~O~B$rzx^I=dI5hiBE*Sp)J$WQ(=xr{Ez^0Cmm3$1l%6iD=xWWjg%VVDq4}KpI2emr6%N3BqX1~(1!5%LlsUD zb|W3(Uq>0Y!5`|0{`g+M7Bv%PzUCA!L%pZdc|LnqpaE)UkI%GL80%GUo|YyLY^RP= zRMpx6J8zg3K^9ttMB3NA>j^rM=UHxp{D zS7YLQjBY=)>oD2u6NhWsnh@u4n`0SD(pHLS`<=^Bm*NU zFHV-Bw{h_@eJ?9eGbQQ+k<=3&fW zSoHARwGr&9k|M#qp<(Pv{}Id6*~6Gp-qmQU-B$GBDPKZrZYwhGz5AA;eh!ma4-L?r zpT*`kap!(@OkwvmEg7!}RU>*TeYUpmjffq~(Qpl{M~p_lEvND3u@i|lw*(qto_5CF z#Tp?VK~42p=;8|U>Mi%#GONJIF~Sp6eW_Sk+}r;916kN84b68>t4vI)@~my5ZZ0<5 zBwc^R{b431@u?5nYJ+&&HU% zrOHFNK1$6i_|1snf&1NiK5ZyZ?FvtLM;CH4rwJ=SzhMM% zl43G8x!C!~2Gl|1Mc4;P{GB&*g_zs5@l>m0fNOOl+gF53vChwDpV?ZJVR!q!|H5CY z!?fGO;z;uAu*@`3mH&&O>yF3jjpC8fP-#gSSy3V>Lc}RFkWisgh>)LzBrSXIz4zXG z&tuEp*?X^yNTPoCulMu*^}hGs=Q+B z3)Hy>qh^Xqapy-t&+$|7+vaY#{NmkGGifcX0R#UdnE}XO4`3+~$wag1k=T@!91Nb( zQ_1eiL5=gGx`F~t@UI(&ep0mq>4D*4xw1y6PpV1CI~RxQi$*6xb`xxzPeiOda3f)hOO6O!+f$$lLjEeO`m-h7VS=UAgs`vU4(M8lv zfdXwC`8H7db+GL3!AAHg&&klPfKc^2t@7^q0eE_D&^6vY0A8H`I9lZu106m_23Kg} z;K{#Q;lc81I6&8VbJ6iL$j*e~8nr2$mAvOu+F=Vix<_E4`8rs{ME$o0Vc~SH+~0uK=tNUYw@lZ4E@QloFJ=%o$Z^m z6ipQ{JE=Pn%+&&Zm+X=bcC$|uxu!BD7B(g>+Ly) z{cTu-k0TBLd4tY&$B5;GH`Kp1DPmIa1&USybGBFCk@x*u-mmSQ$o(=gcH?m`>YH(Y zj!h`R{fZ}k(ub5F?X8!T!}P@{ASb`amZ=T$82L)NzBXgViqVf~x<_!+sPvBD_Y!o` z*r2`0l7N-;w@z>QwBd370KQD^He@P2Z91b}gKjHl;#(<8u`_`D>PL=o0#57NK6G~+ z8ssmyJyEFv&ZNla&GmJVl(BG1IIjf+$a5ywPemhD#g#6HvOv@kKC3XJ6MRVvPJZ!|9DgkXc?*T$dPQD|nEajST3yf!O)*NAD|Y#FczE+J9VK zIL(;5@?O3Z8RdlxbyoUNM|6Sf*+MyHrrxEVK3$Hye-&;nwf2L;I%*`E48UGfPX0m$ zV*gbO9=%f701);mH)0WiEJmsmG(Zq>qg1ITYp6&Y$ORD{Lyme zFinOf`|#ItnRA%>dgy7(n*}`bGC}A@#2m7>Mu-#`m%(LLSLQ1pT)^V(!hbxKSs*Jk zAE@qL54`3bSTz(y!EIa$*$lm_;@`{Ap8-i+VhD!nE2W9acS~Fme&cd%s`>s`O5DNfjW3 z9Kq{$y~SX6(BeJS(F%x-N#QuXkq8AJFD#G{JapDk&+X*cX((&@$DXcQi|U3~#C_e0 z@M3P`K7RE~eDb-^TE;9JgNN*?cZmM$VVhVBy!(z)DL(tYU+qMj?()k;=eseHS)qXR*6Is*{fHF;VjsS%tR4>H{dZGcA{Zq?V8n&8=CrDg{GI*7JY zJ*WGy0!sdw51i)<1;ghNLR4?Ou{d9UH80%;KA-=OeQ8AilS04wx93_w#Gb4Oy%Wa3 za;~e>3*FHn<~{rVS}#a@ZIs(}*%ej`UOnRqc7^@+FZmv{SwhA&|Ec4l9=O4k@v~9K z53D@J>=oqwU?=d-AM=Q6+*R-?z3NqiD_J|-S?*PM@FL|#ibfT_;n+*Tqh5nA;znN` z-dBr@!OR?ix2o_3+ws7PzEVW~MDmw`@u-)#>1s%vhYg31?`@Ke!g~29R2#RWQBanW z{RUq+9yzNzVjCWVik5M6wHlG=-)!@rhWZqqQoFCFJvxp0pSKcC^QSRvkC`&%wK?43 z(SADMZI4Soxr9D{vBt3vl^5+DZ1C{P10!zQAdI9WJ>>Qz0J+%JK5UYUm+WD(#wiF;xThz>r4F+!ti^&}eZ%KN=N^0^@Zgx?+c&cfbMc{&c(8tX zCXUDEHRP? zsZxs)AE#`k_fNy#H0?q&j|mtoD2wHCjYZNF-+j8xnXq$(`d?*@9avOeFUX9Qgy3FL z6m9S)?!QRsIhh0y$`D8{%T0!anaN3=iZr^o96+-=q zvEv*jmB9Pl#okw?8~3vIwe@RtV?*tY1I;J8aY&%NU%{sg8#AKqd+t@DX2T`yI;4&k z+Q-R=%zJ$7H|d0exfpz|!u!ESAzppaJE7v8fx0{TuZlip;w0^nfA&mS=)X+z?aQ?+ ztUr$~i%uzc+QF7t|4u5lygxKewVs1&CExSTl9i&H&gY&j&LX@N(BSr>xQ;xt zqis)L<>H!9WJr;@2SV)$F?;bb)G0Ojufu;3NsW;5=cgf}{pD>rb@@A{{rk=Q^n5f% z4+|QIFcLUz>>^#z`#2m_GvD@ooQ;Kp5kf4Fld)y@(U_)8F0yx#rIuduKre~7w)!hx zc#~{h(pA_M^A1J`@t)LxQ_lV;X-up!!0jMgqhLMWP`|2cK;edH%s%($RtEMi-87rb zNyBFK@buo79#F)PG*ma|2CshJ{!!@f0XGJ_IU~1cP|4%MR6{`}mTXhEbnLG}ZOS&v zzU4N2SzlIjTCp8f721!O`m`gHN}%H&?s_ynz%nm$as>Y=|Mb*!9Yiu26-!NPVjkQ} z-A~s$fsK|Ejv5*hc%|un#CGu%<`~sE*?T$Ty~@)cswbRalQN5q{b&c0c05WdFls`T ztPjFELBWu$wn`yB;{#7UEZT2aCqt+xhs0ZwWQaM{yY#LuA5Ts7{UdQLN4dkJ(PKvz zF;L>D$MW$KlzPIW@h>A2&$ZTdd0Ld<^+mY_y_SABKmJMiR-zn)n0tsMnj2!!KSK`d zuwfASsS!td+zZUujH_6DeBtU~e+&69f@i%vOhdXl1N4)3cwZRTA;qX)zhHYiw#zmg ze9|(F`~1_E{qGdxrx#Z%)F<;%Nxm{jZTo&0EZSx6xmY$t>v66vXc}AnO{E1jdX*!h>n}B<_Z<4be$ifziV5Z&s z@koC|M@;Q)2D+TKziLhJB1e9IzVh`+F&=EYe(T=vT7-+_e|^4}!kI>CM*5`+e4Y5& zYkhwXE>$?OQvS%smABb}f1I2!ZSvPZn3^%3av8jE>P0e|PM!R?&p!rTy8U`9^7oWx|!Jn(|h2h|4Mm82Si(=X#`tj`m?j}HCaPgzBvD0oPz z)w}^K$0iaD-&7*|74CZ>%9Z$o)sZ}W6w~LR$DD(V ze6-*5jx>PsoI`n*WhF#9>=aIUHel`gwMz;aO;|5p`TEfBR*-Q$*kG#fp8?7QM{E5Ey^=F)Cj*r_HP-TGh`A`xQ~0JtB~FSg?h_p?LpH6m z)+GB&amnAdT~{v?b;_NX;zY-g$=|}KO??c14q3-@YOX-hhLw-wVd8pg+{;QE8qqI; z^rW=UH!S5kn@{(l89VMiHQ%~fi){un+ucW+G3}j?{<9r-9F`znNBPl~z*~0G!Y#W{ zhT|n0O>Gn^rg)gT|08f|3iuZ)(uVKK8z1*1=i@oU;iE2f8K@VR-(~T<6Rb)td!8+q zU=UCJjnXSs=+gcsy50lvxmZf~sm5&lc)@he#01gEIY`%OxDF&Irqe$%55S8VS)3>lq&rkiP3;PJhNCotnAe;@I@SxZ{TeQ_$Kkr#)eD+V zNG5vi`1J7wTv{$)DL>*0W=^`UUr2)?!&^7`8j~CJ7xSDy8s!0ogj_=qEUb%f%DamN}O)%fZ6vt%)0}`{yLfA!SSL2+4C8HGEvkbDMh9K98D|0 zj411kv1WK;ZLa0nRt+L8XS4Gk)9KE=fQ?bI`z^zA z=z4HIg|TWBH|t(SU+*8r*3AAR&+k{`N?g9xC8;6;hv9qL+gk-^&VD%l^hh!6H+XWJ zUO6AEj`-xvI2XWp`4WwdL?U=n^2i0yrvrEL5Ys++JD|CIrbN=s%!84UdX&r!Ev9>{V(i8$#AA<`3h) z_MzSZpUT?yQjE3Qd&%!;C0ra*d{rZ|3Qn3jd%2euf%=W&#fl} zf8HA+C46AU4sSRA8Yw;PgxdGL_y&mnM!I%^t3II;WOt^&R)$sKz8T}9&0RZWz7`sD zfY}LV{}owVMigOGlFw%GrD9Ax?cA^4QG|*XxmQSX48i}A`O8bIfl&FiwUklT2AD|q zH(p<12bBf$717Ws+}h%Ne|OpzgCAQCd{cI|6qO~##9vO!pvzS4K*V<3vT z)J97j_QnebUVm&1_=sohFZTKuXraAMiMHPGC)E5>8oMup55M$#j-B}>2lOO==hpwU zqL*aqFS~!O=#cs6uNz+rK0Tps{a~mXpPr=t@?UEuK0A0?dcDRK7d-_!2aj7J=ZnhY zCzxBY5 ziaq0OC!UU!p-p>;^SR7M!e{b!J?B9W-fcVaIk~zYkJNu7(Ti!v*9s0@Cy4*c$Gv1* z@v99lFZY%NZVcnM+mmZ9922;&`Pt>@u?a|~TYvn`z7kT2_to_(SAk)2@~6{(`oL5} zX~kr{0B$}CyTN|g3I_A`F;Jc6?m)fBF*Yw* zbWfk6As_1tZG#%N^B_whSXnhAA5u;)r3n)JY~fCTct>G52s&FmnJT_R;NuaB{#+*b zNigQz;|m?=c=jiM*d~FCdz~*jLCl*ruW8bV1&IoWeK{gGJz#{dKO#@e6Lc4ZHEPV; zP>4LM?h|tx`rL27VcXG$t&u6>hG7MGQi!20Yc<#0X*F+__yZq}m5Z7n9% zP@=DyQyj?*t->}<@;#KL9q5p*F|ArNgFe|bPsRe~5NY1C=s%msey0eI=N98=*>`F^ zly(#?o-e&LkDn-i?NuJ$s?SpN8#ydU<t#^aSd+|R^bDl`Lmt-O1j0cXodj8Pj4@1dZ(Ru5R0ZhJQV2TWhK-(B^m@4kHBE)~N;tM*?jgZ@Q`_x0{PN&A9S|DR( z+zCW=)!ow+?4J0_=EAmEN-1ufy+Fik`6+8LTA6xI@_S?3Re`=kb?Z3aQ$^2U{wY^jn=*o{vYhEj!o=Au>A^YEC?Y@ArQJ9%1g22+mopXn!= z!n?O4E$CCHknf^}OJ3X*k`|ebq`sa)%~P8{+4zWl#3udl0ofs>uY0;;^!XlC%{-RW zmNP@araOP6WJb{Bx9A~bl|iH^W)u#In8AFm(p2?m0+&T_y?S>L#AmLj``?+yk5i_Z zYS*T*#>$^hle!B(7KW1N1hwFk0+|`^)h2Yve%GfWk&Rt;x<;gN1xWVrQL_BWT)em| zF1-073OZWu-xl47!`hfohEvXYz_&Ezcv6Ab=T_g@gq*6-SfL{3jMF!CJQ|Z%N$^N} zyDjo_RNO)0l*w@OlPcU>Bk=C`PB~_@y6oh6c45v7)2n$K-*JTDh>z6XZVbzE<^9O- zjS&a(8%7fQF-C5zFm1jQ$MdTrs&9>e4}%}qC|57sk*+>Ol~sz4BlBByHkHT)ek75z z9he;>PbILY6XRB&bv_PC!grT%meik0M5??R+kn|bRQOqb_VtZ69Q>OtNwHmtjF#lr zt`Ypi-~Nj>cMfG^Ot1(=A=fbSz1D1VN}I&tc@y5pGyP~Mai9IwKZ55G{246fJ%Fi7 zlW?d$6zqzl;qDPnsCbjG@!cj8I*kfgmQT6ERo`K)`);0KGg1EB`?VwdiOTw;#o&dE zr`^o`4|!s^xZ&gp!%>VZQ1x~>9gS<__5%*P$#`HQF265z82NjB^#d(>Q2Ps~VXSWl zikz0VIBQ&mbV9c!Pr7ts2HVG?xFdtew=DXjqt_OCXyffF&vjtS{eot3evB6;MzA1Jg+=Is z9whwad(up)3d{WrbTzTo;4@oCombF{PSs_iySz@wbHTW*l;DqNxX(rGZ5x5*7d7Uz zKC$@yhe>|DdNT&QiaNA}wxL|~{kfbIt+?COtHFIJ1?Tn2xoR~K8Qs~LYJ{uN?tySL z?}tiU*r_XCsA*Y>&C?fl{PtDg%S@)U z2drc08#@-#cVrAxicT8KdktZ}lIo4u8pCLMqEv|cT@z+pXStv3{tZ3o{LGFQwjvp4 z!PhexQ`PnWtfJ zSL#!j^>`GxNoI~_QxxHcr!spixpMHl<)gztOtR3#swhnIW-6-uN7FG;(1Tl>5)*lm zUC3emBdl7y5(D?<2yHZXV64r3D@N~Z=*=rK+40PQLtMl+7mau?#YOAc z4+)-xjypJXC-9I!C-NQ4dDTt$COedidnM{7KzU*F59y_T(4*?P7M@7(S&sU{Rdr1` zpRj#r`9}@G=RLgf+-L-*^;T7mcJ!mKin@%^f)x~f0udKWC&;4xqFY8&ift>>)!xZP zxV@g=zGl#Xf-Tu2Vy9;z@imvTZh19^yxK8mUZ}z#rQ?wS9M#Bqs?^r)b{qixqGvFBLKS2~7KWNTadQ+1&ljo%Fm zeVMPsM0wGq)3Q~_A*cFE`&I3_q(9?*LC^{wzgl&MyD#~J2>i)znIuTvFZJ3Vd(-snUo@0sS^tG!6| zNNY&@MlZTY4k>rW7#pU{q%hmYTCcE{PK@}>k+`Wfh_A?McV7@f`k5EP|P-)q$0Hr@$=pX!I zgZ;d=(OtuX1aAG~L%G2?iriA*uA#O8YX+-Vv@>yNHO!(JXy1vQ8H+rmg#*}GPhD#z z+KUR5+mxqTJW=5lRcPQ12f}x4GpZ>l13TAd7kRi`V4wfmP-Tb@%ItqA&df{reEp76 zmM3_l+`7_-u1rfj#T>+a^|mWqoR)6Nx0=ANcFIgJ^n`>KjyAJ^D%=o!^!gC7ZX2(4 z$#qzEqGE(MURmmgH&4x1E|_;hh5o^!!+VB+#^&c)*UP?Gc&ULSqp}lONM0_=IX2_? zCWSt2O()J-&)+fPYR7sTirNcryD%ut_Q)gGI=sh=208?O;>2X1agwhaEfiF9&o@*; zUa~4n=RzF@7HE8MY3swkJsRh`Jt|Pq%H8B_WHtI*XV!mxL-b{bV6Xe$I*jPPxt5Vz zh2KO}(lwolKI|Ju)_h_S#*3)BlChTHPJryw2U%lyUSBHD#BKm154MiePPbuEZx=v}V@l-dl?W~5BOeb0{_ zIDfw#8$?b2bZB+pp^u99e-Yn#+ryrzKkr19GzxRIxlSAo*6^(M>p-0ii}Hgjoft^r z)n$3j62IzNc)YzGicUIF32DCramsaDj3uWVS^A@n|7T$eL(V+{^`$W=|73lBLo*9; zt>v1Mlml`Wb}^_tBj!6f=D##=%|I~M?BgX#!XNKc-!Gm}iIw&oUmJ1~(JpW5hG|P0 zF58u_otI5T1J#2D(X-J=vz8;k>);99Ldta}(U~xLlH{pAOF9_%9}|-ki^eC_toAYl zFSJ(j;9LIBDx}$#X;2OF0Ns^!7Zb%~4DIo*5AW5HTN zVjm^1C1&bYeX`1kN4^6#^s3)FP}<;zs62sxU)KEfjGy4wD0)Oh<6q@qcj(abJjqt< z7wJBKa&H^*rV1S&`4I)A69+Z!91el?2C2^R_*j^;V2gNuJq2mV|LVVHNx{@;Bm0fo zWKcNPe81vG3dB%E=vc(}{c;dy^t9zOTQC(?AcCE7qKkela-43e-E*HmG zoAX|fPWwQ&J}4QUfAH3>@=6BBD+wVI&2b3gV-}1?J-G12SlTzE2SuVEB$ICs;7a%> zC0(fw4C*)P5c94_52m1a>5w+u_!p`sWKa0I{>?`8?IO}wo{aG$w}-yc|L(K$b%H~9 zCr1i3;#snLt-qECegBWX%SLS8pcqcM86*)4!|Faud)KOwx}W6GneRk>;&J_JG@FYy z;a6nBuMI%~wW#{u$Z?YYaDb8{M4KZJfA zgR4KIh4{!nLfaj^2+vQ2D4R!7V_=zt|Lk-XO~2RTtE(SDOjl?~(lPt=K71YVM1?Mt^&O6Z1*mTykj)#HoTTG5ZrWnt8Ucg_ilcC2z7 ztEk!?06NE|(qC2;Xs|X^K~7qO-fg!(9w}+ZBL|d}c&0mXC!4cTLazrON4Z2aKIz6s zj_o8mfpxgKenE-KAP4k$9Ch|-y~Nu}aU?YwG3d7V?q3g6G;&@!wO$yLj%2$j`7*l& zcr$$^h~a(>9(<`eG1Hn1TX%o?s}axbkF|=0Qdk_=nMyDeO~(Q~&G;+lw>7vWTFb1I zmISAy_*=;~%J4FO;@3gTJj}ChIFjj~jT~z!qjFYtz}s?fGC;c%9x9H0@~w%5;~U8~ zk5meg?WyGZ`#*}&CDtcrxhDtxl@q@{B6#hITfI5j2A+^CkhbX&RRu@XBs3U6sU!0L~_>zf*r{C8&d1s6c_qx~(YR!8pobt@dG^=Lh0 z_v@n9lSwnG{aIKhaIT$9Jr|qXU+exRbVat#(%t$$YjImcLPPmvE2^or?9+Wk%+0?g zzkhSfg5wfM+n(DDUuz`Q9S?OQW18FU`OYRRKzby3>4D+1kNt|4SgRPeq8JL$qhDQ7!Pu(n#O=a-PsV#x! z9$*=*TLiuHp}+5r&O%sD4`;qv59l9$=bbs+19{JOS{0>-fu)GI((=a`)O~r<*km~l zsRe6{&n?D4E~WU|xrPalwb7^e5;zUDFhkcu@MzNXpH5kakHSe^C5~*}YG}LCyzFqN z3b?4Balf{$0D613hXpPYvD&XHQHmRVqMX%JOAG7l^pb+_0h=OM$NGv?x_dH54N zej$VK2gi6qZp!R`{2Te5@v7{B6g(sUca2Rv z73&YCr$%TdqlTUa^V*jla8W(`%Z0ffI4*Z9i4C+v+MY+ubswtWJa3HCMo|@TK4$B3 z-%|>AXeT3CSSz4w---Bz&l6y9n?0|9aSY5q8H>Im?&HfU8P7(#QIO<&IH^o zlfPGad<^`&_OQO<8Yk-LajT}yaiC<&F{sKK1;bwre;AzyVOws)vW#jBMzWs#5TzLc z9)n8-742gX8lwT^U-z?-mqy(1zhY?FSB_4fRfaC(Gzb8mAK0f%7Zj(aKLI7GUQj`o*m^{n9d4x zP>szt+**d;l;jVm2*1~{SP?ag3oB3-Io~4qZWUt28OnYM5|mPHeY%7 zWiWAmsOIav3{tH;aa%J(aN-)tqp`$l$cxsy6{uGUVFHw9Ym8N3V|UjiL%a&E?`~BY zf2#y{E&;j$={h(xcudl8Edy#vS&|t%D&eopQPBe#RYV?>se6~-0Q{s-r9F^L=wzH| zR>;ECpkj3K*MA&OpgW4ca&zq~1gl?_+)nHP&oX85@(XP++mJa~Fxm>bmo@@IPt^mH z5v*{`&BM7=N;YlUS@;(h^ZkX`BA8s&;Iz!10zG;|ED~LWYau$XsdE;gyKKkQy=@ty z3am@b{w=}&nZf^T+Lxf-^jXxF3?|9sr0DN z!!pwAx3cRCFsbZdc;eGu=xUVP9^GmIY3I6jCITNSiRJNV{xtwChcoP0w1`ZK`3BAi z!e5$4X_`poJOhiX-x5!g&B9UL537pLXMvRJNGu;M;kP?pcIyuF0_Y28t@9=j`+sCM z?SlF!L^*W7%~~3OettidKUOu+S?}vgCy@y;iJQ3KQ3Q@HG;@}EwcxRx=Mz>x0%yv< z|MiI*0h>wXV6>}%TNCb@2VL92d#2q!{Zl|X;3ba93 z%dUWiOgsF%FWETIH3?ev#s6)R&ciGJfHdQVWpH4PWs+xDf<0rg{7Y8L@TJ@7@XYcm zXwq~|oIbD$o8{^T^3-dfNv_HJba4gd-rEFSJwo`a|4LMpy<36AG1uXT1BAcO`%S+R zv47U8g_PGS*5PaL$NO%E9guo}rs@1-DJrm#*?e&7gt5?b{o$|jaX=yQe8blj@D3Y( zm>#nNYm53WLF_Bw|5%FTXv!k=KJ_XnuwR1b!p4{Fh+HmX(;zwam1PLDpw_X;pMy1V zwytMH4uP*@JYNZ#!@h5_JdTS&z*GKjUMtoISC~({?`DpLLE)RJbf>DoYS@f`^%G&A z!65B5A0G^?br_{Tn1fQEc(WzPvykSish^)uG)k5lQ+L{R=&>CJ+lX_w1inmI`NP`*@57lGU^uD=tNW08yb+fwHa?6vUxm+%zn}A6gUEuj#`$wAKz-Zt z;Ezt?ce&$JhE`*ds^RMe@5_Ymg+Z3V&$j{v9PN8aEpt$-emgLJZxncq2R+nQ`UXro zcJndf%AmpLZfl%U1r|z2KW*P4c%JdbWksnflnPGsi7l*0{m0oorB7=DAR8ORiLICp*+;wM(NtP6X)}C!#FoVIKG=7G zM@sPlQHQEBpB>*Bfs(?ojbzyta5z@RY+khtydae3W;F`lJGUre%?R8!J1NLSXcR^> znq)+`r$DfPu5|450{mQg$$U~}1`gbfCW#AKg2>0?&wNfT!RReFeu3mE;C!{RXlpPB zds1jH_RBg5Um0_T*)^D6c3uw|S_6j*Qe8h&R^bg*dy8`3D)1KH;e9CG1^xH4bE6%* zU_&r6|J(eITvFzM)QppPBuxqsYqg#ey$vu^17ZKwNJ1PE% z5cn_yBUR=C;_AR1xOJiush($j^SjswfAa5HJ)r1=Pdm&bN+v^KfAl*)sqYZ9UKJ}a zZJB|Y(3f&8zed4E-%qQfV429Vqf}LDpMuSW=fxL3n&Q^lYn$#~C#=rdD)_tPjE`M+ z5|=;P;(gZnttu5;OvqI@S$(+_%b1G-ANiNznF9NtKhlZ(3~7>7#o{8=aPe1^k9I-R zfas5WI;-$^U>wOY@$D!4RepYm9Ypw@rJe1e+c(;cXB=# zA3!?&v>BT{gSg2wLX$M`gV4{>{jMhJihy4y9I%;$wZV4kOTmOMoptb4nDr#c=cQ~p zO$~s@YkvFr>`vI15xHYGzYcW;(PAa5>%guke>R24Man9qx}@p04r@V7q8&EbnC7Bz zP)0TrTRa#z$&51aF7bL{^{hp}(URoHpBF$~Wg%T%ZV@VTseV`!{qcpSlR4jn7ohjX z_j{&v3n2BaOkCG#0lpWG73%)C1a`I}FM5e}b!A12K9tEG^~x7p#ZD1W{-bIAz_O6!swYA6u>*SJgb6zS2^z z6*3Qkiw4I|9h`@2{J;LNL@k1Tm(il)*)j|=aXI($Sr1&Uh$dl(>4EFH*HieT8X@+c zR7&&x9{5`QkvkxA8S2%JMA-0-g4SQL#eWpNu=*`rT3NA&@C_dkpx7b$nwBV?W2~#7 zNcuOyIOYdb2|9Od5xI){zr1igXSo88s+4vt;+x}4k}(Ku_fkaQ$%_@oh3%>!g|Fo% zsm2JzZ)!4JK0gej>^E;#NRI+`J$xWun1V}7PgitX8qT1-Krl-<>v3G@gEMl68nW_aec4#e`vQbxw-=yKFipa`gK6m#<3xp;dZEZta=|--2j%%6=bSRt3VsOAw2H70^*Tt zS9r8mq5gvpnd)yMXLGwCPvub=t~eWeTaOf@+QxhL$U0@by6%zq^IQqgGY_aWY#76- zfy^IU91{>V)Bm-bc^qhIX58dp<3-8twyWbH&}G7X!mmk+2Ax5M}k@D!hvjFz8+lAaJz=G-w5Eu>#89V2pBuk&nly;+8}iom9hr38qo zd?L|dUIy3qG$oN82m`J&pSe>wB0%(Bkq8@mDCFH~wxRvg0H@fFYBy}gfWgD33}IE# zSn&L**_HTMY$o(c2cJZsjq+UUu24G^$n9rsnP~@J&RV|5JlznkkvqD2w+}=eEt&UM zc7kkF_sqg<8~nL+i#?0*6MXGg30FTn0+Y8KH~7!?!=5icLOxyThj$m*$fex|V2}P4 z$KKXf6y$nktGd*NO^4KzN=+JY{nsAt%13ixdb{T4DsheqIA40}M>7wLZ}YxLFU^3u z?9WJ&vxRt9{?PH?W%)SX(3mxTJ0A}(-uW%Hh1k~O(MCz+e#~e$mAFq=;0g0yv1;}j z_++M7!BW%#)D^ecsLqr_3PpI{mR1y0^UiA=JK+Lp%!RAP(~H3N^Dxuy)*3KBEwq35 ztP^PaSef)Dh&gf1eRt{#Q6FqGK0bN21P$d;S36T@;pQRJn!~lzVD9}ml+Jk)t{a-w zD6%d9jGd)#rJ&AxaDoY3eyR#FE{Bqf=Z#q;oUVwUZ` zIO3g9<3EYXr6|Z^w~jH0A6Zx0f^U=}NwC0yeZ=?1fWrz8Vn*SOq5frFqF&CiNl*GU zjl!j{Y{t7p{$l^iaOClhe%NI!Sa&!%2#=_1p0jNxqO1b%yM0or_*E?RSj$KPY8_)9 z9?8mtJOSUi*H0IKGjF%!Mdb|4z1_;XT`>dinGMO>p3lRGLrW_+fg3m&@)z3^xCmF+ z%7vMM7I@qe9#G0gcn@Ge_tokJo%y4J3;?5oizz{aw5Zye}!x5!3)sm7K= z)xJdNwZ(a}W6aJ}W7OsS>+wH3;l2xf=7aur7|@xE;iq2(-{(1eYVTK|z|NIze4bo-#Umb_a^v+7j z#z`pCHt^Oaa381D<=$V(mMHwOkMhD)K3?3Lh)`0Rq1$Z(F>{>iGkP<+bxulvZHuzOB#d;1BR7VmamNiCpsf z4_H#n((t-1^sYBQr|DO0feCbjsJi3%i(mZsFyDa1F?RyE>M zqwtNB1rHGQO-o&JwM(@d*ew=+f79)Swf$7iZr?`1;aQk1``;itq4DfWb9-eZ07yUidi`J=;)7ML|z>yy~Y?%%M=9m{L!d!p9c9&QDd#p8SofS|0cmPL!7_IUzMC+ zhCGf0${)>Burt)$oJQ0uErxQ_mx66T|G9*(;8q`8=mGBE<@J8!SNBPaU5gvT>& zqBS^F{fIO{x&=)oI)Z;OeZz_WDLU_XEZ;AVtB_PmWYpJ4%19wmWSlezB{GUaqKp(t zp_1&q_uhMtY>vJ6-h0bRk=F12{q?*&FMmAGbKlpwuKS$N=lu!&Q^^+Ajg(KQ_^8&a zQH1cxy6x*mo%YPqv-xf4!%+Lk^zJI`d?lB6)4dH%VZ9c!esA!>u>7OY7Ar_=PL@CG z-GoZ%sTDiKJT{4Rq`h&V6YW&aR2`mJhZ-$L-?%+Q|LaMr{muOwP&P#AS--Rn{L)c; z1q9&yUg%QK%(Yr1nd+(QSgFEzm!l+m#cDC)2k*Bd-ov;R(v~{tF^n7acN$GEj-Y`g z=e58)vrzF$tcFhUGxB)Kk)zuOq&XH#BYH6b&9Z(9YJ0_EQuWY}zJGr3`W{2&>)1%B zp^wO7(2s(cn~yK02o_?6&`#45^6l6KQ9S2X$ zLH^KWU^(9`P%P9x$Qmic596OMUS4!Vm&3*%_O#pKgL2Kor6RT{-^Oa@{HP1wxG(MN z;cWzADId#V=05oQb424Todk#a97a7~IoKw;fP2O7 zpH~j&MJSI>H6;hzMh zc-?E!DD;la3Wbx9{WWWN^oMxNN#+mEIMt77Cnp$m9J;aQ2l;pZnI8Q2l-cRtg(~#A zJg;zIu^P>zKdNG69kw1VT6LGL!MZ_*o0*S@`Kw@F`4QI|%vRm_(;rfcd_|`|#mv{? zn&%rv%V!OE>FMj$^issYS1Q5BK9?g;)W?GhC8apTY%2Xui{MxmO?PnM0?_%GC*Npa zgn|pVj$OVw2`(?<$cw55@pEfCNA~#%{N8u;GHuf!R>T%$?Uk88s|E|&bCk1C)x>>P zYGM{>ziuZ`6S`$Ch0}s>XzEdSD6&w_sqspPQT2jZmSR* z^{U{H#u}(m`qMDKA#}8Uafft8YEa&BeBFb$0g6?kR+uggf!K~ww}nh5Xoy(0n#N|r z-oJ&Hb`Ryj6p!z|pKVESCsJFi!#EYhPU^lt`g$0BD@eX|< zy=%DYu!k$hcuH#lzt?`>8seYBLp4{^{r=71aZ2&Vu-OO*RCH-Fh(xTUBxhHttibDm zyyhW23n-bK`Cfu-7VrJXc1ve}1@`onGkNo^g5u>1QAx2mC_8%Saq&VNdRH+K^#GeE;|f8M(hLs7~tW|KnH-#)XA6Z=P$#g9oA^dVsn`E;4kLHF%< zzInI@cYg@gTuOV1js;rVy9s^}PA4^M@x~1cUxsBg-t>nk#=^9sho!_hh+NloqZEed zURqBR`44G@$wuXQDVXx3=?$NJHO`qvd;4%!Vu!93%)T#0uDE%L#dG6;&s>BuQf$FZ z{=&3@lQT|yWO6#$s)jBq#fw>br6A&edm3cza4!1CqZG<$3<(VRA=nX$@y;a{2Zoa1 zXKch7+1NPPp8I|}p75b7eHYjS&Sa4K@ksSq*BsWdhaD~uoWp?RAn)SJSv=cl7~^3& zjf}F#3PrhxKyCY$;l`a&pk%Jtk_;b&&-t9+AIJ4W=(5JWW9WAzH z#xB@H;xQ9-nW<(%pk*oT!y-eain`$ zX?}$8JWzoB<@RSZWs6aC+c7$)JrBpOF6JAymEq6Y+j9rkT97h6jf8iu z4nyxxFd33Jpc$844pp!pz9^+!qc-&+)~nu~lOaBM-`=9>YP%mY{LwI6kmUfy18#36&h z45w!`_|YOG<=OzEgx(8<`FpuoFt*a%Vi<=yk;>jepL>zB|rLZUDP( z(jG|Ds6ba%#c|5sYE1Kkqy5O9D2lL?QCgaIih|r?+fR z9ZpMWRpfuGMg6hUEMv=rjw5t65l=2co)7cdJEtMYX!!2AB{~M;fy<&_S?Z9K;zCv- z;a9OzWr zTNai0>ECy~_p69%my>>cyIqMM=U1~|diq0(qxJ}mfDcdws$KV3z7M$2UN6l21T6zA zQY|#X;S>q0)8DoL$iMw`Vw67uyp-3^aqant8b1_NZ50Di+nRshhIlx!Z@;dheiMyl z*BImK_r&7Ax%I*OR~%sE`!lM&x9q`&9|!5WWcelepbC1gY{KCL)1R@H=!q!rC_qj=8*$JYy3D3Dm6XDSkm<_T#tu zIpc7Ej%l?!=fWT=AD{VpBiy@_+}$LFaBF6Oi+LX5i6Z$8f6_|$p*U_T>NkZv{`3)7 zm!@$eQ}}ckQNN1wHf#^rR)RiD)9n@eIw(`USh@9X83GQesh#ax21WYS+CPiSpf<@B z^p$NE^lVDmEO=^gH|>m*^G*lqP!1PW66c#?)tg6~_Y%62=XC?ON^0>p18J;pcrDTh zh3#=Bj&aiY-HUf?=PxG$J1Vc81VdBuE zEKp^CW!jg}LiC5;P=*DyLi#Vh6>rxz;5P4^+8JqJ9kBs8APl1&V zR1;VeMmu<1eFA;fH8xVJm+;L#B0lVMdr_Ct12HCSo^+ZM_)qe! zrT48V{Gw2lX{A1eO8+dnTHI%_c~+RT>ER@7*&JHcC)QVSDG$$OSwfFaC+xVRP56qr zx91G@W#Jb?DhJc%FR+(_B;##N7nDA#&~b0+1R45X?cr1%II3_Zw1UeHgx(eSev5IRHHtQnUTOU>zJQo_VnJx) zF5;84$4#Ynl_(#Z+s`dmgyhyIDDIS`qgHv6>Wb_z(2cN~a3Ai%j0Qp-Dbq)AuGZI@ z<_WG`?zCT_UpMv}g>81J68Q&zBHr&TIpU-=9kruD7Dj7PYUE%Z-i|m$ve&2&sB+8a zBM%dEGAWCuy)0v({H?a<;{JY^s|ZOgP;ABA18j~LlRA;f$*$!5ZU+Y6J|OKMMd&7P z9p=pcHjSIiyYPXio*;eX3>JM2*qU*f!Eh3w`c3Q~U-jf-&hCvtX>KMHCZR;g zrXNg|Z7fCktSGjwTd{bcaiW`)(2+*H&6s$X9D_kZPoI2{&c>Xz_ZR<}L=(E@=X=J+ z8j&^OhpG1MbPS5`o9XLMMYZRZzCi}rxIcHGzyvc;W5pzWCnX7M*iRSl->5{N&{d7@ z12yQCVe4P_zyU7(-uo?+&>5zq_nCV;Hpu5eGeV|si@MzYTlTxC?e8OX|1rzh&nxiO(1qq*JUMVyP5Z7w~vA#{Pt<3bO966^eWF*n8KGhJ9-U*Sb>8V^RZrjew& zi6~(9xp(HT6N-Ic-_lkHK)G^GJ`Ud?bQfm0dG3xsat0n>I({t!JkJlsPX3nwyPbo9 zV=w&(pOujgQ`%=N%9?*jnQxAP=G@Qv<<0RyAUjE*z#C-bsp+zP{tmau_w!%86OL+C zxzF=Y_JObuIW2wUECkfN*DsMMMXP`A??*Nha7a!zr`^sU@BR_m+xn*lJEQlpy*`?a zPL4}#u0+3=>1v=gGJCsC6xE#F%+9)eJXm1FL8X+A>DKS!h7zJMzx9Dw0>Mis-zD`v zBan*wOgPhQKg7bxBLSC`qT>PUe_aTarv>9R&8NMi&rm&`+H+IEAMzcdKKwfE2jrg? z-I)DdfH&yl7K@lYJiFet!MYg*6JZj2KD8x4kDAK0*oHV5TWGx7NsIM6=iaJjq{0=tVq^GOf61%)*K*=Bu;~gK>GOqVub1EYs7v?)$PE zhg)bv-w@{omfQ)ybSMF(HQh|N>k0U_RDhBgobGjiX>yRny|Ho1hc4{hjOdzDjq@Q`AXz;Ci%oTG3|UVNTNaNxR+abzJb4S)LU z)lq@=j@34IHj41gHtTsVmrUGg3)4u*Y(+9Wt9fg+0?+mzt^3Vhj8@ToB(jppSbO!g z+xuanuI!e&tIB3$k9L4a_TFq9IdAQ;6FPw{iYc8#6B0ZnJPUsicn;4`MDP0Xfq*!KJ7t&8?^2&IBo;!EZ+Hpt&Zfbd7I zpKxxSyE=~pnD^g4*(LOAyJb~L=ybc4#W#JD2+kw@BkLU3Sd=OX&q)3og{)^{*Mp7H zQ8o4?1FuLrnun`R^?Br?myF?QKcif%=k3V3LGB01nPPid9pcek_b10z>msV}zqK+m zU5m3Va{BiSi8=o-#_xQkwJ0C3Ue}+SjUtpJ9(ug_Fu>Rs^=-Nk} zR!uDlpOw`$q_n}M8ypAvXbN%04h(++M_MBjSuM&iRy@fcIP#d6{L9CC_y zk?hkp0g{s%AEWf|!3ed1>aSgE&{OkmC=CdNPwZBEb9w^66^u$dPX&P*6`ND7d^ils zbL@8zBJS&^9XjDd{xI(#W?6K{54bP$|L)cHhY0IA{h&_C1V% zat)opEGi#(`^O}{nl%E*3%ft1DMf=q$ki`gr~E)-mz0!L(;xZ=JY^^L8bNGrZPXiU zKj2xC3k|&)0fi&`Tp3B6@mZ5Vt(>JlRtmi$SvyvSU!FAl+VXHDo=aH+1qpwnkXWj$ z)l)sNtJ&FIer<}R1s(pq#y(&+p;)bY^b^Y8W_;zd`vKKH?D&6_`h={FJyboG)@a=B zH)H1LfJ=JCrsfF~pq}0Lk;$0o`(M5I{3;=Bq$(<)kA)eA5&8{^50voM*SL?(N`5Ubeon%^r;lB*Z@IGT>ZrI=&I33LmxP-{{1&hA(m5nO*ohgho`V zZVYMb>A&CpI*GZJje~uIRgm?-oZW=b*N{uq`iuzIK+_M>5AKQS5XK-?8o^Zqla-cZ zcH#AKK7vcNE4?24I-WlGR6mFdGV^|{I%Bxuf3d^#9&x^TS0<40b^<+_$!cFPCqh-h z4c+pkQaI_t^2=bZ5~}R`z7FIjLYNY*oB5$!_}22cZs~Ll8m~-+O)58Hu_%jEKzsvGyev1Vyh8o<~>x%D!$K8&9}@FAFU3=Z&)2|&iB*Y9bOX4z z?krtVDFcK2e(INPZLlC+5PtGr1!x_hz9uA{h_y|7ChpMX;O;{f4o>kD^iij_x8HAt z1*8=HY`Gt?qGd~e+o=Nmp3Zb%{AP?P5UEA> zzU|PhL(>yniz?F9XtBA!M?9(?OQ}>YdZz}WkI7bWKZ7fAim7RyxLk&e;lGuQ6Wd_# z#BO(ERtKD)4))3-cvX8SH@;``_rl@*{+^#0rZJRC_G3WPDDIs~@r{WZL%R_Y?onO~ z6g~Cjq|oOTkm%JZJ$z;fI)1;(yVlx4^lgXgHDe3#?*~a5|MPih_QySDy0sc5HB16z z`J3?GQw7Pu5MmxDllZbaAQatmZA1EwcHy9y$Be$)0P=ISGDI*{qn_qp9?1`l7(AhP z?U2qg9-`Dj8XbaL7~yr!v9}(FJ>{;?+6N%{qVvzlZa>u0b&FPzjz^ihUOKVf1PAJn z66K_AEJkRE?Du>fgP!AA!;S)ZKvg5v;vH8F!lz$t_9te6SSQ)WZM$4Z306J0^sN~E zOG%W!B)7sBk%iuWscn#X=|tm!)d=t@Qp-vkQYCok8(XZpNrbPDcdu4l6uNB&NSxY< z!|1UW`-e1xkbulU7wA(^@HtPVpb8Xm2>>g1G)ebl=P&J8KpB7Q*2#!mnX%o~4f`?({H zc<*7C78gt%_gnU?dyDOFBW>SrnPU&bugNymIkaAI7Yt=VB)!+y@`Ug!kcY-quoxBL z?GMXwH_Kb`nTmtXY26MqpuEgX!_kcZI%y9DaZV{<5$Qf+ zI*gYtmFV4$9!ID5q4PX~6L^-ps~NRM@#rIKN`HcDkk{6|z4yl~YLCh=ij>Zw7ySi^ z4EhB$8gMf`t2m2&qWJA#B4$Ipj9;< z3jD#by;y_CYaUASpRL0XI$t~I*Tj75K5vr$gKB&_tiD7|RSTxV2Xl{{CHkD-sh;i{ zbwi(tt@{&~R?tq!IWn77hi)PQX`v0JsK$1D{t6j!Drq(Pa5z2^%3L{%5-0p&Vr%ML z#nDPke;qEFe6bySi#3n)m2{xe-b;Er&ED&V2*uRxjjUWT%7l{b73B0Hf5NKysib8Zo(>wbE;Qf!DeLYk) z$Rz8c@zE~}3S(VV@3G_o9b0wy%@aigk4pP<(ep;UHS)~U`7xrc!heZ{%1x+q?C8!Z z{x+<7^@`CndlqMM4heIF2BZGG=i?~nVAQH!?xp(cjWXgr>u!Yq?eDSf!P5V-@bgfw z+?A0moIg^zXsl!f&mI*WKUU)eH1=0zd(OWh%&6q&n~r?O@=u23XG=_x`%}j9#*+-# zUXD~A3&{eW;RNGrcap*KTK#_>x3W>N%K-Vh0)TAo!w<5sEQmPmzQNv|2OrBGHoUG& zM4It)BANGzT)(fASEIz6;kP!WZ~{pMe2geQo;|nKI>AU?k_`Hix&OxpU$&G6cq1Ss{c=s^Jr9=_S-)&GBf%uHoXo+eZTi>lZvbZBw78HJPCa}WnL?tL(K#f9h4H7x;O_$ibL7Q&6dIL_zvTv z+8LmvGH+kj?7$mxdyL*#bsvNm%l!-!!w;91=AV4{S~Vf!0oup zF_4%IpLp=DuRn2rZt_xklsDmXSJyMYB8c3Ao#+4Yd>jL@8XK$Z`w;|RMpvd?HVoQY z-M3E}_Tb>n$bg!0LI)taG!Wd|fa~$6$=XlW!H6((ZSqA=eDA5S@O|4A;~GBFyfO|# z3N2sd)S^VlJjUi;T^ftua{fep5{p5mRR+<6r(^J1&j%I1P+s9p7`s~TIr2551lsc-r03K(6JvUdNS50P8P z{rFo|6KWQh_zwuSK&$5mmbzQjShinSv1Kj|hf;liZqv2lyJKyKS1)zo9U4XI&zC#U z(noo9oS_u;y{J|@{g)wB%R@d}sTbm3j08{)1i*t@_Wet`{&17^vuAj!53F@{YNSba zpgDOP;{}&0yeb@0U%xaB?3di?*j8>jJ8^9IcI*O4K-RR&V7V&Ri54tJrco-0Uu@03ldg(;IZr$(E z+PD8Q;O_|mCKkzfsJ(SDWa?4`oIK;j74jeupRQZwU7ZTS4eOtyH^LB0dopG_rbbby z;)v>vH>2qM@}^S(alNGPychkN)rmXBkEZl;x^O7@;KB;wD?eKLG_#zg1w{f{-?{y% z!-#2Z#xB8H{F`w1MVD9;(brt95q?>XI{Ua3TZe_n`D8 zbIu{=@0Xa%Q^JsjW|G##uML8)9Mf?0Yy&+b6>Y1~T!^3$j0z>@hr3$xPdg$Sa8C6> zZ5uHk7Rs|^<$pMaG)hHG$DtqdnIerJ+0|ekhokHLi}uj|&g6GnpA*DW*`zWB&Z1mZ zXLf+yBvNbYGX?IOLTZYiUk8>a@i(baaNF1{R?W4P9~K(LkXp+J<|jr_D>rT-n)^Kp zhQ2@knIL^P8Vs{tGa3LqRQpYMWB?xBYl_|5KZDwFa~xD-Gw7Eo#eL4X6BFF1RC(NH zP*#9*6GdiFyymB(vq&F|lZOBNy&VE#ftG&^Tx@Zy?l4~F?!kdQ2Y)kgbff?7t#yaK zE>sQLW1jFn2?rgw+4xetP{B>_*wv*Xh|;@tvGJ@O;hQ*Ua_X}^^1EMSf2Wy@R9AA% z%+!4`OeyBR)Q2qGrGGAAb)gfJ%UgEb^@+Ns%6FHA!+B||j#T+%>g@k@;RWyx~=(iu~ zQNCj-p9MAw4^1nzThLuFX@-7Z7$iqrzDN-e206(!2mGYMA%=@gsk~_%$hfbZ>HFsh zH|I(DjD6oh$@5cZ?mh4a%K^8rXUs%ik)BB0df^;yWo1{1bx#mn!a6fgj|o(4ub1V% zJP5k?jK1HlYR3o0RPqta&6suW_1zHSTsSw<-_~x}j1SN9QOaFw#EpSJlmgpLxQF4$ z8&mk z$-cZfn1dhw7^PQ*2BG4OKWFbg?nPDyI`$;#VZ0#u>BZyt0EC3$lQ$%*Fl2oB&XXHe zcyiBisUt)_%)_#l#o}OJv<-7p>Gn><`8JYNr!$>M!7b@z;X=$s<)<4-FOT4b-7y-0 ztwH2ZT7vNNqo^SjK6R~m3ircfcKXLN*wX6X!rwfDg_5)aH(&K5dv{BP#Qs_||IN#s z)S8VM?>OJ(CK7yrw^6(wi-~iB>$31**eAlDe5yR}To8yZ_-_1-41p|aQ3@5W4BV@n z5yBugM*J3D>@G=yD3QI0PZJfI82o;9xWs|GMBwaH}5JR8Us5DdJ+osiy%8-B|d_WS|~< z(c#MKZ&2>Ud1bQejbwvxF7?0TUpoe%F+Jwsl;!~N)PDQpE#?PJE!hvEV(dX_lbSr_ zpgy!w^72m;`WRM!ZsF9T9=ygO!8&c$iua}^OR1RJafbT&^R%UQ%;^vKp2^*Tt=om= zSwH&FaDLh2ZCfA8@d}dr>?d>rEUS4nYPGnGej9PV(`b8}@moAmS9qEqkbkx7$7AJv zMmHX|B8ABQ^SuVG1b?DchIXtEv-1y&JI@**W!|yD+ly)tJ2qk=A@L42xA#IdG2c-Z zmvnEKX$APkYoS@%0e4>CV7U1F3pA>j=pLHyKz&ZFN>yTiY;Ch})qT?eyJkfwXxoDo zX~irdEj?JCqI4p`xCRp)Dp*J_5!h$N(;X%uMFjt6IqJUy?I=*|E5!J#5e+!{_jr^v z;NM>Yr>&Ul@!GGYV?sY$a3{f~Gda2!RxbCBehM9iS2m7@DnheBYI%mtdU_f(xziu?A{u`1Ol@6U zAb6n4eqSYXJaJBAf2CC0X!5NkKCBTNb>S<)t`kGmI@K?tM4%8^%vx z)~>o55;X~$!!3b-8+(nruxEPT*oEg^sH}Tu znU|y!4PFeKni==SjNQEC<%|$4zLah91M+aQlI*beavA2#=#3nlBJN8*t?dFrw+7wI zbmx3Bky}uI?WA!(QmJ{Hbx4ik_AMR9I>t!6ba=$AKgEZra|DZ1r4n6k5dsy z*KLG`pYj4;`VhA9W?$fX`-1w1Og!FD@#EcpWeUG}&WZduod<)aH}#Sa+X6{~(C00e)bKO2-7Tk*JJq1*W+--P7A-${4 zB^*c{0*e^mSL3m9b`g&5MwDy2@Gs1;3Z>*aE=vY9;SkfF5og;*>?)z0JancRDR?o7 zovayI@!bbeUlU}OS`cI!eF2;Hoa&`ZZ$X%E+f{+6M+1kUf|52Bm43yTm>2rNSIYvw zOqS%Tsx6NZf*PTb0_#>|AWuPtQa{} z{qAN@`hw_n<*k6Oa47Xqk~ewp4OvEC@0wg}M9rRB&XxWSjO5(uD)a8ZUem)|%>vfI zH7VEIrRxoU9E2HF+&zFQG3%S=cQ4$nD`g0*sv>yL)?$@6iQM+T9hrok6yxOnG=GLv3glt~_GPzL%Q~lTNOD<9WWY^jcbhOeGQTs`%bdcwdW38*A=1 z@|Bntb1kOBuoh*KU8>!KYw*!A<7iwnz-PI$$6dIHxv~N0pFMgFD5J6G!&%-&1PYzh zQ}lH>D*y7Jo?#qCxVHCyAWX=Oc78E-`r5mTz44T`n56=d`z%ejzxW zd<KV5(&Z@Scu63>70?o#*W2;I=_L-J%PG67JL>`?rg zI|Ag{I@k^l=i-HhXU8zC1nopOLN<(>h+-Jrk(H<&RN+ z|3t(>XXM~i!@FoOnEk>0oVynDP7{PD)dtLFH|3R;{RCIyB+m2%`D5e>QgN5dP3R#a zK-V1AhR-(nRVc{A@WKt1e_=X>xYGW1iNCo8t4>XCEd2jB}62?hO~?MT0}iU8%*Wb)mNHo)Pi9lEG`ZTj+t)#pb8Y zmx(??w8FzK`2kRNuRXZk*N2UbIhU_<_M`v%JA5|ebNFI4i|w%LDBe?HH&>}1MY7eG z|K8I!BTc^*)rtNQs8h`Mv`NUtUtAn-O=ELWA*<17ip&|x#He_OripxjpXo`t54(}# zzGZdPg&}3F|`3r-Jv7r*>kO&HI0n zoqZ^Ky7teqUNbhwY%AShZG^CG0X6~S2B3J!V|FX98R=CdDDCvC&|yKJK6Dqc@4rV8 zCl@QxB*fyd!GE(bZt$_$?OYT#%>~FN&9~z>iN_Vye!2J`$*y&9NFz$kI@=o)tuXPeSMt1pI^)vK&#Vn7JftUj@>R_-ZqfSK;&j>N=^|dN8^@lGWF?7thL;PR-Hx z-Cp6;j2DC*5XcmnyV2;_>S`3 ze2nm;gX%=?R3#kZqt4(jse_z~0S>dQT%?wmwmBG|i<+y)tj1i6ur{gY$r1J<+#@XX z$?cj4(ARhQ2;RxX6uZQ^e@D!b;^f4C{1;S_Uesw&s#X(7T57I-AoRa4HIF#HCwN5i z^Fnz}OhgV+X~@Y(k^w;TM!0C^L8z6DFmbcTOKKDeOq0&X5} zMcxxDyf6OL0YM&YQ)C+=@*Sd?9hix{7Y>ivL(0C;_)A#2=O$6F)Qc{PBqQD(zLdg8 zPUK$3___-5bfSBUxgbMzFOJbOKaL4&!JlMoKc(4wfmZ(&*AbNwV(yc~pB)_q8Odcl z6EDJnXXNoQoX>-be_XrvZE?_%tv|VZGajy}yt!OY_z@Lar}d;u;((UgFCxh#99p05 zi}PcM#d|L(|B;@^Mp{MXsgH8*fLd#Vb4$KZHF`Dc(WyE(ra78pA@haM<nXP;?;fQgiDYML%sA>TLGS(37C+VnFz(`* zI!pMw8~>!8^e3D6C`5^GjA4t(?Fn=b;#ftF@G;@u5`yQH<-J32oNK3B_M6lKl;d-hA6 zqLmr&^*l#!a(f1zi0Lx+u+Bo+ws@)K_6*$dWFK!fpNFaVm;Q6a|M9SJ*R+<-fk2aP zAphTK;GSU&cU+o+76&)aeO8koZ6Nx&^WPMhuSF&7$j`ug_B&nE%+nxKf8e{v%>g(h z<@4_m(Lb@o%yQ-v1EBJ#F(agk@Qd#u*?*>U8v5=S9CQwv2CK%K#V&@^@Ll&qmcQyW zkh=bWK3!@!^{^94ioJD8(ad^%b&4pJqHG zuCMmO=uGL_3ecq;=RTsc1X1MLiGxDDaNvuc!}j$)koer4p&Zl;V})Rm5%jC#)YDPFCL5ne7HEeF-&hA0mJEY{w_T^)|R6Jhnf6X%4ux)ozIiEJ6gC zimJxeJZ$#53;Q3M0c$z?uH%yn;I|?!!*!ilSG*RtL^g?jDyN2mhVLTy)QaE!^^wTO zd|W)!X1f3i%U8s6IOm}D?gq_?<~ahxmn@l-It{!-9`jfFdx6gD;Zl$GBwVNF8`!Y! z1q?zRuLvL6pi^U0vBL!X61qgSE;s@9!9IT- z?oL3--y6b%#OoFMs0aIq_l+H?_nqOI1hqHN$a{MXHX_I6X9$0+tC^@8DWQX^KGkU@ zb)lcoRgIdcIubc+TX7HZbt+zx7giiTl7VS&UXIf}%Rq9&U!VVs_JbK|gh$QcZb)6s zIrqkn;BBbN`|O?U1WksT^ev%rD7*FcG|PQLr#kS(HJ#vET`EsrJL}X7OIekAy?-Vk zWL@m)y3+*ky3lGin+?FyJ{en2rmv83-iBSnU&)MMc$-W6VYHf1<9YrRNlPB>U zd7pv}6blu7bjEnYjcrWB(+32dcs>*yoPlD`)5&DFXCY#~ynKmj77jLC`7d!~4xY7; zy0=ObK6l^CizL&F@LFMZRrA#ZtejWoVoDf>oR#z1Z)BRmd*3mMR_b~<;StA+!Yy#{ zX)8?`p`&e3w#b-!Oymzx@a0~k3x?fso*+MtPT+9so7~+NM*6I0$VFX``%`*!j#&h42yZ9uD`WC zEB5>=bc@Dxk0`A`^*1S{utDO!{cIEJ#rPE>XmPLGU0it%uvz|bJ5hQ$f}jOzu)%Jb^V$A~=O?;JhX7zUts^lz#qub;c=Otxx>zsZmrYM0Ii3UB{=#fJ<%IuoV~TV3;T%{? zAE4ThC)TgEm`3DXf?wnxJeB%w9wN`l51B|2e(*OjmEkO_z(V$4j!^0f+-P3-{`ScV zB)=_}_?Ng2jDEkitku_HOqMQ^oVE+@9h{SFzE7OPbE{3Bov8;7>-)T-!)+fV7>tjgESP7MqR0 z5^X8MeRdaa@njUx>ms=<)&~P;NbYE6)#9_N3Vn@V9k}P!_NfNKJ1A-#B^*H12Y1R7 zc$P+--tnpDsWopn$4+$))avCGf<`wvb+&XL`_x)gJ8&1Tx%&v*gEw=Hih>`s9| z(klgx*hwHA=0NDQhU{|ZEJQxIS2U(T-1=>L$5D ze0ir!K$WUcUJRCR=Yt@@+ zk(x`euXN|)n)56Oapw=6VqJnK&!4zM_`-DKmdB1<6g=$|OkEkHv0)+foqenBe_>>d@&b# zc%yOsKn93L7xj`(mBPA#rtI7dVHP>B5HrJ)1&hfN1#bym3ai&S@)#|Gm$)i!T@*VA zeTCd#Z5~d5;4E|1wbSF!F;iB{L-Y}ueyUTFTEwCD#L~^jV>zH=Qn%e$8H*F|a~w+# z4MGLaRS5;X9wN76ukpZe7vAL$WJ~7jMzWxhr{OwX=yyfW|8AQPa2};;etg8^|NGr8 zZJGu+9TkbRI$?*t+V_V)@pR+Bn-P6Aw{>v6{X2a?ZVmp*9ub*rU59VlvC-)S*Il$k ze)nG50!^X~L2jA}e@r{!LQvya!%g{UinDI^LkhO40A z<@Tuk{4xmhFp%76wuGI!wTyEo--0aH!9b6%MlkO2-w$p3b=arg9IkG%0bza)3HJMl zy7!mcN{wR@UZ|3?$P&E5v8+0#u4gmgC%Mo3S8G2s8T~aM!$t6KIdEt5&kXD`MTKuB z5j>R+s|G!a8Mq6F=!)6i5<57bYNX zR=n1Rc)$Pfy@j0dVQ|d2D}7>Z2FR7S*GagB!6geF38RyzVs#tvo*-*1lZI0qg@Sx3RWBSV*0`9{(*-h$ z!g({tdZ6hiBTs*GKRk>V{rq{Y8jKx&E_Nu?fS$@J8TZge;&Z47l56+Fib+`Ygl9i= zUlC{wuI_@|-ME7?a;-2%7ClqX*#V}8w$-f3Ent)oV*KJ`CpfHpc=~Lr8BR|Lce7A- z!Yv)`&ZXfFXya-Z3T^5G@j~|M|Ez{!RxGz-Hf91o<@}cSb?Ju(ObxvoT>bFhLV`WH z`53&A>29IE)(2y&eibUKJy7dJE_`687krkfKHQ$^g{&Kef4;qM0kh0KNe3o6VP@Z* z&jns1z}VksNVR6-Qb+-l%sjcptajAj2cj-EzMBQCo z2K8C?!QLpezNq@J z=dvH_{e1mbop7tfxju5ebs!FJxqPgUCF(TyY}=!clrh+37W2-Ql30%%8j@mKGoX^A zJ3J#d4Q7d)8Fz#R!9P@OPI1!`(+rJ5jz$nZ1MYm)s=!#3=UQYdd6NPfu4Kn!O8p_D z@8IzJl60_a)}c61O`OMV9|n~Y{QLXqP;ngaCUL0J-7(WyN5fu~* z=~O@o1qE@EVi5)yh=59~l!%0cq%_hX-Q69N?(XhXlr~WK_kXpY!^OGShc(w)bB^&2 z+fhQN{*Tjsq66;HUyii96^$Vu;HIjjIVc|cFG27748eI!zDoOF7w9=3+Fp&E0IyuH z!=r}8eM*OKRV1Gn!@c$C3W#n3(=Fw;MPA~*9OroM#*JLKsB`z|JfR=#oNl|%aWV(W zRKJgwc{alLABk>1=m~84o}WJ-K|ko)eq@d&>d+yY?iUhI`azw0qnn1<5A%Iqk`_ky z0!z}f_%B4=$o@$ggb%8a*No+aGvN#4VEaQY@x2N!nUL?vx9o!0i*8~+9xj4`oW+0v z^8n2H{F3oYAArYRSsxBa%z*f_@Taybqfixnqcfna6+AZbHbjU%CjC7TE&AyW_+hUZ zv8q`K31e#RU(S|;aZD#;;Z7L@x3II&3wJ`s&Dakmxd|v;(PO!u6_3H;)Nx*{IViO) zr!6X#ffHl8uI6#EXkKM~%s8 zY;=EYfg?UwIn$nwfdlhls@ckHsCfTIDVO+t+vxq13)srRZc3cFtg#Yum|Fj{p6Z1c z+yS%hB7@-Kbt!Y7Kr3vD-^;WS>49w}+kOREq8>A!XZZJi2zr9z%X`H}LE?RA zG^qNtyQ=y=6PzD0oj#V539ZJD^qQqZV6OMbHS>-yaH@99_Tyd)5LhbTetgscZGwL> z7M2mYSEH>5wF{zA>&W5t04zuKpr(J^MTiDfPPb&ji2U5BtbU3=X&83fFr$ew6Yt+w z;L}e}B656d7i??&aVd^McZkyiQQ#*1wT)<`F(@I~6W0izT7~vTca8(?xPwKo!zB3g zEOx*0?gVGuSy{3gVt*NZb6{$`8+7IaJxGalD>V2oswcVw+^w8U^r%{(aN}Thk-6_h@CCfYTJ*uxl8gJ2wM28ysvH3L9ZAM4iGxt{QURT{2Tt z&q5(uUfI^8#Jbv1|JUI~@D#gGPldG+=dl~-FV-gXKrdtW^`zS!aLJYWzRcVlp>y@@ zi>RE#8CAPjZGi>Me5FvRs9pdT^eHxPBpYGiaz1v8RdP1LZI=Fjav^(hGT)`f}je zWD7MS{2r7cU$=(T>VQbWk;&BQ1h1KqEjzst&<*-b!Fpx{PAuz)-SHTJ{6AmhW;Pno z!BhCA?ylcry$_d5^o{u=VvH%JT(axh^!sRaOUvG{)0& zn1(hJ8i%C+MuX0qNikoK6bO%!c~9j$3GRXwCjYM^EB(LU_#3st;J^_bqnr*XGm+?( zgnD@1AG}ho*#%~VGJ$_@11MV!@85sC74Dlac27O)20y7r@^HsV93I-&z}qp6S3mbK zcUn!LO9<`54A)lBrg+jl___|(xgSeJv(nY_JTmGFai#Pd{l6R74bs)X{)p`Wgv*y7YIk$XOtx*afs2IY16 zZ@wj=-kBI-gPdG6?^2oWC-PC(fUOjH&AxFQi`p=hDH55DqN}X=AsrkT--z z^;Ub%zi(je@Tf%r**NaEs(KqZGJ#n!v#EP-P2oeU!^8ZsQz)b4VS7_>p2%0o0AbE~ z9O`P6WWTp8@`)|1=ab7(i^E^^M+5F%p(axG%DU`v&X*6NWV=rH%P z*1k#r)633#jttp>XO-o#e#b0S)C%P^vGzoA)4?Af8B0Or#Pppk4SS64h-nPADaGr} z@&R)D>hLk^<9{8*{I&eUU!Nq^S)BE(uxpH)MfY}L3B>pgji%u?$|Ir%2j^vfE4p)=m zk!R+FWnVIIcqOc`XhfsJ31`(bvphhi@+jV&M3@S2%{Ev}hu!_&h7FyaKzSjW;2&jy zw9bd{D{(o%^SJxhJ@(sJcVeFP-2)@IE$9*_6=r~Srke0A&lp);Zfjb}7-G7(7_a*q zLLc%})$7N48L}A1HobB*2AMzaNCs_8pu1MjZ8Ogq2EN(_oXsBs{e3o-Lb91aXKlH% zrzi?&c59rKoAjXn;`ibSTW5&hJkGW}-2i_|hT{$sxxRmw%Qm|2#9|m7*#&2o6g=e0 zukqk}IN={)cj$qE79sN%9 z74aZgXx{jbwJHw&RI=nGRr?^RvwZ5%Wx{{Gw=y{Td?@a21`Dnlq~YNAFcLwvc=UVa zZ?*o;A6|L0`n@--L@#D81C1TxUWIvb++A%zF~dM>ala4Bu4jLq?<@qTTuaHv8-;M= za_9pOhfYwZd?e^-=7ujzrYywBf>Bgtd-X^M!LQxXZaw_E7SCQkr^=q%f(Fm6$(RGH z3H`K7yvfThLYG#rbYM6e+7kbrWECv~M_7L8T@Zy|@)h zj3#oIt{+jnl8N_5AnQBf3;hxQmn;LKkt=jcBV^AP6dj;h^Oem-r&|@L=!quMV)%%3 zv{XL+%N~r3Ad5q?0I)tQ;)CO>ZgVoJ!B`W`eZ2Psk((Vd8gf7)0*^ApGxu@^;nH+a z!&YxRve+>sXlWCEzR*Ypv$-_<7N?T8a z>U?b4bsWLNsi7Zs;|V|3)oK2@J7d_l!S{+}rXSTSm1VQ#2GFUAaw*eu3?($VoLa4m z(fF+Bm=$9#GHCb>-z+FZ27Yf^$|rHi^{`6p*N<|%S%2_}%OH^t{5>p^>wG!h5lbR9 zn`uB+Chh%q3y5QIB!3DM#T-7BeewQD`5cnZ@Cj;N=)n%(3(fwU19;&l<0FUe9?X~Z zEwm@NCG6?+&SMz_KO^EWP>Ru-aib@9dCQ`HJ4 z3ZLd|d2*Z@?; z`EtXaL|)0p@Y^wtOf+sc3?ows!g-mdt}(`BLWdxu=UqRK#vcPcnxbcLZx{W4_nr}X z$ZY=l0~hC!>`d0D-CK)j?en4NKgI>Z&(mL+IJ$sOISeR|$If5`w7;hBY{9Js7lqAB z)krz}CA{u@J@Op2^A%vpMwd@He!s>iQ8s>YY>|BemnUkB)#lCM;bDP&Q-dTq~J=0%ETV^QZxppT(?BRyLz* z2iIA?+A$pUY`rDMGK{HMWfb8tjHJN~W0pD|z_JoxElA@9L#g3?`>6dPGsh|0MA`#f zZEuQ8@@U}Fpv+Z@Wm9}hGVrjsTNCnkLht+>iG)p)Qx+X}D&S1P!z1T=bHT^_pjzwK zeBcYz_qth-2gw)ghF4kope?S&jEN})8=`JH8h(nyISB(S{E&^-l8e9lA7^8AL`HSH zaW=Lx{4P`fl#Oz?PW`jb^@ccx^YLPH=3vZoR`g%VXLzRlNb1L00p5!~M4$Qsv06vf zgGC_$KfO{;^(QMs+lfY1CjT;&&~W#Pub3d_1_xG3?~S1H>?tc(mNC5f<SZXqCr^&W;Ekxc4U%}De21F zo8~F_^+`-H@2duUv+mO$&eDwMpFAh~e5eS`QXc6A`d8y`bu#Mh+$NkVc3xx(2fb)~lTeOpzxNt_=E&e}$@M&mTVl~Ky zv|Z-HybDC`A$PD?aWNA6_rm@7)*{rlSQL(|3qsGhND`_l2dG(9kqgir1y+v^xk>ss z9FnZkrsj;n_j&>(cf_-iG03jgihX4(TNFOmC{4QhSfIr153vd!sO?n1h3q z!x%Z6Cesy`t3aq}#x!h^$g%bg3b{z|sE72gOSV`fpi-Psn%lWabl=s~*EuzY^t5K` zUc2L{*mNgfcV`lx7&JfgoDIX@x*EP}B*FOAktHhIA{gbI{VrV23Pq8(qm)1X1mQ@V z=~ZpQS3%cKD?VrtgvFBHIo5F==>Bi@2_KysN=NC>%BN4Eo7)NT&zv)uGWjLf>)|x+ zyi}KRubaZ9Q9eQEhAI5cN&RiIt^V@xjOR-UUlN)5?1MGaamyUhnU*+KLHP9Q4!>2VzBYxA`spx3j?i}(J9lib&tMSKVy!?AF-NxP%e+X}4hc%- z@7*pGBT2*wjk^swcrsDsUYT|gQVL%jUsWb@F79uaMhv^doTbd60{M4D-t+496?RuB zVqHHT|J4GR&8`$u5!`^v@WJi&kS2mFu**=8+lXdl{lzD-3eVDPuyJJ&JmkKB%f{(} zn0inC-&5OI)VU-aYesPK6PBXdf*D%Tdh68ff{E*J|H`ZPikDb%>FQ|`kyu$Mv}cu{ zQpiIyp^J^HTl)eR8eN4FyK{Ru|12k^@!N*H$6 z&X#o19?zxb0293-QlI>kX`Va`{l)n=In5AMYBaened__6Vu>5R?fuXqxV`&@a{&Ik zAucB3)CrL@_ljly<>B(ylMml6<>A98f9PuT5Y_3o_x@DPK?8bynJX;0Sl4IEchWEt z4;?*tPnsqXr5<0?7$e>z$NI|*c~?(tk&5fD%=AXBw`a7iZ~EXVhV*Z-Z=K*>(zS?x zE3J4=J?!$mt4?rbMOo&rts_{O@|8SH(z5mA3>7K85b>ah_%rDt0k zqMH`8_|{?f4*T_4OnqKnuF^e^u^s0-;zZ_AS;a@fg-H!D>pD4w%Z*nvxO; z**?Wv8CsEeK`f{D!*&#^y>`7wz8i%H)RumGb;d(flrJNE&W2fD>Y$j0E<{PKtxdL8 zL_7AV){tcMvU3T_%1Xot6{W#%xpBz)^w`>`FmI&(On)=Mj__}3U)g`AITSxG4JId! z_~P*~n|D@9RhX#kllD5X1}WLy<|ZCCB2Ds%?`FB4*u78R)w;#l8Oj`kotZT{LNh84ZL*EBP)WPsyz~2VF8ps*y(jqQSS9Xn%cLp@4T3Oh55;?v z3Gk+rIq_teE5y0-h)nOKfMyS0h$mAt{CLfAa#cJ9c2W=NrA|kIAOkl`c0(n8TBRLZ zE+F*8ODp8(kjQgl6nEP9y9kfY>{nfotU|6D;qV}i1Z;p7EkW@(P;mtMt|#@F82pQz zrK*$0%=o;R|WN!}~SCa`Mpbfunf61pd4CDrmLpqk~UFLI=xu=*{3=(h?Fq;cKR z77q17GETnKd(ZVT@uckI>(s``P9jiwNZJ>9T@^1UU-ZOhF8NJufj;=1c4+CeW&?h= zKY2bdy$R{A7}tN?&Hz~TJ`}932c^&C3=OtD;f2NF>#NhbaEYvXZi>?vep^o}kUAw| zF=)IJ3__^lzIX40ye|fw{w(k*D*$(uW5UaPgYnNn?oc-cZ@m7R>dNJ_9+(jxN#|GQ zjaKUu<4$^hD6wM1lGhT2@)DAb^~2$K!|uoi?N@&^4SM%hpTQ43U4$;hKXyPauYUJv zA$wHnRG+y&9E2P!QxYA9FbGYb~3jy8u@LVBF`Sr#T`~L zI^|=zNVadnj@2{oC^H;=T>l0bI|oJnQObi9BW^ zF9qct(2pZBKzKC+1w$CbFP$jFHOAql{Y%YI*y#VuT%(zoi>PnMG7{$;pV$52ma*75 z6rf?_7KK;H#kJ*jT7hqdHXtwU4W#i2sEPc2jPkwAy|Xs=A=%^(gRZpx8~a#`YK<@1^JS6)Hai-Y?T8Qx-Gn<{DW z#c8~DD}VbuagJcQ6Swfwe+CzMEguD+BF=wztDD!Zn}9pt>#<$sQjo}_8hxrOVuDrdOYUd;Y`#aJ3zJ1 zFduZD$x{AoOT?f=k-#rstiY}&aP!}45!g;~otyJ;0J-H2-vZHl;PRUM?BIXC$hKf@ zDv}q8x5s)OpZ?ViPT!+1v!qO*P#;}|Yx^{sR8stAZJI>>^u>lfd&V%RYIfnudOylR zMHN3Y;)?a7to@u3IN~fAbu}UaX|J+6m@o#S>|2|y81u25D}mnZ z{H1$DKH|#x!VS*ZDO5kJa3Y;D9yq?eh^)4F4!mnQH!C$_P-{BNw=>xdYvTCG=}A0L z_4xj-FRmK!KGmT8OLQS7y!q931|#>>N^-tvu;_~&JB>QG=mhQ(TITm0hXC9=kMhqc?r&H7owK8nE;}|K~l8JttseV_As&LV3 zRY*dm1~LHbYunxPV@ctd|`rS?c)n9jnBcQM2xq@p&d=+PNZ8A+@DCF1Ao{n zOd#n|E02YCEsF0;AnjtQ$Ne{rLWi0NKf-KaUh8Z#>bQ#KhE29$RMx`xdol!Hc!yEd zi@6KUWsLSeSnR?9+EedV7&2hQisa74zuCa_DDlAE?$fw^?GgAr?8IVEOFhQ!7NnNF z_WUZr)nTnG*9k}CIziB*&WZtSUU)lH%Q1(V^pT%uwMGMf$MjmKmCFZVf!!nhJ?4Xj&Qd2wT1DwT>zrpQp&|q-C`2pb%;bp&B zs2y$qMsd#*Qtb?Z+9rH`?=@q1xLh&9yx$l^W^=U9OMV2dh)>Tu`agj*e@zp&M3;HcY{ZA0CW|+fMUryCT5PiV64r!XEg+1uY+{bypG8+%fBcyyfkfF9&gI5ISGxmpcVxvF3%{PHgWKBp|r5=h!-cJz?>%*~_ z2~j=Q5@OJUBU|UG{AWmEEVmZ6sYWth?o~z$!Y?1-8rt;G5v2~=;3#`8mX;k*fg_IK zdTU7TXK^QT<;Zq&+ZV&&zxhjto*>#g)=EBMFT$HgyuwCJW6|PbdA(P0B_0rs(CnZ-d=XC+yKWr$$2J~k zPlvK!6O6!@2`9(X?zN-u!As|ffHt`OJp9f_Z9V);vfoP1DiV{r()UX3PeV$sZJ+eQ zQ7o=ap<6jTisQB->P^q4kz9X|t))mcS}wR1NJeeULkhtJfU9|O5rg# z+$Q|uyHBgGQg&eSSsCSI^=7QHWfrIFZA8U}XHVyd_3_t0$U`l~2kTZ-s{UL2jFZDl zO}C8HAwE^x&w|MYGbu4}vB8GOaj*2#%DD+FhyOj|qObrxqCI~|*A%Wey-?H%3W2y| z2e-9y9YDaalVa_f1}sY|vo;KwU_r3`&gc4Y4DB6bP}rA%79G~k?vFjOE^xt;G(i(J z&S^yIPdnkB_%scD^(dqlnf542iNZ*K%0qaek>C?Y?lY#W1DD6{uijEOKw{#^*_vX2 ztufBsZS8u{X097`Ev$oOVb|Ssj#~KX_N}sNt_GTab0x)$O%NPMc@@F%3Cw?QHC{S3 zgl)4Dxf+SXxbo*}?epneOey?0y-j9_JR6=Tf=*eWjat?FvvUjR@%x)*$3;`P^n&rZ z_N8)ENx$EJf|B4#KFhQ^KU<6c2_0v+P54WTRdq$IB0r^>{Aqi!^VQ@FDak9JG5$XU6fh&Y1K%$2fM$Cwcsd zn#9q$^oa+1LvhqCjtib9U{=z@CuwF$c;WB^)o$JZ)N#0&els@}+d86_B6V6YsM^jd z>`5EOK7Qb5#o34h#iQvmn$5V~-|ngHm;+8{YEs@f`Xk!QUY~F{VrY$I?g`!w zRF26EFM*B?QK8cNrQl|~qP^hK1a-ep!rk>oC?TKW3H(avl71KMPUbg&)Mm%!s;C-d za$~436-qT`zeo8c@{qS`Sh4akS)0IOr)H7<_+vpmv{572ID2BZMs|e z{umVT-rT3;Bj)}%xRy@vl+L@TAD>;ULvf>*0va6kSW)h*sqNQ~E6e5u?{81yKT>W6 zHoZygoQjH?lX;J)nBr@16aK^X2Q5FYd^Ci|Y8?fKn7mPEnvpG!X#x`+bpA7+9mld~ zbsImzB4F2_H2oZfKLl>a@FKMr;$VB3Y}dtnZ7__*>f^*Rtb zQL~K!=E7W*xW9ENP@VXDMU3OpP8@nqm|yU->%hUr^;ymfomlU+V2BTcwT_heP15Eon~- zT_|`F_HQN8MD*rX(k)&dMTa`&sd?9F)alEa9sfLvHp2PUuZj6bakYE763*bC$MV?0 zO3YziR|=LAb$;aC7qTMhel%OKsXL)v2v008@b7u;L-2s>A7qMa!m(AJV?V-5;L%jl zZzUyV_$SZ#<#-<%v1DKXsv^ z(#WRV)lw47VTO zJC5nS%Bw*RBk2BG(ZXL~5+@#K^*Hj3;G4F?>E_bo_`+tLs?LbuAT>}u@ZT6h<%lW^ zU1NW=oi6>&XcCR**%kir9F7OAzd^a4?D5$1yz@ijrEpaFRFU_Y!Vv!5Bbzz;_6a<; z&5-)S8vxQ8v0q${A`_75r8 zB-{41*)SOo5B8M?^hcxp&5=JwOr3a0^!cBYv0b?0-mR>$;fWhn%hg3FU#ZQ3;~H2vLl8M02&cG-^FhIMF|1 z8uQBoQkqXcnhOYl(!h(~bX|hrc4x`#?US!@FNoy?GDTwtub)^^Nh})73Qow$M}qpx z5y@LmzQExVGiUr06oKqzy!NB(W@vo#I?Iy=HB>x*Uz&$82e*oYKXMjk;{`#?1 zYZ^6}Igf2`;#FFoFI`ZtXdd_jWA8*OX2rncJVAjDD zS5|5wKSG>DTdW}l!Y_r!yCpt?9x>!`h=_+K`k?pWKjNW^L~roVN-~5n3(0OB3WVY} zmp|>Mrh(nU``f)w2eB*v8h>-oII`ON zuTDd4@YfMkwbW8ckS;=c$@u@&^z#T^JI`J-2Snxm$Ne-Y8 zdpWd!Urh14K=7mA|4a3Eo5ojFtd~i$dN7vHj?!m!04H5XNZlu_fJ)+1zs7++)SMUx=b)@dg?>t`Rv3MDZIyt9HHTnoNQ--h*` zImKEh8<8alCC_9xBafJ&Nrg;5YL3?~p1Tu=kx7^CZ_`iVzlEs~CWA376f=+c>pX#( z-5)rshR>n>@0U(;D2=w3Ba=N};~+I>t?Elo7)T$vE_mp7HU265{Y}3h3^~L~!gea2 zLs(^g{hFOEOdQBhy-m#fW`-)r`od?B$1tNNMqn7n^XsUE6teMl-rL5DwoL>-=gRoD zR5MwBSw5;zg`aoU+|^~$VJ2=XtI((tH>)*i zrLzVx?`nhWS=C;Y7eDxcQArQX{-_B&KOc_*wV6wjmC4wtzE4+mUn-7Vy62Km9fwBT zcV7K!5QC#@=dZByHsj)~@!`;ql^~=QGAbpC;9kaTL84g(YDMhVUMmv(d7ChEPb()3 zOIfnnWUqp{`0-aYoVi#zeYWW}sVnZ?joR~XAP97Hn>Lel{LxLL*o2nInc@-lIx5BB zja7G&E=8L5qh8FN^2`i-6sVb^5)aC5IvL?i`^Cj))1e@jYBqJp zJ_|TbF4Z`aCSoGVz-`k**{E@%Ln6l@2fqfT6{T0lBCYACih=!c`1i_S;i1JB7_lQ+ zV>Dxkb9qbJ?h+XrxB!G*6} zatKFz=H*H~ijko#tn4>2Zpv(VoZdk>ju)ubNTZqhFyPGo*GHLj@WOoQidJADu7A6F zn%SZd(a*v*#mE*5sjcrOcvK?g)SWN%6bVSy?zux@Nc`ViqCGmJGK`8(sHb}sig3zQ z_~!<3Uv#J99Gjoa#hLXx`lpD``)j8%w8^tELG^p#ocAzZy6GSL>_Zd06DznY=#Yo? z-wIx`zR1Hamdll@bVS~E>$C33h9ZJLW}Bs4l7~ZY))EZ|%84AXh@`-*PK=Xx*1TEJ zi+AYw>MmXDN2l$bW8z&M$PzP@d_27p>qd{iJs4JvlU@A>FZjfv@=@+lW6ewiicdv% zH(HRv;r6>AyI%Zu=&SnRuPz)@Sh;i8trJza^cmSb+HuXIn{On#3xA*97f6^4Q7!)0 zmAr{owDMhfTYZ|~d#5kV^TrZSAJf7iOt;tX)#n~W@flg_hUa}qR~lL_8a#;FnQsQe3j0u2UyH`DCmCNG zir?3aQidAEv~L;A(rDmwLpb<&1oj^4zU|VIixy71F}wA7=>GLr^`ut=M%!ZzR#E3?WF(x?l#X}lf7r?jF6-BmsDR-w{~ zJnD4RVjGc*WkQs8ohuWXdXF!b)ZZ=)#{-W@q}IXrafE-$VNdU#0AT$dGH@s^7fhdz z=dv?L0*QKMCF8GJtd*bJ4LBZ$A6>cZLTrimw=|mq_QZ4cqV1)#O=uD-#_?336s~zS zHPT+LhE0FF@78lUFn@P;n~Cu6sE3W&x)8h~uEJYsk8P8ns6Vk{=2ieU2%k}rEF}8h zEfv%MTu0F1*nOX`w#0fDshxbA9fHDSnVIkOBeCJMqE~%e2Qme{EIUv1+0>2(4TeU$ z!7pbwCVDR;xPdQtDK42{j1Eja*j0DcvH+@0@4Nd466>^iwE~E_A1!T!H3`AnyZyC* zBBrtt|5hm`v;^1U!oo=I=MW%wLA`y0zLr?-b$sC@npyJ)!?6^~h+osex(t$99Q%raXaiKz zfv&V+Pssv?u#*4pBWD+4z2pgsy`sv-;ASR)1l?imrmFQ=AvmrrJ4fW?Z`Gsvwj%A{ z1EF|ed_&}MK?kC*vb5^aUbHuS(RAFN$T>Wy`@omCA2}|tk?v3HBhDe}hiO42X-)U5#$h$8BO;MG2B2VwMVo)VU^RkE4=?`LS zeU&O25qwDRQ**+Y4;N0-UFH-|1j*Pvo(~3^QPDmuld3BQnT&3kdK?demebW)&kuCr zaHZa+$z(UK-gtUll(`8VwkKcO5cj@nKcVh0(*exOrAvSGw*n{C5<5*25xae-bf|4J-7*kdIO*L8aESu!&sUgu}Ywu+WJYzEucz* zAzIk52`)slpL?>3;H|&RCUCe4%E{IwMMRr{tmw?#T4@6?4RU|7TdxO^pkJ+OKbzrf z&bhciN5UVpoIJ@x=m2)dULDwpYJ#VhNB`DeZ3PCJ+b2jy2>(dqd>LZ}=(D^vp6V?F|7U7WpH^z1y0NhJoJ1X5&^2@v-D-mS);97s zJ9Y3*yxTi&xgI2!K8ec@rb6F*B2PtG0$jF|@L1YNg@3%>R;si~&?NVmxtah@4-J!V zvQ38JNN2$QJ-8z5zD&v(mzmCZO_uQp0hl0bKLn8dq`DgVs-#JmIB$kbM4&HJ}c`l{(C}*{TA# zyRJPOBDRxC)!-ocJ7ehUJJ& zW+N=?y}5ogs}UHCPd_S3Yl0xV2T4q}#Cy>VVB6f^0HQ7(4Rovo*S|9;L|UW{?glwr z4R5c24UaIbhTBCT{=BPHMkyDLeU>-PE=Ex6wiO)xRSUX-S&DCB2z`D^AKj`r@)N=}?g<;;&#q-j?GmvWbl3nb01|ISEC+8o@M;VdTA-cZwq^t5o8Vb1$QyTdad8}6PbxqlYD0r`ZL~64N=iT1qdEc!<_rD~WImyI4 zi1B+ow?H7&+}fv3`z-;2rUvhy;17bCSL=gxMBjQg{{^{Ie>rgS$Y6MBDKIe{zN_U^ z2EUljxs#QZ!PnsDcNyDCq4uVzL;hwlOf_+*$iK<}t}Z*rdkLi=DAY1!DOwIpiiZ9F z!V^*K-L1*R!x8w0tl-jgM+n**)X)gWM&na@y1l`Kp7DHDud@mP_0_TscC}^HalV6+kxR#ZtpsP55lyWY6f1qli>;j=fkn`UTf_((Io_ zCJE`2uKP#uZpc`u;p`;#42TLWwNB!6lkv;7{CaTudrPx9X|2SkCQbn1G({}Rpo+8IRD|o-|!ce(8*EwpscGBT4xt% zZZg+_=UusZ4MJyHr_>tX%9aE(N~TY;b(LU^oZ8{YbQ#WS_`cpvEXNm;88;486yYd^ zT8^wmDUr8E*YaOkCzjS$H2YI@;@%n8xrO8TXd2vIqeaq-rd20D`eyWFap51fKQH?6 zUV+=nLB4#@mD=-|FNM$}gt}2mrxt=TU3g+?ZX%H9AId!L&;Xu%my|0K^1(RpJ`-0$ z4e*K{SexdphRhyGP5#*&oRHrQTaxfaCf!x$0_RYi6PLRZs~ikpy~{Yp@o3WLC{C?%PFcg!c$g6(Riefh{3r_WoK;x4CF~%Q6&M>{Y5!L;4F!<|B&nFNC{SS^+y!;*s!$wbi;z|fTF#m>Gyl6CV z=qFq@buEMY9!dqnFET;mN@ly$%K)IUaD9v{gdSZe7UjS`h@Sg^})i+1B0cYEX0t*Ss95R&iJkD zw#H(=ynExR%mP%^Wt~(a_Vp#bKh3n}#JMKtCQW=r4Z0MB_pyjt5&heaFXMF8aDHw$ z`8{qo`OqhM|ZXEYLo>+I{-tDLk`#*oT`h24hsdv}??h$&liEUeV{RSj7`Qy{OHb^d)WQ?J zq>-pbW6;96Cl>X-a~v-v&j+;*XOmv;Okf;qYcgCe0~(L%dG3fxaNiy$XV9vG*Xae> z1|gO3U5jm>`jG?JkSNol2>#W!Rrta?a%TAhkBU0zJ#>}LrC-5&k&qYhM(gmOHMWTiTrZs zWI-{K$vqpACUm(%_8&vNDBgk46K)HuC&Bo^*1X^RgAdM7i*z~uAoi<+XDHV_OORrs z5j`$lb%e_$;*q|K@vGpK|v_mz%l|#+>YN zXrBq4V}BOhPa~&LH%$W zdgHH`qA?1kKFIR%zVMYUZ#>Iw_=F_P9ql4dypcKPhSrWb+7(0}BzuWjpU18Y+1_q2 z?eQ(a>*2^zJ^XdnBphP{+yA;qyo9Y9 zfy0@UaVY7#q!{BG4lBdktBO*#sG`xFl6{NFiKExM`CB#vE;wH`;t#5Trr3VXD)$ta z^}oE6{D{zFQD2YjplC%w6NNgRH%+)pDZn2Q--IVFXDod>5(X3Eji*Zotl&nHl*+N#bB71L6d+)vXOhQ%(@Adxa zI6BI6_uSX@9iQ_&sT=tMmB!B5l&%`@E1kEA)QVf`YBEzC_2!HFoyoO{6#J20tJ0!$ zs`LWcGegzYd%tMx!wh(?D$4yrZOd~o-?Nci=LX4?XjXVzDXM25-_4HinY!g zYo$Y*@!!9IL{CasOHiHrdm45gH#mD)KaaX#abmK|FOTFiS$_%<$tT>o=5<#x|MTEt zrF*wpNUgUY%veiXKy59b%$WL1c+4x-UGGfus4kj0^Q|+3-u*_UP|OaAZQ>aKS=meUWUo3-*_CS9M{Lep-y{DCpN@={tyE~}ym{1}_#8jZN~C=wb3?U#vEo{qA^7}PU18-` z8v;g-rF=f#2CnAB6Tivga5W-3Es634Hn?5oS z$j7f!8wD@Oc(gVL`JH>yB7MH# zZEk{>NLDt(^M*I>EKfWXt??z2MyTJBYJf8CMczvzBGy-y$xmUlqN5cHB zfx3S?$zOkzV%fk-xW3ZUB=))vQYO)tgvM&oB~xQ(omdS+7uW7t^7HBIO*v1u)?=LY zOlbP)BCHL#tZY$Hh8z{y;|0g7kVSPx?n|nK`Q)Xczpho-^k23tQx&0;Zt^ooTqJxp zk0J|eAmQ_ax*pvkIj_C7B#^Q)gUVk0#o@Lgg}U8*=gb=|2N)SUvGgftVC~k>fc?X1 z#5cNEM!Yi(n!6fY?G>}&XVNqhKyvZ4_=~ys%w-dVrHAg$eQU^upI{W9OF{d9j+=ji zH&nCymey)#qW?ddQLgZGxQiWOxe=a=I{_^LT)b7V-?*F0|8F|9Dy7sI;V_Mp8$9#Zz)Hz-tb#eeu@cmjg;q`IBU^jd~*CoY&8^p8y~&cR1Hz88D%E3r_kvwv+iu6ibrBWEZObd_E{j^ldleR{t_xOTyk|-v#}9osc`pA|))| ziXBtYZ3*vtu+`%JoNqKx9Qb}CjYJN%<&W+R49uh~9wlF-bu5Hpn3ao(QWmb7M?+m(nGC7=z>FXU zqC@=GTkpQ^L^XY}#Yaa! zM?dRJZas=ZV2(vfuV-kZ~{C(or? zP{+|8!rzf4t#|Z+j_M%U3pj|pvH38A29Onkh77_gA>Q^rY#0} zNZqO?Txw3v#V_(rt7T1CSCKGjb*2v$C55zS%sRjyIbHU1T?hKx4~ZK!heIMbZC&fW zL_}T+-ewnSi84lNT!iFkN(i33e%+-6KGCZ=PnU}D^|0*Y3E@1T&Hs~KX$E9(JM?W( zOh9P9gOT!hA-=no{L$qBsmn~cl#?>?F;2LXb>JIL7-`*lp+j;ce<_SDk$G$N69e^# zx&pkq`zL0$q5$F!o3i^4=EES^@wAOiE;ierlYezC7b7oH?7j#PePDR*o9RlDv!fCC zMg3nMUiUjTOZ-Rlk+#7}G+T+@*|V$ni*pW^wis;*OUogBWE?)Kud?b^CH`lpLm|hV7&J$ga4WFe! zC28LLV@4{NM{F!ivQjbhZ({e(_ym}!?PYPROoiYIr-%BEsH5Yd=RgH9UA+& zX%H#*bQz)s2C!+2K4(m$0D+T>lObo)u~T7G>6lSGTqdsf3#zB$qM793L&RrY)!8=W zoxaENzv^D(E~E1+}YbkJ0W+7&vH62mjoNwLhvq(<6*H5i4x#00SlYN@x9Tx4q>HgY!09w3vALyiy zB4eWmKTRXyz#>PUEH`x`{VAJ--=kQ{^`69eg>&Djzj4?42Z%1$p_I;rKF}E(I_NG^ zM?XQ1RV7a;7A z>y5VH2o!g&9sVtw59^?cBSB=Jn8#~BJDb*qwaL%7>e@y_xK*#k+bS6Mn50h2aDIae z>%&X`DWzf8pYDNVdGfw3Y1_ zde^S~jvMRE<$rjlBFFPxOQIFvRQa`%At{W^^H*8fIl`c`R-NG@PZW$br9#wSH9+XV z+<=Nr3;qjwWa@aJ7`GZ6B2&}z@VN4h{nSh;UhYU6Iwai)C&?KdEuBVuk7@(oR1+8~ z(z2emKEt7q3`N$42dI9w+?NIy@-<-5_+U1q7^o^1WmcqJNf=KD;3RYc)q)IlW7Z|y?ze0ymci2dmg2s+YRVpjJV^DD;ddDwjL%DRig z{DKc8A9PymlmCRj6U&TWA9%u!J^hf@CoghdTs$i?>WbIwb;9oV2`AjOftAVJ4cou& zjhD40xmdJW$)^-Op#C9+y0OlkAEFGL`65+ z%>_|i6XxXxF(l`6(e%2H~E*GyAI3Q zI)c~R&>o&E&n?u7)Ykh2S`vwHTMiQC%}j@QUGS|Yk664ff4!QwGZSMGqJ4dLYw&pX z6rbpL3#w+rm#JIX_?)eDu9+zS`!>G#xSzcOh5M}bhVK1@N%e9kXUif)6wI4jZY_qL zMh@fI%|#F@;T7Q9Sd6;(6Jf`ei(qR_`)#Ga41c7W*N%FWVcX?#N9$Q~9@g#X;E>G4 zrjftD42T~mcdo~5E18d$v1Xl__??aJrBwyQ5Ir+rfay42KJH)DNPc*&7?t!s;*-rq zxG}mWv}L&pqq4rr+Nsr04%kJbbH4_rjHVsNFRH=JNH-<+s2ZF>yz!-ntI+!;SvY)4 z6*$s5@bpzHM8$^dr?OiR5Tk6PINAvAQx3=S(xWIo-CPr~Lq(Y6^L(&|=nW>zMwZS8 zwnCHdw0KQVBkTrZd-6k4@iK&yW@@<)@|v$i{cMWRb&Ey+5$T`zMm2o8Se{FAnXIdl zlhcvK;PPh!YYx^d8J)e}lnsGD*S8(&&c?GtJAKB^W`j8?fh{943liJ9*zMy}@s__u z=wo&gbeiRVb!|-p+o8izrQ~yV@SLx=%B?0kO}d6og=*NXca}|8t445%_Q{3RN_70b z7F{h{gK(vf34fnfL&wR-w41gDJ#wut4c#iCVEf@q?CmO?@jYDg;ZO~@5ACvREUH3) zoc!+Xmr34N*p>2sXoJaL!Lj|-xHsMuK9DDmTZCb z7Qs)J3f@ye#~+P`3mE$yhpGX=G!GAI{0e>!Y8tO3@XmsZdt_pdI4p=`rTfMThm+rJZi%A8=qHoxtd^D;Zab`*8u|Fq=}lE+tQ*EOb6fL^QO`L9X|Fm_7GsM0LPznsX} zT}O!S<;J_uhZn0zPW^bp#+^X+iW1{RnF44PoYyt*NQcB;c1u5z6ev|izbjlUMXS?4 zPaOr42Y1ImZe+L&cm64OuD1zL}cr0psgzHaEKzqFd=c8T2?E<74MetMaF#Ia$zXlgJ>fdS1&(<_};j z+=AhZ%QQN~Os**!Pa!8{<72mkUs#{s(RH?S3eDQi9N*bzkkhpPf{n&+xVv`$eoW1R zuHxFu)PJ*Ju}OM2^nM!s)i0iRILw1>-EaUNLJNuFE>W@ThrK{NC zzUm6E5&0R98c*Eq-feNg-V=qDekT_uUD007#v(V{h>go#O3x~r&`;;g7dcfAtE4>6 zCj+%8G|!q0-`tF_uUBW>85_}jD7@H`xe-b!mv8R5SC9F*d)gD%no%C>6_cwK0l$s= zq62&hZ*)!d3&XZTOz%nmxXhAAIAiy!u@9uKlikH(b~6K))5n!V3RBVP;#e?yAr?n3 z{8wF}la5Z+$9n}t6EIP9#4@D4mGC^08tJ6|k+Z#X?djeg=rIK`-F3^ygOQWn!q&xD zvcIwEA2w+9 zhc~a)d@v3Zh=g9-pn6pW%rM1h0jd`O>NnZ|%rNjcOXp2$gwb#Rm z)Vn95@9jBD^i3-P3oF9zc{m^cZSQ&uQrGlJ{j&N>i5IuGs$p%-Y57h<7bs zF|~0e{nh=Th##S-al7sw;GKl%&v`}0p-HHZu0NS0lZ!n1P4TNMsTkLtMzm2&q)^_YBj;9v+|z6;>YoAd--ynKR`^(W{beI0g}@UO8>(#Gbt{RqC- z+`4wd2!c42UuB&dMU3W|tG=2IFlee>7Nj4-+0v^Sl`n~pZiLM@o0E_>oo;e6zyJTDAii8tH3M07J=#(H|)-gx^{|9I4qAQ-Gatj0PNguEX&c5=!V zA|mSOzfZ?3XNr$asQ)F!6gZzb|;4fsWJ-MVgf&5u~0_d~?&ZmgpnY7B>WN z6hgMdw~jX;9~?Vkw&-WJz(q}N$Cqy{;Mo#&{u}X`y0@_+P~i!gKTs3x+sy{>!R1lX zZ`NVdw~dd=kM~3Hq3M7u%>X>x?l$U9^%K3pCu5J%ew@tQ*Joba0&d2^3og8EC>8T9 z5xd-iHAjPC`KktiBfP&({j5jVN!gV?GRG-cbH71Rq8XQ+c$!#`H$mf#Yw*$mAFSDR z(Dc4r9L%^t_hVhwT>R&g~paI;;Dm}nO^ndMi)~sBFTgn@h zHwf%1#WK%-EU>P?#5et})dxigN%_Ekha&ax3E!QJ6AhsExaxN(s|i{ivEKJGThX#@ z>b|0HD{AtOt8_NCfTlp1t~;&{IR^1E3*}9y-NnOYRYo}EQs#SOfsLqFHj!+6*n(~G zf#c`8{e6KUjJ`j)et>xpYg*KcemshydQ)FVt-d6C zcylV{`6}^)a30;Q{GS(c-OoMQPwLo|%U-AH)-+-5{a#mj{(8Jqb(`(m*#JMAtXJZ@ zY7rt5H&YTq=7}Ru%to{3IRqt;(wymud`c6K7dHuV$9gj$!I-7|_ zkxw;dM$c_X<}bygMnz@I)>4SnSoMf_7UA}0{mslM#MTlST2Ap|M0P$pQGY3m_{WLQ zSbr$N?jv6+ZQOJz)19AmmA(w)$!B3V3728mzId{i(WDW1U8hg)``v^}1D4u7Re4aa zUiX)&mH2)(**w&thEdd`!7yJ#`oK*+7tN*1u;6G?AM<3K=+1`pIemX2q1d;%q;?ug zhk4jGjAw%>a$ICYbyL16wz3|_}Ki!twX1^PGc}!UCPv_#Bd)J}i09 zhN63Z^lvEhXX0lkK`(C}hPQQVpK);e;zMHf_+Uo{4jFciJar;`>VqeqndgXZ>lK5# z$;Tx8?YWs(mK~3Yf!rH!&W`jW%xBAEx)?Mcm5dKobB>1m9#s>dM)qo@kM5LEJ) zjSRTI0ITPPh`ATZ5UQ}vSQbw}na^f{GV^FWbCv7Z{45D&RaaB^%;Pb;`Adb_<7Rwr z_|751-Ugc&qIyFYTJZb&{h(e_=WB=AC)<;{yVkl_ScBwD=2P1`^cf3b$eocEmze>j znBD5nR?|`cQRMo0(RzqT_TK)pAro(Qxjb3d=7I9vRvyE@oRG8X9G5K-0x@c=KT-D^ zzU+wP(C8sPk3*g#tt%wYjJZ=A(0T12X&nPB1EGp>k%iYXjl6*V|=u zKiid#wI+o@2Ja@I?)2nql*lA>UcaV2v~C>hT(qR6F1Mki=I>8&ixxEZy}Ej7dkdbO z*|2_MJJItun#7#f$ie)-*WVbQm?O8^h`o{B67)moBo^`_p$wPLPBsasc(;+aiT4|< zF-!Lx~AMX3$6B?x_nQxnYhS0kG)X0}8jOWU1WAsf# z)6|9#3GM_$m1w-W#E9iH!F z%aEQcB%w@KiN|83M(UYB*+UsS0ik(xPIG5&csGv--B|1MocZVw{@57rRER}0nyT)L zWvH|GI?A9_NOU!F+6^7eu;q7uX{pnK?FDyxi}dqgm#?K(yFwbU>8|1Z)^qq1)y2Pm z=r>YcDE?8anu7}q-{~E5)i7SUSaE~1f%Fqx3gx+Ezn!fl`tv8@P)6Qu6lJT%ahixH z{>Ci`T&ReVANGNi({H)2N{L8*x=|-3G#;lFHt%|*M*Ph!6jdCi1L1dqtwfvf=G&WC zlFeZhU~fBh!r62dlQRz9xI$3sWlr_G1 zNUp7*%adP$Nu*w4j@OTjgKq7vohk2=5l6H0eZ-?gn9)rveiqL{-<4KR(}hfoTfG(H zuFFA~@X)_Oa({&-DrU-&dne9fM@p7i9(G7ZbLSt)MR9e=vwtrMxBiRqnx0=V8rZ{F z&P}w#+~HL2gD33>FfeF3O8OYkY+-x059i8wEG{3}0TT!S}VI z%8yg)u}P^hoPTo)dbqpTL!J?R?-`wlR~rf-$;|9rbu9-Wt_4R_GV-vfcFUhf%pE#o z>{-th+`%_DZ!RXbAKEVsF z=xiLy=l&T6=2q4HxecB;HNB_xYrGE*kLP;}Gq}QPg87Qp8c%#L=(y|tC>ZTNC)8wb zl5_Pd=W)5dEZFI8%HK)8Pf`<(+8Y$(Va9&(RE3TozH2zd))&~}l3sIsH|qy1TV*K? zciLiR@2XRcfCC<+y7U~%bjJ1^#p3^XiH}c2#Jbs6!%%&rN&n%`F#L`QIHepI!Rd?% z!*v7yt7(tU_i z-T9QMU=Zhd&FgLEiI2cWLD7F5#Lw>SHE_6b;U>Iu9&`~#0&_>Er_ zjpBf$zUx5m7@l(PYa4KyKuo3g*nyUxs8PzCx+OP(bypUDtj!pMQ_;D`JGwvMF5tr@ zS3d?{zY|3!V`DhJi_TxdVh$_Qf@hr)YjOCNSkPA9dMLbXySx5wC3?9+eqZUY2J$-C zKMu?zDvkSSPS!k1#NM0OpDROLb<*VT>m~4uIA8zAFb7}q^9-^Ra$$2!M%(u3G&cQu zocnfo3LCZeIU43ogXzF07ir?(qL94E^=KiK_${BckIPJjOHr!SZ|x+gj+H4eo%MjJ zo90(Y-4Yu&XY-691MAK?u$53i{LX7*CR&(yz|e3S!8$rn=}Rq`+y_D*pt*~`7yKjwTdCa zeabGv=dvA9G^ehY)1TMLf_b$h}2_c+uN@k0b{7# z64fKsI0j`ueS6lSKy=R;NjyJVfZ^yXX+vV^px>TJ((DRAXXW{+RNWi*+mGa5CjHX| z=S%NPwDUo`Nh!F?0l64d&xqWTjvR(kK!t7ZhEas5zs%&lJq&@A1MD(f5g6Z4!R;p; ziurIW=YH={{JEO)REqSQmX^{*g2a!4D|YZKi(j}7BD zBceQIl`~q=K{zX3cxNN2gZ_!Iv1{99VPP;!gyCiZoQ=zOeiQdWvQzBRUJozi7lxTj zI)#8~^wGNqH~!l!R-caSt8|m!j-czq(0Q1)>~pI)8Xlilapf z`;6@g=XmWlHvLZnwCo=0pJb>*zO?9T)7y2>>2aEzzgGuI zOV0P}(rVy0u0IqYS_fYB@O?72wNQ;}AnK_eUK z!+k0jM=##t8?mau1!={y-l-Ziy&R@xvaW?<3pW$*uWG!N8M~E{+=R@m;J%?Bq~8;I zlfAK&=HDWwtv~(-UPm3C(j)!- zL+f>?Q^klb=c>Ukhn7;X)BEl@e5?YO%Bug^4-_M;T6>Nryaa29KMh~{K1=vIt2qVR zLbz8QT4FDlfxwKRF#3L@MNDYffap*3Y0900MDua`sez0W>8oNwk7-4d`IXW52jR$X z!5FV=+<5GKJZNHFlpprgfIj_@e8uY;*e=}~ei=kK35ldWm8C?S=a9Mo<9-#?jLHw* z(XNDWNrSg+Rtf%=XNYFD7U94q+F(P0Vu)IV6gONS#(@3o<-{|?nDLRA%P=0sDQ6vK zRywj@+a;R6e#Q^cTWs1q8Oi+Lzvn``>^MRgE2PDS#t;>f<#nYZ2S=}pA9P=zC3@4I2ek${ERs}(*m)tsp1o29bidia#A*Hhbv#1dVfJXW^VO7DI+|I z`|T^+OgHr*=>DfKIz=IfoL+nEo8>o%S&u%vIrIb0>!lO@M5mA>;!bn6bpp*2C8onh z)wt#sDsE^@{HjW~9`Z6T#qs!N<($P72pDg(IjNP1&Ww*H2Nb^|D9p6@mS_mh+Nr-Y zxkGe<*Iw<9dsu}==PxeD1Ilpu+gL-rZzbM%Cr`x172`?MrME*dMWE47QphKJ!{cFn z%&vShxE00h9%wuR`tgG4bmcM7n3}s9J#R;(w$Kgz>^|hSvHpDWz7>}=jbvH6d-1D( z@v4YX4tDtFC%wE|jC}KZ8*jd^!J>M1@F|{hgk}^p@4nlG$hsrxOK2C<}i8F`X-Vogxn?jf{@n_($uJ%*ca)bE6yN>HK>M1`iXGR4 zl|D4%Ez3XY$8xnyJrY|(&X3i(RFgHSZ2~T$N z#LdUz=Zm3vOZDBJFU4@RG$?vSxO4qTn@~|h^4=}1*Y6)GLFZ-b_w^H{Xe#$Spkq=Eo~uuoIR4Arw%c%%a-e}mEelVl@kdm#W=daZnExt zEo}Ws7-2&dj(%o(QcLP%Cf|pmvGsKj6c#MwsjJ7 zQHWlD9+rNx6bq%vt4Gbf5o>4hsDXjZnWG!sDx&gnqG5_fxULZE1K+3DvgN>1p?B!^ z&Q{pPbAQ;%QUklTA|IK2cU%#-Z>aY!gUINYtFh6|>0#dYc)1KO1tS zk8T`vivHW*4Gf`4{+7uVQ+;%@dR141e+S#e;Pgm=c%)pon8ceD2lu1?FKpuqaO-cd z+s7Xjh@hFCx^{*1Tme*ZCYZ%j5G zp5GO)+LD7<71OP17J#Jfq#DgU(4@A@;;dHz0umlO?!TOiiY*a`?L#x+_K@k>5wU!< zCJe+%5>AWvliNBc=>mk$JU(-U=!BXRZ+QP9xj1Uy9z+DH6P-nIym`n#Ec6d}*Y&y7 z!B9$|TXmjr&K6Qt`H3|+b7#?hmCT7>*oB;{SS=v@BD+{rd@BS`9;VkcsKM6|!Jhr9 z`6yfeA(PFiABJ>e%PJh1&^Aw2T~1BI)QF_(fzOSE@3fuVJlcSciz2H39yUVLGH>kz zjYfplh!|*%HA4SSQu$+sD#ASs&6F&Yz44g_)s%;8H(zZyGsn^*_%%CO&$@EPub%U?7EL>`ahsR;sjaP`jePD=s?v^0gPoytkGdew z?3J_oQY$jedW0IfNj?7i+TooC@^P%&-QoNxGH1QT=cmKxg|Et1j|=t?ZcWmt=bJ!3 z*lu@oiU;>%-g-#$!M1v+T3WPnn>OIdBRkVp#yXfjymI;>(H$RueBku5cq7{X?2^*v z>csM&E7{7oC*e~W`F6AK6x2>?(e1bD$IBZgoIwW%Ahhq-Sn$4K{8z1h^z4p7424BL zbG6UFKJ{}Wg;^v|;)&77*nB!>V@_DI4~{^g*n9PHS{B+3Z1YZR$i~0;|8g{3B5_gp z<-PpqMqK^H6W%`11b%gs%_oo5Lvz2w-MP6saE+=Kxe>wUk4+Eezm%53VfRPY5>>$7 z>*!}m!7?1bnyQ`BO7@N!p(1ZoGtfKI-?+$|ja9LkRaMhZSoW^vUpD^&{`S&?eHGQX zCFNN9I?x{M>pyhcuQSHEwf1(QLzQ^eK1R1@J|9Ti(>P4d&*2do?i8|LIUYJXSlCB6 zx|M(De&4IWHizxCbvpu~FcaPDshN+3HmxjOQkU)DQEPqaH<_OX@~k~5>JGEi9SZ5f z?zpWH+Mgmgjcr5xF2^)Uj#Z44e&^8|a(@>{`54zD#^kvAPpKNbHQ&j+h3p};^LAPf z-fKj+^7&y0@?1hYH(j1MS%Wpaqm#Qj$hoRF&64{v0W%rqLs_TeV8t!%L~$o0@+HT* z&Y#h!KJ|0-U3v&OL?3i1JP*OCLkeX{@!_~rb|js-egYRR*XZyasfXY2iKJ!wQS8%} zkFazhb2`RXj#h=CczjGGOKOY_%szSSTlTp!EjmR&^CK)r~M}p^(G8! z#QclL0!JbCk9(I_x(!@!{X92cLi|e0H>ro1lYA~sb5TyRS2a0(KC)N47P{=c!|a)L zP`7H6w%hZ?MEi z4}iLpxcZ*V9gKbT=55KjCY;^XH?E$GxvpL(zNl;*+CEf&$fgzyF*`2$ysCw;MW<7s zNG0fAC~G8au7YHENPWpVa<3d?Ea7b{##$%V#w%` z0y_FJx_gW8<&O>Ejb)t}Yplk;zoIRI9E8(2!>@YqQW^AC^1e@=AoW6#*`XVD`KV~# z!7KhI1BW+$J&_ijigPiu6C&LSc)aC2gNJS%*0EkYs2n#8&S1`0H75NyL!z-Yb`InI zx%!4!;v1=Y^l9eTtCbl2Rq|2IX9^QSn+tge|H6JXPNQ`_TgW-j`{cqCHMt@r?@Rrz!$th$z%jKKatY?U$qqZ|2xR(%eX#h6jfyxq9*w>AylAsRB9$2Rk_m73I&1kAl_`2 z0~1(tk71mH`ayV7C9ac~r*QMG=`pLBB<$YiZ)L%dh45n^QzdB!Kqr~D5D_p1o1fal zruwDmuauh&vLHDo`m*0@{8PXc{3Q7X@!OV97*;w?O`5Kg3 z^|Q73)*#rWM}4Za0VW)@e<}ls(yUR0E12->2bfPCzUfqeZ(rUFC0CIADY>%2jL(Go z<~BTJ=Ro$4*=lzlp#)}YzVZhgtiU~6gH<7-HjWUIl=F#p!;@ReXJ^SiG@_ZEY0t(; z+)_}rp8Oe#nWVJdJmS;0V|o9(8xITM_a9@x-}6PV>7^ane~fTL%mOdWK9pm!@ZvUi zGS?R5Yg!5OB|IWu#iEo0IZte#4rITpK=}5N%kA&8aH`Gm0PmX;v_5x!wymcCoj0E* zHrQ6f{+VCCk8&{lRO3}VcNIguQ=x6HjPzf9XZL<=3&qXbdJ=Sp3UH=I`tFXoJQO5; z|M!dD9V5)b8Ad)hI2VqLVBo`C+wuJaWYU zK!W}&|J-wL*b49rT9bL^f+DfMW~sm$%_kYH8YNiIw6TA8crteDWcpm^%fZd^+-@&+ z7l^)kSsi~W3Ri4IvKf~`AQSYlkka^tsJHqzCK`jVX>wKEGKK8n2VKuNnZ@AmJNf{f z6ZJ?DkT0F)>cFNm6D<~7O0n4Vd;L$1DqN*?9d5Z)0N7rA)-dx6wf$b4Z?p%|7E@^P zcheA(_C8#9fbe>Eb1PJ~5`KhFZsV5|i>N4$=qETm48{EA9J6@zqshH>$ zgRRa_`o@;;cv8Bl$2mV1k)N5;&R)&L6`^_eGhCjy=i4N{+q52T9&;~sNuL)XwYsCH zs|tnJGTT%Mcoojo{|0Q6J_f>{>$4NeyPIT5jiChH!$K14Z{}U3=|3@=Ym4LHn zj{IE7>_db!pW@l@K6nYg?9(8*#qmGO6x5?UaOhS%M+^CWuCT49Ws?3uiEp*+m1s5g z9+$-iqPy$wUEaH+zaN`g%|6=;^+WHIhiPALKYYZ5PY+i1;k%ivp9j$y_-&|cNlM7Z z6Pf!`mmcPGtT zH|nI`9KITnm7E4`t-Pr*&rFyX?$xv)f9|eY%Gx6j^3Y*BTlB=f0gp=!#^wEcaPa&4 zuPtVUpt~w->+MPEmXg0;zi%KqT-zX@-g~v+T;jNy+f$4=--DSlr6us4PWG1FlnIW* zg~93!{xB5MoR%WKX^uBoU(w0{j0fv~-Bu35X^!`vySMdXpQrII73Kld3(W8z_0Gl{ zOMm^>ljQelZkD&rC%$E8_t$WT6MaH$wXW@*N~qFh@BAQM3Aw#jPPK$Kfi- zi_%Lv3D7jKn&f%}bl-~&TuGimKds@eJJJb=KFax|m>~+SapnK^tPr6#!~B#=T`k1) z$JXR@7XcS}tRB>qf>u>CPFHz^XxNPtVR`=$f~FyF_1( zPK|b2QF2^|-_!X!yPI&wCL7OjmQBN!{)ZpyMqlq2ZE_xGnuGEp&p=c`f#$rt}@_0r=-4%pRGE$w8oknItq*jH8st2=)c zx|d2}+#U07-XssgV(&g~zh8hjN7a7KFN7P~!QxkSF$XahBu|&CWTU=SyRTF}9=D#e z-?ZCRkKO;RZRRrRgE|f0S23L*xFY-D*Z*~0+w@AN_86t%L73s;ke)P%qvE2MX&(L! z{Mg3(miQ@JXuF>HL+-m6$om}fAU^qtaV;i3NBmvv1HR{U$ZRA1zy4a5UF#aM zkRPI_!C3tbHv9v;9a_n_bmm-2j(j>^ykt6fU^xwvZpy;(&DGF-KKah>SQXAy$8E1u zA^dyeW#74JK#P9KV2?BDPkk=1?J*+fl9@o&Ci0vLXBBp`u-0HI@!-#v?Szw`yAjTR zp7diS$73pZT2U>-7R1`n3P*{t6*a=YS;;@{)i)@^E>+cqP}1KCYn#l?{;Waw0k*Pe z=?*;97~U`cGavbFrk_6vR8l#N7tis&tfCIRpdD`EtEChl(U+rrc9sQR+%YGaa1%fs_AMfXoF@s$bbXOpO=M4S6Qyl$MWv}s`rQll=)h-=cO1IcVeb-KT>MC7J z?P6BAzi_Ue;!W*m=6%>e?PwWath|vy?X&l1SyT+6@Hx`K=uQTeQm)du@pLGqptUyQ z^GX(_m$wr8tUZg$*8k=BkT#ndmF?;d8!4kylOG=sTq>cub>=lL|0t$xY>cg>MT;r+ zKeMy9j}=l^*sd_EXqQmTm4iw2cgrYK`?dp*gNmqMd_U(lo0U*+Z)JaAmCd2p7;0T4 zM)RpnzByhsA{o@#2l;`^QyG-UrmQsXnhdJ2r@mX>D4&XZ{6ntrc|H|<{!snug)-{; z_`=oB%|%qAaMjywe3?{zklxn&<*`(M%-x$}(h1aJE{&G|yEtm_LM5NbiAL%`Qu)%t zHuC)Q)5oI4nyB56^jId(Hc{-^`bv)snyK>min{XRX5yP2(Xv;vh1ztI?L>)Q6(#ab z=iG_0O6u#3_#-QLqn^_0Mo-=22L^;C>2lf&VxYD)NU7sr*_ za*C#}+AzAbkovRY+0r{&L|MGBkSpWOqAXpnlm-W;QsZ|Fe;;m4qh8SRxhSd>Q0m7& zJ&g4(rOwW(SJl2MrQ)b-CSJ$B5M;2uCygeU+A3SHEjlI$?LwP>y#M1v<-E65o%TLyTOgg+Jr3U3ju7{_p47a!pgH!Dk=6wC>eW zEq`9p)ECuJzX#}(cp2&`=c`5=esI-MuXoA5XT4NMaxw&5A1TyQn)~1EUgJ|s>57SS zs!!Katzv7XJ6Fr8XblFLpBLjpmOD?C`*8SNFCI z9bcTg#o5kG`nP8%Cbc!aw2b8L9Q-~$?uMy@>erp&C-+4eQ+R>apKlm-T^($_5srjT$DaaX$v9`A zs${5FK-us`ayA(hQ7~vVu^KJ_t4_9sf_oa4cAdLAOX{)>CDCUdPLck;IO0|9nF5@U zc5ebt0lG%@mA9}oQiNJ|+r!#K1=ST+IMQYlvO%sj_jDGH9_@|DLJlv3%-(7)Wc&t zUJh4PQ9bG+AxUOFl*jjjZrl!KRBfkDi&Qt39who+N~*}(rjDOc6nq| zPXfmG3r*)xVoygSg7~wkzIAJ}PyfuKWat!IB{_+oQfS{A$B#)Ab*xn6)19xBz}@T~ zfevy`Rxe(Ty6i)FKRa;bN(N<)TJ=Q5v!!En9Vro1u z$WqUa8Me#ZTTw>Syc!C_3+GtlvM3hf+y{gvf|&Ata-56S9ho zB=akZhLFt4mQ~q%@4Y=XZ{xAYW3NbNwhE>ByMKRmI-TQ$&-1zO_w~N6*Tu4Qa%C9B z8ZxP0emaQ!>o>f+&J82N>fRaJ*%5S%>(Jbo{Rmp{%$w2=8bDt&$+glW2GF{nlQB7d zo=3EJuNhVrpw*LCm)D1IAG%ezs=Z-4y1OMH-COw@)gPn3ZlqI&D8zo|S<06qjdOCM z+V(xDck`b@Wmq@5m#5B_bg2Wqx@8gFhxODS_mxDKW}AWQ(8hC5YdlZ5o32SXkx;P2 z1L`Q*b`(fcl*s+K9cj6}*`1YYNB`Z{VX_bHKv5jYvRk*CP-KbzTbkF^=)Fhuy4d4p z6wf4wd^|Cys`mQ!eWM1nkvGP#9a)Ja`i*B=vWAd%@!`K*--gkEtgW!hPZFwp{vh{= zDhVON?vrrw0b~{Y^1AnSIY@RHi5}#_emm#ebbO-4@I6&#gG6ZsZZXG0PhA~E=Mvee z{V>OZKbq`)&QZ*XWuG&9%2WbWZ~l^JI}M-{YxCsX!+pTQLgsRwM1n>|+R{2X5^z;1 z)iVuuB1xAeI{o5qG+M|=IK9@1T3SP`Ki=;_%$i4+)UWj+-ZR-Mzo=@_b}zq@ie4kK zJ7{I+>C%A$wrs}|j7dmv`qJL&a5JJ6JG10SMnVbgpBfrZ;XbhLE1BE(ac+uTu_n8j z1d2E7?xm0QKpA&~3)3eO{I)cimvX8`_TB4i#omA%|7-|UGqfWQW9JABM*Q4JnqB;8 zP=mbO?ruMsZ9_I9MNGT=?dWhmO|HoKc2rVm@3+sW4H*Pb@@xdxpp)ydM0dwpq-ph< zjol&|33clHuCmNRYzarV$+^?fMVeGcmy0!sSNit9PsA)_c{=CW^9Q(h!>1)S`Diwp zxeel^V3F%wP7`P9;>MxpOB%XeyBQ&8xD_x)$u z;V9~jO<~hKK6f|onZ#?gq46=Ir`gF%9#GEn^4v<+L5@q&;%U|vv{#xS#GNIN~%oH|Ixv^z8Y2Av< zPL)*`2)82b2Z4bLr7g%Pziy&OvIViS9Ug93_J=0lL`OfBMwG=ud98G!8QG3d-nw}R zyPUtK$(%C7-{-ormS|`cI>@m2&S}sadBo|y{5?>QzMg2+<}D|phzjo3IEz-~G{0{0 zjj9F7uf_}X8xWD$CGnR*XIqiv1;t)f^F*lTEz?{PO~T5h$a6iJIQSRNlze`q8SxOc zxds1gLgXQC`UWwI_`*32>lQOLQO=+E$2WEon>z07GO5P~eD$SrN!W0XrLqQF?%8}$o-{%E>snmjB)j4PY(B9w?oA^O}) zheNIC(ANQ~BZ(d8$lS>m!)NX2-acW$A?9|ZG<6H}3oTh728z7?bf0mY6JNUl% zU4!>%R26!;WE|pd21qcTLEhbzh^!~5=Puo;K{+001GTX&*T3pMO$nZp79X1C^nb30 z2YQ|_t%)P3$*69sX>0_E)7~Aa|1^yHmcjC$;t+}}AbZEsMgTqILCU^c)gbP!GL;a8 zxhc=gHeYS3wu(Lg%jXd*_;)zVh?klzE;+OJU-dv7IavL?X z9Kd?rgGc1q8^w^y=w+|#;{yb0LDuOXc_7yk{-OAhE3%}dxF%p+0Z8s0nYBw56x?fb zIr_;1E&6)6?(0ZEwB@Xq<_Bt#)P)V(I}Vs%=$~}L+7e*#`dP_`oQ*IuJaY5Gu|^;j z1knf5VcvvmF58R5c-&(&NC+BjKs94&Zzm2`pu+n}b(-=eC^cB^&4l47Y7eA{k1H5P zT&&ELR8%8KRY0G5vU>#O^%UGDzch+?<9#~0#YPYm_TC7$ErI&PUu?ktu9p(MacWt>B7!1R8y_SM}H_7Tr}e zq+)w(4jlVmSG{ucLw#?*ub;V41VQyJC%+_?fIp4WwVZ}BxNTZu^|v1T<*gJiN>lCyJ58Ub{2bom_}8w`IO)TZT;CUiptV0Wzf#Z%8JuK_6`;Z|uEq zM9&J&PICWhK+Y$pLi!i$(Cf~!4`o~8c8ue09FU4_=?>fbg)6wgN_&V~QCKP+*TbRm=Ms$_t)_>oG z8qtBBqFUFCPW1Do=~2eFZFs&|yV&B?iA0l&k`g#7(EI6cPnBFt;U;slUwcnElA3h# zS8u`lgt5tvT|*y;)1q=v`cV(L{6A*DiWPy#XZlCC*PFrCgVP+_eu3JA=fokYI){o;AcJZoc1xsz*QgrFD_$Islw)gBs%z@KYJ^L8zqbz2L64B4Ak#mbALlm(TG4K3QS^Qat z4E!V4AMOi6)VmK|8r56C&p_ue?b}A+=Rn1uDH{=!y3FO6`Fc1vamUIk_7$r3>v_oY zyd0Dm>1QUVs)6mW=D8PrwQ%!T=eLINDs(05;L_%^Dpa&w7Ity94JBO6pS;achv<|q zrYmrCqaxFauP2YUBW14Q#dElSPlQU~#DaP$xP7P@d~b#Sex1td@3J~d4_f|ZLXJ7+ zWZ&2pbTgr9qk!cB1qn7kTfG<8$38;JM5cvTcrUsUzu@*d1%-3gZ$&BN|1UEkXZ=qa z3h*#0o7+o6wBa?0qZbJ1H;vbMkCgVe2h5LE_-*#C4W{G+ zS*=rWt|N3+>azMM%$*UNU&6UZ^qMB>yP7z-&N%VzDmY{RfM+4gtO>Yjj7C3v?hL9* z9i<8fFu(4F(CAI~L9{{Lto=Tx8*N9r=`%cTfQ#f@>+I(m@Ij!$UirKMNNMToSuX(u z6$N+Qo?$-P2fpwGj%F|jBA1b^>i~D^-S9hc^P2D@^AdCCdo1TAH zkt>9&Z)DRC=GK9?8%NY_|7v`%unTyI-$SYj`9dXk!Xc-Z+SvYGCYpC#i0}Td8V=f5Yk`|qf6RD;#Hrie4Xl$Np*$}CsR{5c7T zY)*!{fKo0u`ibo@icpc3Yry9&J*{X1ALfdg@m3|N zT_?e%y+beOkN3hz;tzSb)7W2QJ&M};YvF^XVYCXxFye4jFp5|mL|%hx^sC9~NSQh< zmy@>|?a3eJI)5%61#G8|Uq4@st``sV(=Mcm-X2DS&!l>W(*hFq7Q9ZrlNG ztxTJ2n3KzUyYjU8|G5)-wP#HmlhL5%rsSjnqP9d7Q)+Hh5p53Zm3$!v+RShz%W&)6wH+hSN)&5o3)ogHJy~pgN<&;eEqGM z<7h8@-{tD3e}#JkKe_GpeQtz#$GtD?T>VHp*+?e*GUl&)-U<5TOhVr4DP1q3LeP;~ zGO~Yt-dO((R+PV%jAXTGp89jWM^lUW%6{W-AfotbGV9G^;47-2iMZGZFC+6kB0VeM z4ITf9Gy(kFIGB)IIOjuHkYB;kC)mGmFkGr_I3HfiG0xM6G(ft|(8sN(8SqTQxQm6V z3$C(7ecb<#2qqsh-$$Hlf$oqW?>(|fNHdV~40CW7YW;Gi+QGa7Idy2;1fA(YedpO$ zjfF^v`}Wx9@417B#%7V_yc7vZaSJTaJ;XXN`J+cVhPcNfpu4iPrWvaHgQn(Yn&Fsg zTIR)?26*@V9u33}qDx5%jK$0(Wc{4pachNyq9#xF+{M1UtEb)K5-`^*{l*T{&edj+ zDEe@CjtBPY67rVUc>0kRy*1&Y4+&*Ucnwaf3?g=hU+JO;@_}gk+Si=3 z0J!aGY}XdDz~-h+B}q6PC4@bRUV#_z`VYtW1iu{|uBo|oMAQK^>8WVWtXhM9pp;W~ zPd-Fjikv_SY4Fzfm5MJH?xFJ>EKoDSO$N%GSC!^0Q5f;miRjHJxJ?*6CW*N^;K3HV zj=4GP`%Rks8PySG@gp<ssb~Rt+o^1tfd)$8*-ZJ>VeY)c$s*3l8bq<_u-we)%7& zvn<$mXn*^2O06p`R}{?vdlY;nxKI?!&{4U~lA_PW`?Pp8oeKp1HCPf6gn%npf*_ZdJjC z#}el@D-|P)B%5%*j*{S}Z4-=kc&Q#Q=s?zVOF}&dgHZ2j1)csJGXRRn|C&1S*9)ug zKg!qyDdqCYdDurg)SaM1y;Kb6YQ+E3S1N_3EAOUO;!7aNPL?86pc+Ww_u4i%Yk?}s zbgiowb8p95N{Y2=VC&_i@jsPv5V*Vu-$bfm;tN4G{CGNYHD*kVmQF?PCOJ>JUX>%0 z8?Ft%8|^@Qi1QBOc?%+*wyi-b>2UL&wNi*lEW|M~2|O0fM~8B+n$^j>!srjqGdkIM z&~C<8EAgQW7@n)hi3^oMK%V%T#0>6RJ)jw?ZO4u(+Fs3Xq10j zzV zXl=6vhwqnMxbQu%b)uN*$)|E8PP$lkB)Akb{z-&WniJqy=xCIoTNzw5O?lVtO+pn( zc2OF4{n5)iC+BHS2O}ENWtJtU5Oi5AK46Tl6g@8Xzx~=P6J_Ka$y6%o7VThmzt3 z8#{o^fw);}(+MJm3Yy6{A13y`S3|U-10v0b?RT(W^W4666&m(9cwm(0RH>Q>EN}mW zJ~`BkJY)V*@0=u}UFR4j@1NC(a;1@naJmisOQ3Ij>P5`p z%HyND@31t%xbilr3?Wv@3r;X`3$2sozbV{ z@OcT$+vvxwO4Qp-oxQbFh^#b)@5b8@&@HJG-lq@rp@`ttc!6Wh@Hzge{22n?CqGi) zF%}xc{XsK)_wJJ5)3n*6Fxw{dK<101rc@hp{Gh3Qz8vc!0@EfJd797!r~JfpP9v)O z6=FqxstMKHe%GZ;1xRUhHHWpb9(}iY`;peQ5$*dLi+pT|Fy&Ya#rKO5^Qko?{A34+ zpfCN#^{sI$$atX*evx0^z&NB@URd&kvLw4)GeGe#mz;?g>Y$*1z-~TN6 z_Xu4BY=krR&dD{voeHhdBQy1&*y2I4!Z?f`%*m~5XyzavhBwUZl11q975V>^WvUT0 zNatlA7=q1MLK)veHJoitQQRk53Daht#or8TAby?BmeQ&L>>0K!!>VfF3jB*6w6BKo z7QJDXfoiBd^=vndstz)2?8bKjh9QSyFT+%A7{XXZUw@VxhFp1$rf;W~O5n9MPq~W82C! zSAh2x!V+5N+j*$n}2X-Vk;n-R*6s8i;MmIQ#?qEg63} zu+RP|gA4v=XHOR5y?go+YX=oa0CG=!>&R~@g0oM%gc+ABVMV(kfg5wI=soMif@Le=p@O5CuzL** z53@cu8m)q(toeh=_bVXz?z8?$_EPxka(>}abt&%G+!WLsu7Iv)zeg*)jnG&vyL0&*Y7+d@(Q_Y8I`a6H+FegFgut|U-M;ch^yM1AD&4z6w zy`zGX!!Tq1;x}n{7?Az08HM~12tEiSndA&Y;rnJ?u|vZkmXbHEH$McFEM`hWl*3>& z_@vCnW)L)GwJmnn2XXG>jsEIrHTJ9Pd6dmn18?n*KQ~UdVSbO`MbZb%m)HJl+{f1p zRx-U~f6umns7l!7nk;-D5!LWkcW;0v93BCCjrnN5aJa^snM&keszg=FM1bPM!kP(! z>99gkHxzU=3+4-7t}vSxqSxx9j~4J=qJV>D*R!b-^thXC^2CZEX0%%{IK^BGjkby?92927U*V$3s6mU|(N$iSI-gd|Tu& zGP{l6&)?hQ-jj7BCzGDOlj~jRmYbWPhekJIa$k#8%NYR?nfEHvoBa^wE~VS6IRrD> z#&)@^-G~@=*YI-*3B^dSO)DjIBK5T>rK7B!D59^EPsgbXRV7|vNLlDYE58XsOFXy_ zWo@=uJf<2BZJv92z9|!#AKciM?Jq*wFS@F47M7!C)yVqiu`Td5>iof>RDg@bMiwV% zh99C|d(E}lVgA3>)06i)p<{E+{M(-va61*v+b7Tl-P;XLogG-GY;aPF(B|Wo_{GR)1~R_z=PVoHXd`{@h^3!T~J9tI4-mcT_1m9PcUEYb65TLC- zo%ysK6ya=bCsR8lUCX_xj(umX-|83W-eaGn$;zqLk!VOV{ca(r6$`bwH@XJc5`ZG$ zzVYKp0;nB$Et*Xtfa{#%g-HDhDD7>azJ{NhI*%K@IjhaEk3O_TYpoeP6i$EHy4?aX zxAnNngJWUKwB@0P!+U6_vwI{m6%4}r>|Xp|hk2#J^dIe;Mu^s$2w0HCJgvuXtxqov z0xz{}uRYfg?43`Y2{1{7Y<+`^%Ug{QC$!yt{aGytZ!S1>S7LpN)+tCht`R;-uovzx zXoU0K_GE4~SZ7?^di!=h7U@SallG*e5TeVj6g-uN9$e2hOvCcV-DF**5Oo{NV077$5>doL>X^!LH zLr*trpz!$bB-vO1k;tYWde|R4{`rP^H1q?S_R6vJ)_ZDKtSt_{`1eDWg$HDPo!^WBN4gR!U7a|sOT*4y z{NyrNj38zrRrdws3lx>8`}a3~S^6rJtn10-d?O3JJTD!-vrz>Z<}6FrcwW8iI@VNI z)CRj_iDz!ocYsqyR_V^MZkQEG-8Yywfby?DU~|BFQnyHklIV6LP`;5*p;?H4iOA6_ z*QLYYO36Md(byrVkXg{CW*>x^`61nW>_<#_-@8@*w-;Pj2JEBX3;E;43*-J|(h6&x8QoXc@z3Y$ zwu#`r3>4!Lwp08#5glu*jbi+qf?_8p9JfXr(Y%e{nT-h|P*Zu&mgt;G9s)pE(Wb{IJqImDOYeaBM@)O;?-T^71#ARcg zKjdv2&l20k`(hcF;4)_HtK>KBIxf==>8zTMIIu2aDR#7tF%PRt*(6@u*^D3DM0Ss1ATp2F18{bCKUk~rWZe*w=0yEWr~ zSMYgCXg$cq-U76b!jtqWeW0@I$vfTt5IEHLwD*EvD(3FqU{i*4m^8oS+@*#&QUA>K zHBQw7!vmwh4qbrW?D(jvy)pQ$HszA`c?>A`zh&NB83it;nzYu{QII`7j~whrf#Sc_ zvjQ6Uy>m~uJhTGmXAj>Kd~%>0B9g@~S76;&JW~IW>lf@>+OqyiFENDH^cA^3Y?Z?J z9FZt!SOj4Drp<+y08P^JzBwGFkp2QATf?2u{M4||>V^R#Ub2s8KktDiO}Qo2!aJ*<><1#SPr2gWK#xW( z&_(4EY|6S(-rvK2_aC%Jx^G5jYq$~+?tb2X+c6%czd7cs%^i=ZxvNPV-!MPHM<@cU z0k+fM9Jk|%M-Q@e2=)OP=<~_g$PezRXxondV{}RuI&%hBIz7ulAD9=(cerzrzbj)& z4k-%_ox3w2ViJMm4(a8){|twnh9`kmvIEFg&udkZatJk;$Z9Oj^dr#0e$6pR$cUvfI_9|PZj>nKrP>oOkU_OM@uXRPd7ftz96nvp)u1csB4(3d%#u! z_js6!rbFxD8f-P6qyh*JJFarI5BuqM{e$~)KHPXYXFqFlE0E{>Jt)3A3Q9lR_kMr3 z0O!hH?fD8@xcKto$tOcYF!bt+1zq(B=vX#Up1_~x+ zmq>7uEX1J8p&yI^!0`T?uJV$0D@ciWI|M8aqi*28}P4RhtnapKNsUGJcB@XCm;C#!> zy08*+2<{PK_F|>;%>sp|^LeA)%`l*1T2y|w4NA0<+oh%2U?!F%_~B+3JZ7|6ds5T_ zrVYgO=EhEFlG4m#|AG1bcV)@MRI)%sh@oIXJqe6^g=KEfRDj3F=3-XSCdd&mvpZ>5M_d`S)~~EA~_UtGPCH zhOZB0e01(Q6@dM^?}x+b3QFK~ZmXlMS}_PGiv9i5nu&gY<|j<<41%s0t(3xlgK#%) z%%_bT*Ea6I!Y)A7fbRW}zPb6R0qJOIA2j+!L~FqoDRg4t@VSTP){BfF@H^P3Ct3Rr z%+mGm)jzNSAGt4${8|oh)U?5~gi8nXro_&jel~!_FRWZ9-&X|`S=nT*R&nV4Kc9A{ z=rrV`W%-a{I~ia)PMuYypVXGfnHL%dgaumqnlKg+R^TH@PoN&BT2LxTEyPl zPm}V|Xmh{j+>b7l;w|@;QnwvRib*gSw{@X*m$U>K*DBZ@X)-pkZUt4=Y<*VzXB&~_ zWy|V&m{)39hhNDki>EXtB`y}#vvXTqI8}!->m%333`)Uu{m+C&Krd{3Kb2*al!UxH zmIaM(79oWS`>iQ{oNIaVELMQJ6ZjK}GF&+4vpHt1{=kF)hsgKuOMQ>;HNi*YEg}h^ z(wr!vqMHbF?qg0HTG?RnZ}`2n1>W+O-yhefvk(%qKyP}q4x_38zlHM)vT*Ai1W->^RKT>;hfr^(ez#I zUg*6XY`mk)hBVRzZmbJbxvpoT4p9%lsetkI9cgwSCi% z@5B-K5j9(~WzyoE(s`Svs8k%^UY!h{WAC<$j@LK zS`L?NtgKnyl)&W)k%^C+nZUq%CfQOJzi)@H?aH;`9I*Ex;^E^Rpx4vlKD68jWo-w) zb2v7_oTWY+Y|KH$#FipFNQy<(&T6uGTgwS-$C zZd7e;^KTS7Y25Zj)7}}tW+U9^kSBat=+36g4uQ($!|U(9M8Ve7+Hn>~d+v1AlV%H z&hi%SH4$EEB$y7sa_y^>YhSRxXq!T;u8{-_%t80qIGaG@k9@+pjw;ZsTz)8N9gC{H zZD=Qt6r)N%p>WS*#mL;3%S%Ht0NpukpSOD>9JFm1SR3_i;j&THM&C5q;o+s z!?2*&_OUr@5IoFOyav9KATiQb^zcF4mvHRt>sTG!Q+wZVSAH)CgeQVU3@&s6(@`fA zzU(1nc|>SBBp3Sz$>MHY!1w6_nbj&HLo+<4q+~_>#gMyB^(*Q_G~8keZE#x8KqKBI zi5|tcfAi+(yMRc{aZ4R-Icd=Y27#MG2(yme8X_`RKYTknuLY4W ztnVGm!Mxjl`LFENF?Yj#n$zaZ2u$Ak(qaCk0`wxVTOar7kR|&%{AZs`X1&>9U)U6#yp6aD_o~!B}i~WT-75bjRbvf%I0oI zb%V=%(Po%nELs=+m=yjt0-Zl+|2i-+9GGhd%HmJfBERXos2w*Od_a(zA7wuJQ zua3l|pndmV9@HpGM5!f7DV9Zr$a{Lspz(h0|NR}hn|R+Ss5~ie+Z2aH8nTb4PQ{?) zp`jxbKT?qr4Q0R3t9DeIL;a}BsRM1K4U2yuYeD+?J;z0ps?j&Q73a%rxUcy5KmWMd zdZZ~he9@(?3RSRr_8!$u25vcB=hsfjFyR-wkoGMdz7#hK6;g*n>)!tDvyX9a+`H5G z!x##E=Qk7Cvi;$w^a|B#ODG^qN7h;XB-EkRT_(Ce8LbZYQdkbRA{VM!(!Z`o)F#xS zmMu+0hkqG1^kE-mua;zS7ViJtNeV!MaR4=pawS>N3S{cJ4#d0y#Qq_6-E27zqCQQM zWppHid1lv9IhHES-#y%_da(?0A|;dG(^bGv&YOdeDf2;P_n`mP75v$iAvs|yMt(3?}T3+x+1T{yMtQ2d8?%+qD5h}&?FL);+= z&8>IeuE4yI^p|gGBbVA?==-+@`%`!y{)>6bU#SNQN!>om>OHV;K|IbarW?r5mgwb% z;vNvMs?UaDsldlfd+JtR8a!R}kIa_Nf)ffa8xHN(<9n{vm9~{8bfITfk_Pf1es)3Y z7b6L^57E>-{7r<=?RS!1B-J^_(jI48U4V?PoK`A;_4O%~_-w1i<0j zcbIcBl~Ku6Z=QwvpH((`jh7;$Wg3q}QW=sHVEH5Xpae-Jiq&ndCBcf_FyESI5HpgGGwTKJNf}(eJmx{=XBSDHh!@H~Z4LZ=VJG zpyGYXU&2T?92$AKl!^PUrn3`AN#0%Hn6sDsC9fBLb*Q}HjO&A<@D5&K+|&6LlYf8f zbiuneISz($B0RL-8Pm&2NBZBsznO;+l<ErUOq|8=-y{=~!q+bT8LD)^|X zU7kfV3WI+*b%Yh_p!`JtiGI!w#NRYNbL4FcVtJU8{q|%h@_8bn!-#z}H)Q7?dg@^R zZm4T?49HGOdxQ_M)BR*PQv;V@tlJRQYjN()(a`1g2*}QmS>8V$0|ncTkCb*|KrMFr zvp_>18VR2Gl)>Z##QNg*`NM&$O}XlqVYP~hT0n$sQZzKg2jy#cx1`ub+7UfDjNH3X-#2qHX zh1hbClTO~3_uZL@ad~GrT{;_`O>HV0#pj@nC2>ydQVR^H?-MY<`Wwd#e+5rY3$)9x zG>jkbfl*F7y8ZLv;GlYZ&kFA=vQ&iqmOu4EeY?CT?PeUPdPTC`EFOf*1#5xl{|w?h zo~);(0p=(D{j92SYZ&Ib$0&TbD)IB8(hQBmAQW=wBM$!FEb z=c!Ow*H|}Lg`KYIITH;jH-)k;VcnX-m7|p%=W7^kEyz9g^`p&NdNS{c9`u(lr}%Pj zGrFdK$>;CQA!z@nPA*P01RkSjPOwK1fcfB&9hT&BxTj0Gq!Et$PA{o)InK7j6f0BS z)Rzu;UA)NQ-8ur*nsYH(ZliGeYWwHP+UJ*)2T2p@Rq_WJ)1*?~&XP zWjk&22t0Lp=06hGf&8?_ztgJppp4W+VKqFD8u-coU@hrJ{&8bEGQ4ewiI#6-ZMqdg zZBnZRaUj#2`U0_fe+wL~ZJ2bYEk)DUkEmM?6ri=ov4yE99f=r52EUUfqI37T#eM}5 zk(Cpxd@#-fIM!Z%|KT_Z6?0Sga*gyMk?k@m`x{;8mFesGLSekm_g~k(s#ye`Cr;Wq z=9NLzPu?<`e4U)GHNW!DG)=)`qJIENsHRZ-cZ75ADh%{$Q)Hp3agk-5_vuCPX$ zS&gdAKvq-D215!FAi-;`-ol6VlK>InL~15@sXl!N zBeY_K?D-xLFZw(hfqP&Mzt6sAGE6`mDWR4yK|ltC{jFs|?bt1~seGCY=l7B|tA((y z?`N>zkM8q*@ZcKT(b!(hPv#JRA?r8_IlRo14L0?VLy+ud>urKJkH3(554XcTs^H_c zxMz-4aQmd?D(;b?Z#>F)as*B)mN2PcPQc{mL)Wd1QBWRW3hL#;de7#8o`I@V(0a7= zG-9y?ZcU7tbkkuDLejTDQQHRGm(i&b=9UDP_GjlDn85d0SESZqodFRh=YkQp3+`7B z>ZUbJf}?cz2_*)2k2u<%(DxN{Yq8t*Ob^bv>i<0TDYhBjO??|m!a8F%Db-ID=W%tT zM1FW3tb#jtI~4sG8sX9ES+Z}807suzslLX1iSg^Sd3w?#U?ARIk~lpGpS@;kJ^BWq zRkhUmCvQKeXey^ZiE-tQi=DS#p z?iK@5U^ub$yBR$WlQ7*qUyP7z?3L1AN$9M@AqL;)M6~dRMT|E&A3fZ76Shw;6P@w3 zyHT=%d!?9Ph}~boxdtaQ5yy#kG*SL(!Os%Za?qrtX!x^&etFHJ{qn= zg7YVz|1k;y@w=)w8eaQ@;~lCaR@k4Ag`7v#2Ad&|Y3bp2z8pAQ0ZyLu6{tEMb<}0H zq5~H!#!C9?5#6A9n-{4PRR^y1^BGs53xAJWHu4e?|HA&EvN+uTa+7EKgnyo*P4r+O?i{6Q0-QHX# z&g((~T2oX&Ed9yZA+36_<|vzQ9dw3QzA^Vs?S{eP?JN?5IiMx|kKS)53Xt}U z|JUQbg{U`2(oQor8}0jjkbxN41C^^e>QAz8j`Jwf)5<;EBO$OxVbzH_yUaWy0fPhh z`&JJS(j5k}P?7a7c%L1V)fMfaJ^~^6zbFL*PStU-w147u!3u@ALg(2a&vN5|p^V=W=>7_J>PbyKwZCfFn;mgU`!Cunv{dv48yhXVYXhldTr(PW1P=#t<8GXNQ?9JnQj7$XK`-mTgb$I>T39HEb(yj zC;_E!&Rgwg%!j;!Kb0n^98z?yCiW>6!Gqr}xx&0nNSW;L2odLrOfuJvG?R$vA<4u( zmcIqPuF@9&U6l>|ef}3u@AbipiQ$sC*F9jQ_apHY=9;9bI+lkFbbt`ok*gA>S*Rtj z@)QJ@Af?onoFFZSc#7mY4af8N{441D;H@K}eC$Rd0C`WS2=({K9;zmquh}B@=zP$EZ8$%Vav7`9o}R z;*G@pOTNcOxYEG#!xWWZLMBl5HFDb~wj$fA<;7B*18gUJc6{cEb9|F~wEUbW|sKQ1nK75s>ZIeRbz@9fW_F?ip9>*^OsT69KDf8(rNz8?09<0N8aybK0wpiKwuq4~;3>}C zmD_@Q?;N*e?wQoU>99W!;0NZ~R5@L|G)qAHsC~-?@cw+o{pwpToY%j4)&6?`+aN3o zsc^Oxr@(ZVEc4)na`f7{BG0O&8vFtVZwhZ!gQnJ^zyIMHn7NnCR`oC$j(!`gt?BUs z8^JA7>IWjyvFe~>OKn6^|J1?+1hSBs)Lp;A@e=eo{>v57OTI?zX<-pQ&Uc@0f&=y+e%G4Na_M# zfL}M*-76HJ^XdSvcdPrByOTlDGMzq*JP)esrA3)1Qla?!9{p9hEGQkiD7p1t1BeT( zG;?E)agg(1=NLsh?2)~4IF0=mUsy-@AIAf*45&Ik#Colu^7&J$@9JRXuq1Wx*=krd z{j2`1D-4#XJhmBe{w3)svUxw83x<}Ec8R|Mc?<1MOW@xR7#DIT>eoTyNhPZoytheW z5FWm);0ud)Ei!&%9Z}JOo%6m6-dk@ymx_GU20dwZ`|Oi@fpl>ot}rYIvWo{8!U~(f zT&L>Wfx}Jk^Y+gYC#FjHZlm}tR-Xtl|M}S~60tA)vG{!o_Fgb;Q#<0TTLxAVw+q}1 zD?yEmHTcv0aae2~&hn8;07WCl->*#4KySR?;X*p*k?239a8+x8%ly69lRxGmyQ0(D zu76VyO~GWXwP!bsoN4XVcry$llm_LahNIy7+f-l!`y?0t)AWjJY6Q*8pVP=OfAD#} zRnBGu_7!Nlx3wyFgMWapm8LJ|!d#Bt$Vw(5E~#wdjvWa+J{Y#v%wjGn*AU!i|t*C_uNZ zkbyY<7|bpfJwTh=!y*iS`{8ERyZG<8_f`|P4eO%Ux_ zbMD|M+-$aJV63YHE#DlLZ^qsqfWQh_m(1NJ5L0dz55+n8vXlCGRI_z(-qY^AHA^eXX818MsMm%r zh!k7IVqe`>42{p!ahx03{&xIa*mH=97*V|VwGQ{(qJw|JJL|)0d`X_+77f3 zQf+6K(T8rOQJP!b9Re1r(tC4+PQ+1prCw^W2KffC3?9?1L3HOiZUy+)!qJ<-CGG`X zz`Wa&h;ZqhUKkTOCVcI5FAN-{fq)Z((BUfn->cjVC{y*ln{%uUX|h!I*q>}cicOJz z*6IDAO5V&CmynN!+DlT%wX>l1>e^AV=tA7{)y$)zPzINLiflsgy{qUAlg~+sMAUhM z&w&-^ed)&^#ZLWcMMB)Y!H*EWw+6-WEu9_$(zhu8eT75d`moo{ovazi*xtX7yNtQa zl+>#8e!UPeZ~CAO`%q3OoXU^?)dFpgIwikN_MtyB$xe*i-H7*2KQyrgJ_sAj>z&fs!btL3yV-A;r)#^Bdy7m~rT z`AzstNISG2wyobX>wpg}wc7vhKmC3G(D7t^f0it3(G_&hK{Te^%0Zh0Fl{lt)t5O8 zzIwZQXH+l;`*xq09YYm-ljqC)gL9EQ#Z@f)JXPRyp#FAyHUYxp77f>X@qJo+mTo^W z7s&3!x*l670Ef-^ZhEYHa?=$i$uEvTkp;Ezf9lzAR#kP_HL?Q&y#$z*_`6`INhP8d z-$Oh;R0x;kcR<@K+YhHOs)A|kJFBNE#)-cg4A_0GV(1HT2(+*3gN-=2NmraS^w z6bI)n+{Ii+p|+y`UJS#&tz&cFx5MGOc3Obn?P7>_Iwd``)eXNON#^O}5xBh4XL06K z5M)i>taxG2fnpd6vkEji(XCZhim7KEsQ#?Ab9HG8B9x2$ca^>cRl1CJyO7!thf-gm zhfoWeNE{bpDk37rb;FZh-#Sq*pCXH%N*6M+mS0m-Z9`$)#kbcnk0{RSNLwI(Co&Bv z;*dA(Lfc%^X|6`yC~MP+-7u&Z6$sd}94qZbr>8^CXA=7mJ^#+Ygh)T)p1a^McD@ar zxFpVKY}}4~&k>MjcPpY}eXAb%un*rWO4H?~+Y#q@(?bN;p7hJWFcCU_zm^wHQr>4}R19Cde$r3Gy#!p}ADBjcjC8>tX z)i)yTk*ROjR_joR>BCE=Eku+oc&mF`cmOrN)D|$}9Y!jA4;5px2a!~pk-Duyg&e{BsD2BwmfJlOOsYMduxlh1$k(gb>0}sEnktR~c!zLsTjvLMpOn zR@r+bqaw-9-t)2dg~y(cy^5rgC?cfr-tS+1`r~xY=RA7u`@XK<@B2k&GCf*5Ys2W~ z*}{sdE2AjMxsO%Qh=eqN;{FlQA+)ZQM>zIr0Nr5>8{M2miXtwL32v-rx*zZ(#20`dy!CHM(932w?6cMW`6%EihguQ%d_@<)hIHaImh`z zuL_O)_XzYqRUv^^h87v_Mzqhv{-E=LMx;8b)kVWxiaH-9@O+~#hH|d3_7eVFh%1c! zbit?q*k62&)OCFW`Pq>H1?jmEGaSp`9GMRFzl;mHM=^(^Tq3EAj{v9N^BRf8W3Jc( zhl~BrmFPW~=p6`eK#azxJ2A|&jjGBO+{wEu zP{T_DvzOn}Q15l!f~V^h$k#LPbtvI%&y^YGeL`3Z2~A2(b-J_$BYoI6E5Nho2s z>=|wO0Fr)S&B1~5g50u39!z*2QK<2a>&WOZyenqvpuv0HE5ANQL=(GF7W26ON3LFE zz@7WAL#i9Obv=K6kfI$a59~Yebg~WENxRoGMiEim@cHHEmBXl*G2tu?^Dw&B^?Yxk zrytR8AH1p7J%F6*gzOjealTH(m%Y_!02#3?DfYb@hT{2`CXxQwH?Lzr$fg=Xlt$TR z|CvlcHOnX&h3+Jrv1|T*Gh+e_eU`qaEta4Q+WGEc4@%Lyk|YlnjWWc4sFEymr5E*> ziQf$H>_qzXd5V%T6^QW9wM#|11vNfs;eU9g85uKZDXqLIMJ!Lgub@NKsP*`rVbKeX z=q&q?$hkk1fU67*o}SAzB5Oo)%BlO07?mN0&iOBA za1MQUO)_rWb^;t(J7}Lr;C;G~OtI~y31|&xE)j_-M92M9_P3oaLCPCT3@Sg0kj{M- zU1{8dlse%eWfxR|YVv6!%iN2Qnqvm*9R6IZ-Yw7nzJT|a@A)rg2oO*`>&@BokMoeh zM4^U;Tnh3JivGNKGX)Kdad{GZ-=M8GZh!I%tI(xbcr@%&izF=r4~V80pvH5y<`a>* z@T0M>_injUe_C8EkwcSBXz0_d@6441l60`7m$-Ot8;(-gSll6MSql0C1Npx@ZX5LBtR zgY%4)gxNcXilX4gg+{vtSKNme%s#8}1W*by@E*Jm$U3XsRZOY{+5c_V5?Cgp-Zl4c zwOkFTjb*v7D!v8fT9ey51KR9%2fY+liu!*V=l262~8(*je5B2>jmPfK+>c=SQL_O}~(Yt?B6-)<_ z408i}$s!<|zF{`C(GI&_{ep+~dZ3KAxpTax3wV4E+!u?^0qI47rcB)5m%T4Yb=$rZ zSc$Qh`V~6B{$dd0Nvcfr?1rU_S9<~G3_Nd5*(!i-2iGQ%NCI+EBfRk{%0@fipZ;UQ zIE`T?-GlW5&B%_u|7<$-C~~}8k^23?F#7V}I`sq7QIsL6bkn#K^M)#`O)DoFke?i3 z`BZB&T#8@Ycl=-n3`CvgW3g?86;m6A<8kfqQ9`xiJ~aWknllT;xm6(cOWBIihXGZ{ ze@bI3NG+CQ~`9O z2zjr<&s*NN-q8TS#URjqQhM+HQv=(J3 zfz7ImqCFRCY+3QTc-^Opal zTwV=ur7&N-L^+H&j4taZ3tb!#C)jVo6d`?~J1G7tqG*23VKCWRKV2B4&`R^s-~#h$u=Q}MAS=zfT_>;RtQ za37*dZ80lGq95o_YEac<&eDY1Y14Y7;ud(PE3OZj4O{W#4^^P%L#&>gNmVF9|YgBi;?~ zTx*d^BZpKO5=Ve2@2f^P;vJCL^fv zlfIX)YZlshAi<`hUW}+PX<|4x0bQF`{<znM4_GuebB0kYS+{Z8Ep@yqT%E1mKBs%u5)#gkG3L5CP z6ff>Ukq$Q1ulwszF~yo=u3;;>d~owr@WnPXc>ywKOe@j(>{o3Ldv(aZHK@&MAqwh@ zZ4@oX65-~M*x=F`4;c60W0|o_MJC_(XT@Fh$GsEQU4<{)>B*fNFPNvwsTiIC_4onho0I(dQ?_bM(bY5y1!?ne$;c0Et852Nj&*=v4D+H=b7ESrx8ZOWnh z5AGD9#UK?cFSlY;L_JB*8a9G9)Y!;}gho+&)aue1+Yw}C6B+pW!w8~(EjAv!AOBvA zagAnp7?CIr@aq2^MvBHPjGN^{XeHj2*|UBSxlI{d

->>zV^IMI|ZVbzm*}cT+6f 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

njNoD3DgP%4Zm}4f)}}KE3_9| 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 literal 0 HcmV?d00001 diff --git a/src/TorseurCIH/slice.med b/src/TorseurCIH/slice.med new file mode 100644 index 0000000000000000000000000000000000000000..167c676212df97fe927187dde63bc17e776dd5b4 GIT binary patch literal 330141 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%0DDVhgro7xv-+4&f+HAO>e~ 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�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

->>zV^IMI|ZVbzm*}cT+6f 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

njNoD3DgP%4Zm}4f)}}KE3_9| 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 literal 0 HcmV?d00001 diff --git a/src/View/CMakeLists.txt b/src/View/CMakeLists.txt new file mode 100644 index 0000000..d34cbcb --- /dev/null +++ b/src/View/CMakeLists.txt @@ -0,0 +1,73 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +cmake_minimum_required(VERSION 3.12) + +INCLUDE(UseQtExt) + +# create a plugin with a custom view that shows up in ParaView's multi-view +# manager. this plugin also contains a custom display panel + +# moc the Qt based .h files +QT_WRAP_MOC(MOC_SRCS MyView.h MyDisplay.h MyViewActiveOptions.h MyViewOptions.h) + +# invoke macro to create sources for our custom view and display panel +ADD_PARAVIEW_VIEW_MODULE( + # returns the interfaces defined (pass in + # GUI_INTERFACES parameter) + IFACES + # returns a list of source files for this interface + IFACE_SRCS + # give the view type + # With MyView.h implementing a + # pqGenericViewModule and MyView being the XML name + # for the view on the server side + VIEW_TYPE MyView + # the XML group of the view in the server manager xml + VIEW_XML_GROUP views + # the XML name of the display for this view + DISPLAY_XML MyDisplay + # the name of the display panel for this display + # With MyDisplay.h implementing pqDisplayPanel + DISPLAY_PANEL MyDisplay) + + +ADD_PARAVIEW_VIEW_OPTIONS(OPTIONS_IFACE OPTIONS_IFACE_SRCS + VIEW_TYPE MyView ACTIVE_VIEW_OPTIONS MyViewActiveOptions) + +# create a GUI side plugin with the GUI side code +#ADD_PARAVIEW_PLUGIN(GUISampleView "1.0" GUI_INTERFACES ${IFACES} ${OPTIONS_IFACE} +# GUI_SOURCES MyView.cxx MyDisplay.cxx MyViewActiveOptions.cxx MyViewOptions.cxx +# ${MOC_SRCS} ${IFACE_SRCS} ${OPTIONS_IFACE_SRCS}) + +# create a server side plugin with the server side code +#ADD_PARAVIEW_PLUGIN(SMSampleView "1.0" SERVER_MANAGER_XML MyViewSM.xml) + + ADD_PARAVIEW_PLUGIN(GUISampleView "1.0" + SERVER_MANAGER_XML MyViewSM.xml + GUI_INTERFACES ${IFACES} ${OPTIONS_IFACE} + GUI_SOURCES MyView.cxx MyDisplay.cxx MyViewActiveOptions.cxx MyViewOptions.cxx + ${MOC_SRCS} ${IFACE_SRCS} ${OPTIONS_IFACE_SRCS} ) +# one could combine the two plugins into one if desired + +INSTALL(TARGETS GUISampleView + DESTINATION lib/paraview + LIBRARY DESTINATION lib/paraview + ARCHIVE DESTINATION lib/paraview +) diff --git a/src/View/MyDisplay.cxx b/src/View/MyDisplay.cxx new file mode 100644 index 0000000..77b6318 --- /dev/null +++ b/src/View/MyDisplay.cxx @@ -0,0 +1,36 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +#include "MyDisplay.h" + +#include +#include + +MyDisplay::MyDisplay(pqRepresentation* d, QWidget* p) + : pqDisplayPanel(d,p) +{ + // just make a label that shows we made it in the GUI + QVBoxLayout* l = new QVBoxLayout(this); + l->addWidget(new QLabel("From Plugin", this)); +} + +MyDisplay::~MyDisplay() +{ +} + diff --git a/src/View/MyDisplay.h b/src/View/MyDisplay.h new file mode 100644 index 0000000..04ea437 --- /dev/null +++ b/src/View/MyDisplay.h @@ -0,0 +1,38 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +#ifndef MyDisplay_h +#define MyDisplay_h + +#include "pqDisplayPanel.h" + +/// a simple display panel widget +class MyDisplay : public pqDisplayPanel +{ + Q_OBJECT +public: + + /// constructor + MyDisplay(pqRepresentation* display, QWidget* p = NULL); + ~MyDisplay(); + +}; + +#endif // MyDisplay_h + diff --git a/src/View/MyView.cxx b/src/View/MyView.cxx new file mode 100644 index 0000000..2fa721e --- /dev/null +++ b/src/View/MyView.cxx @@ -0,0 +1,116 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +#include "MyView.h" + +#include +#include +#include +#include + +#include +#include +#include +#include + +MyView::MyView(const QString& viewmoduletype, + const QString& group, + const QString& name, + vtkSMViewProxy* viewmodule, + pqServer* server, + QObject* p) + : pqView(viewmoduletype, group, name, viewmodule, server, p) +{ + // our view is just a simple QWidget + this->MyWidget = new QWidget; + this->MyWidget->setAutoFillBackground(true); + new QVBoxLayout(this->MyWidget); + + // connect to display creation so we can show them in our view + this->connect(this, SIGNAL(representationAdded(pqRepresentation*)), + SLOT(onRepresentationAdded(pqRepresentation*))); + this->connect(this, SIGNAL(representationRemoved(pqRepresentation*)), + SLOT(onRepresentationRemoved(pqRepresentation*))); +} + +MyView::~MyView() +{ + delete this->MyWidget; +} + + +QWidget* MyView::getWidget() +{ + return this->MyWidget; +} + +void MyView::onRepresentationAdded(pqRepresentation* d) +{ + // add a label with the display id + QLabel* l = new QLabel( + QString("Display (%1)").arg(d->getProxy()->GetSelfIDAsString()), + this->MyWidget); + this->MyWidget->layout()->addWidget(l); + this->Labels.insert(d, l); +} + +void MyView::onRepresentationRemoved(pqRepresentation* d) +{ + // remove the label + QLabel* l = this->Labels.take(d); + if(l) + { + this->MyWidget->layout()->removeWidget(l); + delete l; + } +} + +void MyView::setBackground(const QColor& c) +{ + QPalette p = this->MyWidget->palette(); + p.setColor(QPalette::Window, c); + this->MyWidget->setPalette(p); +} + +QColor MyView::background() const +{ + return this->MyWidget->palette().color(QPalette::Window); +} + +bool MyView::canDisplay(pqOutputPort* opPort) const +{ + pqPipelineSource* source = opPort? opPort->getSource() : 0; + // check valid source and server connections + if(!source || + this->getServer()->GetConnectionID() != + source->getServer()->GetConnectionID()) + { + return false; + } + + // we can show MyExtractEdges as defined in the server manager xml + if(QString("MyExtractEdges") == source->getProxy()->GetXMLName()) + { + return true; + } + + return false; +} + + diff --git a/src/View/MyView.h b/src/View/MyView.h new file mode 100644 index 0000000..d2b8b96 --- /dev/null +++ b/src/View/MyView.h @@ -0,0 +1,71 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +#ifndef _MyView_h +#define _MyView_h + +#include "pqView.h" +#include +#include +class QLabel; + +/// a simple view that shows a QLabel with the display's name in the view +class MyView : public pqView +{ + Q_OBJECT +public: + /// constructor takes a bunch of init stuff and must have this signature to + /// satisfy pqView + MyView(const QString& viewtypemodule, + const QString& group, + const QString& name, + vtkSMViewProxy* viewmodule, + pqServer* server, + QObject* p); + ~MyView(); + + /// don't support save images + bool saveImage(int, int, const QString& ) { return false; } + vtkImageData* captureImage(int) { return NULL; } + vtkImageData* captureImage(const QSize&) { return NULL; } + + /// return the QWidget to give to ParaView's view manager + QWidget* getWidget(); + + /// returns whether this view can display the given source + bool canDisplay(pqOutputPort* opPort) const; + + /// set the background color of this view + void setBackground(const QColor& col); + QColor background() const; + +protected slots: + /// helper slots to create labels + void onRepresentationAdded(pqRepresentation*); + void onRepresentationRemoved(pqRepresentation*); + +protected: + + QWidget* MyWidget; + QMap Labels; + +}; + +#endif // _MyView_h + diff --git a/src/View/MyViewActiveOptions.cxx b/src/View/MyViewActiveOptions.cxx new file mode 100755 index 0000000..1e8b6aa --- /dev/null +++ b/src/View/MyViewActiveOptions.cxx @@ -0,0 +1,117 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +/*========================================================================= + + Program: ParaView + Module: $RCSfile$ + + Copyright (c) 2005-2008 Sandia Corporation, Kitware Inc. + All rights reserved. + + ParaView is a free software; you can redistribute it and/or modify it + under the terms of the ParaView license version 1.2. + + See License_v1.2.txt for the full ParaView license. + A copy of this license can be obtained by contacting + Kitware Inc. + 28 Corporate Drive + Clifton Park, NY 12065 + USA + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +=========================================================================*/ + +#include "MyViewActiveOptions.h" + +#include "MyViewOptions.h" + +#include "pqOptionsDialog.h" + +MyViewActiveOptions::MyViewActiveOptions(QObject *parentObject) + : pqActiveViewOptions(parentObject) +{ +} + +MyViewActiveOptions::~MyViewActiveOptions() +{ +} + +void MyViewActiveOptions::showOptions(pqView *view, const QString &page, + QWidget *widgetParent) +{ + if(!this->Dialog) + { + this->Dialog = new pqOptionsDialog(widgetParent); + this->Dialog->setApplyNeeded(true); + this->Dialog->setObjectName("ActiveMyViewOptions"); + this->Dialog->setWindowTitle("My View Options"); + this->Options = new MyViewOptions; + this->Dialog->addOptions(this->Options); + if(page.isEmpty()) + { + QStringList pages = this->Options->getPageList(); + if(pages.size()) + { + this->Dialog->setCurrentPage(pages[0]); + } + } + else + { + this->Dialog->setCurrentPage(page); + } + + this->connect(this->Dialog, SIGNAL(finished(int)), + this, SLOT(finishDialog())); + } + + this->changeView(view); + this->Dialog->show(); +} + +void MyViewActiveOptions::changeView(pqView *view) +{ + this->Options->setView(view); +} + +void MyViewActiveOptions::closeOptions() +{ + if(this->Dialog) + { + this->Dialog->accept(); + } +} + +void MyViewActiveOptions::finishDialog() +{ + emit this->optionsClosed(this); +} + + diff --git a/src/View/MyViewActiveOptions.h b/src/View/MyViewActiveOptions.h new file mode 100755 index 0000000..f79dfba --- /dev/null +++ b/src/View/MyViewActiveOptions.h @@ -0,0 +1,86 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +/*========================================================================= + + Program: ParaView + Module: $RCSfile$ + + Copyright (c) 2005-2008 Sandia Corporation, Kitware Inc. + All rights reserved. + + ParaView is a free software; you can redistribute it and/or modify it + under the terms of the ParaView license version 1.2. + + See License_v1.2.txt for the full ParaView license. + A copy of this license can be obtained by contacting + Kitware Inc. + 28 Corporate Drive + Clifton Park, NY 12065 + USA + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +=========================================================================*/ + +#ifndef _MyViewActiveOptions_h +#define _MyViewActiveOptions_h + + +#include "pqActiveViewOptions.h" +#include +class pqOptionsDialog; +class MyViewOptions; + +class MyViewActiveOptions : public pqActiveViewOptions +{ + Q_OBJECT + +public: + /// Creates a MyView view options instance. + MyViewActiveOptions(QObject *parent=0); + virtual ~MyViewActiveOptions(); + + /// pqActiveViewOptions Methods + //@{ + virtual void showOptions(pqView *view, const QString &page, + QWidget *parent=0); + virtual void changeView(pqView *view); + virtual void closeOptions(); + //@} + +protected slots: + void finishDialog(); + +protected: + QPointer Dialog; + QPointer Options; +}; + +#endif diff --git a/src/View/MyViewOptions.cxx b/src/View/MyViewOptions.cxx new file mode 100755 index 0000000..3da5fba --- /dev/null +++ b/src/View/MyViewOptions.cxx @@ -0,0 +1,111 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +/*========================================================================= + + Program: ParaView + Module: $RCSfile$ + + Copyright (c) 2005-2008 Sandia Corporation, Kitware Inc. + All rights reserved. + + ParaView is a free software; you can redistribute it and/or modify it + under the terms of the ParaView license version 1.2. + + See License_v1.2.txt for the full ParaView license. + A copy of this license can be obtained by contacting + Kitware Inc. + 28 Corporate Drive + Clifton Park, NY 12065 + USA + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +=========================================================================*/ + +#include "MyViewOptions.h" + +#include +#include "pqColorChooserButton.h" +#include "MyView.h" + +//---------------------------------------------------------------------------- +MyViewOptions::MyViewOptions(QWidget *widgetParent) + : pqOptionsContainer(widgetParent) +{ + QHBoxLayout* l = new QHBoxLayout(this); + this->ColorChooser = new pqColorChooserButton(this); + l->addWidget(this->ColorChooser); + QObject::connect(this->ColorChooser, SIGNAL(chosenColorChanged(QColor)), + this, SIGNAL(changesAvailable())); +} + +MyViewOptions::~MyViewOptions() +{ +} + +void MyViewOptions::setPage(const QString&) +{ +} + +QStringList MyViewOptions::getPageList() +{ + QStringList ret; + ret << "My View"; + return ret; +} + +void MyViewOptions::setView(pqView* view) +{ + this->View = qobject_cast(view); + if(this->View) + { + this->ColorChooser->setChosenColor(this->View->background()); + this->ColorChooser->setEnabled(true); + } + else + { + this->ColorChooser->setEnabled(false); + } +} + +void MyViewOptions::applyChanges() +{ + if(!this->View) + { + return; + } + + this->View->setBackground(this->ColorChooser->chosenColor()); +} + +void MyViewOptions::resetChanges() +{ +} + diff --git a/src/View/MyViewOptions.h b/src/View/MyViewOptions.h new file mode 100755 index 0000000..a3418c9 --- /dev/null +++ b/src/View/MyViewOptions.h @@ -0,0 +1,92 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +/*========================================================================= + + Program: ParaView + Module: $RCSfile$ + + Copyright (c) 2005-2008 Sandia Corporation, Kitware Inc. + All rights reserved. + + ParaView is a free software; you can redistribute it and/or modify it + under the terms of the ParaView license version 1.2. + + See License_v1.2.txt for the full ParaView license. + A copy of this license can be obtained by contacting + Kitware Inc. + 28 Corporate Drive + Clifton Park, NY 12065 + USA + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +=========================================================================*/ + +#ifndef _MyViewOptions_h +#define _MyViewOptions_h + +#include "pqOptionsContainer.h" +#include + +class MyView; +class pqView; +class pqColorChooserButton; + +/// options container for pages of my view options +class MyViewOptions : public pqOptionsContainer +{ + Q_OBJECT + +public: + MyViewOptions(QWidget *parent=0); + virtual ~MyViewOptions(); + + // set the view to show options for + void setView(pqView* view); + + // set the current page + virtual void setPage(const QString &page); + // return a list of strings for pages we have + virtual QStringList getPageList(); + + // apply the changes + virtual void applyChanges(); + // reset the changes + virtual void resetChanges(); + + // tell pqOptionsDialog that we want an apply button + virtual bool isApplyUsed() const { return true; } + +protected: + QPointer View; + pqColorChooserButton* ColorChooser; +}; + +#endif diff --git a/src/View/MyViewSM.xml b/src/View/MyViewSM.xml new file mode 100644 index 0000000..0ac91be --- /dev/null +++ b/src/View/MyViewSM.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/XYChartRepresentationColumns/CMakeLists.txt b/src/XYChartRepresentationColumns/CMakeLists.txt new file mode 100644 index 0000000..4d33eff --- /dev/null +++ b/src/XYChartRepresentationColumns/CMakeLists.txt @@ -0,0 +1,49 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +cmake_minimum_required(VERSION 3.8) +project(XYChartRepresentationColumnsPlugin) +find_package(ParaView REQUIRED) + +include(GNUInstallDirs) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}") +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}") +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}") + +set("_paraview_plugin_default_${CMAKE_PROJECT_NAME}" ON) +paraview_plugin_scan ( + ENABLE_BY_DEFAULT YES + PLUGIN_FILES "${CMAKE_CURRENT_SOURCE_DIR}/plugin/paraview.plugin" + PROVIDES_PLUGINS plugins + REQUIRES_MODULES required_modules) + +foreach(module IN LISTS required_modules) + if(NOT TARGET "${module}") + message("Missing required module: ${module}") + return() + endif() +endforeach() + +set(BUILD_SHARED_LIBS ON) +paraview_plugin_build( + RUNTIME_DESTINATION "${CMAKE_INSTALL_BINDIR}" + LIBRARY_DESTINATION "${CMAKE_INSTALL_LIBDIR}" + LIBRARY_SUBDIRECTORY "${PARAVIEW_PLUGIN_SUBDIR}" + PLUGINS ${plugins} + AUTOLOAD ${plugins}) diff --git a/src/XYChartRepresentationColumns/plugin/CMakeLists.txt b/src/XYChartRepresentationColumns/plugin/CMakeLists.txt new file mode 100644 index 0000000..1b6be4b --- /dev/null +++ b/src/XYChartRepresentationColumns/plugin/CMakeLists.txt @@ -0,0 +1,39 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +cmake_minimum_required(VERSION 3.8) +project(XYChartRepresentationColumnsPlugin) +find_package(ParaView REQUIRED) + +set(BUILD_SHARED_LIBS ON) + +paraview_add_plugin(XYChartRepresentationColumns + VERSION "1.0" + MODULES XYChartRepresentationColumnsModule + MODULE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/XYChartRepresentationColumnsModule/vtk.module" + SERVER_MANAGER_XML views.xml + XML_DOCUMENTATION OFF +) + +set_target_properties (XYChartRepresentationColumns PROPERTIES POSITION_INDEPENDENT_CODE ON) +install(TARGETS XYChartRepresentationColumns + RUNTIME DESTINATION lib/paraview + LIBRARY DESTINATION lib/paraview + ARCHIVE DESTINATION lib/paraview +) diff --git a/src/XYChartRepresentationColumns/plugin/XYChartRepresentationColumnsModule/CMakeLists.txt b/src/XYChartRepresentationColumns/plugin/XYChartRepresentationColumnsModule/CMakeLists.txt new file mode 100644 index 0000000..67abf55 --- /dev/null +++ b/src/XYChartRepresentationColumns/plugin/XYChartRepresentationColumnsModule/CMakeLists.txt @@ -0,0 +1,27 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +set(classes + vtkXYChartRepresentationColumns +) + +vtk_module_add_module(XYChartRepresentationColumnsModule + FORCE_STATIC + CLASSES ${classes} +) diff --git a/src/XYChartRepresentationColumns/plugin/XYChartRepresentationColumnsModule/vtk.module b/src/XYChartRepresentationColumns/plugin/XYChartRepresentationColumnsModule/vtk.module new file mode 100644 index 0000000..369cd5c --- /dev/null +++ b/src/XYChartRepresentationColumns/plugin/XYChartRepresentationColumnsModule/vtk.module @@ -0,0 +1,31 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +NAME + XYChartRepresentationColumnsModule +DEPENDS + VTK::CommonCore + VTK::CommonDataModel + VTK::CommonExecutionModel + VTK::FiltersCore + ParaView::RemotingViews +PRIVATE_DEPENDS + VTK::CommonMisc + VTK::CommonSystem + VTK::FiltersGeneral diff --git a/src/XYChartRepresentationColumns/plugin/XYChartRepresentationColumnsModule/vtkXYChartRepresentationColumns.cxx b/src/XYChartRepresentationColumns/plugin/XYChartRepresentationColumnsModule/vtkXYChartRepresentationColumns.cxx new file mode 100644 index 0000000..3184a51 --- /dev/null +++ b/src/XYChartRepresentationColumns/plugin/XYChartRepresentationColumnsModule/vtkXYChartRepresentationColumns.cxx @@ -0,0 +1,53 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +/*========================================================================= + + Program: ParaView + Module: vtkXYChartRepresentationColumns.cxx + + Copyright (c) Kitware, Inc. + All rights reserved. + See Copyright.txt or http://www.paraview.org/HTML/Copyright.html for details. + + This software is distributed WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. See the above copyright notice for more information. + +=========================================================================*/ +#include "vtkXYChartRepresentationColumns.h" + +#include +#include + +vtkStandardNewMacro(vtkXYChartRepresentationColumns); + +//---------------------------------------------------------------------------- +void vtkXYChartRepresentationColumns::PrintSelf(ostream& os, vtkIndent indent) +{ + this->Superclass::PrintSelf(os, indent); +} + +//---------------------------------------------------------------------------- +void vtkXYChartRepresentationColumns::PrepareForRendering() +{ + this->Superclass::PrepareForRendering(); + vtkChartXY* chartXY = this->GetChart(); + chartXY->SetSelectionMethod(vtkChart::SELECTION_COLUMNS); +} diff --git a/src/XYChartRepresentationColumns/plugin/XYChartRepresentationColumnsModule/vtkXYChartRepresentationColumns.h b/src/XYChartRepresentationColumns/plugin/XYChartRepresentationColumnsModule/vtkXYChartRepresentationColumns.h new file mode 100644 index 0000000..cf48c64 --- /dev/null +++ b/src/XYChartRepresentationColumns/plugin/XYChartRepresentationColumnsModule/vtkXYChartRepresentationColumns.h @@ -0,0 +1,64 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +/*========================================================================= + + Program: ParaView + Module: vtkXYChartRepresentationColumns.h + + Copyright (c) Kitware, Inc. + All rights reserved. + See Copyright.txt or http://www.paraview.org/HTML/Copyright.html for details. + + This software is distributed WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. See the above copyright notice for more information. + +=========================================================================*/ +/** + * @class vtkXYChartRepresentationColumns + * + * vtkXYChartRepresentationColumns is a specialisation of vtkXYChartRepresentation + * that supports column selection. + */ + +#ifndef vtkXYChartRepresentationColumns_h +#define vtkXYChartRepresentationColumns_h + +#include + +class VTK_EXPORT vtkXYChartRepresentationColumns : public vtkXYChartRepresentation +{ +public: + static vtkXYChartRepresentationColumns* New(); + vtkTypeMacro(vtkXYChartRepresentationColumns, vtkXYChartRepresentation); + void PrintSelf(ostream& os, vtkIndent indent) override; + +protected: + vtkXYChartRepresentationColumns() = default; + ~vtkXYChartRepresentationColumns() override = default; + + void PrepareForRendering() override; + +private: + vtkXYChartRepresentationColumns(const vtkXYChartRepresentationColumns&) = delete; + void operator=(const vtkXYChartRepresentationColumns&) = delete; +}; + +#endif // vtkXYChartRepresentationColumns_h diff --git a/src/XYChartRepresentationColumns/plugin/paraview.plugin b/src/XYChartRepresentationColumns/plugin/paraview.plugin new file mode 100644 index 0000000..35637fa --- /dev/null +++ b/src/XYChartRepresentationColumns/plugin/paraview.plugin @@ -0,0 +1,25 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +NAME + XYChartRepresentationColumns +DESCRIPTION + Provides a new representation +REQUIRES_MODULES + ParaView::RemotingViews diff --git a/src/XYChartRepresentationColumns/plugin/views.xml b/src/XYChartRepresentationColumns/plugin/views.xml new file mode 100644 index 0000000..e419c9b --- /dev/null +++ b/src/XYChartRepresentationColumns/plugin/views.xml @@ -0,0 +1,327 @@ + + + + This is the proxy for the XY line chart + view that support column selection. + + + + + API for representations used by XYChartView and XYBarChartView. + + + Data input for the representation. + + + + + + + Visibility of the representation. + + + + Typically UseCache and CacheKey are updated by the View + and representations cache based on what the view tells it. However in + some cases we may want to force a representation to cache irrespective + of the view (e.g. comparative views). In which case these ivars can up + set. If ForcedCacheKey is true, it overrides UseCache and CacheKey. + Instead, ForcedCacheKey is used. + + + + Typically UseCache and CacheKey are updated by the View + and representations cache based on what the view tells it. However in + some cases we may want to force a representation to cache irrespective + of the view (e.g. comparative views). In which case these ivars can up + set. If ForcedCacheKey is true, it overrides UseCache and CacheKey. + Instead, ForcedCacheKey is used. + + + + + + + + This property lists the ids of the blocks to extract + from the input multiblock dataset. + + + Select the attribute data to render. + + + + + + + + + + + + + When set, the array index will be used for X axis, + otherwise the array identified by XArrayName will be + used. + + + Set the array to use on X axis. This is used only when + UseIndexForXAxis is set to 0. + + + + + + + + + + + + + + + + + + + Set the series visibility. + + + + + + + + + + + + + + Set the series labels. + + + + + + + + + + Set the series line/bar color. + + + + + + + + + + Set the series axis corner. + + + + + + + + + + + + + + + + + + + + + + + + + Specify a string to prefix to the **SeriesLabel** (**Legend Name**) for each + of series being plotted. This will get prefixed to the labels (legend names) specified + for each of the series individually via the **Series Parameters**. + + + + Set the series line style. + + + + + + + + + + Set the series line thickness. + + + + + + + + + + Set the series marker style. + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/ZJFilter/CMakeLists.txt b/src/ZJFilter/CMakeLists.txt new file mode 100644 index 0000000..a1aafd9 --- /dev/null +++ b/src/ZJFilter/CMakeLists.txt @@ -0,0 +1,49 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +cmake_minimum_required(VERSION 3.8) +project(ZJFilter) +find_package(ParaView REQUIRED) + +include(GNUInstallDirs) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}") +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}") +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}") + +set("_paraview_plugin_default_${CMAKE_PROJECT_NAME}" ON) +paraview_plugin_scan( + ENABLE_BY_DEFAULT YES + PLUGIN_FILES "${CMAKE_CURRENT_SOURCE_DIR}/plugin/paraview.plugin" + PROVIDES_PLUGINS plugins + REQUIRES_MODULES required_modules) + +foreach(module IN LISTS required_modules) + if(NOT TARGET "${module}") + message("Missing required module: ${module}") + return() + endif() +endforeach() + +set(BUILD_SHARED_LIBS ON) +paraview_plugin_build( + RUNTIME_DESTINATION "${CMAKE_INSTALL_BINDIR}" + LIBRARY_DESTINATION "${CMAKE_INSTALL_LIBDIR}" + LIBRARY_SUBDIRECTORY "${PARAVIEW_PLUGIN_SUBDIR}" + PLUGINS ${plugins} + AUTOLOAD ${plugins}) diff --git a/src/ZJFilter/TestCase.py b/src/ZJFilter/TestCase.py new file mode 100644 index 0000000..50d2f83 --- /dev/null +++ b/src/ZJFilter/TestCase.py @@ -0,0 +1,20 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +print("To be done...") diff --git a/src/ZJFilter/plugin/CMakeLists.txt b/src/ZJFilter/plugin/CMakeLists.txt new file mode 100644 index 0000000..83a4f18 --- /dev/null +++ b/src/ZJFilter/plugin/CMakeLists.txt @@ -0,0 +1,65 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +CMAKE_POLICY(SET CMP0071 NEW) +SET(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) +SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib") + +# Common CMake macros +# =================== +SET(TMP_CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH}) +unset(CMAKE_MODULE_PATH) +SET(CONFIGURATION_ROOT_DIR $ENV{CONFIGURATION_ROOT_DIR} CACHE PATH "Path to the Salome CMake configuration files") +IF(EXISTS ${CONFIGURATION_ROOT_DIR}) + LIST(APPEND CMAKE_MODULE_PATH "${CONFIGURATION_ROOT_DIR}/cmake") + INCLUDE(SalomeMacros) +ELSE() + MESSAGE(FATAL_ERROR "We absolutely need the Salome CMake configuration files, please define CONFIGURATION_ROOT_DIR !") +ENDIF() + +SET(MEDCOUPLING_ROOT_DIR $ENV{MEDCOUPLING_ROOT_DIR} CACHE PATH "Path to the MEDCoupling tool") +IF(EXISTS ${MEDCOUPLING_ROOT_DIR}) + LIST(APPEND CMAKE_MODULE_PATH "${MEDCOUPLING_ROOT_DIR}/cmake_files") +ENDIF() +LIST(APPEND CMAKE_MODULE_PATH "${CMAKE_ROOT}/Modules") +LIST(APPEND CMAKE_MODULE_PATH ${TMP_CMAKE_MODULE_PATH}) + +INCLUDE(SalomeSetupPlatform) +SET(BUILD_SHARED_LIBS TRUE) + +FIND_PACKAGE(SalomeHDF5 REQUIRED) +FIND_PACKAGE(SalomeMEDCoupling REQUIRED) + +SALOME_ACCUMULATE_ENVIRONMENT(PYTHONPATH NOCHECK ${CMAKE_INSTALL_PREFIX}/${SALOME_INSTALL_BINS} + ${CMAKE_INSTALL_PREFIX}/${SALOME_INSTALL_PYTHON}) +SALOME_ACCUMULATE_ENVIRONMENT(LD_LIBRARY_PATH NOCHECK ${CMAKE_INSTALL_PREFIX}/${SALOME_INSTALL_LIBS}) +SALOME_ACCUMULATE_ENVIRONMENT(PV_PLUGIN_PATH NOCHECK ${CMAKE_INSTALL_PREFIX}/lib/paraview) + +paraview_add_plugin(ZJFilterPlugin + VERSION "1.0" + MODULES ZJFilterModule + MODULE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/ZJFilterModule/vtk.module" + SERVER_MANAGER_XML filters.xml +) + +install(TARGETS ZJFilterPlugin + RUNTIME DESTINATION lib/paraview + LIBRARY DESTINATION lib/paraview + ARCHIVE DESTINATION lib/paraview +) diff --git a/src/ZJFilter/plugin/ZJFilterModule/CMakeLists.txt b/src/ZJFilter/plugin/ZJFilterModule/CMakeLists.txt new file mode 100644 index 0000000..184a8e2 --- /dev/null +++ b/src/ZJFilter/plugin/ZJFilterModule/CMakeLists.txt @@ -0,0 +1,35 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +set(classes + vtkZJFilter +) + +vtk_module_add_module(ZJFilterModule + FORCE_STATIC + CLASSES ${classes} +) + +target_include_directories(ZJFilterModule PRIVATE ${MEDCOUPLING_INCLUDE_DIRS}) + +if(HDF5_IS_PARALLEL) + target_link_libraries(ZJFilterModule PRIVATE ${MEDCoupling_paramedloader}) +else() + target_link_libraries(ZJFilterModule PRIVATE ${MEDCoupling_medloader}) +endif() diff --git a/src/ZJFilter/plugin/ZJFilterModule/vtk.module b/src/ZJFilter/plugin/ZJFilterModule/vtk.module new file mode 100644 index 0000000..d8b1cc5 --- /dev/null +++ b/src/ZJFilter/plugin/ZJFilterModule/vtk.module @@ -0,0 +1,34 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +NAME + ZJFilterModule +DEPENDS + VTK::CommonCore + VTK::CommonDataModel + VTK::CommonExecutionModel + VTK::FiltersCore + VTK::FiltersGeneral + ParaView::RemotingCore + VTK::IOLegacy +PRIVATE_DEPENDS + VTK::IOLegacy + ParaView::VTKExtensionsMisc + ParaView::VTKExtensionsFiltersRendering + diff --git a/src/ZJFilter/plugin/ZJFilterModule/vtkZJFilter.cxx b/src/ZJFilter/plugin/ZJFilterModule/vtkZJFilter.cxx new file mode 100644 index 0000000..803049e --- /dev/null +++ b/src/ZJFilter/plugin/ZJFilterModule/vtkZJFilter.cxx @@ -0,0 +1,574 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +// Author : Anthony Geay (EDF R&D) + +#include "vtkZJFilter.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "InterpKernelException.hxx" +#include "MEDCouplingRefCountObject.hxx" + +#include +#include +#include +#include + +vtkStandardNewMacro(vtkZJFilter); + +/////////////////// + +vtkInformationDataObjectMetaDataKey* GetMEDReaderMetaDataIfAny() +{ + static const char ZE_KEY[] = "vtkMEDReader::META_DATA"; + MEDCoupling::GlobalDict* gd(MEDCoupling::GlobalDict::GetInstance()); + if (!gd->hasKey(ZE_KEY)) + return 0; + std::string ptSt(gd->value(ZE_KEY)); + void* pt(0); + std::istringstream iss(ptSt); + iss >> pt; + return reinterpret_cast(pt); +} + +bool IsInformationOK(vtkInformation* info) +{ + vtkInformationDataObjectMetaDataKey* key(GetMEDReaderMetaDataIfAny()); + if (!key) + return false; + // Check the information contain meta data key + if (!info->Has(key)) + return false; + // Recover Meta Data + vtkMutableDirectedGraph* sil(vtkMutableDirectedGraph::SafeDownCast(info->Get(key))); + if (!sil) + return false; + int idNames(0); + vtkAbstractArray* verticesNames(sil->GetVertexData()->GetAbstractArray("Names", idNames)); + vtkStringArray* verticesNames2(vtkStringArray::SafeDownCast(verticesNames)); + if (!verticesNames2) + return false; + for (int i = 0; i < verticesNames2->GetNumberOfValues(); i++) + { + vtkStdString& st(verticesNames2->GetValue(i)); + if (st == "MeshesFamsGrps") + return true; + } + return false; +} + +class Grp +{ +public: + Grp(const std::string& name) + : _name(name) + { + } + void setFamilies(const std::vector& fams) { _fams = fams; } + std::string getName() const { return _name; } + std::vector getFamilies() const { return _fams; } + +private: + std::string _name; + std::vector _fams; +}; + +class Fam +{ +public: + Fam(const std::string& name) + { + constexpr char ZE_SEP[] = "@@][@@"; + std::size_t pos(name.find(ZE_SEP)); + std::string name0(name.substr(0, pos)), name1(name.substr(pos + strlen(ZE_SEP))); + std::istringstream iss(name1); + iss >> _id; + _name = name0; + } + std::string getName() const { return _name; } + int getID() const { return _id; } + +private: + std::string _name; + int _id; +}; + +void ExtractInfo(vtkInformationVector* inputVector, vtkUnstructuredGrid*& usgIn) +{ + vtkInformation* inputInfo(inputVector->GetInformationObject(0)); + vtkDataSet* input(0); + vtkDataSet* input0(vtkDataSet::SafeDownCast(inputInfo->Get(vtkDataObject::DATA_OBJECT()))); + vtkMultiBlockDataSet* input1( + vtkMultiBlockDataSet::SafeDownCast(inputInfo->Get(vtkDataObject::DATA_OBJECT()))); + if (input0) + input = input0; + else + { + if (!input1) + throw INTERP_KERNEL::Exception( + "Input dataSet must be a DataSet or single elt multi block dataset expected !"); + if (input1->GetNumberOfBlocks() != 1) + throw INTERP_KERNEL::Exception( + "Input dataSet is a multiblock dataset with not exactly one block ! Use MergeBlocks or " + "ExtractBlocks filter before calling this filter !"); + vtkDataObject* input2(input1->GetBlock(0)); + if (!input2) + throw INTERP_KERNEL::Exception("Input dataSet is a multiblock dataset with exactly one block " + "but this single element is NULL !"); + vtkDataSet* input2c(vtkDataSet::SafeDownCast(input2)); + if (!input2c) + throw INTERP_KERNEL::Exception( + "Input dataSet is a multiblock dataset with exactly one block but this single element is " + "not a dataset ! Use MergeBlocks or ExtractBlocks filter before calling this filter !"); + input = input2c; + } + if (!input) + throw INTERP_KERNEL::Exception("Input data set is NULL !"); + usgIn = vtkUnstructuredGrid::SafeDownCast(input); + if (!usgIn) + throw INTERP_KERNEL::Exception("Input data set is not an unstructured mesh ! This filter works " + "only on unstructured meshes !"); +} + +void LoadFamGrpMapInfo(vtkMutableDirectedGraph* sil, std::string& meshName, + std::vector& groups, std::vector& fams) +{ + if (!sil) + throw INTERP_KERNEL::Exception("LoadFamGrpMapInfo : internal error !"); + int idNames(0); + vtkAbstractArray* verticesNames(sil->GetVertexData()->GetAbstractArray("Names", idNames)); + vtkStringArray* verticesNames2(vtkStringArray::SafeDownCast(verticesNames)); + vtkIdType id0; + bool found(false); + for (int i = 0; i < verticesNames2->GetNumberOfValues(); i++) + { + vtkStdString& st(verticesNames2->GetValue(i)); + if (st == "MeshesFamsGrps") + { + id0 = i; + found = true; + } + } + if (!found) + throw INTERP_KERNEL::Exception( + "There is an internal error ! The tree on server side has not the expected look !"); + vtkAdjacentVertexIterator* it0(vtkAdjacentVertexIterator::New()); + sil->GetAdjacentVertices(id0, it0); + int kk(0), ll(0); + while (it0->HasNext()) + { + vtkIdType id1(it0->Next()); + std::string mName((const char*)verticesNames2->GetValue(id1)); + meshName = mName; + vtkAdjacentVertexIterator* it1(vtkAdjacentVertexIterator::New()); + sil->GetAdjacentVertices(id1, it1); + vtkIdType idZeGrps(it1->Next()); // zeGroups + vtkAdjacentVertexIterator* itGrps(vtkAdjacentVertexIterator::New()); + sil->GetAdjacentVertices(idZeGrps, itGrps); + while (itGrps->HasNext()) + { + vtkIdType idg(itGrps->Next()); + Grp grp((const char*)verticesNames2->GetValue(idg)); + vtkAdjacentVertexIterator* itGrps2(vtkAdjacentVertexIterator::New()); + sil->GetAdjacentVertices(idg, itGrps2); + std::vector famsOnGroup; + while (itGrps2->HasNext()) + { + vtkIdType idgf(itGrps2->Next()); + famsOnGroup.push_back(std::string((const char*)verticesNames2->GetValue(idgf))); + } + grp.setFamilies(famsOnGroup); + itGrps2->Delete(); + groups.push_back(grp); + } + itGrps->Delete(); + vtkIdType idZeFams(it1->Next()); // zeFams + it1->Delete(); + vtkAdjacentVertexIterator* itFams(vtkAdjacentVertexIterator::New()); + sil->GetAdjacentVertices(idZeFams, itFams); + while (itFams->HasNext()) + { + vtkIdType idf(itFams->Next()); + Fam fam((const char*)verticesNames2->GetValue(idf)); + fams.push_back(fam); + } + itFams->Delete(); + } + it0->Delete(); +} + +std::vector FindConds(const std::vector& grps) +{ + constexpr char PAT[] = "COND_"; + constexpr std::size_t SZ_PAT(sizeof(PAT) - 1); + std::vector ret; + for (std::vector::const_iterator it = grps.begin(); it != grps.end(); it++) + { + std::string name((*it).getName()); + std::string part(name.substr(0, SZ_PAT)); + if (part == PAT) + ret.push_back(name.substr(SZ_PAT, std::string::npos)); + } + return ret; +} + +constexpr char EPORT_PAT[] = "EPORT_"; + +std::vector FindEports( + const std::string& condEntry, const std::vector& grps, std::vector& eportsZip) +{ + std::vector ret; + std::string commonPart(std::string(EPORT_PAT) + condEntry); + std::size_t commonPart_sz(commonPart.length()); + for (std::vector::const_iterator it = grps.begin(); it != grps.end(); it++) + { + std::string name((*it).getName()); + std::string part(name.substr(0, commonPart_sz)); + if (part == commonPart) + { + ret.push_back(name); + eportsZip.push_back(name.substr(commonPart_sz, std::string::npos)); + } + } + return ret; +} + +std::string BigestCommonPart(const std::string& s1, const std::string& s2) +{ + std::size_t ls1(s1.length()), ls2(s2.length()), lb(0), lt(0); + std::string b, t; + if (ls1 >= ls2) + { + b = s1; + t = s2; + lb = ls1; + lt = ls2; + } + else + { + b = s2; + t = s1; + lb = ls2; + lt = ls1; + } + for (std::size_t l0 = lt; l0 > 0; l0--) + { + for (std::size_t l1 = 0; l1 < lt - l0 + 1; l1++) + { + std::string cand(t.substr(l1, l0)); + if (b.find(cand) != std::string::npos) + return cand; + } + } + return std::string(); +} + +std::vector DeduceIdsFrom(const std::vector& eportsZip) +{ + if (eportsZip.empty()) + return std::vector(); + std::string ref(eportsZip[0]); + std::size_t sz(eportsZip.size()); + for (std::size_t i = 1; i < sz; i++) + ref = BigestCommonPart(ref, eportsZip[i]); + std::vector ret(sz); + for (std::size_t i = 0; i < sz; i++) + { + std::size_t pos(eportsZip[i].find(ref)); + if (pos == std::string::npos) + throw INTERP_KERNEL::Exception("DeduceIdsFrom : internal error !"); + std::string res( + eportsZip[i].substr(0, pos) + eportsZip[i].substr(pos + ref.length(), std::string::npos)); + std::istringstream iss(res); + int val(0); + iss >> val; + ret[i] = val; + } + return ret; +} + +std::set FamiliesIdsFromGrp( + const std::vector& grps, const std::vector& fams, const std::string& grp) +{ + bool found(false); + std::vector locFams; + for (std::vector::const_iterator it = grps.begin(); it != grps.end(); it++) + { + if ((*it).getName() == grp) + { + locFams = (*it).getFamilies(); + found = true; + break; + } + } + if (!found) + throw INTERP_KERNEL::Exception("FamiliesIdsFromGrp : internal error !"); + std::set ret; + for (std::vector::const_iterator it = fams.begin(); it != fams.end(); it++) + { + if (std::find(locFams.begin(), locFams.end(), (*it).getName()) != locFams.end()) + ret.insert((*it).getID()); + } + return ret; +} + +vtkDataSet* FilterFamilies(vtkZJFilter* zeBoss, vtkDataSet* input, const std::set& idsToKeep) +{ + bool catchAll, catchSmth; + vtkNew thres; + thres->AddObserver(vtkCommand::ProgressEvent, zeBoss->InternalProgressObserver); + constexpr int VTK_DATA_ARRAY_DELETE = vtkDataArrayTemplate::VTK_DATA_ARRAY_DELETE; + constexpr char ZE_SELECTION_ARR_NAME[] = "@@ZeSelection@@"; + constexpr char arrNameOfFamilyField[] = "FamilyIdCell"; + constexpr char associationForThreshold[] = "vtkDataObject::FIELD_ASSOCIATION_CELLS"; + vtkDataSet* output(input->NewInstance()); + output->ShallowCopy(input); + thres->SetInputData(output); + vtkDataSetAttributes *dscIn(input->GetCellData()), *dscIn2(input->GetPointData()); + vtkDataSetAttributes *dscOut(output->GetCellData()), *dscOut2(output->GetPointData()); + // + constexpr double vMin(1.), vMax(2.); + thres->ThresholdBetween(vMin, vMax); + // OK for the output + // + vtkDataArray* da(input->GetCellData()->GetScalars(arrNameOfFamilyField)); + if (!da) + return 0; + std::string daName(da->GetName()); + vtkIntArray* dai(vtkIntArray::SafeDownCast(da)); + if (daName != arrNameOfFamilyField || !dai) + return 0; + // + int nbOfTuples(dai->GetNumberOfTuples()); + vtkCharArray* zeSelection(vtkCharArray::New()); + zeSelection->SetName(ZE_SELECTION_ARR_NAME); + zeSelection->SetNumberOfComponents(1); + char* pt(new char[nbOfTuples]); + zeSelection->SetArray(pt, nbOfTuples, 0, VTK_DATA_ARRAY_DELETE); + const int* inPtr(dai->GetPointer(0)); + std::fill(pt, pt + nbOfTuples, 0); + catchAll = true; + catchSmth = false; + std::vector pt2(nbOfTuples, false); + for (std::set::const_iterator it = idsToKeep.begin(); it != idsToKeep.end(); it++) + { + bool catchFid(false); + for (int i = 0; i < nbOfTuples; i++) + if (inPtr[i] == *it) + { + pt2[i] = true; + catchFid = true; + } + if (!catchFid) + catchAll = false; + else + catchSmth = true; + } + for (int ii = 0; ii < nbOfTuples; ii++) + if (pt2[ii]) + pt[ii] = 2; + int idx(output->GetCellData()->AddArray(zeSelection)); + output->GetCellData()->SetActiveAttribute(idx, vtkDataSetAttributes::SCALARS); + output->GetCellData()->CopyScalarsOff(); + zeSelection->Delete(); + // + thres->SetInputArrayToProcess(idx, 0, 0, associationForThreshold, ZE_SELECTION_ARR_NAME); + thres->Update(); + vtkUnstructuredGrid* zeComputedOutput(thres->GetOutput()); + zeComputedOutput->GetCellData()->RemoveArray(idx); + output->Delete(); + zeComputedOutput->Register(0); + thres->RemoveObserver(zeBoss->InternalProgressObserver); + return zeComputedOutput; +} + +//////////////////// + +vtkZJFilter::vtkZJFilter() + : InternalProgressObserver(0) +{ + this->InternalProgressObserver = vtkCallbackCommand::New(); + this->InternalProgressObserver->SetCallback(&vtkZJFilter::InternalProgressCallbackFunction); + this->InternalProgressObserver->SetClientData(this); +} + +vtkZJFilter::~vtkZJFilter() +{ + this->InternalProgressObserver->Delete(); +} + +void vtkZJFilter::InternalProgressCallbackFunction( + vtkObject* arg, unsigned long, void* clientdata, void*) +{ + reinterpret_cast(clientdata) + ->InternalProgressCallback(static_cast(arg)); +} + +void vtkZJFilter::InternalProgressCallback(vtkAlgorithm* algorithm) +{ + /*this->UpdateProgress(algorithm->GetProgress()); // To intercept progression of Threshold filters + if (this->AbortExecute) + { + algorithm->SetAbortExecute(1); + }*/ +} + +int vtkZJFilter::RequestInformation( + vtkInformation* request, vtkInformationVector** inputVector, vtkInformationVector* outputVector) +{ + // std::cerr << "########################################## vtkZJFilter::RequestInformation + // ##########################################" << std::endl; + try + { + vtkUnstructuredGrid* usgIn = nullptr; + ExtractInfo(inputVector[0], usgIn); + } + catch (INTERP_KERNEL::Exception& e) + { + std::ostringstream oss; + oss << "Exception has been thrown in vtkZJFilter::RequestInformation : " << e.what() + << std::endl; + if (this->HasObserver("ErrorEvent")) + { + this->InvokeEvent("ErrorEvent", const_cast(oss.str().c_str())); + } + else + { + vtkOutputWindowDisplayErrorText(const_cast(oss.str().c_str())); + } + vtkObject::BreakOnError(); + return 0; + } + return 1; +} + +int vtkZJFilter::RequestData( + vtkInformation* request, vtkInformationVector** inputVector, vtkInformationVector* outputVector) +{ + // std::cerr << "########################################## vtkZJFilter::RequestData + // ##########################################" << std::endl; + try + { + vtkInformation *inputInfo(inputVector[0]->GetInformationObject(0)); + vtkInformation *outInfo(outputVector->GetInformationObject(0)); + vtkUnstructuredGrid* usgIn(nullptr); + ExtractInfo(inputVector[0], usgIn); + std::string meshName; + std::vector groups; + std::vector fams; + if (IsInformationOK(inputInfo)) + { + vtkMutableDirectedGraph* famGrpGraph( + vtkMutableDirectedGraph::SafeDownCast(inputInfo->Get(GetMEDReaderMetaDataIfAny()))); + LoadFamGrpMapInfo(famGrpGraph, meshName, groups, fams); + } + std::vector conds(FindConds(groups)); + vtkUnstructuredGrid* output( + vtkUnstructuredGrid::SafeDownCast(outInfo->Get(vtkDataObject::DATA_OBJECT()))); + vtkNew mb2; + std::size_t iblock(0); + for (std::vector::const_iterator it = conds.begin(); it != conds.end(); + it++, iblock++) + { + std::vector eports2; + std::vector eports(FindEports(*it, groups, eports2)); + std::vector ids(DeduceIdsFrom(eports2)); + vtkNew mb; + std::size_t sz(eports.size()); + for (std::size_t i = 0; i < sz; i++) + { + std::set zeIds(FamiliesIdsFromGrp(groups, fams, eports[i])); + // + vtkSmartPointer ds(FilterFamilies(this, usgIn, zeIds)); + { + vtkNew arr; + arr->SetName((*it).c_str()); + arr->SetNumberOfComponents(1); + int nbTuples(ds->GetNumberOfCells()); + arr->SetNumberOfTuples(nbTuples); + int* pt(arr->GetPointer(0)); + std::fill(pt, pt + nbTuples, ids[i]); + ds->GetCellData()->AddArray(arr); + } + this->UpdateProgress(double(i) / double(sz)); + mb->SetBlock(i, ds); + } + vtkNew cd; + cd->SetInputData(mb); + cd->SetMergePoints(0); + cd->Update(); + mb2->SetBlock(iblock, cd->GetOutput()); + } + { + vtkNew cd2; + cd2->SetInputData(mb2); + cd2->SetMergePoints(0); + cd2->Update(); + output->ShallowCopy(cd2->GetOutput()); + } + } + catch (INTERP_KERNEL::Exception& e) + { + std::ostringstream oss; + oss << "Exception has been thrown in vtkZJFilter::RequestInformation : " << e.what() + << std::endl; + if (this->HasObserver("ErrorEvent")) + this->InvokeEvent("ErrorEvent", const_cast(oss.str().c_str())); + else + vtkOutputWindowDisplayErrorText(const_cast(oss.str().c_str())); + vtkObject::BreakOnError(); + return 0; + } + return 1; +} + +void vtkZJFilter::PrintSelf(ostream& os, vtkIndent indent) +{ + this->Superclass::PrintSelf(os, indent); +} diff --git a/src/ZJFilter/plugin/ZJFilterModule/vtkZJFilter.h b/src/ZJFilter/plugin/ZJFilterModule/vtkZJFilter.h new file mode 100644 index 0000000..5cfc537 --- /dev/null +++ b/src/ZJFilter/plugin/ZJFilterModule/vtkZJFilter.h @@ -0,0 +1,52 @@ +// Copyright (C) 2021 CEA/DEN, EDF R&D +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// +// Author : Anthony Geay (EDF R&D) + +#ifndef vtkZJFilter_h__ +#define vtkZJFilter_h__ + +#include + +class vtkCallbackCommand; + +class VTK_EXPORT vtkZJFilter : public vtkUnstructuredGridAlgorithm +{ +public: + static vtkZJFilter* New(); + vtkTypeMacro(vtkZJFilter, vtkUnstructuredGridAlgorithm); + void PrintSelf(ostream& os, vtkIndent indent) override; + + vtkCallbackCommand* InternalProgressObserver; + +protected: + vtkZJFilter(); + ~vtkZJFilter() override; + + int RequestInformation(vtkInformation*, vtkInformationVector**, vtkInformationVector*) override; + int RequestData(vtkInformation*, vtkInformationVector**, vtkInformationVector*) override; + + static void InternalProgressCallbackFunction(vtkObject*, unsigned long, void*, void*); + void InternalProgressCallback(vtkAlgorithm* algorithm); + +private: + vtkZJFilter(const vtkZJFilter&) = delete; + void operator=(const vtkZJFilter&) = delete; +}; + +#endif diff --git a/src/ZJFilter/plugin/filters.xml b/src/ZJFilter/plugin/filters.xml new file mode 100644 index 0000000..9fc528c --- /dev/null +++ b/src/ZJFilter/plugin/filters.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + This property specifies the input to the ZJ filter. + + + + + diff --git a/src/ZJFilter/plugin/paraview.plugin b/src/ZJFilter/plugin/paraview.plugin new file mode 100644 index 0000000..ef6ff03 --- /dev/null +++ b/src/ZJFilter/plugin/paraview.plugin @@ -0,0 +1,28 @@ +# Copyright (C) 2021 CEA/DEN, EDF R&D +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +# + +NAME + ZJFilterPlugin +DESCRIPTION + This plugin provides the ZJFilter filter. +REQUIRES_MODULES + VTK::CommonCore + VTK::IOCore + VTK::FiltersCore + VTK::FiltersGeneral -- 2.39.2