Compare commits

..
113 Commits
Author SHA1 Message Date
Theophile Terraz eae1f83a73 remove GL doublons 2023-12-20 14:53:18 +01:00
Theophile Terraz a738bf3679 merge 2023-12-20 14:10:33 +01:00
Theophile Terraz d81f341303 Merge branch 'master' of gitlab-ssh.irstea.fr:theophile.terraz/pamhyr 2023-12-20 10:43:40 +01:00
Pierre-Antoine Rouby 1ea6fd7c54 Script: Fix missing files and minor change. 2023-12-20 09:45:11 +01:00
Theophile Terraz 3025c995b6 bords ponts Tuto1 2023-12-20 09:38:37 +01:00
Theophile Terraz ecc75c45eb Merge branch 'master' of gitlab-ssh.irstea.fr:theophile.terraz/pamhyr 2023-12-20 09:37:34 +01:00
Pierre-Antoine Rouby 09dcf486d8 Script: Add mage meshing tool. 2023-12-19 17:28:34 +01:00
Pierre-Antoine Rouby 592c1e5dbc MainWindow: Minor fix of file name '.pamhyr' detection. 2023-12-19 11:59:21 +01:00
Pierre-Antoine Rouby cd3baf2870 Results, RunSolver: Stop timer at window close. 2023-12-19 09:45:05 +01:00
Pierre-Antoine Rouby 93734ff1b9 SolverRun: Try catch read results. 2023-12-19 09:17:26 +01:00
Pierre-Antoine Rouby dfb7d37cfb Results: Resize window to respect 16:9. 2023-12-19 09:11:28 +01:00
Pierre-Antoine Rouby 83aa6a5791 Results, Plots: Fix icon paths. 2023-12-19 09:11:01 +01:00
Pierre-Antoine Rouby 45f020edaa Solver: Fix install dir path. 2023-12-19 08:52:24 +01:00
Pierre-Antoine Rouby ad1c5c487e mage: Update mage version. 2023-12-18 17:22:55 +01:00
Theophile Terraz 5c323b28e2 Merge branch 'master' of gitlab-ssh.irstea.fr:theophile.terraz/pamhyr 2023-12-18 16:15:22 +01:00
Theophile Terraz da2250df3a work on tuto 2023-12-18 16:15:19 +01:00
Pierre-Antoine Rouby cb1d6b4db7 pep8: Minor fixes. 2023-12-18 15:44:49 +01:00
Pierre-Antoine Rouby f96cee145c HS: Fix Plot relim at update. 2023-12-18 15:37:22 +01:00
Pierre-Antoine Rouby e4670e4a4d Results: Add current timestamps on hydrograph. 2023-12-18 15:28:22 +01:00
Pierre-Antoine Rouby 5c0f90b43e Results: Fix sl results display. 2023-12-18 15:11:56 +01:00
Pierre-Antoine Rouby e6de528b7f Mage: Update mage gra file reader for mage 83. 2023-12-18 14:56:16 +01:00
Pierre-Antoine Rouby dfff8d2fcb Geometry: Copy disable headers. 2023-12-18 11:26:58 +01:00
Pierre-Antoine Rouby 33a1fd2b52 Geometry: Profile copy/paste minor change. 2023-12-18 11:17:16 +01:00
Pierre-Antoine Rouby f083b7f6ef Geometry: Fix copy/paste command. 2023-12-18 10:44:11 +01:00
Pierre-Antoine Rouby 25de73812b Config: Fix reopen latest study. 2023-12-18 10:25:24 +01:00
Pierre-Antoine Rouby 6b232d2971 Merge branch 'hydraulics-structure' of gitlab-ssh.irstea.fr:theophile.terraz/pamhyr into hydraulics-structure 2023-12-18 10:14:24 +01:00
Theophile Terraz 9c0ffa8356 add option to let Mage generate initial condition 2023-12-15 15:36:17 +01:00
Pierre-Antoine Rouby ffbd2e2413 Mage: Minor code style refactoring. 2023-12-13 16:44:44 +01:00
Pierre-Antoine Rouby 58300b8c1a Mage: Minor indent fixes. 2023-12-13 16:31:57 +01:00
Pierre-Antoine Rouby cd2fda8224 HS, Mage: Rewrite some code for coding style. 2023-12-13 16:30:18 +01:00
Pierre-Antoine Rouby 73ae571de1 Merge branch 'hydraulics-structure' of gitlab-ssh.irstea.fr:theophile.terraz/pamhyr into hydraulics-structure 2023-12-13 16:01:23 +01:00
Pierre-Antoine Rouby 7aee4c64a3 Solver: Fix running pipeline. 2023-12-13 15:57:47 +01:00
Pierre-Antoine Rouby c3cae35de4 pep8: Fixes code style. 2023-12-13 14:53:58 +01:00
Pierre-Antoine Rouby 2736d2f399 SolverRun: Keep in configuration the last solver name used. 2023-12-13 14:48:01 +01:00
Pierre-Antoine Rouby 00afc6c06b SolverRun: Fix bin/gra reading before solver running. 2023-12-13 14:30:29 +01:00
Theophile Terraz 4eefa88490 Merge branch 'hydraulics-structure' of gitlab-ssh.irstea.fr:theophile.terraz/pamhyr into hydraulics-structure 2023-12-13 11:45:01 +01:00
Theophile Terraz 92e54bf11e work on Mage export 2023-12-13 11:44:56 +01:00
Pierre-Antoine Rouby c11350d49a HS: Fix basic HS previous data delete at save. 2023-12-13 11:04:51 +01:00
Pierre-Antoine Rouby 8f5fb4cc3b Geometry: Profile: Some fixes. 2023-12-13 10:36:13 +01:00
Pierre-Antoine Rouby fc7a7bb7fd HS: Fix update relim in PlotAC and PlotKPC. 2023-12-13 10:07:46 +01:00
Pierre-Antoine Rouby 5a1c363e8a IC: Fix reach reference at loading in case of multiple reach. 2023-12-13 09:42:47 +01:00
Theophile Terraz 77a6da3024 Merge branch 'hydraulics-structure' of gitlab-ssh.irstea.fr:theophile.terraz/pamhyr into hydraulics-structure 2023-12-12 16:35:24 +01:00
Theophile Terraz c4a66281f6 debug output SIN 2023-12-12 16:35:17 +01:00
Pierre-Antoine Rouby 7ffc2482fc HS: Minor fixes. 2023-12-12 16:21:47 +01:00
Pierre-Antoine Rouby f37da68a67 HS: Basic: Minor fixes. 2023-12-12 16:08:04 +01:00
Theophile Terraz f64ed131f9 work on BHS 2023-12-12 16:00:40 +01:00
Theophile Terraz 79b31e8d08 merge 2023-12-12 15:40:54 +01:00
Theophile Terraz d1c8e3e4f5 debug SIN output 2023-12-12 15:39:43 +01:00
Theophile Terraz c253ad832d ajout ouvrages mage 2023-12-12 14:57:23 +01:00
Pierre-Antoine Rouby 6081b09267 pep8: Fixes. 2023-12-12 14:53:00 +01:00
Pierre-Antoine Rouby 592c2c9a72 HS: Minor fix. 2023-12-12 14:44:58 +01:00
Pierre-Antoine Rouby ce45776a63 HS: Basic: Fix load sql request. 2023-12-12 14:30:04 +01:00
Pierre-Antoine Rouby 02bbdf9fda HS: Basic: Add parameters table and fix save/load sql methods. 2023-12-12 14:23:36 +01:00
Pierre-Antoine Rouby 6c9e90ceaf MainWindow: Add progress dialog for save as method. 2023-12-12 13:39:38 +01:00
Pierre-Antoine Rouby b99be5a280 MainWindow: Remove cancel button. 2023-12-12 13:35:13 +01:00
Pierre-Antoine Rouby 1acd582de9 MainWindow: Add progress dialog for save process. 2023-12-12 11:57:19 +01:00
Pierre-Antoine Rouby 8ce5a4d215 Network: Change selected reach color and minor change. 2023-12-12 10:51:25 +01:00
Pierre-Antoine Rouby ccb55ff4e1 HS: Fix pep8 and minor change. 2023-12-12 10:22:52 +01:00
Pierre-Antoine Rouby 3ef4b40558 HS: Basic: Add question for BHS type change. 2023-12-12 10:13:10 +01:00
Pierre-Antoine Rouby c667d94e84 HS: Basic: Fix delete undo command. 2023-12-12 09:43:54 +01:00
Pierre-Antoine Rouby 1b601460e6 HS: Basic: Make enabled undo command. 2023-12-12 09:32:05 +01:00
Theophile Terraz 7db65f127e work on hydraulic structures 2023-12-11 16:53:53 +01:00
Theophile Terraz f302e18d71 dirty work on BHS 2023-12-11 15:44:31 +01:00
Theophile Terraz 352c0beb27 add short dict and getter in HS 2023-12-11 15:29:02 +01:00
Theophile Terraz d6561ab44d merge 2023-12-11 15:27:53 +01:00
Pierre-Antoine Rouby 5d9b6f99c6 HS: Fix case with no profile kp. 2023-12-11 15:20:46 +01:00
Pierre-Antoine Rouby 163a842384 HS: Refacto update function and fix NA value at table data set. 2023-12-11 15:19:24 +01:00
Pierre-Antoine Rouby eea355a784 pep8: Change to respect pep8 code style. 2023-12-11 14:40:54 +01:00
Pierre-Antoine Rouby 1ab1cb37b0 HS: Create a enabled undo command. 2023-12-11 11:55:27 +01:00
Theophile Terraz be19a11387 Merge branch 'hydraulics-structure' of gitlab-ssh.irstea.fr:theophile.terraz/pamhyr into hydraulics-structure 2023-12-11 11:20:51 +01:00
Theophile Terraz 062743d748 enable edit window 2023-12-11 11:20:47 +01:00
Pierre-Antoine Rouby ac2a2002b7 HS: Fix checkbox state at undo add command. 2023-12-11 11:12:57 +01:00
Pierre-Antoine Rouby 6951b82339 Friction: Delete useless prints. 2023-12-11 11:12:41 +01:00
Pierre-Antoine Rouby e41c23120a HS: Minor fix add method. 2023-12-11 11:00:14 +01:00
Theophile Terraz 4c5281ea30 Merge branch 'hydraulics-structure' of gitlab-ssh.irstea.fr:theophile.terraz/pamhyr into hydraulics-structure 2023-12-06 16:55:34 +01:00
Theophile Terraz 7b7ba2eb37 add hydraulic structures plots 2023-12-06 16:55:27 +01:00
Pierre-Antoine Rouby 4a43653d7f HydraulicStructure: Fix KP db type. 2023-12-06 14:49:50 +01:00
Pierre-Antoine Rouby 0307927fa9 PamhyrTable: Fix editable column. 2023-12-06 14:15:47 +01:00
Pierre-Antoine Rouby db11c9d968 Geometry: Resize window to respect 16:9 ratio. 2023-12-06 11:44:09 +01:00
Pierre-Antoine Rouby d85e5c4666 PamhyrDialog: Fix window list delete whene dialog is done. 2023-12-06 11:30:09 +01:00
Pierre-Antoine Rouby 45ee8a9662 Friction: Fix load method in case of multiple reaches. 2023-12-06 11:05:55 +01:00
Pierre-Antoine Rouby 5ab06dbe51 HydraulicStructure: Fix db export. 2023-12-06 09:20:18 +01:00
Theophile Terraz d73e901a96 work on hydraulic structures window 2023-12-05 16:46:50 +01:00
Theophile Terraz c17e39e3cf Merge branch 'hydraulics-structure' of gitlab-ssh.irstea.fr:theophile.terraz/pamhyr into hydraulics-structure 2023-12-05 15:14:19 +01:00
Pierre-Antoine Rouby af4cce96ef HydraulicStructure: Rename enable to enabled. 2023-12-05 14:02:10 +01:00
Pierre-Antoine Rouby 0a780e5f78 HydraulicStructure: Minor change and fixes. 2023-12-05 11:56:14 +01:00
Pierre-Antoine Rouby fb2b1f680f HydraulicStructures: Add Model files. 2023-12-05 11:13:52 +01:00
Theophile Terraz 6957986632 add hydraulic structures view files 2023-12-05 09:41:15 +01:00
Theophile Terraz 13d23502a9 Merge branch 'master' into hydraulics-structure 2023-12-04 17:41:39 +01:00
Theophile Terraz 5fa888768e add hydraulic structures ui 2023-12-04 17:41:06 +01:00
Theophile Terraz 07451899c4 export mage SIN file 2023-12-04 17:38:25 +01:00
Theophile Terraz 2aa693e4ae modif sections sliders 2023-12-04 09:35:02 +01:00
Theophile Terraz ff480fb593 add first and last button (visu) 2023-12-04 09:14:35 +01:00
Theophile Terraz d6753dc986 upgrade results slider 2023-12-01 18:37:36 +01:00
Theophile Terraz 2d2484d8fa change colors 2023-12-01 15:51:40 +01:00
Theophile Terraz bb97f1977b debug 2023-12-01 15:51:13 +01:00
Theophile Terraz eb26eba131 optim visu 2023-12-01 14:05:00 +01:00
Theophile Terraz 2d3e214514 debug 2023-12-01 14:04:46 +01:00
Theophile Terraz 894a7c8477 speed up View/Results/PlotXY 2023-11-30 15:06:39 +01:00
Theophile Terraz 21197ead23 work on vater limits for top view visualization 2023-11-30 15:06:00 +01:00
Theophile Terraz d79a0cc973 Merge branch 'master' of gitlab-ssh.irstea.fr:theophile.terraz/pamhyr 2023-11-29 17:10:30 +01:00
Theophile Terraz a55669d181 modif INI format 2023-11-29 17:10:27 +01:00
Pierre-Antoine Rouby 2fd44fbfb4 IC: Fix data sorting at the end of generation. 2023-11-29 16:52:01 +01:00
Pierre-Antoine Rouby 962bfa945d Merge branch 'master' of gitlab-ssh.irstea.fr:theophile.terraz/pamhyr 2023-11-29 16:41:53 +01:00
Pierre-Antoine Rouby 65c1163a55 Reservoir: Allow to select only internal node. 2023-11-29 16:41:05 +01:00
Theophile Terraz 8d67442ca3 Merge branch 'master' of gitlab-ssh.irstea.fr:theophile.terraz/pamhyr 2023-11-29 15:26:22 +01:00
Theophile Terraz f7d2ac8b86 traduction 2023-11-29 15:26:11 +01:00
Theophile Terraz 888002cd10 another purge criterion 2023-10-03 13:13:52 +02:00
Theophile Terraz e1880c6549 work on purge 2023-09-28 17:09:40 +02:00
Theophile Terraz 9f0caaaa69 Merge branch 'master' into mesh 2023-09-27 10:16:02 +02:00
Theophile Terraz fa35d49a42 add purge 2023-07-06 11:33:59 +02:00
Theophile Terraz 4935069864 add a basic st_to_m method 2023-07-05 15:49:30 +02:00
Theophile Terraz 6438d23304 begin interpolation 2023-06-28 15:32:24 +02:00
83 changed files with 5828 additions and 1102 deletions

No files matched your search

+3 -3
View File
@@ -33,7 +33,7 @@ dl-mage-linux:
rules:
- if: $CI_COMMIT_BRANCH == 'ci-test' || $CI_COMMIT_BRANCH == 'master' || $CI_COMMIT_TAG
script:
- curl -L -o mage.tgz https://gitlab.irstea.fr/jean-baptiste.faure/mage/-/releases/Test6/downloads/packages/mage_linux.tgz
- curl -L -o mage.tgz https://gitlab.irstea.fr/jean-baptiste.faure/mage/-/releases/v8.3.0/downloads/packages/mage_linux.tgz
- mkdir -p mage-linux
- cd mage-linux
- tar xvf ../mage.tgz
@@ -50,7 +50,7 @@ dl-mage-windows:
rules:
- if: $CI_COMMIT_BRANCH == 'ci-test' || $CI_COMMIT_BRANCH == 'master' || $CI_COMMIT_TAG
script:
- curl -L -o mage.tgz https://gitlab.irstea.fr/jean-baptiste.faure/mage/-/releases/Test6/downloads/packages/mage_windows.tgz
- curl -L -o mage.tgz https://gitlab.irstea.fr/jean-baptiste.faure/mage/-/releases/v8.3.0/downloads/packages/mage_windows.tgz
- mkdir -p mage-windows
- cd mage-windows
- tar xvf ../mage.tgz
@@ -122,7 +122,7 @@ test-pep8:
- pip3 install -r ./requirements.txt
- pip3 install -U -r ./requirements.txt
- pip3 install pycodestyle
- pycodestyle ./src
- pycodestyle --exclude="*_to_*.py" ./src
allow_failure: true
#########
Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

+12 -4
View File
@@ -65,7 +65,8 @@
31874.0000 14.5000 22.1900
31874.0000 17.6000 22.9900
999.9990 999.9990 999.9990
7 0 0 7 31875.0000 PontRD101m
7 0 0 9 31875.0000 PontRD101m
31875.0000 0.5400 23.0000
31875.0000 0.5400 21.8900
31875.0000 2.5000 19.6900 rg
31875.0000 2.5000 18.6900
@@ -73,8 +74,10 @@
31875.0000 10.7000 18.9900
31875.0000 10.7000 19.9900 rd
31875.0000 13.8900 21.8900
31875.0000 13.8900 23.0000
999.9990 999.9990 999.9990
8 0 0 7 31885.0000 PontRD101v
8 0 0 9 31885.0000 PontRD101v
31885.0000 0.5400 23.0000
31885.0000 0.5400 21.8900
31885.0000 2.5000 19.6900 rg
31885.0000 2.5000 18.6900
@@ -82,6 +85,7 @@
31885.0000 10.7000 18.9900
31885.0000 10.7000 19.9900 rd
31885.0000 13.8900 21.8900
31885.0000 13.8900 23.0000
999.9990 999.9990 999.9990
9 0 0 9 31886.0000
31886.0000 0.0000 22.9900
@@ -303,7 +307,8 @@
34334.0000 14.0000 18.1000 rd
34334.0000 16.1000 21.1300
999.9990 999.9990 999.9990
26 0 0 7 34335.0000 PontmThivencelle
26 0 0 9 34335.0000 PontmThivencelle
34335.0000 2.9000 22.0000
34335.0000 2.9000 20.7400
34335.0000 2.9000 18.2900 rg
34335.0000 6.4000 17.7900
@@ -311,8 +316,10 @@
34335.0000 12.8000 17.2600
34335.0000 12.8000 18.3100 rd
34335.0000 12.8000 20.7400
34335.0000 12.8000 22.0000
999.9990 999.9990 999.9990
27 0 0 7 34343.0000 PontvThivencelle
27 0 0 9 34343.0000 PontvThivencelle
34343.0000 2.9000 22.0000
34343.0000 2.9000 20.7400
34343.0000 2.9000 18.2900 rg
34343.0000 6.4000 17.7900
@@ -320,6 +327,7 @@
34343.0000 12.8000 17.2600
34343.0000 12.8000 18.2900 rd
34343.0000 12.8000 20.7400
34343.0000 12.8000 22.0000
999.9990 999.9990 999.9990
28 0 0 10 34345.0000 avalPontThivencell
34345.0000 0.0000 21.3600
+70 -4
View File
@@ -142,7 +142,7 @@ select the cross section named \textit{PontRD101m} and open the edition window.
On the left panel is the list of all the points of the section, with their coordinates, their name and their transversal absisa. The Z coordinate of the highest point is written in blue and the lowest in red. Points can have a name. If a point with the same name exists in every section in a reach, it forms a longitudinal line. For example, here we have \textit{rg} and \textit{rd} which represent the left bank and the right bank of the main chanel.
On the plot is a projection of the cross section. You can use \texttt{[ctrl + click]} to select a point in the plot. For now we don't have to change anything. You can close the cross section edition window and the geometry edition window.
On the plot is a projection of the cross section. You can use \texttt{[ctrl + click]} to select a point in the plot and \texttt{[shift + click]} to select a water line and fisualize usefull geometric data. You can close the cross section edition window and the geometry edition window.
\section{Edit the boundary conditions}
@@ -172,7 +172,8 @@ You can close the the \textit{Edit Boundary Conditions} and the \textit{Boundary
\section{Create initial conditions}
From the main window, click on \texttt{[Hydraulics] => [Initial conditions]}.
Click on \texttt{Generate minimal height} and enter a height of 2 meters in the pop-up window to generate an initial water height condition that verify the folowing conditions: a water height of at least 2 meters and a decreasing water surface elevation from upstream to downstream. You should see:
If you don't know the initial conditions in water elevation and flow discharge of the river, you can use \texttt{[Generate minimal height]} or \texttt{[Generate constant discharge]} buttons to let Pamhyr2 estimate an initial condition using the Manning-Strickler formula.
Click on \texttt{[Generate minimal height]} and enter a discharge of $4 m^2$ in the pop-up window to generate an initial water height condition based on the Manning-Strickler formula and a uniform discharge of $4 m^2$. You should see:
\begin{center}
\includegraphics[width=15cm]{ic.png}
@@ -184,18 +185,83 @@ Close the \textit{Initial conditions} window.
\section{Edit friction coefficients}
From the main window, click on \texttt{[Hydraulics] => [Edit friction]}.
You fist have to define sets of Strickler coefficients. Click on \includegraphics[width=0.5cm]{"../../../src/View/ui/ressources/edit.png"} to open the \textit{Strickler} window. Here you can create couples of Strickler coefficients, the first one for the minor bed, the second one for the medium bed. Click on \textit{add} four times to create four new couple. Give them the folowing values:
You fist have to define sets of Strickler coefficients. Click on \includegraphics[width=0.5cm]{"../../../src/View/ui/ressources/edit.png"} to open the \textit{Strickler} window. Here you can create couples of Strickler coefficients, the first one for the minor bed, the second one for the medium bed. Click on \textit{add} four times to create four new couple. Give them the folowing values:
\begin{center}
\includegraphics[width=15cm]{K.png}
\par\end{center}
Close the \textit{Strickler} window. On the \textit{Edit friction} window, add four lines with the button \includegraphics[width=0.5cm]{gtk_add.png} to create fous friction zones. Each zone is defined by a \textit{begin} and \textit{end} PK and a \textit{begin} and \textit{end} Strickler couple. The strickler coefficient couples inside a zone are interpolated from the \textit{begin} and \textit{end} couples. In our case, we will use constant coefficients per zone. Set the zones as follow:
Close the \textit{Strickler} window. On the \textit{Edit friction} window, add four lines with the button \includegraphics[width=0.5cm]{gtk_add.png} to create four friction zones. Each zone is defined by a \textit{begin} and \textit{end} PK and a \textit{begin} and \textit{end} Strickler couple. The strickler coefficient couples inside a zone are interpolated from the \textit{begin} and \textit{end} couples. In our case, we will use constant coefficients per zone. Set the zones as follow:
\begin{center}
\includegraphics[width=15cm]{frictions.png}
\par\end{center}
The selected zone is highlighted in blue. Close the \textit{Edit friction} window.
\section{Model hydraulic structures}
Sometimes there can be cross-sections in which Shallow water equations can not be used to model the water flow. In that case, we have to define an other law to link the water elevation and the flow discharge. This is the case, for example, under bridges when the water elevation is too high, leading to a flow in charge. Pamhyr2 enables to define various hydraulic structures with laws that can be parametrized. In our case, a weir and two bridges have to be represented as hydraulic structures.
From the main window, click on \texttt{[Hydraulics] => [Hydraulic structures]} to open the hydraulic structures window. Click tree times on the \includegraphics[width=0.5cm]{gtk_add.png} button to create three hydraulic structures. Each structure can have a name and must have a reach and a kp. Set them as follow:
\begin{center}
\includegraphics[width=15cm]{hs.png}
\par\end{center}
Select the downstream weir and click on \includegraphics[width=0.5cm]{"../../../src/View/ui/ressources/edit.png"} to edit the laws of this structure. Hydraulic structures are composed of basic hydraulic structures. You can combine the laws of several basic hydraulic structures to setup your structure. For this weir, we only need a weir basic hydraulic structure. Click on \includegraphics[width=0.5cm]{gtk_add.png} to add a new basic hydraulic structure, give it the \textit{weir} type and set it up as folow:
\begin{center}
\includegraphics[width=15cm]{seuil.png}
\par\end{center}
Go back to the \textit{hydraulic structures} window.
Select the RS101 bridge and click on \includegraphics[width=0.5cm]{"../../../src/View/ui/ressources/edit.png"} to edit the laws of this structure. A bridge can be modeled as a combination of an orifice for the flow under the bridge and a weir for the flow over the bridge. Create two basic hydraulic structures and set them as folow:
\begin{center}
\includegraphics[width=15cm]{seuilRS101.png}
\includegraphics[width=15cm]{orificeRS101.png}
\par\end{center}
Go back to the \textit{hydraulic structures} window and aply the same procedure for the Thivencelle bridge:
\begin{center}
\includegraphics[width=15cm]{orificeThivencelle.png}
\includegraphics[width=15cm]{seuilThivencelle.png}
\par\end{center}
You can now close the \textit{hydraulic structures} windows.
\section{Solver parameters}
From the main window, click on \texttt{[Execute] => [Numerical parameters for solvers]}.
In the window \textit{solver parameters} select the \textit{Mage v8} tab. Set the solver parameters as follow:
\begin{center}
\includegraphics[width=15cm]{param.png}
\par\end{center}
Close the \textit{solver parameters} window.
\section{Run the simulation}
From the main window, click on \texttt{[Execute] => [Run solver]}.
Select \textit{Defaut-Mage - (Mage8)} anc click on the \textit{Run} button.
It will open two windows: the \textit{Check list} window and the \textit{Solver log} window.
The \textit{Check list} window gives som hints about the validity of your model, and the \textit{Solver log} window displays the outputs of the solver.
From the \textit{Solver log} window you can re-run the computation, and from the textit{Solver log} window you can click on the \textit{Results} button to open the \textit{Results} window.
\section{Visualize the results}
If you closed the textit{Solver log} window, you can click on \texttt{[Results] => [Visualize last results]} from the main window to open the \textit{Results} window. The top lets panel let you select your reach, the bottom left panel lets you select a cross-section in that reach. the tree plots on the right show the reach and the cross-section the same way than in the \textit{Geometry} window. You can use the bottom slider to visualize the water elevation at different timesteps.
To visualize the flow discharge, switch to the \textit{Hydrograph} tab.
To create custom 2D plots, click on the \includegraphics[width=0.5cm]{gtk_add.png} button on the top left of the window. Select the values you want on the $X$ and $Y$ axis and click on \textit{OK}.
You can now see a new tab with the custom 2D plot in the right panel of the \textit{Results} window.
\pagebreak{}
\end{document}
+13 -5
View File
@@ -16,10 +16,14 @@
# -*- coding: utf-8 -*-
import logging
from tools import trace, timer
from Model.Tools.PamhyrDB import SQLSubModel
logger = logging.getLogger()
class Friction(SQLSubModel):
def __init__(self, name: str = "", status=None):
@@ -60,7 +64,8 @@ class Friction(SQLSubModel):
@classmethod
def _db_load(cls, execute, data=None):
new = []
reach = data["parent"] # Reach object
reach = data["reach"]
status = data["status"]
stricklers = data["stricklers"].stricklers
@@ -69,9 +74,6 @@ class Friction(SQLSubModel):
f"FROM friction WHERE reach = {reach.id}"
)
for _ in table:
new.append(None)
for row in table:
ind = row[0]
# Get stricklers
@@ -86,7 +88,9 @@ class Friction(SQLSubModel):
sec.begin_strickler = bs
sec.end_strickler = es
yield ind, sec
new.append((ind, sec))
return new
def _db_save(self, execute, data=None):
ind = data["ind"]
@@ -116,6 +120,10 @@ class Friction(SQLSubModel):
def edge(self):
return self._edge
@property
def reach(self):
return self._edge
@edge.setter
def edge(self, edge):
self._edge = edge
+3 -1
View File
@@ -50,10 +50,12 @@ class FrictionList(PamhyrModelList):
def _db_load(cls, execute, data=None):
new = cls(status=data['status'])
new._lst = Friction._db_load(
ilst = Friction._db_load(
execute, data
)
new._lst = list(map(lambda x: x[1], sorted(ilst)))
return new
def _db_save(self, execute, data=None):
+54 -2
View File
@@ -16,7 +16,7 @@
# -*- coding: utf-8 -*-
from math import dist
from math import dist, sqrt, acos
import numpy as np
from Model.Tools.PamhyrDB import SQLSubModel
@@ -116,7 +116,7 @@ class PointXYZ(Point, SQLSubModel):
sl = self._sl.id if self._sl is not None else -1
sql = (
"INSERT OR REPLACE INTO " +
"INSERT INTO " +
"geometry_pointXYZ(ind, name, x, y, z, profile, sl) " +
"VALUES (" +
f"{ind}, '{self._db_format(self._name)}', " +
@@ -204,3 +204,55 @@ class PointXYZ(Point, SQLSubModel):
Euclidean distance between the two points
"""
return dist((p1.x, p1.y, p1.z), (p2.x, p2.y, p2.z))
@staticmethod
def angle(p1, p2, p3):
"""angle p1 p2 p3.
Args:
p1: A XYZ Point
p2: A XYZ Point
p3: A XYZ Point
Returns:
angle p1 p2 p3
"""
a = PointXYZ.distance(p1, p2)
b = PointXYZ.distance(p2, p3)
c = PointXYZ.distance(p1, p3)
return acos((a**2 + b**2 - c**2) / (2 * a * b))
@staticmethod
def triangle_height(p1, p2, p3):
"""triangle p1 p2 p3 height starting at p2.
Args:
p1: A XYZ Point
p2: A XYZ Point
p3: A XYZ Point
Returns:
triangle height starting at p2
"""
a = PointXYZ.distance(p1, p2)
b = PointXYZ.distance(p2, p3)
c = PointXYZ.distance(p1, p3)
h2 = a**2 - ((c**2 - b**2 + a**2) / 2*c)**2
return sqrt(h2)
@staticmethod
def triangle_area(p1, p2, p3):
"""triangle p1 p2 p3 area.
Args:
p1: A XYZ Point
p2: A XYZ Point
p3: A XYZ Point
Returns:
triangle height starting at p2
"""
h = PointXYZ.triangle_height(p1, p2, p3)
d = PointXYZ.distance(p1, p3)
return h*d/2
+18 -14
View File
@@ -214,31 +214,31 @@ class Profile(object):
self.points.insert(index, point)
self._status.modified()
def delete(self, indexes: int):
"""Delete points at index
def add_point(self, point:Point):
"""append point at end of profile.
Args:
indexes: List of index of points.
point: The point.
Returns:
Nothing.
"""
points = set(
self._points.append(point)
self._status.modified()
def delete_i(self, indexes: list):
self._points = list(
map(
lambda e: e[1],
filter(
lambda e: e[0] in indexes,
lambda e: e[0] not in indexes,
enumerate(self.points)
)
)
)
self.points = list(
filter(
lambda p: p not in points,
self.points
)
)
self._status.modified()
def delete_points(self, points):
@@ -250,7 +250,7 @@ class Profile(object):
Returns:
Nothing.
"""
self.points = list(
self._points = list(
filter(
lambda p: p not in points,
self.points
@@ -286,7 +286,7 @@ class Profile(object):
elif column == 'z':
def predicate(p): return p.z
self.points = sorted(
self._points = sorted(
self.points,
key=predicate,
reverse=is_reversed
@@ -298,7 +298,7 @@ class Profile(object):
if len(self.points) != len(indexes):
logger.critical("Indexes list do not correspond to point list")
self.points = list(
self._points = list(
map(
lambda x: x[1],
sorted(
@@ -351,3 +351,7 @@ class Profile(object):
# Abstract method for width approximation
def width_approximation(self):
raise NotImplementedMethodeError(self, self.width_approximation)
# Abstract method for width approximation
def get_water_limits(self, z):
raise NotImplementedMethodeError(self, self.get_water_limits)
+140 -10
View File
@@ -115,9 +115,6 @@ class ProfileXYZ(Profile, SQLSubModel):
f"WHERE reach = {reach.id}"
)
for _ in table:
profiles.append(None)
for row in table:
id = row[0]
ind = row[1]
@@ -132,7 +129,7 @@ class ProfileXYZ(Profile, SQLSubModel):
id=id, num=num,
name=name, kp=kp,
code1=code1, code2=code2,
reach=data["parent"],
reach=reach,
status=status
)
@@ -151,10 +148,6 @@ class ProfileXYZ(Profile, SQLSubModel):
yield ind, new
# profiles[ind] = new
# return profiles
def _db_save(self, execute, data=None):
ok = True
ind = data["ind"]
@@ -191,11 +184,20 @@ class ProfileXYZ(Profile, SQLSubModel):
profile = None
try:
if len(header) == 0:
name = data[0]
kp = data[1]
reach = data[2]
status = data[3]
profile = cls(
*data
id=-1,
name=name,
kp=kp,
reach=reach,
status=status
)
else:
valid_header = {'name', 'reach', 'kp'}
valid_header = {'name', 'reach', 'kp', 'status'}
d = {}
for i, v in enumerate(data):
h = header[i].strip().lower().split(' ')[0]
@@ -204,6 +206,7 @@ class ProfileXYZ(Profile, SQLSubModel):
profile = cls(**d)
except Exception as e:
logger.error(e)
raise ClipboardFormatError(header, data)
return profile
@@ -437,8 +440,135 @@ class ProfileXYZ(Profile, SQLSubModel):
return list(map(lambda s: s - constant, station))
@timer
def length(self) -> list:
l = []
x = self.x()
y = self.y()
z = self.z()
for i in range(len(self.points)-1):
dx = x[i+1] - x[i]
dy = y[i+1] - y[i]
dz = z[i+1] - z[i]
l.append( np.sqrt(dx*dx + dy*dy + dz*dz) )
return l
@timer
def cumul_length(self) -> list:
lcum = []
lc = 0.0
l = self.length()
for i in l:
lc = lc + i
lcum.append(lc)
return lcum
@timer
def purge1(self, np) -> list:
points = self.points
if (self.number_points <= np): return points
while len(points) > np:
x1 = [point.x() for point in points]
y1 = [point.y() for point in points]
z1 = [point.z() for point in points]
l = [(x*x+y*y+z*z) for (x,y,z) in zip(x1,y1,z1)]
l2 = [(x + y) for (x, y) in zip(l[:-1], l[1:])]
imin = np.argmin(l2)+1
points.pop(imin)
return points
@timer
def purge2(self, np) -> list:
points = self.points
if (self.number_points <= np): return points
while len(points) > np:
l2 = []
for i in range(len(points)-2):
l2.append(PointXYZ.angle(points(i),points(i+1),points(i+2)))
imax = np.argmax(l2)+1
points.pop(imax)
return points
@timer
def purge3(self, np) -> list:
points = self.points
if (self.number_points <= np): return points
while len(points) > np:
l2 = []
for i in range(len(points)-2):
l2.append(PointXYZ.triangle_area(points(i),points(i+1),points(i+2)))
imin = np.argmin(l2)+1
points.pop(imin)
return points
def width_approximation(self):
rg = self.get_point_by_name("rg")
rd = self.get_point_by_name("rd")
return abs(rg.dist(rd))
def get_water_limits(self, z):
# ====================================================================
# détermination des points limites RG et RD pour un niveau
# d'eau donné
#
# irg et ird sont les premiers indices en partant des rives
# gauche et droite qui correspondent à des points sous la
# surface de l'eau ptX et ptY sont les points interpolés où
# le plan d'eau intersecte le profil known_level est le
# niveau d'eau pour lequel on a obtenu irg, ird, ptX et ptY
# ====================================================================
# initialisation
irg = -1
ird = -1
for i in range(self.number_points):
if self.point(i).z <= z:
irg = i
for i in reversed(range(self.number_points)):
if self.point(i).z <= z:
ird = i
# interpolation des points ptX et ptY
if (irg < self.number_points - 1):
x = np.interp(
z,
[self.point(irg).z, self.point(irg + 1).z],
[self.point(irg).x, self.point(irg + 1).x]
)
y = np.interp(
z,
[self.point(irg).z, self.point(irg + 1).z],
[self.point(irg).y, self.point(irg + 1).y]
)
ptX = PointXYZ(x, y, z)
else:
ptX = self.point(0)
if (ird > 0):
x = np.interp(
z,
[self.point(ird-1).z, self.point(ird).z],
[self.point(ird-1).x, self.point(ird).x]
)
y = np.interp(
z,
[self.point(ird).z, self.point(ird - 1).z],
[self.point(ird).y, self.point(ird - 1).y]
)
ptY = PointXYZ(x, y, z)
else:
ptY = self.point(self.number_points - 1)
return ptX, ptY
+261 -1
View File
@@ -61,7 +61,7 @@ class Reach(SQLSubModel):
@classmethod
def _db_load(cls, execute, data=None):
new = cls(status=data["status"], parent=data["parent"])
new = cls(status=data["status"], parent=data["reach"])
new._profiles = ProfileXYZ._db_load(
execute,
@@ -579,6 +579,266 @@ class Reach(SQLSubModel):
file_st.write(" 999.9990 999.9990 999.9990")
file_st.write("\n")
# Interpolate
@timer
def interpolate_step(self, lim: list, guideline1: str, guideline2: str, step: float, longmi: int, linear: bool):
"""interpolate with a given step between profile lim[0] and profile lim[1]
Args:
lim: index of profiles for interpolation
guideline1: first guideline for distance
guideline2: second guideline for distance
step: interpolation step
longmi: 1 if distance computed on min(dist(guideline1),dist(guideline2))
2 if distance computed on max(dist(guideline1),dist(guideline2))
else: distance computed on (dist(guideline1)+dist(guideline2))/2
linear: true if linear interpolation, false if spline interpolation
Returns:
List of profiles including old profiles and interpolated profiles.
"""
if len(lim) != 2:
print('TODO: CRITICAL ERROR!') # error for now, only one interval
if lim[0] < 0 or lim[0] >= lim[1] or lim[1] >= self.number_profiles:
print('TODO: CRITICAL ERROR!')
if lim[1] < 0 or lim[0] > lim[1]:
print('TODO: CRITICAL ERROR!')
comp, inconmp = self.compute_guidelines()
if not guideline1 in comp:
print('TODO: CRITICAL ERROR!')
if not guideline2 in comp:
print('TODO: CRITICAL ERROR!')
nbsti = [] # number of profiles to add between self._profile[i] and self._profile[i+1]
for i in range(lim[0]):
nbsti.append(0)
for i in range(lim[0],lim[1]-1):
points = self.profile(i).points
point11 = next(filter(lambda p: p.name == guideline1, points))
point12 = next(filter(lambda p: p.name == guideline2, points))
points = self.profile(i+1).points
point21 = next(filter(lambda p: p.name == guideline1, points))
point22 = next(filter(lambda p: p.name == guideline2, points))
d1 = point11.dist(point21)
d2 = point12.dist(point22) # warning: 3D distance
if (longmi == 1):
d = max(d1, d2)
elif (longmi == 2):
d = min(d1, d2)
else:
d = 0.5*(d1 + d2)
nbsti[i] = nint(d / pas) - 1
if(nbsti[i] < 0): nbsti[i] = 0
for i in range(lim[1]-1,self.number_profiles-1):
nbsti.append(0)
return self.interpolate_nprof(nbsti, lineaire)
@timer
def interpolate_nprof(self, nbsti: List[int], linear: bool):
"""add nbsti[i] profiles between self._profile[i] and self._profile[i+1]
Args:
nbsti: number of profiles to add between self._profile[i] and self._profile[i+1]
linear: true if linear interpolation, false if spline interpolation
Returns:
List of profiles including old profiles and interpolated profiles.
"""
if(lineaire):
return self.interpolate_linear(nbsti)
else:
# we might add the spline interpolation later
print('TODO: CRITICAL ERROR!')
@timer
def interpolate_linear(self, nbsti: List[int]):
"""add nbsti[i] profiles between self._profile[i] and self._profile[i+1]
Args:
nbsti: number of profiles to add between self._profile[i] and self._profile[i+1]
Returns:
List of profiles including old profiles and interpolated profiles.
"""
if len(nbsti) != self.number_profiles()-1:
print('TODO: CRITICAL ERROR!') # error for now, only one interval
sections_out = []
kps = self.get_kp()
for isect_in in range(len(nbsti)):
sections_out.append(self.profiles(isect_in))
dkp = kps[isect_in + 1] - kps[isect_in]
sect_in1 = self.profiles(isect_in)
sect_in2 = self.profiles(isect_in+1)
for isect_loc in range(nbsti[isect_in]):
# RATIO entre les deux sections initiales
d=float(isect_loc)/float(nbsti(isect_in)+1)
x1 = sect_in1.x()
y1 = sect_in1.y()
z1 = sect_in1.z()
x2 = sect_in2.x()
y2 = sect_in2.y()
z2 = sect_in2.z()
tags = sect_in1.names()
section_out.append(ProfileXYZ(kp = kps[isect_in] + d*dkp,
reach = self))
for i in range(len(x1)): # can do better...
# add the points to the created section
sections_out[-1].add_point(PointXYZ(x = x1[i] + d*(x2[i] - x1[i]),
y = y1[i] + d*(y2[i] - y1[i]),
z = z1[i] + d*(z2[i] - z1[i]),
name = tags[i]))
# don't forget the last one:
sections_out.append(self.profiles[self.number_profiles-1])
return sections_out
@timer
def st_to_m(self):
"""convert a ST bief to a M bief (same number of points on every section)
Args:
nbsti: number of profiles to add between self._profile[i] and self._profile[i+1]
Returns:
List of profiles including old profiles and interpolated profiles.
"""
isectmax = 0
npoint_max = 0
new_profiles = []
np = []
nprof = len(self.number_profiles)
# find the profile with more points
for isect in range(nprof):
new_profiles.append(self.profile(isect))
points = new_profiles[isect].points
np.append(len(points))
if(npoint_max < len(points)):
npoint_max = len(points)
isectmax = isect
# let's go ! up from isectmax
for isect in range(isectmax+1,nprof):
# TODO case new_profiles[isect].length() = 0.0
# TODO case new_profiles[isect-1].length() = 0.0
l = new_profiles[isect-1].length() # length of each segment
lcum = new_profiles[isect-1].cumul_length() # lcum[-1] is length
alpha = [i/lcum[-1] for i in lcum]
l = new_profiles[isect].length()
lcum = new_profiles[isect].cumul_length()
beta = [i/lcum[-1] for i in lcum]
for i in range(np[isect], npoints_max):
beta.append(1.0)
for k in range(np[isect]): # boucle sur les points à rajouter
trouve = False
for j0 in range(np[isect]-1):
if(trouve):break
undeplus=True
for j1 in range(j0, np[isect]-1):
if(undeplus):
if(beta[j1].le.alpha[j1]):
undeplus=False
break
if (undeplus):
if (j0 == np[isect]-2):
trouve=True
else:
j1=j0+1
if (beta[j0]-alpha[j0] > alpha[j1]-beta[j0]): # beta(j0) > (alpha(j1)+alpha(j2))/2
trouve = True
if (trouve):
# on rajoute un point
ratio=(alpha[j0]-beta[j0-1])/(beta[j0]-beta[j0-1])
if (ratio < 0.0):
# on double le point a gauche
new_profiles[isect].insert_point(j0,new_profiles[isect].points[j0])
beta[j0]=beta[j0-1]
new_profiles[isect].points[j0+1].name('')
else:
p = PointXYZ(x = (1.0-ratio)*new_profiles[isect].points[j0].x + ratio*new_profiles[isect].points[j0].x,
y = (1.0-ratio)*new_profiles[isect].points[j0].y + ratio*new_profiles[isect].points[j0].y,
z = (1.0-ratio)*new_profiles[isect].points[j0].z + ratio*new_profiles[isect].points[j0].z
)
new_profiles[isect].insert_point(j0,p)
n = len(new_profiles[isect].points)
if n <= npoint_max: # we add the missing points at the end of the profile
for k in range(n,npoints_max): new_profiles[isect].add_point(new_profiles[isect].points[-1])
# let's go ! down from isectmax
for isect in range(isectmax-1,-1,-1):
# TODO case new_profiles[isect].length() = 0.0
# TODO case new_profiles[isect-1].length() = 0.0
l = new_profiles[isect+1].length() # length of each segment
lcum = new_profiles[isect+1].cumul_length() # lcum[-1] is length
alpha = [i/lcum[-1] for i in lcum]
l = new_profiles[isect].length()
lcum = new_profiles[isect].cumul_length()
beta = [i/lcum[-1] for i in lcum]
for i in range(np[isect], npoints_max):
beta.append(1.0)
for k in range(np[isect]): # boucle sur les points à rajouter
trouve = False
for j0 in range(np[isect]-1):
if(trouve):break
undeplus=True
for j1 in range(j0, np[isect]-1):
if(undeplus):
if(beta[j1].le.alpha[j1]):
undeplus=False
break
if (undeplus):
if (j0 == np[isect]-2):
trouve=True
else:
j1=j0+1
if (beta[j0]-alpha[j0] > alpha[j1]-beta[j0]): # beta(j0) > (alpha(j1)+alpha(j2))/2
trouve = True
if (trouve):
# on rajoute un point
ratio=(alpha[j0]-beta[j0-1])/(beta[j0]-beta[j0-1])
if (ratio < 0.0):
# on double le point a gauche
new_profiles[isect].insert_point(j0,new_profiles[isect].points[j0])
new_profiles[isect].points[j0+1].name('')
beta[j0]=beta[j0-1]
else:
p = PointXYZ(x = (1.0-ratio)*new_profiles[isect].points[j0].x + ratio*new_profiles[isect].points[j0].x,
y = (1.0-ratio)*new_profiles[isect].points[j0].y + ratio*new_profiles[isect].points[j0].y,
z = (1.0-ratio)*new_profiles[isect].points[j0].z + ratio*new_profiles[isect].points[j0].z,
)
new_profiles[isect].insert_point(j0,p)
n = len(new_profiles[isect].points)
if n <= npoint_max: # we add the missing points at the end of the profile
for k in range(n,npoints_max): new_profiles[isect].add_point(new_profiles[isect].points[-1])
@timer
def purge(self, np: int) -> list:
profiles = []
for profile in self.profiles():
points = profile.purge2(np)
profiles.append(ProfileXYZ())
profiles[-1].import_points(points)
return profiles
def get_incline(self):
first = self.profile(0)
last = self.profile(len(self) - 1)
@@ -0,0 +1,191 @@
# HydraulicStructures.py -- Pamhyr
# Copyright (C) 2023 INRAE
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# -*- coding: utf-8 -*-
import logging
from tools import trace, timer
from Model.Tools.PamhyrDB import SQLSubModel
from Model.Except import NotImplementedMethodeError
from Model.HydraulicStructures.Basic.Value import (
BHSValue
)
logger = logging.getLogger()
class BasicHS(SQLSubModel):
_sub_classes = [
BHSValue,
]
_id_cnt = 0
def __init__(self, id: int = -1, name: str = "",
status=None):
super(BasicHS, self).__init__()
self._status = status
if id == -1:
self.id = BasicHS._id_cnt
else:
self.id = id
self._name = name
self._type = ""
self._enabled = True
self._data = []
BasicHS._id_cnt = max(BasicHS._id_cnt + 1, self.id)
@classmethod
def _db_create(cls, execute):
execute("""
CREATE TABLE hydraulic_structures_basic(
id INTEGER NOT NULL PRIMARY KEY,
name TEXT NOT NULL,
type TEXT NOT NULL,
enabled BOOLEAN NOT NULL,
hs INTEGER,
FOREIGN KEY(hs) REFERENCES hydraulic_structures(id)
)
""")
return cls._create_submodel(execute)
@classmethod
def _db_update(cls, execute, version):
major, minor, release = version.strip().split(".")
if major == minor == "0":
if int(release) < 6:
cls._db_create(execute)
return True
@classmethod
def _get_ctor_from_type(cls, t):
from Model.HydraulicStructures.Basic.Types import (
BHS_types, NotDefined,
)
res = NotDefined
if t in BHS_types.keys():
res = BHS_types[t]
return res
@classmethod
def _db_load(cls, execute, data=None):
new = []
table = execute(
"SELECT id, name, type, enabled, hs " +
"FROM hydraulic_structures_basic " +
f"WHERE hs = {data['hs_id']} "
)
for row in table:
bhs_id = row[0]
name = row[1]
type = row[2]
enabled = (row[3] == 1)
hs_id = row[4]
ctor = cls._get_ctor_from_type(type)
bhs = ctor(
id=bhs_id,
name=name,
status=data['status']
)
bhs.enabled = enabled
data['bhs_id'] = bhs_id
bhs._data = BHSValue._db_load(
execute, data
)
new.append(bhs)
return new
def _db_save(self, execute, data=None):
hs_id = data['hs_id']
sql = (
"INSERT INTO " +
"hydraulic_structures_basic(id, name, type, enabled, hs) " +
"VALUES (" +
f"{self.id}, " +
f"'{self._db_format(self._name)}', " +
f"'{self._db_format(self._type)}', " +
f"{self._db_format(self.enabled)}, " +
f"{hs_id} " +
")"
)
execute(sql)
data['bhs_id'] = self.id
execute(
"DELETE FROM hydraulic_structures_basic_value " +
f"WHERE bhs = {self.id}"
)
for values in self._data:
values._db_save(execute, data)
return True
def __len__(self):
return len(self._data)
@property
def name(self):
return self._name
@name.setter
def name(self, name):
self._name = name
self._status.modified()
@property
def type(self):
return self._type
@type.setter
def type(self, type):
self._type = type
self._status.modified()
@property
def enabled(self):
return self._enabled
@enabled.setter
def enabled(self, enabled):
self._enabled = enabled
self._status.modified()
@property
def parameters(self):
return self._data
def convert(self, new_type):
return new_type(id=self.id, name=self.name, status=self._status)
@@ -0,0 +1,228 @@
# Types.py -- Pamhyr
# Copyright (C) 2023 INRAE
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# -*- coding: utf-8 -*-
from Model.HydraulicStructures.Basic.HydraulicStructures import (
BasicHS
)
from Model.HydraulicStructures.Basic.Value import (
BHSValue
)
class NotDefined(BasicHS):
def __init__(self, id: int = -1, name: str = "",
status=None):
super(NotDefined, self).__init__(
id=id, name=name,
status=status
)
self._type = "ND"
self._data = []
class SeuilDeversoir(BasicHS):
def __init__(self, id: int = -1, name: str = "",
status=None):
super(SeuilDeversoir, self).__init__(
id=id, name=name,
status=status
)
self._type = "S1"
self._data = [
BHSValue("Largeur", float, 1.0, status=status),
BHSValue("Cote", float, 1.0, status=status),
BHSValue("Coefficient de debit", float, 0.4, status=status),
]
class SeuilTrapezoidal(BasicHS):
def __init__(self, id: int = -1, name: str = "",
status=None):
super(SeuilTrapezoidal, self).__init__(
id=id, name=name,
status=status
)
self._type = "S2"
self._data = [
BHSValue("Largeur", float, 1.0, status=status),
BHSValue("Cote", float, 1.0, status=status),
BHSValue("Cote de mise en charge", float, 9999.999, status=status),
BHSValue("Coefficient de debit", float, 0.4, status=status),
BHSValue("Tangeante du demi angle", float, 0.0, status=status),
]
class SeuilTriangulaire(BasicHS):
def __init__(self, id: int = -1, name: str = "",
status=None):
super(SeuilTriangulaire, self).__init__(
id=id, name=name,
status=status
)
self._type = "S3"
self._data = [
BHSValue("Cote", float, 1.0, status=status),
BHSValue("Cote de mise en charge", float, 9999.999, status=status),
BHSValue("Coefficient de debit", float, 0.4, status=status),
BHSValue("Tangeante du demi angle", float, 0.0, status=status),
]
class OrificeRectangulaire(BasicHS):
def __init__(self, id: int = -1, name: str = "",
status=None):
super(OrificeRectangulaire, self).__init__(
id=id, name=name,
status=status
)
self._type = "OR"
self._data = [
BHSValue("Largeur", float, 0.0, status=status),
BHSValue("Cote", float, 0.0, status=status),
BHSValue("Cote de mise en charge", float, 9999.999,
status=status),
BHSValue("Coefficient de debit", float, 0.4, status=status),
BHSValue("Cote de mise en charge maximale", float, 9999.999,
status=status),
]
class OrificeCirculaire(BasicHS):
def __init__(self, id: int = -1, name: str = "",
status=None):
super(OrificeCirculaire, self).__init__(
id=id, name=name,
status=status
)
self._type = "OC"
self._data = [
BHSValue("Diametre", float, 0.0, status=status),
BHSValue("Cote", float, 0.0, status=status),
BHSValue("hauteur envasement", float, 0.0, status=status),
BHSValue("Coefficient de debit", float, 0.4, status=status),
]
# class OrificeVoute(BasicHS):
# def __init__(self, id: int = -1, name: str = "",
# status=None):
# super(OrificeVoute, self).__init__(
# id=id, name=name,
# status=status
# )
# self._type = "OV"
# self._data = [
# BHSValue("Cote", float, 1.0, status=status),
# BHSValue("Largeur", float, 1.0, status=status),
# BHSValue("Haut de la voute", float, 0.0, status=status),
# BHSValue("Bas de la voute", float, 0.0, status=status),
# BHSValue("Coefficient de debit", float, 0.4, status=status),
# ]
class VanneRectangulaire(BasicHS):
def __init__(self, id: int = -1, name: str = "",
status=None):
super(VanneRectangulaire, self).__init__(
id=id, name=name,
status=status
)
self._type = "V1"
self._data = [
BHSValue("Largeur", float, 1.0, status=status),
BHSValue("Cote", float, 0.0, status=status),
BHSValue("Coefficient de debit", float, 0.4, status=status),
BHSValue("Ouverture", float, 1.0, status=status),
BHSValue("Ouverture maximale", float, 1.0, status=status),
]
class VanneRectangulaireSimplifiee(BasicHS):
def __init__(self, id: int = -1, name: str = "",
status=None):
super(VanneRectangulaireSimplifiee, self).__init__(
id=id, name=name,
status=status
)
self._type = "V2"
self._data = [
BHSValue("Largeur", float, 1.0, status=status),
BHSValue("Cote", float, 0.0, status=status),
BHSValue("Coefficient de debit", float, 0.4, status=status),
BHSValue("Ouverture", float, 1.0, status=status),
BHSValue("Ouverture maximale", float, 1.0, status=status),
]
class Borda(BasicHS):
def __init__(self, id: int = -1, name: str = "",
status=None):
super(Borda, self).__init__(
id=id, name=name,
status=status
)
self._type = "BO"
self._data = [
BHSValue("Pas espace", float, 0.1, status=status),
BHSValue("Seuil", float, 0.15, status=status),
BHSValue("Coefficient", float, 0.4, status=status),
]
class UserDefined(BasicHS):
def __init__(self, id: int = -1, name: str = "",
status=None):
super(UserDefined, self).__init__(
id=id, name=name,
status=status
)
self._type = "UD"
self._data = [
BHSValue("Parameter 1", float, 0.0, status=status),
BHSValue("Parameter 2", float, 0.0, status=status),
BHSValue("Parameter 3", float, 0.0, status=status),
BHSValue("Parameter 4", float, 0.0, status=status),
BHSValue("Parameter 5", float, 0.0, status=status),
]
BHS_types = {
"ND": NotDefined,
"S1": SeuilDeversoir,
"S2": SeuilTrapezoidal,
"S3": SeuilTriangulaire,
"OR": OrificeRectangulaire,
"OC": OrificeCirculaire,
# "OV": OrificeVoute,
"V1": VanneRectangulaire,
"V2": VanneRectangulaireSimplifiee,
"BO": Borda,
"UD": UserDefined
}
@@ -0,0 +1,145 @@
# Value.py -- Pamhyr
# Copyright (C) 2023 INRAE
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# -*- coding: utf-8 -*-
from Model.Tools.PamhyrDB import SQLSubModel
class BHSValue(SQLSubModel):
_sub_classes = []
_id_cnt = 0
def __init__(self, name: str = "", type=float, value=0.0,
status=None):
super(BHSValue, self).__init__()
self._status = status
self._name = name
self._type = type
self._value = type(value)
@classmethod
def _db_create(cls, execute):
execute("""
CREATE TABLE hydraulic_structures_basic_value(
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
type TEXT NOT NULL,
value TEXT NOT NULL,
bhs INTEGER,
FOREIGN KEY(bhs) REFERENCES hydraulic_structures_basic(id)
)
""")
return cls._create_submodel(execute)
@classmethod
def _db_update(cls, execute, version):
major, minor, release = version.strip().split(".")
if major == minor == "0":
if int(release) < 6:
cls._db_create(execute)
return True
@classmethod
def _str_to_type(cls, type):
res = str
if type == "float":
res = float
elif type == "int":
res = int
elif type == "bool":
res = bool
return res
@classmethod
def _type_to_str(cls, type):
res = "str"
if type == float:
res = "float"
elif type == int:
res = "int"
elif type == bool:
res = "bool"
return res
@classmethod
def _db_load(cls, execute, data=None):
new = []
bhs_id = data["bhs_id"]
table = execute(
"SELECT name, type, value " +
"FROM hydraulic_structures_basic_value " +
f"WHERE bhs = '{bhs_id}'"
)
for row in table:
name = row[0]
type = cls._str_to_type(row[1])
value = row[2]
val = cls(
name=name,
type=type,
value=value,
status=data['status']
)
new.append(val)
return new
def _db_save(self, execute, data=None):
bhs_id = data["bhs_id"]
sql = (
"INSERT INTO " +
"hydraulic_structures_basic_value(name, type, value, bhs) " +
"VALUES (" +
f"'{self._db_format(self._name)}', " +
f"'{self._db_format(self._type_to_str(self._type))}', " +
f"'{self._db_format(self._value)}', " +
f"{bhs_id}" +
")"
)
execute(sql)
return True
@property
def name(self):
return self._name
@property
def type(self):
return self._type
@property
def value(self):
return self._value
@value.setter
def value(self, value):
self._value = self._type(value)
self._status.modified()
@@ -0,0 +1,285 @@
# HydraulicStructures.py -- Pamhyr
# Copyright (C) 2023 INRAE
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# -*- coding: utf-8 -*-
import logging
from functools import reduce
from tools import trace, timer, old_pamhyr_date_to_timestamp
from Model.Tools.PamhyrDB import SQLSubModel
from Model.Except import NotImplementedMethodeError
from Model.HydraulicStructures.Basic.HydraulicStructures import BasicHS
from Model.HydraulicStructures.Basic.Types import (
NotDefined,
)
logger = logging.getLogger()
class HydraulicStructure(SQLSubModel):
_sub_classes = [
BasicHS,
]
_id_cnt = 0
def __init__(self, id: int = -1, name: str = "",
status=None):
super(HydraulicStructure, self).__init__()
self._status = status
if id == -1:
self.id = HydraulicStructure._id_cnt
else:
self.id = id
self._name = name
self._input_kp = None
self._output_kp = None
self._input_reach = None
self._output_reach = None
self._enabled = True
self._data = []
HydraulicStructure._id_cnt = max(
HydraulicStructure._id_cnt + 1,
self.id
)
@classmethod
def _db_create(cls, execute):
execute("""
CREATE TABLE hydraulic_structures(
id INTEGER NOT NULL PRIMARY KEY,
name TEXT NOT NULL,
enabled BOOLEAN NOT NULL,
input_kp REAL NOT NULL,
output_kp REAL NOT NULL,
input_reach INTEGER,
output_reach INTEGER,
FOREIGN KEY(input_reach) REFERENCES river_reach(id),
FOREIGN KEY(output_reach) REFERENCES river_reach(id)
)
""")
return cls._create_submodel(execute)
@classmethod
def _db_update(cls, execute, version):
major, minor, release = version.strip().split(".")
if major == minor == "0":
if int(release) < 6:
cls._db_create(execute)
return True
@classmethod
def _db_load(cls, execute, data=None):
new = []
table = execute(
"SELECT id, name, enabled, " +
"input_kp, output_kp, " +
"input_reach, output_reach " +
"FROM hydraulic_structures "
)
for row in table:
it = iter(row)
hs_id = next(it)
name = next(it)
enabled = (next(it) == 1)
input_kp = next(it)
output_kp = next(it)
input_reach_id = next(it)
output_reach_id = next(it)
hs = cls(
id=hs_id,
name=name,
status=data['status']
)
hs.enabled = enabled
hs.input_kp = input_kp if input_kp != -1 else None
hs.output_kp = output_kp if output_kp != -1 else None
hs.input_reach, hs.output_reach = reduce(
lambda acc, n: (
n if n.id == input_reach_id else acc[0],
n if n.id == output_reach_id else acc[1]
),
data["edges"],
[None, None]
)
data['hs_id'] = hs_id
hs._data = BasicHS._db_load(execute, data)
new.append(hs)
return new
def _db_save(self, execute, data=None):
execute(f"DELETE FROM hydraulic_structures WHERE id = {self.id}")
input_reach_id = -1
if self._input_reach is not None:
input_reach_id = self._input_reach.id
output_reach_id = -1
if self._output_reach is not None:
output_reach_id = self._output_reach.id
input_kp = -1
if self.input_kp is not None:
input_kp = self.input_kp
output_kp = -1
if self.output_kp is not None:
output_kp = self.output_kp
sql = (
"INSERT INTO " +
"hydraulic_structures(" +
" id, name, enabled, input_kp, output_kp, " +
" input_reach, output_reach" +
") " +
"VALUES (" +
f"{self.id}, '{self._db_format(self._name)}', " +
f"{self._db_format(self.enabled)}, " +
f"{input_kp}, {output_kp}, " +
f"{input_reach_id}, {output_reach_id}" +
")"
)
execute(sql)
data['hs_id'] = self.id
execute(
"DELETE FROM hydraulic_structures_basic " +
f"WHERE hs = {self.id}"
)
for basic in self._data:
basic._db_save(execute, data)
return True
def __len__(self):
return len(self._data)
@property
def name(self):
return self._name
@name.setter
def name(self, name):
self._name = name
self._status.modified()
@property
def input_kp(self):
return self._input_kp
@input_kp.setter
def input_kp(self, input_kp):
self._input_kp = input_kp
self._status.modified()
@property
def output_kp(self):
return self._output_kp
@output_kp.setter
def output_kp(self, output_kp):
self._output_kp = output_kp
self._status.modified()
@property
def enabled(self):
return self._enabled
@enabled.setter
def enabled(self, enabled):
self._enabled = enabled
self._status.modified()
@property
def input_reach(self):
return self._input_reach
@input_reach.setter
def input_reach(self, input_reach):
self._input_reach = input_reach
self._status.modified()
@property
def output_reach(self):
return self._output_reach
@output_reach.setter
def output_reach(self, output_reach):
self._output_reach = output_reach
self._status.modified()
@property
def basic_structures(self):
return self._data.copy()
def basic_structure(self, index: int):
return self._data[index]
def add(self, index: int):
value = NotDefined(status=self._status)
self._data.insert(index, value)
self._status.modified()
return value
def insert(self, index: int, value: BasicHS):
self._data.insert(index, value)
self._status.modified()
def delete_i(self, indexes):
self._data = list(
map(
lambda e: e[1],
filter(
lambda e: e[0] not in indexes,
enumerate(self._data)
)
)
)
self._status.modified()
def delete(self, els):
self._data = list(
filter(
lambda e: e not in els,
self._data
)
)
self._status.modified()
def sort(self, _reverse=False, key=None):
if key is None:
self._data.sort(reverse=_reverse)
else:
self._data.sort(reverse=_reverse, key=key)
self._status.modified()
@@ -0,0 +1,78 @@
# HydraulicStructuresList.py -- Pamhyr
# Copyright (C) 2023 INRAE
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# -*- coding: utf-8 -*-
from copy import copy
from tools import trace, timer
from Model.Tools.PamhyrList import PamhyrModelList
from Model.HydraulicStructures.HydraulicStructures import HydraulicStructure
class HydraulicStructureList(PamhyrModelList):
_sub_classes = [
HydraulicStructure,
]
@classmethod
def _db_load(cls, execute, data=None):
new = cls(status=data['status'])
if data is None:
data = {}
new._lst = HydraulicStructure._db_load(
execute, data
)
return new
def _db_save(self, execute, data=None):
execute("DELETE FROM hydraulic_structures")
if data is None:
data = {}
for hs in self._lst:
hs._db_save(execute, data=data)
return True
def new(self, lst, index):
n = HydraulicStructure(status=self._status)
self._lst.insert(index, n)
self._status.modified()
return n
def __copy__(self):
new = HydraulicStructureList()
for lst in self._tabs:
new.tabs[lst] = self._tabs[lst].copy()
return new
def __deepcopy__(self):
new = HydraulicStructureList()
for lst in self._tabs:
new.tabs[lst] = self._tabs[lst].deepcopy()
return new
def copy(self):
return copy(self)
@@ -250,7 +250,7 @@ class InitialConditions(SQLSubModel):
)
if new._data is not None:
yield new
return new
def _db_save(self, execute, data=None):
ok = True
@@ -376,7 +376,7 @@ class InitialConditions(SQLSubModel):
* (abs(incline) ** (0.5)))
)
elevation= max(
elevation = max(
profile.z_min() + height,
previous_elevation
)
@@ -395,6 +395,7 @@ class InitialConditions(SQLSubModel):
self._data.append(new)
previous_elevation = elevation
self._generate_resort_data(profiles)
def generate_discharge(self, discharge: float):
self._data = []
@@ -421,7 +422,7 @@ class InitialConditions(SQLSubModel):
((width * 0.8) * strickler * (abs(incline) ** (0.5)))
) ** (0.6)
elevation= max(
elevation = max(
profile.z_min() + height,
previous_elevation
)
@@ -438,3 +439,15 @@ class InitialConditions(SQLSubModel):
previous_elevation = elevation
self._data.append(new)
self._generate_resort_data(profiles)
def _generate_resort_data(self, profiles):
is_reverse = False
if profiles[0].kp > profiles[-1].kp:
is_reverse = True
self._data.sort(
reverse=not is_reverse,
key=lambda d: d['kp']
)
+13
View File
@@ -212,3 +212,16 @@ class Graph(object):
def is_enable_edge(self, edge):
return edge._enable
# def get_edge_id(self, reach):
# for i, e in enumerate(self.enable_edges):
# if e.id == reach.id:
# return i
def get_edge_id(self, reach):
return next(
filter(
lambda e: e[1].id == reach.id,
enumerate(self.enable_edges())
)
)[0]
+5 -1
View File
@@ -94,7 +94,11 @@ class Reservoir(SQLSubModel):
new_reservoir._node = None
if node_id != -1:
new_reservoir._node = next(filter(lambda n: n.id == node_id, data["nodes"]))
new_reservoir._node = next(
filter(
lambda n: n.id == node_id, data["nodes"]
)
)
new_data = []
table = execute(
+23 -4
View File
@@ -37,6 +37,9 @@ from Model.Friction.FrictionList import FrictionList
from Model.SolverParameters.SolverParametersList import SolverParametersList
from Model.SedimentLayer.SedimentLayerList import SedimentLayerList
from Model.Reservoir.ReservoirList import ReservoirList
from Model.HydraulicStructures.HydraulicStructuresList import (
HydraulicStructureList,
)
from Solver.Solvers import solver_type_list
@@ -154,7 +157,9 @@ class RiverReach(Edge, SQLSubModel):
data = {}
table = execute(
"SELECT id, name, enable, node1, node2 FROM river_reach")
"SELECT id, name, enable, node1, node2 FROM river_reach"
)
for row in table:
# Update id counter
cls._id_cnt = max(cls._id_cnt, row[0])
@@ -169,10 +174,9 @@ class RiverReach(Edge, SQLSubModel):
new = cls(id, name, node1, node2, status=data["status"])
new.enable(enable=enable)
data["reach"] = id
data["parent"] = new
new._reach = Reach._db_load(execute, data)
data["reach"] = new
new._reach = Reach._db_load(execute, data)
new._frictions = FrictionList._db_load(execute, data)
reachs.append(new)
@@ -219,6 +223,7 @@ class River(Graph, SQLSubModel):
SolverParametersList,
SedimentLayerList,
ReservoirList,
HydraulicStructureList,
]
def __init__(self, status=None):
@@ -237,6 +242,9 @@ class River(Graph, SQLSubModel):
self._parameters = {}
self._sediment_layers = SedimentLayerList(status=self._status)
self._reservoir = ReservoirList(status=self._status)
self._hydraulic_structures = HydraulicStructureList(
status=self._status
)
@classmethod
def _db_create(cls, execute):
@@ -303,6 +311,12 @@ class River(Graph, SQLSubModel):
data
)
# Hydraulic Structures
new._hydraulic_structures = HydraulicStructureList._db_load(
execute,
data
)
# Parameters
new._parameters = SolverParametersList._db_load(
execute,
@@ -319,6 +333,7 @@ class River(Graph, SQLSubModel):
objs.append(self._sediment_layers)
objs.append(self._stricklers)
objs.append(self._reservoir)
objs.append(self._hydraulic_structures)
for solver in self._parameters:
objs.append(self._parameters[solver])
@@ -363,6 +378,10 @@ class River(Graph, SQLSubModel):
def reservoir(self):
return self._reservoir
@property
def hydraulic_structures(self):
return self._hydraulic_structures
@property
def parameters(self):
return self._parameters
@@ -142,6 +142,10 @@ class SolverParametersList(PamhyrModelList):
insert(v[0], v[1], ind)
ind += 1
if int(release) < 7:
insert("mage_init_internal", "N", ind)
ind += 1
new = [
("mage_sediment_masse_volumique", "2650.0"),
("mage_sediment_angle_repos", "40.0"),
+21 -5
View File
@@ -41,7 +41,7 @@ class Study(SQLModel):
def __init__(self, filename=None, init_new=True):
# Metadata
self._version = "0.0.5"
self._version = "0.0.7"
self.creation_date = datetime.now()
self.last_modification_date = datetime.now()
self.last_save_date = datetime.now()
@@ -86,7 +86,7 @@ class Study(SQLModel):
def is_saved(self):
return self.status.is_saved()
def save(self):
def save(self, progress=None):
# Save a copy of database
fdir, fname = os.path.split(self.filename)
@@ -110,7 +110,7 @@ class Study(SQLModel):
# Save
self.last_save_date = datetime.now()
self._save()
self._save(progress=progress)
self.status.save()
@property
@@ -266,38 +266,54 @@ class Study(SQLModel):
return new
def _save(self):
def _save(self, progress=None):
progress = progress if progress is not None else lambda: None
self.execute(
f"UPDATE info SET " +
f"value='{self._db_format(self.name)}' WHERE key='name'"
)
progress()
self.execute(
f"UPDATE info SET " +
f"value='{self._db_format(self.description)}' " +
"WHERE key='description'"
)
progress()
self.execute(
f"UPDATE info SET " +
f"value='{self._time_system}' WHERE key='time_system'"
)
progress()
self.execute(
f"UPDATE info SET " +
f"value='{timestamp(self._date)}' WHERE key='date'"
)
progress()
self.execute(
f"UPDATE info SET " +
f"value='{timestamp(self.creation_date)}' " +
"WHERE key='creation_date'"
)
progress()
self.execute(
f"UPDATE info SET " +
f"value='{timestamp(self.last_save_date)}' " +
"WHERE key='last_save_date'"
)
progress()
self._save_submodel([self._river])
self._save_submodel([self._river], data=progress)
self.commit()
def sql_save_request_count(self):
return self._count()
def _count(self):
cnt = self._save_count([self._river])
logger.debug(cnt)
return cnt + 6
def close(self):
"""Close db connection
+46 -6
View File
@@ -21,6 +21,7 @@ import sqlite3
import logging
from pathlib import Path
from functools import reduce
from tools import SQL
from Model.Except import NotImplementedMethodeError
@@ -87,11 +88,16 @@ class SQLModel(SQL):
raise NotImplementedMethodeError(self, self._update)
def _save_submodel(self, objs, data=None):
def fn(sql): return self.execute(
sql,
fetch_one=False,
commit=False
)
progress = data if data is not None else lambda: None
def fn(sql):
res = self.execute(
sql,
fetch_one=False,
commit=False
)
progress()
return res
ok = True
for obj in objs:
@@ -100,9 +106,43 @@ class SQLModel(SQL):
self.commit()
return ok
def _save(self):
def _save(self, progress=None):
raise NotImplementedMethodeError(self, self._save)
def _count(self):
raise NotImplementedMethodeError(self, self._count)
def _save_count(self, objs, data=None):
counter = {
"insert": 0,
"update": 0,
"delete": 0,
"other": 0,
}
def fn(sql):
if "insert" in sql.lower():
counter["insert"] = counter["insert"] + 1
elif "update" in sql.lower():
counter["update"] = counter["update"] + 1
elif "delete" in sql.lower():
counter["delete"] = counter["delete"] + 1
else:
counter["other"] = counter["other"] + 1
return []
ok = True
for obj in objs:
ok &= obj._db_save(fn)
logger.debug(counter)
return reduce(
lambda acc, k: acc + counter[k],
counter,
0
)
@classmethod
def _load(cls, filename=None):
raise NotImplementedMethodeError(cls, cls._load)
+1 -1
View File
@@ -67,7 +67,7 @@ class PamhyrModelDict(SQLSubModel):
if key in self._dict:
v = self._dict[key]
if type(v) == types.GeneratorType:
if type(v) is types.GeneratorType:
return list(v)
return v
+289
View File
@@ -0,0 +1,289 @@
# MageMesh.py -- Pamhyr
# Copyright (C) 2023 INRAE
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# -*- coding: utf-8 -*-
import os
import logging
from ctypes import (
cdll,
byref, Structure,
c_char_p, c_wchar_p,
create_string_buffer,
POINTER, c_void_p,
c_int, c_double, c_bool
)
from Scripts.AScript import AScript
logger = logging.getLogger()
bief_lib = None
c_init_bief_from_geo_file = None
c_get_nb_sections = None
c_get_nb_points_section = None
c_set_bief_name = None
c_st_to_m_compl = None
c_interpolate_profils_pas_transversal = None
c_update_pk = None
c_output_bief = None
##########################
# Binding initialisation #
##########################
def init_clib(lib_path):
global bief_lib
bief_lib = cdll.LoadLibrary(lib_path)
init_c_init_bief_from_geo_file(bief_lib)
init_c_get_nb_sections(bief_lib)
init_c_get_nb_points_section(bief_lib)
init_c_set_bief_name(bief_lib)
init_c_st_to_m_compl(bief_lib)
init_c_interpolate_profils_pas_transversal(bief_lib)
init_c_update_pk(bief_lib)
init_c_output_bief(bief_lib)
def init_c_init_bief_from_geo_file(bief_lib):
global c_init_bief_from_geo_file
c_init_bief_from_geo_file = getattr(
bief_lib, 'c_init_bief_from_geo_file'
)
c_init_bief_from_geo_file.argtypes = [
c_char_p, POINTER(c_int), POINTER(c_int)
]
c_init_bief_from_geo_file.restype = None
def init_c_get_nb_sections(bief_lib):
global c_get_nb_sections
c_get_nb_sections = getattr(bief_lib, 'c_get_nb_sections')
c_get_nb_sections.argtypes = [POINTER(c_int)]
c_get_nb_sections.restype = None
def init_c_get_nb_points_section(bief_lib):
global c_get_nb_points_section
c_get_nb_points_section = getattr(bief_lib, 'c_get_nb_points_section')
c_get_nb_points_section.argtypes = [POINTER(c_int), POINTER(c_int)]
c_get_nb_points_section.restype = None
def init_c_set_bief_name(bief_lib):
global c_set_bief_name
c_set_bief_name = getattr(bief_lib, 'c_set_bief_name')
c_set_bief_name.argtypes = [c_char_p]
c_set_bief_name.restype = None
def init_c_st_to_m_compl(bief_lib):
global c_st_to_m_compl
c_st_to_m_compl = getattr(bief_lib, 'c_st_to_m_compl')
c_st_to_m_compl.argtypes = [POINTER(c_int), c_char_p, c_char_p]
c_st_to_m_compl.restype = None
def init_c_interpolate_profils_pas_transversal(bief_lib):
global c_interpolate_profils_pas_transversal
c_interpolate_profils_pas_transversal = getattr(
bief_lib, 'c_interpolate_profils_pas_transversal'
)
c_interpolate_profils_pas_transversal.argtypes = [
POINTER(c_int), POINTER(c_int),
c_char_p, c_char_p,
POINTER(c_double), POINTER(c_bool),
POINTER(c_int), POINTER(c_bool)
]
c_interpolate_profils_pas_transversal.restype = None
def init_c_update_pk(bief_lib):
global c_update_pk
c_update_pk = getattr(bief_lib, 'c_update_pk')
c_update_pk.argtypes = [c_char_p]
c_update_pk.restype = None
def init_c_output_bief(bief_lib):
global c_output_bief
c_output_bief = getattr(bief_lib, 'c_output_bief')
c_output_bief.argtypes = [c_char_p]
c_output_bief.restype = None
def init_bief_from_geo_file(name, with_charriage, with_water):
logger.info("! call init_bief_from_geo_file:")
cname = create_string_buffer(name.encode())
c_init_bief_from_geo_file(
cname,
byref(c_int(with_charriage)),
byref(c_int(with_water))
)
#####################
# Binding functions #
#####################
def get_nb_sections():
nb_sections = c_int(0)
c_get_nb_sections(byref(nb_sections))
return nb_sections.value
def get_nb_points_section(section):
nb_points = c_int(0)
c_get_nb_points_section(byref(c_int(section)), byref(nb_points))
return nb_points.value
def set_bief_name(name):
logger.info(f"! call set_bief_name: {repr(name)}")
cname = create_string_buffer(name.encode())
c_set_bief_name(cname)
def st_to_m_compl(npoints, tag1=' ', tag2=' '):
logger.info(f"! call st_to_m_compl: {npoints}")
cnpoints = c_int(npoints)
ctag1 = create_string_buffer(tag1.encode())
ctag2 = create_string_buffer(tag2.encode())
c_st_to_m_compl(byref(cnpoints), ctag1, ctag2)
def interpolate_profils_pas_transversal(limite1, limite2,
directrice1, directrice2,
pas, lplan=False,
lm=3, lineaire=False):
logger.info("! call interpolate_profils_pas_transversal:")
climite1 = c_int(limite1)
climite2 = c_int(limite2)
cpas = c_double(pas)
clplan = c_bool(lplan)
clm = c_int(lm)
clineaire = c_bool(lineaire)
cdirectrice1 = create_string_buffer(directrice1.encode())
cdirectrice2 = create_string_buffer(directrice2.encode())
c_interpolate_profils_pas_transversal(
byref(climite1), byref(climite2),
cdirectrice1, cdirectrice2,
byref(cpas), byref(clplan),
byref(clm), byref(clineaire)
)
def update_pk(directrice):
logger.info("! call update_pk:")
cdirectrice = create_string_buffer(directrice.encode())
c_update_pk(cdirectrice)
def output_bief(name):
logger.info("! call output_bief:")
cname = create_string_buffer(name.encode())
c_output_bief(cname)
##########
# Script #
##########
class MageMesh(AScript):
name = "MageMesh"
description = "Mesh ST file to M"
def usage(self):
logger.info(
f"Usage : {self._args[0]} {self._args[1]} " +
"<SO_FILE> <ST_FILE> <STEP>"
)
def run(self):
if len(self._args) < 5:
return 1
try:
so = self._args[2]
st = self._args[3]
step = float(self._args[4])
except Exception as e:
logger.error(f"Argument format error: {e}")
return 2
try:
init_clib(so)
except Exception as e:
logger.error(f"Bindings failed: {e}")
return 3
self.meshing(st, step)
return 0
def meshing(self, st_file: str, step: float):
workdir = self.get_workdir(st_file)
file_name = st_file.rsplit(".", 1)[0]
reach_name = os.path.basename(file_name)
# Open
init_bief_from_geo_file(st_file, 0, 0)
set_bief_name(reach_name)
ns = get_nb_sections()
npts_max = max(
map(
lambda i: get_nb_points_section(i),
range(1, ns)
)
)
# Transform
st_to_m_compl(npts_max, 'rg', 'rd')
interpolate_profils_pas_transversal(
1, ns,
'un', 'np',
step
)
update_pk("un")
# Save
logger.info(f"Saved meshing geometry to {file_name}.M")
output_bief(f"{file_name}.M")
def get_workdir(self, file):
workdir = os.path.abspath(
os.path.dirname(file)
)
os.makedirs(workdir, exist_ok=True)
logger.info(f"Set working dir to {workdir}")
return workdir
+3
View File
@@ -121,6 +121,9 @@ class AbstractSolver(object):
def is_stoped(self):
return self._status == STATUS.STOPED
def has_results_loaded(self):
self._status = STATUS.NOT_LAUNCHED
@name.setter
def name(self, name):
self._name = name
+29 -14
View File
@@ -132,7 +132,7 @@ class CommandLineSolver(AbstractSolver):
return os.path.abspath(
os.path.join(
os.path.dirname(__file__),
"..", ".."
"..", "..", ".."
)
)
@@ -172,12 +172,13 @@ class CommandLineSolver(AbstractSolver):
if not os.path.exists(exe):
error = f"[ERROR] Path {exe} do not exists"
logger.info(error)
logger.warning(error)
return error
self._process.start(
exe, args,
)
self._process.waitForStarted()
return True
@@ -191,7 +192,7 @@ class CommandLineSolver(AbstractSolver):
if not os.path.exists(exe):
error = f"[ERROR] Path {exe} do not exists"
logger.info(error)
logger.warning(error)
return error
self._process.start(
@@ -212,37 +213,48 @@ class CommandLineSolver(AbstractSolver):
if not os.path.exists(exe):
error = f"[ERROR] Path {exe} do not exists"
logger.info(error)
logger.warning(error)
return error
self._process.start(
exe, args,
)
self._process.waitForStarted()
return True
def _data_ready(self):
# Read process output and put lines in queue
s = self._process.readAll().data().decode()
if self._output is not None:
for x in s.split('\n'):
self._output.put(x)
def _run_next(self, study):
self._step += 1
if self._step < len(self._runs):
res = self._runs[self._step](study)
if res is not True:
self._output.put(res)
else:
self._status = STATUS.STOPED
def _finished(self, study, exit_code, exit_status):
if self._output is not None:
self._output.put(exit_code)
logger.debug(
"Process finished with " +
f"code: {exit_code}, status: {exit_status}"
)
self._run_next(study)
def _run_next(self, study):
self._step += 1
if self._step >= len(self._runs):
self._status = STATUS.STOPED
return
fn = self._runs[self._step]
res = fn(study)
if res is not True:
self._output.put(res)
def run(self, study, process=None, output_queue=None):
self._study = study
@@ -255,7 +267,8 @@ class CommandLineSolver(AbstractSolver):
# Connect / reconnect signal
self._process.readyRead.connect(self._data_ready)
self._process.finished.connect(
lambda c, s: self._finished(study, c, s))
lambda c, s: self._finished(study, c, s)
)
# Prepare running step
self._runs = [
@@ -265,6 +278,8 @@ class CommandLineSolver(AbstractSolver):
]
self._step = 0
self._status = STATUS.RUNNING
# Run first step
res = self._runs[0](study)
if res is not True:
+202 -47
View File
@@ -20,7 +20,7 @@ import os
import logging
import numpy as np
from tools import timer
from tools import timer, trace
from Solver.CommandLine import CommandLineSolver
from Checker.Mage import MageNetworkGraphChecker
@@ -36,7 +36,7 @@ def mage_file_open(filepath, mode):
if "w" in mode:
# Write header
f.write("* This file is generate by PAMHYR, please don't modify\n")
f.write("* This file is generated by PAMHYR, please don't modify\n")
return f
@@ -81,6 +81,7 @@ class Mage(CommandLineSolver):
("mage_compute_reach_volume_balance", "y"),
("mage_max_reach_volume_balance", "0.001"),
("mage_min_reach_volume_to_check", "1000.0"),
("mage_init_internal", " "),
]
return lst
@@ -374,13 +375,7 @@ class Mage(CommandLineSolver):
with mage_file_open(os.path.join(repertory, f"{name}.INI"), "w+") as f:
has_ini = False
id = 1
reachs = study.river.edges()
reachs = list(
filter(
lambda e: e.is_enable(),
reachs
)
)
reachs = study.river.enable_edges()
# TODO put real date...
f.write(f"$ date en minutes : 0.00\n")
@@ -397,13 +392,13 @@ class Mage(CommandLineSolver):
id_sec = 1
for d in data:
IR = f"{id:>3}"
IS = f"{id_sec:>3}"
IR = f"{id}"
IS = f"{id_sec}"
discharge = f"{d['discharge']:>10.5f}"
elevation = f"{d['elevation']:>11.6f}"
kp = f"{d['kp']:>9.2f}"
f.write(f" {IR} {IS} {discharge}{elevation} {kp}\n")
f.write(f"{IR} {IS} {discharge} {elevation} {kp}\n")
id_sec += 1
id += 1
@@ -442,13 +437,149 @@ class Mage(CommandLineSolver):
return files
@timer
def _export_SIN(self, study, repertory, qlog, name="0"):
files = []
sin_dict = {
"ND": "*",
"S1": "D", "S2": "T", "S3": "T",
"OR": "O", "OC": "B", "OV": "F",
"V1": "V", "V2": "W",
"BO": "A",
"UD": "X",
"PO": "P",
}
hydraulic_structures = study.river.hydraulic_structures.lst
if len(hydraulic_structures) == 0:
return files
if qlog is not None:
qlog.put("Export SIN file")
with mage_file_open(os.path.join(repertory, f"{name}.SIN"), "w+") as f:
files.append(f"{name}.SIN")
for hs in hydraulic_structures:
if not hs.input_reach.is_enable():
continue
f.write(
'* ouvrage au pk ' +
f"{hs.input_kp:>12.1f}" + ' ' +
hs.name + '\n'
)
for bhs in hs.basic_structures:
reach_id = study.river.get_edge_id(hs.input_reach) + 1
param_str = ' '.join(
[
f'{p:>10.3f}'
for p in self._export_SIN_parameters(bhs)
]
)
f.write(
f"{sin_dict[bhs._type]} " +
f"{reach_id} {hs.input_kp:>12.3f} {param_str} " +
f"{bhs.name}\n"
)
return files
def _export_SIN_parameters(self, bhs):
res = [9999.999] * 5
if len(bhs) == 5:
res = self._export_SIN_parameters_5(bhs)
elif len(bhs) == 4:
res = self._export_SIN_parameters_4(bhs)
elif len(bhs) == 3:
res = self._export_SIN_parameters_3(bhs)
return res
def _export_SIN_parameters_5(self, bhs):
# S2, OR, V1, V2, UD
return [
bhs._data[0].value,
bhs._data[1].value,
bhs._data[2].value,
bhs._data[3].value,
bhs._data[4].value,
]
def _export_SIN_parameters_4(self, bhs):
# S3, OC
res = [
bhs._data[0].value,
bhs._data[1].value,
bhs._data[2].value,
bhs._data[3].value,
0.0,
]
if bhs._type == "T": # S3
res = [0.0] + res[:-1]
return res
def _export_SIN_parameters_3(self, bhs):
# S1, BO
if bhs._type == "S1":
res = [
bhs._data[0].value,
bhs._data[1].value,
0.0,
bhs._data[2].value,
9999.99,
]
else:
res = [
bhs._data[0].value,
bhs._data[1].value,
bhs._data[2].value,
0.0,
0.0,
]
return res
@timer
def _export_DEV(self, study, repertory, qlog, name="0"):
files = []
if qlog is not None:
qlog.put("Export DEV file")
with mage_file_open(
os.path.join(
repertory, f"{name}.DEV"
), "w+"
) as f:
reachs = study.river.enable_edges()
id = 1
for reach in reachs:
f.write(f"YD{id:3}\n")
f.write(f"YG{id:3}\n")
id += 1
files.append(f"{name}.DEV")
return files
@timer
def _export_REP(self, study, repertory, files, qlog, name="0"):
if qlog is not None:
qlog.put("Export REP file")
# Write header
with mage_file_open(os.path.join(repertory, f"{name}.REP"), "w+") as f:
with mage_file_open(
os.path.join(
repertory, f"{name}.REP"
), "w+"
) as f:
f.write("confirmation=non\n")
for file in files:
@@ -588,8 +719,12 @@ class Mage8(Mage):
if name in ["command_line_arguments"]:
continue
if name == "mage_compute_reach_volume_balance":
value = "O" if value == "y" else "N"
if name == "compute_reach_volume_balance":
value = "O" if value.lower() == "y" else "N"
if name == "init_internal":
value = ("p" if value.lower() in ["y", "yes", "true"]
else "")
f.write(f"{name} {value}\n")
@@ -683,7 +818,9 @@ class Mage8(Mage):
self._export_bound_cond(study, repertory, qlog, name=name)
files = files + self._export_RUG(study, repertory, qlog, name=name)
files = files + self._export_INI(study, repertory, qlog, name=name)
files = files + self._export_SIN(study, repertory, qlog, name=name)
files = files + self._export_CAS(study, repertory, qlog, name=name)
files = files + self._export_DEV(study, repertory, qlog, name=name)
self._export_REP(study, repertory, files, qlog, name=name)
return True
@@ -813,6 +950,13 @@ class Mage8(Mage):
# Set data for profile RI
reach.set(ri, timestamp, key, d)
if key == "Z":
profile = study.river\
.current_reach()\
.reach.profile(ri)
ptX, ptY = profile.get_water_limits(d)
reach.set(ri, timestamp, "ptX", ptX)
reach.set(ri, timestamp, "ptY", ptY)
endline()
end = newline().size <= 0
@@ -855,7 +999,7 @@ class Mage8(Mage):
logger.debug(f"read_gra: nb_profile = {nb_profile}")
logger.debug(f"read_gra: mage_version = {mage_version}")
if mage_version <= 80:
if mage_version < 80:
msg = (
"Read GRA files: " +
f"Possible incompatible mage version '{mage_version}', " +
@@ -923,60 +1067,71 @@ class Mage8(Mage):
def ip_to_ri(r, i): return i - reach_offset[r]
ts = set()
ind = 0
end = False
newline()
while not end:
n = read_int(1)[0]
timestamp = read_float64(1)[0]
with_bedload = read_int(1)[0]
logger.debug(f"read_gra: timestamp = {timestamp} sec")
ts.add(timestamp)
logger.debug(f"read_gra: Number of cross section: {n}")
logger.debug(f"read_gra: Timestamp: {timestamp}")
logger.debug(f"read_gra: Type of bedload: {with_bedload}")
endline()
for i in range(n):
npts = [1] * n
if with_bedload == 1:
newline()
nsl = read_int(1)[0]
npts = read_int(n)
endline()
sum_npts = sum(npts)
logger.debug(f"read_gra: Number of points: {sum_npts}")
# Get current profile id
newline()
nsl = read_int(sum_npts)
logger.debug(f"read_gra: Number of sedimentary layers: {nsl}")
endline()
newline()
data = read_float64(sum(nsl) * 3)
endline()
ts.add(timestamp)
i_pts = 0
i_data = 0
# Loop on cross section
for i in range(n):
sec_sl = []
reach = ip_to_r(i)
ri = ip_to_ri(reach, i)
if nsl > 1:
logger.warning(
"read_gra: " +
"Multiple sediment layers for one profile " +
"is not implemented yet..."
)
for j in range(nsl):
newline()
nl = read_int(1)[0]
endline()
p_i = ip_to_ri(reach, i)
# Loop on cross section points
for j in range(npts[i]):
sl = []
for k in range(nl):
newline()
data = read_float64(3)
endline()
h = data[0]
d50 = data[1]
sigma = data[2]
# Loop on sediment layers
for k in range(nsl[i_pts]):
h = data[i_data]
d50 = data[i_data + 1]
sigma = data[i_data + 2]
sl.append((h, d50, sigma))
i_data += 3
reach.set(ri, timestamp, "sl", sl)
i_pts += 1
sec_sl.append(sl)
ind += 1
reach.set(p_i, timestamp, "sl", sec_sl)
logger.debug(
f"read_gra: data size = {len(data)} ({i_data} readed)"
)
end = newline().size <= 0
logger.debug(reachs[0].profiles[0]._data)
results.set("timestamps", ts)
results.set("sediment_timestamps", ts)
logger.info(f"read_gra: ... end with {len(ts)} timestamp read")
@timer
+1 -1
View File
@@ -160,7 +160,7 @@ class EditBoundaryConditionWindow(PamhyrWindow):
table_headers=headers,
editable_headers=self._data.header,
delegates={
#"time": self._delegate_time,
# "time": self._delegate_time,
},
data=self._data,
undo=self._undo_stack,
+2 -2
View File
@@ -115,13 +115,13 @@ class ComboBoxDelegate(QItemDelegate):
class TableModel(PamhyrTableModel):
def __init__(self, trad = None, **kwargs):
def __init__(self, trad=None, **kwargs):
self._trad = trad
self._long_types = {}
if self._trad is not None:
self._long_types = self._trad.get_dict("long_types")
super(TableModel, self).__init__(trad = trad, **kwargs)
super(TableModel, self).__init__(trad=trad, **kwargs)
def _setup_lst(self):
self._lst = self._data.boundary_condition
+3 -3
View File
@@ -93,7 +93,7 @@ class AddCommand(QUndoCommand):
self._point = None
def undo(self):
self._profile.delete([self._index])
self._profile.delete_i([self._index])
def redo(self):
if self._point is None:
@@ -119,7 +119,7 @@ class DelCommand(QUndoCommand):
self._profile.insert_point(row, point)
def redo(self):
self._profile.delete(self._rows)
self._profile.delete_i(self._rows)
class SortCommand(QUndoCommand):
@@ -181,7 +181,7 @@ class PasteCommand(QUndoCommand):
def undo(self):
for ind in range(len(self._points)):
self._profile.delete([self._row])
self._profile.delete_i([self._row])
def redo(self):
for point in self._points:
+15 -18
View File
@@ -135,16 +135,13 @@ class ProfileWindow(PamhyrWindow):
self._tablemodel.blockSignals(False)
def index_selected_row(self):
rows = self._tablemodel\
.selectionModel()\
.selectedRows()
table = self.find(QTableView, "tableView")
rows = table.selectionModel()\
.selectedRows()
if len(rows) == 0:
return 0
return self._tablemodel\
.selectionModel()\
.selectedRows()[0]\
.row()
return rows[0].row()
def add(self):
table = self.find(QTableView, "tableView")
@@ -213,24 +210,24 @@ class ProfileWindow(PamhyrWindow):
self.update_plot()
def copy(self):
rows = self._tablemodel\
.selectionModel()\
.selectedRows()
table = []
table.append(["x", "y", "z", "name"])
def _copy(self):
table = self.find(QTableView, "tableView")
rows = table.selectionModel().selectedRows()
data = []
data.append(["x", "y", "z", "name"])
for row in rows:
point = self._profile.point(row.row())
table.append(
data.append(
[
point.x, point.y, point.z, point.name
]
)
self.copyTableIntoClipboard(table)
self.copyTableIntoClipboard(data)
def paste(self):
def _paste(self):
header, data = self.parseClipboardTable()
if len(data) == 0:
@@ -245,10 +242,10 @@ class ProfileWindow(PamhyrWindow):
self._tablemodel.paste(row, header, data)
self.update_plot()
def undo(self):
def _undo(self):
self._tablemodel.undo()
self.update_plot()
def redo(self):
def _redo(self):
self._tablemodel.redo()
self.update_plot()
+8 -4
View File
@@ -455,7 +455,7 @@ class GeometryWindow(PamhyrWindow):
.selectedRows()
table = []
table.append(["name", "kp"])
# table.append(["name", "kp"])
for row in rows:
profile = self._reach.profile(row.row())
@@ -471,13 +471,17 @@ class GeometryWindow(PamhyrWindow):
if len(data) == 0:
return
if len(header) != 0:
header.append("reach")
# if len(header) != 0:
# header.append("reach")
# header.append("status")
for row in data:
row.append(self._reach)
row.append(self._study.river._status)
row = self.index_selected_row()
self._tablemodel.paste(row, header, data)
# self._tablemodel.paste(row, header, data)
self._tablemodel.paste(row, [], data)
self.select_current_profile()
def _undo(self):
@@ -0,0 +1,275 @@
# Table.py -- Pamhyr
# Copyright (C) 2023 INRAE
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# -*- coding: utf-8 -*-
import logging
import traceback
from tools import trace, timer
from PyQt5.QtCore import (
Qt, QVariant, QAbstractTableModel,
QCoreApplication, QModelIndex, pyqtSlot,
QRect,
)
from PyQt5.QtWidgets import (
QDialogButtonBox, QPushButton, QLineEdit,
QFileDialog, QTableView, QAbstractItemView,
QUndoStack, QShortcut, QAction, QItemDelegate,
QComboBox, QMessageBox,
)
from View.Tools.PamhyrTable import PamhyrTableModel
from View.HydraulicStructures.BasicHydraulicStructures.UndoCommand import (
SetNameCommand, SetTypeCommand,
SetEnabledCommand, AddCommand, DelCommand,
SetValueCommand,
)
from Model.HydraulicStructures.Basic.Types import BHS_types
logger = logging.getLogger()
_translate = QCoreApplication.translate
class ComboBoxDelegate(QItemDelegate):
def __init__(self, data=None, trad=None, parent=None):
super(ComboBoxDelegate, self).__init__(parent)
self._data = data
self._trad = trad
self._long_types = {}
if self._trad is not None:
self._long_types = self._trad.get_dict("long_types")
def createEditor(self, parent, option, index):
self.editor = QComboBox(parent)
lst = list(
map(
lambda k: self._long_types[k],
BHS_types.keys()
)
)
self.editor.addItems(lst)
self.editor.setCurrentText(index.data(Qt.DisplayRole))
return self.editor
def setEditorData(self, editor, index):
value = index.data(Qt.DisplayRole)
self.editor.currentTextChanged.connect(self.currentItemChanged)
def setModelData(self, editor, model, index):
text = str(editor.currentText())
model.setData(index, text)
editor.close()
editor.deleteLater()
def updateEditorGeometry(self, editor, option, index):
r = QRect(option.rect)
if self.editor.windowFlags() & Qt.Popup:
if editor.parent() is not None:
r.setTopLeft(self.editor.parent().mapToGlobal(r.topLeft()))
editor.setGeometry(r)
@pyqtSlot()
def currentItemChanged(self):
self.commitData.emit(self.sender())
class TableModel(PamhyrTableModel):
def __init__(self, trad=None, **kwargs):
self._trad = trad
self._long_types = {}
if self._trad is not None:
self._long_types = self._trad.get_dict("long_types")
super(TableModel, self).__init__(trad=trad, **kwargs)
def rowCount(self, parent):
return len(self._lst)
def data(self, index, role):
if role != Qt.ItemDataRole.DisplayRole:
return QVariant()
row = index.row()
column = index.column()
if self._headers[column] == "name":
return self._data.basic_structure(row).name
elif self._headers[column] == "type":
return self._long_types[self._data.basic_structure(row).type]
return QVariant()
def setData(self, index, value, role=Qt.EditRole):
if not index.isValid() or role != Qt.EditRole:
return False
row = index.row()
column = index.column()
try:
if self._headers[column] == "name":
self._undo.push(
SetNameCommand(
self._data, row, value
)
)
elif self._headers[column] == "type":
if self._question_set_type():
key = next(
k for k, v in self._long_types.items()
if v == value
)
self._undo.push(
SetTypeCommand(
self._data, row, BHS_types[key]
)
)
except Exception as e:
logger.error(e)
logger.debug(traceback.format_exc())
self.dataChanged.emit(index, index)
return True
def _question_set_type(self):
question = QMessageBox(self._parent)
question.setWindowTitle(self._trad['msg_type_change_title'])
question.setText(self._trad['msg_type_change_text'])
question.setStandardButtons(QMessageBox.Cancel | QMessageBox.Ok)
question.setIcon(QMessageBox.Question)
res = question.exec()
return res == QMessageBox.Ok
def add(self, row, parent=QModelIndex()):
self.beginInsertRows(parent, row, row - 1)
self._undo.push(
AddCommand(
self._data, row
)
)
self.endInsertRows()
self.layoutChanged.emit()
def delete(self, rows, parent=QModelIndex()):
self.beginRemoveRows(parent, rows[0], rows[-1])
self._undo.push(
DelCommand(
self._data, rows
)
)
self.endRemoveRows()
self.layoutChanged.emit()
def enabled(self, row, enabled, parent=QModelIndex()):
self._undo.push(
SetEnabledCommand(
self._lst, row, enabled
)
)
self.layoutChanged.emit()
def undo(self):
self._undo.undo()
self.layoutChanged.emit()
def redo(self):
self._undo.redo()
self.layoutChanged.emit()
class ParametersTableModel(PamhyrTableModel):
def __init__(self, trad=None, **kwargs):
self._trad = trad
self._long_types = {}
if self._trad is not None:
self._long_types = self._trad.get_dict("long_types")
self._hs_index = None
super(ParametersTableModel, self).__init__(trad=trad, **kwargs)
def rowCount(self, parent):
if self._hs_index is None:
return 0
return len(
self._data.basic_structure(self._hs_index)
)
def data(self, index, role):
if role != Qt.ItemDataRole.DisplayRole:
return QVariant()
if self._hs_index is None:
return QVariant()
row = index.row()
column = index.column()
hs = self._data.basic_structure(self._hs_index)
if self._headers[column] == "name":
return hs.parameters[row].name
elif self._headers[column] == "value":
return str(hs.parameters[row].value)
return QVariant()
def setData(self, index, value, role=Qt.EditRole):
if not index.isValid() or role != Qt.EditRole:
return False
if self._hs_index is None:
return QVariant()
row = index.row()
column = index.column()
try:
if self._headers[column] == "value":
self._undo.push(
SetValueCommand(
self._data.basic_structure(self._hs_index),
row, value
)
)
except Exception as e:
logger.error(e)
logger.debug(traceback.format_exc())
self.dataChanged.emit(index, index)
return True
def update_hs_index(self, index):
self._hs_index = index
self.layoutChanged.emit()
@@ -0,0 +1,85 @@
# translate.py -- Pamhyr
# Copyright (C) 2023 INRAE
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# -*- coding: utf-8 -*-
from PyQt5.QtCore import QCoreApplication
from View.Tools.PamhyrTranslate import PamhyrTranslate
_translate = QCoreApplication.translate
class BasicHydraulicStructuresTranslate(PamhyrTranslate):
def __init__(self):
super(BasicHydraulicStructuresTranslate, self).__init__()
self._dict['msg_type_change_title'] = _translate(
"BasicHydraulicStructures",
"Change hydraulic structure type"
)
self._dict['msg_type_change_text'] = _translate(
"BasicHydraulicStructures",
"Do you want to change the hydraulic structure type and reset \
hydraulic structure values?"
)
self._sub_dict["long_types"] = {
"ND": _translate(
"BasicHydraulicStructures", "Not defined"
),
"S1": _translate(
"BasicHydraulicStructures", "Seuil déversoir"
),
"S2": _translate(
"BasicHydraulicStructures", "Seuil trapezoidal"
),
"S3": _translate(
"BasicHydraulicStructures", "Seuil triangulaire"
),
"OR": _translate(
"BasicHydraulicStructures", "Orifice rectangulaire"
),
"OC": _translate(
"BasicHydraulicStructures", "Orifice circulaire"
),
"OV": _translate(
"BasicHydraulicStructures", "Orifice voute"
),
"V1": _translate(
"BasicHydraulicStructures", "Vanne rectangulaire"
),
"V2": _translate(
"BasicHydraulicStructures", "Vanne rectangulaire simplifiée"
),
"BO": _translate(
"BasicHydraulicStructures", "Perte de charge à la Borda"
),
"UD": _translate(
"BasicHydraulicStructures", "User defined"
),
}
self._sub_dict["table_headers"] = {
"name": _translate("BasicHydraulicStructures", "Name"),
"type": _translate("BasicHydraulicStructures", "Type"),
}
self._sub_dict["table_headers_parameters"] = {
"name": _translate("BasicHydraulicStructures", "Name"),
"value": _translate("BasicHydraulicStructures", "Value"),
}
@@ -0,0 +1,153 @@
# UndoCommand.py -- Pamhyr
# Copyright (C) 2023 INRAE
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# -*- coding: utf-8 -*-
from copy import deepcopy
from tools import trace, timer
from PyQt5.QtWidgets import (
QMessageBox, QUndoCommand, QUndoStack,
)
class SetNameCommand(QUndoCommand):
def __init__(self, hs, index, new_value):
QUndoCommand.__init__(self)
self._hs = hs
self._index = index
self._old = self._hs.basic_structure(self._index).name
self._new = str(new_value)
def undo(self):
self._hs.basic_structure(self._index).name = self._old
def redo(self):
self._hs.basic_structure(self._index).name = self._new
class SetTypeCommand(QUndoCommand):
def __init__(self, hs, index, new_type):
QUndoCommand.__init__(self)
self._hs = hs
self._index = index
self._type = new_type
self._old = self._hs.basic_structure(self._index)
self._new = self._hs.basic_structure(self._index)\
.convert(self._type)
def undo(self):
self._hs.delete_i([self._index])
self._hs.insert(self._index, self._old)
def redo(self):
self._hs.delete_i([self._index])
self._hs.insert(self._index, self._new)
class SetEnabledCommand(QUndoCommand):
def __init__(self, hs, index, enabled):
QUndoCommand.__init__(self)
self._hs = hs
self._index = index
self._old = not enabled
self._new = enabled
def undo(self):
self._hs.basic_structure(self._index).enabled = self._old
def redo(self):
self._hs.basic_structure(self._index).enabled = self._new
class AddCommand(QUndoCommand):
def __init__(self, hs, index):
QUndoCommand.__init__(self)
self._hs = hs
self._index = index
self._new = None
def undo(self):
self._hs.delete_i([self._index])
def redo(self):
if self._new is None:
self._new = self._hs.add(self._index)
else:
self._hs.insert(self._index, self._new)
class DelCommand(QUndoCommand):
def __init__(self, hs, rows):
QUndoCommand.__init__(self)
self._hs = hs
self._rows = rows
self._bhs = []
for row in rows:
self._bhs.append((row, self._hs.basic_structure(row)))
def undo(self):
for row, el in self._bhs:
self._hs.insert(row, el)
def redo(self):
self._hs.delete_i(self._rows)
class PasteCommand(QUndoCommand):
def __init__(self, hs, row, h_s):
QUndoCommand.__init__(self)
self._hs = hs
self._row = row
self._bhs = deepcopy(h_s)
self._bhs.reverse()
def undo(self):
self._hs.delete_i(range(self._row, self._row + len(self._bhs)))
def redo(self):
for r in self._bhs:
self._hs.insert(self._row, r)
####################################
# Basic hydraulic structure values #
####################################
class SetValueCommand(QUndoCommand):
def __init__(self, bhs, index, value):
QUndoCommand.__init__(self)
self._bhs = bhs
self._index = index
self._old = self._bhs.parameters[self._index].value
self._new = self._bhs.parameters[self._index].type(value)
def undo(self):
self._bhs.parameters[self._index].value = self._old
def redo(self):
self._bhs.parameters[self._index].value = self._new
@@ -0,0 +1,265 @@
# Window.py -- Pamhyr
# Copyright (C) 2023 INRAE
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# -*- coding: utf-8 -*-
import logging
from tools import timer, trace
from View.Tools.PamhyrWindow import PamhyrWindow
from PyQt5 import QtCore
from PyQt5.QtCore import (
Qt, QVariant, QAbstractTableModel, QCoreApplication,
pyqtSlot, pyqtSignal, QItemSelectionModel,
)
from PyQt5.QtWidgets import (
QDialogButtonBox, QPushButton, QLineEdit,
QFileDialog, QTableView, QAbstractItemView,
QUndoStack, QShortcut, QAction, QItemDelegate,
QHeaderView, QDoubleSpinBox, QVBoxLayout, QCheckBox
)
from View.Tools.Plot.PamhyrCanvas import MplCanvas
from View.Tools.Plot.PamhyrToolbar import PamhyrPlotToolbar
from View.HydraulicStructures.PlotAC import PlotAC
from View.HydraulicStructures.BasicHydraulicStructures.Table import (
ComboBoxDelegate, TableModel, ParametersTableModel,
)
from View.Network.GraphWidget import GraphWidget
from View.HydraulicStructures.BasicHydraulicStructures.Translate import (
BasicHydraulicStructuresTranslate
)
_translate = QCoreApplication.translate
logger = logging.getLogger()
class BasicHydraulicStructuresWindow(PamhyrWindow):
_pamhyr_ui = "BasicHydraulicStructures"
_pamhyr_name = "Basic Hydraulic Structures"
def __init__(self, data=None, study=None, config=None, parent=None):
name = self._pamhyr_name + " - " + study.name
super(BasicHydraulicStructuresWindow, self).__init__(
title=name,
study=study,
config=config,
trad=BasicHydraulicStructuresTranslate(),
parent=parent
)
self._hash_data.append(data)
self._hs = data
self.setup_table()
self.setup_checkbox()
self.setup_plot()
self.setup_connections()
self.update()
def setup_table(self):
self.setup_table_bhs()
self.setup_table_bhs_parameters()
def setup_table_bhs(self):
self._table = None
self._delegate_type = ComboBoxDelegate(
trad=self._trad,
parent=self
)
table = self.find(QTableView, f"tableView")
self._table = TableModel(
table_view=table,
table_headers=self._trad.get_dict("table_headers"),
editable_headers=["name", "type"],
delegates={
"type": self._delegate_type,
},
trad=self._trad,
data=self._hs,
undo=self._undo_stack,
parent=self,
)
selectionModel = table.selectionModel()
index = table.model().index(0, 0)
selectionModel.select(
index,
QItemSelectionModel.Rows |
QItemSelectionModel.ClearAndSelect |
QItemSelectionModel.Select
)
table.scrollTo(index)
def setup_table_bhs_parameters(self):
self._table_parameters = None
table = self.find(QTableView, f"tableView_2")
self._table_parameters = ParametersTableModel(
table_view=table,
table_headers=self._trad.get_dict("table_headers_parameters"),
editable_headers=["value"],
delegates={},
trad=self._trad,
data=self._hs,
undo=self._undo_stack,
parent=self,
)
def setup_checkbox(self):
self._checkbox = self.find(QCheckBox, f"checkBox")
self._set_checkbox_state()
def setup_plot(self):
self.canvas = MplCanvas(width=5, height=4, dpi=100)
self.canvas.setObjectName("canvas")
self.toolbar = PamhyrPlotToolbar(
self.canvas, self
)
self.plot_layout = self.find(QVBoxLayout, "verticalLayout")
self.plot_layout.addWidget(self.toolbar)
self.plot_layout.addWidget(self.canvas)
reach = self._hs.input_reach
profile_kp = self._hs.input_kp
if profile_kp is not None:
profiles = reach.reach.get_profiles_from_kp(float(profile_kp))
else:
profiles = None
if profiles is not None:
profile = profiles[0]
else:
profile = None
self.plot_ac = PlotAC(
canvas=self.canvas,
river=self._study.river,
reach=self._hs.input_reach,
profile=profile,
toolbar=self.toolbar
)
self.plot_ac.draw()
def setup_connections(self):
self.find(QAction, "action_add").triggered.connect(self.add)
self.find(QAction, "action_delete").triggered.connect(self.delete)
self._checkbox.clicked.connect(self._set_basic_structure_state)
table = self.find(QTableView, "tableView")
table.selectionModel()\
.selectionChanged\
.connect(self.update)
self._table.dataChanged.connect(self.update)
self._table.layoutChanged.connect(self.update)
def index_selected(self):
table = self.find(QTableView, "tableView")
r = table.selectionModel().selectedRows()
if len(r) > 0:
return r[0]
else:
return None
def index_selected_row(self):
table = self.find(QTableView, "tableView")
r = table.selectionModel().selectedRows()
if len(r) > 0:
return r[0].row()
else:
return None
def index_selected_rows(self):
table = self.find(QTableView, "tableView")
return list(
# Delete duplicate
set(
map(
lambda i: i.row(),
table.selectedIndexes()
)
)
)
def add(self):
rows = self.index_selected_rows()
if len(self._hs) == 0 or len(rows) == 0:
self._table.add(0)
else:
self._table.add(rows[0])
def delete(self):
rows = self.index_selected_rows()
if len(rows) == 0:
return
self._table.delete(rows)
def _copy(self):
logger.info("TODO: copy")
def _paste(self):
logger.info("TODO: paste")
def _undo(self):
self._table.undo()
def _redo(self):
self._table.redo()
def _set_checkbox_state(self):
row = self.index_selected_row()
if row is None:
self._checkbox.setEnabled(False)
self._checkbox.setChecked(True)
else:
self._checkbox.setEnabled(True)
self._checkbox.setChecked(self._hs.basic_structure(row).enabled)
def _set_basic_structure_state(self):
row = self.index_selected_row()
if row is not None:
self._table.enabled(
row,
self._checkbox.isChecked()
)
def update(self):
self._set_checkbox_state()
self._update_parameters_table()
def _update_parameters_table(self):
row = self.index_selected_row()
self._table_parameters.update_hs_index(row)
+133
View File
@@ -0,0 +1,133 @@
# PlotAC.py -- Pamhyr
# Copyright (C) 2023 INRAE
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# -*- coding: utf-8 -*-
from tools import timer
from View.Tools.PamhyrPlot import PamhyrPlot
from matplotlib import pyplot as plt
from PyQt5.QtCore import (
QCoreApplication
)
_translate = QCoreApplication.translate
class PlotAC(PamhyrPlot):
def __init__(self, canvas=None, trad=None, toolbar=None,
river=None, reach=None, profile=None,
parent=None):
super(PlotAC, self).__init__(
canvas=canvas,
trad=trad,
data=river,
toolbar=toolbar,
parent=parent
)
self._current_reach = reach
self._current_profile = profile
@property
def river(self):
return self.data
@river.setter
def river(self, river):
self.data = river
@timer
def draw(self, highlight=None):
self.canvas.axes.cla()
self.canvas.axes.grid(color='grey', linestyle='--', linewidth=0.5)
if self.data is None:
self.line_kp = None
return
if self._current_reach is None:
self.line_kp = None
return
reach = self._current_reach
self.canvas.axes.set_xlabel(
_translate("MainWindow_reach", "X (m)"),
color='black', fontsize=11
)
self.canvas.axes.set_ylabel(
_translate("MainWindow_reach", "Elevation (m)"),
color='black', fontsize=11
)
if self._current_profile is None:
self.line_kp = None
else:
profile = self._current_profile
x = profile.get_station()
z = profile.z()
self.line_kp, = self.canvas.axes.plot(
x, z,
linestyle="solid",
lw=1.8,
color='grey',
)
self.canvas.axes.relim()
self.canvas.axes.autoscale_view()
self.canvas.figure.tight_layout()
self.canvas.figure.canvas.draw_idle()
if self.toolbar is not None:
self.toolbar.update()
def set_reach(self, reach):
self._current_reach = reach
self.update()
def set_profile(self, profile):
self._current_profile = profile
self.update()
def update(self):
if self.line_kp is None:
self.draw()
return
if self._current_reach is None or self._current_profile is None:
self.clear()
return
profile = self._current_profile
x = profile.get_station()
z = profile.z()
self.line_kp.set_data(x, z)
self.canvas.axes.relim()
self.canvas.axes.autoscale_view()
self.canvas.figure.tight_layout()
self.canvas.figure.canvas.draw_idle()
def clear(self):
if self.line_kp is not None:
self.line_kp.set_data([], [])
self.canvas.figure.tight_layout()
self.canvas.figure.canvas.draw_idle()
+161
View File
@@ -0,0 +1,161 @@
# PlotKPC.py -- Pamhyr
# Copyright (C) 2023 INRAE
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# -*- coding: utf-8 -*-
from tools import timer
from View.Tools.PamhyrPlot import PamhyrPlot
from PyQt5.QtCore import (
QCoreApplication
)
from matplotlib.collections import LineCollection
_translate = QCoreApplication.translate
class PlotKPC(PamhyrPlot):
def __init__(self, canvas=None, trad=None, toolbar=None,
river=None, reach=None, profile=None,
parent=None):
super(PlotKPC, self).__init__(
canvas=canvas,
trad=trad,
data=river,
toolbar=toolbar,
parent=parent
)
self._current_reach = reach
self._current_profile = profile
@property
def river(self):
return self.data
@river.setter
def river(self, river):
self.data = river
@timer
def draw(self, highlight=None):
self.canvas.axes.cla()
self.canvas.axes.grid(color='grey', linestyle='--', linewidth=0.5)
if self.data is None:
self.profile = None
self.line_kp_zmin_zmax = None
self.line_kp_zmin = None
return
if self._current_reach is None:
self.profile = None
self.line_kp_zmin_zmax = None
self.line_kp_zmin = None
return
reach = self._current_reach
self.canvas.axes.set_ylabel(
_translate("MainWindow_reach", "Elevation (m)"),
color='black', fontsize=11
)
self.canvas.axes.set_xlabel(
_translate("MainWindow_reach", "KP (m)"),
color='black', fontsize=11
)
kp = reach.reach.get_kp()
z_min = reach.reach.get_z_min()
z_max = reach.reach.get_z_max()
self.line_kp_zmin, = self.canvas.axes.plot(
kp, z_min,
color='grey', lw=1.
)
if len(kp) != 0:
self.line_kp_zmin_zmax = self.canvas.axes.vlines(
x=kp,
ymin=z_min, ymax=z_max,
color='b',
lw=1.
)
if self._current_profile is None:
self.profile = None
else:
self.profile, = self.canvas.axes.plot(
[self._current_profile.kp, self._current_profile.kp],
[self._current_profile.z_min(), self._current_profile.z_max()],
color='red', lw=1.
)
self.canvas.axes.relim()
self.canvas.figure.tight_layout()
self.canvas.figure.canvas.draw_idle()
if self.toolbar is not None:
self.toolbar.update()
def set_reach(self, reach):
self._current_reach = reach
self._current_profile = None
self.update()
def set_profile(self, profile):
self._current_profile = profile
self.update_profil()
def update(self):
self.draw()
def update_profil(self):
reach = self._current_reach
kp = reach.reach.get_kp()
z_min = reach.reach.get_z_min()
z_max = reach.reach.get_z_max()
if self.profile is None:
self.draw()
else:
self.profile.set_data(
[self._current_profile.kp, self._current_profile.kp],
[self._current_profile.z_min(), self._current_profile.z_max()],
)
self.canvas.axes.relim()
self.canvas.axes.autoscale_view()
self.canvas.figure.canvas.draw_idle()
def clear(self):
if self.profile is not None:
self.profile.set_data([], [])
if self.line_kp_zmin_zmax is not None:
self.line_kp_zmin_zmax.remove()
self.line_kp_zmin_zmax = None
if self.line_kp_zmin is not None:
self.line_kp_zmin.set_data([], [])
self.canvas.figure.canvas.draw_idle()
def clear_profile(self):
if self.profile is not None:
self.profile.set_data([], [])
self.canvas.figure.canvas.draw_idle()
+215
View File
@@ -0,0 +1,215 @@
# Table.py -- Pamhyr
# Copyright (C) 2023 INRAE
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# -*- coding: utf-8 -*-
import logging
import traceback
from tools import trace, timer
from PyQt5.QtCore import (
Qt, QVariant, QAbstractTableModel,
QCoreApplication, QModelIndex, pyqtSlot,
QRect,
)
from PyQt5.QtWidgets import (
QDialogButtonBox, QPushButton, QLineEdit,
QFileDialog, QTableView, QAbstractItemView,
QUndoStack, QShortcut, QAction, QItemDelegate,
QComboBox,
)
from View.Tools.PamhyrTable import PamhyrTableModel
from View.HydraulicStructures.UndoCommand import (
SetNameCommand, SetReachCommand, SetKpCommand,
SetEnabledCommand, AddCommand, DelCommand,
)
logger = logging.getLogger()
_translate = QCoreApplication.translate
class ComboBoxDelegate(QItemDelegate):
def __init__(self, data=None, trad=None, parent=None, mode="reaches"):
super(ComboBoxDelegate, self).__init__(parent)
self._data = data
self._trad = trad
self._mode = mode
def createEditor(self, parent, option, index):
self.editor = QComboBox(parent)
val = []
if self._mode == "kp":
reach = self._data.hydraulic_structures\
.get(index.row())\
.input_reach
if reach is not None:
val = list(
map(
lambda kp: str(kp), reach.reach.get_kp()
)
)
else:
val = list(
map(
lambda n: n.name, self._data.edges()
)
)
self.editor.addItems(
[_translate("Hydraulic structure", "Not associated")] +
val
)
self.editor.setCurrentText(str(index.data(Qt.DisplayRole)))
return self.editor
def setEditorData(self, editor, index):
value = index.data(Qt.DisplayRole)
self.editor.currentTextChanged.connect(self.currentItemChanged)
def setModelData(self, editor, model, index):
text = str(editor.currentText())
model.setData(index, text)
editor.close()
editor.deleteLater()
def updateEditorGeometry(self, editor, option, index):
r = QRect(option.rect)
if self.editor.windowFlags() & Qt.Popup:
if editor.parent() is not None:
r.setTopLeft(self.editor.parent().mapToGlobal(r.topLeft()))
editor.setGeometry(r)
@pyqtSlot()
def currentItemChanged(self):
self.commitData.emit(self.sender())
class TableModel(PamhyrTableModel):
def _setup_lst(self):
self._lst = self._data._hydraulic_structures
def rowCount(self, parent):
return len(self._lst)
def data(self, index, role):
if role != Qt.ItemDataRole.DisplayRole:
return QVariant()
row = index.row()
column = index.column()
if self._headers[column] == "name":
return self._lst.get(row).name
elif self._headers[column] == "reach":
n = self._lst.get(row).input_reach
if n is None:
return _translate("Hydraulic structure", "Not associated")
return n.name
elif self._headers[column] == "kp":
n = self._lst.get(row).input_kp
if n is None:
return _translate("Hydraulic structure", "Not associated")
return n
return QVariant()
def setData(self, index, value, role=Qt.EditRole):
if not index.isValid() or role != Qt.EditRole:
return False
row = index.row()
column = index.column()
na = _translate("Hydraulic structure", "Not associated")
try:
if self._headers[column] == "name":
self._undo.push(
SetNameCommand(
self._lst, row, value
)
)
elif self._headers[column] == "reach":
if value == na:
value = None
self._undo.push(
SetReachCommand(
self._lst, row, self._data.edge(value)
)
)
elif self._headers[column] == "kp":
if value == na:
value = None
self._undo.push(
SetKpCommand(
self._lst, row, value
)
)
except Exception as e:
logger.info(e)
logger.debug(traceback.format_exc())
self.dataChanged.emit(index, index)
return True
def add(self, row, parent=QModelIndex()):
self.beginInsertRows(parent, row, row - 1)
self._undo.push(
AddCommand(
self._lst, row
)
)
self.endInsertRows()
self.layoutChanged.emit()
def delete(self, rows, parent=QModelIndex()):
self.beginRemoveRows(parent, rows[0], rows[-1])
self._undo.push(
DelCommand(
self._lst, rows
)
)
self.endRemoveRows()
self.layoutChanged.emit()
def enabled(self, row, enabled, parent=QModelIndex()):
self._undo.push(
SetEnabledCommand(
self._lst, row, enabled
)
)
self.layoutChanged.emit()
def undo(self):
self._undo.undo()
self.layoutChanged.emit()
def redo(self):
self._undo.redo()
self.layoutChanged.emit()
+34
View File
@@ -0,0 +1,34 @@
# translate.py -- Pamhyr
# Copyright (C) 2023 INRAE
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# -*- coding: utf-8 -*-
from PyQt5.QtCore import QCoreApplication
from View.Tools.PamhyrTranslate import PamhyrTranslate
_translate = QCoreApplication.translate
class HydraulicStructuresTranslate(PamhyrTranslate):
def __init__(self):
super(HydraulicStructuresTranslate, self).__init__()
self._sub_dict["table_headers"] = {
"name": _translate("HydraulicStructures", "Name"),
"reach": _translate("HydraulicStructures", "Reach"),
"kp": _translate("HydraulicStructures", "Kp"),
}
+159
View File
@@ -0,0 +1,159 @@
# UndoCommand.py -- Pamhyr
# Copyright (C) 2023 INRAE
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# -*- coding: utf-8 -*-
import logging
from copy import deepcopy
from tools import trace, timer
from PyQt5.QtWidgets import (
QMessageBox, QUndoCommand, QUndoStack,
)
logger = logging.getLogger()
class SetNameCommand(QUndoCommand):
def __init__(self, h_s_lst, index, new_value):
QUndoCommand.__init__(self)
self._h_s_lst = h_s_lst
self._index = index
self._old = self._h_s_lst.get(self._index).name
self._new = str(new_value)
def undo(self):
self._h_s_lst.get(self._index).name = self._old
def redo(self):
self._h_s_lst.get(self._index).name = self._new
class SetReachCommand(QUndoCommand):
def __init__(self, h_s_lst, index, reach):
QUndoCommand.__init__(self)
self._h_s_lst = h_s_lst
self._index = index
self._old = self._h_s_lst.get(self._index).input_reach
self._new = reach
self._old_kp = self._h_s_lst.get(self._index).input_kp
self._new_kp = None
def undo(self):
i = self._h_s_lst.get(self._index)
i.input_reach = self._old
i.input_kp = self._old_kp
def redo(self):
i = self._h_s_lst.get(self._index)
i.input_reach = self._new
i.input_kp = self._new_kp
class SetKpCommand(QUndoCommand):
def __init__(self, h_s_lst, index, kp):
QUndoCommand.__init__(self)
self._h_s_lst = h_s_lst
self._index = index
self._old = self._h_s_lst.get(self._index).input_kp
self._new = kp
def undo(self):
self._h_s_lst.get(self._index).input_kp = self._old
def redo(self):
self._h_s_lst.get(self._index).input_kp = self._new
class SetEnabledCommand(QUndoCommand):
def __init__(self, h_s_lst, index, enabled):
QUndoCommand.__init__(self)
self._h_s_lst = h_s_lst
self._index = index
self._old = not enabled
self._new = enabled
def undo(self):
self._h_s_lst.get(self._index).enabled = self._old
def redo(self):
self._h_s_lst.get(self._index).enabled = self._new
class AddCommand(QUndoCommand):
def __init__(self, h_s_lst, index):
QUndoCommand.__init__(self)
self._h_s_lst = h_s_lst
self._index = index
self._new = None
def undo(self):
self._h_s_lst.delete_i([self._index])
def redo(self):
if self._new is None:
self._new = self._h_s_lst.new(self._h_s_lst, self._index)
else:
self._h_s_lst.insert(self._index, self._new)
class DelCommand(QUndoCommand):
def __init__(self, h_s_lst, rows):
QUndoCommand.__init__(self)
self._h_s_lst = h_s_lst
self._rows = rows
self._h_s = []
for row in rows:
self._h_s.append((row, self._h_s_lst.get(row)))
self._h_s.sort()
def undo(self):
for row, el in self._h_s:
self._h_s_lst.insert(row, el)
def redo(self):
self._h_s_lst.delete_i(self._rows)
class PasteCommand(QUndoCommand):
def __init__(self, h_s_lst, row, h_s):
QUndoCommand.__init__(self)
self._h_s_lst = h_s_lst
self._row = row
self._h_s = deepcopy(h_s)
self._h_s.reverse()
def undo(self):
self._h_s_lst.delete_i(
self._tab,
range(self._row, self._row + len(self._h_s))
)
def redo(self):
for r in self._h_s:
self._h_s_lst.insert(self._row, r)
+313
View File
@@ -0,0 +1,313 @@
# Window.py -- Pamhyr
# Copyright (C) 2023 INRAE
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# -*- coding: utf-8 -*-
import logging
from tools import timer, trace
from View.Tools.PamhyrWindow import PamhyrWindow
from PyQt5 import QtCore
from PyQt5.QtCore import (
Qt, QVariant, QAbstractTableModel, QCoreApplication,
pyqtSlot, pyqtSignal, QItemSelectionModel,
)
from PyQt5.QtWidgets import (
QDialogButtonBox, QPushButton, QLineEdit,
QFileDialog, QTableView, QAbstractItemView,
QUndoStack, QShortcut, QAction, QItemDelegate,
QHeaderView, QDoubleSpinBox, QVBoxLayout, QCheckBox
)
from View.Tools.Plot.PamhyrCanvas import MplCanvas
from View.Tools.Plot.PamhyrToolbar import PamhyrPlotToolbar
from View.HydraulicStructures.PlotAC import PlotAC
from View.HydraulicStructures.PlotKPC import PlotKPC
from View.HydraulicStructures.Table import (
TableModel, ComboBoxDelegate
)
from View.Network.GraphWidget import GraphWidget
from View.HydraulicStructures.Translate import HydraulicStructuresTranslate
from View.HydraulicStructures.BasicHydraulicStructures.Window import (
BasicHydraulicStructuresWindow
)
_translate = QCoreApplication.translate
logger = logging.getLogger()
class HydraulicStructuresWindow(PamhyrWindow):
_pamhyr_ui = "HydraulicStructures"
_pamhyr_name = "Hydraulic Structures"
def __init__(self, study=None, config=None, parent=None):
name = self._pamhyr_name + " - " + study.name
super(HydraulicStructuresWindow, self).__init__(
title=name,
study=study,
config=config,
trad=HydraulicStructuresTranslate(),
parent=parent
)
self._hs_lst = self._study.river._hydraulic_structures
self.setup_table()
self.setup_checkbox()
self.setup_plots()
self.setup_connections()
self.update()
def setup_table(self):
self._table = None
self._delegate_reach = ComboBoxDelegate(
trad=self._trad,
data=self._study.river,
parent=self,
mode="reaches"
)
self._delegate_kp = ComboBoxDelegate(
trad=self._trad,
data=self._study.river,
parent=self,
mode="kp"
)
table = self.find(QTableView, f"tableView")
self._table = TableModel(
table_view=table,
table_headers=self._trad.get_dict("table_headers"),
editable_headers=["name", "reach", "kp"],
delegates={
"reach": self._delegate_reach,
"kp": self._delegate_kp,
},
trad=self._trad,
data=self._study.river,
undo=self._undo_stack,
)
selectionModel = table.selectionModel()
index = table.model().index(0, 0)
selectionModel.select(
index,
QItemSelectionModel.Rows |
QItemSelectionModel.ClearAndSelect |
QItemSelectionModel.Select
)
table.scrollTo(index)
def setup_checkbox(self):
self._checkbox = self.find(QCheckBox, f"checkBox")
self._set_checkbox_state()
def setup_plots(self):
self.canvas = MplCanvas(width=5, height=4, dpi=100)
self.canvas.setObjectName("canvas")
self.toolbar = PamhyrPlotToolbar(
self.canvas, self
)
self.plot_layout = self.find(QVBoxLayout, "verticalLayout")
self.plot_layout.addWidget(self.toolbar)
self.plot_layout.addWidget(self.canvas)
self.plot_kpc = PlotKPC(
canvas=self.canvas,
river=self._study.river,
reach=None,
profile=None,
toolbar=self.toolbar
)
self.plot_kpc.draw()
self.canvas_2 = MplCanvas(width=5, height=4, dpi=100)
self.canvas_2.setObjectName("canvas_2")
self.toolbar_2 = PamhyrPlotToolbar(
self.canvas_2, self
)
self.plot_layout_2 = self.find(QVBoxLayout, "verticalLayout_2")
self.plot_layout_2.addWidget(self.toolbar_2)
self.plot_layout_2.addWidget(self.canvas_2)
self.plot_ac = PlotAC(
canvas=self.canvas_2,
river=self._study.river,
reach=None,
profile=None,
toolbar=self.toolbar_2
)
self.plot_ac.draw()
def setup_connections(self):
self.find(QAction, "action_add").triggered.connect(self.add)
self.find(QAction, "action_delete").triggered.connect(self.delete)
self.find(QAction, "action_edit").triggered.connect(self.edit)
self._checkbox.clicked.connect(self._set_structure_state)
table = self.find(QTableView, "tableView")
table.selectionModel()\
.selectionChanged\
.connect(self.update)
self._table.dataChanged.connect(self.update)
self._table.layoutChanged.connect(self.update)
def index_selected(self):
table = self.find(QTableView, "tableView")
r = table.selectionModel().selectedRows()
if len(r) > 0:
return r[0]
else:
return None
def index_selected_row(self):
table = self.find(QTableView, "tableView")
r = table.selectionModel().selectedRows()
if len(r) > 0:
return r[0].row()
else:
return None
def index_selected_rows(self):
table = self.find(QTableView, "tableView")
return list(
# Delete duplicate
set(
map(
lambda i: i.row(),
table.selectedIndexes()
)
)
)
def add(self):
rows = self.index_selected_rows()
if len(self._hs_lst) == 0 or len(rows) == 0:
self._table.add(0)
else:
self._table.add(rows[0])
def delete(self):
rows = self.index_selected_rows()
if len(rows) == 0:
return
self._table.delete(rows)
def _copy(self):
logger.info("TODO: copy")
def _paste(self):
logger.info("TODO: paste")
def _undo(self):
self._table.undo()
def _redo(self):
self._table.redo()
def edit(self):
rows = self.index_selected_rows()
for row in rows:
data = self._hs_lst.get(row)
print(row)
print(data)
if self.sub_window_exists(
BasicHydraulicStructuresWindow,
data=[self._study, None, data]
):
continue
win = BasicHydraulicStructuresWindow(
data=data,
study=self._study,
parent=self
)
win.show()
def _set_checkbox_state(self):
row = self.index_selected_row()
if row is None:
self._checkbox.setEnabled(False)
self._checkbox.setChecked(True)
else:
self._checkbox.setEnabled(True)
self._checkbox.setChecked(self._hs_lst.get(row).enabled)
def _set_structure_state(self):
row = self.index_selected_row()
if row is not None:
self._table.enabled(
row,
self._checkbox.isChecked()
)
def update(self):
self._set_checkbox_state()
self._update_clear_plot()
def _update_clear_plot(self):
rows = self.index_selected_rows()
if len(rows) == 0 or len(self._hs_lst) == 0:
self._update_clear_all()
return
reach = self._hs_lst.get(rows[0]).input_reach
if reach is not None:
self.plot_kpc.set_reach(reach)
self.plot_ac.set_reach(reach)
profile_kp = self._hs_lst.get(rows[0]).input_kp
if profile_kp is not None:
profiles = reach.reach\
.get_profiles_from_kp(
float(profile_kp)
)
if profiles is not None:
profile = profiles[0]
self.plot_kpc.set_profile(profile)
self.plot_ac.set_profile(profile)
else:
self._update_clear_profile()
else:
self._update_clear_profile()
else:
self._update_clear_all()
def _update_clear_all(self):
self.plot_kpc.clear()
self.plot_ac.clear()
def _update_clear_profile(self):
self.plot_ac.clear()
self.plot_kpc.clear_profile()
+1 -1
View File
@@ -101,7 +101,7 @@ class EditLateralContributionWindow(PamhyrWindow):
table_headers=headers,
editable_headers=self._data.header,
delegates={
#"time": self._delegate_time,
# "time": self._delegate_time,
},
data=self._data,
undo=self._undo_stack,
+1 -1
View File
@@ -107,7 +107,7 @@ class LateralContributionWindow(PamhyrWindow):
self._table[t] = TableModel(
table_view=table,
table_headers=self._trad.get_dict("table_headers"),
editable_headers=True,
editable_headers=self._trad.get_dict("table_headers"),
delegates={
"type": self._delegate_type,
"edge": self._delegate_edge,
+53 -9
View File
@@ -28,12 +28,12 @@ from PyQt5.QtGui import (
)
from PyQt5.QtCore import (
QTranslator, QEvent, QUrl,
Qt, QTranslator, QEvent, QUrl,
)
from PyQt5.QtWidgets import (
QMainWindow, QApplication, QAction,
QFileDialog, QShortcut, QMenu, QToolBar,
QMessageBox,
QMessageBox, QProgressDialog,
)
from PyQt5.uic import loadUi
@@ -48,6 +48,7 @@ from View.Network.Window import NetworkWindow
from View.Geometry.Window import GeometryWindow
from View.BoundaryCondition.Window import BoundaryConditionWindow
from View.Reservoir.Window import ReservoirWindow
from View.HydraulicStructures.Window import HydraulicStructuresWindow
from View.LateralContribution.Window import LateralContributionWindow
from View.InitialConditions.Window import InitialConditionsWindow
from View.Stricklers.Window import StricklersWindow
@@ -102,7 +103,8 @@ define_model_action = [
"action_menu_boundary_conditions", "action_menu_initial_conditions",
"action_menu_edit_friction", "action_menu_edit_lateral_contribution",
"action_menu_run_solver", "action_menu_sediment_layers",
"action_menu_edit_reach_sediment_layers", "action_menu_edit_reservoirs"
"action_menu_edit_reach_sediment_layers", "action_menu_edit_reservoirs",
"action_menu_edit_hydraulic_structures"
]
action = (
@@ -193,6 +195,8 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
"action_menu_edit_geometry": self.open_geometry,
"action_menu_boundary_conditions": self.open_boundary_cond,
"action_menu_edit_reservoirs": self.open_reservoir,
"action_menu_edit_hydraulic_structures":
self.open_hydraulic_structures,
"action_menu_initial_conditions": self.open_initial_conditions,
"action_menu_edit_friction": self.open_frictions,
"action_menu_edit_lateral_contribution": self.open_lateral_contrib,
@@ -279,8 +283,7 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
Nothing
"""
self.update_enable_action()
# Maximise window
#self.showMaximized()
# self.showMaximized()
def set_debug_lvl(self, debug=True):
if debug:
@@ -403,8 +406,20 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
if self._study.is_saved:
return
sql_request_count = self._study.sql_save_request_count()
progress = QProgressDialog(
"Saving...", None,
0, sql_request_count,
parent=self
)
progress.setWindowModality(Qt.WindowModal)
progress.setValue(0)
logger.info("Save...")
self._study.save()
self._study.save(
progress=lambda: progress.setValue(progress.value() + 1)
)
logger.info("Done")
def save_as_study(self):
"""Save current study as new file
@@ -420,12 +435,25 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
"", "Pamhyr(*.pamhyr)"
)
if file_name[-4:] == ".pamhyr":
if file_name.rsplit(".", 1)[-1] == ".pamhyr":
self._study.filename = file_name
else:
self._study.filename = file_name + ".pamhyr"
self._study.save()
sql_request_count = self._study.sql_save_request_count()
progress = QProgressDialog(
"Saving...", None,
0, sql_request_count,
parent=self
)
progress.setWindowModality(Qt.WindowModal)
progress.setValue(0)
logger.info("Save...")
self._study.save(
progress=lambda: progress.setValue(progress.value() + 1)
)
logger.info("Done")
##################
# MSG AND DIALOG #
@@ -628,6 +656,19 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
reservoir = ReservoirWindow(study=self._study, parent=self)
reservoir.show()
def open_hydraulic_structures(self):
if self.sub_window_exists(
HydraulicStructuresWindow,
data=[self._study, None]
):
return
hydraulic_structures = HydraulicStructuresWindow(
study=self._study,
parent=self
)
hydraulic_structures.show()
def open_lateral_contrib(self):
if self.sub_window_exists(
LateralContributionWindow,
@@ -635,7 +676,10 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
):
return
lateral = LateralContributionWindow(study=self._study, parent=self)
lateral = LateralContributionWindow(
study=self._study,
parent=self
)
lateral.show()
def open_stricklers(self):
+6 -3
View File
@@ -178,12 +178,15 @@ class EdgeItem(QGraphicsItem):
if self.graph.selected_item() == self:
color = Qt.red
elif self.graph.current_edge() == self:
color = Qt.black
color = Qt.blue
elif not self.graph.graph.is_enable_edge(self.edge):
color = Qt.darkGray
painter.setPen(QPen(color, 2, Qt.SolidLine, Qt.RoundCap,
Qt.RoundJoin))
painter.setPen(
QPen(
color, 2, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin
)
)
# Draw the line
painter.drawLine(line)
+1 -1
View File
@@ -69,7 +69,7 @@ class Plot(PamhyrPlot):
)
# Plot label
#header = self.data.header
# header = self.data.header
self.canvas.axes.set_xlabel(
self._table_headers["z"], color='black', fontsize=10
)
+2 -7
View File
@@ -88,18 +88,13 @@ class EditReservoirWindow(PamhyrWindow):
def setup_table(self):
headers = {}
table_headers = self._trad.get_dict("table_headers")
#for h in self._data.header:
#headers[h] = table_headers[h]
table = self.find(QTableView, "tableView")
self._table = TableModel(
table_view=table,
table_headers=table_headers,
editable_headers=table_headers,
#editable_headers=self._data.header,
delegates={
#"time": self._delegate_time,
},
delegates={},
data=self._data,
undo=self._undo_stack,
opt_data=self._study.time_system
@@ -181,7 +176,7 @@ class EditReservoirWindow(PamhyrWindow):
rows = self.index_selected_rows()
table = []
#table.append(self._data.header)
# table.append(self._data.header)
table.append(self._trad.get_dict("table_headers"))
data = self._data.data
+14 -1
View File
@@ -56,9 +56,22 @@ class ComboBoxDelegate(QItemDelegate):
def createEditor(self, parent, option, index):
self.editor = QComboBox(parent)
nodes = list(
map(
lambda n: n.name,
filter(
lambda n: not (
self._data.is_upstream_node(n)
or self._data.is_downstream_node(n)
),
self._data.nodes()
)
)
)
self.editor.addItems(
[_translate("Reservoir", "Not associated")] +
self._data.nodes_names()
nodes
)
self.editor.setCurrentText(index.data(Qt.DisplayRole))
+54 -8
View File
@@ -18,6 +18,7 @@
from tools import timer
from View.Tools.PamhyrPlot import PamhyrPlot
from matplotlib import pyplot as plt
from PyQt5.QtCore import (
QCoreApplication
@@ -89,17 +90,17 @@ class PlotAC(PamhyrPlot):
# Water elevation
water_z = profile.get_ts_key(self._current_timestamp, "Z")
self.canvas.axes.plot(
self.water, = self.canvas.axes.plot(
[min(x), max(x)], [water_z, water_z],
lw=1., color='b',
)
x_z = list(map(lambda _: water_z, x))
self.canvas.axes.fill_between(
self.collection = self.canvas.axes.fill_between(
x, z, water_z,
where=z <= water_z,
color='blue', alpha=0.5, interpolate=True
color='skyblue', alpha=0.7, interpolate=True
)
self.liste_chemins = self.collection.get_paths()
self.canvas.figure.tight_layout()
self.canvas.figure.canvas.draw_idle()
@@ -109,15 +110,60 @@ class PlotAC(PamhyrPlot):
def set_reach(self, reach_id):
self._current_reach_id = reach_id
self._current_profile_id = 0
self.draw()
self.update()
def set_profile(self, profile_id):
self._current_profile_id = profile_id
self.draw()
self.update()
def set_timestamp(self, timestamp):
self._current_timestamp = timestamp
self.draw()
self.update_poly()
def update(self):
self.draw()
reach = self.results.river.reach(self._current_reach_id)
profile = reach.profile(self._current_profile_id)
x = profile.geometry.get_station()
z = profile.geometry.z()
self.line_kp.set_data(x, z)
self.canvas.axes.set_xlim(
left=min(x), right=max(x)
)
kp = reach.geometry.get_kp()
# Water elevation
water_z = profile.get_ts_key(self._current_timestamp, "Z")
self.water.set_data([min(x), max(x)], [water_z, water_z])
self.collection.remove()
self.collection = self.canvas.axes.fill_between(
x, z, water_z,
where=z <= water_z,
color='skyblue', alpha=0.7, interpolate=True
)
self.canvas.figure.canvas.draw_idle()
def update_poly(self):
reach = self.results.river.reach(self._current_reach_id)
profile = reach.profile(self._current_profile_id)
x = profile.geometry.get_station()
z = profile.geometry.z()
# Water elevation
water_z = profile.get_ts_key(self._current_timestamp, "Z")
self.water.set_data([min(x), max(x)], [water_z, water_z])
self.collection.remove()
self.collection = self.canvas.axes.fill_between(
x, z, water_z,
where=z <= water_z,
color='skyblue', alpha=0.7, interpolate=True
)
self.canvas.figure.canvas.draw_idle()
+33 -28
View File
@@ -89,36 +89,27 @@ class PlotH(PamhyrPlot):
color='black', fontsize=10
)
ts = list(self.results.get("timestamps"))
ts.sort()
self.canvas.axes.set_xlim(
left=min(ts), right=max(ts)
)
self.ts = list(self.results.get("timestamps"))
self.ts.sort()
# Draw discharge for each timestamp
x = ts
x = self.ts
y = profile.get_key("Q")
if len(ts) != len(x):
logger.warning(
"Results as less Q data ({len(x)}) " +
"than timestamps ({len(ts)}) " +
"for profile {self._current_profile_id}"
)
return
self.canvas.axes.set_ylim(
[min(min(y), 0), max(y) + 10]
self._line, = self.canvas.axes.plot(
x, y, lw=1.,
color='r',
markersize=3, marker='+'
)
self._line = [
self.canvas.axes.plot(
x, y, lw=1.,
color='r',
markersize=3, marker='+'
)
]
self._current, = self.canvas.axes.plot(
self._current_timestamp,
y[self.ts.index(self._current_timestamp)],
lw=1., color='b',
markersize=3, marker='+'
)
self.canvas.axes.relim()
# Custom time display
nb = len(x)
@@ -169,15 +160,29 @@ class PlotH(PamhyrPlot):
def set_reach(self, reach_id):
self._current_reach_id = reach_id
self._current_profile_id = 0
self.draw()
self.update()
def set_profile(self, profile_id):
self._current_profile_id = profile_id
self.draw()
self.update()
def set_timestamp(self, timestamp):
self._current_timestamp = timestamp
self.draw()
self.update()
def update(self):
self.draw()
reach = self.results.river.reach(self._current_reach_id)
profile = reach.profile(self._current_profile_id)
x = self.ts
y = profile.get_key("Q")
self._line.set_data(x, y)
self._current.set_data(
self._current_timestamp,
y[self.ts.index(self._current_timestamp)]
)
self.canvas.axes.relim()
self.canvas.figure.canvas.draw_idle()
+34 -4
View File
@@ -72,6 +72,7 @@ class PlotKPC(PamhyrPlot):
kp = reach.geometry.get_kp()
z_min = reach.geometry.get_z_min()
z_max = reach.geometry.get_z_max()
self.canvas.axes.set_xlim(
left=min(kp), right=max(kp)
@@ -100,9 +101,21 @@ class PlotKPC(PamhyrPlot):
self.canvas.axes.fill_between(
kp, z_min, water_z,
color='blue', alpha=0.5, interpolate=True
color='skyblue', alpha=0.7, interpolate=True
)
self.profile, = self.canvas.axes.plot(
[
kp[self._current_profile_id],
kp[self._current_profile_id]
],
[
z_max[self._current_profile_id],
z_min[self._current_profile_id]
],
color='red', lw=1.
)
self.canvas.figure.tight_layout()
self.canvas.figure.canvas.draw_idle()
if self.toolbar is not None:
@@ -111,15 +124,32 @@ class PlotKPC(PamhyrPlot):
def set_reach(self, reach_id):
self._current_reach_id = reach_id
self._current_profile_id = 0
self.draw()
self.update()
def set_profile(self, profile_id):
self._current_profile_id = profile_id
self.draw()
self.update_profil()
def set_timestamp(self, timestamp):
self._current_timestamp = timestamp
self.draw()
self.update()
def update(self):
self.draw()
def update_profil(self):
reach = self.results.river.reach(self._current_reach_id)
kp = reach.geometry.get_kp()
z_min = reach.geometry.get_z_min()
z_max = reach.geometry.get_z_max()
self.profile.set_data(
[
kp[self._current_profile_id],
kp[self._current_profile_id]
],
[
z_max[self._current_profile_id],
z_min[self._current_profile_id]
]
)
self.canvas.figure.canvas.draw_idle()
+2 -2
View File
@@ -51,13 +51,13 @@ class PlotSedProfile(PamhyrPlot):
profiles_sl = list(
map(
lambda sl: sl[0],
profile.get_ts_key(self._current_timestamp, "sl")
profile.get_ts_key(self._current_timestamp, "sl")[0]
)
)
profiles_sl_0 = list(
map(
lambda sl: sl[0],
profile.get_ts_key(0.0, "sl")
profile.get_ts_key(0.0, "sl")[0]
)
)
+6 -3
View File
@@ -124,14 +124,14 @@ class PlotSedReach(PamhyrPlot):
profiles_sl_0 = list(
map(
# Get SL list for profile p at time 0 (initial data)
lambda p: p.get_ts_key(0.0, "sl"),
lambda p: p.get_ts_key(0.0, "sl")[0],
reach.profiles
)
)
profiles_sl = list(
map(
# Get SL list for profile p at current time
lambda p: p.get_ts_key(self._current_timestamp, "sl"),
lambda p: p.get_ts_key(self._current_timestamp, "sl")[0],
reach.profiles
)
)
@@ -163,7 +163,10 @@ class PlotSedReach(PamhyrPlot):
z_sl = reduce(
lambda acc, v: acc + [
list(
map(lambda x, y: y - x, v, acc[-1])
map(
lambda x, y: y - x,
v, acc[-1]
)
)
],
sl_0,
+68 -42
View File
@@ -129,53 +129,26 @@ class PlotXY(PamhyrPlot):
profile.x(),
profile.y(),
lw=1., markersize=3,
marker='+', color="b"
color="r", marker='+'
)
self.plot_selected.set_visible(False)
self.plot_selected.set_visible(True)
# Display point under water
poly_l_x = []
poly_l_y = []
poly_r_x = []
poly_r_y = []
for profile in reach.profiles:
water_z = profile.get_ts_key(
self._current_timestamp, "Z"
)
pgeo = profile.geometry
poly_x = [0]
poly_y = [0]
x, y = reduce(
lambda acc, pts: (acc[0] + [pts.x], acc[1] + [pts.y]),
filter(
lambda pts: pts.z < water_z,
pgeo.points
),
([], [])
)
self.fill = self.canvas.axes.fill(
poly_x, poly_y,
color='skyblue',
alpha=0.7
)
poly_l_x.append(x[0])
poly_l_y.append(y[0])
poly_r_x.append(x[-1])
poly_r_y.append(y[-1])
self.canvas.axes.plot(
x, y, lw=1.,
color='b',
markersize=1,
marker='o'
)
poly_x = poly_l_x + list(reversed(poly_r_x)) + [poly_l_x[0]]
poly_y = poly_l_y + list(reversed(poly_r_y)) + [poly_l_y[0]]
self.canvas.axes.fill(poly_x, poly_y, color='blue', alpha=1)
self.canvas.axes.autoscale_view(True, True, True)
self.canvas.axes.autoscale()
# self.canvas.axes.autoscale_view(True, True, True)
# self.canvas.axes.autoscale()
self.canvas.figure.tight_layout()
self.canvas.figure.canvas.draw_idle()
if self.toolbar is not None:
self.toolbar.update()
self.update()
def set_reach(self, reach_id):
self._current_reach_id = reach_id
@@ -184,11 +157,64 @@ class PlotXY(PamhyrPlot):
def set_profile(self, profile_id):
self._current_profile_id = profile_id
self.draw()
self.update_profile()
def set_timestamp(self, timestamp):
self._current_timestamp = timestamp
self.draw()
self.update_poly()
def update_profile(self):
reach = self.results.river.reach(self._current_reach_id)
if self.display_current:
# Current profile
profile = reach.profile(self._current_profile_id).geometry
self.plot_selected.set_data(profile.x(), profile.y())
self.plot_selected.set_visible(True)
self.canvas.draw_idle()
def update_poly(self):
reach = self.results.river.reach(self._current_reach_id)
profile = reach.profile(self._current_profile_id).geometry
# Display water
poly_l_x = []
poly_l_y = []
poly_r_x = []
poly_r_y = []
for profile in reach.profiles:
water_z = profile.get_ts_key(
self._current_timestamp, "Z"
)
ptX = profile.get_ts_key(
self._current_timestamp, "ptX"
)
ptY = profile.get_ts_key(
self._current_timestamp, "ptY"
)
poly_l_x.append(ptX.x)
poly_l_y.append(ptX.y)
poly_r_x.append(ptY.x)
poly_r_y.append(ptY.y)
# self.canvas.axes.plot(
# x, y, lw=1.,
# color='b',
# markersize=1,
# marker='o'
# )
poly_x = poly_l_x + list(reversed(poly_r_x))
poly_y = poly_l_y + list(reversed(poly_r_y))
poly = []
for i in range(len(poly_x)):
poly.append([poly_x[i], poly_y[i]])
self.fill[0].set_xy(poly)
self.canvas.draw_idle()
def update(self):
self.draw()
self.update_profile()
self.update_poly()
+80 -7
View File
@@ -16,21 +16,22 @@
# -*- coding: utf-8 -*-
import os
import logging
from datetime import datetime
from datetime import datetime
from tools import trace, timer
from View.Tools.PamhyrWindow import PamhyrWindow
from PyQt5.QtGui import (
QKeySequence,
QKeySequence, QIcon, QPixmap,
)
from PyQt5.QtCore import (
Qt, QVariant, QAbstractTableModel,
QCoreApplication, QModelIndex, pyqtSlot,
QItemSelectionModel,
QItemSelectionModel, QTimer,
)
from PyQt5.QtWidgets import (
@@ -69,6 +70,14 @@ class ResultsWindow(PamhyrWindow):
_pamhyr_ui = "Results"
_pamhyr_name = "Results"
def _path_file(self, filename):
return os.path.abspath(
os.path.join(
os.path.dirname(__file__),
"..", "..", filename
)
)
def __init__(self, study=None, config=None,
solver=None, results=None,
parent=None):
@@ -97,7 +106,7 @@ class ResultsWindow(PamhyrWindow):
self._additional_plot = {}
self.setup_table()
self.setup_plot()
self.setup_plots()
self.setup_slider()
self.setup_statusbar()
self.setup_connections()
@@ -124,7 +133,25 @@ class ResultsWindow(PamhyrWindow):
self._slider_time.setMaximum(len(self._timestamps) - 1)
self._slider_time.setValue(len(self._timestamps) - 1)
def setup_plot(self):
file_path = os.path.abspath(os.path.dirname(__file__))
self._icon_start = QIcon()
self._icon_start.addPixmap(
QPixmap(f'{file_path}/../ui/ressources/media-playback-start.png')
)
self._icon_pause = QIcon()
self._icon_pause.addPixmap(
QPixmap(f'{file_path}/../ui/ressources/media-playback-pause.png')
)
self._button_play = self.find(QPushButton, f"playButton")
self._button_play.setIcon(self._icon_start)
self._button_back = self.find(QPushButton, f"backButton")
self._button_next = self.find(QPushButton, f"nextButton")
self._button_first = self.find(QPushButton, f"firstButton")
self._button_last = self.find(QPushButton, f"lastButton")
self._timer = QTimer(self)
def setup_plots(self):
self.canvas = MplCanvas(width=5, height=4, dpi=100)
self.canvas.setObjectName("canvas")
self.toolbar = PamhyrPlotToolbar(
@@ -140,7 +167,7 @@ class ResultsWindow(PamhyrWindow):
reach_id=0,
profile_id=0,
toolbar=self.toolbar,
display_current=False
display_current=True
)
self.plot_xy.draw()
@@ -238,6 +265,10 @@ class ResultsWindow(PamhyrWindow):
)
self.plot_sed_profile.draw()
def closeEvent(self, event):
self._timer.stop()
super(ResultsWindow, self).closeEvent(event)
def _compute_status_label(self):
# Timestamp
ts = self._timestamps[self._slider_time.value()]
@@ -310,6 +341,13 @@ class ResultsWindow(PamhyrWindow):
self._slider_profile.valueChanged.connect(
self._set_current_profile_slider)
self._slider_time.valueChanged.connect(self._set_current_timestamp)
self._button_play.setChecked(False)
self._button_play.clicked.connect(self._pause)
self._button_back.clicked.connect(self._back)
self._button_next.clicked.connect(self._next)
self._button_first.clicked.connect(self._first)
self._button_last.clicked.connect(self._last)
self._timer.timeout.connect(self._update_slider)
def update_table_selection_reach(self, ind):
table = self.find(QTableView, f"tableView_reach")
@@ -373,7 +411,7 @@ class ResultsWindow(PamhyrWindow):
self.plot_xy.set_timestamp(timestamp)
self.plot_ac.set_timestamp(timestamp)
self.plot_kpc.set_timestamp(timestamp)
# self.plot_h.set_timestamp(timestamp)
self.plot_h.set_timestamp(timestamp)
if self._study.river.has_sediment():
self.plot_sed_reach.set_timestamp(timestamp)
@@ -525,3 +563,38 @@ class ResultsWindow(PamhyrWindow):
def _redo(self):
self._table.redo()
# play / pause buttons
def _update_slider(self):
if self._slider_time.value() == self._slider_time.maximum():
self._slider_time.setValue(self._slider_time.minimum())
else:
self._slider_time.setValue(self._slider_time.value()+1)
def _next(self):
self._slider_time.setValue(self._slider_time.value()+1)
def _back(self):
self._slider_time.setValue(self._slider_time.value()-1)
def _first(self):
self._slider_time.setValue(self._slider_time.minimum())
def _last(self):
self._slider_time.setValue(self._slider_time.maximum())
def _pause(self):
if self._button_play.isChecked():
self._button_next.setEnabled(False)
self._button_back.setEnabled(False)
self._button_first.setEnabled(False)
self._button_last.setEnabled(False)
self._timer.start(100)
self._button_play.setIcon(self._icon_pause)
else:
self._timer.stop()
self._button_next.setEnabled(True)
self._button_back.setEnabled(True)
self._button_first.setEnabled(True)
self._button_last.setEnabled(True)
self._button_play.setIcon(self._icon_start)
+127 -56
View File
@@ -21,7 +21,7 @@ import logging
import tempfile
from queue import Queue
from tools import trace, timer
from tools import trace, timer, logger_exception
from View.Tools.PamhyrWindow import PamhyrDialog, PamhyrWindow
@@ -74,10 +74,16 @@ class SelectSolverWindow(PamhyrDialog):
self.setup_combobox()
self.setup_connections()
self.select_last_solver()
def setup_combobox(self):
solvers = self._config.solvers
solvers_name = list(map(lambda s: s.name + f" - ({s._type})", solvers))
solvers_name = list(
map(
self._format_solver_name,
solvers
)
)
self.combobox_add_items("comboBox", solvers_name)
@@ -86,6 +92,26 @@ class SelectSolverWindow(PamhyrDialog):
self.find(QPushButton, "pushButton_cancel")\
.clicked.connect(self.reject)
def select_last_solver(self):
solvers = self._config.solvers
last = self._config.last_solver_name
solver = list(
filter(
lambda s: s.name == last,
solvers
)
)
if len(solver) != 0:
self.set_combobox_text(
"comboBox",
self._format_solver_name(solver[0])
)
def _format_solver_name(self, solver):
return f"{solver.name} - ({solver._type})"
@property
def solver(self):
return self._solver
@@ -94,6 +120,8 @@ class SelectSolverWindow(PamhyrDialog):
solver_name = self.get_combobox_text("comboBox")
solver_name = solver_name.rsplit(" - ", 1)[0]
self._config.update_last_solver_used(solver_name)
self._solver = next(
filter(
lambda s: s.name == solver_name,
@@ -124,40 +152,11 @@ class SolverLogWindow(PamhyrWindow):
self.setup_action()
self.setup_alarm()
self.setup_connections()
self.setup_workdir()
self.setup_process()
self._workdir = ""
if self._study.filename == "":
self._workdir = tempfile.TemporaryDirectory()
else:
self._workdir = os.path.join(
os.path.dirname(self._study.filename),
"_PAMHYR_",
self._study.name.replace(" ", "_"),
self._solver.name.replace(" ", "_"),
)
os.makedirs(self._workdir, exist_ok=True)
self._alarm.start(500)
self._output = Queue()
self._process = self.new_process(parent)
self._log(f" *** Export study {self._solver.name}", color="blue")
self._solver.export(self._study, self._workdir, qlog=self._output)
self.update()
self._log(f" *** Run solver {self._solver.name}", color="blue")
self._solver.run(
study,
process=self._process,
output_queue=self._output
)
def new_process(self, parent):
new = QProcess(parent)
new.setWorkingDirectory(self._workdir)
new.setProcessChannelMode(QProcess.MergedChannels)
return new
self.export()
self.run()
def setup_action(self):
self.find(QAction, "action_start").setEnabled(False)
@@ -182,30 +181,78 @@ class SolverLogWindow(PamhyrWindow):
self._alarm.timeout.connect(self.update)
def setup_workdir(self):
self._workdir = ""
if self._study.filename == "":
self._workdir = tempfile.TemporaryDirectory()
else:
self._workdir = os.path.join(
os.path.dirname(self._study.filename),
"_PAMHYR_",
self._study.name.replace(" ", "_"),
self._solver.name.replace(" ", "_"),
)
os.makedirs(self._workdir, exist_ok=True)
def setup_process(self):
self._alarm.start(100)
self._output = Queue()
self._process = self.new_process(self._parent)
def new_process(self, parent):
new = QProcess(parent)
new.setWorkingDirectory(self._workdir)
new.setProcessChannelMode(QProcess.MergedChannels)
return new
def export(self):
self._log(f" *** Export study {self._solver.name}", color="blue")
self._solver.export(self._study, self._workdir, qlog=self._output)
self.update()
def closeEvent(self, event):
self._alarm.stop()
super(SolverLogWindow, self).closeEvent(event)
#######
# LOG #
#######
def _log(self, msg, color=None):
if type(msg) is str:
logger.info(f"solver: {msg}")
msg = msg.rsplit('\n')[0]
if color is not None:
msg = f"<font color=\"{color}\">" + msg + "</font>"
self.find(QTextEdit, "textEdit").append(msg)
self._log_str(msg, color)
elif type(msg) is int:
logger.info(f"solver: Returns {msg}")
self._log_int(msg, color)
color = "blue" if msg == 0 else "red"
self.find(QTextEdit, "textEdit")\
.append(f"<font color=\"{color}\">" +
f" *** Finished with code {msg}" +
"</font>")
def _log_str(self, msg, color=None):
logger.info(f"solver: {msg}")
msg = msg.rsplit('\n')[0]
self.statusbar.showMessage(
"Done" if msg == 0 else "Failed",
3000
if color is not None:
msg = f"<font color=\"{color}\">" + msg + "</font>"
self.find(QTextEdit, "textEdit").append(msg)
def _log_int(self, int_code, color=None):
logger.info(f"solver: Returns {int_code}")
color = "blue" if int_code == 0 else "red"
self.find(QTextEdit, "textEdit")\
.append(
f"<font color=\"{color}\">" +
f" *** Finished with code {int_code}" +
"</font>"
)
self.statusbar.showMessage(
"Done" if int_code == 0 else "Failed",
3000
)
##########
# UPDATE #
##########
def update(self):
if self._solver.is_stoped():
self.find(QAction, "action_start").setEnabled(True)
@@ -216,25 +263,43 @@ class SolverLogWindow(PamhyrWindow):
if self._solver.log_file() != "":
self.find(QAction, "action_log_file").setEnabled(True)
self._update_logs_all()
self._update_get_results()
self._update_logs_all()
def _update_get_results(self):
if self._results is None:
self._results = self._solver.results(
self._study, self._workdir, qlog=self._output
)
self._parent.set_results(self._solver, self._results)
try:
self._results = self._solver.results(
self._study, self._workdir, qlog=self._output
)
self._parent.set_results(self._solver, self._results)
except Exception as e:
logger.error(f"Failed to open results")
logger_exception(e)
def _update_logs_all(self):
while self._output.qsize() != 0:
s = self._output.get()
if type(s) is str and "[ERROR]" in s:
self._log(s, color="red")
else:
self._log(s)
####################
# Process controle #
####################
def run(self):
self._log(f" *** Run solver {self._solver.name}", color="blue")
self._solver.run(
self._study,
process=self._process,
output_queue=self._output
)
def start(self):
if self._solver.is_stoped():
self._log(f" *** Export study {self._solver.name}", color="blue")
@@ -276,6 +341,10 @@ class SolverLogWindow(PamhyrWindow):
if self._solver.log_file() != "":
self.find(QAction, "action_log_file").setEnabled(True)
###########
# Results #
###########
def results(self):
if self._results is None:
self._results = self._solver.results(
@@ -284,6 +353,8 @@ class SolverLogWindow(PamhyrWindow):
self._parent.set_results(self._solver, self._results)
self._parent.open_solver_results(self._solver, self._results)
self._solver.has_results_loaded()
def log_file(self):
file_name = os.path.join(self._workdir, self._solver.log_file())
log = SolverLogFileWindow(
+3
View File
@@ -129,4 +129,7 @@ class ParamTranslate(PamhyrTranslate):
"mage_min_reach_volume_to_check":
_translate("SolverParameters",
"Minimum reach volume to check"),
"mage_init_internal":
_translate("SolverParameters",
"Use Mage internal initialization (Y/N)"),
}
+1
View File
@@ -101,4 +101,5 @@ class NewStudyWindow(PamhyrDialog):
self._study.use_date(date)
else:
self._study.use_time()
self.done(True)
+1 -1
View File
@@ -81,7 +81,7 @@ class WindowToolKit(object):
header = row.copy()
continue
values.append(list(filter(lambda s: s != '', row)))
values.append(row)
return header, values
+2 -1
View File
@@ -55,11 +55,12 @@ class ListedSubWindow(object):
logger.info(f"Close window: ({h}) {self.sub_win_cnt}")
def _sub_win_exists(self, h):
return reduce(
res = reduce(
lambda acc, el: (acc or (h == (el[1].hash()))),
self.sub_win_list,
False
)
return res
def sub_win_exists(self, h):
return self._sub_win_exists(h)
+4 -3
View File
@@ -83,7 +83,8 @@ class PamhyrTableModel(QAbstractTableModel):
trad=None,
data=None,
undo=None,
opt_data=None):
opt_data=None,
parent=None):
super(PamhyrTableModel, self).__init__()
self._table_view = table_view
@@ -93,6 +94,7 @@ class PamhyrTableModel(QAbstractTableModel):
self._editable_headers = editable_headers
self._delegates = delegates
self._trad = trad
self._parent = parent
self._data = data
self._opt_data = opt_data
@@ -119,8 +121,7 @@ class PamhyrTableModel(QAbstractTableModel):
options = Qt.ItemIsEnabled | Qt.ItemIsSelectable
if (self._editable_headers or
self._headers[column] in self._editable_headers):
if self._headers[column] in self._editable_headers:
options |= Qt.ItemIsEditable
return options
+5
View File
@@ -186,3 +186,8 @@ class PamhyrDialog(ASubWindow, ListedSubWindow, PamhyrWindowTools):
self._hash_data.append(self._config)
self._set_title()
def done(self, result):
if self.parent is not None:
self.parent.sub_win_del(self.hash())
super(PamhyrDialog, self).done(result)
+3 -3
View File
@@ -98,7 +98,7 @@ class PamhyrPlotToolbar(NavigationToolbar2QT):
icon_zoom = QtGui.QIcon()
icon_zoom.addPixmap(QtGui.QPixmap(
os.path.abspath(f"{file_path}/../ui/ressources/zoom.png")
os.path.abspath(f"{file_path}/../../ui/ressources/zoom.png")
))
icons.append(("zoom", icon_zoom))
@@ -122,7 +122,7 @@ class PamhyrPlotToolbar(NavigationToolbar2QT):
icon_btn_isometric_view.addPixmap(
QtGui.QPixmap(
os.path.abspath(
f"{file_path}/../ui/ressources/zoom_fit_11.png"
f"{file_path}/../../ui/ressources/zoom_fit_11.png"
)
)
)
@@ -131,7 +131,7 @@ class PamhyrPlotToolbar(NavigationToolbar2QT):
icon_btn_global_view.addPixmap(
QtGui.QPixmap(
os.path.abspath(
f"{file_path}/../ui/ressources/zoom_fit.png"
f"{file_path}/../../ui/ressources/zoom_fit.png"
)
)
)
+142
View File
@@ -0,0 +1,142 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>600</height>
</rect>
</property>
<property name="windowTitle">
<string>Basic hydraulic structures</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QSplitter" name="splitter_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QWidget" name="verticalLayoutWidget_3">
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QTableView" name="tableView">
<property name="minimumSize">
<size>
<width>300</width>
<height>0</height>
</size>
</property>
<property name="baseSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="leftMargin">
<number>5</number>
</property>
<item>
<widget class="QCheckBox" name="checkBox">
<property name="text">
<string>Enable / Disable basic hydraulic structure</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<widget class="QWidget" name="verticalLayoutWidget">
<layout class="QVBoxLayout" name="verticalLayout"/>
</widget>
<widget class="QWidget" name="verticalLayoutWidget_2">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QTableView" name="tableView_2"/>
</item>
</layout>
</widget>
</widget>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>22</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
<widget class="QToolBar" name="toolBar">
<property name="windowTitle">
<string>toolBar</string>
</property>
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
</attribute>
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
<addaction name="action_add"/>
<addaction name="action_delete"/>
</widget>
<action name="action_add">
<property name="icon">
<iconset>
<normaloff>ressources/gtk-add.png</normaloff>ressources/gtk-add.png</iconset>
</property>
<property name="text">
<string>Add</string>
</property>
<property name="toolTip">
<string>Add a new point</string>
</property>
</action>
<action name="action_delete">
<property name="icon">
<iconset>
<normaloff>ressources/gtk-remove.png</normaloff>ressources/gtk-remove.png</iconset>
</property>
<property name="text">
<string>Delete</string>
</property>
<property name="toolTip">
<string>Delete points</string>
</property>
</action>
<action name="action_edit">
<property name="icon">
<iconset>
<normaloff>ressources/edit.png</normaloff>ressources/edit.png</iconset>
</property>
<property name="text">
<string>Edit</string>
</property>
<property name="toolTip">
<string>Edit selected hydraulic structure</string>
</property>
</action>
</widget>
<resources/>
<connections/>
</ui>
+2 -2
View File
@@ -114,7 +114,7 @@
<string>Add</string>
</property>
<property name="toolTip">
<string>Add a new boundary condition or lateral contribution</string>
<string>Add a new boundary condition or punctual contribution</string>
</property>
<property name="shortcut">
<string>Ctrl+N</string>
@@ -144,7 +144,7 @@
<string>Edit</string>
</property>
<property name="toolTip">
<string>Edit boundary condition or lateral contribution</string>
<string>Edit boundary condition or punctual contribution</string>
</property>
<property name="shortcut">
<string>Ctrl+E</string>
+2 -2
View File
@@ -86,7 +86,7 @@
<string>Add</string>
</property>
<property name="toolTip">
<string>Add a new point in boundary condition or lateral contribution</string>
<string>Add a new point in boundary condition or punctual contribution</string>
</property>
<property name="shortcut">
<string>Ctrl+N</string>
@@ -116,7 +116,7 @@
<string>Sort</string>
</property>
<property name="toolTip">
<string>Sort boundary condition point</string>
<string>Sort boundary condition points</string>
</property>
</action>
</widget>
+64 -62
View File
@@ -19,78 +19,80 @@
<widget class="QWidget" name="centralwidget">
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QSplitter" name="splitter_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QTableView" name="tableView"/>
<widget class="QSplitter" name="splitter_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QWidget" name="verticalLayoutWidget">
<layout class="QVBoxLayout" name="verticalLayout"/>
</widget>
<widget class="QWidget" name="verticalLayoutWidget_2">
<layout class="QVBoxLayout" name="verticalLayout_2"/>
</widget>
</widget>
<widget class="QWidget" name="verticalLayoutWidget_3">
<layout class="QVBoxLayout" name="verticalLayout_3"/>
</widget>
</widget>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_4">
<widget class="QSplitter" name="splitter_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QWidget" name="horizontalLayoutWidget">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<widget class="QTableView" name="tableView"/>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<widget class="QSlider" name="verticalSlider">
<property name="orientation">
<enum>Qt::Vertical</enum>
<widget class="QPushButton" name="pushButton_up">
<property name="text">
<string/>
</property>
<property name="invertedAppearance">
<bool>true</bool>
<property name="icon">
<iconset>
<normaloff>ressources/go-up2.png</normaloff>ressources/go-up2.png</iconset>
</property>
<property name="invertedControls">
<bool>true</bool>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QSlider" name="verticalSlider">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="invertedAppearance">
<bool>true</bool>
</property>
<property name="invertedControls">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QPushButton" name="pushButton_down">
<property name="text">
<string/>
</property>
<property name="icon">
<iconset>
<normaloff>ressources/go-down1.png</normaloff>ressources/go-down1.png</iconset>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QPushButton" name="pushButton_up">
<property name="text">
<string/>
</property>
<property name="icon">
<iconset>
<normaloff>ressources/go-up2.png</normaloff>ressources/go-up2.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton_down">
<property name="text">
<string/>
</property>
<property name="icon">
<iconset>
<normaloff>ressources/go-down1.png</normaloff>ressources/go-down1.png</iconset>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QSplitter" name="splitter_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QWidget" name="verticalLayoutWidget">
<layout class="QVBoxLayout" name="verticalLayout"/>
</widget>
<widget class="QWidget" name="verticalLayoutWidget_2">
<layout class="QVBoxLayout" name="verticalLayout_2"/>
</widget>
</widget>
<widget class="QWidget" name="verticalLayoutWidget_3">
<layout class="QVBoxLayout" name="verticalLayout_3"/>
</widget>
</widget>
</widget>
</item>
</layout>
</widget>
+139
View File
@@ -0,0 +1,139 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>600</height>
</rect>
</property>
<property name="windowTitle">
<string>Hydraulic structures</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QSplitter" name="splitter_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QWidget" name="verticalLayoutWidget_3">
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QTableView" name="tableView">
<property name="minimumSize">
<size>
<width>300</width>
<height>0</height>
</size>
</property>
<property name="baseSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="leftMargin">
<number>5</number>
</property>
<item>
<widget class="QCheckBox" name="checkBox">
<property name="text">
<string>Enable / Disable hydraulic structure</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<widget class="QWidget" name="verticalLayoutWidget">
<layout class="QVBoxLayout" name="verticalLayout"/>
</widget>
<widget class="QWidget" name="verticalLayoutWidget_2">
<layout class="QVBoxLayout" name="verticalLayout_2"/>
</widget>
</widget>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>22</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
<widget class="QToolBar" name="toolBar">
<property name="windowTitle">
<string>toolBar</string>
</property>
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
</attribute>
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
<addaction name="action_add"/>
<addaction name="action_delete"/>
<addaction name="action_edit"/>
</widget>
<action name="action_add">
<property name="icon">
<iconset>
<normaloff>ressources/gtk-add.png</normaloff>ressources/gtk-add.png</iconset>
</property>
<property name="text">
<string>Add</string>
</property>
<property name="toolTip">
<string>Add a new point</string>
</property>
</action>
<action name="action_delete">
<property name="icon">
<iconset>
<normaloff>ressources/gtk-remove.png</normaloff>ressources/gtk-remove.png</iconset>
</property>
<property name="text">
<string>Delete</string>
</property>
<property name="toolTip">
<string>Delete points</string>
</property>
</action>
<action name="action_edit">
<property name="icon">
<iconset>
<normaloff>ressources/edit.png</normaloff>ressources/edit.png</iconset>
</property>
<property name="text">
<string>Edit</string>
</property>
<property name="toolTip">
<string>Edit selected hydraulic structure</string>
</property>
</action>
</widget>
<resources/>
<connections/>
</ui>
+11 -2
View File
@@ -132,6 +132,7 @@
<addaction name="action_menu_edit_friction"/>
<addaction name="action_menu_edit_lateral_contribution"/>
<addaction name="action_menu_edit_reservoirs"/>
<addaction name="action_menu_edit_hydraulic_structures"/>
</widget>
<widget class="QMenu" name="menu_results">
<property name="title">
@@ -490,7 +491,7 @@
</action>
<action name="action_menu_boundary_conditions">
<property name="text">
<string>Boundary conditions and one-time contributions</string>
<string>Boundary conditions and punctual contributions</string>
</property>
<property name="font">
<font/>
@@ -799,7 +800,7 @@
<string>Boundary conditions</string>
</property>
<property name="toolTip">
<string>Edit boundary conditions and one-time contributions</string>
<string>Edit boundary conditions and punctual contributions</string>
</property>
<property name="font">
<font/>
@@ -934,6 +935,14 @@
<string>Edit reservoirs</string>
</property>
</action>
<action name="action_menu_edit_hydraulic_structures">
<property name="text">
<string>Hydraulic structures</string>
</property>
<property name="toolTip">
<string>Edit hydraulic structures</string>
</property>
</action>
</widget>
<resources/>
<connections>
+134 -40
View File
@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>942</width>
<height>655</height>
<width>1280</width>
<height>720</height>
</rect>
</property>
<property name="windowTitle">
@@ -17,6 +17,12 @@
<locale language="English" country="Europe"/>
</property>
<widget class="QWidget" name="centralwidget">
<property name="minimumSize">
<size>
<width>874</width>
<height>0</height>
</size>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0">
<widget class="QSplitter" name="splitter_4">
@@ -28,7 +34,26 @@
<enum>Qt::Vertical</enum>
</property>
<widget class="QTableView" name="tableView_reach"/>
<widget class="QTableView" name="tableView_profile"/>
<widget class="QWidget" name="layoutWidget">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QTableView" name="tableView_profile"/>
</item>
<item>
<widget class="QSlider" name="verticalSlider_profile">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="invertedAppearance">
<bool>true</bool>
</property>
<property name="invertedControls">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
<widget class="QWidget" name="layoutWidget">
<layout class="QGridLayout" name="gridLayout_2">
@@ -43,25 +68,29 @@
</attribute>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QSplitter" name="splitter_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QWidget" name="verticalLayoutWidget">
<layout class="QVBoxLayout" name="verticalLayout"/>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QSplitter" name="splitter_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QWidget" name="verticalLayoutWidget">
<layout class="QVBoxLayout" name="verticalLayout"/>
</widget>
<widget class="QWidget" name="verticalLayoutWidget_2">
<layout class="QVBoxLayout" name="verticalLayout_2"/>
</widget>
</widget>
<widget class="QWidget" name="verticalLayoutWidget_3">
<layout class="QVBoxLayout" name="verticalLayout_3"/>
</widget>
</widget>
<widget class="QWidget" name="verticalLayoutWidget_2">
<layout class="QVBoxLayout" name="verticalLayout_2"/>
</widget>
</widget>
<widget class="QWidget" name="verticalLayoutWidget_3">
<layout class="QVBoxLayout" name="verticalLayout_3"/>
</widget>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
@@ -103,25 +132,90 @@
</widget>
</widget>
</item>
<item row="0" column="1">
<widget class="QSlider" name="verticalSlider_profile">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="invertedAppearance">
<bool>true</bool>
</property>
<property name="invertedControls">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QSlider" name="horizontalSlider_time">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QPushButton" name="firstButton">
<property name="text">
<string/>
</property>
<property name="icon">
<iconset>
<normaloff>ressources/media-skip-backward.png</normaloff>ressources/media-skip-backward.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="backButton">
<property name="text">
<string/>
</property>
<property name="icon">
<iconset>
<normaloff>ressources/media-seek-backward.png</normaloff>ressources/media-seek-backward.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="playButton">
<property name="enabled">
<bool>true</bool>
</property>
<property name="mouseTracking">
<bool>true</bool>
</property>
<property name="contextMenuPolicy">
<enum>Qt::DefaultContextMenu</enum>
</property>
<property name="autoFillBackground">
<bool>true</bool>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset>
<normaloff>ressources/media-playback-start.png</normaloff>
<normalon>ressources/media-playback-pause.png</normalon>ressources/media-playback-start.png</iconset>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="nextButton">
<property name="text">
<string/>
</property>
<property name="icon">
<iconset>
<normaloff>ressources/media-seek-forward.png</normaloff>ressources/media-seek-forward.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="lastButton">
<property name="text">
<string/>
</property>
<property name="icon">
<iconset>
<normaloff>ressources/media-skip-forward.png</normaloff>ressources/media-skip-forward.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="horizontalSlider_time">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
@@ -134,7 +228,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>942</width>
<width>1280</width>
<height>22</height>
</rect>
</property>
Binary file not shown.

After

Width:  |  Height:  |  Size: 217 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 472 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 539 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 610 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 557 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 558 B

+23 -1
View File
@@ -35,7 +35,7 @@ logger = logging.getLogger()
class Config(SQL):
def __init__(self):
self._version = '0.0.3'
self._version = '0.0.4'
self.filename = Config.filename()
self.set_default_value()
@@ -113,11 +113,17 @@ class Config(SQL):
''
)
""")
if int(release) < 3:
self.execute(f"INSERT INTO data VALUES ('last_study', '')")
self.execute(
f"INSERT INTO data VALUES ('close_correctly', 'True')")
if int(release) < 4:
self.execute(
f"INSERT INTO data VALUES ('last_solver_name', '')"
)
self.commit()
def _load_solver(self):
@@ -195,6 +201,10 @@ class Config(SQL):
v = self.execute("SELECT value FROM data WHERE key='close_correctly'")
self.close_correctly = v[0] == "True"
# Last Solver
v = self.execute("SELECT value FROM data WHERE key='last_solver_name'")
self.last_solver_name = v[0]
# Debug
v = self.execute("SELECT value FROM data WHERE key='debug'")
self.debug = v[0] == "True"
@@ -250,6 +260,7 @@ class Config(SQL):
"last_study": self.last_study,
"close_correctly": self.close_correctly,
"debug": self.debug,
"last_solver_name": self.last_solver_name,
}
for key in data:
@@ -306,6 +317,9 @@ class Config(SQL):
self.last_study = ""
self.close_correctly = False
# Last Solver
self.last_solver_name = ""
# Debug
self.debug = False
@@ -332,6 +346,14 @@ class Config(SQL):
)
self.commit()
def update_last_solver_used(self, solver_name):
self.last_solver_name = solver_name
self.execute(
"UPDATE data SET " +
f"value='{self._db_format(self.last_solver_name)}' " +
"WHERE key='last_solver_name'"
)
@classmethod
def filename(cls):
file = ""
+831 -658
View File
File diff suppressed because it is too large. Load diff
+2
View File
@@ -39,6 +39,7 @@ from Scripts.P3DST import Script3DST
from Scripts.Hello import ScriptHello
from Scripts.ListSolver import ScriptListSolver
from Scripts.Run import ScriptExport, ScriptRun
from Scripts.MageMesh import MageMesh
from init import legal_info, debug_info, setup_lang
@@ -50,6 +51,7 @@ scripts = {
"export": ScriptExport,
"run": ScriptRun,
"3DST": Script3DST,
"mesh": MageMesh,
}
+1
View File
@@ -67,6 +67,7 @@ def logger_color_reset():
return f"{Style.RESET_ALL}"
return ""
def logger_exception(exception):
logger.error(
f"[{Fore.RED}ERROR{Style.RESET_ALL}] " +
+1 -1
View File
@@ -16,7 +16,7 @@ cd ..
echo " PEP8"
pycodestyle ./src
pycodestyle --exclude="*_to_*.py" ./src
if [ $? -eq 0 ]
then