diff --git a/doc/dev/documentation.org b/doc/dev/documentation.org index 28fad839..f7285945 100644 --- a/doc/dev/documentation.org +++ b/doc/dev/documentation.org @@ -1334,7 +1334,7 @@ https://gitlab.irstea.fr/theophile.terraz/pamhyr You can improve or add translation for the project. To contribute to Pamhyr2 translate, you need to use Qt Linguist[fn:qt-linguist]. Open -Qt-linguist and edite the translation ({{{file(.ts)}}}) file, finally, +Qt-linguist and edit the translation ({{{file(.ts)}}}) file, finally, commit the new version of file and make a merge request. If you want add a new language, edit the script diff --git a/doc/users/Tuto1/step2.pamhyr b/doc/users/Tuto1/step2.pamhyr index e10e685b..c7516dff 100644 Binary files a/doc/users/Tuto1/step2.pamhyr and b/doc/users/Tuto1/step2.pamhyr differ diff --git a/src/Meshing/Mage.py b/src/Meshing/Mage.py index f01e5b51..ce408ac0 100644 --- a/src/Meshing/Mage.py +++ b/src/Meshing/Mage.py @@ -411,7 +411,7 @@ class MeshingWithMageMailleurTT(AMeshingTool): str, [ st_file, m_file, - "update_rk", step, + "update_kp", step, limites[0], limites[1], directrices[0], directrices[1], orientation, lm, linear, origin, origin_value diff --git a/src/Model/BoundaryCondition/BoundaryCondition.py b/src/Model/BoundaryCondition/BoundaryCondition.py index 361202aa..c09639e0 100644 --- a/src/Model/BoundaryCondition/BoundaryCondition.py +++ b/src/Model/BoundaryCondition/BoundaryCondition.py @@ -509,3 +509,15 @@ class BoundaryCondition(SQLSubModel): d = self._data d[index], d[prev] = d[prev], d[index] self._status.modified() + + def reach(self, river): + r = [] + if self._node is not None: + if river is not None: + for edge in river.edges(): + if edge.node1.name == self._node.name: + r.append(edge.reach) + if edge.node2.name == self._node.name: + r.append(edge.reach) + + return r diff --git a/src/Model/Friction/Friction.py b/src/Model/Friction/Friction.py index 52e1b360..ad789de9 100644 --- a/src/Model/Friction/Friction.py +++ b/src/Model/Friction/Friction.py @@ -23,6 +23,8 @@ from tools import trace, timer from Model.Tools.PamhyrDB import SQLSubModel from Model.Scenario import Scenario +from numpy import interp + logger = logging.getLogger() @@ -290,3 +292,17 @@ class Friction(SQLSubModel): def end_strickler(self, strickler): self._end_strickler = strickler self._status.modified() + + def get_friction(self, rk): + if not self.contains_rk(rk): + return None + minor = interp(rk, + [self.begin_rk, self.end_rk], + [self.begin_strickler.minor, + self.end_strickler.minor]) + medium = interp(rk, + [self.begin_rk, self.end_rk], + [self.begin_strickler.medium, + self.end_strickler.medium]) + + return minor, medium diff --git a/src/Model/Geometry/Profile.py b/src/Model/Geometry/Profile.py index cd4ce3ef..3713473d 100644 --- a/src/Model/Geometry/Profile.py +++ b/src/Model/Geometry/Profile.py @@ -332,8 +332,17 @@ class Profile(object): def wet_points(self, z): raise NotImplementedMethodeError(self, self.wet_point) + def wet_width(self, z): + raise NotImplementedMethodeError(self, self.wet_width) + def wet_perimeter(self, z): raise NotImplementedMethodeError(self, self.wet_perimeter) def wet_area(self, z): raise NotImplementedMethodeError(self, self.wet_area) + + def wet_radius(self, z): + raise NotImplementedMethodeError(self, self.wet_radius) + + def get_nb_wet_areas(self, z): + raise NotImplementedMethodeError(self, self.get_nb_wet_areas) diff --git a/src/Model/Geometry/ProfileXYZ.py b/src/Model/Geometry/ProfileXYZ.py index 1bab9b1b..ebc7aaf1 100644 --- a/src/Model/Geometry/ProfileXYZ.py +++ b/src/Model/Geometry/ProfileXYZ.py @@ -486,31 +486,125 @@ class ProfileXYZ(Profile, SQLSubModel): return abs(rg.dist(rd)) - def wet_perimeter(self, z): - poly = self.wet_polygon(z) + def wet_width(self, z): + start, end = self.get_all_water_limits_ac(z) - if poly is None: + if len(start) == 0: return 0 - return poly.length + length = 0.0 + for s, e in zip(start, end): + length += abs(s - e) + return length + + def wet_perimeter(self, z): + lines = self.wet_lines(z) + + if lines is None: + return 0 + + length = 0.0 + for line in lines: + length += line.length + return length def wet_area(self, z): - poly = self.wet_polygon(z) + lines = self.wet_lines(z) - if poly is None: + if lines is None: return 0 - return poly.area + area = 0.0 + for line in lines: + if len(line.coords) > 2: + poly = geometry.Polygon(line) + area += poly.area + return area + + def wet_radius(self, z): + p = self.wet_perimeter(z) + a = self.wet_area(z) + + if p == 0: + return 0 + + return a/p + + def wet_line(self, z): + points = self.wet_points(z) + if len(points) < 3: + return None + + zz = map(lambda p: p.z, points) + station = self._get_station(points) + + line = geometry.LineString(list(zip(station, zz))) + return line + + def wet_lines(self, z): + points = self._points + if len(points) < 3: + return None + + lines = [] + + zz = list(map(lambda p: p.z, points)) + station = self._get_station(points) + + line = [] + for i in range(self.number_points-1): + + if zz[i] >= z and zz[i+1] < z: + y = np.interp( + z, + [zz[i], zz[i+1]], + [station[i], station[i+1]] + ) + line.append([y, z]) + + if zz[i] < z: + line.append([station[i], zz[i]]) + + if zz[i] <= z and zz[i+1] >= z: + y = np.interp( + z, + [zz[i], zz[i+1]], + [station[i], station[i+1]] + ) + line.append([y, z]) + if len(line) > 2: + lines.append(geometry.LineString(line)) + line = [] + + if zz[self.number_points-1] < z: + line.append([station[self.number_points-1], z]) + if len(line) > 2: + lines.append(geometry.LineString(line)) + line = [] + + return lines + + def max_water_depth(self, z): + return z - self.z_min() + + def mean_water_depth(self, z): + a = self.wet_area(z) + w = self.wet_width(z) + + if w == 0: + return 0 + + return a/w def wet_polygon(self, z): points = self.wet_points(z) if len(points) < 3: return None - z = map(lambda p: p.z, points) + zz = map(lambda p: p.z, points) station = self._get_station(points) - poly = geometry.Polygon(list(zip(station, z))) + poly = geometry.Polygon(list(zip(station, zz))) return poly def wet_points(self, z): @@ -522,6 +616,63 @@ class ProfileXYZ(Profile, SQLSubModel): return points + def get_nb_wet_areas(self, z): + + n_zones = 0 + points = self._points + if points[0].z <= z: + n_zones += 1 + + for i in range(self.number_points-1): + if points[i].z > z and points[i+1].z <= z: + n_zones += 1 + + return n_zones + + def get_all_water_limits_ac(self, z): + """ + Determine all water limits for z elevation. + """ + + points = self._points + if len(points) < 3: + return None + + zz = list(map(lambda p: p.z, points)) + station = self._get_station(points) + + start = [] + if points[0].z <= z: + start.append(station[0]) + + for i in range(self.number_points-1): + if zz[i] > z and zz[i+1] <= z: + y = np.interp( + z, + [zz[i], zz[i+1]], + [station[i], station[i+1]] + ) + start.append(y) + + end = [] + if points[-1].z <= z: + end.append(station[-1]) + + for i in reversed(range(self.number_points-1)): + if zz[i] <= z and zz[i+1] > z: + y = np.interp( + z, + [zz[i], zz[i+1]], + [station[i], station[i+1]] + ) + end.append(y) + + if len(start) != len(end): + logger.error(f"ERROR in get_all_water_limits_ac") + return [], [] + + return start, list(reversed(end)) + def get_water_limits(self, z): """ Determine left and right limits of water elevation. @@ -585,8 +736,8 @@ class ProfileXYZ(Profile, SQLSubModel): Returns: Projection of the points of the profile on a plane. """ - if self.nb_points < 3: - return None + if self.nb_points < 2: + return [0.0] else: return self._get_station(self.points) @@ -718,3 +869,9 @@ class ProfileXYZ(Profile, SQLSubModel): self.point(i), self.point(i+1) ) + + def shift(self, x, y, z): + for p in self.points: + p.x = p.x + x + p.y = p.y + y + p.z = p.z + z diff --git a/src/Model/InitialConditions/InitialConditions.py b/src/Model/InitialConditions/InitialConditions.py index 9e56df46..55db5f9d 100644 --- a/src/Model/InitialConditions/InitialConditions.py +++ b/src/Model/InitialConditions/InitialConditions.py @@ -479,8 +479,14 @@ class InitialConditions(SQLSubModel): logger.debug(f"incline = {incline}") self._data = [] for profile in profiles: - width = profile.width_approximation() - strickler = 25 + width = profile.wet_width(profile.z_min() + height) + frictions = self._reach.frictions.frictions + strickler = None + for f in frictions: + if f.contains_rk(profile.rk): + strickler = f.get_friction(profile.rk)[0] + if strickler is None: + strickler = 25.0 if not compute_discharge: discharge = data_discharge[profile.rk] @@ -533,7 +539,13 @@ class InitialConditions(SQLSubModel): self._data = [] for profile in profiles: width = profile.width_approximation() - strickler = 25 + frictions = self._reach.frictions.frictions + strickler = None + for f in frictions: + if f.contains_rk(profile.rk): + strickler = f.get_friction(profile.rk)[0] + if strickler is None: + strickler = 25.0 if not compute_height: height = data_height[profile.rk] diff --git a/src/Solver/Mage.py b/src/Solver/Mage.py index 2bface0a..85968999 100644 --- a/src/Solver/Mage.py +++ b/src/Solver/Mage.py @@ -324,7 +324,7 @@ class Mage(CommandLineSolver): if t in ["HYD", "QSO", "LIM"]: v0 /= 60 # Convert first column to minute - f.write(f"{v0:10}{v1:10}\n") + f.write(f"{v0:9} {v1:9} \n") return files diff --git a/src/View/BoundaryCondition/Edit/GenerateDialog.py b/src/View/BoundaryCondition/Edit/GenerateDialog.py new file mode 100644 index 00000000..3b07a4d0 --- /dev/null +++ b/src/View/BoundaryCondition/Edit/GenerateDialog.py @@ -0,0 +1,68 @@ +# GenerateDialog.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, QPushButton +) + + +class GenerateDialog(PamhyrDialog): + _pamhyr_ui = "BoundaryConditionsDialogGenerator" + _pamhyr_name = "Boundary Condition Options" + + def __init__(self, + value, + reach, + title="Boundary Condition Options", + trad=None, + parent=None): + super(GenerateDialog, self).__init__( + title=trad[self._pamhyr_name], + options=[], + trad=trad, + parent=parent + ) + + self.value = value + self.find(QDoubleSpinBox, "doubleSpinBox").setValue(self.value) + self.reach = reach + self.find(QPushButton, "EstimateButton").clicked.connect( + self.estimate + ) + + def accept(self): + self.value = self.find(QDoubleSpinBox, "doubleSpinBox").value() + super().accept() + + def reject(self): + self.close() + + def estimate(self): + self.value = abs(self.reach.get_incline_median_mean()) + self.find(QDoubleSpinBox, "doubleSpinBox").setValue(self.value) diff --git a/src/View/BoundaryCondition/Edit/Table.py b/src/View/BoundaryCondition/Edit/Table.py index d4a9f8a6..a7481666 100644 --- a/src/View/BoundaryCondition/Edit/Table.py +++ b/src/View/BoundaryCondition/Edit/Table.py @@ -48,6 +48,7 @@ from Model.BoundaryCondition.BoundaryConditionTypes import ( from View.BoundaryCondition.Edit.UndoCommand import ( SetDataCommand, AddCommand, DelCommand, SortCommand, MoveCommand, PasteCommand, + ReplaceDataCommand, ) _translate = QCoreApplication.translate @@ -200,3 +201,13 @@ class TableModel(PamhyrTableModel): def update(self): # self.auto_sort() self.layoutChanged.emit() + + def replace_data(self, data1, data2): + self.layoutAboutToBeChanged.emit() + self._undo.push( + ReplaceDataCommand( + self._data, data1, data2 + ) + ) + self.layoutAboutToBeChanged.emit() + self.update() diff --git a/src/View/BoundaryCondition/Edit/UndoCommand.py b/src/View/BoundaryCondition/Edit/UndoCommand.py index 02e0a48e..4dcc811b 100644 --- a/src/View/BoundaryCondition/Edit/UndoCommand.py +++ b/src/View/BoundaryCondition/Edit/UndoCommand.py @@ -181,3 +181,30 @@ class PasteCommand(QUndoCommand): def redo(self): for bc in self._bcs: self._data.insert(self._row, bc) + + +class ReplaceDataCommand(QUndoCommand): + def __init__(self, data, data1, data2): + QUndoCommand.__init__(self) + self._data = data + self._old_rows = len(data) + self._data1 = data1 + self._data2 = data2 + self._rows = len(data1) + + self._old_bc = [] + for row in range(self._old_rows): + self._old_bc.append((row, self._data.get_i(row))) + self._old_bc.sort() + + def undo(self): + self._data.delete_i(list(range(self._rows))) + for row, el in self._old_bc: + self._data.insert(row, el) + + def redo(self): + self._data.delete_i(list(range(self._old_rows))) + for row in range(self._rows): + self._data.add(row) + self._data._set_i_c_v(row, 0, self._data1[row]) + self._data._set_i_c_v(row, 1, self._data2[row]) diff --git a/src/View/BoundaryCondition/Edit/Window.py b/src/View/BoundaryCondition/Edit/Window.py index c9675d8b..ed9da7a3 100644 --- a/src/View/BoundaryCondition/Edit/Window.py +++ b/src/View/BoundaryCondition/Edit/Window.py @@ -20,6 +20,8 @@ import logging from tools import timer, trace +from numpy import sqrt + from View.Tools.PamhyrWindow import PamhyrWindow from View.Tools.PamhyrWidget import PamhyrWidget from View.Tools.PamhyrDelegate import PamhyrExTimeDelegate @@ -48,6 +50,7 @@ from View.BoundaryCondition.Edit.translate import BCETranslate from View.BoundaryCondition.Edit.UndoCommand import SetMetaDataCommand from View.BoundaryCondition.Edit.Table import TableModel from View.BoundaryCondition.Edit.Plot import Plot +from View.BoundaryCondition.Edit.GenerateDialog import GenerateDialog _translate = QCoreApplication.translate @@ -104,6 +107,7 @@ class EditBoundaryConditionWindow(PamhyrWindow): self._data = data trad = BCETranslate() self._long_types = trad.get_dict("long_types") + self._study = study name = trad[self._pamhyr_name] if self._data is not None: @@ -200,6 +204,18 @@ class EditBoundaryConditionWindow(PamhyrWindow): self.find(QAction, "action_del").triggered.connect(self.delete) self.find(QAction, "action_sort").triggered.connect(self.sort) + self.find(QAction, "action_generate_uniform")\ + .triggered.connect(self.generate_uniform) + self.find(QAction, "action_generate_critical")\ + .triggered.connect(self.generate_critical) + + 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) + else: + self.find(QAction, "action_generate_uniform").setVisible(True) + self.find(QAction, "action_generate_critical").setVisible(True) + self._table.dataChanged.connect(self.update) self._table.layoutChanged.connect(self.update) @@ -320,3 +336,46 @@ class EditBoundaryConditionWindow(PamhyrWindow): self._table.redo() self.plot.update() self.widget_update() + + def generate_uniform(self): + if self._data.has_node: + node = self._data.node + reach = self._data.reach(self._study.river)[0] + profile = reach.profiles[-1] + incline = abs(reach.get_incline_median_mean()) + dlg = GenerateDialog(incline, + reach, + trad=self._trad, + parent=self) + if dlg.exec(): + incline = dlg.value + frictions = reach._parent.frictions.frictions + z_min = profile.z_min() + z_max = profile.z_max() + strickler = None + for f in frictions: + if f.contains_rk(profile.rk): + strickler = f.get_friction(profile.rk)[0] + if strickler is None: + 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))) + for h in height] + self._table.replace_data(height, q) + + return + + def generate_critical(self): + if self._data.has_node: + node = self._data.node + reach = self._data.reach(self._study.river)[0] + profile = reach.profiles[-1] + z_min = profile.z_min() + z_max = profile.z_max() + height = [(i)*(z_max-z_min)/50 for i in range(51)] + q = [sqrt(9.81 * (profile.wet_area(z_min + h) ** 3) + / profile.wet_width(z_min + h)) + for h in height] + self._table.replace_data(height, q) + return diff --git a/src/View/BoundaryCondition/Edit/translate.py b/src/View/BoundaryCondition/Edit/translate.py index 9d835550..006c49df 100644 --- a/src/View/BoundaryCondition/Edit/translate.py +++ b/src/View/BoundaryCondition/Edit/translate.py @@ -32,6 +32,8 @@ class BCETranslate(BCTranslate): self._dict["Edit Boundary Conditions"] = _translate( "BoundaryCondition", "Edit boundary conditions" ) + self._dict["Boundary Condition Options"] = _translate( + "BoundaryCondition", "Boundary Condition Options") self._sub_dict["table_headers"] = { "x": _translate("BoundaryCondition", "X"), diff --git a/src/View/Geometry/Profile/Plot.py b/src/View/Geometry/Profile/Plot.py index 136fcaf7..0eb1543f 100644 --- a/src/View/Geometry/Profile/Plot.py +++ b/src/View/Geometry/Profile/Plot.py @@ -313,10 +313,6 @@ class Plot(PamhyrPlot): x_carto = self.data.x() y_carto = self.data.y() - if (len(x_carto) < 3 or len(y_carto) < 3 or len(x) < 3): - # Noting to do in this case - return - self.profile_line2D, = self.canvas.axes.plot( x, y, color=self.color_plot, lw=1.5, markersize=7, marker='+', diff --git a/src/View/Geometry/ShiftDialog.py b/src/View/Geometry/ShiftDialog.py new file mode 100644 index 00000000..4953855a --- /dev/null +++ b/src/View/Geometry/ShiftDialog.py @@ -0,0 +1,60 @@ +# ShiftDialog.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 + + +class ShiftDialog(PamhyrDialog): + _pamhyr_ui = "GeometryReachShift" + _pamhyr_name = "Shift" + + def __init__(self, trad=None, parent=None): + super(ShiftDialog, self).__init__( + title=trad[self._pamhyr_name], + trad=trad, + options=[], + parent=parent + ) + + self._init_default_values() + + def _init_default_values(self): + self._dx = 0.0 + self._dy = 0.0 + self._dz = 0.0 + + @property + def dx(self): + return self._dx + + @property + def dy(self): + return self._dy + + @property + def dz(self): + return self._dz + + def accept(self): + self._dx = self.get_double_spin_box("doubleSpinBox_X") + self._dy = self.get_double_spin_box("doubleSpinBox_Y") + self._dz = self.get_double_spin_box("doubleSpinBox_Z") + super().accept() + + def reject(self): + self.close() diff --git a/src/View/Geometry/Table.py b/src/View/Geometry/Table.py index 2676037d..fd91707a 100644 --- a/src/View/Geometry/Table.py +++ b/src/View/Geometry/Table.py @@ -267,3 +267,12 @@ class GeometryReachTableModel(PamhyrTableModel): ) ) self.layoutChanged.emit() + + def shift(self, rows, dx, dy, dz): + + self._undo.push( + ShiftCommand( + self._data, rows, dx, dy, dz + ) + ) + self.layoutChanged.emit() diff --git a/src/View/Geometry/Translate.py b/src/View/Geometry/Translate.py index b2bc2eec..7944d661 100644 --- a/src/View/Geometry/Translate.py +++ b/src/View/Geometry/Translate.py @@ -82,3 +82,12 @@ class GeometryTranslate(MainTranslate): self._dict["Meshing"] = _translate( "Geometry", "Meshing" ) + self._dict["UpdateRK"] = _translate( + "Geometry", "UpdateRK" + ) + self._dict["Purge"] = _translate( + "Geometry", "Purge" + ) + self._dict["Shift"] = _translate( + "Geometry", "Shift" + ) diff --git a/src/View/Geometry/UndoCommand.py b/src/View/Geometry/UndoCommand.py index 772c84e1..d3cc29ef 100644 --- a/src/View/Geometry/UndoCommand.py +++ b/src/View/Geometry/UndoCommand.py @@ -281,3 +281,32 @@ class PurgeCommand(QUndoCommand): def redo(self): for profile in self._reach._profiles: profile.purge(self._np_purge) + + +class ShiftCommand(QUndoCommand): + def __init__(self, reach, rows, dx, dy, dz): + QUndoCommand.__init__(self) + + self._reach = reach + self._rows = rows + self._dx = dx + self._dy = dy + self._dz = dz + + self._old = [] + for profile in self._reach.profiles: + self._old.append(profile.points.copy()) + + def undo(self): + for i in self._rows: + profile = self._reach.profiles[i] + self._reach.profiles[i].shift(-self._dx, + -self._dy, + -self._dz) + + def redo(self): + for i in self._rows: + profile = self._reach.profiles[i] + self._reach.profiles[i].shift(self._dx, + self._dy, + self._dz) diff --git a/src/View/Geometry/Window.py b/src/View/Geometry/Window.py index 2a8fbf53..e6d7585f 100644 --- a/src/View/Geometry/Window.py +++ b/src/View/Geometry/Window.py @@ -58,6 +58,7 @@ from View.Geometry.PlotRKZ import PlotRKZ from View.Geometry.MeshingDialog import MeshingDialog from View.Geometry.UpdateRKDialog import UpdateRKDialog from View.Geometry.PurgeDialog import PurgeDialog +from View.Geometry.ShiftDialog import ShiftDialog from View.Geometry.Translate import GeometryTranslate from View.Geometry.Profile.Window import ProfileWindow @@ -205,6 +206,7 @@ class GeometryWindow(PamhyrWindow): "action_meshing": self.edit_meshing, "action_update_rk": self.update_rk, "action_purge": self.purge, + "action_shift": self.shift, } for action in actions: @@ -511,10 +513,6 @@ class GeometryWindow(PamhyrWindow): self._table.move_down(row) self.select_current_profile() - def purge(self): - self._table.purge() - self.update_redraw() - def purge(self): try: dlg = PurgeDialog( @@ -527,6 +525,28 @@ class GeometryWindow(PamhyrWindow): logger_exception(e) return + def shift(self): + rows = sorted( + list( + set( + [index.row() for index in self.tableView.selectedIndexes()] + ) + ) + ) + try: + dlg = ShiftDialog( + trad=self._trad, + parent=self + ) + if dlg.exec(): + self._table.shift(rows, + dlg.dx, + dlg.dy, + dlg.dz) + except Exception as e: + logger_exception(e) + return + def duplicate(self): rows = [ row.row() for row in @@ -615,7 +635,7 @@ class GeometryWindow(PamhyrWindow): QSettings.UserScope, 'MyOrg' ) - if self._study.filename != "" or self._study.filename is not None: + if self._study.filename != "" and self._study.filename is not None: default_directory = os.path.basename(self._study.filename) current_dir = settings.value( 'current_directory', diff --git a/src/View/InitialConditions/Window.py b/src/View/InitialConditions/Window.py index e998bf64..2bc617bd 100644 --- a/src/View/InitialConditions/Window.py +++ b/src/View/InitialConditions/Window.py @@ -200,7 +200,7 @@ class InitialConditionsWindow(PamhyrWindow): return rows[0].row() def update(self): - self.update(propagate=False) + self._update(propagate=False) def _update(self, propagate=True): self._update_plot() diff --git a/src/View/Results/CustomPlot/Plot.py b/src/View/Results/CustomPlot/Plot.py index e45d9ea1..a3073c7a 100644 --- a/src/View/Results/CustomPlot/Plot.py +++ b/src/View/Results/CustomPlot/Plot.py @@ -20,6 +20,7 @@ import logging from functools import reduce from datetime import datetime +from numpy import sqrt from tools import timer from View.Tools.PamhyrPlot import PamhyrPlot @@ -32,6 +33,11 @@ unit = { "elevation": "0-meter", "water_elevation": "0-meter", "discharge": "1-m3s", + "velocity": "2-ms", + "depth": "3-meter", + "mean_depth": "3-meter", + "froude": "4-dimensionless", + "wet_area": "5-m2", } @@ -76,74 +82,155 @@ class CustomPlot(PamhyrPlot): reach = results.river.reach(self._reach) rk = reach.geometry.get_rk() z_min = reach.geometry.get_z_min() + q = list( + map( + lambda p: p.get_ts_key(self._timestamp, "Q"), + reach.profiles + ) + ) + z = list( + map( + lambda p: p.get_ts_key(self._timestamp, "Z"), + reach.profiles + ) + ) - # self.canvas.axes.set_xlim( - # left=min(rk), right=max(rk) - # ) - - meter_axes = self.canvas.axes - m3S_axes = self.canvas.axes - if "0-meter" in self._y_axes and "1-m3s" in self._y_axes: - m3s_axes = self._axes["1-m3s"] + shift = 0 + compt = 0 + for ax in sorted(self._axes): + if compt == 0: + self._axes[ax].spines['left'].set_position(('outward', shift)) + compt += 1 + else: + self._axes[ax].spines['right'].set_position(('outward', shift)) + shift += 60 lines = {} if "elevation" in self._y: - # meter_axes.set_ylim( - # bottom=min(0, min(z_min)), - # top=max(z_min) + 1 - # ) - line = meter_axes.plot( + ax = self._axes[unit["elevation"]] + line = ax.plot( rk, z_min, color='grey', lw=1., ) lines["elevation"] = line if "water_elevation" in self._y: - # Water elevation - water_z = list( - map( - lambda p: p.get_ts_key(self._timestamp, "Z"), - reach.profiles - ) - ) - # meter_axes.set_ylim( - # bottom=min(0, min(z_min)), - # top=max(water_z) + 1 - # ) - - line = meter_axes.plot( - rk, water_z, lw=1., + ax = self._axes[unit["water_elevation"]] + line = ax.plot( + rk, z, lw=1., color='blue', ) lines["water_elevation"] = line if "elevation" in self._y: - meter_axes.fill_between( - rk, z_min, water_z, + ax.fill_between( + rk, z_min, z, color='blue', alpha=0.5, interpolate=True ) if "discharge" in self._y: - q = list( - map( - lambda p: p.get_ts_key(self._timestamp, "Q"), - reach.profiles - ) - ) - # m3s_axes.set_ylim( - # bottom=min(0, min(q)), - # top=max(q) + 1 - # ) - - line = m3s_axes.plot( + ax = self._axes[unit["discharge"]] + line = ax.plot( rk, q, lw=1., color='r', ) lines["discharge"] = line + if "velocity" in self._y: + + ax = self._axes[unit["velocity"]] + v = list( + map( + lambda p: p.geometry.speed( + p.get_ts_key(self._timestamp, "Q"), + p.get_ts_key(self._timestamp, "Z")), + reach.profiles + ) + ) + + line = ax.plot( + rk, v, lw=1., + color='g', + ) + lines["velocity"] = line + + if "depth" in self._y: + + ax = self._axes[unit["depth"]] + d = list( + map( + lambda p: p.geometry.max_water_depth( + p.get_ts_key(self._timestamp, "Z")), + reach.profiles + ) + ) + line = ax.plot( + rk, d, + color='brown', lw=1., + ) + lines["depth"] = line + + if "mean_depth" in self._y: + + ax = self._axes[unit["mean_depth"]] + d = list( + map( + lambda p: p.geometry.mean_water_depth( + p.get_ts_key(self._timestamp, "Z")), + reach.profiles + ) + ) + + line = ax.plot( + rk, d, + color='orange', lw=1., + ) + lines["mean_depth"] = line + + if "froude" in self._y: + + ax = self._axes[unit["froude"]] + fr = list( + map( + lambda p: + p.geometry.speed( + p.get_ts_key(self._timestamp, "Q"), + p.get_ts_key(self._timestamp, "Z")) / + sqrt(9.81 * ( + p.geometry.wet_area( + p.get_ts_key(self._timestamp, "Z")) / + p.geometry.wet_width( + p.get_ts_key(self._timestamp, "Z")) + )), + reach.profiles + ) + ) + + line = ax.plot( + rk, fr, color='black', linestyle='--', lw=1., + ) + lines["froude"] = line + + if "wet_area" in self._y: + + ax = self._axes[unit["wet_area"]] + d = list( + map( + lambda p: p.geometry.wet_area( + p.get_ts_key(self._timestamp, "Z")), + reach.profiles + ) + ) + + line = ax.plot( + rk, d, + color='blue', linestyle='--', lw=1., + ) + lines["wet_area"] = line + # Legend lns = reduce( lambda acc, line: acc + line, @@ -151,7 +238,7 @@ class CustomPlot(PamhyrPlot): [] ) labs = list(map(lambda line: self._trad[line], lines)) - self.canvas.axes.legend(lns, labs, loc="lower left") + self.canvas.axes.legend(lns, labs, loc="best") def _customize_x_axes_time(self, ts, mode="time"): # Custom time display @@ -198,79 +285,137 @@ class CustomPlot(PamhyrPlot): reach = results.river.reach(self._reach) profile = reach.profile(self._profile) - meter_axes = self.canvas.axes - m3S_axes = self.canvas.axes - if "0-meter" in self._y_axes and "1-m3s" in self._y_axes: - m3s_axes = self._axes["1-m3s"] + shift = 0 + compt = 0 + for ax in sorted(self._axes): + if compt == 0: + self._axes[ax].spines['left'].set_position(('outward', shift)) + compt += 1 + else: + self._axes[ax].spines['right'].set_position(('outward', shift)) + shift += 60 ts = list(results.get("timestamps")) ts.sort() - # self.canvas.axes.set_xlim( - # left=min(ts), right=max(ts) - # ) + q = profile.get_key("Q") + z = profile.get_key("Z") + z_min = profile.geometry.z_min() + ts_z_min = list( + map( + lambda ts: z_min, + ts + ) + ) - x = ts lines = {} if "elevation" in self._y: # Z min is constant in time - z_min = profile.geometry.z_min() - ts_z_min = list( - map( - lambda ts: z_min, - ts - ) - ) - line = meter_axes.plot( + ax = self._axes[unit["elevation"]] + + line = ax.plot( ts, ts_z_min, color='grey', lw=1. ) lines["elevation"] = line if "water_elevation" in self._y: - # Water elevation - z = profile.get_key("Z") - # meter_axes.set_ylim( - # bottom=min(0, min(z)), - # top=max(z) + 1 - # ) - - line = meter_axes.plot( + ax = self._axes[unit["water_elevation"]] + line = ax.plot( ts, z, lw=1., color='b', ) lines["water_elevation"] = line if "elevation" in self._y: - z_min = profile.geometry.z_min() - ts_z_min = list( - map( - lambda ts: z_min, - ts - ) - ) - meter_axes.fill_between( + ax.fill_between( ts, ts_z_min, z, color='blue', alpha=0.5, interpolate=True ) if "discharge" in self._y: - q = profile.get_key("Q") - # m3s_axes.set_ylim( - # bottom=min(0, min(q)), - # top=max(q) + 1 - # ) - - line = m3s_axes.plot( + ax = self._axes[unit["discharge"]] + line = ax.plot( ts, q, lw=1., color='r', ) lines["discharge"] = line + if "velocity" in self._y: + + ax = self._axes[unit["velocity"]] + v = list( + map( + lambda q, z: profile.geometry.speed(q, z), + q, z + ) + ) + + line = ax.plot( + ts, v, lw=1., + color='g', + ) + lines["velocity"] = line + + if "depth" in self._y: + + ax = self._axes[unit["depth"]] + d = list( + map(lambda z: profile.geometry.max_water_depth(z), z) + ) + + line = ax.plot( + ts, d, + color='brown', lw=1., + ) + lines["depth"] = line + + if "mean_depth" in self._y: + + ax = self._axes[unit["mean_depth"]] + d = list( + map(lambda z: profile.geometry.mean_water_depth(z), z) + ) + + line = ax.plot( + ts, d, + color='orange', lw=1., + ) + lines["depth"] = line + + if "froude" in self._y: + + ax = self._axes[unit["froude"]] + d = 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) + ) + + line = ax.plot( + ts, d, color='black', linestyle='--', lw=1., + ) + lines["froude"] = line + + if "wet_area" in self._y: + + ax = self._axes[unit["wet_area"]] + d = list( + map(lambda z: profile.geometry.wet_area(z), z) + ) + + line = ax.plot( + ts, d, color='blue', linestyle='--', lw=1., + ) + lines["wet_area"] = line + self._customize_x_axes_time(ts) # Legend @@ -280,7 +425,7 @@ class CustomPlot(PamhyrPlot): [] ) labs = list(map(lambda line: self._trad[line], lines)) - self.canvas.axes.legend(lns, labs, loc="lower left") + self.canvas.axes.legend(lns, labs, loc="best") @timer def draw(self): @@ -300,6 +445,7 @@ class CustomPlot(PamhyrPlot): color='black', fontsize=10 ) + self._axes[self._y_axes[0]] = self.canvas.axes for axes in self._y_axes[1:]: if axes in self._axes: self._axes[axes].clear() @@ -316,8 +462,22 @@ class CustomPlot(PamhyrPlot): self._draw_rk() elif self._x == "time": self._draw_time() + if self._x == "rk": + reach = self.data.river.reach(self._reach) + profile = reach.profile(self._profile) + x = profile.rk + elif self._x == "time": + x = self._timestamp + + self._current, = self.canvas.axes.plot( + [x, x], + self.canvas.axes.get_ylim(), + # label=self.label_timestamp, + color='grey', + linestyle="dashed", + lw=1., + ) - self.canvas.figure.tight_layout() self.canvas.figure.canvas.draw_idle() if self.toolbar is not None: self.toolbar.update() @@ -326,6 +486,7 @@ class CustomPlot(PamhyrPlot): def update(self): if not self._init: self.draw() + self.draw_current() return def set_reach(self, reach_id): @@ -339,9 +500,23 @@ class CustomPlot(PamhyrPlot): if self._x != "rk": self.update() + else: + self.draw_current() def set_timestamp(self, timestamp): self._timestamp = timestamp if self._x != "time": self.update() + else: + self.draw_current() + + def draw_current(self): + if self._x == "rk": + reach = self.data.river.reach(self._reach) + profile = reach.profile(self._profile) + x = profile.rk + elif self._x == "time": + x = self._timestamp + self._current.set_data([x, x], self.canvas.axes.get_ylim()) + self.canvas.figure.canvas.draw_idle() diff --git a/src/View/Results/CustomPlot/Translate.py b/src/View/Results/CustomPlot/Translate.py index c32d2b58..b00bab0a 100644 --- a/src/View/Results/CustomPlot/Translate.py +++ b/src/View/Results/CustomPlot/Translate.py @@ -40,6 +40,14 @@ class CustomPlotTranslate(ResultsTranslate): self._dict['elevation'] = _translate( "CustomPlot", "Bed elevation (m)" ) + self._dict['velocity'] = self._dict["unit_speed"] + self._dict['width'] = self._dict["unit_width"] + self._dict['max_depth'] = self._dict["unit_max_height"] + self._dict['mean_depth'] = self._dict["unit_mean_height"] + self._dict['wet_area'] = self._dict["unit_wet_area"] + self._dict['wet_perimeter'] = self._dict["unit_wet_perimeter"] + self._dict['hydraulic_radius'] = self._dict["unit_hydraulic_radius"] + self._dict['froude'] = self._dict["unit_froude"] # Unit corresponding long name (plot axes display) @@ -47,6 +55,10 @@ class CustomPlotTranslate(ResultsTranslate): "CustomPlot", "Bed elevation (m)" ) self._dict['1-m3s'] = self._dict["unit_discharge"] + self._dict['2-ms'] = self._dict["unit_speed"] + 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 @@ -58,4 +70,9 @@ class CustomPlotTranslate(ResultsTranslate): "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/PlotH.py b/src/View/Results/PlotH.py index 2ac499e4..90aa6674 100644 --- a/src/View/Results/PlotH.py +++ b/src/View/Results/PlotH.py @@ -88,13 +88,14 @@ class PlotH(PamhyrPlot): self.draw_max(reach) self.draw_data(reach, profile) - self.draw_current(reach, profile) + self.draw_current() self.set_ticks_time_formater() self.enable_legend() self.idle() + self.update_current() self._init = True def draw_data(self, reach, profile): @@ -111,21 +112,12 @@ class PlotH(PamhyrPlot): **self.plot_default_kargs ) - def draw_current(self, reach, profile): - min_y, max_y = reduce( - lambda acc, p: ( - acc[0] + [min(p.get_key("Q"))], - acc[1] + [max(p.get_key("Q"))] - ), - reach.profiles, - ([], []) - ) - + def draw_current(self): self._current, = self.canvas.axes.plot( [self._current_timestamp, self._current_timestamp], - [min(min_y), max(max_y)], + self.canvas.axes.get_ylim(), # label=self.label_timestamp, - color=self.color_plot_river_bottom, + color="grey", linestyle="dashed", lw=1., ) @@ -162,14 +154,14 @@ class PlotH(PamhyrPlot): def set_timestamp(self, timestamp): self._current_timestamp = timestamp - self.update() + self.update_current() + self.update_idle() def update(self): if not self._init: self.draw() self.update_data() - self.update_idle() def update_data(self): @@ -181,8 +173,13 @@ class PlotH(PamhyrPlot): self._line.set_data(x, y) - _, min_max = self._current.get_data() self._current.set_data( self._current_timestamp, - min_max + self.canvas.axes.get_ylim() + ) + + def update_current(self): + self._current.set_data( + self._current_timestamp, + self.canvas.axes.get_ylim() ) diff --git a/src/View/Results/Table.py b/src/View/Results/Table.py index 7ce91f38..c92e994e 100644 --- a/src/View/Results/Table.py +++ b/src/View/Results/Table.py @@ -19,6 +19,8 @@ import logging import traceback +from numpy import sqrt + from tools import timer, trace from PyQt5.QtGui import ( @@ -86,12 +88,46 @@ class TableModel(PamhyrTableModel): elif self._headers[column] == "discharge": v = self._lst[row].get_ts_key(self._timestamp, "Q") return f"{v:.4f}" - elif self._headers[column] == "speed": + elif self._headers[column] == "velocity": q = self._lst[row].get_ts_key(self._timestamp, "Q") z = self._lst[row].get_ts_key(self._timestamp, "Z") - v = self._lst[row].geometry.speed(q, z) return f"{v:.4f}" + elif self._headers[column] == "width": + z = self._lst[row].get_ts_key(self._timestamp, "Z") + v = self._lst[row].geometry.wet_width(z) + return f"{v:.4f}" + elif self._headers[column] == "max_depth": + z = self._lst[row].get_ts_key(self._timestamp, "Z") + v = self._lst[row].geometry.max_water_depth(z) + return f"{v:.4f}" + elif self._headers[column] == "mean_depth": + z = self._lst[row].get_ts_key(self._timestamp, "Z") + v = self._lst[row].geometry.mean_water_depth(z) + return f"{v:.4f}" + elif self._headers[column] == "wet_area": + z = self._lst[row].get_ts_key(self._timestamp, "Z") + v = self._lst[row].geometry.wet_area(z) + return f"{v:.4f}" + elif self._headers[column] == "wet_perimeter": + z = self._lst[row].get_ts_key(self._timestamp, "Z") + v = self._lst[row].geometry.wet_perimeter(z) + return f"{v:.4f}" + elif self._headers[column] == "hydraulic_radius": + z = self._lst[row].get_ts_key(self._timestamp, "Z") + v = self._lst[row].geometry.wet_radius(z) + return f"{v:.4f}" + elif self._headers[column] == "froude": + q = self._lst[row].get_ts_key(self._timestamp, "Q") + z = self._lst[row].get_ts_key(self._timestamp, "Z") + v = self._lst[row].geometry.speed(q, z) + a = self._lst[row].geometry.wet_area(z) + b = self._lst[row].geometry.wet_width(z) + froude = v / sqrt(9.81 * (a / b)) + return f"{froude:.4f}" + else: + v = 0.0 + return f"{v:.4f}" return QVariant() diff --git a/src/View/Results/Window.py b/src/View/Results/Window.py index ac1f3585..03be2da7 100644 --- a/src/View/Results/Window.py +++ b/src/View/Results/Window.py @@ -40,7 +40,7 @@ from PyQt5.QtWidgets import ( QFileDialog, QTableView, QAbstractItemView, QUndoStack, QShortcut, QAction, QItemDelegate, QComboBox, QVBoxLayout, QHeaderView, QTabWidget, - QSlider, QLabel, QWidget, QGridLayout, + QSlider, QLabel, QWidget, QGridLayout, QTabBar ) from View.Tools.Plot.PamhyrCanvas import MplCanvas @@ -108,9 +108,9 @@ class ResultsWindow(PamhyrWindow): try: self._timestamps = sorted(list(self._results.get("timestamps"))) + self.setup_slider() self.setup_table() self.setup_plots() - self.setup_slider() self.setup_statusbar() self.setup_connections() except Exception as e: @@ -128,12 +128,11 @@ class ResultsWindow(PamhyrWindow): undo=self._undo_stack, opt_data=t ) + self._table[t]._timestamp = self._timestamps[ + self._slider_time.value()] def setup_slider(self): - self._slider_profile = self.find(QSlider, f"verticalSlider_profile") default_reach = self._results.river.reach(0) - self._slider_profile.setMaximum(len(default_reach.profiles) - 1) - self._slider_profile.setValue(0) self._slider_time = self.find(QSlider, f"horizontalSlider_time") self._slider_time.setMaximum(len(self._timestamps) - 1) @@ -158,6 +157,12 @@ class ResultsWindow(PamhyrWindow): def setup_plots(self): self.canvas = MplCanvas(width=5, height=4, dpi=100) + tab_widget = self.find(QTabWidget, f"tabWidget") + tab_widget.setTabsClosable(True) + tab_widget.tabCloseRequested.connect(self.delete_tab) + tab_widget.tabBar().setTabButton(0, QTabBar.RightSide, None) + tab_widget.tabBar().setTabButton(1, QTabBar.RightSide, None) + tab_widget.tabBar().setTabButton(2, QTabBar.RightSide, None) self.canvas.setObjectName("canvas") self.toolbar = PamhyrPlotToolbar( self.canvas, self, items=[ @@ -303,7 +308,8 @@ class ResultsWindow(PamhyrWindow): actions = { "action_reload": self._reload, "action_add": self._add_custom_plot, - "action_export": self.export, + # "action_export": self.export, + "action_export": self.export_current, } for action in actions: @@ -327,8 +333,6 @@ class ResultsWindow(PamhyrWindow): self._table[t].dataChanged.connect(fun[t]) - self._slider_profile.valueChanged.connect( - self._set_current_profile_slider) self._slider_time.valueChanged.connect(self._set_current_timestamp) self._button_play.setChecked(False) self._button_play.clicked.connect(self._pause) @@ -442,7 +446,6 @@ class ResultsWindow(PamhyrWindow): ind = indexes[0].row() self.update(profile_id=ind) - self._slider_profile.setValue(ind) def _set_current_profile_raw_data(self): table = self.find(QTableView, f"tableView_raw_data") @@ -452,11 +455,6 @@ class ResultsWindow(PamhyrWindow): ind = indexes[0].row() self.update(profile_id=ind) - self._slider_profile.setValue(ind) - - def _set_current_profile_slider(self): - pid = self._slider_profile.value() - self.update(profile_id=pid) def _set_current_timestamp(self): timestamp = self._timestamps[self._slider_time.value()] @@ -500,7 +498,7 @@ class ResultsWindow(PamhyrWindow): tab_widget = self.find(QTabWidget, f"tabWidget") # This plot already exists - if name in self._additional_plot: + if name in [tab_widget.tabText(i) for i in range(tab_widget.count())]: tab_widget.setCurrentWidget( tab_widget.findChild(QWidget, wname) ) @@ -591,12 +589,15 @@ class ResultsWindow(PamhyrWindow): ) def export_to(self, directory): + timestamps = sorted(self._results.get("timestamps")) for reach in self._results.river.reachs: - self.export_reach(reach, directory) + self.export_reach(reach, directory, timestamps) - def export_reach(self, reach, directory): + def export_reach(self, reach, directory, timestamps): name = reach.name name = name.replace(" ", "-") + if len(timestamps) == 1: + name = f"{name}_t{timestamps[0]}" file_name = os.path.join( directory, @@ -606,28 +607,40 @@ class ResultsWindow(PamhyrWindow): with open(file_name, 'w', newline='') as csvfile: writer = csv.writer(csvfile, delimiter=',', quotechar='|', quoting=csv.QUOTE_MINIMAL) - writer.writerow(["name", "rk", "data-file"]) - for profile in reach.profiles: - p_file_name = os.path.join( - directory, - f"cs_{profile.geometry.id}.csv" - ) + 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 - ]) + writer.writerow([ + profile.name, + profile.rk, + p_file_name + ]) - self.export_profile(reach, profile, 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): + 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"]) - timestamps = sorted(self._results.get("timestamps")) for ts in timestamps: writer.writerow([ @@ -635,3 +648,18 @@ class ResultsWindow(PamhyrWindow): profile.get_ts_key(ts, "Z"), profile.get_ts_key(ts, "Q"), ]) + + def export_current(self): + self.file_dialog( + select_file=False, + callback=lambda d: self.export_current_to(d[0]) + ) + + def export_current_to(self, directory): + reach = self._results.river.reachs[self._get_current_reach()] + self.export_reach(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) diff --git a/src/View/Results/translate.py b/src/View/Results/translate.py index 55c9c45e..faab6ecb 100644 --- a/src/View/Results/translate.py +++ b/src/View/Results/translate.py @@ -57,5 +57,12 @@ class ResultsTranslate(MainTranslate): "name": _translate("Results", "Profile"), "water_elevation": self._dict["unit_water_elevation"], "discharge": self._dict["unit_discharge"], - "speed": self._dict["unit_speed"], + "velocity": self._dict["unit_speed"], + "width": self._dict["unit_width"], + "max_depth": self._dict["unit_max_height"], + "mean_depth": self._dict["unit_mean_height"], + "wet_area": self._dict["unit_wet_area"], + "wet_perimeter": self._dict["unit_wet_perimeter"], + "hydraulic_radius": self._dict["unit_hydraulic_radius"], + "froude": self._dict["unit_froude"], } diff --git a/src/View/Tools/PamhyrPlot.py b/src/View/Tools/PamhyrPlot.py index 76159205..b893db84 100644 --- a/src/View/Tools/PamhyrPlot.py +++ b/src/View/Tools/PamhyrPlot.py @@ -192,7 +192,6 @@ class PamhyrPlot(APlot): self.canvas.axes.autoscale_view(True, True, True) self.canvas.axes.autoscale() - self.canvas.figure.tight_layout() self.canvas.figure.canvas.draw_idle() self.toolbar_update() @@ -205,7 +204,6 @@ class PamhyrPlot(APlot): self.canvas.axes.autoscale_view(True, True, True) self.canvas.axes.autoscale() - self.canvas.figure.tight_layout() self.canvas.figure.canvas.draw_idle() self.toolbar_update() diff --git a/src/View/Tools/Plot/PamhyrCanvas.py b/src/View/Tools/Plot/PamhyrCanvas.py index 1694d1fd..2101aa60 100644 --- a/src/View/Tools/Plot/PamhyrCanvas.py +++ b/src/View/Tools/Plot/PamhyrCanvas.py @@ -23,7 +23,7 @@ class MplCanvas(FigureCanvasQTAgg): fig = Figure( figsize=(width, height), dpi=dpi, - layout='tight', + layout='constrained', ) super(MplCanvas, self).__init__(fig) @@ -36,7 +36,6 @@ class MplCanvas(FigureCanvasQTAgg): self.axes.yaxis.tick_left() self.axes.xaxis.tick_bottom() self.axes.spines[['top', 'right']].set_color('none') - self.figure.tight_layout() self.add_arrows() def add_arrows(self): diff --git a/src/View/Translate.py b/src/View/Translate.py index 7e03dff9..f819660a 100644 --- a/src/View/Translate.py +++ b/src/View/Translate.py @@ -55,16 +55,18 @@ class UnitTranslate(CommonWordTranslate): def __init__(self): super(UnitTranslate, self).__init__() - self._dict["unit_rk"] = _translate("Unit", "River Kilometric (m)") + self._dict["unit_rk"] = _translate("Unit", "River Kilometer (m)") self._dict["unit_width"] = _translate("Unit", "Width (m)") - self._dict["unit_height"] = _translate("Unit", "Height (m)") + self._dict["unit_height"] = _translate("Unit", "Depth (m)") + self._dict["unit_max_height"] = _translate("Unit", "Max Depth (m)") + self._dict["unit_mean_height"] = _translate("Unit", "Mean Depth (m)") 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_speed"] = _translate("Unit", "Speed (m/s)") + 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)") @@ -74,6 +76,15 @@ 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_perimeter"] = _translate( + "Unit", "Wet Perimeter (m)" + ) + self._dict["unit_hydraulic_radius"] = _translate( + "Unit", "Hydraulic Radius (m)" + ) + self._dict["unit_froude"] = _translate("Unit", "Froude number") + class MainTranslate(UnitTranslate): def __init__(self): diff --git a/src/View/ui/BoundaryConditionsDialogGenerator.ui b/src/View/ui/BoundaryConditionsDialogGenerator.ui new file mode 100644 index 00000000..6d3793bf --- /dev/null +++ b/src/View/ui/BoundaryConditionsDialogGenerator.ui @@ -0,0 +1,98 @@ + + + Dialog + + + + 0 + 0 + 356 + 107 + + + + Options + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + Slope + + + + + + + 6 + + + 999999.998999999952503 + + + + + + + Estimate + + + + + + + + + + + buttonBox + accepted() + Dialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + Dialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/View/ui/EditBoundaryConditions.ui b/src/View/ui/EditBoundaryConditions.ui index b27232d2..c2c81378 100644 --- a/src/View/ui/EditBoundaryConditions.ui +++ b/src/View/ui/EditBoundaryConditions.ui @@ -73,6 +73,8 @@ + + @@ -119,6 +121,25 @@ Sort points + + + Generate uniform + + + Generate uniform + + + Generate rating curve from Manning law + + + + + Generate critical + + + Generate rating curve as Q(z) = Sqrt(g*S(z)^3/L(z)) + + diff --git a/src/View/ui/GeometryReach.ui b/src/View/ui/GeometryReach.ui index 97be0806..2d175bdc 100644 --- a/src/View/ui/GeometryReach.ui +++ b/src/View/ui/GeometryReach.ui @@ -92,6 +92,7 @@ + @@ -226,6 +227,14 @@ Purge cross-sections to keep a given number of points + + + Shift + + + Shift selected sections coordinates + + diff --git a/src/View/ui/GeometryReachShift.ui b/src/View/ui/GeometryReachShift.ui new file mode 100644 index 00000000..575dbb98 --- /dev/null +++ b/src/View/ui/GeometryReachShift.ui @@ -0,0 +1,134 @@ + + + Dialog + + + + 0 + 0 + 381 + 144 + + + + Dialog + + + Qt::LeftToRight + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + Y coordinate + + + + + + + X coordinate + + + + + + + 4 + + + -99999999.000000000000000 + + + 99999999.000000000000000 + + + + + + + 4 + + + -99999999.000000000000000 + + + 99999999.000000000000000 + + + + + + + true + + + Z coordinate + + + + + + + 4 + + + -99999999.000000000000000 + + + 99999999.000000000000000 + + + + + + + + + + + buttonBox + accepted() + Dialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + Dialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/View/ui/InitialConditions.ui b/src/View/ui/InitialConditions.ui index 403a5fe2..68ed2f94 100644 --- a/src/View/ui/InitialConditions.ui +++ b/src/View/ui/InitialConditions.ui @@ -27,14 +27,14 @@ - Generate from height + Generate height - Generate from discharge + Generate discharge diff --git a/src/View/ui/Results.ui b/src/View/ui/Results.ui index 9bbd4580..256ab368 100644 --- a/src/View/ui/Results.ui +++ b/src/View/ui/Results.ui @@ -39,19 +39,6 @@ - - - - Qt::Vertical - - - true - - - true - - - @@ -145,7 +132,10 @@ - 1 + 0 + + + true diff --git a/src/View/ui/UpdateKPOptions.ui b/src/View/ui/UpdateRKOptions.ui similarity index 100% rename from src/View/ui/UpdateKPOptions.ui rename to src/View/ui/UpdateRKOptions.ui diff --git a/src/lang/fr.ts b/src/lang/fr.ts index 81b4d312..dc68faec 100644 --- a/src/lang/fr.ts +++ b/src/lang/fr.ts @@ -2168,13 +2168,13 @@ - Generate from height - Générer pour une hauteur donnée + Generate height + Générer une hauteur - Generate from discharge - Générer pour un débit donné + Generate discharge + Générer un débit @@ -3057,7 +3057,7 @@ - Height (m) + Depth (m) Hauteur (m)