From f95c646ef3fd3de88cf420d18a4a937e528cd86e Mon Sep 17 00:00:00 2001 From: Dylan Jeannin Date: Tue, 11 Aug 2026 17:29:49 +0200 Subject: [PATCH 1/3] Split reach: D90 and DIF now spread by the split --- src/Model/D90AdisTS/D90AdisTS.py | 41 ++++++++++++++++++++++++++++ src/Model/D90AdisTS/D90AdisTSList.py | 4 +++ src/Model/D90AdisTS/D90AdisTSSpec.py | 10 +++++++ src/Model/DIFAdisTS/DIFAdisTS.py | 41 ++++++++++++++++++++++++++++ src/Model/DIFAdisTS/DIFAdisTSList.py | 4 +++ src/Model/DIFAdisTS/DIFAdisTSSpec.py | 12 ++++++++ src/Model/River.py | 6 ++++ src/View/D90AdisTS/Table.py | 9 +++++- src/View/DIFAdisTS/Table.py | 9 +++++- 9 files changed, 134 insertions(+), 2 deletions(-) diff --git a/src/Model/D90AdisTS/D90AdisTS.py b/src/Model/D90AdisTS/D90AdisTS.py index 5c222f65..f901bf01 100644 --- a/src/Model/D90AdisTS/D90AdisTS.py +++ b/src/Model/D90AdisTS/D90AdisTS.py @@ -275,3 +275,44 @@ class D90AdisTS(SQLSubModel): x.set_as_not_deleted() self.modified() + + def split_reach(self, reach, profile, reach1, reach2): + split_rk = profile.rk + reach_rks = reach.reach.get_rk() + if len(reach_rks) == 0: + return + + parts = [ + (reach1.id, min(reach_rks), split_rk), + (reach2.id, split_rk, max(reach_rks)), + ] + specifications = [ + specification + for specification in self._data + if (not specification.is_deleted() and + specification.reach == reach.id) + ] + + for specification in specifications: + begin = specification.start_rk + end = specification.end_rk + if begin is None or end is None: + continue + + lower, upper = sorted((begin, end)) + reverse = begin > end + + for new_reach, part_lower, part_upper in parts: + clipped_lower = max(lower, part_lower) + clipped_upper = min(upper, part_upper) + if clipped_lower > clipped_upper: + continue + + start_rk = clipped_upper if reverse else clipped_lower + end_rk = clipped_lower if reverse else clipped_upper + self._data.append(specification.cloned_for( + new_reach, start_rk, end_rk + )) + + if len(specifications) != 0: + self.modified() diff --git a/src/Model/D90AdisTS/D90AdisTSList.py b/src/Model/D90AdisTS/D90AdisTSList.py index ce4f6ef6..89a0353d 100644 --- a/src/Model/D90AdisTS/D90AdisTSList.py +++ b/src/Model/D90AdisTS/D90AdisTSList.py @@ -61,6 +61,10 @@ class D90AdisTSList(PamhyrModelList): self._status.modified() return n + def split_reach(self, reach, profile, reach1, reach2): + for d90 in self.lst: + d90.split_reach(reach, profile, reach1, reach2) + @property def D90_AdisTS_List(self): return self.lst diff --git a/src/Model/D90AdisTS/D90AdisTSSpec.py b/src/Model/D90AdisTS/D90AdisTSSpec.py index 8d8252bb..4da6b1bf 100644 --- a/src/Model/D90AdisTS/D90AdisTSSpec.py +++ b/src/Model/D90AdisTS/D90AdisTSSpec.py @@ -261,3 +261,13 @@ class D90AdisTSSpec(SQLSubModel): def enabled(self, enabled): self._enabled = enabled self.modified() + + def cloned_for(self, reach, start_rk, end_rk): + new = D90AdisTSSpec(name=self._name_section, status=self._status) + new._reach = reach + new._start_rk = start_rk + new._end_rk = end_rk + new._d90 = self._d90 + new._enabled = self._enabled + new.modified() + return new diff --git a/src/Model/DIFAdisTS/DIFAdisTS.py b/src/Model/DIFAdisTS/DIFAdisTS.py index b642f7d6..97c05064 100644 --- a/src/Model/DIFAdisTS/DIFAdisTS.py +++ b/src/Model/DIFAdisTS/DIFAdisTS.py @@ -329,3 +329,44 @@ class DIFAdisTS(SQLSubModel): x.set_as_not_deleted() self.modified() + + def split_reach(self, reach, profile, reach1, reach2): + split_rk = profile.rk + reach_rks = reach.reach.get_rk() + if len(reach_rks) == 0: + return + + parts = [ + (reach1.id, min(reach_rks), split_rk), + (reach2.id, split_rk, max(reach_rks)), + ] + specifications = [ + specification + for specification in self._data + if (not specification.is_deleted() and + specification.reach == reach.id) + ] + + for specification in specifications: + begin = specification.start_rk + end = specification.end_rk + if begin is None or end is None: + continue + + lower, upper = sorted((begin, end)) + reverse = begin > end + + for new_reach, part_lower, part_upper in parts: + clipped_lower = max(lower, part_lower) + clipped_upper = min(upper, part_upper) + if clipped_lower > clipped_upper: + continue + + start_rk = clipped_upper if reverse else clipped_lower + end_rk = clipped_lower if reverse else clipped_upper + self._data.append(specification.cloned_for( + new_reach, start_rk, end_rk + )) + + if len(specifications) != 0: + self.modified() diff --git a/src/Model/DIFAdisTS/DIFAdisTSList.py b/src/Model/DIFAdisTS/DIFAdisTSList.py index a765547c..d80fabc1 100644 --- a/src/Model/DIFAdisTS/DIFAdisTSList.py +++ b/src/Model/DIFAdisTS/DIFAdisTSList.py @@ -61,6 +61,10 @@ class DIFAdisTSList(PamhyrModelList): self._status.modified() return n + def split_reach(self, reach, profile, reach1, reach2): + for dif in self.lst: + dif.split_reach(reach, profile, reach1, reach2) + @property def DIF_AdisTS_List(self): return self.lst diff --git a/src/Model/DIFAdisTS/DIFAdisTSSpec.py b/src/Model/DIFAdisTS/DIFAdisTSSpec.py index fe185dfa..83daa989 100644 --- a/src/Model/DIFAdisTS/DIFAdisTSSpec.py +++ b/src/Model/DIFAdisTS/DIFAdisTSSpec.py @@ -293,3 +293,15 @@ class DIFAdisTSSpec(SQLSubModel): def enabled(self, enabled): self._enabled = enabled self._status.modified() + + def cloned_for(self, reach, start_rk, end_rk): + new = DIFAdisTSSpec(method=self._method, status=self._status) + new._reach = reach + new._start_rk = start_rk + new._end_rk = end_rk + new._dif = self._dif + new._b = self._b + new._c = self._c + new._enabled = self._enabled + new.modified() + return new diff --git a/src/Model/River.py b/src/Model/River.py index 35e4416f..8c29fef3 100644 --- a/src/Model/River.py +++ b/src/Model/River.py @@ -1000,5 +1000,11 @@ Last export at: @date.""" self._WeatherParameters.split_reach( reach, profile, r1, r2 ) + self._D90AdisTS.split_reach( + reach, profile, r1, r2 + ) + self._DIFAdisTS.split_reach( + reach, profile, r1, r2 + ) return r1, r2 diff --git a/src/View/D90AdisTS/Table.py b/src/View/D90AdisTS/Table.py index 0e2b21d0..f875c6ba 100644 --- a/src/View/D90AdisTS/Table.py +++ b/src/View/D90AdisTS/Table.py @@ -123,9 +123,16 @@ class D90TableModel(PamhyrTableModel): self._undo.indexChanged.connect(lambda _: self.update()) def _setup_lst(self): + enabled_reaches = { + reach.id for reach in self._river.enable_edges() + } self._lst = list( filter( - lambda d90: d90._deleted is False, + lambda d90: ( + d90._deleted is False + and (d90.reach in (None, -1) + or d90.reach in enabled_reaches) + ), self._data._data ) ) diff --git a/src/View/DIFAdisTS/Table.py b/src/View/DIFAdisTS/Table.py index 2a8fce12..3a41d379 100644 --- a/src/View/DIFAdisTS/Table.py +++ b/src/View/DIFAdisTS/Table.py @@ -125,9 +125,16 @@ class DIFTableModel(PamhyrTableModel): self._undo.indexChanged.connect(lambda _: self.update()) def _setup_lst(self): + enabled_reaches = { + reach.id for reach in self._river.enable_edges() + } self._lst = list( filter( - lambda dif: dif._deleted is False, + lambda dif: ( + dif._deleted is False + and (dif.reach in (None, -1) + or dif.reach in enabled_reaches) + ), self._data._data ) ) From 3d467c96204b6a3fdfe680729c48c1315ed050a9 Mon Sep 17 00:00:00 2001 From: Dylan Jeannin Date: Tue, 11 Aug 2026 15:57:04 +0200 Subject: [PATCH 2/3] Limit case: we cannot split reaches that contains less than 3 profiles, and not split on a limit profile (first or last one) --- src/Model/River.py | 6 ++++++ src/View/Network/GraphWidget.py | 11 +++++++++++ src/View/Network/ProfileDialog.py | 7 +++++-- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/Model/River.py b/src/Model/River.py index 8c29fef3..eecdb4b8 100644 --- a/src/Model/River.py +++ b/src/Model/River.py @@ -963,6 +963,12 @@ Last export at: @date.""" self._results[solv_type] = results def _split_reach(self, reach, profile): + profiles = reach.reach.profiles + if not profiles or profile not in profiles[1:-1]: + raise ValueError( + "A reach cannot be split at its first or last profile" + ) + node1 = reach.node1 node2 = reach.node2 diff --git a/src/View/Network/GraphWidget.py b/src/View/Network/GraphWidget.py index 114053a4..ba7e40c6 100644 --- a/src/View/Network/GraphWidget.py +++ b/src/View/Network/GraphWidget.py @@ -32,6 +32,7 @@ from PyQt5.QtGui import ( from PyQt5.QtWidgets import ( QApplication, QGraphicsScene, QGraphicsView, QGraphicsItem, QGraphicsTextItem, QMenu, + QMessageBox, ) from Model.Network.Node import Node @@ -724,6 +725,16 @@ class GraphWidget(QGraphicsView): def split_edge(self, item): edge = item.edge + if len(edge.reach.profiles) < 3: + QMessageBox.information( + self, + self._trad["menu_split_edge"], + _translate( + "Network", + "No profile is available to split this reach." + ) + ) + return dlg = SelectProfileDialog( study=self.parent._study, diff --git a/src/View/Network/ProfileDialog.py b/src/View/Network/ProfileDialog.py index 28caa99d..66abccaf 100644 --- a/src/View/Network/ProfileDialog.py +++ b/src/View/Network/ProfileDialog.py @@ -73,7 +73,10 @@ class SelectProfileDialog(PamhyrDialog): list( map( lambda p: p.display_name(), - self._reach.reach.profiles))) + self._reach.reach.profiles[1:-1] + ) + ) + ) def accept(self): profile = self.get_combobox_text("comboBox") @@ -81,7 +84,7 @@ class SelectProfileDialog(PamhyrDialog): self._profile = next( filter( lambda p: p.display_name() == profile, - self._reach.reach.profiles + self._reach.reach.profiles[1:-1] ) ) From dd31ffc5a23f803ceac3c036715d24298b9c6c8b Mon Sep 17 00:00:00 2001 From: Dylan Jeannin Date: Wed, 12 Aug 2026 12:09:47 +0200 Subject: [PATCH 3/3] Split reach: handling the case where the reach is split on a data used to define a data (an initial condition for example) --- src/Model/D90AdisTS/D90AdisTS.py | 2 +- src/Model/DIFAdisTS/DIFAdisTS.py | 2 +- src/Model/Friction/FrictionList.py | 20 ++++++++++++------- .../InitialConditionsAdisTS.py | 2 +- .../InitialConditionsTemperature.py | 2 +- .../LateralContributionList.py | 2 +- .../LateralContributionsAdisTSList.py | 2 +- .../WeatherParametersList.py | 2 +- 8 files changed, 20 insertions(+), 14 deletions(-) diff --git a/src/Model/D90AdisTS/D90AdisTS.py b/src/Model/D90AdisTS/D90AdisTS.py index f901bf01..80ac1267 100644 --- a/src/Model/D90AdisTS/D90AdisTS.py +++ b/src/Model/D90AdisTS/D90AdisTS.py @@ -305,7 +305,7 @@ class D90AdisTS(SQLSubModel): for new_reach, part_lower, part_upper in parts: clipped_lower = max(lower, part_lower) clipped_upper = min(upper, part_upper) - if clipped_lower > clipped_upper: + if clipped_lower >= clipped_upper: continue start_rk = clipped_upper if reverse else clipped_lower diff --git a/src/Model/DIFAdisTS/DIFAdisTS.py b/src/Model/DIFAdisTS/DIFAdisTS.py index 97c05064..f907a40e 100644 --- a/src/Model/DIFAdisTS/DIFAdisTS.py +++ b/src/Model/DIFAdisTS/DIFAdisTS.py @@ -359,7 +359,7 @@ class DIFAdisTS(SQLSubModel): for new_reach, part_lower, part_upper in parts: clipped_lower = max(lower, part_lower) clipped_upper = min(upper, part_upper) - if clipped_lower > clipped_upper: + if clipped_lower >= clipped_upper: continue start_rk = clipped_upper if reverse else clipped_lower diff --git a/src/Model/Friction/FrictionList.py b/src/Model/Friction/FrictionList.py index 963c3316..df4573b8 100644 --- a/src/Model/Friction/FrictionList.py +++ b/src/Model/Friction/FrictionList.py @@ -109,16 +109,22 @@ class FrictionList(PamhyrModelList): ((999 ** 100), -(999 ** 100)) ) - frictions = list( - filter( - lambda f: ((rk_min <= f.begin_rk <= rk_max) or - (rk_min <= f.end_rk <= rk_max)), - self._lst)) + for friction in self.lst: + lower, upper = sorted((friction.begin_rk, friction.end_rk)) + clipped_lower = max(lower, rk_min) + clipped_upper = min(upper, rk_max) + if clipped_lower >= clipped_upper: + continue - for ind, friction in enumerate(frictions): n = friction.cloned_for(reach) + if friction.begin_rk > friction.end_rk: + n.begin_rk = clipped_upper + n.end_rk = clipped_lower + else: + n.begin_rk = clipped_lower + n.end_rk = clipped_upper - new._lst.insert(ind, n) + new._lst.append(n) new._status.modified() return new diff --git a/src/Model/InitialConditionsAdisTS/InitialConditionsAdisTS.py b/src/Model/InitialConditionsAdisTS/InitialConditionsAdisTS.py index 65a72e90..903fe877 100644 --- a/src/Model/InitialConditionsAdisTS/InitialConditionsAdisTS.py +++ b/src/Model/InitialConditionsAdisTS/InitialConditionsAdisTS.py @@ -391,7 +391,7 @@ class InitialConditionsAdisTS(SQLSubModel): for new_reach, part_lower, part_upper in parts: clipped_lower = max(lower, part_lower) clipped_upper = min(upper, part_upper) - if clipped_lower > clipped_upper: + if clipped_lower >= clipped_upper: continue start_rk = clipped_upper if reverse else clipped_lower diff --git a/src/Model/InitialConditionsTemperature/InitialConditionsTemperature.py b/src/Model/InitialConditionsTemperature/InitialConditionsTemperature.py index 33e2c99e..986e603e 100644 --- a/src/Model/InitialConditionsTemperature/InitialConditionsTemperature.py +++ b/src/Model/InitialConditionsTemperature/InitialConditionsTemperature.py @@ -254,7 +254,7 @@ class InitialConditionsTemperature(SQLSubModel): for new_reach, part_lower, part_upper in parts: clipped_lower = max(lower, part_lower) clipped_upper = min(upper, part_upper) - if clipped_lower > clipped_upper: + if clipped_lower >= clipped_upper: continue start_rk = clipped_upper if reverse else clipped_lower diff --git a/src/Model/LateralContribution/LateralContributionList.py b/src/Model/LateralContribution/LateralContributionList.py index c6aa7162..803d2c27 100644 --- a/src/Model/LateralContribution/LateralContributionList.py +++ b/src/Model/LateralContribution/LateralContributionList.py @@ -117,7 +117,7 @@ class LateralContributionList(PamhyrModelListWithTab): for new_reach, part_lower, part_upper, sections in parts: clipped_lower = max(lower, part_lower) clipped_upper = min(upper, part_upper) - if clipped_lower > clipped_upper: + if clipped_lower >= clipped_upper: continue begin_index = ( diff --git a/src/Model/LateralContributionsAdisTS/LateralContributionsAdisTSList.py b/src/Model/LateralContributionsAdisTS/LateralContributionsAdisTSList.py index ad9d2783..05bc07f1 100644 --- a/src/Model/LateralContributionsAdisTS/LateralContributionsAdisTSList.py +++ b/src/Model/LateralContributionsAdisTS/LateralContributionsAdisTSList.py @@ -95,7 +95,7 @@ class LateralContributionsAdisTSList(PamhyrModelList): for new_reach, part_lower, part_upper in parts: clipped_lower = max(lower, part_lower) clipped_upper = min(upper, part_upper) - if clipped_lower > clipped_upper: + if clipped_lower >= clipped_upper: continue begin_rk = clipped_upper if reverse else clipped_lower diff --git a/src/Model/WeatherParameters/WeatherParametersList.py b/src/Model/WeatherParameters/WeatherParametersList.py index 399308ff..8669e34f 100644 --- a/src/Model/WeatherParameters/WeatherParametersList.py +++ b/src/Model/WeatherParameters/WeatherParametersList.py @@ -204,7 +204,7 @@ class WeatherParametersList(PamhyrModelList): for new_reach, part_lower, part_upper, sections in parts: clipped_lower = max(lower, part_lower) clipped_upper = min(upper, part_upper) - if clipped_lower > clipped_upper: + if clipped_lower >= clipped_upper: continue begin_index = clipped_upper if reverse else clipped_lower