diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4b21b5e9..c4172794 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -186,7 +186,7 @@ unittest: - pip3 install -r ./full-requirements.txt - pip3 install -U -r ./full-requirements.txt - cd src - - python3 -m unittest discover -t . + - python3.12 -m unittest discover -t . test-pep8: stage: test diff --git a/README.md b/README.md index e7a36070..787cfee9 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Pamhyr is a free and open source graphical user interface for 1D hydro-sedimentary modelling of rivers. -![logo](https://gitlab.irstea.fr/theophile.terraz/pamhyr/-/raw/master/src/View/ui/ressources/Pamhyr2_logo.png) +![logo](https://gitlab.com/pamhyr/pamhyr2/-/raw/master/src/View/ui/ressources/Pamhyr2_logo.png) ## Features @@ -21,9 +21,9 @@ modelling of rivers. + Create and edit study alternative scenario tree Let see the -[documentation](https://gitlab.irstea.fr/theophile.terraz/pamhyr/-/wikis/home) -([:fr:](https://gitlab.irstea.fr/theophile.terraz/pamhyr/-/wikis/home-fr), -[:gb:](https://gitlab.irstea.fr/theophile.terraz/pamhyr/-/wikis/home-en)) +[documentation](https://gitlab.com/pamhyr/pamhyr2/-/wikis/home) +([:fr:](https://gitlab.com/pamhyr/pamhyr2/-/wikis/home-fr), +[:gb:](https://gitlab.com/pamhyr/pamhyr2/-/wikis/home-en)) for more details. ## Install @@ -31,11 +31,11 @@ for more details. ### GNU/Linux See documentation: -- [French :fr:](https://gitlab.irstea.fr/theophile.terraz/pamhyr/-/wikis/home/fr/Install%20on%20GNULinux) -- [English :gb:](https://gitlab.irstea.fr/theophile.terraz/pamhyr/-/wikis/home/en/Install%20on%20GNULinux) +- [French :fr:](https://gitlab.com/pamhyr/pamhyr2/-/wikis/home/fr/Install%20on%20GNULinux) +- [English :gb:](https://gitlab.com/pamhyr/pamhyr2/-/wikis/home/en/Install%20on%20GNULinux) ### Windows See documentation: -- [French :fr:](https://gitlab.irstea.fr/theophile.terraz/pamhyr/-/wikis/home/fr/Install%20on%20Windows) -- [English :gb:](https://gitlab.irstea.fr/theophile.terraz/pamhyr/-/wikis/home/en/Install%20on%20Windows) +- [French :fr:](https://gitlab.com/pamhyr/pamhyr2/-/wikis/home/fr/Install%20on%20Windows) +- [English :gb:](https://gitlab.com/pamhyr/pamhyr2/-/wikis/home/en/Install%20on%20Windows) diff --git a/doc/users/TP_AdisTS_Vieux_Rhone/Vieux_Rhone_TP_AdisTS.pamhyr b/doc/users/TP_AdisTS_Vieux_Rhone/Vieux_Rhone_TP_AdisTS.pamhyr index 02a1a1eb..d1b6f381 100644 Binary files a/doc/users/TP_AdisTS_Vieux_Rhone/Vieux_Rhone_TP_AdisTS.pamhyr and b/doc/users/TP_AdisTS_Vieux_Rhone/Vieux_Rhone_TP_AdisTS.pamhyr differ diff --git a/requirements.txt b/requirements.txt index 1856f36f..ebe3523f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,3 +14,4 @@ platformdirs>=4.2.0 pyshp>=2.3.1 rasterio==1.3.11 #fortranformat==2.0.3 + diff --git a/src/Meshing/Internal.py b/src/Meshing/Internal.py index 7a14bad5..eacd04b4 100644 --- a/src/Meshing/Internal.py +++ b/src/Meshing/Internal.py @@ -66,7 +66,6 @@ class InternalMeshing(AMeshingTool): return new_profiles def st_to_m(self, profiles, guide_list): - guide_list = ["un"] + guide_list + ["np"] max_values = [0] * (len(guide_list) - 1) max_values_index = [0] * (len(guide_list) - 1) @@ -85,11 +84,13 @@ class InternalMeshing(AMeshingTool): profiles[isect+1], guide_list[i], guide_list[i+1]) + for isect in reversed(range(0, max_values_index[i])): self.compl_sect(profiles[isect+1], profiles[isect], guide_list[i], guide_list[i+1]) + return profiles def interpolate_transversal_step(self, @@ -129,6 +130,7 @@ class InternalMeshing(AMeshingTool): p.name = f'interpol{p.rk}' new_profiles2.append(p) new_profiles.append(new_profiles2) + return new_profiles def compl_sect(self, sect1, sect2, tag1, tag2): @@ -243,8 +245,9 @@ class InternalMeshing(AMeshingTool): sect2.point(start2+len2).name = '' if tag1 != "un": sect2.point(start2).name = tag1 - if tag1 != "np": + if tag2 != "np": sect2.point(start2+len2).name = tag2 + sect2.modified() def update_rk(self, reach, begin_rk, end_rk, diff --git a/src/Model/DIFAdisTS/DIFAdisTSSpec.py b/src/Model/DIFAdisTS/DIFAdisTSSpec.py index b3265034..6baec9b3 100644 --- a/src/Model/DIFAdisTS/DIFAdisTSSpec.py +++ b/src/Model/DIFAdisTS/DIFAdisTSSpec.py @@ -34,15 +34,13 @@ class DIFAdisTSSpec(SQLSubModel): def __init__(self, id: int = -1, method: str = "", status=None, owner_scenario=-1): - super(DIFAdisTSSpec, self).__init__() + super(DIFAdisTSSpec, self).__init__( + id=id, status=status, + owner_scenario=owner_scenario + ) self._status = status - if id == -1: - self._id = DIFAdisTSSpec._id_cnt - else: - self._id = id - self._method = method self._reach = None self._start_rk = None @@ -52,8 +50,6 @@ class DIFAdisTSSpec(SQLSubModel): self._c = None self._enabled = True - DIFAdisTSSpec._id_cnt = max(DIFAdisTSSpec._id_cnt + 1, self._id) - @classmethod def _db_create(cls, execute, ext=""): execute(f""" diff --git a/src/Model/Geometry/ProfileXYZ.py b/src/Model/Geometry/ProfileXYZ.py index d928fdd1..1b513447 100644 --- a/src/Model/Geometry/ProfileXYZ.py +++ b/src/Model/Geometry/ProfileXYZ.py @@ -1098,6 +1098,7 @@ class ProfileXYZ(Profile, SQLSubModel): rk=self.rk, reach=self.reach, status=self._status) + for i, k in enumerate(self.points): p.insert_point(i, k.copy()) diff --git a/src/Model/Reservoir/Reservoir.py b/src/Model/Reservoir/Reservoir.py index e120f9ec..6137122c 100644 --- a/src/Model/Reservoir/Reservoir.py +++ b/src/Model/Reservoir/Reservoir.py @@ -312,7 +312,7 @@ class Reservoir(SQLSubModel): new_reservoir._node = next( filter( lambda n: n.id == node_id, data["nodes"] - ) + ), None ) data["reservoir"] = new_reservoir diff --git a/src/Solver/AdisTS.py b/src/Solver/AdisTS.py index b8310dfb..e3eddc4c 100644 --- a/src/Solver/AdisTS.py +++ b/src/Solver/AdisTS.py @@ -171,6 +171,187 @@ class AdisTS(CommandLineSolver): name = self._study.name return f"{name}.TRA" + def _export_ST(self, study, repertory, qlog, name="0"): + files = [] + + if qlog is not None: + qlog.put("Export ST file") + + os.makedirs(os.path.join(repertory, "net"), exist_ok=True) + + # Write header + edges = study.river.enable_edges() + for edge in edges: + name = f"Reach_{edge.id + 1:>3}".replace(" ", "0") + + with adists_file_open( + os.path.join(repertory, "net", f"{name}.ST"), + "w+" + ) as f: + files.append(str(os.path.join("net", f"{name}.ST"))) + + cnt_num = 1 + for profile in edge.reach.profiles: + self._export_ST_profile_header( + f, files, profile, cnt_num + ) + cnt_num += 1 + + # Points + for point in profile.points: + self._export_ST_point_line( + f, files, point + ) + + # Profile last line + f.write(f" 999.9990 999.9990 999.9990\n") + + def _export_ST_profile_header(self, wfile, files, + profile, cnt): + num = f"{cnt:>6}" + c1 = f"{profile.code1:>6}" + c2 = f"{profile.code2:>6}" + t = f"{len(profile.points):>6}" + rk = f"{profile.rk:>12f}"[0:12] + pname = profile.name + if profile.name == "": + # Generate name from profile id prefixed with + # 'p' (and replace space char with '0' char) + pname = f"p{profile.id:>3}".replace(" ", "0") + name = f"{pname:<19}" + + # Generate sediment additional data if available + sediment = "" + if profile.sl is not None: + if not any(filter(lambda f: ".GRA" in f, files)): + files.append(self._gra_file) + + # Number of layers + nl = len(profile.sl) + sediment = f" {nl:>3}" + + # Layers data + for layer in profile.sl.layers: + sediment += ( + f" {layer.height:>10} {layer.d50:>10} " + + f"{layer.sigma:>10} " + + f"{layer.critical_constraint:>10}" + ) + + # Profile header line + wfile.write(f"{num}{c1}{c2}{t} {rk} {pname} {sediment}\n") + + def _export_ST_point_line(self, wfile, files, point): + x = f"{point.x:<12.4f}"[0:12] + y = f"{point.y:<12.4f}"[0:12] + z = f"{point.z:<12.4f}"[0:12] + n = f"{point.name:<3}" + + # Generate sediment additional data if available + sediment = "" + prev = point.z + if point.sl is not None: + # Number of layers + nl = len(point.sl) + sediment = f"{nl:>3}" + + # Layers data + for layer in point.sl.layers: + prev = round(prev - layer.height, 5) + sediment += ( + f" {prev:>10} {layer.d50:>10} " + + f"{layer.sigma:>10} " + + f"{layer.critical_constraint:>10}" + ) + + # Point line + wfile.write(f"{x} {y} {z} {n} {sediment}\n") + + def _export_NET(self, study, repertory, qlog=None, name="0"): + + if qlog is not None: + qlog.put("Export NET file") + + with adists_file_open( + os.path.join(repertory, f"{name}.NET"), "w+") as f: + edges = study.river.enable_edges() + + for e in edges: + name = f"Reach_{e.id + 1:>3}".replace(" ", "0") + id = name + + n1 = f"{e.node1.id:3}".replace(" ", "x") + n2 = f"{e.node2.id:3}".replace(" ", "x") + file = os.path.join("net", name + ".ST") + + f.write(f"{id} {n1} {n2} {file}\n") + + def _export_fake_INI(self, study, repertory, qlog=None, name="0"): + if qlog is not None: + qlog.put("Export fake INI file") + + with adists_file_open( + os.path.join( + repertory, "Mage_fin.ini" + ), "w+" + ) as f: + edges = study.river.enable_edges() + for i, edge in enumerate(edges): + lst = list(filter( + lambda f: f.is_full_defined(), + edge.frictions.frictions + )) + rk_min = 9999999.9 + rk_max = -9999999.9 + coeff_min = -1.0 + coeff_max = -1.0 + for s in lst: # TODO optimise ? + if s.begin_rk > rk_max: + rk_max = s.begin_rk + coeff_max = s.begin_strickler + if s.begin_rk < rk_min: + rk_min = s.begin_rk + coeff_min = s.begin_strickler + if s.end_rk > rk_max: + rk_max = s.end_rk + coeff_max = s.end_strickler + if s.end_rk < rk_min: + rk_min = s.end_rk + coeff_min = s.end_strickler + + print("min max", rk_min, rk_max) + + def get_stricklers_from_rk(rk, lst): + print("rk", rk) + + coeff = None + if rk > rk_max: + coeff = coeff_max + elif rk < rk_min: + coeff = coeff_min + else: + for s in lst: + if (rk >= s.begin_rk and rk <= s.end_rk or + rk <= s.begin_rk and rk >= s.end_rk): + coeff = s.begin_strickler # TODO: inerpolate + break + + # TODO interpolation if rk is not in frictons + + if coeff is None: + logger.error( + "Study frictions are not fully defined" + ) + return None + + return coeff.minor, coeff.medium + + for j, profile in enumerate(edge.reach.profiles): + coef_min, coef_moy = get_stricklers_from_rk(profile.rk, + lst) + f.write( + f" {i+1:3}{j+1:4}{' '*116}{coef_min:9}{coef_moy:9}\n") + def _export_REP_additional_lines(self, study, rep_file): lines = filter( lambda line: line.is_enabled(), @@ -192,7 +373,7 @@ class AdisTS(CommandLineSolver): ), "w+" ) as f: path = os.path.join("..", mage_rep, name) - f.write(f"NET {path}.NET\n") + f.write(f"NET {name}.NET\n") f.write(f"REP {path}.REP\n") for file in files: @@ -201,13 +382,25 @@ class AdisTS(CommandLineSolver): self._export_REP_additional_lines(study, f) - path_mage_net = os.path.join(os.path.abspath( - os.path.join(repertory, os.pardir) - ), os.path.join(mage_rep, "net")) - path_adists_net = os.path.join(repertory, "net") + self._export_ST(study, repertory, qlog, name=name) + self._export_NET(study, repertory, qlog, name=name) - if os.path.exists(path_mage_net): - shutil.copytree(path_mage_net, path_adists_net, dirs_exist_ok=True) + # fake mage_fin.ini: + + path_mage = os.path.join(os.path.abspath( + os.path.join(repertory, os.pardir)), mage_rep) + self._export_fake_INI(study, path_mage, + qlog, name=name) + + # path_mage_net = os.path.join(os.path.abspath( + # os.path.join(repertory, os.pardir) + # ), os.path.join(mage_rep, "net")) + # path_adists_net = os.path.join(repertory, "net") + + # if os.path.exists(path_mage_net): + # shutil.copytree(path_mage_net, + # path_adists_net, + # dirs_exist_ok=True) @timer def export(self, study, repertory, qlog=None): diff --git a/src/Solver/RubarBE.py b/src/Solver/RubarBE.py index e92ca2cd..4da1ad1f 100644 --- a/src/Solver/RubarBE.py +++ b/src/Solver/RubarBE.py @@ -58,12 +58,12 @@ class Rubar3(CommandLineSolver): ("rubarbe_iovis", "n"), ("rubarbe_rep", "n"), ("rubarbe_tinit", "000:00:00:00"), - ("rubarbe_tmax", "999:99:99:00"), + ("rubarbe_tmax", "000:01:00:00"), # 1 day ("rubarbe_tiopdt", "000:00:00:00"), ("rubarbe_dt", "5.0"), ("rubarbe_ts", "999:99:99:00"), - ("rubarbe_dtsauv", "00:00:00:05"), - ("rubarbe_psave", "00:00:00:05"), + ("rubarbe_dtsauv", "00:00:05:00"), + ("rubarbe_psave", "00:00:05:00"), ("rubarbe_fdeb1", "1"), ("rubarbe_fdeb2", "10"), ("rubarbe_fdeb3", "100"), @@ -610,6 +610,28 @@ class Rubar3(CommandLineSolver): ) ts = set() + timestamp = next(filter( + lambda p: p.name == 'rubarbe_tinit', + study.river.get_params(self._type).parameters + )).value + if timestamp.count(':') == 3: + timestamp = old_pamhyr_date_to_timestamp(timestamp) + ts.add(timestamp) + + # add initial condition + for r, edge in enumerate(study.river.enable_edges()): + reach = edge.reach + ics = study.river.initial_conditions.get(edge) + q = ics.get_discharge() + z = ics.get_elevation() + k = 0 + for i, j in zip(z, q): + v = reach.profiles[k].speed(i, j) + set_and_compute_limites(reachs[r][0], k, i, j, v) + k += 1 + + # start read + end = False while True: line = f.readline() @@ -649,6 +671,97 @@ class Rubar3(CommandLineSolver): h, s, q, z = read_data_line(f) set_and_compute_limites(reach, ind, z+h, q, s) + @timer + def write_bin(self, study, fname, results, qlog=None, name="0"): + logger.info(f"write_bin: Start writing '{fname}' ...") + + with open(fname, "wb") as f: + def newline(j): return np.asarray([j], dtype=np.int32).tofile(f) + def endline(j): return np.asarray([j], dtype=np.int32).tofile(f) + + def write_int(i): + newline(len(i)*4) + np.array(i, dtype=np.int32).tofile(f) + endline(len(i)*4) + + def write_float(i): + newline(len(i)*4) + np.array(i, dtype=np.float32).tofile(f) + endline(len(i)*4) + + def write_float64(i): + newline(len(i)*8) + np.array(i, dtype=np.float64).tofile(f) + endline(len(i)*8) + + def write_float64(i): + newline(len(i)*8) + np.array(i, dtype=np.float64).tofile(f) + endline(len(i)*8) + + def write_data(npts, t, a, val): + newline(npts*4 + 13) + np.array(npts, dtype=np.int32).tofile(f) + np.array(t, dtype=np.float64).tofile(f) + np.array(bytearray(a.encode()), dtype=np.byte).tofile(f) + np.array(val, dtype=np.float32).tofile(f) + endline(npts*4 + 13) + + ts_list = sorted(results.get("timestamps")) + profiles = [] + for r in results.river.reachs: + profiles += r.profiles + + # Meta data (1st line) + nb_reach = len(results.river) + nb_profile = len(profiles) + write_int([nb_reach, nb_profile, "82"]) + + # Reach information (2nd line) + is1 = [] + is2 = [] + ltmp = [] + i = 1 + for r in results.river.reachs: + j = i+len(r)-1 + ltmp += [i, j] + is1.append(i) + is2.append(j) + i += i+len(r) + write_int(ltmp) + + # X (3rd line) + rk = [] + for r in results.river.reachs: + rk += r.geometry.get_rk() + write_float(rk) + + # Z and Y (4th line) + # ltmp = [] + # for r in results.river.reachs: + # for p in r.prifiles: + # ltpm.append(p.rk) + write_float(3*nb_profile*[0.0]) + + # Data + + for timestamp in ts_list: + q = list( + map( + lambda p: p.get_ts_key(timestamp, "Q"), + profiles + ) + ) + write_data(nb_profile, timestamp, "Q", q) + z = list( + map( + lambda p: p.get_ts_key(timestamp, "Z"), + profiles + ) + ) + write_data(nb_profile, timestamp, "Z", z) + logger.info(f"write_bin: ... end with {len(ts_list)} timestamps") + @timer def results(self, study, repertory, qlog=None, name=None): results = Results( diff --git a/src/View/Geometry/Window.py b/src/View/Geometry/Window.py index ce4bc954..2744c190 100644 --- a/src/View/Geometry/Window.py +++ b/src/View/Geometry/Window.py @@ -302,7 +302,6 @@ class GeometryWindow(PamhyrWindow): self.tableView.model().blockSignals(False) def edit_meshing(self): - rows = list( set( (i.row() for i in self.tableView.selectedIndexes()) @@ -326,6 +325,25 @@ 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() @@ -371,9 +389,6 @@ class GeometryWindow(PamhyrWindow): except Exception as e: logger_exception(e) raise ExternFileMissingError( - module="mage", - filename="MailleurTT", - path=MeshingWithMageMailleurTT._path(), src_except=e ) diff --git a/src/View/LateralContribution/translate.py b/src/View/LateralContribution/translate.py index ed04fbd3..24fe45d2 100644 --- a/src/View/LateralContribution/translate.py +++ b/src/View/LateralContribution/translate.py @@ -53,7 +53,8 @@ class LCTranslate(MainTranslate): self._dict["y"] = _translate("Geometry", "Y (m)") self._dict["z"] = _translate("Geometry", "Z (m)") self._dict["file_lat"] = _translate( - "LateralContribution", "Shapefile (*.LAT *.lat)") + "LateralContribution", + "Mage lateral contributions file (*.LAT *.lat)") self._dict["file_all"] = _translate( "LateralContribution", "All files (*)") diff --git a/src/View/MainWindow.py b/src/View/MainWindow.py index f651b3d9..561b81c3 100644 --- a/src/View/MainWindow.py +++ b/src/View/MainWindow.py @@ -2006,6 +2006,13 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): if res.river.reach(r).has_bedload(): res.river.reach(r).bufferize(ts, "zfd") + for res in (result3, result4, result5): + for r in range(int(res.get("nb_reach"))): + for key in ["Z", "Q", "V"]: + res.river.reach(r).bufferize(ts, key) + if res.river.reach(r).has_bedload(): + res.river.reach(r).bufferize(ts, "zfd") + return [result4, result5, result3] def open_results_adists(self): diff --git a/src/View/Results/CustomExport/CustomExport.py b/src/View/Results/CustomExport/CustomExport.py index fe430c7c..93efae93 100644 --- a/src/View/Results/CustomExport/CustomExport.py +++ b/src/View/Results/CustomExport/CustomExport.py @@ -19,7 +19,7 @@ from View.Tools.PamhyrWindow import PamhyrDialog from PyQt5.QtWidgets import ( - QRadioButton, QCheckBox, QVBoxLayout, QLabel, + QRadioButton, QCheckBox, QVBoxLayout, QLabel, QFrame ) from View.Results.translate import ResultsTranslate @@ -43,6 +43,7 @@ class CustomExportDialog(PamhyrDialog): self.setup_radio_buttons_x() self.setup_radio_buttons_res() + self.setup_label() self.setup_envelop_box() self.setup_check_boxes() @@ -87,6 +88,22 @@ class CustomExportDialog(PamhyrDialog): layout.addStretch() + def setup_label(self): + self._label = self.find(QLabel, "label_4") + self._label.setFrameStyle(QFrame.StyledPanel) + self._label.setStyleSheet('background-color: white') + self.set_label() + for r in self._radio: + r[1].clicked.connect(self.set_label) + + def set_label(self): + if self._radio[0][1].isChecked(): + self._label.setText(self._parent.text_bief() + "\n" + + self._parent.text_time()) + else: + self._label.setText(self._parent.text_bief() + "\n" + + self._parent.text_profile()) + def setup_envelop_box(self): layout = self.find(QVBoxLayout, "verticalLayout_x") self._envelop = QCheckBox( diff --git a/src/View/Results/CustomExport/CustomExportAdis.py b/src/View/Results/CustomExport/CustomExportAdis.py index d2364ac6..ec3fd68a 100644 --- a/src/View/Results/CustomExport/CustomExportAdis.py +++ b/src/View/Results/CustomExport/CustomExportAdis.py @@ -19,7 +19,7 @@ from View.Tools.PamhyrWindow import PamhyrDialog from PyQt5.QtWidgets import ( - QRadioButton, QCheckBox, QVBoxLayout, + QRadioButton, QCheckBox, QVBoxLayout, QLabel, QFrame ) from View.Results.translate import ResultsTranslate @@ -50,6 +50,7 @@ class CustomExportAdisDialog(PamhyrDialog): self.setup_radio_buttons_x() self.setup_radio_buttons_pol() + self.setup_label() self.setup_check_boxes() self.value = None @@ -84,6 +85,22 @@ class CustomExportAdisDialog(PamhyrDialog): self._radio2[0][1].setChecked(True) layout.addStretch() + def setup_label(self): + self._label = self.find(QLabel, "label_4") + self._label.setFrameStyle(QFrame.StyledPanel) + self._label.setStyleSheet('background-color: white') + self.set_label() + for r in self._radio: + r[1].clicked.connect(self.set_label) + + def set_label(self): + if self._radio[0][1].isChecked(): + self._label.setText(self._parent.text_bief() + "\n" + + self._parent.text_time()) + else: + self._label.setText(self._parent.text_bief() + "\n" + + self._parent.text_profile()) + def setup_check_boxes(self): self._check = [] layout = self.find(QVBoxLayout, "verticalLayout_y") diff --git a/src/View/Results/CustomPlot/CustomPlotValuesSelectionDialog.py b/src/View/Results/CustomPlot/CustomPlotValuesSelectionDialog.py index aa3daadc..c9b8fa17 100644 --- a/src/View/Results/CustomPlot/CustomPlotValuesSelectionDialog.py +++ b/src/View/Results/CustomPlot/CustomPlotValuesSelectionDialog.py @@ -19,7 +19,7 @@ from View.Tools.PamhyrWindow import PamhyrDialog from PyQt5.QtWidgets import ( - QRadioButton, QCheckBox, QVBoxLayout, + QRadioButton, QCheckBox, QVBoxLayout, QLabel, QFrame ) from View.Results.CustomPlot.Translate import CustomPlotTranslate @@ -38,10 +38,12 @@ class CustomPlotValuesSelectionDialog(PamhyrDialog): parent=parent ) + self._parent = parent self._available_values_x = self._trad.get_dict("values_x") self._available_values_y = self._trad.get_dict("values_y") self.setup_radio_buttons() + self.setup_label() self.setup_envelop_box() self.setup_check_boxs() @@ -62,6 +64,22 @@ class CustomPlotValuesSelectionDialog(PamhyrDialog): self._radio[0][1].setChecked(True) layout.addStretch() + def setup_label(self): + self._label = self.find(QLabel, "label_3") + self._label.setFrameStyle(QFrame.StyledPanel) + self._label.setStyleSheet('background-color: white') + self.set_label() + for r in self._radio: + r[1].clicked.connect(self.set_label) + + def set_label(self): + if self._radio[0][1].isChecked(): + self._label.setText(self._parent.text_bief() + "\n" + + self._parent.text_time()) + else: + self._label.setText(self._parent.text_bief() + "\n" + + self._parent.text_profile()) + def setup_envelop_box(self): self._envelop = [] layout = self.find(QVBoxLayout, "verticalLayout_x") diff --git a/src/View/Results/CustomPlot/Plot.py b/src/View/Results/CustomPlot/Plot.py index 2f3e4741..ddbeb72b 100644 --- a/src/View/Results/CustomPlot/Plot.py +++ b/src/View/Results/CustomPlot/Plot.py @@ -204,7 +204,8 @@ class CustomPlot(PamhyrPlot): e = list( map( - lambda p: max(self.get_ts_zmin(p, res_id)), + lambda p: max(self.get_ts_zmin( + p, self._current_res_id)), range(len(reach)) ) ) @@ -217,7 +218,8 @@ class CustomPlot(PamhyrPlot): e = list( map( - lambda p: min(self.get_ts_zmin(p, res_id)), + lambda p: min(self.get_ts_zmin( + p, self._current_res_id)), range(len(reach)) ) ) diff --git a/src/View/Results/Window.py b/src/View/Results/Window.py index 9324f356..868aeeb5 100644 --- a/src/View/Results/Window.py +++ b/src/View/Results/Window.py @@ -314,16 +314,12 @@ class ResultsWindow(PamhyrWindow): super(ResultsWindow, self).closeEvent(event) def _compute_status_label(self): - # Timestamp - ts = self._timestamps[self._slider_time.value()] - t0 = datetime.fromtimestamp(0) - fts = str( - datetime.fromtimestamp(ts) - t0 - ) - fts.replace("days", _translate("Results", "days"))\ - .replace("day", _translate("Results", "day")) + return (self.text_bief() + " | " + + self.text_profile() + " | " + + self.text_time()) + def text_bief(self): # Reach table = self.find(QTableView, f"tableView_reach") indexes = table.selectedIndexes() @@ -331,7 +327,16 @@ class ResultsWindow(PamhyrWindow): reach = self._study.river.enable_edges()[0] else: reach = self._study.river.enable_edges()[indexes[0].row()] + return f"{self._trad['reach']}: {reach.name}" + def text_profile(self): + # Reach + table = self.find(QTableView, f"tableView_reach") + indexes = table.selectedIndexes() + if len(indexes) == 0: + reach = self._study.river.enable_edges()[0] + else: + reach = self._study.river.enable_edges()[indexes[0].row()] # Profile table = self.find(QTableView, f"tableView_profile") indexes = table.selectedIndexes() @@ -341,10 +346,22 @@ class ResultsWindow(PamhyrWindow): profile = reach.reach.profile(indexes[0].row()) pname = profile.name if profile.name != "" else profile.rk + return f"{self._trad['cross_section']}: {pname}" - return (f"{self._trad['reach']}: {reach.name} | " + - f"{self._trad['cross_section']}: {pname} | " + - f"{self._trad['unit_time_s']} : {fts} ({ts} sec)") + def text_time(self): + # Timestamp + ts = self._timestamps[self._slider_time.value()] + + t0 = datetime.fromtimestamp(0) + fts = str( + datetime.fromtimestamp(ts) - t0 + ) + fts = str( + datetime.fromtimestamp(ts) - t0 + ) + fts.replace("days", _translate("Results", "days"))\ + .replace("day", _translate("Results", "day")) + return f"{self._trad['time']} : {fts} ({ts} sec)" def setup_statusbar(self): txt = self._compute_status_label() @@ -779,20 +796,29 @@ class ResultsWindow(PamhyrWindow): first_line.append(f"Profile: {pname}") val_dict = self._export_time(profile_id, y, solver_id) - with open(filename, 'w', newline='') as csvfile: - writer = csv.writer(csvfile, delimiter=',', - quotechar='|', quoting=csv.QUOTE_MINIMAL) - dict_x = self._trad.get_dict("values_x") - header = [] - writer.writerow(first_line) - for text in val_dict.keys(): - header.append(text) - writer.writerow(header) - for row in range(len(val_dict[dict_x[x]])): - line = [] - for var in val_dict.keys(): - line.append(val_dict[var][row]) - writer.writerow(line) + try: + with open(filename, 'w', newline='') as csvfile: + writer = csv.writer(csvfile, delimiter=',', + quotechar='|', quoting=csv.QUOTE_MINIMAL) + dict_x = self._trad.get_dict("values_x") + header = [] + writer.writerow(first_line) + for text in val_dict.keys(): + header.append(text) + writer.writerow(header) + for row in range(len(val_dict[dict_x[x]])): + line = [] + for var in val_dict.keys(): + line.append(val_dict[var][row]) + writer.writerow(line) + self._timer.stop() + except Exception as e: + self.message_box( + window_title=self._trad["Warning"], + text=self._trad["mb_write_error"], + informative_text=self._trad["mb_close_file"] + ) + logger_exception(e) def export_all(self, reach, directory, timestamps): name = reach.name @@ -805,17 +831,25 @@ class ResultsWindow(PamhyrWindow): f"reach_{name}.csv" ) - with open(file_name, 'w', newline='') as csvfile: - writer = csv.writer(csvfile, delimiter=',', - quotechar='|', quoting=csv.QUOTE_MINIMAL) - ts = timestamps[0] - writer.writerow(self._table["raw_data"]._headers) - for row in range(self._table["raw_data"].rowCount()): - line = [] - for column in range(self._table["raw_data"].columnCount()): - index = self._table["raw_data"].index(row, column) - line.append(self._table["raw_data"].data(index)) - writer.writerow(line) + try: + with open(file_name, 'w', newline='') as csvfile: + writer = csv.writer(csvfile, delimiter=',', + quotechar='|', quoting=csv.QUOTE_MINIMAL) + ts = timestamps[0] + writer.writerow(self._table["raw_data"]._headers) + for row in range(self._table["raw_data"].rowCount()): + line = [] + for column in range(self._table["raw_data"].columnCount()): + index = self._table["raw_data"].index(row, column) + line.append(self._table["raw_data"].data(index)) + writer.writerow(line) + except Exception as e: + self.message_box( + window_title=self._trad["Warning"], + text=self._trad["mb_write_errore"], + informative_text=self._trad["mb_close_file"] + ) + logger_exception(e) def export_current(self): self.file_dialog( diff --git a/src/View/Results/WindowAdisTS.py b/src/View/Results/WindowAdisTS.py index 727d17ea..74b4cd8d 100644 --- a/src/View/Results/WindowAdisTS.py +++ b/src/View/Results/WindowAdisTS.py @@ -345,24 +345,29 @@ class ResultsWindowAdisTS(PamhyrWindow): super(ResultsWindowAdisTS, self).closeEvent(event) def _compute_status_label(self): - # Timestamp - ts = self._timestamps[self._slider_time.value()] - - t0 = datetime.fromtimestamp(0) - fts = str( - datetime.fromtimestamp(ts) - t0 - ) - fts.replace("days", _translate("Results", "days"))\ - .replace("day", _translate("Results", "day")) + return (self.text_bief() + " | " + + self.text_profile() + " | " + + self.text_pollutant() + " | " + + self.text_time()) + def text_bief(self): # Reach table = self.find(QTableView, f"tableView_reach") indexes = table.selectedIndexes() if len(indexes) == 0: - reach = self._study.river.edges()[0] + reach = self._study.river.enable_edges()[0] else: - reach = self._study.river.edges()[indexes[0].row()] + reach = self._study.river.enable_edges()[indexes[0].row()] + return f"{self._trad['reach']}: {reach.name}" + def text_profile(self): + # Reach + table = self.find(QTableView, f"tableView_reach") + indexes = table.selectedIndexes() + if len(indexes) == 0: + reach = self._study.river.enable_edges()[0] + else: + reach = self._study.river.enable_edges()[indexes[0].row()] # Profile table = self.find(QTableView, f"tableView_profile") indexes = table.selectedIndexes() @@ -372,7 +377,24 @@ class ResultsWindowAdisTS(PamhyrWindow): profile = reach.reach.profile(indexes[0].row()) pname = profile.name if profile.name != "" else profile.rk + return f"{self._trad['cross_section']}: {pname}" + def text_time(self): + # Timestamp + ts = self._timestamps[self._slider_time.value()] + + t0 = datetime.fromtimestamp(0) + fts = str( + datetime.fromtimestamp(ts) - t0 + ) + fts = str( + datetime.fromtimestamp(ts) - t0 + ) + fts.replace("days", _translate("Results", "days"))\ + .replace("day", _translate("Results", "day")) + return f"{self._trad['time']} : {fts} ({ts} sec)" + + def text_pollutant(self): # Pollutant table = self.find(QTableView, f"tableView_pollutants") indexes = table.selectedIndexes() @@ -381,10 +403,7 @@ class ResultsWindowAdisTS(PamhyrWindow): self._results.pollutants_list[i.row()+1] for i in indexes ] - return (f"{self._trad['reach']}: {reach.name} | " + - f"{self._trad['cross_section']}: {pname} | " + - f"Pollutant: {', '.join(self.pollutant_label)} | " + - f"{self._trad['unit_time_s']} : {fts} ({ts} sec)") + return (f"Pollutant: {', '.join(self.pollutant_label)}") def setup_statusbar(self): txt = self._compute_status_label() diff --git a/src/View/Results/translate.py b/src/View/Results/translate.py index 95ddd9a4..df4c26dd 100644 --- a/src/View/Results/translate.py +++ b/src/View/Results/translate.py @@ -56,6 +56,13 @@ class ResultsTranslate(MainTranslate): self._dict["ImageCoordinates"] = _translate( "Results", "Image coordinates" ) + self._dict["mb_write_error"] = _translate( + "Results", "An error occured when writing to file" + ) + self._dict["mb_close_file"] = _translate( + "Results", + "If the file is in use, close it and try again" + ) self._sub_dict["table_headers_reach"] = { "name": _translate("Results", "Reach name"), diff --git a/src/View/RunSolver/WindowAdisTS.py b/src/View/RunSolver/WindowAdisTS.py index 17c5a85c..450b2275 100644 --- a/src/View/RunSolver/WindowAdisTS.py +++ b/src/View/RunSolver/WindowAdisTS.py @@ -94,7 +94,8 @@ class SelectSolverWindowAdisTS(PamhyrDialog): ) solvers_mage = list(filter( - lambda x: "mage" in x._type.lower(), self._config.solvers + lambda x: "mage" or "rubar" in x._type.lower(), + self._config.solvers )) solvers_mage_names = list(map(lambda x: x._name, solvers_mage)) diff --git a/src/View/ui/CustomExportAdisDialog.ui b/src/View/ui/CustomExportAdisDialog.ui index b1c0f419..c2b4a81f 100644 --- a/src/View/ui/CustomExportAdisDialog.ui +++ b/src/View/ui/CustomExportAdisDialog.ui @@ -7,23 +7,13 @@ 0 0 194 - 70 + 126 Dialog - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - @@ -64,6 +54,23 @@ + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + TextLabel + + + diff --git a/src/View/ui/CustomPlotValuesSelectionDialog.ui b/src/View/ui/CustomPlotValuesSelectionDialog.ui index f7daec37..0414519c 100644 --- a/src/View/ui/CustomPlotValuesSelectionDialog.ui +++ b/src/View/ui/CustomPlotValuesSelectionDialog.ui @@ -7,14 +7,24 @@ 0 0 414 - 70 + 132 Dialog - + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + Qt::Horizontal @@ -43,13 +53,10 @@ - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + TextLabel diff --git a/src/View/ui/InitialConditions_Dialog_Generator_Height.ui b/src/View/ui/InitialConditions_Dialog_Generator_Height.ui index 36d77a44..68dbe7f6 100644 --- a/src/View/ui/InitialConditions_Dialog_Generator_Height.ui +++ b/src/View/ui/InitialConditions_Dialog_Generator_Height.ui @@ -22,7 +22,7 @@ - Upstream height (m) + Upstream elevation (m) @@ -70,7 +70,7 @@ - Downstream height (m) + Downstream elevation (m) diff --git a/src/config.py b/src/config.py index eb3f3f9b..d94b39ca 100644 --- a/src/config.py +++ b/src/config.py @@ -101,6 +101,7 @@ class Config(SQL): # Add default solver posix = os.name == 'posix' ext = "" if posix else ".exe" + path = os.path.join("@install_dir", "mage8", f"mage{ext}") self.execute(f""" INSERT INTO solver VALUES ( @@ -111,7 +112,7 @@ class Config(SQL): '', '', '', '', - '@install_dir/mage/mage{ext} @args @input', + '{path} @args @input', '' ) """) @@ -135,16 +136,19 @@ class Config(SQL): if int(release) < 5: posix = os.name == 'posix' ext = "" if posix else ".exe" + path = os.path.join("@install_dir", "mage8", f"mage{ext}") self.execute( "UPDATE solver SET cmd_solver=" + - f"'@install_dir/mage8/mage{ext} @args @input' " + f"'{path} @args @input' " "WHERE name='default-mage'" ) if int(release) < 6: posix = os.name == 'posix' ext = "" if posix else ".exe" + path = os.path.join("@install_dir", + "adists", f"adists{ext}") self.execute(f""" INSERT INTO solver VALUES ( @@ -155,19 +159,21 @@ class Config(SQL): '', '', '', '', - '@install_dir/adists/adists{ext} @args @input', + '{path} @args @input', '' ) """) self.execute( "UPDATE solver SET cmd_solver=" + - f"'@install_dir/adists/adists{ext} @args @input' " + f"'{path} @args @input' " "WHERE name='default-AdisTS'" ) if int(release) < 7: posix = os.name == 'posix' ext = "" if posix else ".exe" + path = os.path.join("@install_dir", + "rubar", f"rubarbe{ext}") self.execute(f""" INSERT INTO solver VALUES ( @@ -178,13 +184,13 @@ class Config(SQL): '', '', '', '', - '@install_dir/rubar/rubar3{ext} @args @input', + '{path} @args @input', '' ) """) self.execute( "UPDATE solver SET cmd_solver=" + - f"'@install_dir/rubar/rubar3{ext} @args @input' " + f"'{path} @args @input' " "WHERE name='default-Rubar3'" ) @@ -350,14 +356,24 @@ class Config(SQL): ctor = solver_type_list["mage8"] new = ctor("default-mage") new._description = "Default Pamhyr2 mage 8 version" - new._cmd_solver = f""""@install_dir/mage8/mage{ext}" @args @input""" + path = os.path.join("@install_dir", "mage8", "mage") + new._cmd_solver = f""""{path}{ext}" @args @input""" self._solvers.append(new) # AdisTS ctor = solver_type_list["adistswc"] new = ctor("default-AdisTS") new._description = "Default Pamhyr2 AdisTS version" - new._cmd_solver = f""""@install_dir/adists/adists{ext}" @args @input""" + path = os.path.join("@install_dir", "adists", "adists") + new._cmd_solver = f""""{path}{ext}" @args @input""" + self._solvers.append(new) + + # Rubar3 + ctor = solver_type_list["rubar3"] + new = ctor("default-Rubar3") + new._description = "Default Pamhyr2 Rubar 3 version" + path = os.path.join("@install_dir", "rubar", f"rubarbe{ext}") + new._cmd_solver = f""""{path}" @args @input""" self._solvers.append(new) # Rubar3