From 5fc4e3903ac4e2c9fd5ea82930cbaae42cd9aba3 Mon Sep 17 00:00:00 2001 From: Theophile Terraz Date: Tue, 9 Sep 2025 09:22:43 +0200 Subject: [PATCH 01/22] debug Hogneau case2 --- doc/users/TP_Hydraulique_Hogneau/step2.pamhyr | Bin 208896 -> 225280 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/doc/users/TP_Hydraulique_Hogneau/step2.pamhyr b/doc/users/TP_Hydraulique_Hogneau/step2.pamhyr index c7516dff606939dd005eafa8e54776cee528a4ee..95c6648033fdec12c15c396783cdab8af2b738eb 100644 GIT binary patch delta 24664 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 From 7c4c772f43733d6bb43c7eb29b60f1e774e017ee Mon Sep 17 00:00:00 2001 From: Theophile Terraz Date: Thu, 11 Sep 2025 17:15:48 +0200 Subject: [PATCH 02/22] load initial state from .INI --- src/View/InitialConditions/Table.py | 32 +++- src/View/InitialConditions/Window.py | 28 ++- src/View/InitialConditions/translate.py | 9 + src/lang/fr.ts | 217 ++++++++++++++---------- 4 files changed, 187 insertions(+), 99 deletions(-) diff --git a/src/View/InitialConditions/Table.py b/src/View/InitialConditions/Table.py index a008979d..13e02ab4 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,36 @@ 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 + print(f"reach : {reach_id}") + + 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]]) + + print(data) + + 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..1946706f 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,10 +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(row, results) + def _import_from_ini_file(self, file_name): + self._table.read_from_ini(file_name) + def move_up(self): row = self.index_selected_row() self._table.move_up(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/lang/fr.ts b/src/lang/fr.ts index 91bf0e67..592dbdd7 100644 --- a/src/lang/fr.ts +++ b/src/lang/fr.ts @@ -1,6 +1,5 @@ - - + About @@ -448,27 +447,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 +477,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 +1271,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 +1494,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 +1602,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 +1832,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 +2417,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 +2597,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 @@ -2623,12 +2652,12 @@ Cette fonctionnalité nécessite un bief muni d'une géométrie.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 +2682,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 +2897,12 @@ Cette fonctionnalité nécessite un bief muni d'une géométrie.Export données brutes - + Yes Oui - + No Non @@ -2892,6 +2921,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 +3657,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 +3780,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 +3925,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 From b582e133a17f04c95a8fae6f80da4e1dd9ce607e Mon Sep 17 00:00:00 2001 From: Theophile Terraz Date: Fri, 12 Sep 2025 14:22:46 +0200 Subject: [PATCH 03/22] load boundary conditions from file --- src/View/BoundaryCondition/Edit/Table.py | 20 ++++++++ src/View/BoundaryCondition/Edit/Window.py | 31 ++++++++++++- src/View/BoundaryCondition/Edit/translate.py | 11 +++++ src/View/InitialConditions/Table.py | 2 - src/View/ui/EditBoundaryConditions.ui | 13 ++++++ src/lang/fr.ts | 49 +++++++++++++++----- 6 files changed, 110 insertions(+), 16 deletions(-) diff --git a/src/View/BoundaryCondition/Edit/Table.py b/src/View/BoundaryCondition/Edit/Table.py index a7481666..ad4ba66d 100644 --- a/src/View/BoundaryCondition/Edit/Table.py +++ b/src/View/BoundaryCondition/Edit/Table.py @@ -211,3 +211,23 @@ class TableModel(PamhyrTableModel): ) self.layoutAboutToBeChanged.emit() self.update() + + def read_from_file(self, file_name, bctype): + + logger.debug(f"Import initial conditions from {file_name}") + data0 = [] + data1 = [] + if bctype == "ZD": + mult = 1 + else: + mult = 60 + 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() + data0.append(float(line[0]) * mult) + 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..efc180d1 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_lim"] = _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/InitialConditions/Table.py b/src/View/InitialConditions/Table.py index 13e02ab4..e61f0d9d 100644 --- a/src/View/InitialConditions/Table.py +++ b/src/View/InitialConditions/Table.py @@ -297,8 +297,6 @@ class InitialConditionTableModel(PamhyrTableModel): if int(line[0]) == reach_id: data.append([line[4], line[2], line[3]]) - print(data) - self._undo.push( ReplaceDataCommand( self._lst, 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/lang/fr.ts b/src/lang/fr.ts index 592dbdd7..b25cab15 100644 --- a/src/lang/fr.ts +++ b/src/lang/fr.ts @@ -192,17 +192,17 @@ Éditer les conditions aux limites - + X X - + Y Y - + Solid (kg/s) Solide (kg/s) @@ -242,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 @@ -263,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 @@ -2622,32 +2647,32 @@ 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 From 31fe765e51d7ecd6439a9763e3e951840cc2ac67 Mon Sep 17 00:00:00 2001 From: Theophile Terraz Date: Mon, 15 Sep 2025 15:40:17 +0200 Subject: [PATCH 04/22] load frictions from file --- src/View/BoundaryCondition/Edit/Table.py | 6 +-- src/View/Frictions/Table.py | 47 +++++++++++++++++++++++- src/View/Frictions/UndoCommand.py | 35 ++++++++++++++++++ src/View/Frictions/Window.py | 28 ++++++++++++-- src/View/Frictions/translate.py | 4 ++ src/View/InitialConditions/Table.py | 1 - src/View/ui/Frictions.ui | 13 +++++++ 7 files changed, 125 insertions(+), 9 deletions(-) diff --git a/src/View/BoundaryCondition/Edit/Table.py b/src/View/BoundaryCondition/Edit/Table.py index ad4ba66d..e50308fc 100644 --- a/src/View/BoundaryCondition/Edit/Table.py +++ b/src/View/BoundaryCondition/Edit/Table.py @@ -214,15 +214,15 @@ class TableModel(PamhyrTableModel): def read_from_file(self, file_name, bctype): - logger.debug(f"Import initial conditions from {file_name}") + 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 ini_file: - for line in ini_file: + 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("$")): diff --git a/src/View/Frictions/Table.py b/src/View/Frictions/Table.py index 2347a41d..9ba17e3a 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,34 @@ 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"): + line = line.split() + if int(line[1]) == reach_id: + data.append(line[1:]) + + print(data) + new_data = [] + for d in data: + new = None + minor = float(d[3]) + medium = float(d[4]) + 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[1]), float(d[2]), new, new]) + self.replace_data(new_data) diff --git a/src/View/Frictions/UndoCommand.py b/src/View/Frictions/UndoCommand.py index 6aab35d5..4fc0637d 100644 --- a/src/View/Frictions/UndoCommand.py +++ b/src/View/Frictions/UndoCommand.py @@ -143,6 +143,41 @@ 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._old_friction = [] + for row in self._old_rows: + self._old_friction.append((row, self._frictions.lst[row])) + + self._new_friction = [] + for row in self._new_rows: + new = Friction(status=self._frictions._status) + d = 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_friction.append((row, new)) + + 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): + self._frictions.delete_i(self._old_rows) + for row, el in self._new_friction: + 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/InitialConditions/Table.py b/src/View/InitialConditions/Table.py index e61f0d9d..bf1d0f28 100644 --- a/src/View/InitialConditions/Table.py +++ b/src/View/InitialConditions/Table.py @@ -284,7 +284,6 @@ class InitialConditionTableModel(PamhyrTableModel): def read_from_ini(self, file_name): reach_id = self._data.river.enable_edges().index(self._reach) + 1 - print(f"reach : {reach_id}") logger.debug(f"Import initial conditions from {file_name}") data = [] 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 + + From fce034419d2e20dd978ad9d4a6e247129eb49f7b Mon Sep 17 00:00:00 2001 From: Theophile Terraz Date: Thu, 18 Sep 2025 10:11:49 +0200 Subject: [PATCH 05/22] cleaning --- src/View/Frictions/Table.py | 1 - src/View/Frictions/UndoCommand.py | 21 ++++++++------------- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/src/View/Frictions/Table.py b/src/View/Frictions/Table.py index 9ba17e3a..64a3e18f 100644 --- a/src/View/Frictions/Table.py +++ b/src/View/Frictions/Table.py @@ -278,7 +278,6 @@ class FrictionTableModel(PamhyrTableModel): if int(line[1]) == reach_id: data.append(line[1:]) - print(data) new_data = [] for d in data: new = None diff --git a/src/View/Frictions/UndoCommand.py b/src/View/Frictions/UndoCommand.py index 4fc0637d..14d6395b 100644 --- a/src/View/Frictions/UndoCommand.py +++ b/src/View/Frictions/UndoCommand.py @@ -156,17 +156,6 @@ class ReplaceDataCommand(QUndoCommand): for row in self._old_rows: self._old_friction.append((row, self._frictions.lst[row])) - self._new_friction = [] - for row in self._new_rows: - new = Friction(status=self._frictions._status) - d = 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_friction.append((row, new)) - def undo(self): self._frictions.delete_i(self._new_rows) for row, el in self._old_friction: @@ -174,8 +163,14 @@ class ReplaceDataCommand(QUndoCommand): def redo(self): self._frictions.delete_i(self._old_rows) - for row, el in self._new_friction: - self._frictions.insert(row, el) + 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] class DelCommand(QUndoCommand): From ba8a50e0b957ffde1fa32ef28bc5d6d9053038c8 Mon Sep 17 00:00:00 2001 From: Theophile Terraz Date: Fri, 19 Sep 2025 16:03:23 +0200 Subject: [PATCH 06/22] debug frictions --- src/View/Frictions/UndoCommand.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/View/Frictions/UndoCommand.py b/src/View/Frictions/UndoCommand.py index 14d6395b..dc060f00 100644 --- a/src/View/Frictions/UndoCommand.py +++ b/src/View/Frictions/UndoCommand.py @@ -151,6 +151,7 @@ class ReplaceDataCommand(QUndoCommand): 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: @@ -162,15 +163,20 @@ class ReplaceDataCommand(QUndoCommand): self._frictions.insert(row, el) def redo(self): - 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] + 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): From 9270dd0ff18969e94203bd291a5c628977acd155 Mon Sep 17 00:00:00 2001 From: Theophile Terraz Date: Fri, 19 Sep 2025 16:04:10 +0200 Subject: [PATCH 07/22] add HHLab test case --- tests_cases/HHLab/HHLab.pamhyr | Bin 0 -> 176128 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests_cases/HHLab/HHLab.pamhyr diff --git a/tests_cases/HHLab/HHLab.pamhyr b/tests_cases/HHLab/HHLab.pamhyr new file mode 100644 index 0000000000000000000000000000000000000000..f2b713ab41dcb91db99fa16c6890d4134c065717 GIT binary patch literal 176128 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 literal 0 HcmV?d00001 From 41cffd13e0ab19751f019c48a00eefc817c44c84 Mon Sep 17 00:00:00 2001 From: Theophile Terraz Date: Fri, 19 Sep 2025 16:33:06 +0200 Subject: [PATCH 08/22] debug --- src/View/LateralContribution/Table.py | 2 +- src/View/LateralContribution/UndoCommand.py | 22 ++++++++++----------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/View/LateralContribution/Table.py b/src/View/LateralContribution/Table.py index 56eb70e2..e7596d14 100644 --- a/src/View/LateralContribution/Table.py +++ b/src/View/LateralContribution/Table.py @@ -116,7 +116,7 @@ class ComboBoxDelegate(QItemDelegate): def setModelData(self, editor, model, index): text = str(editor.currentText()) - if self._mode == "rk": + if self._mode == "rk" and self._data.reach is not None: profiles = list( filter( lambda p: p.display_name() == text, diff --git a/src/View/LateralContribution/UndoCommand.py b/src/View/LateralContribution/UndoCommand.py index e101f513..2ecbe67b 100644 --- a/src/View/LateralContribution/UndoCommand.py +++ b/src/View/LateralContribution/UndoCommand.py @@ -143,13 +143,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): @@ -219,14 +219,14 @@ class PasteCommand(QUndoCommand): self._lcs = lcs self._tab = tab self._row = row - self._bc = deepcopy(bc) - self._bc.reverse() + self._lc = deepcopy(bc) + 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: + for bc in self._lc: self._lcs.insert(self._tab, self._row, bc) @@ -237,11 +237,11 @@ class DuplicateCommand(QUndoCommand): self._lcs = lcs self._tab = tab self._rows = rows - self._bc = deepcopy(bc) - self._bc.reverse() + self._lc = deepcopy(bc) + 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: From 80ae4c36adaa96f9c0819b7c8d5d601a062a6e8a Mon Sep 17 00:00:00 2001 From: Theophile Terraz Date: Tue, 23 Sep 2025 14:17:59 +0200 Subject: [PATCH 09/22] import lateral contributions from file --- src/View/LateralContribution/Table.py | 56 +++++++++++++++++---- src/View/LateralContribution/UndoCommand.py | 56 ++++++++++++++++++--- src/View/LateralContribution/Window.py | 28 ++++++++++- src/View/LateralContribution/translate.py | 4 ++ src/View/ui/EditLateralContribution.ui | 12 +++++ src/View/ui/LateralContributions.ui | 13 +++++ 6 files changed, 149 insertions(+), 20 deletions(-) diff --git a/src/View/LateralContribution/Table.py b/src/View/LateralContribution/Table.py index e7596d14..5bee4de8 100644 --- a/src/View/LateralContribution/Table.py +++ b/src/View/LateralContribution/Table.py @@ -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" 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: + 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,34 @@ 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([float(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 2ecbe67b..a320073f 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): @@ -213,36 +217,72 @@ 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._lc = deepcopy(bc) + self._lc = deepcopy(lc) self._lc.reverse() def undo(self): self._lcs.delete(self._tab, self._lc) def redo(self): - for bc in self._lc: - 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 == 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._lc = deepcopy(bc) + self._lc = deepcopy(lc) self._lc.reverse() def undo(self): 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/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/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 + + From e7b63fe2705520d97a02093c9ee425a45b02f1d6 Mon Sep 17 00:00:00 2001 From: Theophile Terraz Date: Tue, 23 Sep 2025 14:55:31 +0200 Subject: [PATCH 10/22] debug --- src/View/BoundaryCondition/Edit/Table.py | 5 ++++- src/View/LateralContribution/Table.py | 13 +++++++++---- src/tools.py | 5 ++++- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/View/BoundaryCondition/Edit/Table.py b/src/View/BoundaryCondition/Edit/Table.py index e50308fc..313abb89 100644 --- a/src/View/BoundaryCondition/Edit/Table.py +++ b/src/View/BoundaryCondition/Edit/Table.py @@ -227,7 +227,10 @@ class TableModel(PamhyrTableModel): line.startswith("*") or line.startswith("$")): line = line.split() - data0.append(float(line[0]) * mult) + 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/LateralContribution/Table.py b/src/View/LateralContribution/Table.py index 5bee4de8..9e69dddd 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, @@ -310,12 +310,17 @@ class TableModel(PamhyrTableModel): if line[0] == "$": current_reach = int(line[1]) - 1 if (current_reach <= len(self._data.enable_edges()) and - current_reach > 0) : + 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([float(line[0]), float(line[1])]) + current_reach >= 0) : + data[-1].append( + [ + old_pamhyr_date_to_timestamp(line[0]), + float(line[1]) + ] + ) self.layoutAboutToBeChanged.emit() diff --git a/src/tools.py b/src/tools.py index 8c14e267..553cc1fb 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 From 1b2f98494b612f436c84b7ad6c8f8bbbfdc97121 Mon Sep 17 00:00:00 2001 From: Theophile Terraz Date: Tue, 23 Sep 2025 15:53:49 +0200 Subject: [PATCH 11/22] pep8 --- src/View/LateralContribution/Table.py | 11 ++++++++--- src/View/LateralContribution/UndoCommand.py | 3 ++- src/tools.py | 4 ++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/View/LateralContribution/Table.py b/src/View/LateralContribution/Table.py index 9e69dddd..8b33f76b 100644 --- a/src/View/LateralContribution/Table.py +++ b/src/View/LateralContribution/Table.py @@ -310,11 +310,16 @@ class TableModel(PamhyrTableModel): 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])]) + 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) : + current_reach >= 0): data[-1].append( [ old_pamhyr_date_to_timestamp(line[0]), diff --git a/src/View/LateralContribution/UndoCommand.py b/src/View/LateralContribution/UndoCommand.py index a320073f..d3afd803 100644 --- a/src/View/LateralContribution/UndoCommand.py +++ b/src/View/LateralContribution/UndoCommand.py @@ -256,7 +256,7 @@ class ImportCommand(QUndoCommand): def redo(self): self._lcs.delete_i(self._tab, self._old_rows) - if self._new_lc == None: + if self._new_lc is None: self._new_lc = [] for row, data in enumerate(self._data): new = LateralContrib(status=self._lcs._status) @@ -270,6 +270,7 @@ class ImportCommand(QUndoCommand): for row, el in enumerate(self._new_lc): self._lcs.insert(self._tab, row, el) + class DuplicateCommand(QUndoCommand): def __init__(self, lcs, tab, rows, lc): QUndoCommand.__init__(self) diff --git a/src/tools.py b/src/tools.py index 553cc1fb..6f0919d4 100644 --- a/src/tools.py +++ b/src/tools.py @@ -260,8 +260,8 @@ def date_dmy_to_timestamp(date: str): def old_pamhyr_date_to_timestamp(date: str): v = date.split(":") if len(v) != 4: - if len(v) == 1: # minutes - return int(float(v[0]) * 60) # Minute to sec + if len(v) == 1: # minutes + return int(float(v[0]) * 60) # Minute to sec else: return 0 From 7d42c164fc76021c9e6fc7c125c3e52771ed9811 Mon Sep 17 00:00:00 2001 From: Theophile Terraz Date: Thu, 25 Sep 2025 17:01:01 +0200 Subject: [PATCH 12/22] debug --- src/Model/SedimentLayer/SedimentLayer.py | 21 +++++++++++---------- src/View/Results/Window.py | 2 +- 2 files changed, 12 insertions(+), 11 deletions(-) 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/View/Results/Window.py b/src/View/Results/Window.py index b5270fdd..d37f37f9 100644 --- a/src/View/Results/Window.py +++ b/src/View/Results/Window.py @@ -494,7 +494,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] From da03f39425ea83b650402ac64705999f5f4f68eb Mon Sep 17 00:00:00 2001 From: Theophile Terraz Date: Mon, 29 Sep 2025 11:23:46 +0200 Subject: [PATCH 13/22] debug --- src/View/BoundaryCondition/Edit/translate.py | 2 +- src/View/InitialConditions/Window.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/View/BoundaryCondition/Edit/translate.py b/src/View/BoundaryCondition/Edit/translate.py index efc180d1..3312c7d0 100644 --- a/src/View/BoundaryCondition/Edit/translate.py +++ b/src/View/BoundaryCondition/Edit/translate.py @@ -41,7 +41,7 @@ class BCETranslate(BCTranslate): "BoundaryCondition", "Mage hydrograph file (*.HYD)") self._dict["file_lim"] = _translate( "BoundaryCondition", "Mage limnigraph file (*.LIM)") - self._dict["file_lim"] = _translate( + self._dict["file_ava"] = _translate( "BoundaryCondition", "Mage rating curve file (*.AVA)") self._dict["file_all"] = _translate( "BoundaryCondition", "All files (*)") diff --git a/src/View/InitialConditions/Window.py b/src/View/InitialConditions/Window.py index 1946706f..90ced991 100644 --- a/src/View/InitialConditions/Window.py +++ b/src/View/InitialConditions/Window.py @@ -314,9 +314,10 @@ class InitialConditionsWindow(PamhyrWindow): def _import_from_results(self, results): logger.debug(f"import from results: {results}") - self._table.import_from_results(row, results) + self._table.import_from_results(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): From 706a900c8ece1d2fba90df0046443201c601948d Mon Sep 17 00:00:00 2001 From: Theophile Terraz Date: Mon, 29 Sep 2025 15:25:35 +0200 Subject: [PATCH 14/22] work on rubar3 --- src/Solver/RubarBE.py | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/Solver/RubarBE.py b/src/Solver/RubarBE.py index bf946f42..448ace6a 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): From 911a2bbbffb019d1ac3beec49dc29d77ac9b426b Mon Sep 17 00:00:00 2001 From: Theophile Terraz Date: Wed, 1 Oct 2025 10:29:14 +0200 Subject: [PATCH 15/22] mesh on selection --- src/View/Geometry/MeshingDialog.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/View/Geometry/MeshingDialog.py b/src/View/Geometry/MeshingDialog.py index 07478f6e..d33f66b6 100644 --- a/src/View/Geometry/MeshingDialog.py +++ b/src/View/Geometry/MeshingDialog.py @@ -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" @@ -89,8 +89,15 @@ class MeshingDialog(PamhyrDialog): 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]) + if len(self.parent.tableView.selectedIndexes()) <= 1: + self.set_combobox_text("comboBox_begin_rk", profiles[0]) + self.set_combobox_text("comboBox_end_rk", profiles[-1]) + else: + r = self.parent.tableView\ + .selectionModel()\ + .selectedRows() + 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): From ebe906fce0be19559fe24f447cb7ec3a9c0cea8d Mon Sep 17 00:00:00 2001 From: Theophile Terraz Date: Tue, 7 Oct 2025 11:52:31 +0200 Subject: [PATCH 16/22] switch from MailleurTT to internal meshing --- src/Meshing/Internal.py | 326 +++++++++++++++++++++++++++++ src/Meshing/Mage.py | 1 + src/Model/Geometry/PointXYZ.py | 10 + src/Model/Geometry/Profile.py | 10 +- src/Model/Geometry/ProfileXYZ.py | 30 +++ src/View/Geometry/MeshingDialog.py | 23 +- src/View/Geometry/UndoCommand.py | 6 +- src/View/Geometry/Window.py | 14 +- src/View/ui/MeshingOptions.ui | 66 ++---- 9 files changed, 407 insertions(+), 79 deletions(-) create mode 100644 src/Meshing/Internal.py diff --git a/src/Meshing/Internal.py b/src/Meshing/Internal.py new file mode 100644 index 00000000..3cdacf0a --- /dev/null +++ b/src/Meshing/Internal.py @@ -0,0 +1,326 @@ +# 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]: + l = limites[1] + limites[1] = limites[0] + limites[0] = l + elif limites[0] == limites[1]: + return reach + + self.st_to_m(reach, limites) + self.interpolate_transversal_step(reach, + limites, + step) + 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() + 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) 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..4d16f08f 100644 --- a/src/Model/Geometry/PointXYZ.py +++ b/src/Model/Geometry/PointXYZ.py @@ -228,3 +228,13 @@ 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 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..d0f44ea9 100644 --- a/src/Model/Geometry/ProfileXYZ.py +++ b/src/Model/Geometry/ProfileXYZ.py @@ -882,3 +882,33 @@ 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 diff --git a/src/View/Geometry/MeshingDialog.py b/src/View/Geometry/MeshingDialog.py index d33f66b6..e5928472 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, ) @@ -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,19 +83,22 @@ 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) - if len(self.parent.tableView.selectedIndexes()) <= 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: - r = self.parent.tableView\ - .selectionModel()\ - .selectedRows() self.set_combobox_text("comboBox_begin_rk", profiles[r[0].row()]) self.set_combobox_text("comboBox_end_rk", profiles[r[-1].row()]) @@ -174,8 +177,10 @@ 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") + + self.parent.tableView.selectionModel().clearSelection() 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..237f70ed 100644 --- a/src/View/Geometry/Window.py +++ b/src/View/Geometry/Window.py @@ -49,9 +49,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 @@ -298,8 +296,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) @@ -309,16 +305,10 @@ class GeometryWindow(PamhyrWindow): 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/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 - - - - From 5dcbc6b4e16206f882cfd9d63743bd17db638aaf Mon Sep 17 00:00:00 2001 From: Theophile Terraz Date: Tue, 7 Oct 2025 11:56:01 +0200 Subject: [PATCH 17/22] test clean mage rep --- .gitlab-ci.yml | 8 -------- src/Model/Results/River/River.py | 10 ++++++++++ src/View/Results/Table.py | 10 +++++++++- 3 files changed, 19 insertions(+), 9 deletions(-) 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/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/View/Results/Table.py b/src/View/Results/Table.py index 436b9bf6..badda333 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 @@ -150,7 +156,9 @@ class TableModel(PamhyrTableModel): if self._opt_data == "reach": self._lst = _river.reachs elif self._opt_data == "profile" or self._opt_data == "raw_data": - self._lst = _river.reach(reach).profiles + # 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 From f6e08f203cadfb9939b5c463916129fb4687be59 Mon Sep 17 00:00:00 2001 From: Theophile Terraz Date: Tue, 7 Oct 2025 15:28:36 +0200 Subject: [PATCH 18/22] purge + pep8 --- src/Meshing/Internal.py | 37 ++++++++++++++++++-------------- src/Model/Geometry/PointXYZ.py | 12 +++++++++++ src/Model/Geometry/ProfileXYZ.py | 29 ++++++++++++++++++------- src/Solver/RubarBE.py | 8 +++---- src/Solver/Solvers.py | 4 ++-- 5 files changed, 60 insertions(+), 30 deletions(-) diff --git a/src/Meshing/Internal.py b/src/Meshing/Internal.py index 3cdacf0a..ed9a5c48 100644 --- a/src/Meshing/Internal.py +++ b/src/Meshing/Internal.py @@ -44,16 +44,18 @@ class InternalMeshing(AMeshingTool): return reach if limites[0] > limites[1]: - l = limites[1] + lim = limites[1] limites[1] = limites[0] - limites[0] = l + limites[0] = lim elif limites[0] == limites[1]: return reach self.st_to_m(reach, limites) self.interpolate_transversal_step(reach, - limites, - step) + limites, + step) + self.purge_aligned_points(reach) + return reach def st_to_m(self, reach, limites): @@ -110,11 +112,11 @@ class InternalMeshing(AMeshingTool): # 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) + 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]) @@ -122,24 +124,23 @@ class InternalMeshing(AMeshingTool): d = 1.0/float(np[i]+1) ptr0 = ptr for j in range(np[i]): - #p = reach.profiles[ptr0].copy() p = reach.profiles[ptr0].copy() # RATIO entre les deux sections initiales - dj=float(j+1)*d + 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) + 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) + 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) + 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) + 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 @@ -324,3 +325,7 @@ class InternalMeshing(AMeshingTool): ).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/Model/Geometry/PointXYZ.py b/src/Model/Geometry/PointXYZ.py index 4d16f08f..7a233a54 100644 --- a/src/Model/Geometry/PointXYZ.py +++ b/src/Model/Geometry/PointXYZ.py @@ -229,6 +229,18 @@ class PointXYZ(Point, SQLSubModel): 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, diff --git a/src/Model/Geometry/ProfileXYZ.py b/src/Model/Geometry/ProfileXYZ.py index d0f44ea9..db92bd57 100644 --- a/src/Model/Geometry/ProfileXYZ.py +++ b/src/Model/Geometry/ProfileXYZ.py @@ -900,15 +900,28 @@ class ProfileXYZ(Profile, SQLSubModel): 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) + 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/Solver/RubarBE.py b/src/Solver/RubarBE.py index 448ace6a..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"), 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, } From bc4557e21f74540f34c5cc63548d2a5cc2b88f5b Mon Sep 17 00:00:00 2001 From: Theophile Terraz Date: Wed, 8 Oct 2025 09:45:35 +0200 Subject: [PATCH 19/22] keep selection after meshing --- src/View/Geometry/MeshingDialog.py | 2 -- src/View/Geometry/Window.py | 25 +++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/View/Geometry/MeshingDialog.py b/src/View/Geometry/MeshingDialog.py index e5928472..3f1875a6 100644 --- a/src/View/Geometry/MeshingDialog.py +++ b/src/View/Geometry/MeshingDialog.py @@ -180,8 +180,6 @@ class MeshingDialog(PamhyrDialog): # self._begin_dir = self.get_combobox_text("comboBox_begin_gl") # self._end_dir = self.get_combobox_text("comboBox_end_gl") - self.parent.tableView.selectionModel().clearSelection() - super().accept() def reject(self): diff --git a/src/View/Geometry/Window.py b/src/View/Geometry/Window.py index 237f70ed..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, @@ -286,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, @@ -303,6 +311,23 @@ 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 = InternalMeshing() From 5f6c823301b50b12bf63f4710b33463794e7918d Mon Sep 17 00:00:00 2001 From: Theophile Terraz Date: Tue, 14 Oct 2025 11:45:45 +0200 Subject: [PATCH 20/22] debug results --- src/View/Results/Table.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/View/Results/Table.py b/src/View/Results/Table.py index badda333..a1bcbbb3 100644 --- a/src/View/Results/Table.py +++ b/src/View/Results/Table.py @@ -156,9 +156,9 @@ class TableModel(PamhyrTableModel): if self._opt_data == "reach": 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)) + 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 From 7ef31846993b9fc75fb915f7713b7ecc82532ca1 Mon Sep 17 00:00:00 2001 From: Theophile Terraz Date: Tue, 14 Oct 2025 11:52:28 +0200 Subject: [PATCH 21/22] debug + disable rasterio --- src/View/Results/Window.py | 93 ++++++++++++++++++++------------------ 1 file changed, 48 insertions(+), 45 deletions(-) diff --git a/src/View/Results/Window.py b/src/View/Results/Window.py index d37f37f9..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) @@ -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 From bcbb440ad4a2867473a968a0dbd3e8dfcf954ec9 Mon Sep 17 00:00:00 2001 From: Theophile Terraz Date: Tue, 21 Oct 2025 15:11:28 +0200 Subject: [PATCH 22/22] debug import friction --- src/View/Frictions/Table.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/View/Frictions/Table.py b/src/View/Frictions/Table.py index 64a3e18f..7b5d278c 100644 --- a/src/View/Frictions/Table.py +++ b/src/View/Frictions/Table.py @@ -274,15 +274,14 @@ class FrictionTableModel(PamhyrTableModel): with open(file_name, encoding="utf-8") as rug_file: for line in rug_file: if line.upper().startswith("K"): - line = line.split() - if int(line[1]) == reach_id: - data.append(line[1:]) + if int(line[1:4]) == reach_id: + data.append(line[4:].split()) new_data = [] for d in data: new = None - minor = float(d[3]) - medium = float(d[4]) + 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 @@ -292,5 +291,5 @@ class FrictionTableModel(PamhyrTableModel): self._study.river.stricklers)) new.minor = minor new.medium = medium - new_data.append([self._data, float(d[1]), float(d[2]), new, new]) + new_data.append([self._data, float(d[0]), float(d[1]), new, new]) self.replace_data(new_data)