From 83c03c998a9d40b5e01c86e4f136c7ef0662075a Mon Sep 17 00:00:00 2001 From: Theophile Terraz Date: Mon, 16 Sep 2024 11:28:46 +0200 Subject: [PATCH 01/32] debug auto BC without node --- src/View/BoundaryCondition/Edit/Window.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/View/BoundaryCondition/Edit/Window.py b/src/View/BoundaryCondition/Edit/Window.py index 168958bd..67ff4476 100644 --- a/src/View/BoundaryCondition/Edit/Window.py +++ b/src/View/BoundaryCondition/Edit/Window.py @@ -331,6 +331,8 @@ class EditBoundaryConditionWindow(PamhyrWindow): def generate_uniform(self): if self._data.has_node: node = self._data.node + if node is None: + return reach = self._data.reach(self._study.river)[0] profile = reach.profiles[-1] incline = abs(reach.get_incline_median_mean()) @@ -360,6 +362,8 @@ class EditBoundaryConditionWindow(PamhyrWindow): def generate_critical(self): if self._data.has_node: node = self._data.node + if node is None: + return reach = self._data.reach(self._study.river)[0] profile = reach.profiles[-1] z_min = profile.z_min() From 8e6323ae48895629e74ffd62e7860a2c2710a986 Mon Sep 17 00:00:00 2001 From: Theophile Terraz Date: Mon, 16 Sep 2024 11:41:36 +0200 Subject: [PATCH 02/32] debug print --- src/Meshing/Mage.py | 5 +++-- src/Model/Tools/PamhyrDict.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Meshing/Mage.py b/src/Meshing/Mage.py index ce408ac0..dce49464 100644 --- a/src/Meshing/Mage.py +++ b/src/Meshing/Mage.py @@ -397,12 +397,13 @@ class MeshingWithMageMailleurTT(AMeshingTool): logger.info( f"! {self._exe_path()} " + f"{st_file} {m_file} " + - f"update_rk " + + f"update_kp " + f"{str(step)} " + f"{limites[0]} {limites[1]} " + f"{directrices[0]} {directrices[1]} " + f"{orientation} {lm} {linear} " + - f"{origin} " + f"{origin} " + + f"{origin_value} " ) proc.start( self._exe_path(), diff --git a/src/Model/Tools/PamhyrDict.py b/src/Model/Tools/PamhyrDict.py index 93e563eb..30ef98e9 100644 --- a/src/Model/Tools/PamhyrDict.py +++ b/src/Model/Tools/PamhyrDict.py @@ -67,7 +67,7 @@ class PamhyrModelDict(SQLSubModel): if key in self._dict: v = self._dict[key] - if type(v) is types.GeneratorType: + if isinstance(v, types.GeneratorType): return list(v) return v From 8ce85e5d23be66b889983641d11a55313e5e27f1 Mon Sep 17 00:00:00 2001 From: Theophile Terraz Date: Wed, 18 Sep 2024 09:20:57 +0200 Subject: [PATCH 03/32] debugs --- src/Meshing/Mage.py | 2 -- src/View/BoundaryCondition/Edit/Window.py | 4 ++++ src/View/Results/PlotH.py | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Meshing/Mage.py b/src/Meshing/Mage.py index dce49464..fb591292 100644 --- a/src/Meshing/Mage.py +++ b/src/Meshing/Mage.py @@ -381,8 +381,6 @@ class MeshingWithMageMailleurTT(AMeshingTool): st_file = self.export_reach_to_st(reach, tmp) m_file = st_file.rsplit(".ST", 1)[0] + ".M" - os.sync() - proc = QProcess() proc.setWorkingDirectory(tmp) diff --git a/src/View/BoundaryCondition/Edit/Window.py b/src/View/BoundaryCondition/Edit/Window.py index 67ff4476..7a1dae2c 100644 --- a/src/View/BoundaryCondition/Edit/Window.py +++ b/src/View/BoundaryCondition/Edit/Window.py @@ -355,6 +355,8 @@ class EditBoundaryConditionWindow(PamhyrWindow): q = [((profile.wet_width(z_min + h) * 0.8) * strickler * (h ** (5/3)) * (abs(incline) ** (0.5))) for h in height] + for i in range(len(height)): + height[i] += z_min self._table.replace_data(height, q) return @@ -372,5 +374,7 @@ class EditBoundaryConditionWindow(PamhyrWindow): q = [sqrt(9.81 * (profile.wet_area(z_min + h) ** 3) / profile.wet_width(z_min + h)) for h in height] + for i in range(len(height)): + height[i] += z_min self._table.replace_data(height, q) return diff --git a/src/View/Results/PlotH.py b/src/View/Results/PlotH.py index 90aa6674..5920d837 100644 --- a/src/View/Results/PlotH.py +++ b/src/View/Results/PlotH.py @@ -174,12 +174,12 @@ class PlotH(PamhyrPlot): self._line.set_data(x, y) self._current.set_data( - self._current_timestamp, + [self._current_timestamp, self._current_timestamp], self.canvas.axes.get_ylim() ) def update_current(self): self._current.set_data( - self._current_timestamp, + [self._current_timestamp, self._current_timestamp], self.canvas.axes.get_ylim() ) From 7ebb683facbda6f0cde28d82c96c09a66c06d0e2 Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Wed, 18 Sep 2024 14:15:14 +0200 Subject: [PATCH 04/32] RubarBE: Rename 'tps.*' file to 'condin.*'. --- src/Solver/RubarBE.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Solver/RubarBE.py b/src/Solver/RubarBE.py index 5c9f7909..9d2e7bee 100644 --- a/src/Solver/RubarBE.py +++ b/src/Solver/RubarBE.py @@ -143,7 +143,7 @@ class RubarBE(CommandLineSolver): self._export_ts(study, repertory, qlog, name=name) self._export_geomac_i(study, repertory, qlog, name=name) self._export_mail(study, repertory, qlog, name=name) - self._export_tps(study, repertory, qlog, name=name) + self._export_condin(study, repertory, qlog, name=name) self._export_stricklers(study, repertory, qlog, name=name) def _export_donnee(self, study, repertory, qlog, name="0"): @@ -361,13 +361,13 @@ class RubarBE(CommandLineSolver): ind += 1 f.write("\n") - def _export_tps(self, study, repertory, qlog, name="0"): + def _export_condin(self, study, repertory, qlog, name="0"): if qlog is not None: - qlog.put("Export TPS file") + qlog.put("Export CONDIN file") with open( os.path.join( - repertory, f"tps.{name}" + repertory, f"condin.{name}" ), "w+" ) as f: for edge in study.river.enable_edges(): @@ -376,7 +376,7 @@ class RubarBE(CommandLineSolver): f.write(f"0.0\n") ics = study.river.initial_conditions.get(edge) - data = self._export_tps_init_data(ics) + data = self._export_condin_init_data(ics) profiles = reach.profiles first = profiles[0] @@ -388,8 +388,8 @@ class RubarBE(CommandLineSolver): ) return - f_h_s = self._export_tps_profile_height_speed(first, data) - l_h_s = self._export_tps_profile_height_speed(last, data) + f_h_s = self._export_condin_profile_height_speed(first, data) + l_h_s = self._export_condin_profile_height_speed(last, data) # First mail f.write(f"{1:>5} {f_h_s[0]} {f_h_s[1]}") @@ -404,7 +404,7 @@ class RubarBE(CommandLineSolver): ind += 1 continue - cur_h, cur_s = self._export_tps_profile_height_speed( + cur_h, cur_s = self._export_condin_profile_height_speed( profile, data ) @@ -420,7 +420,7 @@ class RubarBE(CommandLineSolver): # Last mail f.write(f"{ind:>5} {f_h_s[0]} {f_h_s[1]}") - def _export_tps_init_data(self, ics): + def _export_condin_init_data(self, ics): data = {} for d in ics.data: @@ -431,7 +431,7 @@ class RubarBE(CommandLineSolver): return data - def _export_tps_profile_height_speed(self, profile, data): + def _export_condin_profile_height_speed(self, profile, data): z = data[profile.rk][0] q = data[profile.rk][1] From c698f2af2b3b80df7f30dd94d268b7f946e8379a Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Wed, 18 Sep 2024 16:05:40 +0200 Subject: [PATCH 05/32] RubarBE (Rubar3): Add minimal rubar3. --- src/Solver/RubarBE.py | 104 ++++++++++++++++++++++++++++++++++-------- src/Solver/Solvers.py | 4 +- 2 files changed, 89 insertions(+), 19 deletions(-) diff --git a/src/Solver/RubarBE.py b/src/Solver/RubarBE.py index 9d2e7bee..3ad4723e 100644 --- a/src/Solver/RubarBE.py +++ b/src/Solver/RubarBE.py @@ -30,13 +30,13 @@ from Model.Results.River.River import River, Reach, Profile logger = logging.getLogger() -class RubarBE(CommandLineSolver): - _type = "rubarbe" +class Rubar3(CommandLineSolver): + _type = "rubar3" def __init__(self, name): - super(RubarBE, self).__init__(name) + super(Rubar3, self).__init__(name) - self._type = "rubarbe" + self._type = "rubar3" self._cmd_input = "" self._cmd_solver = "@path @input -o @output" @@ -44,9 +44,9 @@ class RubarBE(CommandLineSolver): @classmethod def default_parameters(cls): - lst = super(RubarBE, cls).default_parameters() + # lst = super(Rubar3, cls).default_parameters() - lst += [ + lst = [ ("rubarbe_cfl", "0.50000E+00"), ("rubarbe_condam", "1"), ("rubarbe_condav", "3"), @@ -60,10 +60,10 @@ class RubarBE(CommandLineSolver): ("rubarbe_tinit", "000:00:00:00"), ("rubarbe_tmax", "999:99:99:00"), ("rubarbe_tiopdt", "000:00:00:00"), - ("rubarbe_dt", "3000.0"), + ("rubarbe_dt", "5.0"), ("rubarbe_ts", "999:99:99:00"), - ("rubarbe_dtsauv", "999:99:99:00"), - ("rubarbe_psave", "999:99:99:00"), + ("rubarbe_dtsauv", "00:00:00:05"), + ("rubarbe_psave", "00:00:00:05"), ("rubarbe_fdeb1", "1"), ("rubarbe_fdeb2", "10"), ("rubarbe_fdeb3", "100"), @@ -119,21 +119,21 @@ class RubarBE(CommandLineSolver): ########## def cmd_args(self, study): - lst = super(RubarBE, self).cmd_args(study) + lst = super(Rubar3, self).cmd_args(study) return lst def input_param(self): name = self._study.name - return f"{name}.REP" + return f"{name}" def output_param(self): name = self._study.name - return f"{name}.BIN" + return f"{name}" def log_file(self): name = self._study.name - return f"{name}.TRA" + return f"{name}" def export(self, study, repertory, qlog=None): self._study = study @@ -145,6 +145,10 @@ class RubarBE(CommandLineSolver): self._export_mail(study, repertory, qlog, name=name) self._export_condin(study, repertory, qlog, name=name) self._export_stricklers(study, repertory, qlog, name=name) + self._export_hydro(study, repertory, qlog, name=name) + self._export_condav(study, repertory, qlog, name=name) + + return True def _export_donnee(self, study, repertory, qlog, name="0"): if qlog is not None: @@ -168,6 +172,9 @@ class RubarBE(CommandLineSolver): name = param.name value = param.value + if "all_" in name: + continue + if value != "": # Value format if value.count(':') == 3: @@ -276,7 +283,9 @@ class RubarBE(CommandLineSolver): if label[0] == "r": label = label[1].upper() else: - label = lable[0] + label = label[1].upper() + else: + label = " " y = point.y z = point.z @@ -392,7 +401,7 @@ class RubarBE(CommandLineSolver): l_h_s = self._export_condin_profile_height_speed(last, data) # First mail - f.write(f"{1:>5} {f_h_s[0]} {f_h_s[1]}") + f.write(f"{1:>5} {f_h_s[0]} {f_h_s[1]}\n") ind = 2 it = iter(profiles) @@ -418,7 +427,7 @@ class RubarBE(CommandLineSolver): ind += 1 # Last mail - f.write(f"{ind:>5} {f_h_s[0]} {f_h_s[1]}") + f.write(f"{ind:>5} {f_h_s[0]} {f_h_s[1]}\n") def _export_condin_init_data(self, ics): data = {} @@ -435,7 +444,66 @@ class RubarBE(CommandLineSolver): z = data[profile.rk][0] q = data[profile.rk][1] - height = z - profile.z_min() + #height = z - profile.z_min() speed = profile.speed(q, z) - return height, speed + return z, speed + + def _export_hydro(self, study, repertory, qlog, name="0"): + if qlog is not None: + qlog.put("Export HYDRO file") + + with open( + os.path.join( + repertory, f"hydro.{name}" + ), "w+" + ) as f: + bcs = [] + for edge in study.river.enable_edges(): + for bound in study.river.boundary_condition.get_tab("liquid"): + # BC is an hydrogramme + if bound.bctype == "TD" or bound.bctype == "PC": + # BC is on input node of this reach + if bound.node == edge.node1: + bcs.append(bound) + + for bc in bcs: + f.write(f"{len(bc)}\n") + for d0, d1 in bc.data: + f.write(f"{d0} {d1}\n") + + def _export_condav(self, study, repertory, qlog, name="0"): + if qlog is not None: + qlog.put("Export CONDAV file") + + with open( + os.path.join( + repertory, f"condav.{name}" + ), "w+" + ) as f: + bcs = [] + for edge in study.river.enable_edges(): + for bound in study.river.boundary_condition.get_tab("liquid"): + # BC is an hydrogramme + if bound.bctype == "ZD" or bound.bctype == "TZ": + # BC is on input node of this reach + if bound.node == edge.node2: + bcs.append(bound) + + for bc in bcs: + f.write(f"{len(bc)}\n") + for d0, d1 in bc.data: + f.write(f"{d0} {d1}\n") + + +class RubarBE(Rubar3): + _type = "rubarbe" + + def __init__(self, name): + super(RubarBE, self).__init__(name) + + self._type = "rubarbe" + + self._cmd_input = "" + self._cmd_solver = "@path @input -o @output" + self._cmd_output = "" diff --git a/src/Solver/Solvers.py b/src/Solver/Solvers.py index cd336e68..d0a8a981 100644 --- a/src/Solver/Solvers.py +++ b/src/Solver/Solvers.py @@ -22,7 +22,7 @@ from Solver.GenericSolver import GenericSolver from Solver.Mage import ( Mage7, Mage8, MageFake7, ) -from Solver.RubarBE import RubarBE +from Solver.RubarBE import Rubar3, RubarBE _translate = QCoreApplication.translate @@ -32,6 +32,7 @@ solver_long_name = { "mage8": "Mage v8", # "mage_fake7": "Mage fake v7", # "rubarbe": "RubarBE", + # "rubar3": "Rubar3", } solver_type_list = { @@ -40,4 +41,5 @@ solver_type_list = { "mage8": Mage8, # "mage_fake7": MageFake7, # "rubarbe": RubarBE, + # "rubar3": Rubar3, } From 8250efcd6202c1ca7ac5a1b9a3cd16699ec6faa2 Mon Sep 17 00:00:00 2001 From: Theophile Terraz Date: Wed, 18 Sep 2024 17:22:26 +0200 Subject: [PATCH 06/32] add generate constant height in IC --- .../InitialConditions/InitialConditions.py | 23 ++++- src/View/InitialConditions/DialogDepth.py | 56 +++++++++++ src/View/InitialConditions/DialogHeight.py | 4 +- src/View/InitialConditions/UndoCommand.py | 6 +- src/View/InitialConditions/Window.py | 22 ++++- src/View/ui/InitialConditions.ui | 13 ++- ...nitialConditions_Dialog_Generator_Depth.ui | 95 +++++++++++++++++++ ...itialConditions_Dialog_Generator_Height.ui | 12 +-- 8 files changed, 205 insertions(+), 26 deletions(-) create mode 100644 src/View/InitialConditions/DialogDepth.py create mode 100644 src/View/ui/InitialConditions_Dialog_Generator_Depth.ui diff --git a/src/Model/InitialConditions/InitialConditions.py b/src/Model/InitialConditions/InitialConditions.py index a4becbae..c16c87ac 100644 --- a/src/Model/InitialConditions/InitialConditions.py +++ b/src/Model/InitialConditions/InitialConditions.py @@ -374,8 +374,8 @@ class InitialConditions(SQLSubModel): key=lambda p: p.rk ) - def generate_growing_constante_height(self, height: float, - compute_discharge: bool): + def generate_growing_constant_depth(self, height: float, + compute_discharge: bool): profiles = self._reach.reach.profiles.copy() self._sort_by_z_and_rk(profiles) @@ -492,6 +492,25 @@ class InitialConditions(SQLSubModel): self._generate_resort_data(profiles) + def generate_height(self, elevation: float): + profiles = self._reach.reach.profiles.copy() + data_discharge = {} + if len(self._data) == 0: + for profile in profiles: + data_discharge[profile.rk] = 0.0 + else: + for data in self._data: + data_discharge[data["rk"]] = data["discharge"] + self._data = [] + for profile in profiles: + new = Data(reach=self._reach, status=self._status) + new["rk"] = profile.rk + new["discharge"] = data_discharge[profile.rk] + new["elevation"] = elevation + self._data.append(new) + + self._generate_resort_data(profiles) + def _generate_resort_data(self, profiles): is_reverse = False if profiles[0].rk > profiles[-1].rk: diff --git a/src/View/InitialConditions/DialogDepth.py b/src/View/InitialConditions/DialogDepth.py new file mode 100644 index 00000000..67c89ca0 --- /dev/null +++ b/src/View/InitialConditions/DialogDepth.py @@ -0,0 +1,56 @@ +# DialogDepth.py -- Pamhyr +# Copyright (C) 2023-2024 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 . + +# -*- coding: utf-8 -*- + +from View.Tools.PamhyrWindow import PamhyrDialog + +from PyQt5.QtGui import ( + QKeySequence, +) + +from PyQt5.QtCore import ( + Qt, QVariant, QAbstractTableModel, +) + +from PyQt5.QtWidgets import ( + QDialogButtonBox, QComboBox, QUndoStack, QShortcut, + QDoubleSpinBox, QCheckBox, +) + + +class DepthDialog(PamhyrDialog): + _pamhyr_ui = "InitialConditions_Dialog_Generator_Depth" + _pamhyr_name = "Depth" + + def __init__(self, trad=None, parent=None): + super(DepthDialog, self).__init__( + title=trad[self._pamhyr_name], + options=[], + trad=trad, + parent=parent + ) + + self.value = None + self.option = None + + def accept(self): + self.value = self.find(QDoubleSpinBox, "doubleSpinBox").value() + self.option = self.find(QCheckBox, "checkBox").isChecked() + super().accept() + + def reject(self): + self.close() diff --git a/src/View/InitialConditions/DialogHeight.py b/src/View/InitialConditions/DialogHeight.py index d4e541aa..e835716d 100644 --- a/src/View/InitialConditions/DialogHeight.py +++ b/src/View/InitialConditions/DialogHeight.py @@ -28,7 +28,7 @@ from PyQt5.QtCore import ( from PyQt5.QtWidgets import ( QDialogButtonBox, QComboBox, QUndoStack, QShortcut, - QDoubleSpinBox, QCheckBox, + QDoubleSpinBox ) @@ -45,11 +45,9 @@ class HeightDialog(PamhyrDialog): ) self.value = None - self.option = None def accept(self): self.value = self.find(QDoubleSpinBox, "doubleSpinBox").value() - self.option = self.find(QCheckBox, "checkBox").isChecked() super().accept() def reject(self): diff --git a/src/View/InitialConditions/UndoCommand.py b/src/View/InitialConditions/UndoCommand.py index 2331d9bc..b461e643 100644 --- a/src/View/InitialConditions/UndoCommand.py +++ b/src/View/InitialConditions/UndoCommand.py @@ -188,8 +188,10 @@ class GenerateCommand(QUndoCommand): def redo(self): if self._generator == "growing": - self._ics.generate_growing_constante_height(self._param, - self._option) + self._ics.generate_growing_constant_depth(self._param, + self._option) elif self._generator == "discharge": self._ics.generate_discharge(self._param, self._option) + elif self._generator == "height": + self._ics.generate_height(self._param) diff --git a/src/View/InitialConditions/Window.py b/src/View/InitialConditions/Window.py index e6563169..739f4974 100644 --- a/src/View/InitialConditions/Window.py +++ b/src/View/InitialConditions/Window.py @@ -58,6 +58,7 @@ from View.Tools.Plot.PamhyrToolbar import PamhyrPlotToolbar from View.InitialConditions.PlotDRK import PlotDRK from View.InitialConditions.PlotDischarge import PlotDischarge from View.InitialConditions.translate import ICTranslate +from View.InitialConditions.DialogDepth import DepthDialog from View.InitialConditions.DialogHeight import HeightDialog from View.InitialConditions.DialogDischarge import DischargeDialog from View.Results.ReadingResultsDialog import ReadingResultsDialog @@ -174,13 +175,17 @@ class InitialConditionsWindow(PamhyrWindow): .connect(self.import_from_file) self.find(QPushButton, "pushButton_generate_1").clicked.connect( - self.generate_growing_constante_height + self.generate_growing_constant_depth ) self.find(QPushButton, "pushButton_generate_2").clicked.connect( self.generate_discharge ) + self.find(QPushButton, "pushButton_generate_3").clicked.connect( + self.generate_height + ) + self._table.dataChanged.connect(self._update_plot) def index_selected_row(self): @@ -345,8 +350,8 @@ class InitialConditionsWindow(PamhyrWindow): self._table.redo() self._update() - def generate_growing_constante_height(self): - dlg = HeightDialog(trad=self._trad, parent=self) + def generate_growing_constant_depth(self): + dlg = DepthDialog(trad=self._trad, parent=self) if dlg.exec(): value = dlg.value compute_discharge = dlg.option @@ -357,6 +362,13 @@ class InitialConditionsWindow(PamhyrWindow): dlg = DischargeDialog(trad=self._trad, parent=self) if dlg.exec(): value = dlg.value - compute_height = dlg.option - self._table.generate("discharge", value, compute_height) + compute_depth = dlg.option + self._table.generate("discharge", value, compute_depth) + self._update() + + def generate_height(self): + dlg = HeightDialog(trad=self._trad, parent=self) + if dlg.exec(): + value = dlg.value + self._table.generate("height", value, None) self._update() diff --git a/src/View/ui/InitialConditions.ui b/src/View/ui/InitialConditions.ui index 68ed2f94..e0268872 100644 --- a/src/View/ui/InitialConditions.ui +++ b/src/View/ui/InitialConditions.ui @@ -6,7 +6,7 @@ 0 0 - 1024 + 849 576 @@ -27,7 +27,7 @@ - Generate height + Generate depth @@ -38,6 +38,13 @@ + + + + Generate elevation + + + @@ -65,7 +72,7 @@ 0 0 - 1024 + 849 22 diff --git a/src/View/ui/InitialConditions_Dialog_Generator_Depth.ui b/src/View/ui/InitialConditions_Dialog_Generator_Depth.ui new file mode 100644 index 00000000..deb89169 --- /dev/null +++ b/src/View/ui/InitialConditions_Dialog_Generator_Depth.ui @@ -0,0 +1,95 @@ + + + Dialog + + + + 0 + 0 + 284 + 107 + + + + Dialog + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + Depth (m) + + + + + + + 1000000.000000000000000 + + + + + + + + + Generate discharge + + + true + + + + + + + + + buttonBox + accepted() + Dialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + Dialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/View/ui/InitialConditions_Dialog_Generator_Height.ui b/src/View/ui/InitialConditions_Dialog_Generator_Height.ui index 500a2b0b..40e9cd9b 100644 --- a/src/View/ui/InitialConditions_Dialog_Generator_Height.ui +++ b/src/View/ui/InitialConditions_Dialog_Generator_Height.ui @@ -17,7 +17,7 @@ - + Qt::Horizontal @@ -45,16 +45,6 @@ - - - - Generate discharge - - - true - - - From fe4ae467840aaf230a33c4fea5b1769010650a75 Mon Sep 17 00:00:00 2001 From: Theophile Terraz Date: Thu, 19 Sep 2024 10:03:44 +0200 Subject: [PATCH 07/32] work on initial conditions --- .../InitialConditions/InitialConditions.py | 35 +++++--- src/View/InitialConditions/DialogHeight.py | 23 +++++- src/View/InitialConditions/UndoCommand.py | 5 +- src/View/InitialConditions/Window.py | 3 +- ...itialConditions_Dialog_Generator_Height.ui | 82 ++++++++++++++++--- src/View/ui/Results.ui | 2 +- 6 files changed, 121 insertions(+), 29 deletions(-) diff --git a/src/Model/InitialConditions/InitialConditions.py b/src/Model/InitialConditions/InitialConditions.py index c16c87ac..82756070 100644 --- a/src/Model/InitialConditions/InitialConditions.py +++ b/src/Model/InitialConditions/InitialConditions.py @@ -21,6 +21,7 @@ import logging from copy import copy, deepcopy from tools import trace, timer from functools import reduce +from numpy import interp from Model.Tools.PamhyrDB import SQLSubModel @@ -492,25 +493,39 @@ class InitialConditions(SQLSubModel): self._generate_resort_data(profiles) - def generate_height(self, elevation: float): + def generate_height(self, + elevation1: float, + elevation2: float, + compute_discharge: bool, + discharge: float): profiles = self._reach.reach.profiles.copy() + upstream_rk = profiles[0].rk + downstream_rk = profiles[-1].rk data_discharge = {} - if len(self._data) == 0: - for profile in profiles: - data_discharge[profile.rk] = 0.0 - else: - for data in self._data: - data_discharge[data["rk"]] = data["discharge"] + if not compute_discharge: + if len(self._data) == 0: + for profile in profiles: + data_discharge[profile.rk] = 0.0 + else: + for data in self._data: + data_discharge[data["rk"]] = data["discharge"] + self._data = [] for profile in profiles: + + if not compute_discharge: + d = data_discharge[profile.rk] + else: + d = discharge + elevation = interp(profile.rk, + [upstream_rk, downstream_rk], + [elevation1, elevation2]) new = Data(reach=self._reach, status=self._status) new["rk"] = profile.rk - new["discharge"] = data_discharge[profile.rk] + new["discharge"] = d new["elevation"] = elevation self._data.append(new) - self._generate_resort_data(profiles) - def _generate_resort_data(self, profiles): is_reverse = False if profiles[0].rk > profiles[-1].rk: diff --git a/src/View/InitialConditions/DialogHeight.py b/src/View/InitialConditions/DialogHeight.py index e835716d..f65bf24f 100644 --- a/src/View/InitialConditions/DialogHeight.py +++ b/src/View/InitialConditions/DialogHeight.py @@ -28,7 +28,7 @@ from PyQt5.QtCore import ( from PyQt5.QtWidgets import ( QDialogButtonBox, QComboBox, QUndoStack, QShortcut, - QDoubleSpinBox + QDoubleSpinBox, QCheckBox, QLabel ) @@ -44,10 +44,27 @@ class HeightDialog(PamhyrDialog): parent=parent ) - self.value = None + self.value = [None, None, None] + self.option = None + self.find(QCheckBox, "checkBox").clicked.connect( + self.enable_discharge + ) + + def enable_discharge(self): + cb = self.find(QCheckBox, "checkBox") + dsb = self.find(QDoubleSpinBox, "doubleSpinBox_3") + l = self.find(QLabel, "label_3") + dsb.setEnabled(cb.isChecked()) + l.setEnabled(cb.isChecked()) def accept(self): - self.value = self.find(QDoubleSpinBox, "doubleSpinBox").value() + self.value[0] = self.find(QDoubleSpinBox, "doubleSpinBox_1").value() + self.value[1] = self.find(QDoubleSpinBox, "doubleSpinBox_2").value() + self.option = self.find(QCheckBox, "checkBox").isChecked() + if self.option: + self.value[2] = self.find(QDoubleSpinBox, "doubleSpinBox_3").value() + else: + self.value[2] = None super().accept() def reject(self): diff --git a/src/View/InitialConditions/UndoCommand.py b/src/View/InitialConditions/UndoCommand.py index b461e643..64fb1a37 100644 --- a/src/View/InitialConditions/UndoCommand.py +++ b/src/View/InitialConditions/UndoCommand.py @@ -194,4 +194,7 @@ class GenerateCommand(QUndoCommand): self._ics.generate_discharge(self._param, self._option) elif self._generator == "height": - self._ics.generate_height(self._param) + self._ics.generate_height(self._param[0], + self._param[1], + self._option, + self._param[2]) diff --git a/src/View/InitialConditions/Window.py b/src/View/InitialConditions/Window.py index 739f4974..5c6589e6 100644 --- a/src/View/InitialConditions/Window.py +++ b/src/View/InitialConditions/Window.py @@ -370,5 +370,6 @@ class InitialConditionsWindow(PamhyrWindow): dlg = HeightDialog(trad=self._trad, parent=self) if dlg.exec(): value = dlg.value - self._table.generate("height", value, None) + compute_discharge = dlg.option + self._table.generate("height", value, compute_discharge) self._update() diff --git a/src/View/ui/InitialConditions_Dialog_Generator_Height.ui b/src/View/ui/InitialConditions_Dialog_Generator_Height.ui index 40e9cd9b..ee015bd1 100644 --- a/src/View/ui/InitialConditions_Dialog_Generator_Height.ui +++ b/src/View/ui/InitialConditions_Dialog_Generator_Height.ui @@ -6,8 +6,8 @@ 0 0 - 284 - 80 + 396 + 182 @@ -17,7 +17,70 @@ + + + + + + Upstream Height (m) + + + + + + + -1000000.000000000000000 + + + 1000000.000000000000000 + + + + + + + + + + + false + + + Discharge + + + + + + + false + + + + + + + + + + Downstream Height (m) + + + + + + + -1000000.000000000000000 + + + 0.000000000000000 + + + + + + Qt::Horizontal @@ -27,19 +90,12 @@ - - + + - + - Height (m) - - - - - - - 1000000.000000000000000 + Generate Constant Discharge diff --git a/src/View/ui/Results.ui b/src/View/ui/Results.ui index 256ab368..1b4a93ee 100644 --- a/src/View/ui/Results.ui +++ b/src/View/ui/Results.ui @@ -132,7 +132,7 @@ - 0 + 1 true From 3814f9a1c91a82c78e4cd5add68631c83f111cfd Mon Sep 17 00:00:00 2001 From: Theophile Terraz Date: Thu, 19 Sep 2024 10:35:24 +0200 Subject: [PATCH 08/32] keep previons settings in generate IC dialogs --- src/View/InitialConditions/DialogDepth.py | 14 ++++-- src/View/InitialConditions/DialogDischarge.py | 14 ++++-- src/View/InitialConditions/DialogHeight.py | 36 +++++++------- src/View/InitialConditions/Window.py | 48 ++++++++++++++----- 4 files changed, 73 insertions(+), 39 deletions(-) diff --git a/src/View/InitialConditions/DialogDepth.py b/src/View/InitialConditions/DialogDepth.py index 67c89ca0..53abfe39 100644 --- a/src/View/InitialConditions/DialogDepth.py +++ b/src/View/InitialConditions/DialogDepth.py @@ -36,7 +36,7 @@ class DepthDialog(PamhyrDialog): _pamhyr_ui = "InitialConditions_Dialog_Generator_Depth" _pamhyr_name = "Depth" - def __init__(self, trad=None, parent=None): + def __init__(self, value, option, trad=None, parent=None): super(DepthDialog, self).__init__( title=trad[self._pamhyr_name], options=[], @@ -44,12 +44,16 @@ class DepthDialog(PamhyrDialog): parent=parent ) - self.value = None - self.option = None + self.value = value + self.option = option + self.sb = self.find(QDoubleSpinBox, "doubleSpinBox") + self.sb.setValue(self.value) + self.cb = self.find(QCheckBox, "checkBox") + self.cb.setChecked(self.option) def accept(self): - self.value = self.find(QDoubleSpinBox, "doubleSpinBox").value() - self.option = self.find(QCheckBox, "checkBox").isChecked() + self.value = self.sb.value() + self.option = self.cb.isChecked() super().accept() def reject(self): diff --git a/src/View/InitialConditions/DialogDischarge.py b/src/View/InitialConditions/DialogDischarge.py index 3f0a83bb..c1947950 100644 --- a/src/View/InitialConditions/DialogDischarge.py +++ b/src/View/InitialConditions/DialogDischarge.py @@ -36,7 +36,7 @@ class DischargeDialog(PamhyrDialog): _pamhyr_ui = "InitialConditions_Dialog_Generator_Discharge" _pamhyr_name = "Discharge" - def __init__(self, title="Discharge", trad=None, parent=None): + def __init__(self, value, option, title="Discharge", trad=None, parent=None): super(DischargeDialog, self).__init__( title=trad[self._pamhyr_name], options=[], @@ -44,12 +44,16 @@ class DischargeDialog(PamhyrDialog): parent=parent ) - self.value = None - self.option = None + self.value = value + self.option = option + self.sb = self.find(QDoubleSpinBox, "doubleSpinBox") + self.sb.setValue(self.value) + self.cb = self.find(QCheckBox, "checkBox") + self.cb.setChecked(self.option) def accept(self): - self.value = self.find(QDoubleSpinBox, "doubleSpinBox").value() - self.option = self.find(QCheckBox, "checkBox").isChecked() + self.value = self.sb.value() + self.option = self.cb.isChecked() super().accept() def reject(self): diff --git a/src/View/InitialConditions/DialogHeight.py b/src/View/InitialConditions/DialogHeight.py index f65bf24f..41683855 100644 --- a/src/View/InitialConditions/DialogHeight.py +++ b/src/View/InitialConditions/DialogHeight.py @@ -36,7 +36,7 @@ class HeightDialog(PamhyrDialog): _pamhyr_ui = "InitialConditions_Dialog_Generator_Height" _pamhyr_name = "Height" - def __init__(self, trad=None, parent=None): + def __init__(self, values, option, trad=None, parent=None): super(HeightDialog, self).__init__( title=trad[self._pamhyr_name], options=[], @@ -44,27 +44,29 @@ class HeightDialog(PamhyrDialog): parent=parent ) - self.value = [None, None, None] - self.option = None - self.find(QCheckBox, "checkBox").clicked.connect( - self.enable_discharge - ) + self.values = values + self.option = option + self.sb1 = self.find(QDoubleSpinBox, "doubleSpinBox_1") + self.sb1.setValue(self.values[0]) + self.sb2 = self.find(QDoubleSpinBox, "doubleSpinBox_2") + self.sb2.setValue(self.values[1]) + self.sb3 = self.find(QDoubleSpinBox, "doubleSpinBox_3") + self.sb3.setValue(self.values[2]) + self.cb = self.find(QCheckBox, "checkBox") + self.cb.setChecked(self.option) + self.enable_discharge() + self.cb.clicked.connect(self.enable_discharge) def enable_discharge(self): - cb = self.find(QCheckBox, "checkBox") - dsb = self.find(QDoubleSpinBox, "doubleSpinBox_3") l = self.find(QLabel, "label_3") - dsb.setEnabled(cb.isChecked()) - l.setEnabled(cb.isChecked()) + self.sb3.setEnabled(self.cb.isChecked()) + l.setEnabled(self.cb.isChecked()) def accept(self): - self.value[0] = self.find(QDoubleSpinBox, "doubleSpinBox_1").value() - self.value[1] = self.find(QDoubleSpinBox, "doubleSpinBox_2").value() - self.option = self.find(QCheckBox, "checkBox").isChecked() - if self.option: - self.value[2] = self.find(QDoubleSpinBox, "doubleSpinBox_3").value() - else: - self.value[2] = None + self.values[0] = self.sb1.value() + self.values[1] = self.sb2.value() + self.values[2] = self.sb3.value() + self.option = self.cb.isChecked() super().accept() def reject(self): diff --git a/src/View/InitialConditions/Window.py b/src/View/InitialConditions/Window.py index 5c6589e6..d8484aba 100644 --- a/src/View/InitialConditions/Window.py +++ b/src/View/InitialConditions/Window.py @@ -104,6 +104,7 @@ class InitialConditionsWindow(PamhyrWindow): self.setup_table() self.setup_plot() self.setup_connections() + self.setub_dialogs() self.ui.setWindowTitle(self._title) @@ -188,6 +189,14 @@ class InitialConditionsWindow(PamhyrWindow): self._table.dataChanged.connect(self._update_plot) + def setub_dialogs(self): + self.height_values = [0.0, 0.0, 0.0] + self.height_option = True + self.discharge_value = 0.0 + self.discharge_option = True + self.depth_value = 0.0 + self.depth_option = True + def index_selected_row(self): table = self.find(QTableView, f"tableView") rows = table.selectionModel()\ @@ -351,25 +360,40 @@ class InitialConditionsWindow(PamhyrWindow): self._update() def generate_growing_constant_depth(self): - dlg = DepthDialog(trad=self._trad, parent=self) + dlg = DepthDialog(self.depth_value, + self.depth_option, + trad=self._trad, + parent=self) if dlg.exec(): - value = dlg.value - compute_discharge = dlg.option - self._table.generate("growing", value, compute_discharge) + self.depth_value = dlg.value + self.depth_option = dlg.option + self._table.generate("growing", + self.depth_value, + self.depth_option) self._update() def generate_discharge(self): - dlg = DischargeDialog(trad=self._trad, parent=self) + dlg = DischargeDialog(self.discharge_value, + self.discharge_option, + trad=self._trad, + parent=self) if dlg.exec(): - value = dlg.value - compute_depth = dlg.option - self._table.generate("discharge", value, compute_depth) + self.discharge_value = dlg.value + self.discharge_option = dlg.option + self._table.generate("discharge", + self.discharge_value, + self.discharge_option) self._update() def generate_height(self): - dlg = HeightDialog(trad=self._trad, parent=self) + dlg = HeightDialog(self.height_values, + self.height_option, + trad=self._trad, + parent=self) if dlg.exec(): - value = dlg.value - compute_discharge = dlg.option - self._table.generate("height", value, compute_discharge) + self.height_values = dlg.values + self.height_option = dlg.option + self._table.generate("height", + self.height_values, + self.height_option) self._update() From 0b78522ca5d7bbf735345badaea0a3e9954edb2d Mon Sep 17 00:00:00 2001 From: Theophile Terraz Date: Thu, 19 Sep 2024 10:44:19 +0200 Subject: [PATCH 09/32] keep previons settings in generate BC dialog --- src/View/BoundaryCondition/Edit/Window.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/View/BoundaryCondition/Edit/Window.py b/src/View/BoundaryCondition/Edit/Window.py index 7a1dae2c..34c9e503 100644 --- a/src/View/BoundaryCondition/Edit/Window.py +++ b/src/View/BoundaryCondition/Edit/Window.py @@ -133,6 +133,7 @@ class EditBoundaryConditionWindow(PamhyrWindow): self.setup_plot() self.setup_data() self.setup_connections() + self.setup_dialog() def setup_data(self): self._is_solid = self._data.bctype == "SL" @@ -217,6 +218,10 @@ class EditBoundaryConditionWindow(PamhyrWindow): self._d50sigma.d50Changed.connect(self.d50_changed) self._d50sigma.sigmaChanged.connect(self.sigma_changed) + def setup_dialog(self): + reach = self._data.reach(self._study.river)[0] + self.slope_value = abs(reach.get_incline_median_mean()) + def d50_changed(self, value): self._undo_stack.push( SetMetaDataCommand( @@ -335,13 +340,12 @@ class EditBoundaryConditionWindow(PamhyrWindow): return reach = self._data.reach(self._study.river)[0] profile = reach.profiles[-1] - incline = abs(reach.get_incline_median_mean()) - dlg = GenerateDialog(incline, + dlg = GenerateDialog(self.slope_value, reach, trad=self._trad, parent=self) if dlg.exec(): - incline = dlg.value + self.slope_value = dlg.value frictions = reach._parent.frictions.frictions z_min = profile.z_min() z_max = profile.z_max() @@ -353,7 +357,7 @@ class EditBoundaryConditionWindow(PamhyrWindow): strickler = 25.0 height = [(i)*(z_max-z_min)/50 for i in range(51)] q = [((profile.wet_width(z_min + h) * 0.8) * strickler - * (h ** (5/3)) * (abs(incline) ** (0.5))) + * (h ** (5/3)) * (abs(self.slope_value) ** (0.5))) for h in height] for i in range(len(height)): height[i] += z_min From f755b7ce742ceb773d437be5264e34618470fc10 Mon Sep 17 00:00:00 2001 From: Theophile Terraz Date: Thu, 19 Sep 2024 11:39:06 +0200 Subject: [PATCH 10/32] add make increasing button in boundary condition rating curve --- src/View/BoundaryCondition/Edit/Window.py | 23 +++++++++++++++++++ src/View/ui/EditBoundaryConditions.ui | 9 ++++++++ ...itialConditions_Dialog_Generator_Height.ui | 6 ++--- 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/src/View/BoundaryCondition/Edit/Window.py b/src/View/BoundaryCondition/Edit/Window.py index 34c9e503..dba64da5 100644 --- a/src/View/BoundaryCondition/Edit/Window.py +++ b/src/View/BoundaryCondition/Edit/Window.py @@ -204,12 +204,17 @@ class EditBoundaryConditionWindow(PamhyrWindow): self.find(QAction, "action_generate_critical").triggered.connect( self.generate_critical ) + self.find(QAction, "action_increasing").triggered.connect( + self.make_increasing + ) if self._data.bctype != "ZD" or not self._data.has_node: self.find(QAction, "action_generate_uniform").setVisible(False) self.find(QAction, "action_generate_critical").setVisible(False) + self.find(QAction, "action_increasing").setVisible(False) else: self.find(QAction, "action_generate_uniform").setVisible(True) self.find(QAction, "action_generate_critical").setVisible(True) + self.find(QAction, "action_increasing").setVisible(True) self._table.dataChanged.connect(self.update) self._table.layoutChanged.connect(self.update) @@ -382,3 +387,21 @@ class EditBoundaryConditionWindow(PamhyrWindow): height[i] += z_min self._table.replace_data(height, q) return + + def make_increasing(self): + if self._data.has_node: + node = self._data.node + if node is None: + return + if len(self._table._data) < 2: + return + h = [self._data.get_i(0)[0]] + q = [self._data.get_i(0)[1]] + for i in range(len(self._table._data)): + if i == 0: + continue + row = self._data.get_i(i) + if row[1] > q[-1]: + h.append(row[0]) + q.append(row[1]) + self._table.replace_data(h, q) diff --git a/src/View/ui/EditBoundaryConditions.ui b/src/View/ui/EditBoundaryConditions.ui index c2c81378..f24120f4 100644 --- a/src/View/ui/EditBoundaryConditions.ui +++ b/src/View/ui/EditBoundaryConditions.ui @@ -75,6 +75,7 @@ + @@ -140,6 +141,14 @@ Generate rating curve as Q(z) = Sqrt(g*S(z)^3/L(z)) + + + Make increasing + + + Remove points to make the curve increasing + + diff --git a/src/View/ui/InitialConditions_Dialog_Generator_Height.ui b/src/View/ui/InitialConditions_Dialog_Generator_Height.ui index ee015bd1..414c3d12 100644 --- a/src/View/ui/InitialConditions_Dialog_Generator_Height.ui +++ b/src/View/ui/InitialConditions_Dialog_Generator_Height.ui @@ -22,7 +22,7 @@ - Upstream Height (m) + Upstream height (m) @@ -64,7 +64,7 @@ - Downstream Height (m) + Downstream height (m) @@ -95,7 +95,7 @@ - Generate Constant Discharge + Generate constant discharge From 9d01ab0e16e7cd4394a512cc54953b5879935fb1 Mon Sep 17 00:00:00 2001 From: Theophile Terraz Date: Fri, 20 Sep 2024 14:02:12 +0200 Subject: [PATCH 11/32] add custom plot export to CSV --- src/View/InitialConditions/Window.py | 1 + .../CustomPlotValuesSelectionDialog.py | 3 + src/View/Results/Window.py | 222 ++++++++++++++---- src/View/Tools/ASubWindow.py | 13 +- .../ui/CustomPlotValuesSelectionDialog.ui | 60 +++-- 5 files changed, 222 insertions(+), 77 deletions(-) diff --git a/src/View/InitialConditions/Window.py b/src/View/InitialConditions/Window.py index d8484aba..649e3459 100644 --- a/src/View/InitialConditions/Window.py +++ b/src/View/InitialConditions/Window.py @@ -263,6 +263,7 @@ class InitialConditionsWindow(PamhyrWindow): workdir = os.path.dirname(self._study.filename) return self.file_dialog( + select_file=True, callback=lambda d: self._import_from_file(d[0]), directory=workdir, default_suffix=".BIN", diff --git a/src/View/Results/CustomPlot/CustomPlotValuesSelectionDialog.py b/src/View/Results/CustomPlot/CustomPlotValuesSelectionDialog.py index 796934fb..631a530a 100644 --- a/src/View/Results/CustomPlot/CustomPlotValuesSelectionDialog.py +++ b/src/View/Results/CustomPlot/CustomPlotValuesSelectionDialog.py @@ -45,6 +45,7 @@ class CustomPlotValuesSelectionDialog(PamhyrDialog): self.setup_check_boxs() self.value = None + self.export_to_csv = False def setup_radio_buttons(self): self._radio = [] @@ -95,4 +96,6 @@ class CustomPlotValuesSelectionDialog(PamhyrDialog): ) self.value = x, y + self.export_to_csv = self.find(QCheckBox, + "checkBox_export").isChecked() super().accept() diff --git a/src/View/Results/Window.py b/src/View/Results/Window.py index 03be2da7..f51ed02c 100644 --- a/src/View/Results/Window.py +++ b/src/View/Results/Window.py @@ -20,6 +20,8 @@ import os import csv import logging +from numpy import sqrt + from datetime import datetime from tools import trace, timer, logger_exception @@ -489,12 +491,17 @@ class ResultsWindow(PamhyrWindow): dlg = CustomPlotValuesSelectionDialog(parent=self) if dlg.exec(): x, y = dlg.value - self.create_new_tab_custom_plot(x, y) + export = dlg.export_to_csv + self.create_new_tab_custom_plot(x, y, export) - def create_new_tab_custom_plot(self, x: str, y: list): + def create_new_tab_custom_plot(self, x: str, y: list, export: bool): name = f"{x}: {','.join(y)}" wname = f"tab_custom_{x}_{y}" + if export: + self.export(x, y) + return + tab_widget = self.find(QTabWidget, f"tabWidget") # This plot already exists @@ -534,6 +541,7 @@ class ResultsWindow(PamhyrWindow): grid.addWidget(canvas, 1, 0) widget.setLayout(grid) tab_widget.addTab(widget, name) + tab_widget.setCurrentWidget(widget) def _copy(self): logger.info("TODO: copy") @@ -582,18 +590,28 @@ class ResultsWindow(PamhyrWindow): self._button_last.setEnabled(True) self._button_play.setIcon(self._icon_start) - def export(self): + def export(self, x: str, y: list): + + logger.debug( + "Export custom plot for: " + + f"{x} -> {','.join(y)}" + ) self.file_dialog( - select_file=False, - callback=lambda d: self.export_to(d[0]) + callback=lambda f: self.export_to(f[0], x, y), + default_suffix=".csv", + file_filter=["CSV (*.csv)"], ) - def export_to(self, directory): + def export_to(self, filename, x, y): timestamps = sorted(self._results.get("timestamps")) - for reach in self._results.river.reachs: - self.export_reach(reach, directory, timestamps) + if x == "rk": + timestamp = self._get_current_timestamp() + self._export_rk(timestamp, y, filename) + elif x == "time": + profile = self._get_current_profile() + self._export_time(profile, y, filename) - def export_reach(self, reach, directory, timestamps): + def export_all(self, reach, directory, timestamps): name = reach.name name = name.replace(" ", "-") if len(timestamps) == 1: @@ -607,47 +625,14 @@ class ResultsWindow(PamhyrWindow): with open(file_name, 'w', newline='') as csvfile: writer = csv.writer(csvfile, delimiter=',', quotechar='|', quoting=csv.QUOTE_MINIMAL) - if len(timestamps) > 1: - writer.writerow(["name", "rk", "data-file"]) - for profile in reach.profiles: - p_file_name = os.path.join( - directory, - f"cs_{profile.geometry.id}.csv" - ) - - writer.writerow([ - profile.name, - profile.rk, - p_file_name - ]) - - self.export_profile(reach, - profile, - p_file_name, - timestamps) - else: - ts = timestamps[0] - writer.writerow(self._table["raw_data"]._headers) - for row in range(self._table["raw_data"].rowCount()): - line = [] - for column in range(self._table["raw_data"].columnCount()): - index = self._table["raw_data"].index(row, column) - line.append(self._table["raw_data"].data(index)) - writer.writerow(line) - - def export_profile(self, reach, profile, file_name, timestamps): - with open(file_name, 'w', newline='') as csvfile: - writer = csv.writer(csvfile, delimiter=',', - quotechar='|', quoting=csv.QUOTE_MINIMAL) - - writer.writerow(["timestamp", "z", "q"]) - - for ts in timestamps: - writer.writerow([ - ts, - profile.get_ts_key(ts, "Z"), - profile.get_ts_key(ts, "Q"), - ]) + ts = timestamps[0] + writer.writerow(self._table["raw_data"]._headers) + for row in range(self._table["raw_data"].rowCount()): + line = [] + for column in range(self._table["raw_data"].columnCount()): + index = self._table["raw_data"].index(row, column) + line.append(self._table["raw_data"].data(index)) + writer.writerow(line) def export_current(self): self.file_dialog( @@ -657,9 +642,144 @@ class ResultsWindow(PamhyrWindow): def export_current_to(self, directory): reach = self._results.river.reachs[self._get_current_reach()] - self.export_reach(reach, directory, [self._get_current_timestamp()]) + self.export_all(reach, directory, [self._get_current_timestamp()]) def delete_tab(self, index): tab_widget = self.find(QTabWidget, f"tabWidget") self._additional_plot.pop(tab_widget.tabText(index)) tab_widget.removeTab(index) + + def _export_rk(self, timestamp, y, filename): + reach = self._results.river.reachs[self._get_current_reach()] + rk = reach.geometry.get_rk() + my_dict = {} + if "elevation" in y: + my_dict["elevation"] = reach.geometry.get_z_min() + if "discharge" in y: + my_dict["discharge"] = list( + map( + lambda p: p.get_ts_key(timestamp, "Q"), + reach.profiles + ) + ) + if "water_elevation" in y: + my_dict["water_elevation"] = list( + map( + lambda p: p.get_ts_key(timestamp, "Z"), + reach.profiles + ) + ) + if "velocity" in y: + my_dict["velocity"] = list( + map( + lambda p: p.geometry.speed( + p.get_ts_key(timestamp, "Q"), + p.get_ts_key(timestamp, "Z")), + reach.profiles + ) + ) + if "depth" in y: + my_dict["depth"] = list( + map( + lambda p: p.geometry.max_water_depth( + p.get_ts_key(timestamp, "Z")), + reach.profiles + ) + ) + if "mean_depth" in y: + my_dict["mean_depth"] = list( + map( + lambda p: p.geometry.mean_water_depth( + p.get_ts_key(timestamp, "Z")), + reach.profiles + ) + ) + if "froude" in y: + my_dict["froude"] = list( + map( + lambda p: + p.geometry.speed( + p.get_ts_key(timestamp, "Q"), + p.get_ts_key(timestamp, "Z")) / + sqrt(9.81 * ( + p.geometry.wet_area( + p.get_ts_key(timestamp, "Z")) / + p.geometry.wet_width( + p.get_ts_key(timestamp, "Z")) + )), + reach.profiles + ) + ) + if "wet_area" in y: + my_dict["wet_area"] = list( + map( + lambda p: p.geometry.wet_area( + p.get_ts_key(timestamp, "Z")), + reach.profiles + ) + ) + + with open(filename, 'w', newline='') as csvfile: + writer = csv.writer(csvfile, delimiter=',', + quotechar='|', quoting=csv.QUOTE_MINIMAL) + header = ["rk"] + y + writer.writerow(header) + for row in range(len(rk)): + line = [rk[row]] + for var in y: + line.append(my_dict[var][row]) + writer.writerow(line) + + def _export_time(self, profile, y, filename): + reach = self._results.river.reachs[self._get_current_reach()] + profile = reach.profile(profile) + ts = list(self._results.get("timestamps")) + ts.sort() + my_dict = {} + z = profile.get_key("Z") + q = profile.get_key("Q") + if "elevation" in y: + my_dict["elevation"] = [profile.geometry.z_min()] * len(ts) + if "discharge" in y: + my_dict["discharge"] = q + if "water_elevation" in y: + my_dict["water_elevation"] = z + if "velocity" in y: + my_dict["velocity"] = list( + map( + lambda q, z: profile.geometry.speed(q, z), + q, z + ) + ) + if "depth" in y: + my_dict["depth"] = list( + map(lambda z: profile.geometry.max_water_depth(z), z) + ) + if "mean_depth" in y: + my_dict["mean_depth"] = list( + map(lambda z: profile.geometry.mean_water_depth(z), z) + ) + if "froude" in y: + my_dict["froude"] = list( + map(lambda z, q: + profile.geometry.speed(q, z) / + sqrt(9.81 * ( + profile.geometry.wet_area(z) / + profile.geometry.wet_width(z)) + ), z, q) + ) + if "wet_area" in y: + my_dict["wet_area"] = list( + map(lambda z: profile.geometry.wet_area(z), z) + ) + + with open(filename, 'w', newline='') as csvfile: + writer = csv.writer(csvfile, delimiter=',', + quotechar='|', quoting=csv.QUOTE_MINIMAL) + header = ["time"] + y + writer.writerow(header) + for row in range(len(ts)): + line = [ts[row]] + for var in y: + line.append(my_dict[var][row]) + writer.writerow(line) diff --git a/src/View/Tools/ASubWindow.py b/src/View/Tools/ASubWindow.py index e985217f..32d7a873 100644 --- a/src/View/Tools/ASubWindow.py +++ b/src/View/Tools/ASubWindow.py @@ -85,7 +85,7 @@ class WindowToolKit(object): return header, values - def file_dialog(self, select_file=True, + def file_dialog(self, select_file=None, callback=lambda x: None, directory=None, default_suffix=None, @@ -107,16 +107,19 @@ class WindowToolKit(object): dialog = QFileDialog(self, options=options) - if select_file: - mode = QFileDialog.FileMode.ExistingFile + if select_file is not None: + if select_file: + mode = QFileDialog.FileMode.ExistingFile + else: + mode = QFileDialog.FileMode.Directory else: - mode = QFileDialog.FileMode.Directory + mode = QFileDialog.FileMode.AnyFile dialog.setFileMode(mode) if directory is not None: dialog.setDirectory(directory) - if select_file: + if select_file is not False: if default_suffix is not None: dialog.setDefaultSuffix(default_suffix) diff --git a/src/View/ui/CustomPlotValuesSelectionDialog.ui b/src/View/ui/CustomPlotValuesSelectionDialog.ui index 88ca363c..fb9334d1 100644 --- a/src/View/ui/CustomPlotValuesSelectionDialog.ui +++ b/src/View/ui/CustomPlotValuesSelectionDialog.ui @@ -7,7 +7,7 @@ 0 0 414 - 482 + 81 @@ -44,35 +44,37 @@ - + Qt::Horizontal - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - + + + + + + Export to CSV + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + - - buttonBox - accepted() - Dialog - accept() - - - 248 - 254 - - - 157 - 274 - - - buttonBox rejected() @@ -89,5 +91,21 @@ + + buttonBox + accepted() + Dialog + accept() + + + 248 + 254 + + + 157 + 274 + + + From bfa0f0fd9b49bcf7dc73c3be3799e427acdcc9da Mon Sep 17 00:00:00 2001 From: Theophile Terraz Date: Fri, 20 Sep 2024 14:06:21 +0200 Subject: [PATCH 12/32] pep8 --- src/Solver/RubarBE.py | 2 +- src/View/InitialConditions/DialogDischarge.py | 7 ++++++- src/View/InitialConditions/DialogHeight.py | 4 ++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/Solver/RubarBE.py b/src/Solver/RubarBE.py index 3ad4723e..bf946f42 100644 --- a/src/Solver/RubarBE.py +++ b/src/Solver/RubarBE.py @@ -444,7 +444,7 @@ class Rubar3(CommandLineSolver): z = data[profile.rk][0] q = data[profile.rk][1] - #height = z - profile.z_min() + # height = z - profile.z_min() speed = profile.speed(q, z) return z, speed diff --git a/src/View/InitialConditions/DialogDischarge.py b/src/View/InitialConditions/DialogDischarge.py index c1947950..c7640037 100644 --- a/src/View/InitialConditions/DialogDischarge.py +++ b/src/View/InitialConditions/DialogDischarge.py @@ -36,7 +36,12 @@ class DischargeDialog(PamhyrDialog): _pamhyr_ui = "InitialConditions_Dialog_Generator_Discharge" _pamhyr_name = "Discharge" - def __init__(self, value, option, title="Discharge", trad=None, parent=None): + def __init__(self, + value, + option, + title="Discharge", + trad=None, + parent=None): super(DischargeDialog, self).__init__( title=trad[self._pamhyr_name], options=[], diff --git a/src/View/InitialConditions/DialogHeight.py b/src/View/InitialConditions/DialogHeight.py index 41683855..0f9814c3 100644 --- a/src/View/InitialConditions/DialogHeight.py +++ b/src/View/InitialConditions/DialogHeight.py @@ -58,9 +58,9 @@ class HeightDialog(PamhyrDialog): self.cb.clicked.connect(self.enable_discharge) def enable_discharge(self): - l = self.find(QLabel, "label_3") + label = self.find(QLabel, "label_3") self.sb3.setEnabled(self.cb.isChecked()) - l.setEnabled(self.cb.isChecked()) + label.setEnabled(self.cb.isChecked()) def accept(self): self.values[0] = self.sb1.value() From c2d1e228a89bbef719be91b7929fa2ff02f206f3 Mon Sep 17 00:00:00 2001 From: Theophile Terraz Date: Fri, 20 Sep 2024 14:29:09 +0200 Subject: [PATCH 13/32] debug --- src/View/ui/InitialConditions_Dialog_Generator_Height.ui | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/View/ui/InitialConditions_Dialog_Generator_Height.ui b/src/View/ui/InitialConditions_Dialog_Generator_Height.ui index 414c3d12..36d77a44 100644 --- a/src/View/ui/InitialConditions_Dialog_Generator_Height.ui +++ b/src/View/ui/InitialConditions_Dialog_Generator_Height.ui @@ -55,6 +55,12 @@ false + + -1000000.000000000000000 + + + 1000000.000000000000000 + @@ -73,6 +79,9 @@ -1000000.000000000000000 + + 1000000.000000000000000 + 0.000000000000000 From 91510a17558c75da8902baba486aa0f851401998 Mon Sep 17 00:00:00 2001 From: Theophile Terraz Date: Fri, 20 Sep 2024 14:29:09 +0200 Subject: [PATCH 14/32] debug --- src/View/Results/CustomPlot/Translate.py | 17 ------- src/View/Results/Window.py | 45 +++++++++---------- src/View/Results/translate.py | 16 +++++++ ...itialConditions_Dialog_Generator_Height.ui | 9 ++++ 4 files changed, 46 insertions(+), 41 deletions(-) diff --git a/src/View/Results/CustomPlot/Translate.py b/src/View/Results/CustomPlot/Translate.py index b00bab0a..abc6b244 100644 --- a/src/View/Results/CustomPlot/Translate.py +++ b/src/View/Results/CustomPlot/Translate.py @@ -59,20 +59,3 @@ class CustomPlotTranslate(ResultsTranslate): self._dict['3-meter'] = self._dict["unit_height"] self._dict['4-dimensionless'] = self._dict["unit_froude"] self._dict['5-m2'] = self._dict["wet_area"] - - # SubDict - - self._sub_dict["values_x"] = { - "rk": self._dict["rk"], - "time": self._dict["time"], - } - self._sub_dict["values_y"] = { - "elevation": self._dict["elevation"], - "water_elevation": self._dict["water_elevation"], - "discharge": self._dict["discharge"], - "velocity": self._dict["velocity"], - "depth": self._dict["max_depth"], - "mean_depth": self._dict["mean_depth"], - "froude": self._dict["froude"], - "wet_area": self._dict["wet_area"], - } diff --git a/src/View/Results/Window.py b/src/View/Results/Window.py index f51ed02c..60fc14ee 100644 --- a/src/View/Results/Window.py +++ b/src/View/Results/Window.py @@ -606,10 +606,25 @@ class ResultsWindow(PamhyrWindow): timestamps = sorted(self._results.get("timestamps")) if x == "rk": timestamp = self._get_current_timestamp() - self._export_rk(timestamp, y, filename) + val_dict = self._export_rk(timestamp, y, filename) elif x == "time": profile = self._get_current_profile() - self._export_time(profile, y, filename) + val_dict = self._export_time(profile, y, filename) + + with open(filename, 'w', newline='') as csvfile: + writer = csv.writer(csvfile, delimiter=',', + quotechar='|', quoting=csv.QUOTE_MINIMAL) + dict_x = self._trad.get_dict("values_x") + dict_y = self._trad.get_dict("values_y") + header = [dict_x[x]] + for text in y: + header.append(dict_y[text]) + writer.writerow(header) + for row in range(len(val_dict[x])): + line = [val_dict[x][row]] + for var in y: + line.append(val_dict[var][row]) + writer.writerow(line) def export_all(self, reach, directory, timestamps): name = reach.name @@ -651,8 +666,8 @@ class ResultsWindow(PamhyrWindow): def _export_rk(self, timestamp, y, filename): reach = self._results.river.reachs[self._get_current_reach()] - rk = reach.geometry.get_rk() my_dict = {} + my_dict["rk"] = reach.geometry.get_rk() if "elevation" in y: my_dict["elevation"] = reach.geometry.get_z_min() if "discharge" in y: @@ -719,23 +734,14 @@ class ResultsWindow(PamhyrWindow): ) ) - with open(filename, 'w', newline='') as csvfile: - writer = csv.writer(csvfile, delimiter=',', - quotechar='|', quoting=csv.QUOTE_MINIMAL) - header = ["rk"] + y - writer.writerow(header) - for row in range(len(rk)): - line = [rk[row]] - for var in y: - line.append(my_dict[var][row]) - writer.writerow(line) + return my_dict def _export_time(self, profile, y, filename): reach = self._results.river.reachs[self._get_current_reach()] profile = reach.profile(profile) ts = list(self._results.get("timestamps")) - ts.sort() my_dict = {} + my_dict["time"] = ts.sort() z = profile.get_key("Z") q = profile.get_key("Q") if "elevation" in y: @@ -773,13 +779,4 @@ class ResultsWindow(PamhyrWindow): map(lambda z: profile.geometry.wet_area(z), z) ) - with open(filename, 'w', newline='') as csvfile: - writer = csv.writer(csvfile, delimiter=',', - quotechar='|', quoting=csv.QUOTE_MINIMAL) - header = ["time"] + y - writer.writerow(header) - for row in range(len(ts)): - line = [ts[row]] - for var in y: - line.append(my_dict[var][row]) - writer.writerow(line) + return my_dict diff --git a/src/View/Results/translate.py b/src/View/Results/translate.py index faab6ecb..e434af9d 100644 --- a/src/View/Results/translate.py +++ b/src/View/Results/translate.py @@ -66,3 +66,19 @@ class ResultsTranslate(MainTranslate): "hydraulic_radius": self._dict["unit_hydraulic_radius"], "froude": self._dict["unit_froude"], } + + self._sub_dict["values_x"] = { + "rk": self._dict["unit_rk"], + "time": self._dict["unit_time_s"], + } + + self._sub_dict["values_y"] = { + "elevation": self._dict["unit_elevation"], + "water_elevation": self._dict["unit_water_elevation"], + "discharge": self._dict["unit_discharge"], + "velocity": self._dict["unit_speed"], + "depth": self._dict["unit_max_height"], + "mean_depth": self._dict["unit_mean_height"], + "froude": self._dict["unit_froude"], + "wet_area": self._dict["unit_wet_area"], + } diff --git a/src/View/ui/InitialConditions_Dialog_Generator_Height.ui b/src/View/ui/InitialConditions_Dialog_Generator_Height.ui index 414c3d12..36d77a44 100644 --- a/src/View/ui/InitialConditions_Dialog_Generator_Height.ui +++ b/src/View/ui/InitialConditions_Dialog_Generator_Height.ui @@ -55,6 +55,12 @@ false + + -1000000.000000000000000 + + + 1000000.000000000000000 + @@ -73,6 +79,9 @@ -1000000.000000000000000 + + 1000000.000000000000000 + 0.000000000000000 From 47ac8e8bef483b60555449590816c353ba00c4b8 Mon Sep 17 00:00:00 2001 From: Theophile Terraz Date: Fri, 20 Sep 2024 15:39:25 +0200 Subject: [PATCH 15/32] keyword for file_dialog mode selection --- src/View/Configure/Solver/Window.py | 6 +++--- src/View/Configure/Window.py | 2 +- src/View/InitialConditions/Window.py | 2 +- src/View/Results/Window.py | 3 ++- src/View/Tools/ASubWindow.py | 13 ++++++------- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/View/Configure/Solver/Window.py b/src/View/Configure/Solver/Window.py index d3551ba4..6293832f 100644 --- a/src/View/Configure/Solver/Window.py +++ b/src/View/Configure/Solver/Window.py @@ -74,17 +74,17 @@ class ConfigureSolverWindow(PamhyrDialog): # File button buttons = { "pushButton_input": (lambda: self.file_dialog( - select_file=True, + select_file="ExistingFile", callback=lambda f: self.set_line_edit_text( "lineEdit_input", f[0]) )), "pushButton_solver": (lambda: self.file_dialog( - select_file=True, + select_file="ExistingFile", callback=lambda f: self.set_line_edit_text( "lineEdit_solver", f[0]) )), "pushButton_output": (lambda: self.file_dialog( - select_file=True, + select_file="ExistingFile", callback=lambda f: self.set_line_edit_text( "lineEdit_output", f[0]) )), diff --git a/src/View/Configure/Window.py b/src/View/Configure/Window.py index 60f1dd67..1ed011ca 100644 --- a/src/View/Configure/Window.py +++ b/src/View/Configure/Window.py @@ -157,7 +157,7 @@ class ConfigureWindow(PamhyrDialog): "pushButton_stricklers_sort": self.sort_stricklers, # Others # "pushButton_backup_path": lambda: self.file_dialog( - # select_file=False, + # select_file="Directory", # callback=lambda f: self.set_line_edit_text( # "lineEdit_backup_path", f[0] # ) diff --git a/src/View/InitialConditions/Window.py b/src/View/InitialConditions/Window.py index 649e3459..58156cb1 100644 --- a/src/View/InitialConditions/Window.py +++ b/src/View/InitialConditions/Window.py @@ -263,7 +263,7 @@ class InitialConditionsWindow(PamhyrWindow): workdir = os.path.dirname(self._study.filename) return self.file_dialog( - select_file=True, + select_file="ExistingFile", callback=lambda d: self._import_from_file(d[0]), directory=workdir, default_suffix=".BIN", diff --git a/src/View/Results/Window.py b/src/View/Results/Window.py index 60fc14ee..325b16e3 100644 --- a/src/View/Results/Window.py +++ b/src/View/Results/Window.py @@ -597,6 +597,7 @@ class ResultsWindow(PamhyrWindow): f"{x} -> {','.join(y)}" ) self.file_dialog( + select_file="AnyFile", callback=lambda f: self.export_to(f[0], x, y), default_suffix=".csv", file_filter=["CSV (*.csv)"], @@ -651,7 +652,7 @@ class ResultsWindow(PamhyrWindow): def export_current(self): self.file_dialog( - select_file=False, + select_file="Directory", callback=lambda d: self.export_current_to(d[0]) ) diff --git a/src/View/Tools/ASubWindow.py b/src/View/Tools/ASubWindow.py index 32d7a873..f483b7b0 100644 --- a/src/View/Tools/ASubWindow.py +++ b/src/View/Tools/ASubWindow.py @@ -85,7 +85,7 @@ class WindowToolKit(object): return header, values - def file_dialog(self, select_file=None, + def file_dialog(self, select_file="ExistingFile", callback=lambda x: None, directory=None, default_suffix=None, @@ -107,11 +107,10 @@ class WindowToolKit(object): dialog = QFileDialog(self, options=options) - if select_file is not None: - if select_file: - mode = QFileDialog.FileMode.ExistingFile - else: - mode = QFileDialog.FileMode.Directory + if select_file == "Existing_file": + mode = QFileDialog.FileMode.ExistingFile + elif select_file == "Directory": + mode = QFileDialog.FileMode.Directory else: mode = QFileDialog.FileMode.AnyFile @@ -119,7 +118,7 @@ class WindowToolKit(object): if directory is not None: dialog.setDirectory(directory) - if select_file is not False: + if select_file != "Directory": if default_suffix is not None: dialog.setDefaultSuffix(default_suffix) From a95364db1db915c09edaeb1bba5d04f46169b7b6 Mon Sep 17 00:00:00 2001 From: Theophile Terraz Date: Fri, 20 Sep 2024 16:10:09 +0200 Subject: [PATCH 16/32] replace export raw data by export to CSV --- .../CustomPlotValuesSelectionDialog.py | 4 +-- src/View/Results/Window.py | 24 ++++++++-------- .../ui/CustomPlotValuesSelectionDialog.ui | 28 ++++--------------- src/View/ui/Results.ui | 2 +- 4 files changed, 20 insertions(+), 38 deletions(-) diff --git a/src/View/Results/CustomPlot/CustomPlotValuesSelectionDialog.py b/src/View/Results/CustomPlot/CustomPlotValuesSelectionDialog.py index 631a530a..e88e5386 100644 --- a/src/View/Results/CustomPlot/CustomPlotValuesSelectionDialog.py +++ b/src/View/Results/CustomPlot/CustomPlotValuesSelectionDialog.py @@ -45,7 +45,6 @@ class CustomPlotValuesSelectionDialog(PamhyrDialog): self.setup_check_boxs() self.value = None - self.export_to_csv = False def setup_radio_buttons(self): self._radio = [] @@ -96,6 +95,5 @@ class CustomPlotValuesSelectionDialog(PamhyrDialog): ) self.value = x, y - self.export_to_csv = self.find(QCheckBox, - "checkBox_export").isChecked() + super().accept() diff --git a/src/View/Results/Window.py b/src/View/Results/Window.py index 325b16e3..b1872102 100644 --- a/src/View/Results/Window.py +++ b/src/View/Results/Window.py @@ -310,8 +310,8 @@ class ResultsWindow(PamhyrWindow): actions = { "action_reload": self._reload, "action_add": self._add_custom_plot, - # "action_export": self.export, - "action_export": self.export_current, + "action_export": self._export, + # "action_export": self.export_current, } for action in actions: @@ -491,17 +491,12 @@ class ResultsWindow(PamhyrWindow): dlg = CustomPlotValuesSelectionDialog(parent=self) if dlg.exec(): x, y = dlg.value - export = dlg.export_to_csv - self.create_new_tab_custom_plot(x, y, export) + self.create_new_tab_custom_plot(x, y) - def create_new_tab_custom_plot(self, x: str, y: list, export: bool): + def create_new_tab_custom_plot(self, x: str, y: list): name = f"{x}: {','.join(y)}" wname = f"tab_custom_{x}_{y}" - if export: - self.export(x, y) - return - tab_widget = self.find(QTabWidget, f"tabWidget") # This plot already exists @@ -590,7 +585,13 @@ class ResultsWindow(PamhyrWindow): self._button_last.setEnabled(True) self._button_play.setIcon(self._icon_start) - def export(self, x: str, y: list): + def _export(self): + + dlg = CustomPlotValuesSelectionDialog(parent=self) + if dlg.exec(): + x, y = dlg.value + else: + return logger.debug( "Export custom plot for: " + @@ -741,8 +742,9 @@ class ResultsWindow(PamhyrWindow): reach = self._results.river.reachs[self._get_current_reach()] profile = reach.profile(profile) ts = list(self._results.get("timestamps")) + ts.sort() my_dict = {} - my_dict["time"] = ts.sort() + my_dict["time"] = ts z = profile.get_key("Z") q = profile.get_key("Q") if "elevation" in y: diff --git a/src/View/ui/CustomPlotValuesSelectionDialog.ui b/src/View/ui/CustomPlotValuesSelectionDialog.ui index fb9334d1..f7daec37 100644 --- a/src/View/ui/CustomPlotValuesSelectionDialog.ui +++ b/src/View/ui/CustomPlotValuesSelectionDialog.ui @@ -7,7 +7,7 @@ 0 0 414 - 81 + 70 @@ -44,33 +44,15 @@ - + Qt::Horizontal + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + - - - - - - Export to CSV - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - diff --git a/src/View/ui/Results.ui b/src/View/ui/Results.ui index 1b4a93ee..e92bdab4 100644 --- a/src/View/ui/Results.ui +++ b/src/View/ui/Results.ui @@ -262,7 +262,7 @@ Export - Export raw data + Export data as CSV Ctrl+E From 5f37767207494004e3c6ef6f5a377a65b4b36c5a Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Mon, 23 Sep 2024 09:50:57 +0200 Subject: [PATCH 17/32] Network: Translate default node and reach name in ui. --- src/Model/Network/Edge.py | 9 +- src/Model/Network/Node.py | 6 +- src/View/Network/GraphWidget.py | 15 +- src/View/Network/Table.py | 12 + src/View/Network/Window.py | 2 + src/View/Network/translate.py | 15 ++ src/lang/fr.ts | 389 +++++++++++++++++++++++++------- 7 files changed, 346 insertions(+), 102 deletions(-) diff --git a/src/Model/Network/Edge.py b/src/Model/Network/Edge.py index a21c89ab..eb5b07c5 100644 --- a/src/Model/Network/Edge.py +++ b/src/Model/Network/Edge.py @@ -51,9 +51,9 @@ class Edge(object): elif name == "id": ret = self.id elif name == "node1": - ret = self.node1.name + ret = self.node1 elif name == "node2": - ret = self.node2.name + ret = self.node2 elif name == "enable": ret = self._enable @@ -75,10 +75,7 @@ class Edge(object): @property def name(self): - name = self._name - if self._name == "": - name = f"{self.node1.name} -> {self.node2.name}" - return name + return self._name def is_enable(self): return self._enable diff --git a/src/Model/Network/Node.py b/src/Model/Network/Node.py index eb763939..87a29c09 100644 --- a/src/Model/Network/Node.py +++ b/src/Model/Network/Node.py @@ -35,11 +35,7 @@ class Node(object): else: self.id = id - if name == "": - self._name = f"Node {self.id}" - else: - self._name = name - + self._name = name self.pos = Point(x, y) def __getitem__(self, key): diff --git a/src/View/Network/GraphWidget.py b/src/View/Network/GraphWidget.py index 5b55d764..febeeb2d 100644 --- a/src/View/Network/GraphWidget.py +++ b/src/View/Network/GraphWidget.py @@ -368,11 +368,18 @@ class EdgeItem(QGraphicsItem): class NodeText(QGraphicsTextItem): - def __init__(self, node_item): + def __init__(self, node_item, graph): super(NodeText, self).__init__() self.item = node_item - self.setPlainText(self.item.node.name) + self.graph = graph + + name = self.item.node.name + if name == "": + name = self.graph._trad.node_name(self.item.node) + + self.setPlainText(name) + self.setDefaultTextColor(Qt.black) self.set_custom_pos(self.item.pos()) self.setZValue(2) @@ -502,7 +509,7 @@ class GraphWidget(QGraphicsView): for node in self.graph.nodes(): inode = NodeItem(node, self) - self.texts[inode] = NodeText(inode) + self.texts[inode] = NodeText(inode, self) self.scene().addItem(self.texts[inode]) self.scene().addItem(inode) self.node_items.append(inode) @@ -578,7 +585,7 @@ class GraphWidget(QGraphicsView): inode = NodeItem(node, self) self.scene().addItem(inode) self.node_items.append(inode) - self.texts[inode] = NodeText(inode) + self.texts[inode] = NodeText(inode, self) self.scene().addItem(self.texts[inode]) self.changeNode.emit(self.sender()) diff --git a/src/View/Network/Table.py b/src/View/Network/Table.py index 8ba889bd..97a26548 100644 --- a/src/View/Network/Table.py +++ b/src/View/Network/Table.py @@ -94,6 +94,9 @@ class NodeTableModel(PamhyrTableModel): return ret + if self._headers[index.column()] == "name": + return self._trad.node_name(self._lst[index.row()]) + return self._lst[index.row()][self._headers[index.column()]] @pyqtSlot() @@ -133,6 +136,15 @@ class EdgeTableModel(PamhyrTableModel): if role != Qt.ItemDataRole.DisplayRole: return QVariant() + if (self._headers[index.column()] == "node1" or + self._headers[index.column()] == "node2"): + return self._trad.node_name( + self._lst[index.row()][self._headers[index.column()]] + ) + + if self._headers[index.column()] == "name": + return self._trad.edge_name(self._lst[index.row()]) + return self._lst[index.row()][self._headers[index.column()]] @pyqtSlot() diff --git a/src/View/Network/Window.py b/src/View/Network/Window.py index eab8cae0..4a2c6efc 100644 --- a/src/View/Network/Window.py +++ b/src/View/Network/Window.py @@ -90,6 +90,7 @@ class NetworkWindow(PamhyrWindow): table_view=table, table_headers=self._table_headers_node, editable_headers=["name"], + trad=self._trad, data=self._graph, undo=self._undo_stack, ) @@ -109,6 +110,7 @@ class NetworkWindow(PamhyrWindow): table_view=table, table_headers=self._table_headers_edge, editable_headers=["name", "node1", "node2"], + trad=self._trad, delegates={ "node1": self.delegate_combobox, "node2": self.delegate_combobox, diff --git a/src/View/Network/translate.py b/src/View/Network/translate.py index ea999db2..da3e9618 100644 --- a/src/View/Network/translate.py +++ b/src/View/Network/translate.py @@ -31,6 +31,9 @@ class NetworkTranslate(MainTranslate): "Network", "River network" ) + self._dict["node"] = _translate("Network", "Node") + self._dict["edge"] = _translate("Network", "Reach") + self._dict["menu_add_node"] = _translate("Network", "Add node") self._dict["menu_del_node"] = _translate("Network", "Delete the node") self._dict["menu_edit_res_node"] = _translate( @@ -63,3 +66,15 @@ class NetworkTranslate(MainTranslate): "node2": _translate("Network", "Destination node"), "id": _translate("Network", "Index"), } + + def node_name(self, node): + if node.name == "": + return f"{self['node']} #{node.id}" + + return node.name + + def edge_name(self, edge): + if edge.name == "": + return f"{self['edge']} #{edge.id}" + + return edge.name diff --git a/src/lang/fr.ts b/src/lang/fr.ts index dc68faec..ea11ec02 100644 --- a/src/lang/fr.ts +++ b/src/lang/fr.ts @@ -192,17 +192,17 @@ Modifier les conditions aux limites - + X X - + Y Y - + Solid (kg/s) Solide (kg/s) @@ -236,6 +236,11 @@ Node Nœud + + + Boundary Condition Options + + BoundaryConditions @@ -448,7 +453,7 @@ Sélection des graphiques personnalisés - + Bed elevation (m) Cote du fond (m) @@ -649,9 +654,9 @@ Axe Y : - + Discharge - Débit + Débit @@ -729,9 +734,9 @@ Limites - + Options - Options + Options @@ -746,7 +751,7 @@ Height (m) - Hauteur (m) + Hauteur (m) @@ -754,27 +759,27 @@ Stricklers - + Distance computation Calcule des distances - + Second guide-line Deuxième ligne directrice - + First guide-line Première ligne directrice - + Origin Origine - + Origin value Valeur de l'origine @@ -803,8 +808,88 @@ Second guideline Seconde ligne directrice + + + Y coordinate + + + + + X coordinate + + + + + Z coordinate + + + + + Upstream to downstream + + + + + Downstream to upstream + + + + + Orientation + + + + + Keep current + + + + + Upstream height (m) + + + + + Downstream height (m) + + + + + Generate constant discharge + + + + + Slope + + + + + Estimate + + + + + Number of points to keep + + + + + Generate height + Générer une hauteur + + + + Depth (m) + Hauteur (m) + + + + Generate discharge + Générer un débit + - + Discharge (m³/s) @@ -1127,35 +1212,40 @@ Modifier les frottements - + Start (m) PK de départ (m) - + End (m) PK de fin (m) - + Start coefficient Coefficient de départ - + End coefficient Coefficient de fin - + Stricklers Stricklers - + Edit frictions Éditer les frotements + + + Strickler ($m^{1/3}/s$) + + Geometry @@ -1309,6 +1399,21 @@ the mean over the two guide-lines la moyenne entre les deux ligne directrice + + + UpdateRK + + + + + Purge + Purger + + + + Shift + + HydraulicStructures @@ -1387,17 +1492,17 @@ MainWindow - + Open debug window Ouvrir la fenêtre de débogage - + Open SQLite debuging tool ('sqlitebrowser') Ouvrir l'outil de débogage SQLite ('sqlitebrowser') - + Enable this window Activer cette fenêtre @@ -1412,7 +1517,7 @@ Barre d'outils - + Add Ajouter @@ -1432,7 +1537,7 @@ Supprimer les casier(s) - + Edit modifier @@ -1622,12 +1727,12 @@ Modifier la géométrie - + Import geometry Importer une géométrie - + Export geometry Exporter la géométrie @@ -1952,42 +2057,42 @@ Modifier une condition aux limites ou un apport ponctuel - + Raw data Données brutes - + Water elevation Cote de l'eau - + Discharge time series Hydrogramme - + Add customized visualization Ajouter une visualisation personnalisée - + Reload Recharger - + Export Exporter Export raw data - Exporter les données brutes + Exporter les données brutes - + delete supprimer @@ -2042,7 +2147,7 @@ Activer/Désactiver l'ouvrage hydraulique élémentaire - + Add a new point Ajouter un nouveau point @@ -2092,7 +2197,7 @@ Supprimer les points sélectionnés - + Up Monter @@ -2102,7 +2207,7 @@ Déplacer les points sélectionnés vers le haut - + Down Descendre @@ -2112,7 +2217,7 @@ Déplacer les points sélectionnés vers le bas - + sort_asc sort_asc @@ -2122,7 +2227,7 @@ Trier les points par leurs plus proches voisins - + sort_des sort_des @@ -2169,7 +2274,7 @@ Generate height - Générer une hauteur + Générer une hauteur @@ -2177,22 +2282,22 @@ Générer un débit - + Add new initial condition Ajouter une nouvelle condition initiale - + Delete inital condition Supprimer une condition initiale - + sort sort - + Sort inital conditions Trier les conditions initiales @@ -2237,57 +2342,57 @@ Modifier les couches sédimentaires - + Import Importer - + Add a cross-section Ajouter une section en travers - + Delete selected cross-section(s) Supprimer les sections en travers sélectionnées - + Edit selected cross section(s) Modifier les sections en travers sélectionnées - + Sort cross-sections by ascending position Trier les sections en travers par PK croissant - + Sort cross-sections by descending position Trier les sections en travers par PK décroissant - + Move up selected cross-section(s) Déplacer les sections en travers vers le haut - + Move down selected cross-section(s) Déplacer les sections en travers vers le bas - + Meshing Maillage - + Summary Résumé - + Checks Vérifications @@ -2382,7 +2487,7 @@ Modifier les informations de l'étude - + toolBar @@ -2397,7 +2502,7 @@ Éditer les frotements - + Purge Purger @@ -2417,22 +2522,22 @@ Retourner l'ordre des points - + Import from file Importer depuis un fichier - + Update RK Mise à jour des PK - + Recompute RK Recalcule des PK - + Purge cross-sections to keep a given number of points Purger les profiles pour garder un nombre fixer de points @@ -2441,6 +2546,61 @@ Sort points Trier les points + + + Export data as CSV + + + + + Generate depth + + + + + Generate elevation + + + + + Generate uniform + + + + + Generate rating curve from Manning law + + + + + Generate critical + + + + + Generate rating curve as Q(z) = Sqrt(g*S(z)^3/L(z)) + + + + + Make increasing + + + + + Remove points to make the curve increasing + + + + + Shift + + + + + Shift selected sections coordinates + + MainWindow_reach @@ -2473,60 +2633,75 @@ Réseau - + Add node Ajouter un nœud - + Delete the node Supprimer le nœud - + Edit node reservoir Modifier le casier du nœud - + Add node reservoir Ajouter un casier au nœud - + Delete the reach Supprimer l'arrête - + Enable the reach Activer l'arête - + Disable the reach Désactiver l'arête - + Reverse the reach orientation Inverser l'orientation de l'arête - + Source node Nœud source - + Destination node Nœud destination - + Delete node reservoir Supprimer le casier du nœud + + + Node + Nœud + + + + Reach + Bief + + + + Index + Index + Pamhyr @@ -2585,7 +2760,7 @@ X (m) - + Y (m) Y (m) @@ -3048,7 +3223,7 @@ Position (m) - PK (m) + PK (m) @@ -3061,47 +3236,47 @@ Hauteur (m) - + Diameter (m) Diamètre (m) - + Thickness (m) Épaisseur (m) - + Elevation (m) Cote (m) - + Water elevation (m) Cote de l'eau (m) - + Area (hectare) Aire (hectare) - + Time (sec) Temps (s) - + Time (JJJ:HH:MM:SS) Temps (JJJ:HH:MM:SS) - + Date (sec) Date (s) - + Date (ISO format) Date (format ISO) @@ -3113,12 +3288,52 @@ Speed (m/s) - Vitesse (m/s) + Vitesse (m/s) + + + + River Kilometer (m) + + + + + Max Depth (m) + + + + + Mean Depth (m) + + + + + Velocity (m/s) + + + + + Wet Perimeter (m) + + + + + Hydraulic Radius (m) + + + + + Froude number + - + Discharge (m³/s) + + + Wet Area (m²) + + From bf0cd7685e07c19805ca14df604c0d980c53380a Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Mon, 23 Sep 2024 10:37:16 +0200 Subject: [PATCH 18/32] trad: Update some translate. --- src/View/Geometry/PlotAC.py | 2 +- src/View/Geometry/Profile/Plot.py | 4 +- src/View/ui/PurgeOptions.ui | 4 +- src/lang/fr.ts | 70 +++++++++++++------------------ 4 files changed, 33 insertions(+), 47 deletions(-) diff --git a/src/View/Geometry/PlotAC.py b/src/View/Geometry/PlotAC.py index ad43fdf3..2be00355 100644 --- a/src/View/Geometry/PlotAC.py +++ b/src/View/Geometry/PlotAC.py @@ -41,7 +41,7 @@ class PlotAC(PamhyrPlot): self._autoscale_update = True self.label_x = self._trad["transverse_abscissa"] - self.label_y = self._trad["unit_height"] + self.label_y = self._trad["unit_elevation"] self.label_previous_plot_selected = self._trad["prev_cs"] self.label_plot_selected = self._trad["cs"] diff --git a/src/View/Geometry/Profile/Plot.py b/src/View/Geometry/Profile/Plot.py index 0eb1543f..f8c790f6 100644 --- a/src/View/Geometry/Profile/Plot.py +++ b/src/View/Geometry/Profile/Plot.py @@ -56,8 +56,8 @@ class Plot(PamhyrPlot): self.line_xy = [] self.line_gl = [] - self.label_x = self._trad["unit_rk"] - self.label_y = self._trad["unit_height"] + self.label_x = self._trad["transverse_abscissa"] + self.label_y = self._trad["unit_elevation"] self.before_plot_selected = None self.plot_selected = None diff --git a/src/View/ui/PurgeOptions.ui b/src/View/ui/PurgeOptions.ui index 9b027f97..2571a6be 100644 --- a/src/View/ui/PurgeOptions.ui +++ b/src/View/ui/PurgeOptions.ui @@ -6,7 +6,7 @@ 0 0 - 194 + 251 114 @@ -27,7 +27,7 @@ - Number of points to keep + Maximum number of points to keep diff --git a/src/lang/fr.ts b/src/lang/fr.ts index ea11ec02..eac47140 100644 --- a/src/lang/fr.ts +++ b/src/lang/fr.ts @@ -1,5 +1,6 @@ - + + About @@ -785,7 +786,7 @@ - Discharge (m³/s) + Discharge (m³/s) Débit (m³/s) @@ -868,30 +869,25 @@ Estimate - - - Number of points to keep - - Generate height - Générer une hauteur + Générer une profondeur Depth (m) - Hauteur (m) + Profondeur (m) Generate discharge - Générer un débit + Générer un débit - - - Discharge (m³/s) - + + + Maximum number of points to keep + Nombre de points maxi à conserver @@ -1244,7 +1240,7 @@ Strickler ($m^{1/3}/s$) - + Strickler ($m^{1/3}/s$) @@ -1402,17 +1398,17 @@ UpdateRK - + Mise à jour PK Purge - Purger + Simplifier Shift - + Décaler @@ -2594,12 +2590,12 @@ Shift - + Décaler Shift selected sections coordinates - + Décaler les coordonnées des sections sélectionnées @@ -2612,7 +2608,7 @@ Height (m) - Hauteur (m) + Altitude (m) @@ -2820,14 +2816,9 @@ Profil - - Discharge (m³/s) - Débit (m³/s) - - Discharge (m³/s) - + Débit (m³/s) @@ -3282,7 +3273,7 @@ - Discharge (m³/s) + Discharge (m³/s) Débit (m³/s) @@ -3293,47 +3284,42 @@ River Kilometer (m) - + Point Kilométrique (m) Max Depth (m) - + Profondeur max (m) Mean Depth (m) - + Profondeur moyenne (m) Velocity (m/s) - + Vitesse (m/s) Wet Perimeter (m) - + Périmètre mouiller (m) Hydraulic Radius (m) - + Rayon hydraulique (m) Froude number - + Nombre de Froude - - - Discharge (m³/s) - - - + Wet Area (m²) - + Aire mouiller (m²) From 35221c8973fea5e6d2f2b8490676cc51bd24844a Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Mon, 23 Sep 2024 12:10:36 +0200 Subject: [PATCH 19/32] trad: Some update. --- .../BasicHydraulicStructures/Translate.py | 2 +- src/View/InitialConditions/translate.py | 6 +- src/View/Results/PlotH.py | 4 +- src/View/Results/translate.py | 2 +- src/View/Translate.py | 5 +- src/View/ui/InitialConditions.ui | 6 +- src/View/ui/Results.ui | 2 +- src/lang/fr.ts | 320 ++++++------------ 8 files changed, 108 insertions(+), 239 deletions(-) diff --git a/src/View/HydraulicStructures/BasicHydraulicStructures/Translate.py b/src/View/HydraulicStructures/BasicHydraulicStructures/Translate.py index 29a12f35..9d872d24 100644 --- a/src/View/HydraulicStructures/BasicHydraulicStructures/Translate.py +++ b/src/View/HydraulicStructures/BasicHydraulicStructures/Translate.py @@ -108,7 +108,7 @@ hydraulic structure values?" self._sub_dict["long_types"] = { "ND": self._dict["not_defined"], "S1": _translate( - "BasicHydraulicStructures", "Discharge weir" + "BasicHydraulicStructures", "Rectangular weir" ), "S2": _translate( "BasicHydraulicStructures", "Trapezoidal weir" diff --git a/src/View/InitialConditions/translate.py b/src/View/InitialConditions/translate.py index 3ed1c398..0edee4dc 100644 --- a/src/View/InitialConditions/translate.py +++ b/src/View/InitialConditions/translate.py @@ -28,11 +28,7 @@ class ICTranslate(MainTranslate): super(ICTranslate, self).__init__() self._dict["Initial condition"] = _translate( - "InitialCondition", "Initial condition") - self._dict["Discharge"] = _translate( - "InitialCondition", "Discharge") - self._dict["Height"] = _translate( - "InitialCondition", "Height") + "InitialCondition", "Initial conditions") self._dict["elevation"] = self._dict["unit_elevation"] self._dict["discharge"] = self._dict["unit_discharge"] diff --git a/src/View/Results/PlotH.py b/src/View/Results/PlotH.py index 5920d837..5dc25831 100644 --- a/src/View/Results/PlotH.py +++ b/src/View/Results/PlotH.py @@ -51,8 +51,8 @@ class PlotH(PamhyrPlot): self._current_reach_id = reach_id self._current_profile_id = profile_id - self.label_x = _translate("Results", "Time (s)") - self.label_y = _translate("Results", "Discharge (m³/s)") + self.label_x = self._trad["unit_time_s"] + self.label_y = self._trad["unit_discharge"] self.label_discharge = _translate("Results", "Cross-section discharge") self.label_discharge_max = _translate("Results", "Max discharge") diff --git a/src/View/Results/translate.py b/src/View/Results/translate.py index e434af9d..b8e40d3b 100644 --- a/src/View/Results/translate.py +++ b/src/View/Results/translate.py @@ -37,7 +37,7 @@ class ResultsTranslate(MainTranslate): self._dict['x'] = _translate("Results", "X (m)") - self._dict['label_bottom'] = _translate("Results", "River bottom") + self._dict['label_bottom'] = _translate("Results", "Bottom") self._dict['label_water'] = _translate("Results", "Water elevation") self._dict['label_water_max'] = _translate( "Results", diff --git a/src/View/Translate.py b/src/View/Translate.py index 30a8a564..90d7b423 100644 --- a/src/View/Translate.py +++ b/src/View/Translate.py @@ -60,9 +60,8 @@ class UnitTranslate(CommonWordTranslate): self._dict["unit_diameter"] = _translate("Unit", "Diameter (m)") self._dict["unit_thickness"] = _translate("Unit", "Thickness (m)") self._dict["unit_elevation"] = _translate("Unit", "Elevation (m)") - self._dict["unit_water_elevation"] = _translate( - "Unit", "Water elevation (m)" - ) + self._dict["unit_water_elevation"] = _translate("Unit", "Water elevation (m)") + self._dict["unit_speed"] = _translate("Unit", "Velocity (m/s)") self._dict["unit_discharge"] = _translate("Unit", "Discharge (m³/s)") self._dict["unit_area"] = _translate("Unit", "Area (hectare)") diff --git a/src/View/ui/InitialConditions.ui b/src/View/ui/InitialConditions.ui index e0268872..81faeb6a 100644 --- a/src/View/ui/InitialConditions.ui +++ b/src/View/ui/InitialConditions.ui @@ -27,21 +27,21 @@ - Generate depth + Generate uniform depth - Generate discharge + Generate uniform discharge - Generate elevation + Generate uniform elevation diff --git a/src/View/ui/Results.ui b/src/View/ui/Results.ui index e92bdab4..90ca6fb4 100644 --- a/src/View/ui/Results.ui +++ b/src/View/ui/Results.ui @@ -139,7 +139,7 @@ - Raw data + Data diff --git a/src/lang/fr.ts b/src/lang/fr.ts index eac47140..51778d75 100644 --- a/src/lang/fr.ts +++ b/src/lang/fr.ts @@ -1,6 +1,5 @@ - - + About @@ -181,8 +180,8 @@ - Discharge weir - + Rectangular weir + Déversoir rectangulaire @@ -240,7 +239,7 @@ Boundary Condition Options - + Options des conditions limites @@ -276,21 +275,11 @@ Mage geometry guideline checker Vérificateur des lignes directrices pour Mage - - - Check if geometry guidelines are correctly defined for each reach - Vérifie si les lignes directrices sont définies pour tous les biefs - Study reach network checker Vérificateur des biefs de l'étude - - - Check if at least one reach exists - Vérifie s'il existe au moins un bief dans l'étude - Study geometry checker @@ -539,11 +528,6 @@ . . - - - Height - Hauteur - Solvers @@ -574,11 +558,6 @@ HH:mm:ss HH:mm:ss - - - Strickler coefficients - Coefficients de Strickler - Editor @@ -657,7 +636,7 @@ Discharge - Débit + Débit @@ -674,16 +653,6 @@ First guideline Première ligne directrice - - - Last guideline - Dernière ligne directrice - - - - Space step - Pas d'espace - Guideline used for distance computation @@ -724,11 +693,6 @@ Line Ligne - - - Comment lines start with '*' char (let see Mage documentation for more details) - Les lignes de commentaire commencent par un caractère '*' (voir la documentation de Mage pour plus de détails) - Limits @@ -737,7 +701,7 @@ Options - Options + Options @@ -749,11 +713,6 @@ Type of interpolation: Type d'interpolation : - - - Height (m) - Hauteur (m) - Stricklers @@ -784,11 +743,6 @@ Origin value Valeur de l'origine - - - Discharge (m³/s) - Débit (m³/s) - Parameters @@ -812,62 +766,62 @@ Y coordinate - + Coordonnées Y X coordinate - + Coordonnées X Z coordinate - + Coordonnées Z Upstream to downstream - + De l'amont à l'aval Downstream to upstream - + De l'aval à l'amont Orientation - + Orientation Keep current - + Actuelle Upstream height (m) - + Cote à l'amont (m) Downstream height (m) - + Cote à l'aval (m) Generate constant discharge - + Générer un débit constant Slope - + Pente Estimate - + Estimer @@ -889,6 +843,16 @@ Maximum number of points to keep Nombre de points maxi à conserver + + + Discharge (m³/s) + Débit (m³/s) + + + + Discharge (m³/s) + + Documentation @@ -1064,16 +1028,6 @@ Study Étude - - - Description: - Description : - - - - Name: - Nom : - @study_name @@ -1179,11 +1133,6 @@ @nb_hs @nb_hs - - - Restart - Relancer - Checkers list @@ -1197,16 +1146,6 @@ Frictions - - - Strickler coefficients - Coefficients de Strickler - - - - Edit friction coefficients - Modifier les frottements - Start (m) @@ -1320,21 +1259,6 @@ Points Points - - - First guideline - Première ligne directrice - - - - Second guideline - Seconde ligne directrice - - - - Mean over the two guidelines - Moyenne sur les deux lignes directrices - Meshing @@ -1428,18 +1352,8 @@ InitialCondition - Initial condition - Condition initiale - - - - Discharge - Débit - - - - Height - Hauteur + Initial conditions + Conditions initiales @@ -1488,17 +1402,17 @@ MainWindow - + Open debug window Ouvrir la fenêtre de débogage - + Open SQLite debuging tool ('sqlitebrowser') Ouvrir l'outil de débogage SQLite ('sqlitebrowser') - + Enable this window Activer cette fenêtre @@ -1507,11 +1421,6 @@ MainWindow Fenêtre principale - - - Toolbar - Barre d'outils - Add @@ -1847,21 +1756,11 @@ Friction Frottements - - - Edit friction coefficients - Modifier les frottements - Edit study Modifier l'étude - - - Edit the study metadata - Modifier les informations de l'étude - Define initial conditions @@ -2055,7 +1954,7 @@ Raw data - Données brutes + Données brutes @@ -2082,11 +1981,6 @@ Export Exporter - - - Export raw data - Exporter les données brutes - delete @@ -2267,16 +2161,6 @@ Edit sediment layers of the profile Modifier les couches sédimentaires du profil - - - Generate height - Générer une hauteur - - - - Generate discharge - Générer un débit - Add new initial condition @@ -2297,16 +2181,6 @@ Sort inital conditions Trier les conditions initiales - - - Add a new point in boundary condition or point source - Ajouter un nouveau point - - - - Sort boundary condition points - Trier les points des conditions aux limites - Add a new point in boundary condition or lateral source @@ -2383,12 +2257,12 @@ Maillage - + Summary Résumé - + Checks Vérifications @@ -2485,12 +2359,12 @@ toolBar - + toolBar toolBar_2 - + toolBar_2 @@ -2545,47 +2419,37 @@ Export data as CSV - - - - - Generate depth - - - - - Generate elevation - + Exporter les données au format CSV Generate uniform - + Générer un regime uniforme Generate rating curve from Manning law - + Générer une courbe de tarage (loi de Maning) Generate critical - + Générer régime critique Generate rating curve as Q(z) = Sqrt(g*S(z)^3/L(z)) - + Générer une courbe de tarage (Q(z) = Sqrt(g*S(z)^3/L(z))) Make increasing - + Augmenter Remove points to make the curve increasing - + Supprimer des points pour rendre la courbe croissante @@ -2597,6 +2461,26 @@ Shift selected sections coordinates Décaler les coordonnées des sections sélectionnées + + + Generate uniform depth + Générer une profondeur uniforme + + + + Generate uniform discharge + Générer un débit uniforme + + + + Generate uniform elevation + Générer une cote uniforme + + + + Data + Données + MainWindow_reach @@ -2760,11 +2644,6 @@ Y (m) Y (m) - - - Time (s) - Temps (s) - Cross-section discharge @@ -2793,7 +2672,7 @@ River bottom - Fond de la rivière + Fond de la rivière @@ -2816,9 +2695,9 @@ Profil - - Discharge (m³/s) - Débit (m³/s) + + Bottom + Fond @@ -3012,7 +2891,7 @@ Minimum water height (meter) - Hauteur d'eau minimale (mètres) + Profondeur minimale (mètres) @@ -3147,11 +3026,6 @@ Strickler coefficients Coefficient de Strickler - - - Stricklers - Stricklers - Study @@ -3211,11 +3085,6 @@ Unit - - - Position (m) - PK (m) - Width (m) @@ -3224,7 +3093,7 @@ Depth (m) - Hauteur (m) + Profondeur (m) @@ -3247,40 +3116,30 @@ Cote de l'eau (m) - + Area (hectare) Aire (hectare) - + Time (sec) Temps (s) - + Time (JJJ:HH:MM:SS) Temps (JJJ:HH:MM:SS) - + Date (sec) Date (s) - + Date (ISO format) Date (format ISO) - - - Discharge (m³/s) - Débit (m³/s) - - - - Speed (m/s) - Vitesse (m/s) - River Kilometer (m) @@ -3297,29 +3156,44 @@ Profondeur moyenne (m) - + Velocity (m/s) Vitesse (m/s) - + Wet Perimeter (m) Périmètre mouiller (m) - + Hydraulic Radius (m) Rayon hydraulique (m) - + Froude number Nombre de Froude - - Wet Area (m²) + + Discharge (m³/s) + Débit (m³/s) + + + + Wet Area (m²) Aire mouiller (m²) + + + Discharge (m³/s) + + + + + Wet Area (m²) + + From dd38b3d725d43c78ecb42ad74ae33301985b5de4 Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Mon, 23 Sep 2024 12:13:36 +0200 Subject: [PATCH 20/32] trad: Minor change (uniformisation of Height/Depth/...). --- src/lang/fr.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lang/fr.ts b/src/lang/fr.ts index 51778d75..0d6a1340 100644 --- a/src/lang/fr.ts +++ b/src/lang/fr.ts @@ -2492,7 +2492,7 @@ Height (m) - Altitude (m) + Cote (m) From 1a79dc87bacf22eb277f555430c566e2334d694b Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Mon, 23 Sep 2024 13:40:07 +0200 Subject: [PATCH 21/32] trad: Minor change. --- src/lang/fr.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lang/fr.ts b/src/lang/fr.ts index 0d6a1340..16fe1adf 100644 --- a/src/lang/fr.ts +++ b/src/lang/fr.ts @@ -2502,7 +2502,7 @@ Select destination file - Selectionner fichier de destination + Sélectionner fichier de destination @@ -2778,7 +2778,7 @@ Select solver - Selection du solveur + Sélection du solveur From 6a3bb38dbc0bfb23f17c09cf6c4cd89b1fc6032a Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Mon, 23 Sep 2024 13:42:02 +0200 Subject: [PATCH 22/32] Config: Minor change in trad. --- src/View/ui/ConfigureDialog.ui | 2 +- src/lang/fr.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/View/ui/ConfigureDialog.ui b/src/View/ui/ConfigureDialog.ui index 9c23ce2d..adcab8b5 100644 --- a/src/View/ui/ConfigureDialog.ui +++ b/src/View/ui/ConfigureDialog.ui @@ -382,7 +382,7 @@ - Langue + Language diff --git a/src/lang/fr.ts b/src/lang/fr.ts index 16fe1adf..4bd645dd 100644 --- a/src/lang/fr.ts +++ b/src/lang/fr.ts @@ -751,7 +751,7 @@ Langue - Langue + Langue From e7c93602a16b947f67f507ad9663b4455644c0de Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Mon, 23 Sep 2024 14:30:56 +0200 Subject: [PATCH 23/32] trad: Add some message window traduction. --- src/View/MainWindow.py | 22 +++++++++++++--------- src/View/Tools/ASubWindow.py | 3 +++ src/View/Translate.py | 26 ++++++++++++++++++++++++++ src/lang/fr.ts | 35 +++++++++++++++++++++++++++++++++++ 4 files changed, 77 insertions(+), 9 deletions(-) diff --git a/src/View/MainWindow.py b/src/View/MainWindow.py index 946eb54e..4f28cd8f 100644 --- a/src/View/MainWindow.py +++ b/src/View/MainWindow.py @@ -496,6 +496,9 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): self.update() def close_model(self): + if not self.dialog_close(cancel=True): + return + self._study = None self.update_enable_action() self.conf.set_close_correctly() @@ -745,15 +748,16 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): ################## def msg_select_reach(self): - self.message_box("Please select a reach", - "Geometry edition need a reach selected " - "into river network window to work on it") + self.message_box( + self._trad["mb_select_reach_title"], + self._trad["mb_select_reach_msg"] + ) def dialog_reopen_study(self): dlg = QMessageBox(self) - dlg.setWindowTitle("Last open study") - dlg.setText("Do you want to open again the last open study?") + dlg.setWindowTitle(self._trad["mb_last_open_title"]) + dlg.setText(self._trad["mb_last_open_msg"]) opt = QMessageBox.Cancel | QMessageBox.Ok # | QMessageBox.Open dlg.setStandardButtons(opt) @@ -793,9 +797,9 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): def dialog_close(self, cancel=True): dlg = QMessageBox(self) - dlg.setWindowTitle("Close PAMHYR without saving study") - dlg.setText("Do you want to save current study before PAMHYR close ?") - opt = QMessageBox.Save | QMessageBox.Ignore + dlg.setWindowTitle(self._trad["mb_close_title"]) + dlg.setText(self._trad["mb_close_msg"]) + opt = QMessageBox.Save | QMessageBox.Close if cancel: opt |= QMessageBox.Cancel @@ -807,7 +811,7 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): if res == QMessageBox.Save: self.save_study() return True - elif res == QMessageBox.Ignore: + elif res == QMessageBox.Close: return True elif res == QMessageBox.Cancel: return False diff --git a/src/View/Tools/ASubWindow.py b/src/View/Tools/ASubWindow.py index f483b7b0..01db62f4 100644 --- a/src/View/Tools/ASubWindow.py +++ b/src/View/Tools/ASubWindow.py @@ -149,6 +149,9 @@ class WindowToolKit(object): msg.setInformativeText(informative_text) msg.setWindowTitle(window_title) + msg.findChild(QLabel, "qt_msgbox_label")\ + .setFixedWidth(384) + msg.exec_() diff --git a/src/View/Translate.py b/src/View/Translate.py index 90d7b423..e41812e9 100644 --- a/src/View/Translate.py +++ b/src/View/Translate.py @@ -102,3 +102,29 @@ class MainTranslate(UnitTranslate): self._dict["active_window"] = _translate( "MainWindow", "Enable this window" ) + + # Message box + self._dict["mb_select_reach_title"] = _translate( + "MainWindow", "Please select a reach" + ) + self._dict["mb_select_reach_msg"] = _translate( + "MainWindow", + "This edition window need a reach selected " + "into the river network to work on it" + ) + + self._dict["mb_last_open_title"] = _translate( + "MainWindow", "Last open study" + ) + self._dict["mb_last_open_msg"] = _translate( + "MainWindow", + "Do you want to open again the last open study?" + ) + + self._dict["mb_close_title"] = _translate( + "MainWindow", "Close without saving study" + ) + self._dict["mb_close_msg"] = _translate( + "MainWindow", + "Do you want to save current study before closing it?" + ) diff --git a/src/lang/fr.ts b/src/lang/fr.ts index 4bd645dd..3597c8aa 100644 --- a/src/lang/fr.ts +++ b/src/lang/fr.ts @@ -2481,6 +2481,41 @@ Data Données + + + Please select a reach + Veuillez sélectionner un bief + + + + Geometry edition need a reach selected into the river network to work on it + L'édition de la géométrie nécessite un bief sélectionné dans le réseau fluvial pour pouvoir travailler dessus + + + + Last open study + Dernière étude ouverte + + + + Do you want to open again the last open study? + Voulez-vous rouvrir la dernière étude ? + + + + This edition window need a reach selected into the river network to work on it + Cette fenêtre d'édition a besoin d'un bief sélectionné dans le réseau pour travailler dessus + + + + Close without saving study + Fermer sans sauvegarder l'étude + + + + Do you want to save current study before closing it? + Souhaitez-vous sauvegarder l'étude en cours avant de la fermer ? + MainWindow_reach From 0c7924aa7c5dbae584587520882efb35e0107aad Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Mon, 23 Sep 2024 14:45:10 +0200 Subject: [PATCH 24/32] MainWindow: Delete dead code. --- src/View/MainWindow.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/View/MainWindow.py b/src/View/MainWindow.py index 4f28cd8f..b4cbc648 100644 --- a/src/View/MainWindow.py +++ b/src/View/MainWindow.py @@ -1415,15 +1415,3 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): f"sqlitebrowser {file}", shell=True ) - - # TODO: Delete me ! - ############### - # DUMMY STUFF # - ############### - - def open_dummy(self, title="Dummy"): - self.dummy = DummyWindow( - title=title if type(title) is str else "Dummy", - parent=self - ) - self.dummy.show() From 2673ad9ce404140eb14652817b2240e3892fc2da Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Mon, 23 Sep 2024 14:55:54 +0200 Subject: [PATCH 25/32] Results: Translate status bar. --- src/View/Results/Window.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/View/Results/Window.py b/src/View/Results/Window.py index b1872102..76c16b9d 100644 --- a/src/View/Results/Window.py +++ b/src/View/Results/Window.py @@ -292,9 +292,9 @@ class ResultsWindow(PamhyrWindow): pname = profile.name if profile.name != "" else profile.rk - return (f"Reach: {reach.name} | " + - f"Profile: {pname} | " + - f"Timestamp : {fts} ({ts} sec)") + return (f"{self._trad['reach']}: {reach.name} | " + + f"{self._trad['cross_section']}: {pname} | " + + f"{self._trad['unit_time_s']} : {fts} ({ts} sec)") def setup_statusbar(self): txt = self._compute_status_label() From 0cd60920ca4448ebc9e4e5a65e53f5d75b915d73 Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Mon, 23 Sep 2024 15:12:27 +0200 Subject: [PATCH 26/32] Trad: Minor change. --- ...alConditions_Dialog_Generator_Discharge.ui | 2 +- src/lang/fr.ts | 21 +++---------------- 2 files changed, 4 insertions(+), 19 deletions(-) diff --git a/src/View/ui/InitialConditions_Dialog_Generator_Discharge.ui b/src/View/ui/InitialConditions_Dialog_Generator_Discharge.ui index 58ae2b98..2ff32c47 100644 --- a/src/View/ui/InitialConditions_Dialog_Generator_Discharge.ui +++ b/src/View/ui/InitialConditions_Dialog_Generator_Discharge.ui @@ -32,7 +32,7 @@ - Discharge (m³/s) + Discharge (m³/s) diff --git a/src/lang/fr.ts b/src/lang/fr.ts index 3597c8aa..6a073871 100644 --- a/src/lang/fr.ts +++ b/src/lang/fr.ts @@ -843,15 +843,10 @@ Maximum number of points to keep Nombre de points maxi à conserver - - - Discharge (m³/s) - Débit (m³/s) - Discharge (m³/s) - + Débit (m³/s) @@ -3210,25 +3205,15 @@ Froude number Nombre de Froude - - - Discharge (m³/s) - Débit (m³/s) - - - - Wet Area (m²) - Aire mouiller (m²) - Discharge (m³/s) - + Débit (m³/s) Wet Area (m²) - + Aire mouiller (m²) From 3b83cd3418d2ca1045e5519a2cd9a04f782065b8 Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Mon, 23 Sep 2024 15:20:59 +0200 Subject: [PATCH 27/32] Results: CustomPlot: Fix index name 'max_depth'. --- src/View/Results/CustomPlot/Plot.py | 16 ++++++++-------- src/View/Results/Window.py | 8 ++++---- src/View/Results/translate.py | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/View/Results/CustomPlot/Plot.py b/src/View/Results/CustomPlot/Plot.py index a3073c7a..f1f2438f 100644 --- a/src/View/Results/CustomPlot/Plot.py +++ b/src/View/Results/CustomPlot/Plot.py @@ -34,7 +34,7 @@ unit = { "water_elevation": "0-meter", "discharge": "1-m3s", "velocity": "2-ms", - "depth": "3-meter", + "max_depth": "3-meter", "mean_depth": "3-meter", "froude": "4-dimensionless", "wet_area": "5-m2", @@ -157,9 +157,9 @@ class CustomPlot(PamhyrPlot): ) lines["velocity"] = line - if "depth" in self._y: + if "max_depth" in self._y: - ax = self._axes[unit["depth"]] + ax = self._axes[unit["max_depth"]] d = list( map( lambda p: p.geometry.max_water_depth( @@ -171,7 +171,7 @@ class CustomPlot(PamhyrPlot): rk, d, color='brown', lw=1., ) - lines["depth"] = line + lines["max_depth"] = line if "mean_depth" in self._y: @@ -361,9 +361,9 @@ class CustomPlot(PamhyrPlot): ) lines["velocity"] = line - if "depth" in self._y: + if "max_depth" in self._y: - ax = self._axes[unit["depth"]] + ax = self._axes[unit["max_depth"]] d = list( map(lambda z: profile.geometry.max_water_depth(z), z) ) @@ -372,7 +372,7 @@ class CustomPlot(PamhyrPlot): ts, d, color='brown', lw=1., ) - lines["depth"] = line + lines["max_depth"] = line if "mean_depth" in self._y: @@ -385,7 +385,7 @@ class CustomPlot(PamhyrPlot): ts, d, color='orange', lw=1., ) - lines["depth"] = line + lines["mean_depth"] = line if "froude" in self._y: diff --git a/src/View/Results/Window.py b/src/View/Results/Window.py index 76c16b9d..eb4684cf 100644 --- a/src/View/Results/Window.py +++ b/src/View/Results/Window.py @@ -695,8 +695,8 @@ class ResultsWindow(PamhyrWindow): reach.profiles ) ) - if "depth" in y: - my_dict["depth"] = list( + if "max_depth" in y: + my_dict["max_depth"] = list( map( lambda p: p.geometry.max_water_depth( p.get_ts_key(timestamp, "Z")), @@ -760,8 +760,8 @@ class ResultsWindow(PamhyrWindow): q, z ) ) - if "depth" in y: - my_dict["depth"] = list( + if "max_depth" in y: + my_dict["max_depth"] = list( map(lambda z: profile.geometry.max_water_depth(z), z) ) if "mean_depth" in y: diff --git a/src/View/Results/translate.py b/src/View/Results/translate.py index b8e40d3b..8b66124e 100644 --- a/src/View/Results/translate.py +++ b/src/View/Results/translate.py @@ -77,7 +77,7 @@ class ResultsTranslate(MainTranslate): "water_elevation": self._dict["unit_water_elevation"], "discharge": self._dict["unit_discharge"], "velocity": self._dict["unit_speed"], - "depth": self._dict["unit_max_height"], + "max_depth": self._dict["unit_max_height"], "mean_depth": self._dict["unit_mean_height"], "froude": self._dict["unit_froude"], "wet_area": self._dict["unit_wet_area"], From 92b81f499664929459d0d0f6e461a74f78d158fd Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Mon, 23 Sep 2024 15:48:48 +0200 Subject: [PATCH 28/32] About: Renew window. --- src/View/About/Window.py | 2 +- src/View/ui/about.ui | 264 ++++++++++++++++++++++---- src/View/ui/ressources/GPLv3_Logo.png | Bin 0 -> 62532 bytes 3 files changed, 231 insertions(+), 35 deletions(-) create mode 100644 src/View/ui/ressources/GPLv3_Logo.png diff --git a/src/View/About/Window.py b/src/View/About/Window.py index 24931a08..aa6eab08 100644 --- a/src/View/About/Window.py +++ b/src/View/About/Window.py @@ -67,5 +67,5 @@ class AboutWindow(PamhyrDialog): label = f"\n - {author}" + label except StopIteration: label = _translate("About", "Contributors: ") + label - label = "Copyright © 2022-2024 INRAE\n" + label + # label = "Copyright © 2022-2024 INRAE\n" + label self.set_label_text("label_copyright", label) diff --git a/src/View/ui/about.ui b/src/View/ui/about.ui index 36f0efd7..1b1041bb 100644 --- a/src/View/ui/about.ui +++ b/src/View/ui/about.ui @@ -17,8 +17,35 @@ - + + + + + + + + + ressources/Pamhyr2_logo.png + + + + + + + + + + ressources/Logo-INRAE.png + + + + + + + + 0 + @@ -33,34 +60,213 @@ + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Copyright © 2022-2024 INRAE + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Version: @version + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + License: GPLv3+ + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + 100 + 50 + + + + + + + ressources/GPLv3_Logo.png + + + true + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + <a href="https://gitlab.irstea.fr/theophile.terraz/pamhyr">Source code</a> + + + Qt::RichText + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + ... - - - - - - Version: @version - - - - - - - License: GPLv3+ - - - - - - - <a href="https://gitlab.irstea.fr/theophile.terraz/pamhyr">Source code</a> - - - Qt::RichText + + Qt::AlignCenter @@ -79,16 +285,6 @@ - - - - - - - ressources/Pamhyr2_logo.png - - - diff --git a/src/View/ui/ressources/GPLv3_Logo.png b/src/View/ui/ressources/GPLv3_Logo.png new file mode 100644 index 0000000000000000000000000000000000000000..1f6ddf89ad8e956bf674a98e9f0cb08d0f29e651 GIT binary patch literal 62532 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%0A(^ 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 yZ)9m^c>ppnGBYhOIV~_YR4_F Date: Mon, 23 Sep 2024 15:52:37 +0200 Subject: [PATCH 29/32] trad: Minor change. --- src/lang/fr.ts | 41 +++++++++++++++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/src/lang/fr.ts b/src/lang/fr.ts index 6a073871..19a3ea8f 100644 --- a/src/lang/fr.ts +++ b/src/lang/fr.ts @@ -843,6 +843,11 @@ Maximum number of points to keep Nombre de points maxi à conserver + + + Discharge (m³/s) + Débit (m³/s) + Discharge (m³/s) @@ -969,27 +974,27 @@ Formulaire - + About Pamhyr2 À propos de Pamhyr2 - + ... ... - + Version: @version Version : @version - + License: GPLv3+ Licence : GPLv3+ - + <a href="https://gitlab.irstea.fr/theophile.terraz/pamhyr">Source code</a> <a href="https://gitlab.irstea.fr/theophile.terraz/pamhyr">Code source</a> @@ -1138,6 +1143,16 @@ Errors summary Résumé des erreurs + + + Copyright © 2022-2024 INRAE + Copyright © 2022-2024 INRAE + + + + Copyright © 2022-2024 INRAE + + Frictions @@ -1327,7 +1342,7 @@ Shift - Décaler + Translater @@ -2449,12 +2464,12 @@ Shift - Décaler + Translater Shift selected sections coordinates - Décaler les coordonnées des sections sélectionnées + Translater les coordonnées des sections sélectionnées @@ -3205,6 +3220,16 @@ Froude number Nombre de Froude + + + Discharge (m³/s) + Débit (m³/s) + + + + Wet Area (m²) + Aire mouiller (m²) + Discharge (m³/s) From a9df5384a335bbf0c427a7a53882fdc491501994 Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Mon, 23 Sep 2024 15:57:15 +0200 Subject: [PATCH 30/32] trad: Stop using UTF-8 for some translate. --- src/View/Translate.py | 4 ++-- src/lang/fr.ts | 26 ++++++++------------------ 2 files changed, 10 insertions(+), 20 deletions(-) diff --git a/src/View/Translate.py b/src/View/Translate.py index e41812e9..a96a5cb2 100644 --- a/src/View/Translate.py +++ b/src/View/Translate.py @@ -63,7 +63,7 @@ class UnitTranslate(CommonWordTranslate): self._dict["unit_water_elevation"] = _translate("Unit", "Water elevation (m)") self._dict["unit_speed"] = _translate("Unit", "Velocity (m/s)") - self._dict["unit_discharge"] = _translate("Unit", "Discharge (m³/s)") + self._dict["unit_discharge"] = _translate("Unit", "Discharge (m^3/s)") self._dict["unit_area"] = _translate("Unit", "Area (hectare)") self._dict["unit_time_s"] = _translate("Unit", "Time (sec)") @@ -72,7 +72,7 @@ class UnitTranslate(CommonWordTranslate): self._dict["unit_date_s"] = _translate("Unit", "Date (sec)") self._dict["unit_date_iso"] = _translate("Unit", "Date (ISO format)") - self._dict["unit_wet_area"] = _translate("Unit", "Wet Area (m²)") + self._dict["unit_wet_area"] = _translate("Unit", "Wet Area (m^2)") self._dict["unit_wet_perimeter"] = _translate( "Unit", "Wet Perimeter (m)" ) diff --git a/src/lang/fr.ts b/src/lang/fr.ts index 19a3ea8f..9211c5ed 100644 --- a/src/lang/fr.ts +++ b/src/lang/fr.ts @@ -845,13 +845,13 @@ - Discharge (m³/s) - Débit (m³/s) + Discharge (m^3/s) + Débit (m^3/s) Discharge (m³/s) - Débit (m³/s) + @@ -1151,7 +1151,7 @@ Copyright © 2022-2024 INRAE - + Copyright © 2022-2024 INRAE @@ -3222,23 +3222,13 @@ - Discharge (m³/s) - Débit (m³/s) + Discharge (m^3/s) + Débit (m^3/s) - Wet Area (m²) - Aire mouiller (m²) - - - - Discharge (m³/s) - Débit (m³/s) - - - - Wet Area (m²) - Aire mouiller (m²) + Wet Area (m^2) + Aire mouiller (m^2) From 9bf125aa15f435913206d01d317593d9a6e57403 Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Mon, 23 Sep 2024 16:06:30 +0200 Subject: [PATCH 31/32] Pamhyr2: Fix pep8. --- src/View/Translate.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/View/Translate.py b/src/View/Translate.py index a96a5cb2..99b3e542 100644 --- a/src/View/Translate.py +++ b/src/View/Translate.py @@ -60,7 +60,9 @@ class UnitTranslate(CommonWordTranslate): self._dict["unit_diameter"] = _translate("Unit", "Diameter (m)") self._dict["unit_thickness"] = _translate("Unit", "Thickness (m)") self._dict["unit_elevation"] = _translate("Unit", "Elevation (m)") - self._dict["unit_water_elevation"] = _translate("Unit", "Water elevation (m)") + self._dict["unit_water_elevation"] = _translate( + "Unit", "Water elevation (m)" + ) self._dict["unit_speed"] = _translate("Unit", "Velocity (m/s)") self._dict["unit_discharge"] = _translate("Unit", "Discharge (m^3/s)") From c4c24492169d9a1dc400bd926c7f1af7011334f8 Mon Sep 17 00:00:00 2001 From: Pierre-Antoine Rouby Date: Mon, 23 Sep 2024 16:08:59 +0200 Subject: [PATCH 32/32] trad: Minor fix. --- ...alConditions_Dialog_Generator_Discharge.ui | 2 +- src/lang/fr.ts | 51 +++++++++---------- 2 files changed, 24 insertions(+), 29 deletions(-) diff --git a/src/View/ui/InitialConditions_Dialog_Generator_Discharge.ui b/src/View/ui/InitialConditions_Dialog_Generator_Discharge.ui index 2ff32c47..0390f175 100644 --- a/src/View/ui/InitialConditions_Dialog_Generator_Discharge.ui +++ b/src/View/ui/InitialConditions_Dialog_Generator_Discharge.ui @@ -32,7 +32,7 @@ - Discharge (m³/s) + Discharge (m^3/s) diff --git a/src/lang/fr.ts b/src/lang/fr.ts index 9211c5ed..4d6f0091 100644 --- a/src/lang/fr.ts +++ b/src/lang/fr.ts @@ -846,12 +846,7 @@ Discharge (m^3/s) - Débit (m^3/s) - - - - Discharge (m³/s) - + Débit (m^3/s) @@ -1412,17 +1407,17 @@ MainWindow - + Open debug window Ouvrir la fenêtre de débogage - + Open SQLite debuging tool ('sqlitebrowser') Ouvrir l'outil de débogage SQLite ('sqlitebrowser') - + Enable this window Activer cette fenêtre @@ -2267,12 +2262,12 @@ Maillage - + Summary Résumé - + Checks Vérifications @@ -2492,7 +2487,7 @@ Données - + Please select a reach Veuillez sélectionner un bief @@ -2502,27 +2497,27 @@ L'édition de la géométrie nécessite un bief sélectionné dans le réseau fluvial pour pouvoir travailler dessus - + Last open study Dernière étude ouverte - + Do you want to open again the last open study? Voulez-vous rouvrir la dernière étude ? - + This edition window need a reach selected into the river network to work on it Cette fenêtre d'édition a besoin d'un bief sélectionné dans le réseau pour travailler dessus - + Close without saving study Fermer sans sauvegarder l'étude - + Do you want to save current study before closing it? Souhaitez-vous sauvegarder l'étude en cours avant de la fermer ? @@ -3161,27 +3156,27 @@ Cote de l'eau (m) - + Area (hectare) Aire (hectare) - + Time (sec) Temps (s) - + Time (JJJ:HH:MM:SS) Temps (JJJ:HH:MM:SS) - + Date (sec) Date (s) - + Date (ISO format) Date (format ISO) @@ -3201,32 +3196,32 @@ Profondeur moyenne (m) - + Velocity (m/s) Vitesse (m/s) - + Wet Perimeter (m) Périmètre mouiller (m) - + Hydraulic Radius (m) Rayon hydraulique (m) - + Froude number Nombre de Froude - + Discharge (m^3/s) Débit (m^3/s) - + Wet Area (m^2) Aire mouiller (m^2)