diff --git a/src/Checker/Mage.py b/src/Checker/Mage.py
index 949e61ae..f7d4d747 100644
--- a/src/Checker/Mage.py
+++ b/src/Checker/Mage.py
@@ -229,7 +229,11 @@ for each reach"
gls = []
for edge in edges:
- comp, incomp = edge.reach.compute_guidelines()
+ profiles = edge.reach.enabled_profiles
+ comp, incomp = edge.reach.compute_guidelines(
+ profiles=profiles,
+ update_cache=False
+ )
if len(incomp) != 0:
self._status = STATUS.WARNING
self._summary = "incomplete_guideline"
@@ -237,7 +241,6 @@ for each reach"
gls.append(comp)
- profiles = edge.reach.profiles
for profile in profiles:
if not profile.has_standard_named_points():
self._status = STATUS.WARNING
diff --git a/src/Checker/Study.py b/src/Checker/Study.py
index bddfa4a7..7889cf2e 100644
--- a/src/Checker/Study.py
+++ b/src/Checker/Study.py
@@ -87,7 +87,7 @@ class StudyGeometryChecker(AbstractModelChecker):
return False
for edge in edges:
- if len(edge.reach.profiles) < 2:
+ if len(edge.reach.enabled_profiles) < 2:
summary = "no_geometry_defined"
status = STATUS.ERROR
ok = False
@@ -132,8 +132,12 @@ class StudyInitialConditionsChecker(AbstractModelChecker):
return ok
ic = river.initial_conditions[reach]
- len_ic = len(ic)
- len_reach = len(reach)
+ enabled_profiles = set(reach.enabled_profiles)
+ len_ic = sum(
+ data["section"] in enabled_profiles
+ for data in ic.data
+ )
+ len_reach = len(enabled_profiles)
if len_ic < len_reach:
self._summary = "initial_condition_missing_profile"
diff --git a/src/Model/Geometry/ProfileXYZ.py b/src/Model/Geometry/ProfileXYZ.py
index 905b2701..dc6b0c87 100644
--- a/src/Model/Geometry/ProfileXYZ.py
+++ b/src/Model/Geometry/ProfileXYZ.py
@@ -58,7 +58,8 @@ class ProfileXYZ(Profile, SQLSubModel):
num=0,
nb_point: int = 0,
code1: int = 0, code2: int = 0,
- status=None, owner_scenario=-1):
+ status=None, owner_scenario=-1,
+ enabled=True):
"""ProfileXYZ constructor
Args:
@@ -90,6 +91,7 @@ class ProfileXYZ(Profile, SQLSubModel):
self.time_l = 0.0
self._station = []
self.station_up_to_date = False
+ self._enabled = bool(enabled)
self._get_water_limits_cache = {}
self._get_water_limits_ac_cache = {}
@@ -108,6 +110,7 @@ class ProfileXYZ(Profile, SQLSubModel):
code1 INTEGER NOT NULL,
code2 INTEGER NOT NULL,
sl INTEGER,
+ enabled BOOLEAN NOT NULL DEFAULT TRUE,
{Scenario.create_db_add_scenario()},
{Scenario.create_db_add_scenario_fk()},
FOREIGN KEY(reach) REFERENCES river_reach(pamhyr_id),
@@ -152,6 +155,13 @@ class ProfileXYZ(Profile, SQLSubModel):
"ADD COLUMN deleted BOOLEAN NOT NULL DEFAULT FALSE"
)
+ if major == "0" and int(minor) <= 2:
+ if int(release) < 9:
+ execute(
+ "ALTER TABLE geometry_profileXYZ " +
+ "ADD COLUMN enabled BOOLEAN NOT NULL DEFAULT TRUE"
+ )
+
return cls._update_submodel(execute, version, data)
@classmethod
@@ -237,7 +247,7 @@ class ProfileXYZ(Profile, SQLSubModel):
table = execute(
"SELECT pamhyr_id, ind, deleted, name, rk, num, " +
- "code1, code2, sl, scenario " +
+ "code1, code2, sl, scenario, enabled " +
"FROM geometry_profileXYZ " +
f"WHERE reach = {reach.id} " +
f"AND scenario = {scenario.id} " +
@@ -258,6 +268,7 @@ class ProfileXYZ(Profile, SQLSubModel):
code2 = next(it)
sl = next(it)
owner_scenario = next(it)
+ enabled = (next(it) == 1)
profile = cls(
id=pid, ind=ind, num=num,
@@ -265,7 +276,8 @@ class ProfileXYZ(Profile, SQLSubModel):
code1=code1, code2=code2,
reach=reach,
status=status,
- owner_scenario=owner_scenario
+ owner_scenario=owner_scenario,
+ enabled=enabled
)
if deleted:
profile.set_as_deleted()
@@ -318,12 +330,13 @@ class ProfileXYZ(Profile, SQLSubModel):
execute(
"INSERT OR REPLACE INTO " +
"geometry_profileXYZ(pamhyr_id, deleted, ind, name, reach, " +
- "rk, num, code1, code2, sl, scenario) " +
+ "rk, num, code1, code2, sl, scenario, enabled) " +
"VALUES (" +
f"{self.pamhyr_id}, {self._db_format(self.is_deleted())}, " +
f"{ind}, '{self._db_format(self._name)}', " +
f"{self.reach.pamhyr_id}, {self.rk}, {self.num}, " +
- f"{self.code1}, {self.code1}, {sl}, {self._status.scenario_id}" +
+ f"{self.code1}, {self.code1}, {sl}, {self._status.scenario_id}, " +
+ f"{self._db_format(self._enabled)}" +
")"
)
@@ -390,7 +403,8 @@ class ProfileXYZ(Profile, SQLSubModel):
name=self.name,
rk=self.rk,
reach=self.reach,
- status=self._status
+ status=self._status,
+ enabled=self.is_enabled
)
if self.is_deleted():
new_p.set_as_deleted()
@@ -410,7 +424,8 @@ class ProfileXYZ(Profile, SQLSubModel):
name=self.name,
rk=self.rk,
reach=new_reach,
- status=self._status
+ status=self._status,
+ enabled=self.is_enabled
)
if self.is_deleted():
new_p.set_as_deleted()
@@ -1131,7 +1146,8 @@ class ProfileXYZ(Profile, SQLSubModel):
p = ProfileXYZ(name=self.name,
rk=self.rk,
reach=self.reach,
- status=self._status)
+ status=self._status,
+ enabled=self.is_enabled)
for i, k in enumerate(self.points):
p.insert_point(i, k.copy())
@@ -1165,3 +1181,16 @@ class ProfileXYZ(Profile, SQLSubModel):
break
self._points = points
self.modified()
+
+ @property
+ def is_enabled(self):
+ return self._enabled
+
+ @property
+ def enabled(self):
+ return self._enabled
+
+ @enabled.setter
+ def enabled(self, value):
+ self._enabled = bool(value)
+ self.modified()
diff --git a/src/Model/Geometry/Reach.py b/src/Model/Geometry/Reach.py
index d2fea850..cb96699e 100644
--- a/src/Model/Geometry/Reach.py
+++ b/src/Model/Geometry/Reach.py
@@ -157,6 +157,14 @@ class Reach(SQLSubModel):
def profiles(self, profiles):
self._profiles = profiles
+ @property
+ def enabled_profiles(self):
+ return list(filter(lambda p: p.is_enabled, self.profiles))
+
+ @property
+ def number_enabled_profiles(self):
+ return len(self.enabled_profiles)
+
def get_profiles_from_rk(self, rk):
return list(
filter(
@@ -367,6 +375,28 @@ class Reach(SQLSubModel):
)
)
+ def get_z_min_enabled_profiles(self):
+ return list(
+ map(
+ lambda profile: profile.z_min(),
+ filter(
+ lambda profile: len(profile) > 0 and profile.is_enabled,
+ self.profiles
+ )
+ )
+ )
+
+ def get_z_max_enabled_profiles(self):
+ return list(
+ map(
+ lambda profile: profile.z_max(),
+ filter(
+ lambda profile: len(profile) > 0 and profile.is_enabled,
+ self.profiles
+ )
+ )
+ )
+
def get_rk(self):
"""List of profiles rk
@@ -375,6 +405,17 @@ class Reach(SQLSubModel):
"""
return [profile.rk for profile in self.profiles]
+ def get_rk_enabled_profiles(self):
+ return list(
+ map(
+ lambda profile: profile.rk,
+ filter(
+ lambda profile: len(profile) > 0 and profile.is_enabled,
+ self.profiles
+ )
+ )
+ )
+
def get_rk_complete_profiles(self):
return list(
map(
@@ -486,14 +527,22 @@ class Reach(SQLSubModel):
)
@timer
- def compute_guidelines(self):
+ def compute_guidelines(self, profiles=None, update_cache=True):
"""Compute reach guidelines
+ Args:
+ profiles: Profiles used to compute the guidelines. If omitted,
+ all non-deleted profiles of the reach are used.
+ update_cache: Whether to update the reach guidelines cache.
+
Returns:
Tuple of complete and incomplete guidelines name.
"""
+ if profiles is None:
+ profiles = self.profiles
+
# Get all point contained into a guideline
- named_points = [profile.named_points() for profile in self.profiles]
+ named_points = [profile.named_points() for profile in profiles]
points_name = list(
map(
lambda lst: list(map(lambda p: p.name, lst)),
@@ -524,11 +573,13 @@ class Reach(SQLSubModel):
complete = guide_set - incomplete
- # Compute guideline and put data in cache
- self._compute_guidelines_cache(guide_set, named_points,
- complete, incomplete)
+ if update_cache:
+ # Compute guideline and put data in cache
+ self._compute_guidelines_cache(
+ guide_set, named_points, complete, incomplete
+ )
+ self.modified()
- self.modified()
return (complete, incomplete)
def _map_guidelines_points(self, func, full=False):
diff --git a/src/Model/InitialConditionsTemperature/InitialConditionsTemperature.py b/src/Model/InitialConditionsTemperature/InitialConditionsTemperature.py
index 986e603e..79f2d934 100644
--- a/src/Model/InitialConditionsTemperature/InitialConditionsTemperature.py
+++ b/src/Model/InitialConditionsTemperature/InitialConditionsTemperature.py
@@ -167,7 +167,11 @@ class InitialConditionsTemperature(SQLSubModel):
return True
def __len__(self):
- return len(self._data)
+ return len(self.data)
+
+ @property
+ def data(self):
+ return list(filter(lambda item: not item.is_deleted(), self._data))
@property
def name(self):
diff --git a/src/Model/Results/Results.py b/src/Model/Results/Results.py
index 99d7b8b5..ebc076b1 100644
--- a/src/Model/Results/Results.py
+++ b/src/Model/Results/Results.py
@@ -522,7 +522,7 @@ class Results(SQLSubModel):
i = 0
for reach in new_results._river.reachs:
nb = len(reach.profiles)
- reach.set_global_index(range(i, i + nb + 1))
+ reach.set_global_index(range(i, i + nb))
i += nb
if "Z" in table_data:
diff --git a/src/Model/Results/River/River.py b/src/Model/Results/River/River.py
index 6e32b7a3..cd6d591b 100644
--- a/src/Model/Results/River/River.py
+++ b/src/Model/Results/River/River.py
@@ -154,6 +154,7 @@ class Profile(SQLSubModel):
"len_data, data, scenario " +
"FROM results_data " +
f"WHERE scenario = {scenario.id} " +
+ f"AND result = {data['result_pid']} " +
f"AND reach = {reach.pamhyr_id} " +
f"AND section = {profile.pamhyr_id}"
)
@@ -276,7 +277,7 @@ class Profile(SQLSubModel):
class Reach(SQLSubModel):
_sub_classes = [Profile]
- def __init__(self, reach, study, parent, with_init=True):
+ def __init__(self, reach, study, parent, with_init=True, profiles=None):
super(Reach, self).__init__(
id=-1, status=study.status,
owner_scenario=study.status.scenario.id
@@ -287,10 +288,12 @@ class Reach(SQLSubModel):
self._reach = reach # Source reach in the study
self._profiles = []
if with_init:
+ if profiles is None:
+ profiles = reach.profiles
self._profiles = list(
map(
lambda p: Profile(p, self._study, self._parent),
- reach.profiles
+ profiles
)
)
@@ -379,7 +382,14 @@ class Reach(SQLSubModel):
for i, profile in enumerate(reach.profiles):
data["profile"] = profile
- new_reach._profiles += [Profile._db_load(execute, data)]
+ result_profile = Profile._db_load(execute, data)
+ if len(result_profile) > 0:
+ new_reach._profiles.append(result_profile)
+
+ new_reach._profile_mask = [
+ profile.name[0:8] != 'interpol'
+ for profile in new_reach._profiles
+ ]
return new_reach
@@ -418,10 +428,13 @@ class River(SQLSubModel):
def has_reach(self, id):
return 0 <= id < len(self._reachs)
- def add(self, reach_id):
+ def add(self, reach_id, profiles=None):
reachs = self._study.river.enable_edges()
- new = Reach(reachs[reach_id].reach, self._study, self._parent)
+ new = Reach(
+ reachs[reach_id].reach, self._study, self._parent,
+ profiles=profiles
+ )
self._reachs.append(new)
return new
@@ -460,9 +473,9 @@ class River(SQLSubModel):
for reach in study.river.reachs():
data["reach"] = reach.reach
- new_river._reachs.append(
- Reach._db_load(execute, data)
- )
+ result_reach = Reach._db_load(execute, data)
+ if len(result_reach) > 0:
+ new_river._reachs.append(result_reach)
return new_river
diff --git a/src/Model/River.py b/src/Model/River.py
index eecdb4b8..5673efc1 100644
--- a/src/Model/River.py
+++ b/src/Model/River.py
@@ -907,6 +907,63 @@ Last export at: @date."""
def weather_parameters(self):
return self._WeatherParameters
+ def profile_dependencies(self, profile):
+ """Return model data which directly reference *profile*.
+
+ PK-based intervals are deliberately not included: only explicit
+ references to the profile object prevent it from being disabled.
+ """
+ dependencies = []
+
+ def add(kind, item, association):
+ dependencies.append({
+ "kind": kind,
+ "item": item,
+ "association": association,
+ })
+
+ for reach in list(self._initial_conditions._dict):
+ initial_conditions = self._initial_conditions.get(reach)
+ for data in initial_conditions.data:
+ if data["section"] is profile:
+ add("initial_condition", data, "section")
+
+ for structure in self._hydraulic_structures.lst:
+ if structure.input_section is profile:
+ add("hydraulic_structure", structure, "input_section")
+ if structure.output_section is profile:
+ add("hydraulic_structure", structure, "output_section")
+
+ for tab in self._lateral_contribution._tabs_list:
+ for contribution in self._lateral_contribution.get_tab(tab):
+ if contribution.begin_section is profile:
+ add("lateral_contribution", contribution, "begin_section")
+ if contribution.end_section is profile:
+ add("lateral_contribution", contribution, "end_section")
+
+ for parameter in self._WeatherParameters.lst:
+ if parameter.begin_section is profile:
+ add("weather_parameter", parameter, "begin_section")
+ if parameter.end_section is profile:
+ add("weather_parameter", parameter, "end_section")
+
+ reach_id = profile.reach.pamhyr_id
+ for initial_condition in self._InitialConditionsTemperature.lst:
+ for specification in initial_condition.data:
+ same_reach = specification.reach == reach_id
+ same_rk = (
+ specification.start_rk == profile.rk or
+ specification.end_rk == profile.rk
+ )
+ if same_reach and same_rk:
+ add(
+ "initial_condition_temperature",
+ specification,
+ "cross_section_pk",
+ )
+
+ return dependencies
+
def get_params(self, solver):
if solver in self._parameters:
return self._parameters[solver]
diff --git a/src/Model/Study.py b/src/Model/Study.py
index ded212ba..de86b428 100644
--- a/src/Model/Study.py
+++ b/src/Model/Study.py
@@ -46,7 +46,7 @@ logger = logging.getLogger()
class Study(SQLModel):
- _version = "0.2.7"
+ _version = "0.2.9"
_sub_classes = [
Scenario,
diff --git a/src/Solver/AdisTS.py b/src/Solver/AdisTS.py
index 4c0c11e7..4a619113 100644
--- a/src/Solver/AdisTS.py
+++ b/src/Solver/AdisTS.py
@@ -180,7 +180,7 @@ class AdisTS(CommandLineSolver):
files.append(str(os.path.join("net", f"{name}.ST")))
cnt_num = 1
- for profile in edge.reach.profiles:
+ for profile in edge.reach.enabled_profiles:
self._export_ST_profile_header(
f, files, profile, cnt_num
)
@@ -335,7 +335,7 @@ class AdisTS(CommandLineSolver):
return coeff.minor, coeff.medium
- for j, profile in enumerate(edge.reach.profiles):
+ for j, profile in enumerate(edge.reach.enabled_profiles):
coef_min, coef_moy = get_stricklers_from_rk(profile.rk,
lst)
f.write(
@@ -861,7 +861,10 @@ class AdisTSwc(AdisTS):
for i in range(ibmax):
# Add results reach to reach list
- r = results.river.add(i)
+ geometry_reach = study.river.enable_edges()[i].reach
+ r = results.river.add(
+ i, profiles=geometry_reach.enabled_profiles
+ )
reachs.append(r)
is1[i] = data[2 * i] - 1 # first section of reach i
diff --git a/src/Solver/AdisTT.py b/src/Solver/AdisTT.py
index 418a845d..7605ed13 100644
--- a/src/Solver/AdisTT.py
+++ b/src/Solver/AdisTT.py
@@ -187,7 +187,7 @@ class AdisTT(CommandLineSolver):
files.append(str(os.path.join("net", f"{name}.ST")))
cnt_num = 1
- for profile in edge.reach.profiles:
+ for profile in edge.reach.enabled_profiles:
self._export_ST_profile_header(
f, files, profile, cnt_num
)
@@ -310,7 +310,7 @@ class AdisTT(CommandLineSolver):
return coeff.minor, coeff.medium
- for j, profile in enumerate(edge.reach.profiles):
+ for j, profile in enumerate(edge.reach.enabled_profiles):
coef_min, coef_moy = get_stricklers_from_rk(profile.rk,
lst)
f.write(
@@ -647,7 +647,10 @@ class AdisTTwc(AdisTT):
for i in range(ibmax):
# Add results reach to reach list
- r = results.river.add(i)
+ geometry_reach = study.river.enable_edges()[i].reach
+ r = results.river.add(
+ i, profiles=geometry_reach.enabled_profiles
+ )
reachs.append(r)
is1[i] = data[2 * i] - 1 # first section of reach i
diff --git a/src/Solver/Mage.py b/src/Solver/Mage.py
index a7f5740d..c15b2152 100644
--- a/src/Solver/Mage.py
+++ b/src/Solver/Mage.py
@@ -224,7 +224,7 @@ class Mage(CommandLineSolver):
files.append(str(os.path.join("net", f"{name}.ST")))
cnt_num = 1
- for profile in edge.reach.profiles:
+ for profile in edge.reach.enabled_profiles:
self._export_ST_profile_header(
f, files, profile, cnt_num
)
@@ -489,7 +489,12 @@ class Mage(CommandLineSolver):
if cond.is_deleted():
continue
- data = cond.data
+ enabled_profiles = set(reach.reach.enabled_profiles)
+ data = [
+ item for item in cond.data
+ if not item.is_deleted()
+ and item["section"] in enabled_profiles
+ ]
if len(data) == 0:
continue
@@ -497,9 +502,6 @@ class Mage(CommandLineSolver):
id_sec = 1
for d in data:
- if d.is_deleted():
- continue
-
IR = f"{id}"
IS = f"{id_sec}"
discharge = f"{d['discharge']:>10.5f}"
@@ -1097,7 +1099,10 @@ class Mage8(Mage):
for i in range(nb_reach):
# Add results reach to reach list
- r = results.river.add(i)
+ geometry_reach = study.river.enable_edges()[i].reach
+ r = results.river.add(
+ i, profiles=geometry_reach.enabled_profiles
+ )
reachs.append(r)
# ID of first and last reach profiles
@@ -1402,7 +1407,10 @@ class Mage8(Mage):
zfd_lst = []
for r in reachs:
- z_min = r.geometry.get_z_min()
+ z_min = [
+ profile.geometry.z_min()
+ for profile in r.profiles
+ ]
sls = map(
lambda p: p.get_ts_key(ts_list[0], "sl")[0],
r.profiles
diff --git a/src/View/Geometry/Profile/Window.py b/src/View/Geometry/Profile/Window.py
index bcafc75c..4ada0795 100644
--- a/src/View/Geometry/Profile/Window.py
+++ b/src/View/Geometry/Profile/Window.py
@@ -128,6 +128,15 @@ class ProfileWindow(PamhyrWindow):
self._tablemodel.blockSignals(False)
def setup_connections(self):
+ navigation_actions = {
+ "action_previous_profile": self.previous_profile,
+ "action_next_profile": self.next_profile,
+ }
+
+ for action in navigation_actions:
+ self.find(QAction, action)\
+ .triggered.connect(navigation_actions[action])
+
if self._study.is_read_only():
actions = {}
else:
@@ -152,6 +161,74 @@ class ProfileWindow(PamhyrWindow):
.connect(self.update_points_selection)
self._tablemodel.dataChanged.connect(self.update)
+ self.update_navigation_actions()
+
+ def previous_profile(self):
+ self.navigate_profile(-1)
+
+ def next_profile(self):
+ self.navigate_profile(1)
+
+ def profiles(self):
+ """Return profiles from the geometry model owning this profile."""
+ return self._profile.reach.reach.profiles
+
+ def navigate_profile(self, offset):
+ profiles = self.profiles()
+
+ try:
+ index = profiles.index(self._profile)
+ except ValueError:
+ self.update_navigation_actions()
+ return
+
+ target_index = index + offset
+ if not 0 <= target_index < len(profiles):
+ self.update_navigation_actions()
+ return
+
+ target = profiles[target_index]
+ if self._parent is not None and self._parent.sub_window_exists(
+ ProfileWindow,
+ data=[self._study, self._config, target]
+ ):
+ self.close()
+ return
+
+ self.set_profile(target)
+
+ def set_profile(self, profile):
+ self._profile = profile
+ self._hash_data[-1] = profile
+ self._undo_stack.clear()
+
+ self._tablemodel.beginResetModel()
+ self._tablemodel._data = profile
+ self._tablemodel._setup_lst()
+ self._tablemodel.endResetModel()
+
+ self._plot.data = profile
+ self._plot.highlight = None
+ self._plot.draw()
+
+ self._title = (
+ self._trad[self._pamhyr_name] +
+ f" - {profile.name} {profile.rk}"
+ )
+ self._set_title()
+ self.update_navigation_actions()
+
+ def update_navigation_actions(self):
+ profiles = self.profiles()
+ try:
+ index = profiles.index(self._profile)
+ except ValueError:
+ index = -1
+
+ self.find(QAction, "action_previous_profile")\
+ .setEnabled(index > 0)
+ self.find(QAction, "action_next_profile")\
+ .setEnabled(0 <= index < len(profiles) - 1)
def update_points_selection(self):
rows = self.index_selected_rows()
diff --git a/src/View/Geometry/Table.py b/src/View/Geometry/Table.py
index 847722d9..20b8ac01 100644
--- a/src/View/Geometry/Table.py
+++ b/src/View/Geometry/Table.py
@@ -64,15 +64,20 @@ class GeometryReachTableModel(PamhyrTableModel):
if not index.isValid():
return QVariant()
+ profile = self._data.profile(index.row())
+
+ if role == Qt.BackgroundRole and not profile.is_enabled:
+ return QColor(190, 190, 190)
+
if role == Qt.DisplayRole and index.column() == 0:
- return self._data.profile(index.row()).name
+ return profile.name
if role == Qt.DisplayRole and index.column() == 1:
- rk = self._data.profile(index.row()).rk
+ rk = profile.rk
return f"{rk:.4f}"
if role == Qt.DisplayRole and index.column() == 2:
- return str(self._data.profile(index.row()).nb_points)
+ return str(profile.nb_points)
if role == Qt.TextAlignmentRole:
return Qt.AlignHCenter | Qt.AlignVCenter
@@ -155,6 +160,14 @@ class GeometryReachTableModel(PamhyrTableModel):
self.endRemoveRows()
self.layoutChanged.emit()
+ def enabled(self, rows, enabled):
+ self._undo.push(
+ SetEnabledCommand(
+ self._data, rows, enabled
+ )
+ )
+ self.layoutChanged.emit()
+
def sort_profiles(self, _reverse):
self.layoutAboutToBeChanged.emit()
diff --git a/src/View/Geometry/Translate.py b/src/View/Geometry/Translate.py
index 10f93bd9..a0f9682a 100644
--- a/src/View/Geometry/Translate.py
+++ b/src/View/Geometry/Translate.py
@@ -19,6 +19,7 @@
from PyQt5.QtCore import QCoreApplication
from View.Translate import MainTranslate
+from View.WeatherParameters.translate import WeatherParametersTranslate
_translate = QCoreApplication.translate
@@ -44,6 +45,66 @@ class GeometryTranslate(MainTranslate):
self._dict["cross_sections"] = _translate("Geometry", "cross-sections")
self._dict["profile"] = _translate("Geometry", "cross-section")
self._dict["profiles"] = _translate("Geometry", "cross-sections")
+ self._dict["cross_section_enabled_count"] = _translate(
+ "Geometry", "Cross-section enabled ({count} disabled)"
+ )
+ self._dict["profile_disable_blocked"] = _translate(
+ "Geometry", "The cross-section cannot be disabled."
+ )
+ self._dict["profile_disable_blocked_info"] = _translate(
+ "Geometry",
+ "It is referenced by the following data:\n{dependencies}\n\n"
+ "Change or remove these associations before disabling it."
+ )
+ self._dict["profile_dependency"] = _translate(
+ "Geometry", "{kind} “{name}” ({association})"
+ )
+ self._dict["initial_condition"] = _translate(
+ "Geometry", "Initial condition"
+ )
+ self._dict["initial_condition_temperature"] = _translate(
+ "Geometry", "Initial condition temperature"
+ )
+ self._dict["hydraulic_structure"] = _translate(
+ "Geometry", "Hydraulic structure"
+ )
+ self._dict["lateral_contribution"] = _translate(
+ "Geometry", "Lateral contribution"
+ )
+ self._dict["weather_parameter"] = _translate(
+ "Geometry", "Weather parameter"
+ )
+ self._dict["section"] = _translate("Geometry", "cross-section")
+ self._dict["input_section"] = _translate(
+ "Geometry", "input cross-section"
+ )
+ self._dict["output_section"] = _translate(
+ "Geometry", "output cross-section"
+ )
+ self._dict["begin_section"] = _translate(
+ "Geometry", "first cross-section"
+ )
+ self._dict["end_section"] = _translate(
+ "Geometry", "last cross-section"
+ )
+ self._dict["cross_section_pk"] = _translate(
+ "Geometry", "cross-section PK"
+ )
+
+ weather_parameters = WeatherParametersTranslate().get_dict(
+ "weather_parameters"
+ )
+ self._sub_dict["weather_parameter_types"] = {
+ "AT": weather_parameters["air_temperature"],
+ "SH": weather_parameters["specific_humidity"],
+ "GR": weather_parameters["global_radiation"],
+ "RWS": weather_parameters["reference_wind_speed"],
+ "GT": weather_parameters["surface_temperature"],
+ "GFR": weather_parameters["surface_flow_rate"],
+ "ALB": weather_parameters["albedo"],
+ "SC": weather_parameters["shading_coefficient"],
+ "CCF": weather_parameters["cloud_cover_fraction"],
+ }
self._dict["transverse_abscissa"] = _translate(
"Geometry", "Transverse abscissa (m)"
@@ -97,6 +158,15 @@ class GeometryTranslate(MainTranslate):
self._dict["format_not_exportable"] = _translate(
"Geometry", "The format of the file is not exportable."
)
+ self._dict["export_options"] = _translate(
+ "Geometry", "Export options"
+ )
+ self._dict["export_options_text"] = _translate(
+ "Geometry", "Choose which cross-sections to export."
+ )
+ self._dict["export_interpolated_profiles"] = _translate(
+ "Geometry", "Export interpolated cross-sections"
+ )
self._dict["update_rk_error"] = _translate(
"Geometry",
"RK update can't be executed."
diff --git a/src/View/Geometry/UndoCommand.py b/src/View/Geometry/UndoCommand.py
index 71e9c313..f14bfef8 100644
--- a/src/View/Geometry/UndoCommand.py
+++ b/src/View/Geometry/UndoCommand.py
@@ -68,6 +68,27 @@ class SetRKCommand(SetDataCommand):
self._reach.profile(self._index).rk = self._new
+class SetEnabledCommand(QUndoCommand):
+ def __init__(self, reach, rows, enabled):
+ QUndoCommand.__init__(self)
+
+ self._profiles = [reach.profile(row) for row in rows]
+ self._old = [profile.is_enabled for profile in self._profiles]
+ self._new = bool(enabled)
+
+ @staticmethod
+ def _set_enabled(profile, enabled):
+ profile.enabled = enabled
+
+ def undo(self):
+ for profile, enabled in zip(self._profiles, self._old):
+ self._set_enabled(profile, enabled)
+
+ def redo(self):
+ for profile in self._profiles:
+ self._set_enabled(profile, self._new)
+
+
class AddCommand(QUndoCommand):
def __init__(self, reach, index):
QUndoCommand.__init__(self)
diff --git a/src/View/Geometry/Window.py b/src/View/Geometry/Window.py
index 0827a094..13ab9140 100644
--- a/src/View/Geometry/Window.py
+++ b/src/View/Geometry/Window.py
@@ -99,6 +99,7 @@ class GeometryWindow(PamhyrWindow):
self._profile_window = []
self.setup_table()
+ self.setup_checkbox()
self.setup_plots()
self.setup_statusbar()
self.setup_connections()
@@ -124,6 +125,10 @@ class GeometryWindow(PamhyrWindow):
table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
table.setAlternatingRowColors(True)
+ def setup_checkbox(self):
+ self._checkbox = self.find(QCheckBox, "checkBox_enabled")
+ self._set_checkbox_state()
+
def setup_plots(self):
self.setup_plots_xy()
self.setup_plots_rkc()
@@ -223,6 +228,9 @@ class GeometryWindow(PamhyrWindow):
.selectionChanged\
.connect(self.select_current_profile)
+ if not self._study.is_read_only():
+ self._checkbox.clicked.connect(self._set_profiles_state)
+
self._table.layoutChanged.connect(self.update_redraw)
def update(self):
@@ -287,13 +295,14 @@ class GeometryWindow(PamhyrWindow):
if self.sub_window_exists(
ProfileWindow,
- data=[None, None, profile]
+ data=[self._study, self._config, profile]
):
continue
win = ProfileWindow(
profile=profile,
study=self._study,
+ config=self._config,
parent=self,
)
self._profile_window.append(win)
@@ -314,6 +323,10 @@ class GeometryWindow(PamhyrWindow):
trad=self._trad,
parent=self
)
+ dlg.adjustSize()
+ dialog_geometry = dlg.frameGeometry()
+ dialog_geometry.moveCenter(self.frameGeometry().center())
+ dlg.move(dialog_geometry.topLeft())
if dlg.exec():
data = {
"step": dlg.space_step,
@@ -520,6 +533,92 @@ class GeometryWindow(PamhyrWindow):
self._plot_ac.draw()
self.tableView.model().blockSignals(False)
+ self._set_checkbox_state()
+
+ def index_selected_rows(self):
+ return [
+ index.row()
+ for index in self.tableView.selectionModel().selectedRows()
+ ]
+
+ def _set_checkbox_state(self):
+ rows = self.index_selected_rows()
+ editable = not self._study.is_read_only()
+ disabled_count = (self._reach.number_profiles -
+ self._reach.number_enabled_profiles)
+ self._checkbox.setText(
+ self._trad["cross_section_enabled_count"].format(
+ count=disabled_count
+ )
+ )
+
+ self._checkbox.setEnabled(editable and len(rows) > 0)
+ if len(rows) == 0:
+ self._checkbox.setTristate(False)
+ self._checkbox.setChecked(False)
+ return
+
+ states = {
+ self._reach.profile(row).is_enabled
+ for row in rows
+ }
+ if len(states) == 1:
+ self._checkbox.setTristate(False)
+ self._checkbox.setChecked(states.pop())
+ else:
+ self._checkbox.setTristate(True)
+ self._checkbox.setCheckState(Qt.PartiallyChecked)
+
+ def _set_profiles_state(self, enabled):
+ rows = self.index_selected_rows()
+ if len(rows) == 0:
+ return
+
+ if not enabled:
+ blocked = []
+ for row in rows:
+ profile = self._reach.profile(row)
+ dependencies = self._study.river.profile_dependencies(profile)
+ if len(dependencies) != 0:
+ blocked.append((profile, dependencies))
+
+ if len(blocked) != 0:
+ details = []
+ for profile, dependencies in blocked:
+ details.append(f"{profile.name}:")
+ for dependency in dependencies:
+ item = dependency["item"]
+ if dependency["kind"] == "weather_parameter":
+ name = self._trad.get_dict(
+ "weather_parameter_types"
+ ).get(item.type, item.type)
+ else:
+ name = getattr(item, "name", "")
+ if name == "":
+ name = self._trad[dependency["kind"]]
+ details.append(
+ " - " +
+ self._trad["profile_dependency"].format(
+ kind=self._trad[dependency["kind"]],
+ name=name,
+ association=self._trad[
+ dependency["association"]
+ ],
+ )
+ )
+
+ self._set_checkbox_state()
+ self.message_box(
+ self._trad["profile_disable_blocked"],
+ self._trad["profile_disable_blocked_info"].format(
+ dependencies="\n".join(details)
+ ),
+ window_title=self._trad["warning"],
+ )
+ return
+
+ self._checkbox.setTristate(False)
+ self._table.enabled(rows, enabled)
def add(self):
if len(self.tableView.selectedIndexes()) == 0:
@@ -735,7 +834,11 @@ class GeometryWindow(PamhyrWindow):
suffix = Path(filename).suffix
if suffix != "":
if suffix == ".st" or suffix == ".ST":
- self._export_to_file_st(filename[:-3])
+ export_interpolated = self._ask_export_options()
+ if export_interpolated is not None:
+ self._export_to_file_st(
+ filename[:-3], export_interpolated
+ )
else:
# Warning popup when the format is not exportable
win = QtWidgets.QMessageBox()
@@ -746,16 +849,45 @@ class GeometryWindow(PamhyrWindow):
pass
else:
- self._export_to_file_st(filename)
+ export_interpolated = self._ask_export_options()
+ if export_interpolated is not None:
+ self._export_to_file_st(filename, export_interpolated)
- def _export_to_file_st(self, filename):
+ def _ask_export_options(self):
+ win = QtWidgets.QMessageBox(self)
+ win.setIcon(QtWidgets.QMessageBox.Question)
+ win.setWindowTitle(self._trad["export_options"])
+ win.setText(self._trad["export_options_text"])
+ win.setStandardButtons(
+ QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel
+ )
+
+ export_interpolated = QCheckBox(
+ self._trad["export_interpolated_profiles"], win
+ )
+ export_interpolated.setChecked(True)
+ win.setCheckBox(export_interpolated)
+
+ if win.exec() != QtWidgets.QMessageBox.Ok:
+ return None
+
+ return export_interpolated.isChecked()
+
+ def _export_to_file_st(self, filename, export_interpolated=True):
with open(filename+".st", "w+") as f:
f.write("# Exported from Pamhyr2\n")
- self._export_to_file_st_reach(f, self._reach)
+ self._export_to_file_st_reach(
+ f, self._reach, export_interpolated
+ )
- def _export_to_file_st_reach(self, wfile, reach):
+ def _export_to_file_st_reach(self, wfile, reach,
+ export_interpolated=True):
pid = 0
for profile in reach.profiles:
+ is_interpolated = profile.name.lower().startswith("interpol")
+ if is_interpolated and not export_interpolated:
+ continue
+
self._export_to_file_st_profile(wfile, profile, pid)
pid += 1
diff --git a/src/View/MainWindow.py b/src/View/MainWindow.py
index 1e5d3a88..8e31bb35 100644
--- a/src/View/MainWindow.py
+++ b/src/View/MainWindow.py
@@ -158,6 +158,7 @@ other_model_action = [
define_model_action = [
# Toolbar
"action_toolBar_network", "action_toolBar_geometry",
+ "action_toolBar_meshing",
"action_toolBar_boundary_cond", "action_toolBar_lateral_contrib",
"action_toolBar_frictions", "action_toolBar_initial_cond",
# Menu
@@ -175,6 +176,7 @@ define_model_action = [
"action_menu_rep_additional_lines",
"action_menu_run_adists", "action_menu_pollutants",
"action_menu_d90", "action_menu_dif", "action_menu_edit_geotiff",
+ "action_menu_meshing",
"action_menu_boundary_conditions_temperature",
"action_menu_initial_conditions_temperature",
"action_menu_weather_parameters", "action_menu_run_adistt",
@@ -310,6 +312,7 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
"action_menu_edit_scenarios": self.open_scenarios,
"action_menu_edit_network": self.open_network,
"action_menu_edit_geometry": self.open_geometry,
+ "action_menu_meshing": self.open_meshing,
"action_menu_boundary_conditions": self.open_boundary_cond,
"action_menu_boundary_conditions_sediment":
self.open_boundary_cond_sed,
@@ -351,6 +354,7 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
# Current actions
"action_toolBar_network": self.open_network,
"action_toolBar_geometry": self.open_geometry,
+ "action_toolBar_meshing": self.open_meshing,
"action_toolBar_boundary_cond": self.open_boundary_cond,
"action_toolBar_lateral_contrib": self.open_lateral_contrib,
"action_toolBar_frictions": self.open_frictions,
@@ -1339,7 +1343,10 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
GeometryWindow,
data=[self._study, self.conf, reach]
):
- return
+ return self.get_sub_window(
+ GeometryWindow,
+ data=[self._study, self.conf, reach]
+ )
geometry = GeometryWindow(
study=self._study,
@@ -1348,8 +1355,21 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
parent=self
)
geometry.show()
+ return geometry
else:
self.msg_select_reach()
+ return None
+
+ def open_meshing(self):
+ """Open the current reach geometry and its meshing dialog."""
+ geometry = self.open_geometry()
+ if geometry is None or self._study.is_read_only():
+ return
+
+ geometry.raise_()
+ geometry.activateWindow()
+ QApplication.processEvents()
+ geometry.edit_meshing()
def open_boundary_cond_sed(self):
self.open_boundary_cond(tab=1)
diff --git a/src/View/Network/ProfileDialog.py b/src/View/Network/ProfileDialog.py
index 66abccaf..446327e7 100644
--- a/src/View/Network/ProfileDialog.py
+++ b/src/View/Network/ProfileDialog.py
@@ -64,6 +64,7 @@ class SelectProfileDialog(PamhyrDialog):
self._reach = reach
self._profile = None
+ self._profiles = self._reach.reach.enabled_profiles
self.setup_combobox()
@@ -73,20 +74,19 @@ class SelectProfileDialog(PamhyrDialog):
list(
map(
lambda p: p.display_name(),
- self._reach.reach.profiles[1:-1]
- )
- )
- )
+ self._profiles)))
+
+ if len(self._profiles) == 0:
+ self.find(QDialogButtonBox, "buttonBox")\
+ .button(QDialogButtonBox.Ok)\
+ .setEnabled(False)
def accept(self):
- profile = self.get_combobox_text("comboBox")
+ index = self.find(QComboBox, "comboBox").currentIndex()
+ if not 0 <= index < len(self._profiles):
+ return
- self._profile = next(
- filter(
- lambda p: p.display_name() == profile,
- self._reach.reach.profiles[1:-1]
- )
- )
+ self._profile = self._profiles[index]
super(SelectProfileDialog, self).accept()
diff --git a/src/View/Results/PlotRKC.py b/src/View/Results/PlotRKC.py
index dc658c65..8c8f3c40 100644
--- a/src/View/Results/PlotRKC.py
+++ b/src/View/Results/PlotRKC.py
@@ -62,6 +62,8 @@ class PlotRKC(PamhyrPlot):
self._auto_relim_update = True
self._autoscale_update = False
+ self.profile = None
+
@property
def results(self):
return self.data
@@ -71,9 +73,19 @@ class PlotRKC(PamhyrPlot):
self.data = results
self._current_timestamp = max(self._timestamps)
+ @staticmethod
+ def reach_geometry(reach):
+ """Return geometry values for profiles present in the results."""
+ return (
+ reach.geometry.get_rk_enabled_profiles(),
+ reach.geometry.get_z_min_enabled_profiles(),
+ reach.geometry.get_z_max_enabled_profiles()
+ )
+
@timer
def draw(self, highlight=None):
self.init_axes()
+ self.profile = None
if self.results is None:
return
@@ -104,7 +116,7 @@ class PlotRKC(PamhyrPlot):
def draw_bottom_with_bedload(self, reach):
results = self.results[self._current_res_id]
- rk = reach.geometry.get_rk()
+ rk, _, _ = self.reach_geometry(reach)
table = results.get("table")["zfd"]
ts = results.get_timestamp_id(self._current_timestamp)
@@ -136,8 +148,7 @@ class PlotRKC(PamhyrPlot):
for hs in lhs:
x = hs.input_section.rk
- z_min = reach.geometry.get_z_min()
- z_max = reach.geometry.get_z_max()
+ _, z_min, z_max = self.reach_geometry(reach)
self.canvas.axes.plot(
[x, x],
@@ -157,9 +168,7 @@ class PlotRKC(PamhyrPlot):
)
def draw_bottom_geometry(self, reach):
- rk = reach.geometry.get_rk()
- z_min = reach.geometry.get_z_min()
- z_max = reach.geometry.get_z_max()
+ rk, z_min, _ = self.reach_geometry(reach)
self.line_rk_zmin = self.canvas.axes.plot(
rk, z_min,
@@ -170,10 +179,9 @@ class PlotRKC(PamhyrPlot):
self._river_bottom = z_min
def draw_water_elevation(self, reach):
- if len(reach.geometry.profiles) != 0:
+ if len(reach.profiles) != 0:
result = self.results[self._current_res_id]
- rk = reach.geometry.get_rk()
- z_min = reach.geometry.get_z_min()
+ rk, _, _ = self.reach_geometry(reach)
table = result.get("table")["Z"]
ts = result.get_timestamp_id(self._current_timestamp)
@@ -201,10 +209,9 @@ class PlotRKC(PamhyrPlot):
)
def draw_water_elevation_max(self, reach):
- if len(reach.geometry.profiles) != 0:
+ if len(reach.profiles) != 0:
result = self.results[self._current_res_id]
- rk = reach.geometry.get_rk()
- z_min = reach.geometry.get_z_min()
+ rk, _, _ = self.reach_geometry(reach)
table = result.get("table")["Z"]
ts = result.get_timestamp_id(self._current_timestamp)
@@ -250,9 +257,7 @@ class PlotRKC(PamhyrPlot):
self.canvas.draw_idle()
def draw_current(self, reach):
- rk = reach.geometry.get_rk()
- z_min = reach.geometry.get_z_min()
- z_max = reach.geometry.get_z_max()
+ rk, z_min, z_max = self.reach_geometry(reach)
self.profile, = self.canvas.axes.plot(
[
@@ -349,8 +354,7 @@ class PlotRKC(PamhyrPlot):
def update_water_elevation(self):
result = self.results[self._current_res_id]
reach = result.river.reach(self._current_reach_id)
- rk = reach.geometry.get_rk()
- z_min = reach.geometry.get_z_min()
+ rk, _, _ = self.reach_geometry(reach)
table = result.get("table")["Z"]
ts = result.get_timestamp_id(self._current_timestamp)
@@ -380,11 +384,13 @@ class PlotRKC(PamhyrPlot):
self.update_idle()
def update_current(self):
+ if not self._init or self.profile is None:
+ self.draw()
+ return
+
results = self.results[self._current_res_id]
reach = results.river.reach(self._current_reach_id)
- rk = reach.geometry.get_rk()
- z_min = reach.geometry.get_z_min()
- z_max = reach.geometry.get_z_max()
+ rk, z_min, z_max = self.reach_geometry(reach)
cid = self._current_profile_id
self.profile.set_data(
@@ -396,7 +402,7 @@ class PlotRKC(PamhyrPlot):
def update_bottom_with_bedload(self):
results = self.results[self._current_res_id]
reach = results.river.reach(self._current_reach_id)
- rk = reach.geometry.get_rk()
+ rk, _, _ = self.reach_geometry(reach)
# z = self.sl_compute_current_z(reach)
table = results.get("table")["zfd"]
diff --git a/src/View/ui/GeometryCrossSection.ui b/src/View/ui/GeometryCrossSection.ui
index dfc47bb6..8ecbcd8c 100644
--- a/src/View/ui/GeometryCrossSection.ui
+++ b/src/View/ui/GeometryCrossSection.ui
@@ -52,6 +52,9 @@
false
+
+
+
@@ -61,6 +64,30 @@
+
+
+
+ ressources/left.pngressources/left.png
+
+
+ Previous profile
+
+
+ Open the previous profile
+
+
+
+
+
+ ressources/right.pngressources/right.png
+
+
+ Next profile
+
+
+ Open the next profile
+
+
diff --git a/src/View/ui/GeometryReach.ui b/src/View/ui/GeometryReach.ui
index 8c80a7bf..c87ed1db 100644
--- a/src/View/ui/GeometryReach.ui
+++ b/src/View/ui/GeometryReach.ui
@@ -24,10 +24,20 @@
Qt::Horizontal
-
+
-
+ -
+
+
+ Cross-section enabled (0 disabled)
+
+
+ true
+
+
+
diff --git a/src/View/ui/MainWindow.ui b/src/View/ui/MainWindow.ui
index 4d13ceb6..0b9f87de 100644
--- a/src/View/ui/MainWindow.ui
+++ b/src/View/ui/MainWindow.ui
@@ -19,6 +19,7 @@
Serif
+ 75
true
false
@@ -64,6 +65,7 @@
Ubuntu
+ 50
false
false
@@ -87,6 +89,7 @@
Sans Serif
+ 50
false
false
@@ -126,6 +129,7 @@
&Geometry
+
+
+
+
+ ressources/meshing.pngressources/meshing.png
+
+
+ Meshing
+
+
+
+
+ false
+
+
+ true
+
+
+
+ ressources/meshing.pngressources/meshing.png
+
+
+ Meshing
+
+
+ Mesh geometry
+
+