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)