diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 97812b42..a64fdc12 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -60,10 +60,6 @@ dl-mage8-linux: artifacts: paths: - mage8-linux/mage - - mage8-linux/mage_as7 - - mage8-linux/mage_extraire - - mage8-linux/mailleurTT - - mage8-linux/libbief.so dl-mage8-windows: stage: downloads @@ -79,10 +75,6 @@ dl-mage8-windows: artifacts: paths: - mage8-windows/mage.exe - - mage8-windows/mage_as7.exe - - mage8-windows/mage_extraire.exe - - mage8-windows/mailleurTT.exe - - mage8-windows/libbief.dll dl-adists-linux: stage: downloads diff --git a/doc/users/TP_Hydraulique_Hogneau/step2.pamhyr b/doc/users/TP_Hydraulique_Hogneau/step2.pamhyr index c7516dff..95c66480 100644 Binary files a/doc/users/TP_Hydraulique_Hogneau/step2.pamhyr and b/doc/users/TP_Hydraulique_Hogneau/step2.pamhyr differ diff --git a/src/Meshing/Internal.py b/src/Meshing/Internal.py new file mode 100644 index 00000000..ed9a5c48 --- /dev/null +++ b/src/Meshing/Internal.py @@ -0,0 +1,331 @@ +# Internal.py -- Pamhyr +# Copyright (C) 2023-2025 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 -*- + +import logging +from numpy import sign +from copy import deepcopy + +from tools import logger_color_red, logger_color_reset, logger_exception +from Meshing.AMeshingTool import AMeshingTool + +from Model.Geometry.ProfileXYZ import ProfileXYZ + +logger = logging.getLogger() + + +class InternalMeshing(AMeshingTool): + def __init__(self): + super(InternalMeshing, self).__init__() + + ########### + # Meshing # + ########### + + def meshing(self, reach, + step: float = 50, + limites=[-1, -1], + linear: bool = True): + if reach is None or len(reach.profiles) == 0: + return reach + + if limites[0] > limites[1]: + lim = limites[1] + limites[1] = limites[0] + limites[0] = lim + elif limites[0] == limites[1]: + return reach + + self.st_to_m(reach, limites) + self.interpolate_transversal_step(reach, + limites, + step) + self.purge_aligned_points(reach) + + return reach + + def st_to_m(self, reach, limites): + + gl = reach.compute_guidelines() + profiles = reach.profiles[limites[0]:limites[1]+1] + + print(profiles) + + # we make sure that the lines are in the left-to-right order + guide_list = [ + x.name + for x in profiles[0].named_points() + if x.name in gl[0] + ] + + gl1 = [""] + guide_list + gl2 = guide_list + [""] + max_values = [0] * (len(guide_list) + 1) + max_values_index = [0] * (len(guide_list) + 1) + # we work between each couple of guidelines + for j, p in enumerate(profiles): + index1 = 0 + index2 = p.named_point_index(guide_list[0]) + if index2 - index1 > max_values[0]: + max_values[0] = index2 - index1 + max_values_index[0] = j + for i in range(len(guide_list) - 1): + index1 = p.named_point_index(guide_list[i]) + index2 = p.named_point_index(guide_list[i+1]) + if index2 - index1 > max_values[i + 1]: + max_values[i + 1] = index2 - index1 + max_values_index[i + 1] = j + index1 = p.named_point_index(guide_list[-1]) + index2 = len(p) - 1 + if index2 - index1 > max_values[-1]: + max_values[-1] = index2 - index1 + max_values_index[-1] = j + + for i in range(len(max_values)): + for isect in range(max_values_index[i], len(profiles)-1): + self.compl_sect(profiles[isect], + profiles[isect+1], + gl1[i], gl2[i]) + for isect in reversed(range(0, max_values_index[i])): + self.compl_sect(profiles[isect+1], + profiles[isect], + gl1[i], gl2[i]) + + def interpolate_transversal_step(self, + reach, + limites, + step): + # calcul number of intermediate profiles + np = [] + for i in range(limites[0], limites[1]): + np.append(int((reach.profiles[i+1].rk - + reach.profiles[i].rk) / step) - 1) + if np[-1] < 0: + np[-1] = 0 + + d = [] # ratios + p = [] # new profiles + ptr = int(limites[0]) + for i in range(limites[1] - limites[0]): + d = 1.0/float(np[i]+1) + ptr0 = ptr + for j in range(np[i]): + p = reach.profiles[ptr0].copy() + # RATIO entre les deux sections initiales + dj = float(j+1)*d + ptr += 1 # next profile, original + for k in range(len(reach.profiles[ptr0].points)): + p.points[k].x = reach.profiles[ptr0].points[k].x + \ + dj*(reach.profiles[ptr].points[k].x - + reach.profiles[ptr0].points[k].x) + p.points[k].y = reach.profiles[ptr0].points[k].y + \ + dj*(reach.profiles[ptr].points[k].y - + reach.profiles[ptr0].points[k].y) + p.points[k].z = reach.profiles[ptr0].points[k].z + \ + dj*(reach.profiles[ptr].points[k].z - + reach.profiles[ptr0].points[k].z) + p.rk = reach.profiles[ptr0].rk + \ + dj*(reach.profiles[ptr].rk - + reach.profiles[ptr0].rk) + p.name = f'interpol{p.rk}' + reach.insert_profile(ptr, p) + ptr += 1 # next profile, original + + def compl_sect(self, sect1, sect2, tag1, tag2): + # sect1: reference section + # sect2: section to complete + # start1 and 2: indices of the first point of the bed to complete + # end = start + len + # len1 and 2: number of intervals + # len1: target len + + if tag1 == '': # left end point + start1 = 0 + start2 = 0 + else: + start1 = sect1.named_point_index(tag1) + start2 = sect2.named_point_index(tag1) + + if tag2 == '': # right end point + end1 = sect1.nb_points-1 + end2 = sect2.nb_points-1 + else: + end1 = sect1.named_point_index(tag2) + end2 = sect2.named_point_index(tag2) + + len1 = end1 - start1 + len2 = end2 - start2 + + if len1 < len2: + self.compl_sect(sect2, sect1, start2, start1, + len2, len1, tag1, tag2) + return + elif len1 == len2: + return + + ltot1 = 0.0 + ltot2 = 0.0 + alpha = [] + beta = [] + # remove names + sect2.points[start2].name = '' + sect2.points[end2].name = '' + for i in range(len1): + ltot1 += sect1.point(start1+i).dist(sect1.point(start1+i+1)) + alpha.append(ltot1) + alpha = list(map(lambda x: x/ltot1, alpha)) # target ratios + for i in range(len2): + ltot2 += sect2.point(start2+i).dist(sect2.point(start2+i+1)) + beta.append(ltot2) + beta = list(map(lambda x: x/ltot2, beta)) # current ratios + for i in range(len1 - len2): + beta.append(1.0) + beta.append(0.0) # beta[-1] + if len2 < 1 or ltot2 < 0.0001: + # unique point (copy len1 times) + # we are at an edge of the profile + for i in range(len1-len2): + p = sect2.point(start2).copy() + sect2.insert_point(start2, p) + if tag1 == '': # left end point + sect2.point(start2).name = '' + if tag2 == '': # left end point + sect2.point(start2+1).name = '' + elif ltot1 < 0.0001: + sect2.add_npoints(len1-len2) + else: # regular case + # loop on the points to insert + for k in range(len1-len2): + trouve = False + for j0 in range(len2): + if trouve: + break + undeplus = True + for j1 in range(j0, len2): + if undeplus: + if beta[j1] <= alpha[j1]: + undeplus = False + break + if undeplus: + if j0 == len2: + trouve = True + else: + j1 = j0+1 + if beta[j0]-alpha[j0] > alpha[j1]-beta[j0]: + trouve = True + + if trouve: + len2 += 1 + for i in reversed(range(j0, len2)): + beta[i+1] = beta[i] + if (beta[j0] == beta[j0-1]): + # rien + pass + else: + ratio = (alpha[j0] - beta[j0-1]) \ + / (beta[j0] - beta[j0-1]) + if ratio < 0.0: + print(f"ratio négatif {ratio}") + # on double le point a gauche + p = sect2.point(start2+j0-1).copy() + sect2.insert_point(start2+j0-1, p) + sect2.point(start2+j0-1).name = '' + beta[j0] = beta[j0-1] + else: + p = sect2.point(start2+j0).copy() + sect2.insert_point(start2+j0, p) + sect2.point(start2+j0+1).name = '' + sect2.point(start2+j0+1).x = \ + (1.0-ratio) * sect2.point(start2+j0).x + \ + ratio * sect2.point(start2+j0+2).x + sect2.point(start2+j0+1).y = \ + (1.0-ratio) * sect2.point(start2+j0).y + \ + ratio * sect2.point(start2+j0+2).y + sect2.point(start2+j0+1).z = \ + (1.0-ratio) * sect2.point(start2+j0).z + \ + ratio * sect2.point(start2+j0+2).z + beta[j0] = (1.-ratio)*beta[j0-1] \ + + ratio*beta[j0+1] + + if len1 > len2: + for i in range(len1 - len2): + p = sect2.point(start2+len2).copy() + sect2.insert_point(start2+len2, p) + sect2.point(start2+len2).name = '' + sect2.point(start2).name = tag1 + sect2.point(start2+len2).name = tag2 + sect2.modified() + + def update_rk(self, reach, + step: float = 50, + limites=[-1, -1], + origin=0, + directrices=['un', 'np'], + lplan: bool = False, + lm: int = 3, + linear: bool = False, + origin_value=0.0, + orientation=0): + if reach is None or len(reach.profiles) == 0: + return reach + + nprof = reach.number_profiles + if origin >= nprof or origin < 0: + logger.warning( + f"Origin outside reach" + ) + return reach + + # orientation is the orintation of the bief: + # 1: up -> downstream + # 2: down -> upstream + # else: keep current orientation + if orientation == 1: + sgn = 1.0 + elif orientation == 2: + sgn = -1.0 + else: + sgn = sign(reach.profiles[-1].rk - reach.profiles[0].rk) + + reach.profiles[origin].rk = origin_value + + if origin < nprof - 1: + for i in range(origin+1, nprof): + # 2D + d1 = reach.profiles[i-1].named_point( + directrices[0] + ).dist_2d(reach.profiles[i].named_point(directrices[0])) + d2 = reach.profiles[i-1].named_point( + directrices[1] + ).dist_2d(reach.profiles[i].named_point(directrices[1])) + reach.profiles[i].rk = reach.profiles[i-1].rk \ + + (sgn * (d1 + d2) / 2) + if origin > 0: + for i in reversed(range(0, origin)): + # 2D + d1 = reach.profiles[i+1].named_point( + directrices[0] + ).dist_2d(reach.profiles[i].named_point(directrices[0])) + d2 = reach.profiles[i+1].named_point( + directrices[1] + ).dist_2d(reach.profiles[i].named_point(directrices[1])) + reach.profiles[i].rk = reach.profiles[i+1].rk \ + - (sgn * (d1 + d2) / 2) + + def purge_aligned_points(self, reach): + for p in reach.profiles: + p.purge_aligned_points() diff --git a/src/Meshing/Mage.py b/src/Meshing/Mage.py index 2d28fb04..0dd271ff 100644 --- a/src/Meshing/Mage.py +++ b/src/Meshing/Mage.py @@ -19,6 +19,7 @@ import os import logging import tempfile +from numpy import sign from ctypes import ( cdll, diff --git a/src/Model/Geometry/PointXYZ.py b/src/Model/Geometry/PointXYZ.py index 9ba79e66..7a233a54 100644 --- a/src/Model/Geometry/PointXYZ.py +++ b/src/Model/Geometry/PointXYZ.py @@ -228,3 +228,25 @@ class PointXYZ(Point, SQLSubModel): c = PointXYZ.distance(p3, p1) s = (a + b + c) / 2 return (s*(s-a) * (s-b)*(s-c)) ** 0.5 + + def dist_from_seg(self, p1, p2): + a2 = pow(PointXYZ.distance(self, p1), 2) + b2 = pow(PointXYZ.distance(self, p2), 2) + c2 = pow(PointXYZ.distance(p1, p2), 2) + if c2 < 0.00000001: + # si les deux points sont confondus + d = 0.0 + else: + d = np.sqrt(abs(a2 - pow((c2 - b2 + a2) / (2*np.sqrt(c2)), 2))) + + return d + + def copy(self): + p = PointXYZ(self.x, + self.y, + self.z, + self.name, + self._profile, + self._status) + p.sl = self.sl + return p diff --git a/src/Model/Geometry/Profile.py b/src/Model/Geometry/Profile.py index 6a4b45ef..7f9d5373 100644 --- a/src/Model/Geometry/Profile.py +++ b/src/Model/Geometry/Profile.py @@ -88,6 +88,14 @@ class Profile(object): def point(self, index): return self.points[index] + def named_point(self, name): + return next((p for p in self.points if p.name == name), None) + + def named_point_index(self, name): + return next( + (p for p in enumerate(self.points) if p[1].name == name), None + )[0] + @property def reach(self): return self._reach @@ -206,7 +214,7 @@ class Profile(object): """Insert point at index. Args: - index: The index of new profile. + index: The index of new point. point: The point. Returns: diff --git a/src/Model/Geometry/ProfileXYZ.py b/src/Model/Geometry/ProfileXYZ.py index 6a4f196b..db92bd57 100644 --- a/src/Model/Geometry/ProfileXYZ.py +++ b/src/Model/Geometry/ProfileXYZ.py @@ -882,3 +882,46 @@ class ProfileXYZ(Profile, SQLSubModel): def modified(self): self.tab_up_to_date = False self.station_up_to_date = False + + def add_npoints(self, npoints): + # add npoints in a profile + for k in range(npoints): + disti = 0.0 + i = 1 + for j in range(len(self.points)-1): + distj = self.point(j).dist(self.point(j+1)) + if distj > disti: + disti = distj + i = j + self.insert_point(i, self.point(i).copy()) + self.point(i+1).name = '' + self.point(i+1).x = 0.5 * self.point(i).x + 0.5 * self.point(i+2).x + self.point(i+1).y = 0.5 * self.point(i).y + 0.5 * self.point(i+2).y + self.point(i+1).z = 0.5 * self.point(i).z + 0.5 * self.point(i+2).z + + def copy(self): + p = ProfileXYZ(self.id, + self.name, + self.rk, + self.reach, + self.num, + 0, + 0, 0, + self._status) + for i, k in enumerate(self.points): + p.insert_point(i, k.copy()) + + return p + + def purge_aligned_points(self): + + align = True + while (align): + align = False + for i in range(1, self.number_points - 1): + d = self.point(i).dist_from_seg(self.point(i-1), + self.point(i+1)) + if d < 0.00001 and self.point(i).name == "": + align = True + self.delete_i([i]) + break diff --git a/src/Model/Results/River/River.py b/src/Model/Results/River/River.py index fbf979c0..0497a167 100644 --- a/src/Model/Results/River/River.py +++ b/src/Model/Results/River/River.py @@ -77,6 +77,12 @@ class Reach(object): ) ) + self._profile_mask = list( + map( + lambda p: p.name[0:8] != 'interpol', self._profiles + ) + ) + def __len__(self): return len(self._profiles) @@ -92,6 +98,10 @@ class Reach(object): def profiles(self): return self._profiles.copy() + @property + def profile_mask(self): + return self._profile_mask + def profile(self, id): return self._profiles[id] diff --git a/src/Model/SedimentLayer/SedimentLayer.py b/src/Model/SedimentLayer/SedimentLayer.py index b31238cd..b8fc19dc 100644 --- a/src/Model/SedimentLayer/SedimentLayer.py +++ b/src/Model/SedimentLayer/SedimentLayer.py @@ -277,18 +277,19 @@ class SedimentLayer(SQLSubModel): "FROM sedimentary_layer " ) - for row in table: - sl = cls( - id=row[0], - name=row[1], - comment=row[2], - status=data['status'] - ) + if table is not None: + for row in table: + sl = cls( + id=row[0], + name=row[1], + comment=row[2], + status=data['status'] + ) - data["sl"] = sl.id - sl._layers = Layer._db_load(execute, data) + data["sl"] = sl.id + sl._layers = Layer._db_load(execute, data) - new.append(sl) + new.append(sl) return new diff --git a/src/Solver/RubarBE.py b/src/Solver/RubarBE.py index bf946f42..ab50532a 100644 --- a/src/Solver/RubarBE.py +++ b/src/Solver/RubarBE.py @@ -74,10 +74,10 @@ class Rubar3(CommandLineSolver): ("rubarbe_tf_5", "y"), ("rubarbe_tf_6", "n"), ("rubarbe_trased", "y"), - ("rubarbe_optfpc", "0"), - ("rubarbe_ros", "2650.0"), - ("rubarbe_dm", "0.1"), - ("rubarbe_segma", "1.0"), + # ("rubarbe_optfpc", "0"), + # ("rubarbe_ros", "2650.0"), + # ("rubarbe_dm", "0.1"), + # ("rubarbe_segma", "1.0"), # Sediment parameters ("rubarbe_sediment_ros", "2650.0"), ("rubarbe_sediment_por", "0.4"), @@ -167,7 +167,7 @@ class Rubar3(CommandLineSolver): it = iter(params) line = 0 - while line < 29: + while line < 25: param = next(it) name = param.name value = param.value @@ -277,19 +277,20 @@ class Rubar3(CommandLineSolver): f.write(f"{ind:>4} {rk:>11.3f} {n_points:>4}\n") - for point in profile.points: + station = profile.get_station() + for i, point in enumerate(profile.points): label = point.name.lower() if label != "": if label[0] == "r": label = label[1].upper() else: - label = label[1].upper() + label = " " else: label = " " - y = point.y + y = station[i] z = point.z - dcs = 0.001 + dcs = 1.0 scs = 1.0 tmcs = 0.0 @@ -444,10 +445,10 @@ class Rubar3(CommandLineSolver): z = data[profile.rk][0] q = data[profile.rk][1] - # height = z - profile.z_min() + height = z - profile.z_min() speed = profile.speed(q, z) - return z, speed + return height, speed def _export_hydro(self, study, repertory, qlog, name="0"): if qlog is not None: @@ -470,7 +471,7 @@ class Rubar3(CommandLineSolver): for bc in bcs: f.write(f"{len(bc)}\n") for d0, d1 in bc.data: - f.write(f"{d0} {d1}\n") + f.write(f"{d1} {d0}\n") def _export_condav(self, study, repertory, qlog, name="0"): if qlog is not None: @@ -493,7 +494,7 @@ class Rubar3(CommandLineSolver): for bc in bcs: f.write(f"{len(bc)}\n") for d0, d1 in bc.data: - f.write(f"{d0} {d1}\n") + f.write(f"{d1} {d0}\n") class RubarBE(Rubar3): diff --git a/src/Solver/Solvers.py b/src/Solver/Solvers.py index 71e82bb9..9ddfd2c1 100644 --- a/src/Solver/Solvers.py +++ b/src/Solver/Solvers.py @@ -34,7 +34,7 @@ solver_long_name = { # "mage_fake7": "Mage fake v7", "adistswc": "Adis-TS_WC", # "rubarbe": "RubarBE", - # "rubar3": "Rubar3", + "rubar3": "Rubar3", } solver_type_list = { @@ -44,5 +44,5 @@ solver_type_list = { # "mage_fake7": MageFake7, "adistswc": AdisTSwc, # "rubarbe": RubarBE, - # "rubar3": Rubar3, + "rubar3": Rubar3, } diff --git a/src/View/BoundaryCondition/Edit/Table.py b/src/View/BoundaryCondition/Edit/Table.py index a7481666..313abb89 100644 --- a/src/View/BoundaryCondition/Edit/Table.py +++ b/src/View/BoundaryCondition/Edit/Table.py @@ -211,3 +211,26 @@ class TableModel(PamhyrTableModel): ) self.layoutAboutToBeChanged.emit() self.update() + + def read_from_file(self, file_name, bctype): + + logger.debug(f"Import boundary conditions from {file_name}") + data0 = [] + data1 = [] + if bctype == "ZD": + mult = 1 + else: + mult = 60 + with open(file_name, encoding="utf-8") as bc_file: + for line in bc_file: + if not (line.startswith("#") or + line.startswith("*") or + line.startswith("$")): + line = line.split() + if bctype == "ZD": + data0.append(float(line[0])) + else: + data0.append(old_pamhyr_date_to_timestamp(line[0])) + data1.append(line[1]) + + self.replace_data(data0, data1) diff --git a/src/View/BoundaryCondition/Edit/Window.py b/src/View/BoundaryCondition/Edit/Window.py index 88dbae69..64dccbf3 100644 --- a/src/View/BoundaryCondition/Edit/Window.py +++ b/src/View/BoundaryCondition/Edit/Window.py @@ -30,10 +30,10 @@ from PyQt5.QtGui import ( QKeySequence, ) -from PyQt5 import QtCore +from PyQt5 import QtCore, QtWidgets from PyQt5.QtCore import ( Qt, QVariant, QAbstractTableModel, QCoreApplication, - pyqtSlot, pyqtSignal, + pyqtSlot, pyqtSignal, QSettings, ) from PyQt5.QtWidgets import ( @@ -198,6 +198,8 @@ class EditBoundaryConditionWindow(PamhyrWindow): self.find(QAction, "action_add").triggered.connect(self.add) self.find(QAction, "action_del").triggered.connect(self.delete) self.find(QAction, "action_sort").triggered.connect(self.sort) + self.find(QAction, "action_import").triggered\ + .connect(self.import_from_file) self.find(QAction, "action_generate_uniform").triggered.connect( self.generate_uniform ) @@ -293,6 +295,31 @@ class EditBoundaryConditionWindow(PamhyrWindow): self._table.sort(False) self.plot.update() + def import_from_file(self): + options = QFileDialog.Options() + settings = QSettings(QSettings.IniFormat, + QSettings.UserScope, 'MyOrg', ) + options |= QFileDialog.DontUseNativeDialog + + if self._data.bctype == "TD": + file_types = [self._trad["file_hyd"]] + if self._data.bctype == "TZ": + file_types = [self._trad["file_lim"]] + if self._data.bctype == "ZD": + file_types = [self._trad["file_ava"]] + file_types.append(self._trad["file_all"]) + + file_name, _ = QtWidgets.QFileDialog.getOpenFileName( + self, + self._trad["open_file"], + "", + ";; ".join(file_types), + options=options + ) + + if file_name != "": + self._table.read_from_file(file_name, self._data.bctype) + def move_up(self): row = self.index_selected_row() self._table.move_up(row) diff --git a/src/View/BoundaryCondition/Edit/translate.py b/src/View/BoundaryCondition/Edit/translate.py index 76b8ff3e..3312c7d0 100644 --- a/src/View/BoundaryCondition/Edit/translate.py +++ b/src/View/BoundaryCondition/Edit/translate.py @@ -35,6 +35,17 @@ class BCETranslate(BCTranslate): self._dict["Boundary Condition Options"] = _translate( "BoundaryCondition", "Boundary Condition Options") + self._dict["open_file"] = _translate( + "BoundaryCondition", "Open a file") + self._dict["file_hyd"] = _translate( + "BoundaryCondition", "Mage hydrograph file (*.HYD)") + self._dict["file_lim"] = _translate( + "BoundaryCondition", "Mage limnigraph file (*.LIM)") + self._dict["file_ava"] = _translate( + "BoundaryCondition", "Mage rating curve file (*.AVA)") + self._dict["file_all"] = _translate( + "BoundaryCondition", "All files (*)") + self._sub_dict["table_headers"] = { "x": _translate("BoundaryCondition", "X"), "y": _translate("BoundaryCondition", "Y"), diff --git a/src/View/Frictions/Table.py b/src/View/Frictions/Table.py index 2347a41d..7b5d278c 100644 --- a/src/View/Frictions/Table.py +++ b/src/View/Frictions/Table.py @@ -38,7 +38,7 @@ from View.Frictions.UndoCommand import ( SetNameCommand, SetBeginCommand, SetEndCommand, SetBeginStricklerCommand, SetEndStricklerCommand, AddCommand, DelCommand, SortCommand, - MoveCommand, PasteCommand, DuplicateCommand, + MoveCommand, PasteCommand, DuplicateCommand, ReplaceDataCommand, ) from View.Tools.PamhyrTable import PamhyrTableModel @@ -99,7 +99,7 @@ class ComboBoxDelegate(QItemDelegate): self.commitData.emit(self.sender()) -class TableModel(PamhyrTableModel): +class FrictionTableModel(PamhyrTableModel): def _setup_lst(self): self._lst = self._data.frictions self._study = self._opt_data @@ -199,6 +199,18 @@ class TableModel(PamhyrTableModel): self.endRemoveRows() self.layoutChanged.emit() + def replace_data(self, new_data): + self.layoutAboutToBeChanged.emit() + + self._undo.push( + ReplaceDataCommand( + self._lst, new_data + ) + ) + + self.layoutAboutToBeChanged.emit() + self.layoutChanged.emit() + def sort(self, _reverse, parent=QModelIndex()): self.layoutAboutToBeChanged.emit() @@ -252,3 +264,32 @@ class TableModel(PamhyrTableModel): def redo(self): self._undo.redo() self.layoutChanged.emit() + + def read_from_file(self, file_name): + + reach_id = self._study.river.enable_edges().index(self._data) + 1 + + logger.debug(f"Import frictions from {file_name}") + data = [] + with open(file_name, encoding="utf-8") as rug_file: + for line in rug_file: + if line.upper().startswith("K"): + if int(line[1:4]) == reach_id: + data.append(line[4:].split()) + + new_data = [] + for d in data: + new = None + minor = float(d[2]) + medium = float(d[3]) + for s in self._study.river.stricklers.stricklers: + if s.minor == minor and s.medium == medium: + new = s + break + if new is None: + new = self._study.river.stricklers.new(len( + self._study.river.stricklers)) + new.minor = minor + new.medium = medium + new_data.append([self._data, float(d[0]), float(d[1]), new, new]) + self.replace_data(new_data) diff --git a/src/View/Frictions/UndoCommand.py b/src/View/Frictions/UndoCommand.py index 6aab35d5..dc060f00 100644 --- a/src/View/Frictions/UndoCommand.py +++ b/src/View/Frictions/UndoCommand.py @@ -143,6 +143,42 @@ class AddCommand(QUndoCommand): self._frictions.insert(self._index, self._new) +class ReplaceDataCommand(QUndoCommand): + def __init__(self, frictions, new_data): + QUndoCommand.__init__(self) + + self._frictions = frictions + self._new_data = new_data + self._old_rows = list(range(len(frictions))) + self._new_rows = list(range(len(new_data))) + self._new = [] + + self._old_friction = [] + for row in self._old_rows: + self._old_friction.append((row, self._frictions.lst[row])) + + def undo(self): + self._frictions.delete_i(self._new_rows) + for row, el in self._old_friction: + self._frictions.insert(row, el) + + def redo(self): + if len(self._new) == 0: + self._frictions.delete_i(self._old_rows) + for row in self._new_rows: + new = self._frictions.new(row) + d = self._new_data[row] + new.edge = d[0] + new.begin_rk = d[1] + new.end_rk = d[2] + new.begin_strickler = d[3] + new.end_strickler = d[4] + self._new.append((row, new)) + else: + for row, el in self._new: + self._frictions.insert(row, el) + + class DelCommand(QUndoCommand): def __init__(self, frictions, rows): QUndoCommand.__init__(self) diff --git a/src/View/Frictions/Window.py b/src/View/Frictions/Window.py index 8e4959ea..f335a777 100644 --- a/src/View/Frictions/Window.py +++ b/src/View/Frictions/Window.py @@ -26,10 +26,11 @@ from PyQt5.QtGui import ( QKeySequence, ) +from PyQt5 import QtWidgets from PyQt5.QtCore import ( Qt, QVariant, QAbstractTableModel, QCoreApplication, QModelIndex, pyqtSlot, - QRect, + QRect, QSettings, ) from PyQt5.QtWidgets import ( @@ -46,7 +47,7 @@ from View.Frictions.UndoCommand import ( ) from View.Frictions.Table import ( - TableModel, ComboBoxDelegate + FrictionTableModel, ComboBoxDelegate ) from View.Tools.Plot.PamhyrCanvas import MplCanvas @@ -106,7 +107,7 @@ class FrictionsWindow(PamhyrWindow): ) table = self.find(QTableView, f"tableView") - self._table = TableModel( + self._table = FrictionTableModel( table_view=table, table_headers=self._trad.get_dict("table_headers"), editable_headers=[ @@ -157,6 +158,8 @@ class FrictionsWindow(PamhyrWindow): self.plot_2.draw() def setup_connections(self): + self.find(QAction, "action_import").triggered\ + .connect(self.import_from_file) self.find(QAction, "action_add").triggered.connect(self.add) self.find(QAction, "action_del").triggered.connect(self.delete) self.find(QAction, "action_sort").triggered.connect(self.sort) @@ -265,3 +268,22 @@ class FrictionsWindow(PamhyrWindow): parent=self ) strick.show() + + def import_from_file(self): + options = QFileDialog.Options() + settings = QSettings(QSettings.IniFormat, + QSettings.UserScope, 'MyOrg', ) + options |= QFileDialog.DontUseNativeDialog + + file_types = [self._trad["file_rug"], self._trad["file_all"]] + + file_name, _ = QtWidgets.QFileDialog.getOpenFileName( + self, + self._trad["open_file"], + "", + ";; ".join(file_types), + options=options + ) + + if file_name != "": + self._table.read_from_file(file_name) diff --git a/src/View/Frictions/translate.py b/src/View/Frictions/translate.py index 9f2d329b..969b9849 100644 --- a/src/View/Frictions/translate.py +++ b/src/View/Frictions/translate.py @@ -38,6 +38,10 @@ class FrictionsTranslate(MainTranslate): self._dict["Edit frictions"] = _translate( "Frictions", "Edit frictions" ) + self._dict["file_rug"] = _translate( + "Frictions", "Mage initial frictions file (*.RUG *.rug)") + self._dict["file_all"] = _translate( + "Frictions", "All files (*)") self._sub_dict["table_headers"] = { # "name": self._dict["name"], diff --git a/src/View/Geometry/MeshingDialog.py b/src/View/Geometry/MeshingDialog.py index 07478f6e..3f1875a6 100644 --- a/src/View/Geometry/MeshingDialog.py +++ b/src/View/Geometry/MeshingDialog.py @@ -23,12 +23,12 @@ from PyQt5.QtGui import ( ) from PyQt5.QtCore import ( - Qt, QVariant, QAbstractTableModel, + Qt, QVariant, QAbstractTableModel, QItemSelectionModel, ) from PyQt5.QtWidgets import ( QDialogButtonBox, QComboBox, QUndoStack, QShortcut, - QDoubleSpinBox, + QDoubleSpinBox, QRadioButton, ) @@ -55,7 +55,7 @@ class MeshingDialog(PamhyrDialog): self._space_step = 50.0 self._lplan = False self._lm = "3" - self._linear = False + self._linear = True self._begin_cs = -1 self._end_cs = -1 self._begin_dir = "un" @@ -71,7 +71,7 @@ class MeshingDialog(PamhyrDialog): self._origin = self._reach.profile(0) self._init_default_values_profiles() - self._init_default_values_guidelines() + # self._init_default_values_guidelines() self.set_double_spin_box( "doubleSpinBox_space_step", @@ -83,14 +83,24 @@ class MeshingDialog(PamhyrDialog): else: self.set_radio_button("radioButton_spline", True) + self.find(QRadioButton, "radioButton_spline").setEnabled(False) + self.find(QRadioButton, "radioButton_linear").setEnabled(False) + def _init_default_values_profiles(self): profiles = self.profiles self.combobox_add_items("comboBox_begin_rk", profiles) self.combobox_add_items("comboBox_end_rk", profiles) - self.set_combobox_text("comboBox_begin_rk", profiles[0]) - self.set_combobox_text("comboBox_end_rk", profiles[-1]) + r = self.parent.tableView\ + .selectionModel()\ + .selectedRows() + if len(r) <= 1: + self.set_combobox_text("comboBox_begin_rk", profiles[0]) + self.set_combobox_text("comboBox_end_rk", profiles[-1]) + else: + self.set_combobox_text("comboBox_begin_rk", profiles[r[0].row()]) + self.set_combobox_text("comboBox_end_rk", profiles[r[-1].row()]) @property def profiles(self): @@ -167,8 +177,8 @@ class MeshingDialog(PamhyrDialog): self._begin_cs = self.profiles.index(p1) self._end_cs = self.profiles.index(p2) - self._begin_dir = self.get_combobox_text("comboBox_begin_gl") - self._end_dir = self.get_combobox_text("comboBox_end_gl") + # self._begin_dir = self.get_combobox_text("comboBox_begin_gl") + # self._end_dir = self.get_combobox_text("comboBox_end_gl") super().accept() diff --git a/src/View/Geometry/UndoCommand.py b/src/View/Geometry/UndoCommand.py index f031c008..e60ad848 100644 --- a/src/View/Geometry/UndoCommand.py +++ b/src/View/Geometry/UndoCommand.py @@ -28,8 +28,6 @@ from PyQt5.QtWidgets import ( from Model.Geometry import Reach from Model.Except import exception_message_box -from Meshing.Mage import MeshingWithMage - logger = logging.getLogger() @@ -240,7 +238,7 @@ class MeshingCommand(QUndoCommand): self._mesher = mesher self._command = command - self._profiles = reach.profiles.copy() + self._profiles = [p.copy() for p in reach.profiles] self._profiles.reverse() self._new_profiles = None @@ -264,7 +262,7 @@ class MeshingCommand(QUndoCommand): **self._data ) - self._new_profiles = self._reach.profiles.copy() + self._new_profiles = [p.copy() for p in self._reach.profiles] self._new_profiles.reverse() else: self._reach.purge() diff --git a/src/View/Geometry/Window.py b/src/View/Geometry/Window.py index 4356755b..999ee561 100644 --- a/src/View/Geometry/Window.py +++ b/src/View/Geometry/Window.py @@ -32,6 +32,7 @@ from PyQt5.QtGui import ( from PyQt5.QtCore import ( QModelIndex, Qt, QSettings, pyqtSlot, QItemSelectionModel, QCoreApplication, QSize, + QItemSelection, QItemSelectionRange, ) from PyQt5.QtWidgets import ( QApplication, QMainWindow, QFileDialog, QCheckBox, @@ -49,9 +50,7 @@ from View.Tools.Plot.PamhyrCanvas import MplCanvas from View.SelectReach.Window import SelectReachWindow -from Meshing.Mage import ( - MeshingWithMage, MeshingWithMageMailleurTT -) +from Meshing.Internal import InternalMeshing from View.Geometry.Table import GeometryReachTableModel from View.Geometry.PlotXY import PlotXY @@ -288,6 +287,13 @@ class GeometryWindow(PamhyrWindow): self.tableView.model().blockSignals(False) def edit_meshing(self): + + rows = list( + set( + (i.row() for i in self.tableView.selectedIndexes()) + ) + ) + selected_rk = [self._reach.profile(r).rk for r in rows] try: dlg = MeshingDialog( reach=self._reach, @@ -298,8 +304,6 @@ class GeometryWindow(PamhyrWindow): data = { "step": dlg.space_step, "limites": [dlg.begin_cs, dlg.end_cs], - "directrices": [dlg.begin_dir, dlg.end_dir], - "lplan": dlg.lplan, "linear": dlg.linear, } self._edit_meshing(data) @@ -307,18 +311,29 @@ class GeometryWindow(PamhyrWindow): logger_exception(e) return + ind = [] + for i in range(self._reach.number_profiles): + if self._reach.profile(i).rk in selected_rk: + ind.append(i) + self.tableView.setFocus() + selection = self.tableView.selectionModel() + index = QItemSelection() + if len(ind) > 0: + for i in ind: + index.append(QItemSelectionRange(self.tableView.model().index(i, 0))) + selection.select( + index, + QItemSelectionModel.Rows | + QItemSelectionModel.ClearAndSelect | + QItemSelectionModel.Select + ) + def _edit_meshing(self, data): try: - mesher = MeshingWithMageMailleurTT() + mesher = InternalMeshing() self._table.meshing(mesher, data) except Exception as e: logger_exception(e) - raise ExternFileMissingError( - module="mage", - filename="MailleurTT", - path=MeshingWithMageMailleurTT._path(), - src_except=e - ) def update_rk(self): try: diff --git a/src/View/InitialConditions/Table.py b/src/View/InitialConditions/Table.py index a008979d..bf1d0f28 100644 --- a/src/View/InitialConditions/Table.py +++ b/src/View/InitialConditions/Table.py @@ -244,7 +244,7 @@ class InitialConditionTableModel(PamhyrTableModel): self.layoutAboutToBeChanged.emit() self.layoutChanged.emit() - def import_from_results(self, index, results): + def import_from_results(self, results): if results is None: logger.error("No results data") return @@ -281,6 +281,33 @@ class InitialConditionTableModel(PamhyrTableModel): self.layoutAboutToBeChanged.emit() self.layoutChanged.emit() + def read_from_ini(self, file_name): + + reach_id = self._data.river.enable_edges().index(self._reach) + 1 + + logger.debug(f"Import initial conditions from {file_name}") + data = [] + with open(file_name, encoding="utf-8") as ini_file: + for line in ini_file: + if not (line.startswith("#") or + line.startswith("*") or + line.startswith("$")): + line = line.split() + if int(line[0]) == reach_id: + data.append([line[4], line[2], line[3]]) + + self._undo.push( + ReplaceDataCommand( + self._lst, + list( + map( + lambda d: self._lst.new_from_data(*d), + data + ) + ) + ) + ) + def undo(self): self._undo.undo() self.layoutChanged.emit() diff --git a/src/View/InitialConditions/Window.py b/src/View/InitialConditions/Window.py index e671a547..90ced991 100644 --- a/src/View/InitialConditions/Window.py +++ b/src/View/InitialConditions/Window.py @@ -265,20 +265,29 @@ class InitialConditionsWindow(PamhyrWindow): settings = QSettings(QSettings.IniFormat, QSettings.UserScope, 'MyOrg', ) options |= QFileDialog.DontUseNativeDialog - filename, _ = QtWidgets.QFileDialog.getOpenFileName( + + file_types = [ + self._trad["file_bin"], + self._trad["file_ini"], + self._trad["file_all"], + ] + file_name, _ = QtWidgets.QFileDialog.getOpenFileName( self, self._trad["open_file"], "", - ";; ".join(["Mage (*.BIN)"]), + ";; ".join(file_types), options=options ) - if filename != "": - size = os.stat(filename).st_size - # self._table.import_geometry(0, filename) - self._import_from_file(filename) + if file_name != "": + size = os.stat(file_name).st_size + # self._table.import_geometry(0, file_name) + if file_name[-4:] == ".BIN": + self._import_from_bin_file(file_name) + elif file_name[-4:].upper() == ".INI": + self._import_from_ini_file(file_name) - def _import_from_file(self, file_name): + def _import_from_bin_file(self, file_name): solver = Mage8("dummy") name = os.path.basename(file_name)\ .replace(".BIN", "") @@ -305,9 +314,11 @@ class InitialConditionsWindow(PamhyrWindow): def _import_from_results(self, results): logger.debug(f"import from results: {results}") - row = self.index_selected_row() + self._table.import_from_results(results) - self._table.import_from_results(row, results) + def _import_from_ini_file(self, file_name): + logger.debug(f"import from INI file: {file_name}") + self._table.read_from_ini(file_name) def move_up(self): row = self.index_selected_row() diff --git a/src/View/InitialConditions/translate.py b/src/View/InitialConditions/translate.py index 0b782f21..8b29f98b 100644 --- a/src/View/InitialConditions/translate.py +++ b/src/View/InitialConditions/translate.py @@ -34,6 +34,15 @@ class ICTranslate(MainTranslate): self._dict["discharge"] = self._dict["unit_discharge"] self._dict["rk"] = self._dict["unit_rk"] + self._dict["open_file"] = _translate( + "InitialCondition", "Open a file") + self._dict["file_bin"] = _translate( + "InitialCondition", "Mage results file (*.BIN)") + self._dict["file_ini"] = _translate( + "InitialCondition", "Mage initial conditions file (*.INI *.ini)") + self._dict["file_all"] = _translate( + "InitialCondition", "All files (*)") + self._sub_dict["table_headers"] = { # "name": _translate("InitialCondition", "Name"), "rk": self._dict["unit_rk"], diff --git a/src/View/LateralContribution/Table.py b/src/View/LateralContribution/Table.py index 56eb70e2..8b33f76b 100644 --- a/src/View/LateralContribution/Table.py +++ b/src/View/LateralContribution/Table.py @@ -19,7 +19,7 @@ import logging import traceback -from tools import trace, timer +from tools import trace, timer, old_pamhyr_date_to_timestamp from PyQt5.QtCore import ( Qt, QVariant, QAbstractTableModel, @@ -38,7 +38,7 @@ from View.LateralContribution.UndoCommand import ( SetNameCommand, SetEdgeCommand, SetTypeCommand, SetBeginCommand, SetEndCommand, AddCommand, DelCommand, SortCommand, - MoveCommand, PasteCommand, DuplicateCommand, + MoveCommand, PasteCommand, DuplicateCommand, ImportCommand, ) from Model.LateralContribution.LateralContributionTypes import ( @@ -116,17 +116,20 @@ class ComboBoxDelegate(QItemDelegate): def setModelData(self, editor, model, index): text = str(editor.currentText()) - if self._mode == "rk": - profiles = list( - filter( - lambda p: p.display_name() == text, - self._data.reach.profiles - ) - ) - - value = profiles[0].rk if len(profiles) > 0 else None - else: + if self._data is None: value = text + else: + if self._mode == "rk" and self._data.reach is not None: + profiles = list( + filter( + lambda p: p.display_name() == text, + self._data.reach.profiles + ) + ) + + value = profiles[0].rk if len(profiles) > 0 else None + else: + value = text model.setData(index, value) editor.close() @@ -293,3 +296,44 @@ class TableModel(PamhyrTableModel): self.endMoveRows() self.layoutChanged.emit() + + def read_from_lat(self, file_name): + logger.debug(f"Import lateral contributions from {file_name}") + data = [] + current_reach = -1 + with open(file_name, encoding="utf-8") as lat_file: + for line in lat_file: + if not (line.startswith("#") or + line.startswith("*") or + len(line) < 1): + line = line.split() + if line[0] == "$": + current_reach = int(line[1]) - 1 + if (current_reach <= len(self._data.enable_edges()) and + current_reach >= 0): + data.append( + [self._data.enable_edges()[current_reach], + float(line[2]), + float(line[3]) + ] + ) + else: + if (current_reach <= len(self._data.enable_edges()) and + current_reach >= 0): + data[-1].append( + [ + old_pamhyr_date_to_timestamp(line[0]), + float(line[1]) + ] + ) + + self.layoutAboutToBeChanged.emit() + + self._undo.push( + ImportCommand( + self._lst, self._tab, data + ) + ) + + self.layoutAboutToBeChanged.emit() + self.layoutChanged.emit() diff --git a/src/View/LateralContribution/UndoCommand.py b/src/View/LateralContribution/UndoCommand.py index e101f513..d3afd803 100644 --- a/src/View/LateralContribution/UndoCommand.py +++ b/src/View/LateralContribution/UndoCommand.py @@ -28,6 +28,10 @@ from Model.LateralContribution.LateralContributionList import ( LateralContributionList ) +from Model.LateralContribution.LateralContributionTypes import ( + NotDefined, LateralContrib, +) + class SetNameCommand(QUndoCommand): def __init__(self, lcs, tab, index, new_value): @@ -143,13 +147,13 @@ class DelCommand(QUndoCommand): self._tab = tab self._rows = rows - self._bc = [] + self._lc = [] for row in rows: - self._bc.append((row, self._lcs.get(self._tab, row))) - self._bc.sort() + self._lc.append((row, self._lcs.get(self._tab, row))) + self._lc.sort() def undo(self): - for row, el in self._bc: + for row, el in self._lc: self._lcs.insert(self._tab, row, el) def redo(self): @@ -213,36 +217,73 @@ class MoveCommand(QUndoCommand): class PasteCommand(QUndoCommand): - def __init__(self, lcs, tab, row, bc): + def __init__(self, lcs, tab, row, lc): QUndoCommand.__init__(self) self._lcs = lcs self._tab = tab self._row = row - self._bc = deepcopy(bc) - self._bc.reverse() + self._lc = deepcopy(lc) + self._lc.reverse() def undo(self): - self._lcs.delete(self._tab, self._bc) + self._lcs.delete(self._tab, self._lc) def redo(self): - for bc in self._bc: - self._lcs.insert(self._tab, self._row, bc) + for lc in self._lc: + self._lcs.insert(self._tab, self._row, lc) + + +class ImportCommand(QUndoCommand): + def __init__(self, lcs, tab, data): + QUndoCommand.__init__(self) + + self._tab = tab + self._lcs = lcs + self._data = data + self._old_rows = list(range(len(self._lcs.get_tab(self._tab)))) + self._new_rows = list(range(len(self._data))) + + self._new_lc = None + self._old_lc = [] + for row in self._old_rows: + self._old_lc.append((row, self._lcs.get(self._tab, row))) + + def undo(self): + self._lcs.delete_i(self._tab, self._new_rows) + for row, el in self._old_lc: + self._lcs.insert(self._tab, row, el) + + def redo(self): + self._lcs.delete_i(self._tab, self._old_rows) + if self._new_lc is None: + self._new_lc = [] + for row, data in enumerate(self._data): + new = LateralContrib(status=self._lcs._status) + new.edge = data[0] + new.begin_rk = data[1] + new.end_rk = data[2] + for i, val in enumerate(data[3:]): + new.insert(i, val) + self._new_lc.append(new) + + for row, el in enumerate(self._new_lc): + self._lcs.insert(self._tab, row, el) class DuplicateCommand(QUndoCommand): - def __init__(self, lcs, tab, rows, bc): + def __init__(self, lcs, tab, rows, lc): QUndoCommand.__init__(self) self._lcs = lcs self._tab = tab self._rows = rows - self._bc = deepcopy(bc) - self._bc.reverse() + self._lc = deepcopy(lc) + self._lc.reverse() def undo(self): - self._lcs.delete(self._tab, self._bc) + self._lcs.delete(self._tab, self._lc) def redo(self): - for bc in self._lcs: - self._lcs.insert(self._tab, self._rows[0], bc) + for lc in self._lcs: + self._lcs.insert(self._tab, self._rows[0], lc) diff --git a/src/View/LateralContribution/Window.py b/src/View/LateralContribution/Window.py index db8b2791..1b74f5cf 100644 --- a/src/View/LateralContribution/Window.py +++ b/src/View/LateralContribution/Window.py @@ -22,6 +22,7 @@ from tools import trace, timer from View.Tools.PamhyrWindow import PamhyrWindow +from PyQt5 import QtWidgets from PyQt5.QtGui import ( QKeySequence, ) @@ -29,7 +30,7 @@ from PyQt5.QtGui import ( from PyQt5.QtCore import ( Qt, QVariant, QAbstractTableModel, QCoreApplication, QModelIndex, pyqtSlot, - QRect, + QRect, QSettings, ) from PyQt5.QtWidgets import ( @@ -153,6 +154,8 @@ class LateralContributionWindow(PamhyrWindow): ) def setup_connections(self): + self.find(QAction, "action_import").triggered.connect( + self.import_from_file) self.find(QAction, "action_add").triggered.connect(self.add) self.find(QAction, "action_del").triggered.connect(self.delete) self.find(QAction, "action_edit").triggered.connect(self.edit) @@ -297,3 +300,26 @@ class LateralContributionWindow(PamhyrWindow): parent=self ) win.show() + + def import_from_file(self): + options = QFileDialog.Options() + settings = QSettings(QSettings.IniFormat, + QSettings.UserScope, 'MyOrg', ) + options |= QFileDialog.DontUseNativeDialog + + file_types = [ + self._trad["file_lat"], + self._trad["file_all"], + ] + + filename, _ = QtWidgets.QFileDialog.getOpenFileName( + self, + self._trad["open_file"], + "", + ";; ".join(file_types), + options=options + ) + + if filename != "": + tab = self.current_tab() + self._table[tab].read_from_lat(filename) diff --git a/src/View/LateralContribution/translate.py b/src/View/LateralContribution/translate.py index e3de3d87..787e0cfb 100644 --- a/src/View/LateralContribution/translate.py +++ b/src/View/LateralContribution/translate.py @@ -52,6 +52,10 @@ class LCTranslate(MainTranslate): self._dict["x"] = _translate("Geometry", "X (m)") self._dict["y"] = _translate("Geometry", "Y (m)") self._dict["z"] = _translate("Geometry", "Z (m)") + self._dict["file_lat"] = _translate( + "LateralContribution", "Shapefile (*.LAT *.lat)") + self._dict["file_all"] = _translate( + "LateralContribution", "All files (*)") self._sub_dict["table_headers"] = { "name": self._dict["name"], diff --git a/src/View/Results/Table.py b/src/View/Results/Table.py index 436b9bf6..a1bcbbb3 100644 --- a/src/View/Results/Table.py +++ b/src/View/Results/Table.py @@ -23,6 +23,8 @@ from numpy import sqrt from tools import timer, trace +from itertools import compress + from PyQt5.QtGui import ( QKeySequence, QColor ) @@ -51,8 +53,12 @@ class TableModel(PamhyrTableModel): self._lst = _river.reachs elif self._opt_data == "profile": self._lst = _river.reach(0).profiles + # self._lst = list(filter(lambda x: x.name[0:8] != 'interpol', + # _river.reach(0).profiles)) elif self._opt_data == "raw_data": self._lst = _river.reach(0).profiles + # self._lst = list(filter(lambda x: x.name[0:8] != 'interpol', + # _river.reach(0).profiles)) elif self._opt_data == "solver": self._lst = self._parent._solvers @@ -151,6 +157,8 @@ class TableModel(PamhyrTableModel): self._lst = _river.reachs elif self._opt_data == "profile" or self._opt_data == "raw_data": self._lst = _river.reach(reach).profiles + # self._lst = list(compress(_river.reach(reach).profiles, + # _river.reach(reach).profile_mask)) elif self._opt_data == "solver": self._lst = self._parent._solvers diff --git a/src/View/Results/Window.py b/src/View/Results/Window.py index b5270fdd..36550c9c 100644 --- a/src/View/Results/Window.py +++ b/src/View/Results/Window.py @@ -19,7 +19,7 @@ import os import csv import logging -import rasterio +# import rasterio from numpy import sqrt @@ -338,6 +338,8 @@ class ResultsWindow(PamhyrWindow): # "action_export": self.export_current, "action_Geo_tiff": self.import_geotiff } + self.find(QAction, "action_Geo_tiff").setEnabled(False) + self.find(QAction, "action_Geo_tiff").setVisible(False) if len(self._results) > 1: self.find(QAction, "action_reload").setEnabled(False) @@ -494,7 +496,7 @@ class ResultsWindow(PamhyrWindow): table = self.find(QTableView, f"tableView_profile") indexes = table.selectedIndexes() if len(indexes) == 0: - return 0 + return [] return [i.row() for i in indexes] @@ -1166,47 +1168,48 @@ class ResultsWindow(PamhyrWindow): self.update_table_selection_profile(profile_id) def import_geotiff(self): - options = QFileDialog.Options() - settings = QSettings(QSettings.IniFormat, - QSettings.UserScope, 'MyOrg', ) - options |= QFileDialog.DontUseNativeDialog - - file_types = [ - self._trad["file_geotiff"], - self._trad["file_all"], - ] - - filename, _ = QFileDialog.getOpenFileName( - self, - self._trad["open_file"], - "", - ";; ".join(file_types), - options=options - ) - - if filename != "": - with rasterio.open(filename) as data: - img = data.read() - b = data.bounds[:] - # b[0] left - # b[1] bottom - # b[2] right - # b[3] top - xlim = self.canvas.axes.get_xlim() - ylim = self.canvas.axes.get_ylim() - if b[2] > b[0] and b[1] < b[3]: - self.canvas.axes.imshow(img.transpose((1, 2, 0)), - extent=[b[0], b[2], b[1], b[3]]) - else: - dlg = CoordinatesDialog( - xlim, - ylim, - trad=self._trad, - parent=self - ) - if dlg.exec(): - self.canvas.axes.imshow(img.transpose((1, 2, 0)), - extent=dlg.values) - self.plot_xy.idle() - self.canvas.axes.set_xlim(xlim) - self.canvas.axes.set_ylim(ylim) +# options = QFileDialog.Options() +# settings = QSettings(QSettings.IniFormat, +# QSettings.UserScope, 'MyOrg', ) +# options |= QFileDialog.DontUseNativeDialog +# +# file_types = [ +# self._trad["file_geotiff"], +# self._trad["file_all"], +# ] +# +# filename, _ = QFileDialog.getOpenFileName( +# self, +# self._trad["open_file"], +# "", +# ";; ".join(file_types), +# options=options +# ) +# +# if filename != "": +# with rasterio.open(filename) as data: +# img = data.read() +# b = data.bounds[:] +# # b[0] left +# # b[1] bottom +# # b[2] right +# # b[3] top +# xlim = self.canvas.axes.get_xlim() +# ylim = self.canvas.axes.get_ylim() +# if b[2] > b[0] and b[1] < b[3]: +# self.canvas.axes.imshow(img.transpose((1, 2, 0)), +# extent=[b[0], b[2], b[1], b[3]]) +# else: +# dlg = CoordinatesDialog( +# xlim, +# ylim, +# trad=self._trad, +# parent=self +# ) +# if dlg.exec(): +# self.canvas.axes.imshow(img.transpose((1, 2, 0)), +# extent=dlg.values) +# self.plot_xy.idle() +# self.canvas.axes.set_xlim(xlim) +# self.canvas.axes.set_ylim(ylim) + return diff --git a/src/View/ui/EditBoundaryConditions.ui b/src/View/ui/EditBoundaryConditions.ui index 056c02a8..9d4f9293 100644 --- a/src/View/ui/EditBoundaryConditions.ui +++ b/src/View/ui/EditBoundaryConditions.ui @@ -70,6 +70,7 @@ false + @@ -149,6 +150,18 @@ Remove points to make the curve increasing + + + + ressources/import.pngressources/import.png + + + Import + + + Import from file + + diff --git a/src/View/ui/EditLateralContribution.ui b/src/View/ui/EditLateralContribution.ui index 21736a6b..2e739f88 100644 --- a/src/View/ui/EditLateralContribution.ui +++ b/src/View/ui/EditLateralContribution.ui @@ -112,6 +112,18 @@ Sort points + + + + ressources/import.pngressources/import.png + + + Import + + + Import from file + + diff --git a/src/View/ui/Frictions.ui b/src/View/ui/Frictions.ui index 77c32181..44127713 100644 --- a/src/View/ui/Frictions.ui +++ b/src/View/ui/Frictions.ui @@ -60,6 +60,7 @@ false + @@ -107,6 +108,18 @@ Ctrl+E + + + + ressources/import.pngressources/import.png + + + Import + + + Import from file + + diff --git a/src/View/ui/LateralContributions.ui b/src/View/ui/LateralContributions.ui index 32f4f60a..5d44cba2 100644 --- a/src/View/ui/LateralContributions.ui +++ b/src/View/ui/LateralContributions.ui @@ -97,6 +97,7 @@ false + @@ -162,6 +163,18 @@ Sort by names + + + + ressources/import.pngressources/import.png + + + Import + + + Import from file + + diff --git a/src/View/ui/MeshingOptions.ui b/src/View/ui/MeshingOptions.ui index de48342a..b7eab409 100644 --- a/src/View/ui/MeshingOptions.ui +++ b/src/View/ui/MeshingOptions.ui @@ -7,24 +7,14 @@ 0 0 520 - 341 + 245 Dialog - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - + Parameters @@ -79,7 +69,7 @@ Spline - true + false @@ -88,6 +78,9 @@ Linear + + true + @@ -108,47 +101,14 @@ - - - - Guideline used for distance computation + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - true - - - Second guideline - - - - - - - true - - - First guideline - - - - - - - true - - - - - - - true - - - - diff --git a/src/lang/fr.ts b/src/lang/fr.ts index 91bf0e67..b25cab15 100644 --- a/src/lang/fr.ts +++ b/src/lang/fr.ts @@ -1,6 +1,5 @@ - - + About @@ -193,17 +192,17 @@ Éditer les conditions aux limites - + X X - + Y Y - + Solid (kg/s) Solide (kg/s) @@ -243,19 +242,19 @@ Options des conditions limites - + No geometry Pas de géométrie - + No geometry found for this reach. This feature requires a reach with a geometry. Aucune géométrie n'a été trouvée sur ce bief. Cette fonctionnalité nécessite un bief muni d'une géométrie. - + Warning Avertissement @@ -264,6 +263,31 @@ Cette fonctionnalité nécessite un bief muni d'une géométrie.Pollutant Polluant + + + Open a file + Ouvrir un fichier + + + + Mage hydrograph file (*.HYD) + Hydrogramme Mage (*.HYD) + + + + Mage limnigraph file (*.LIM) + Limnigramme Mage (*.LIM) + + + + Mage rating curve file (*.AVA) + Courbe de tarage Mage (*.AVA) + + + + All files (*) + Tous les fichiers (*) + BoundaryConditions @@ -448,27 +472,27 @@ Cette fonctionnalité nécessite un bief muni d'une géométrie.Biefs - + Main channel Lit mineur - + Floodway Lit moyen - + Not defined Non défini - + Not associated Non associé - + Cross-section Section en travers @@ -478,10 +502,15 @@ Cette fonctionnalité nécessite un bief muni d'une géométrie.Titre - + Method Méthode + + + Select reach + Sélectionner un bief + Configure @@ -1267,9 +1296,14 @@ Cette fonctionnalité nécessite un bief muni d'une géométrie. - Copyright © 2022-2025 INRAE + Copyright © 2022-2025 INRAE Copyright © 2022-2025 INRAE + + + Copyright © 2022-2025 INRAE + + Frictions @@ -1485,6 +1519,26 @@ Cette fonctionnalité nécessite un bief muni d'une géométrie.Initial conditions Conditions initiales + + + Open a file + Ouvrir un fichier + + + + Mage results file (*.BIN) + Fichier de résultats Mage (*.BIN) + + + + Mage initial conditions file (*.INI *.ini) + Fichiers de conditions initiales Mage (*.INI *.ini) + + + + All files (*) + Tous les fichiers (*) + InitialConditionAdisTS @@ -1573,17 +1627,17 @@ Cette fonctionnalité nécessite un bief muni d'une géométrie. MainWindow - + Open debug window Ouvrir la fenêtre de débogage - + Open SQLite debuging tool ('sqlitebrowser') Ouvrir l'outil de débogage SQLite ('sqlitebrowser') - + Enable this window Activer cette fenêtre @@ -1803,12 +1857,12 @@ Cette fonctionnalité nécessite un bief muni d'une géométrie.Éditer la géométrie - + Import geometry Importer une géométrie - + Export geometry Exporter la géométrie @@ -2388,52 +2442,52 @@ Cette fonctionnalité nécessite un bief muni d'une géométrie.Importer - + Add a cross-section Ajouter une section en travers - + Delete selected cross-section(s) Supprimer les sections en travers sélectionnées - + Edit selected cross section(s) Éditer les sections en travers sélectionnées - + Sort cross-sections by ascending position Trier les sections en travers par PK croissant - + Sort cross-sections by descending position Trier les sections en travers par PK décroissant - + Move up selected cross-section(s) Déplacer les sections en travers vers le haut - + Move down selected cross-section(s) Déplacer les sections en travers vers le bas - + Meshing Maillage - + Summary Résumé - + Checks Vérifications @@ -2568,17 +2622,17 @@ Cette fonctionnalité nécessite un bief muni d'une géométrie.Importer depuis un fichier - + Update RK Mise à jour des PK - + Recompute RK Recalcule des PK - + Purge cross-sections to keep a given number of points Purger les profiles pour garder un nombre fixer de points @@ -2593,42 +2647,42 @@ Cette fonctionnalité nécessite un bief muni d'une géométrie.Exporter les données au format CSV - + Generate uniform Générer un regime uniforme - + Generate rating curve from Manning law Générer une courbe de tarage (loi de Maning) - + Generate critical Générer régime critique - + Generate rating curve as Q(z) = Sqrt(g*S(z)^3/L(z)) Générer une courbe de tarage (Q(z) = Sqrt(g*S(z)^3/L(z))) - + Make increasing Augmenter - + Remove points to make the curve increasing Supprimer des points pour rendre la courbe croissante - + Shift Translater - + Shift selected sections coordinates Translater les coordonnées des sections sélectionnées @@ -2653,47 +2707,47 @@ Cette fonctionnalité nécessite un bief muni d'une géométrie.Données - + Please select a reach Veuillez sélectionner un bief - + Last open study Dernière étude ouverte - + Do you want to open again the last open study? Voulez-vous rouvrir la dernière étude ? - + This edition window need a reach selected into the river network to work on it Cette fenêtre d'édition a besoin d'un bief sélectionné dans le réseau pour travailler dessus - + Close without saving study Fermer sans sauvegarder l'étude - + Do you want to save current study before closing it? Souhaitez-vous sauvegarder l'étude en cours avant de la fermer ? - + Warning Avertissement - + X (m) X (m) - + Y (m) Y (m) @@ -2868,12 +2922,12 @@ Cette fonctionnalité nécessite un bief muni d'une géométrie.Export données brutes - + Yes Oui - + No Non @@ -2892,6 +2946,16 @@ Cette fonctionnalité nécessite un bief muni d'une géométrie.Import background image Importer une image de fond + + + Select reach + Sélectionner un bief + + + + Change current reach + Changer le bief courrant + MainWindow_reach @@ -3618,87 +3682,87 @@ Cette fonctionnalité nécessite un bief muni d'une géométrie. Unit - + Width (m) Largeur (m) - + Depth (m) Profondeur (m) - + Diameter (m) Diamètre (m) - + Thickness (m) Épaisseur (m) - + Elevation (m) Cote (m) - + Area (hectare) Aire (hectare) - + Time (sec) Temps (s) - + Time (JJJ:HH:MM:SS) Temps (JJJ:HH:MM:SS) - + Date (sec) Date (s) - + Date (ISO format) Date (format ISO) - + River Kilometer (m) Point Kilométrique (m) - + Max Depth (m) Profondeur max (m) - + Mean Depth (m) Profondeur moyenne (m) - + Velocity (m/s) Vitesse (m/s) - + Wet Perimeter (m) Périmètre mouillé (m) - + Hydraulic Radius (m) Rayon hydraulique (m) - + Froude number Nombre de Froude @@ -3741,132 +3805,132 @@ moyen droit (m) D90 - + Width Envelop (m) Enveloppe de la argeur (m) - + Max Width (m) Largeur max (m) - + Min Width (m) Largeur min (m) - + Min Depth (m) Profondeur min (m) - + Depth Envelop (m) Enveloppe de la profondeur (m) - + Bed Elevation (m) Cote du fond (m) - + Bed Elevation Envelop (m) Enveloppe de la cote du fond (m) - + Max Bed Elevation (m) Cote du fond max (m) - + Min Bed Elevation (m) Cote du fond min (m) - + Water Elevation (m) Cote de l'eau (m) - + Water Elevation Envelop (m) Enveloppe de la cote de l'eau (m) - + Max Water Elevation (m) Cote de l'eau max (m) - + Min Water Elevation (m) Cote de l'eau min (m) - + Velocity Envelop (m/s) Enveloppe de la vitesse (m/s) - + Max Velocity (m/s) Vitesse max (m/s) - + Min Velocity (m/s) Vitesse min (m/s) - + Area Aire - + Rho Rho - + Porosity Porosité - + CDC_RIV CDC_RIV - + CDC_CAS CDC_CAS - + APD APD - + AC AC - + BC BC - + Concentration (g/l) Concentration (g/l) - + Mass (kg) Masse @@ -3886,32 +3950,32 @@ moyen droit (m) Coeff c - + Discharge Débit - + Discharge Envelop Enveloppe du débit - + Max Discharge Débit max - + Min Discharge Débit min - + Concentration Concentration - + Wet Area Aire mouillée diff --git a/src/tools.py b/src/tools.py index 8c14e267..6f0919d4 100644 --- a/src/tools.py +++ b/src/tools.py @@ -260,7 +260,10 @@ def date_dmy_to_timestamp(date: str): def old_pamhyr_date_to_timestamp(date: str): v = date.split(":") if len(v) != 4: - return 0 + if len(v) == 1: # minutes + return int(float(v[0]) * 60) # Minute to sec + else: + return 0 m = [ (24 * 60 * 60), # Day to sec diff --git a/tests_cases/HHLab/HHLab.pamhyr b/tests_cases/HHLab/HHLab.pamhyr new file mode 100644 index 00000000..f2b713ab Binary files /dev/null and b/tests_cases/HHLab/HHLab.pamhyr differ