mirror of
https://gitlab.com/pamhyr/pamhyr2.git
synced 2026-10-30 03:20:41 +01:00
Compare commits
131
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a0f964e89e | ||
|
|
30b3b8800e | ||
|
|
366baa737a | ||
|
|
453a2084e7 | ||
|
|
fe30a20882 | ||
|
|
70dcb1a6ee | ||
|
|
c4554d38c1 | ||
|
|
4deed897ff | ||
|
|
460450f524 | ||
|
|
60336c25f1 | ||
|
|
82093bcf95 | ||
|
|
7b8191e2f0 | ||
|
|
88dd9cf15a | ||
|
|
7d4091eb24 | ||
|
|
59fc80a10b | ||
|
|
ea146cbe87 | ||
|
|
c6b28ba3a0 | ||
|
|
fe57890da4 | ||
|
|
8cd9ec939b | ||
|
|
872ff0ef62 | ||
|
|
052faed48d | ||
|
|
31b37a7792 | ||
|
|
dfe12c8bcb | ||
|
|
89d5b2d576 | ||
|
|
601ef73b5d | ||
|
|
e4f8bc347d | ||
|
|
6c4b67d93a | ||
|
|
2ee8420b94 | ||
|
|
bbe127865b | ||
|
|
50686e1b65 | ||
|
|
7b3c3d891d | ||
|
|
98feca7fca | ||
|
|
e45eb3fd69 | ||
|
|
dd86b8b461 | ||
|
|
57d43cd5d3 | ||
|
|
14b5b5e3df | ||
|
|
a7e43ca6a7 | ||
|
|
8ca21c413f | ||
|
|
61fd54c496 | ||
|
|
b017127b3a | ||
|
|
52f2d15280 | ||
|
|
5c86e46c72 | ||
|
|
a5648851f2 | ||
|
|
cb41520a20 | ||
|
|
537612e5e3 | ||
|
|
4e0c671f68 | ||
|
|
507cfb9d53 | ||
|
|
2013009e8a | ||
|
|
f1a1d08458 | ||
|
|
7de6cc4468 | ||
|
|
783d83e4cc | ||
|
|
c366a1a4e7 | ||
|
|
b784cab94b | ||
|
|
78e361d486 | ||
|
|
3b1f135fdb | ||
|
|
2f93d1a83f | ||
|
|
653a26fee1 | ||
|
|
e33c9cb2f0 | ||
|
|
4be43528e3 | ||
|
|
9d343a42ce | ||
|
|
459722610a | ||
|
|
f745dce04e | ||
|
|
c1f4aaf957 | ||
|
|
f4bf8d3766 | ||
|
|
6e94d7a707 | ||
|
|
87e8e17e91 | ||
|
|
9e79178d0f | ||
|
|
9d0168a9ee | ||
|
|
9760d41561 | ||
|
|
0c039793df | ||
|
|
4cec45131e | ||
|
|
e6c08cb4e4 | ||
|
|
2d17d8cfe4 | ||
|
|
ef583fc9bb | ||
|
|
db87b1e214 | ||
|
|
5790bdfe82 | ||
|
|
731567369e | ||
|
|
317cbf904d | ||
|
|
f4561494f6 | ||
|
|
5164891928 | ||
|
|
5e79e29823 | ||
|
|
cc3649fcb8 | ||
|
|
ab11d15729 | ||
|
|
0e75d67671 | ||
|
|
ba81f923d9 | ||
|
|
a0cdb182fd | ||
|
|
ca7296ca8c | ||
|
|
fb2d0d3e71 | ||
|
|
2afd641f0f | ||
|
|
d871bbfc3b | ||
|
|
5e9ab81363 | ||
|
|
e066c6ab94 | ||
|
|
8e82772cfc | ||
|
|
6dc96b86b4 | ||
|
|
8c2874e04d | ||
|
|
c7001bf0fa | ||
|
|
f62b209ffb | ||
|
|
0fe71b2b92 | ||
|
|
e528ba5278 | ||
|
|
0c0d7f3481 | ||
|
|
9c5ea0bdd9 | ||
|
|
cd540b0146 | ||
|
|
6d9b193d25 | ||
|
|
982d346264 | ||
|
|
4c927eb7c7 | ||
|
|
fead93f0b9 | ||
|
|
ca3ba3adb6 | ||
|
|
9f87a38107 | ||
|
|
329572d191 | ||
|
|
8a0f214144 | ||
|
|
5ff3b9457a | ||
|
|
8ec25cac46 | ||
|
|
39180f8ea3 | ||
|
|
2203b2ca8b | ||
|
|
c3a5ea1b91 | ||
|
|
6349c4f752 | ||
|
|
8c5fa68f11 | ||
|
|
cd2e29a77d | ||
|
|
ce090c6437 | ||
|
|
f80be0f3da | ||
|
|
fd9a338365 | ||
|
|
776164310b | ||
|
|
52ab9f4ade | ||
|
|
88c75c0d35 | ||
|
|
cd571b6ec4 | ||
|
|
701f9ab69b | ||
|
|
6d47664767 | ||
|
|
8cb8c50998 | ||
|
|
f14a149748 | ||
|
|
beb2c2fec0 | ||
|
|
48e010f195 |
No files matched your search
+7
-2
@@ -29,7 +29,7 @@ stages:
|
||||
#############
|
||||
|
||||
variables:
|
||||
MAGE_8_VERSION: "v8.3.5"
|
||||
MAGE_8_VERSION: "v8.3.6"
|
||||
|
||||
dl-mage8-doc:
|
||||
stage: downloads
|
||||
@@ -262,6 +262,11 @@ build-linux:
|
||||
- cp -v ../mage8-linux/* pamhyr/mage8/
|
||||
# Copy Pamhyr
|
||||
- cp -r dist/pamhyr/* pamhyr/
|
||||
# Pamhyr script to force x11
|
||||
- echo '#!/usr/bin/env bash' > pamhyr/pamhyr-x11
|
||||
- echo "" >> pamhyr/pamhyr-x11
|
||||
- echo "GDK_BACKEND=x11 ./pamhyr $@" >> pamhyr/pamhyr-x11
|
||||
- chmod +x pamhyr/pamhyr-x11
|
||||
# Copy Pamhyr resources
|
||||
- mkdir -p pamhyr/_internal/View
|
||||
- mkdir -p pamhyr/_internal/View/ui
|
||||
@@ -333,7 +338,7 @@ build-windows:
|
||||
- python -m pip install -U -r ..\requirements.txt
|
||||
# Run Pyinstaller
|
||||
# - pyinstaller --noconsole -y ..\src\pamhyr.py
|
||||
- pyinstaller --hide-console hide-early -y ..\src\pamhyr.py
|
||||
- pyinstaller -i ../src/View/ui/ressources/icon.ico --hide-console hide-early -y ..\src\pamhyr.py
|
||||
# Create directory
|
||||
- mkdir pamhyr
|
||||
- dir
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -646,7 +646,7 @@ class AddNodeCommand(QUndoCommand):
|
||||
self._node = node
|
||||
|
||||
def undo(self):
|
||||
self._graph.remove_node(self._node.name)
|
||||
self._graph.remove_node(self._node)
|
||||
|
||||
def redo(self):
|
||||
self._graph.insert_node(self._node)
|
||||
|
||||
@@ -11,3 +11,4 @@ pyinstaller>=5.11.0
|
||||
shapely>=2.0.1
|
||||
lxml>=4.9.3
|
||||
platformdirs>=4.2.0
|
||||
pyshp>=2.3.1
|
||||
@@ -3,6 +3,7 @@
|
||||
!define LIC_NAME "LICENSE"
|
||||
!define APP_NAME "PAMHYR"
|
||||
|
||||
Icon "pamhyr\_internal\View\ui\ressources\icon.ico"
|
||||
Name "Pamhyr2"
|
||||
OutFile "pamhyr-windows.exe"
|
||||
LicenseData "pamhyr\_internal\LICENSE"
|
||||
|
||||
@@ -11,3 +11,4 @@ pyinstaller>=5.11.0
|
||||
shapely>=2.0.1
|
||||
lxml>=4.9.3
|
||||
platformdirs>=4.2.0
|
||||
pyshp>=2.3.1
|
||||
@@ -92,6 +92,21 @@ class AbstractModelChecker(object):
|
||||
def is_error(self):
|
||||
return self._status == STATUS.ERROR
|
||||
|
||||
# Basic check
|
||||
def basic_check(self, study):
|
||||
if study is None:
|
||||
self._status = STATUS.ERROR
|
||||
self._summary = "invalid_study"
|
||||
return False
|
||||
|
||||
river = study.river
|
||||
if river is None:
|
||||
self._status = STATUS.ERROR
|
||||
self._summary = "no_river_found"
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
# Abstract function
|
||||
|
||||
def _run(self, study):
|
||||
|
||||
+3
-3
@@ -209,8 +209,8 @@ class MageGeometryGuideLineChecker(StudyGeometryChecker):
|
||||
self._name = _translate("Checker", "Mage geometry guideline checker")
|
||||
self._description = _translate(
|
||||
"Checker",
|
||||
"Check if exists geometry guide line is correctly defined \
|
||||
for each reaches of the study"
|
||||
"Check if exists geometry guidelines are correctly defined \
|
||||
for each reach"
|
||||
)
|
||||
self._solver = version
|
||||
self._modules = Modules.GEOMETRY
|
||||
@@ -241,7 +241,7 @@ for each reaches of the study"
|
||||
for profile in profiles:
|
||||
if not profile.has_standard_named_points():
|
||||
self._status = STATUS.WARNING
|
||||
self._summary = "no_standard_guileline_defined"
|
||||
self._summary = "no_standard_guideline_defined"
|
||||
return False
|
||||
|
||||
for gl in gls[1:]:
|
||||
|
||||
+127
-17
@@ -17,6 +17,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import time
|
||||
import logging
|
||||
from functools import reduce
|
||||
|
||||
from PyQt5.QtCore import QCoreApplication
|
||||
|
||||
@@ -25,28 +27,24 @@ from Checker.Checker import AbstractModelChecker, STATUS
|
||||
|
||||
_translate = QCoreApplication.translate
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
class StudyNetworkReachChecker(AbstractModelChecker):
|
||||
def __init__(self):
|
||||
super(StudyNetworkReachChecker, self).__init__()
|
||||
|
||||
self._name = _translate("Checker", "Study network reach checker")
|
||||
self._name = _translate("Checker", "Study reach network checker")
|
||||
self._description = _translate(
|
||||
"Checker", "Check if exists at least one reach for study"
|
||||
"Checker", "Check if exists at least one reach exists"
|
||||
)
|
||||
self._modules = Modules.NETWORK
|
||||
|
||||
def run(self, study):
|
||||
if study is None:
|
||||
self._status = STATUS.ERROR
|
||||
self._summary = "invalid_study"
|
||||
if not self.basic_check(study):
|
||||
return False
|
||||
|
||||
river = study.river
|
||||
if river is None:
|
||||
self._status = STATUS.ERROR
|
||||
self._summary = "no_river_found"
|
||||
return False
|
||||
|
||||
edges = list(filter(lambda e: e.is_enable(), river.edges()))
|
||||
if len(edges) == 0:
|
||||
@@ -65,7 +63,7 @@ class StudyGeometryChecker(AbstractModelChecker):
|
||||
|
||||
self._name = _translate("Checker", "Study geometry checker")
|
||||
self._description = _translate(
|
||||
"Checker", "Check if exists geometry for each reach of study"
|
||||
"Checker", "Check if the geometry of each reach exists"
|
||||
)
|
||||
self._modules = Modules.GEOMETRY
|
||||
|
||||
@@ -77,16 +75,10 @@ class StudyGeometryChecker(AbstractModelChecker):
|
||||
summary = "ok"
|
||||
status = STATUS.OK
|
||||
|
||||
if study is None:
|
||||
self._status = STATUS.ERROR
|
||||
self._summary = "invalid_study"
|
||||
if not self.basic_check(study):
|
||||
return False
|
||||
|
||||
river = study.river
|
||||
if river is None:
|
||||
self._status = STATUS.ERROR
|
||||
self._summary = "no_river_found"
|
||||
return False
|
||||
|
||||
edges = river.enable_edges()
|
||||
if len(edges) == 0:
|
||||
@@ -106,6 +98,124 @@ class StudyGeometryChecker(AbstractModelChecker):
|
||||
return ok
|
||||
|
||||
|
||||
class StudyInitialConditionsChecker(AbstractModelChecker):
|
||||
def __init__(self):
|
||||
super(StudyInitialConditionsChecker, self).__init__()
|
||||
|
||||
self._name = _translate("Checker", "Study initial conditions checker")
|
||||
self._description = _translate(
|
||||
"Checker", "Check initial conditions for each node of study"
|
||||
)
|
||||
self._modules = Modules.INITIAL_CONDITION
|
||||
|
||||
def run(self, study):
|
||||
ok = True
|
||||
nerror = 0
|
||||
self._summary = "ok"
|
||||
self._status = STATUS.OK
|
||||
|
||||
if not self.basic_check(study):
|
||||
return False
|
||||
|
||||
for reach in study.river.enable_edges():
|
||||
ok &= self.check_reach(study, reach.reach)
|
||||
|
||||
return ok
|
||||
|
||||
def check_reach(self, study, reach):
|
||||
ok = True
|
||||
river = study.river
|
||||
|
||||
if reach not in river.initial_conditions:
|
||||
self._summary = "missing_initial_condition_defined"
|
||||
self._status = STATUS.WARNING
|
||||
return ok
|
||||
|
||||
ic = river.initial_conditions[reach]
|
||||
len_ic = len(ic)
|
||||
len_reach = len(reach)
|
||||
|
||||
if len_ic < len_reach:
|
||||
self._summary = "initial_condition_missing_profile"
|
||||
self._status = STATUS.WARNING
|
||||
|
||||
if len_ic > len_reach:
|
||||
self._summary = "more_initial_condition_than_profile"
|
||||
self._status = STATUS.ERROR
|
||||
ok = False
|
||||
|
||||
return ok
|
||||
|
||||
|
||||
class StudyBoundaryConditionChecker(AbstractModelChecker):
|
||||
def __init__(self):
|
||||
super(StudyBoundaryConditionChecker, self).__init__()
|
||||
|
||||
self._name = _translate("Checker", "Study boundary conditions checker")
|
||||
self._description = _translate(
|
||||
"Checker", "Check boundary conditions for each node of study"
|
||||
)
|
||||
self._modules = Modules.BOUNDARY_CONDITION
|
||||
|
||||
def run(self, study):
|
||||
ok = True
|
||||
nerror = 0
|
||||
self._summary = "ok"
|
||||
self._status = STATUS.OK
|
||||
|
||||
if not self.basic_check(study):
|
||||
return False
|
||||
|
||||
ok = self.check_liquid(study)
|
||||
|
||||
return ok
|
||||
|
||||
def check_liquid(self, study):
|
||||
river = study.river
|
||||
|
||||
bcs = river.boundary_condition.get_tab('liquid')
|
||||
if len(bcs) == 0:
|
||||
self._status = STATUS.ERROR
|
||||
self._summary = "no_boundary_condition_defined"
|
||||
return False
|
||||
|
||||
upstream, downstream = reduce(
|
||||
lambda acc, n: (
|
||||
acc[0] + [n] if river.is_upstream_node(n) else acc[0],
|
||||
acc[1] + [n] if river.is_downstream_node(n) else acc[1],
|
||||
),
|
||||
filter(
|
||||
lambda n: river.is_enable_node(n),
|
||||
river.nodes()
|
||||
),
|
||||
([], [])
|
||||
)
|
||||
|
||||
bcs_nodes = set(map(lambda bc: bc.node, bcs))
|
||||
|
||||
upstream_ok = self.check_liquid_all_node_has_bc(
|
||||
bcs_nodes, upstream
|
||||
)
|
||||
downstream_ok = self.check_liquid_all_node_has_bc(
|
||||
bcs_nodes, downstream
|
||||
)
|
||||
|
||||
ok = upstream_ok and downstream_ok
|
||||
|
||||
if not ok:
|
||||
self._status = STATUS.ERROR
|
||||
self._summary = "no_boundary_condition_at_boundary_node"
|
||||
|
||||
return ok
|
||||
|
||||
def check_liquid_all_node_has_bc(self, bcs_nodes, nodes):
|
||||
return reduce(
|
||||
lambda acc, n: (acc and (n in bcs_nodes)),
|
||||
nodes,
|
||||
True
|
||||
)
|
||||
|
||||
|
||||
class DummyOK(AbstractModelChecker):
|
||||
def __init__(self):
|
||||
super(DummyOK, self).__init__()
|
||||
|
||||
+98
-11
@@ -293,10 +293,12 @@ class MeshingWithMageMailleurTT(AMeshingTool):
|
||||
def meshing(self, reach,
|
||||
step: float = 50,
|
||||
limites=[-1, -1],
|
||||
origin=0,
|
||||
directrices=['un', 'np'],
|
||||
lplan: bool = False,
|
||||
lm: int = 3,
|
||||
linear: bool = False):
|
||||
linear: bool = False,
|
||||
origin_value=0.0):
|
||||
if reach is None or len(reach.profiles) == 0:
|
||||
return reach
|
||||
|
||||
@@ -305,32 +307,41 @@ class MeshingWithMageMailleurTT(AMeshingTool):
|
||||
st_file = self.export_reach_to_st(reach, tmp)
|
||||
m_file = st_file.rsplit(".ST", 1)[0] + ".M"
|
||||
|
||||
os.sync()
|
||||
|
||||
proc = QProcess()
|
||||
proc.setWorkingDirectory(tmp)
|
||||
|
||||
# Mage section index start at 1
|
||||
origin += 1
|
||||
limites[0] += 1
|
||||
limites[1] += 1
|
||||
|
||||
lplan = 1 if lplan else 0
|
||||
linear = 1 if linear else 0
|
||||
|
||||
logger.info(
|
||||
f"! {self._exe_path()} {st_file} {m_file} " +
|
||||
f"! {self._exe_path()} " +
|
||||
f"{st_file} {m_file} " +
|
||||
f"mesh " +
|
||||
f"{str(step)} " +
|
||||
f"{limites[0]} {limites[1]} " +
|
||||
f"{directrices[0]} {directrices[1]} " +
|
||||
f"{lplan} {lm} {linear}"
|
||||
f"{lplan} {lm} {linear} " +
|
||||
f"{origin} "
|
||||
)
|
||||
proc.start(
|
||||
self._exe_path(),
|
||||
list(map(
|
||||
list(
|
||||
map(
|
||||
str,
|
||||
[
|
||||
st_file, m_file, step,
|
||||
st_file, m_file,
|
||||
"mesh", step,
|
||||
limites[0], limites[1],
|
||||
directrices[0], directrices[1],
|
||||
lplan, lm, linear
|
||||
lplan, lm, linear, origin, origin_value
|
||||
]
|
||||
))
|
||||
)
|
||||
)
|
||||
)
|
||||
proc.waitForFinished()
|
||||
|
||||
@@ -347,9 +358,85 @@ class MeshingWithMageMailleurTT(AMeshingTool):
|
||||
logger.error(
|
||||
f"{logger_color_red()}{errors}{logger_color_reset()}"
|
||||
)
|
||||
else:
|
||||
self.import_m_file(reach, m_file)
|
||||
return reach
|
||||
|
||||
self.import_m_file(reach, m_file)
|
||||
return reach
|
||||
|
||||
def update_kp(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
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
logger.debug(f"temp file: {tmp}")
|
||||
st_file = self.export_reach_to_st(reach, tmp)
|
||||
m_file = st_file.rsplit(".ST", 1)[0] + ".M"
|
||||
|
||||
os.sync()
|
||||
|
||||
proc = QProcess()
|
||||
proc.setWorkingDirectory(tmp)
|
||||
|
||||
# Mage section indices start at 1
|
||||
origin += 1
|
||||
limites[0] += 1
|
||||
limites[1] += 1
|
||||
|
||||
lplan = 1 if lplan else 0
|
||||
linear = 1 if linear else 0
|
||||
|
||||
logger.info(
|
||||
f"! {self._exe_path()} " +
|
||||
f"{st_file} {m_file} " +
|
||||
f"update_kp " +
|
||||
f"{str(step)} " +
|
||||
f"{limites[0]} {limites[1]} " +
|
||||
f"{directrices[0]} {directrices[1]} " +
|
||||
f"{orientation} {lm} {linear} " +
|
||||
f"{origin} "
|
||||
)
|
||||
proc.start(
|
||||
self._exe_path(),
|
||||
list(
|
||||
map(
|
||||
str,
|
||||
[
|
||||
st_file, m_file,
|
||||
"update_kp", step,
|
||||
limites[0], limites[1],
|
||||
directrices[0], directrices[1],
|
||||
orientation, lm, linear, origin, origin_value
|
||||
]
|
||||
)
|
||||
)
|
||||
)
|
||||
proc.waitForFinished()
|
||||
|
||||
if proc.exitCode() != 0:
|
||||
logger.error(
|
||||
f"{logger_color_red()}" +
|
||||
f"MailleurTT error: {proc.exitCode()}" +
|
||||
f"{logger_color_reset()}"
|
||||
)
|
||||
outputs = proc.readAllStandardOutput()
|
||||
logger.debug(outputs)
|
||||
|
||||
errors = proc.readAllStandardError()
|
||||
logger.error(
|
||||
f"{logger_color_red()}{errors}{logger_color_reset()}"
|
||||
)
|
||||
return reach
|
||||
|
||||
self.import_m_file(reach, m_file)
|
||||
return reach
|
||||
|
||||
def export_reach_to_st(self, reach, tmp):
|
||||
|
||||
@@ -315,6 +315,9 @@ class BoundaryCondition(SQLSubModel):
|
||||
self._data.sort(reverse=_reverse, key=key)
|
||||
self._status.modified()
|
||||
|
||||
def index(self, bc):
|
||||
self._data.index(bc)
|
||||
|
||||
def get_i(self, index):
|
||||
return self.data[index]
|
||||
|
||||
|
||||
+2
-1
@@ -21,7 +21,7 @@ from PyQt5.QtCore import (
|
||||
)
|
||||
|
||||
from PyQt5.QtWidgets import (
|
||||
QApplication, QMessageBox,
|
||||
QApplication, QMessageBox, QLabel,
|
||||
)
|
||||
|
||||
_translate = QCoreApplication.translate
|
||||
@@ -34,6 +34,7 @@ _translate = QCoreApplication.translate
|
||||
def exception_message_box(exception):
|
||||
msg = QMessageBox()
|
||||
msg.setIcon(QMessageBox.Critical)
|
||||
msg.findChild(QLabel, "qt_msgbox_label").setFixedWidth(384)
|
||||
|
||||
msg.setText("Exception :")
|
||||
msg.setInformativeText(f"{exception}")
|
||||
|
||||
@@ -209,6 +209,14 @@ class Friction(SQLSubModel):
|
||||
|
||||
self._status.modified()
|
||||
|
||||
def __contains__(self, kp):
|
||||
return self.contains_kp(kp)
|
||||
|
||||
def contains_kp(self, kp):
|
||||
return (
|
||||
self._begin_kp <= kp <= self._end_kp
|
||||
)
|
||||
|
||||
@property
|
||||
def begin_strickler(self):
|
||||
return self._begin_strickler
|
||||
|
||||
@@ -192,6 +192,22 @@ class PointXYZ(Point, SQLSubModel):
|
||||
def dist(self, p2):
|
||||
return PointXYZ.distance(self, p2)
|
||||
|
||||
def dist_2d(self, p2):
|
||||
return PointXYZ.distance_2d(self, p2)
|
||||
|
||||
@staticmethod
|
||||
def distance_2d(p1, p2):
|
||||
"""Euclidean distance between p1 and p2.
|
||||
|
||||
Args:
|
||||
p1: A XYZ Point
|
||||
p2: A XYZ Point
|
||||
|
||||
Returns:
|
||||
Euclidean 2D distance between the two points
|
||||
"""
|
||||
return dist((p1.x, p1.y), (p2.x, p2.y))
|
||||
|
||||
@staticmethod
|
||||
def distance(p1, p2):
|
||||
"""Euclidean distance between p1 and p2.
|
||||
@@ -201,6 +217,14 @@ class PointXYZ(Point, SQLSubModel):
|
||||
p2: A XYZ Point
|
||||
|
||||
Returns:
|
||||
Euclidean distance between the two points
|
||||
Euclidean 3D distance between the two points
|
||||
"""
|
||||
return dist((p1.x, p1.y, p1.z), (p2.x, p2.y, p2.z))
|
||||
|
||||
@staticmethod
|
||||
def areatriangle3d(p1, p2, p3):
|
||||
a = PointXYZ.distance(p1, p2)
|
||||
b = PointXYZ.distance(p2, p3)
|
||||
c = PointXYZ.distance(p3, p1)
|
||||
s = (a + b + c) / 2
|
||||
return (s*(s-a) * (s-b)*(s-c)) ** 0.5
|
||||
@@ -19,6 +19,7 @@
|
||||
import logging
|
||||
|
||||
from tools import timer
|
||||
from shapely import geometry
|
||||
|
||||
from Model.Geometry.Point import Point
|
||||
from Model.Except import NotImplementedMethodeError
|
||||
@@ -295,6 +296,11 @@ class Profile(object):
|
||||
)
|
||||
self._status.modified()
|
||||
|
||||
@timer
|
||||
def reverse(self):
|
||||
self._points.reverse()
|
||||
self._status.modified()
|
||||
|
||||
# Sediment Layers
|
||||
|
||||
def get_sl(self):
|
||||
@@ -341,3 +347,12 @@ class Profile(object):
|
||||
# Abstract method for width approximation
|
||||
def get_water_limits(self, z):
|
||||
raise NotImplementedMethodeError(self, self.get_water_limits)
|
||||
|
||||
def wet_points(self, z):
|
||||
raise NotImplementedMethodeError(self, self.wet_point)
|
||||
|
||||
def wet_perimeter(self, z):
|
||||
raise NotImplementedMethodeError(self, self.wet_perimeter)
|
||||
|
||||
def wet_area(self, z):
|
||||
raise NotImplementedMethodeError(self, self.wet_area)
|
||||
@@ -22,6 +22,7 @@ from typing import List
|
||||
from functools import reduce
|
||||
|
||||
from tools import timer
|
||||
from shapely import geometry
|
||||
|
||||
from Model.Tools.PamhyrDB import SQLSubModel
|
||||
from Model.Except import ClipboardFormatError
|
||||
@@ -373,99 +374,13 @@ class ProfileXYZ(Profile, SQLSubModel):
|
||||
"""
|
||||
return [x for x in lst if not np.isnan(x)]
|
||||
|
||||
def _first_point_not_nan(self):
|
||||
first_point = self.points[0]
|
||||
def speed(self, q, z):
|
||||
area = self.wet_area(z)
|
||||
|
||||
for point in self.points:
|
||||
if not point.is_nan():
|
||||
first_point = point
|
||||
break
|
||||
if area == 0:
|
||||
return 0
|
||||
|
||||
return first_point
|
||||
|
||||
def _last_point_not_nan(self):
|
||||
last_point = self.points[-1]
|
||||
|
||||
for point in self.points[::-1]:
|
||||
if not point.is_nan():
|
||||
last_point = point
|
||||
break
|
||||
|
||||
return last_point
|
||||
|
||||
@timer
|
||||
def get_station(self) -> np.ndarray:
|
||||
"""Projection of the points of the profile on a plane.
|
||||
|
||||
Args:
|
||||
profile: The profile
|
||||
|
||||
Returns:
|
||||
Projection of the points of the profile on a plane.
|
||||
"""
|
||||
if self.nb_points < 3:
|
||||
return None
|
||||
else:
|
||||
first_named_point = None
|
||||
index_first_named_point = None
|
||||
last_named_point = None
|
||||
|
||||
first_point_not_nan = self._first_point_not_nan()
|
||||
last_point_not_nan = self._last_point_not_nan()
|
||||
|
||||
for index, point in enumerate(self.points):
|
||||
if point.point_is_named():
|
||||
index_first_named_point = index
|
||||
first_named_point = point
|
||||
break
|
||||
|
||||
for point in reversed(self.points):
|
||||
if point.point_is_named():
|
||||
last_named_point = point
|
||||
break
|
||||
|
||||
station = []
|
||||
constant = 0.0
|
||||
|
||||
if (first_named_point is not None and
|
||||
last_named_point is not None):
|
||||
if (first_named_point != last_named_point and
|
||||
first_named_point.x != last_named_point.x):
|
||||
vector = Vector1d(first_named_point, last_named_point)
|
||||
norm_dir_vec = vector.normalized_direction_vector()
|
||||
else:
|
||||
vector = Vector1d(first_point_not_nan, last_point_not_nan)
|
||||
norm_dir_vec = vector.normalized_direction_vector()
|
||||
|
||||
for point in self.points:
|
||||
xi = point.x - first_named_point.x
|
||||
yi = point.y - first_named_point.y
|
||||
station_i = (norm_dir_vec[0] * xi +
|
||||
norm_dir_vec[1] * yi)
|
||||
station.append(station_i)
|
||||
|
||||
constant = station[index_first_named_point]
|
||||
elif first_named_point is None:
|
||||
vector = Vector1d(first_point_not_nan, last_point_not_nan)
|
||||
norm_dir_vec = vector.normalized_direction_vector()
|
||||
|
||||
for point in self.points:
|
||||
xi = point.x - first_point_not_nan.x
|
||||
yi = point.y - first_point_not_nan.y
|
||||
station_i = (norm_dir_vec[0] * xi +
|
||||
norm_dir_vec[1] * yi)
|
||||
station.append(station_i)
|
||||
|
||||
z_min = self.z_min()
|
||||
index_profile_z_min = list(
|
||||
filter(
|
||||
lambda z: z[1] == z_min,
|
||||
enumerate(self.z())
|
||||
)
|
||||
)[0]
|
||||
constant = station[index_profile_z_min[0]]
|
||||
|
||||
return list(map(lambda s: s - constant, station))
|
||||
return q / area
|
||||
|
||||
def width_approximation(self):
|
||||
if self.has_standard_named_points():
|
||||
@@ -477,6 +392,42 @@ class ProfileXYZ(Profile, SQLSubModel):
|
||||
|
||||
return abs(rg.dist(rd))
|
||||
|
||||
def wet_perimeter(self, z):
|
||||
poly = self.wet_polygon(z)
|
||||
|
||||
if poly is None:
|
||||
return 0
|
||||
|
||||
return poly.length
|
||||
|
||||
def wet_area(self, z):
|
||||
poly = self.wet_polygon(z)
|
||||
|
||||
if poly is None:
|
||||
return 0
|
||||
|
||||
return poly.area
|
||||
|
||||
def wet_polygon(self, z):
|
||||
points = self.wet_points(z)
|
||||
if len(points) < 3:
|
||||
return None
|
||||
|
||||
z = map(lambda p: p.z, points)
|
||||
station = self._get_station(points)
|
||||
|
||||
poly = geometry.Polygon(list(zip(station, z)))
|
||||
return poly
|
||||
|
||||
def wet_points(self, z):
|
||||
left, right = self.get_water_limits(z)
|
||||
|
||||
points = list(filter(lambda p: p.z < z, self._points))
|
||||
points = [left] + points + [right]
|
||||
points = sorted(points, key=lambda p: p.x)
|
||||
|
||||
return points
|
||||
|
||||
def get_water_limits(self, z):
|
||||
"""
|
||||
Determine left and right limits of water elevation.
|
||||
@@ -509,7 +460,7 @@ class ProfileXYZ(Profile, SQLSubModel):
|
||||
[self.point(i_left).z, self.point(i_left - 1).z],
|
||||
[self.point(i_left).y, self.point(i_left - 1).y]
|
||||
)
|
||||
pt_left = PointXYZ(x, y, z)
|
||||
pt_left = PointXYZ(x, y, z, name="wl_left")
|
||||
else:
|
||||
pt_left = self.point(0)
|
||||
|
||||
@@ -525,8 +476,151 @@ class ProfileXYZ(Profile, SQLSubModel):
|
||||
[self.point(i_right).z, self.point(i_right + 1).z],
|
||||
[self.point(i_right).y, self.point(i_right + 1).y]
|
||||
)
|
||||
pt_right = PointXYZ(x, y, z)
|
||||
pt_right = PointXYZ(x, y, z, name="wl_right")
|
||||
else:
|
||||
pt_right = self.point(self.number_points - 1)
|
||||
|
||||
return pt_left, pt_right
|
||||
|
||||
def get_station(self):
|
||||
"""Projection of the points of the profile on a plane.
|
||||
|
||||
Args:
|
||||
self: The profile
|
||||
|
||||
Returns:
|
||||
Projection of the points of the profile on a plane.
|
||||
"""
|
||||
if self.nb_points < 3:
|
||||
return None
|
||||
else:
|
||||
return self._get_station(self.points)
|
||||
|
||||
@timer
|
||||
def _get_station(self, points):
|
||||
first_named_point = None
|
||||
index_first_named_point = None
|
||||
last_named_point = None
|
||||
|
||||
first_point_not_nan = self._first_point_not_nan(points)
|
||||
last_point_not_nan = self._last_point_not_nan(points)
|
||||
|
||||
for index, point in enumerate(points):
|
||||
if point.point_is_named():
|
||||
index_first_named_point = index
|
||||
first_named_point = point
|
||||
break
|
||||
|
||||
for point in reversed(points):
|
||||
if point.point_is_named():
|
||||
last_named_point = point
|
||||
break
|
||||
|
||||
station = []
|
||||
constant = 0.0
|
||||
|
||||
if (first_named_point is not None
|
||||
and last_named_point is not None):
|
||||
if (first_named_point != last_named_point
|
||||
and first_named_point.x != last_named_point.x):
|
||||
vector = Vector1d(first_named_point, last_named_point)
|
||||
norm_dir_vec = vector.normalized_direction_vector()
|
||||
else:
|
||||
vector = Vector1d(first_point_not_nan, last_point_not_nan)
|
||||
norm_dir_vec = vector.normalized_direction_vector()
|
||||
|
||||
for point in points:
|
||||
xi = point.x - first_named_point.x
|
||||
yi = point.y - first_named_point.y
|
||||
station_i = (norm_dir_vec[0] * xi +
|
||||
norm_dir_vec[1] * yi)
|
||||
station.append(station_i)
|
||||
|
||||
constant = station[index_first_named_point]
|
||||
elif first_named_point is None:
|
||||
vector = Vector1d(first_point_not_nan, last_point_not_nan)
|
||||
norm_dir_vec = vector.normalized_direction_vector()
|
||||
|
||||
for point in points:
|
||||
xi = point.x - first_point_not_nan.x
|
||||
yi = point.y - first_point_not_nan.y
|
||||
station_i = (norm_dir_vec[0] * xi +
|
||||
norm_dir_vec[1] * yi)
|
||||
station.append(station_i)
|
||||
|
||||
z_min = self.z_min()
|
||||
index_profile_z_min = next(
|
||||
filter(
|
||||
lambda z: z[1] == z_min,
|
||||
enumerate(self.z())
|
||||
)
|
||||
)
|
||||
constant = station[index_profile_z_min[0]]
|
||||
|
||||
return list(map(lambda s: s - constant, station))
|
||||
|
||||
def _first_point_not_nan(self, points):
|
||||
first_point = None
|
||||
|
||||
for point in points:
|
||||
if not point.is_nan():
|
||||
first_point = point
|
||||
break
|
||||
|
||||
return first_point
|
||||
|
||||
def _last_point_not_nan(self, points):
|
||||
last_point = None
|
||||
|
||||
for point in reversed(points):
|
||||
if not point.is_nan():
|
||||
last_point = point
|
||||
break
|
||||
|
||||
return last_point
|
||||
|
||||
def purge(self, np_purge):
|
||||
"""
|
||||
Remove points to keep at most np_purge points.
|
||||
"""
|
||||
if (self.nb_points <= np_purge):
|
||||
return
|
||||
|
||||
nb_named = 2 # we consider the first and last point as named
|
||||
area = [0.0]
|
||||
|
||||
for i in range(1, self.nb_points-1):
|
||||
if self.point(i).point_is_named():
|
||||
area.append(9999999.999)
|
||||
nb_named += 1
|
||||
else:
|
||||
area.append(
|
||||
PointXYZ.areatriangle3d(
|
||||
self.point(i-1),
|
||||
self.point(i),
|
||||
self.point(i+1))
|
||||
)
|
||||
|
||||
area.append(0.0)
|
||||
|
||||
while self.nb_points > max(np_purge, nb_named):
|
||||
to_rm = np.argmin(area[1:self.nb_points - 1]) + 1
|
||||
|
||||
self.delete_i([to_rm])
|
||||
area.pop(to_rm)
|
||||
|
||||
for i in [to_rm-1, to_rm]:
|
||||
if (i == 0):
|
||||
continue
|
||||
|
||||
if (i == self.nb_points - 1):
|
||||
continue
|
||||
|
||||
if self.point(i).point_is_named():
|
||||
area[i] = 9999999.999
|
||||
else:
|
||||
area[i] = PointXYZ.areatriangle3d(
|
||||
self.point(i-1),
|
||||
self.point(i),
|
||||
self.point(i+1)
|
||||
)
|
||||
+106
-1
@@ -16,7 +16,9 @@
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import logging
|
||||
import shapefile
|
||||
import numpy as np
|
||||
|
||||
from time import time
|
||||
@@ -364,6 +366,30 @@ class Reach(SQLSubModel):
|
||||
else:
|
||||
return 0.0
|
||||
|
||||
def inter_profiles_kp(self):
|
||||
profiles = sorted(self.profiles, key=lambda p: p.kp)
|
||||
first = profiles[0]
|
||||
last = profiles[-1]
|
||||
|
||||
res_kp = [first.kp]
|
||||
|
||||
profiles = iter(profiles)
|
||||
previous = next(profiles)
|
||||
|
||||
for profile in profiles:
|
||||
prev = previous.kp
|
||||
curr = profile.kp
|
||||
|
||||
diff = abs(previous.kp - profile.kp)
|
||||
|
||||
new = previous.kp + (diff / 2.0)
|
||||
res_kp.append(new)
|
||||
|
||||
previous = profile
|
||||
|
||||
res_kp.append(last.kp)
|
||||
return res_kp
|
||||
|
||||
# Sediment Layers
|
||||
|
||||
def get_sl(self):
|
||||
@@ -533,8 +559,66 @@ class Reach(SQLSubModel):
|
||||
|
||||
# Import/Export
|
||||
|
||||
@timer
|
||||
def import_geometry(self, file_path_name: str):
|
||||
ext = file_path_name.split(".")
|
||||
if len(ext) > 0:
|
||||
ext = ext[-1].lower()
|
||||
|
||||
if ext in ['st', 'm']:
|
||||
return self.import_geometry_st(file_path_name)
|
||||
|
||||
if ext in ['shp']:
|
||||
return self.import_geometry_shp(file_path_name)
|
||||
|
||||
raise FileFormatError(
|
||||
file_path_name,
|
||||
f"Geometry file extention ('.{ext}') unkown"
|
||||
)
|
||||
|
||||
@timer
|
||||
def import_geometry_shp(self, file_path_name: str):
|
||||
sf = shapefile.Reader(
|
||||
os.path.abspath(file_path_name)
|
||||
)
|
||||
|
||||
if sf.shapeType != shapefile.POLYLINEZ:
|
||||
raise FileFormatError(
|
||||
file_path_name,
|
||||
"Shapefile is not a POLYLINEZ"
|
||||
)
|
||||
|
||||
profiles = []
|
||||
|
||||
for i in range(len(sf)):
|
||||
s_profile = sf.shape(i)
|
||||
z = s_profile.z
|
||||
|
||||
ind = 0
|
||||
points = []
|
||||
for point in s_profile.points:
|
||||
points.append([
|
||||
point[0],
|
||||
point[1],
|
||||
z[ind]
|
||||
])
|
||||
|
||||
ind += 1
|
||||
|
||||
prof = ProfileXYZ(
|
||||
reach=self, status=self._status
|
||||
)
|
||||
prof.import_points(points)
|
||||
|
||||
profiles.append(prof)
|
||||
|
||||
self.profiles = profiles + self.profiles
|
||||
self._update_profile_numbers()
|
||||
self._recompute_kp()
|
||||
|
||||
return profiles
|
||||
|
||||
@timer
|
||||
def import_geometry_st(self, file_path_name: str):
|
||||
"""Import a geometry from file (.ST or .st)
|
||||
|
||||
Args:
|
||||
@@ -762,3 +846,24 @@ class Reach(SQLSubModel):
|
||||
except Exception as e:
|
||||
logger_exception(e)
|
||||
return 0
|
||||
|
||||
def _recompute_kp(self, offset=0.0):
|
||||
self._recompute_kp_no_gl(offset=offset)
|
||||
|
||||
def _recompute_kp_no_gl(self, offset=0.0):
|
||||
profiles = iter(self.profiles)
|
||||
|
||||
previous = next(profiles)
|
||||
previous.kp = offset
|
||||
|
||||
for profile in profiles:
|
||||
prev_points = previous.points
|
||||
curr_points = profile.points
|
||||
|
||||
dist = (
|
||||
prev_points[0].dist_2d(curr_points[0]) +
|
||||
prev_points[-1].dist_2d(curr_points[-1])
|
||||
) / 2.0
|
||||
|
||||
profile.kp = previous.kp + dist
|
||||
previous = profile
|
||||
@@ -140,7 +140,6 @@ class Data(SQLSubModel):
|
||||
reach=self._reach,
|
||||
status=self._status,
|
||||
)
|
||||
|
||||
return new
|
||||
|
||||
@property
|
||||
@@ -266,6 +265,9 @@ class InitialConditions(SQLSubModel):
|
||||
def __len__(self):
|
||||
return len(self._data)
|
||||
|
||||
def lst(self):
|
||||
return self._data
|
||||
|
||||
@property
|
||||
def reach(self):
|
||||
return self._reach
|
||||
@@ -295,6 +297,15 @@ class InitialConditions(SQLSubModel):
|
||||
self._data.insert(index, n)
|
||||
self._status.modified()
|
||||
|
||||
def new_from_data(self, kp, discharge, elevation):
|
||||
n = Data(reach=self._reach, status=self._status)
|
||||
|
||||
n['kp'] = kp
|
||||
n['discharge'] = discharge
|
||||
n['elevation'] = elevation
|
||||
|
||||
return n
|
||||
|
||||
def insert(self, index, data):
|
||||
self._data.insert(index, data)
|
||||
self._status.modified()
|
||||
@@ -356,19 +367,33 @@ class InitialConditions(SQLSubModel):
|
||||
key=lambda p: p.kp
|
||||
)
|
||||
|
||||
def generate_growing_constante_height(self, height: float):
|
||||
self._data = []
|
||||
def generate_growing_constante_height(self, height: float,
|
||||
compute_discharge: bool):
|
||||
|
||||
profiles = self._reach.reach.profiles.copy()
|
||||
self._sort_by_z_and_kp(profiles)
|
||||
|
||||
previous_elevation = -99999.99
|
||||
|
||||
data_discharge = {}
|
||||
if not compute_discharge:
|
||||
if len(self._data) == 0:
|
||||
for profile in profiles:
|
||||
data_discharge[profile.kp] = 0.0
|
||||
else:
|
||||
for data in self._data:
|
||||
data_discharge[data["kp"]] = data["discharge"]
|
||||
|
||||
incline = self._reach.reach.get_incline_median_mean()
|
||||
logger.debug(f"incline = {incline}")
|
||||
|
||||
previous_elevation = -99999.99
|
||||
self._data = []
|
||||
for profile in profiles:
|
||||
width = profile.width_approximation()
|
||||
strickler = 25
|
||||
|
||||
if not compute_discharge:
|
||||
discharge = data_discharge[profile.kp]
|
||||
else:
|
||||
discharge = (
|
||||
((width * 0.8)
|
||||
* strickler
|
||||
@@ -389,33 +414,39 @@ class InitialConditions(SQLSubModel):
|
||||
new = Data(reach=self._reach, status=self._status)
|
||||
new["kp"] = profile.kp
|
||||
new["discharge"] = discharge
|
||||
|
||||
new["elevation"] = elevation
|
||||
|
||||
self._data.append(new)
|
||||
previous_elevation = elevation
|
||||
self._data.append(new)
|
||||
|
||||
self._generate_resort_data(profiles)
|
||||
|
||||
def generate_discharge(self, discharge: float):
|
||||
self._data = []
|
||||
def generate_discharge(self, discharge: float, compute_height: bool):
|
||||
|
||||
self._generate_height_estimation_from_discharge(
|
||||
discharge
|
||||
)
|
||||
|
||||
def _generate_height_estimation_from_discharge(self, discharge: float):
|
||||
profiles = self._reach.reach.profiles.copy()
|
||||
self._sort_by_z_and_kp(profiles)
|
||||
|
||||
previous_elevation = -99999.99
|
||||
|
||||
data_height = {}
|
||||
if not compute_height:
|
||||
if len(self._data) == 0:
|
||||
for profile in profiles:
|
||||
data_height[profile.kp] = 0.0
|
||||
else:
|
||||
for data in self._data:
|
||||
data_height[data["kp"]] = data["height"]
|
||||
|
||||
incline = self._reach.reach.get_incline_median_mean()
|
||||
logger.debug(f"incline = {incline}")
|
||||
|
||||
self._data = []
|
||||
for profile in profiles:
|
||||
width = profile.width_approximation()
|
||||
strickler = 25
|
||||
|
||||
if not compute_height:
|
||||
height = data_height[profile.kp]
|
||||
else:
|
||||
height = (
|
||||
discharge
|
||||
/
|
||||
|
||||
+14
-24
@@ -142,26 +142,21 @@ class Graph(object):
|
||||
def insert_node(self, node):
|
||||
return self._add_node(node)
|
||||
|
||||
def remove_node(self, node_name: str):
|
||||
self._nodes = list(
|
||||
filter(
|
||||
lambda n: n.name != node_name,
|
||||
self._nodes
|
||||
)
|
||||
)
|
||||
self._remove_associated_edge(node_name)
|
||||
def remove_node(self, node):
|
||||
self._nodes.remove(node)
|
||||
self._remove_associated_edge(node)
|
||||
self._status.modified()
|
||||
|
||||
def _remove_associated_edge(self, node_name: str):
|
||||
def _remove_associated_edge(self, node: str):
|
||||
edges = list(
|
||||
filter(
|
||||
lambda e: (e.node1.name == node_name or
|
||||
e.node2.name == node_name),
|
||||
lambda e: (e.node1 == node or
|
||||
e.node2 == node),
|
||||
self._edges,
|
||||
)
|
||||
)
|
||||
for edge in edges:
|
||||
self.remove_edge(edge.name)
|
||||
self.remove_edge(edge)
|
||||
|
||||
def create_node(self, x: float = 0.0, y: float = 0.0):
|
||||
node = self._create_node(x, y)
|
||||
@@ -176,11 +171,11 @@ class Graph(object):
|
||||
return edge
|
||||
|
||||
def _add_edge(self, edge):
|
||||
# This edge already exists ?
|
||||
if any(filter(lambda e: (e.node1 == edge.node1 and
|
||||
e.node2 == edge.node2),
|
||||
self._edges)):
|
||||
return None
|
||||
# # This edge already exists ?
|
||||
# if any(filter(lambda e: (e.node1 == edge.node1 and
|
||||
# e.node2 == edge.node2),
|
||||
# self._edges)):
|
||||
# return None
|
||||
|
||||
self._edges.append(edge)
|
||||
|
||||
@@ -197,13 +192,8 @@ class Graph(object):
|
||||
def create_edge(self, n1: Node, n2: Node):
|
||||
return self._create_edge(n1, n2)
|
||||
|
||||
def remove_edge(self, edge_name: str):
|
||||
self._edges = list(
|
||||
filter(
|
||||
lambda e: e.name != edge_name,
|
||||
self._edges
|
||||
)
|
||||
)
|
||||
def remove_edge(self, edge):
|
||||
self._edges.remove(edge)
|
||||
self._status.modified()
|
||||
|
||||
def is_upstream_node(self, node):
|
||||
|
||||
@@ -60,7 +60,7 @@ class GraphTestCase(unittest.TestCase):
|
||||
|
||||
e0 = g.add_edge(n0, n1)
|
||||
|
||||
g.remove_edge(e0.name)
|
||||
g.remove_edge(e0)
|
||||
|
||||
self.assertEqual(g.nodes_counts(), 2)
|
||||
self.assertEqual(g.edges_counts(), 0)
|
||||
@@ -73,7 +73,7 @@ class GraphTestCase(unittest.TestCase):
|
||||
|
||||
e0 = g.add_edge(n0, n1)
|
||||
|
||||
g.remove_node(n0.name)
|
||||
g.remove_node(n0)
|
||||
|
||||
self.assertEqual(g.nodes_counts(), 1)
|
||||
self.assertEqual(g.edges_counts(), 0)
|
||||
@@ -88,7 +88,7 @@ class GraphTestCase(unittest.TestCase):
|
||||
e0 = g.add_edge(n0, n1)
|
||||
e1 = g.add_edge(n1, n2)
|
||||
|
||||
g.remove_node(n1.name)
|
||||
g.remove_node(n1)
|
||||
|
||||
self.assertEqual(g.nodes_counts(), 2)
|
||||
self.assertEqual(g.edges_counts(), 0)
|
||||
|
||||
@@ -126,3 +126,11 @@ class River(object):
|
||||
|
||||
self._reachs.append(new)
|
||||
return new
|
||||
|
||||
def get_reach_by_geometry(self, geometry_reach):
|
||||
return next(
|
||||
filter(
|
||||
lambda r: r.geometry is geometry_reach,
|
||||
self._reachs
|
||||
)
|
||||
)
|
||||
@@ -378,6 +378,7 @@ class River(Graph, SQLSubModel):
|
||||
|
||||
def init_default(self):
|
||||
self.init_default_network()
|
||||
self.init_default_sediment()
|
||||
self.init_default_additional_files()
|
||||
|
||||
def init_default_network(self):
|
||||
@@ -386,6 +387,20 @@ class River(Graph, SQLSubModel):
|
||||
|
||||
e = self.add_edge(n1, n2)
|
||||
|
||||
def init_default_sediment(self):
|
||||
sediment = self._sediment_layers
|
||||
default = sediment.new(0)
|
||||
default_0 = default.new(0)
|
||||
|
||||
default.name = "default"
|
||||
default.comment = "Default sediment layers"
|
||||
|
||||
default_0.name = "L0"
|
||||
default_0.height = 1.0
|
||||
default_0.d50 = 0.002
|
||||
default_0.sigma = 1.0
|
||||
default_0.critical_constraint = 0.047
|
||||
|
||||
def init_default_additional_files(self):
|
||||
add_file = self._additional_files.new(0)
|
||||
add_file.name = "Pamhyr2 stamp file"
|
||||
|
||||
+17
-2
@@ -22,10 +22,19 @@ logger = logging.getLogger()
|
||||
|
||||
|
||||
class SavedStatus(object):
|
||||
def __init__(self):
|
||||
def __init__(self, version=0):
|
||||
super(SavedStatus, self).__init__()
|
||||
self._version = version
|
||||
self._saved = True
|
||||
|
||||
@property
|
||||
def version(self):
|
||||
return self._version
|
||||
|
||||
@version.setter
|
||||
def version(self, version):
|
||||
self._version = version
|
||||
|
||||
def is_saved(self):
|
||||
return self._saved
|
||||
|
||||
@@ -34,5 +43,11 @@ class SavedStatus(object):
|
||||
self._saved = True
|
||||
|
||||
def modified(self):
|
||||
# logger.debug("model status set as modified")
|
||||
if self._saved:
|
||||
self._version += 1
|
||||
|
||||
logger.debug(
|
||||
"STATUS: Model status set as modified " +
|
||||
f"at version {self._version}"
|
||||
)
|
||||
self._saved = False
|
||||
+36
-9
@@ -41,7 +41,7 @@ class Study(SQLModel):
|
||||
|
||||
def __init__(self, filename=None, init_new=True):
|
||||
# Metadata
|
||||
self._version = "0.0.9"
|
||||
self._version = "0.0.10"
|
||||
self.creation_date = datetime.now()
|
||||
self.last_modification_date = datetime.now()
|
||||
self.last_save_date = datetime.now()
|
||||
@@ -71,6 +71,8 @@ class Study(SQLModel):
|
||||
lst = [
|
||||
StudyNetworkReachChecker(),
|
||||
StudyGeometryChecker(),
|
||||
StudyInitialConditionsChecker(),
|
||||
StudyBoundaryConditionChecker(),
|
||||
# DummyOK(),
|
||||
# DummyWARNING(),
|
||||
# DummyERROR(),
|
||||
@@ -115,6 +117,9 @@ class Study(SQLModel):
|
||||
if ".backup" in self.filename:
|
||||
is_new = True
|
||||
|
||||
if not os.path.exists(self.filename):
|
||||
is_new = True
|
||||
|
||||
self._init_db_file(self.filename, is_new=is_new)
|
||||
self.commit()
|
||||
|
||||
@@ -207,7 +212,12 @@ class Study(SQLModel):
|
||||
def _create(self):
|
||||
# Info (metadata)
|
||||
self.execute(
|
||||
"CREATE TABLE info(key TEXT NOT NULL UNIQUE, value TEXT NOT NULL)")
|
||||
"INSERT INTO info VALUES ('study_release', '0')"
|
||||
)
|
||||
|
||||
self.execute(
|
||||
"CREATE TABLE info(key TEXT NOT NULL UNIQUE, value TEXT NOT NULL)"
|
||||
)
|
||||
self.execute(
|
||||
"INSERT INTO info VALUES ('version', " +
|
||||
f"'{self._db_format(self._version)}')",
|
||||
@@ -216,19 +226,20 @@ class Study(SQLModel):
|
||||
self.execute("INSERT INTO info VALUES ('name', '')")
|
||||
self.execute("INSERT INTO info VALUES ('description', '')")
|
||||
self.execute(
|
||||
f"INSERT INTO info VALUES ('time_system', '{self._time_system}')")
|
||||
f"INSERT INTO info VALUES ('time_system', '{self._time_system}')"
|
||||
)
|
||||
self.execute(
|
||||
f"INSERT INTO info VALUES ('date', " +
|
||||
"'{self._date.timestamp()}')"
|
||||
"INSERT INTO info VALUES ('date', " +
|
||||
f"'{self._date.timestamp()}')"
|
||||
)
|
||||
|
||||
self.execute(
|
||||
f"INSERT INTO info VALUES ('creation_date', " +
|
||||
"'{self.creation_time.timestamp()}')"
|
||||
"INSERT INTO info VALUES ('creation_date', " +
|
||||
f"'{self.creation_date.timestamp()}')"
|
||||
)
|
||||
self.execute(
|
||||
f"INSERT INTO info VALUES ('last_save_date', " +
|
||||
"'{self.last_save_time.timestamp()}')"
|
||||
"INSERT INTO info VALUES ('last_save_date', " +
|
||||
f"'{self.last_save_date.timestamp()}')"
|
||||
)
|
||||
|
||||
self._create_submodel()
|
||||
@@ -243,6 +254,12 @@ class Study(SQLModel):
|
||||
|
||||
logger.debug("Update database")
|
||||
|
||||
major, minor, release = version[0].split('.')
|
||||
if major == "0" and minor == "0" and int(release) < 10:
|
||||
self.execute(
|
||||
"INSERT INTO info VALUES ('study_release', '0')"
|
||||
)
|
||||
|
||||
if self._update_submodel(version[0]):
|
||||
self.execute(
|
||||
f"UPDATE info SET value='{self._version}' WHERE key='version'"
|
||||
@@ -256,6 +273,11 @@ class Study(SQLModel):
|
||||
def _load(cls, filename):
|
||||
new = cls(init_new=False, filename=filename)
|
||||
|
||||
version = new.execute(
|
||||
"SELECT value FROM info WHERE key='study_release'"
|
||||
)
|
||||
new.status.version = int(version[0])
|
||||
|
||||
# TODO: Load metadata
|
||||
new.name = new.execute("SELECT value FROM info WHERE key='name'")[0]
|
||||
new.description = new.execute(
|
||||
@@ -290,6 +312,11 @@ class Study(SQLModel):
|
||||
def _save(self, progress=None):
|
||||
progress = progress if progress is not None else lambda: None
|
||||
|
||||
self.execute(
|
||||
"INSERT INTO info VALUES ('study_release', " +
|
||||
f"'{self.status.version}')"
|
||||
)
|
||||
|
||||
self.execute(
|
||||
f"UPDATE info SET " +
|
||||
f"value='{self._db_format(self.name)}' WHERE key='name'"
|
||||
|
||||
+70
-1
@@ -23,7 +23,20 @@ from enum import Flag, auto
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
class Modules(Flag):
|
||||
class IterableFlag(Flag):
|
||||
def __iter__(self):
|
||||
all = filter(
|
||||
lambda v: v in self,
|
||||
type(self).values()
|
||||
)
|
||||
return all
|
||||
|
||||
@classmethod
|
||||
def values(cls):
|
||||
raise Exception("Not implemented yet")
|
||||
|
||||
|
||||
class Modules(IterableFlag):
|
||||
NONE = 0
|
||||
|
||||
# General
|
||||
@@ -48,6 +61,24 @@ class Modules(Flag):
|
||||
# Display
|
||||
WINDOW_LIST = auto()
|
||||
|
||||
@classmethod
|
||||
def values(cls):
|
||||
return [
|
||||
cls.STUDY, cls.CONFIG,
|
||||
cls.NETWORK,
|
||||
cls.GEOMETRY,
|
||||
cls.BOUNDARY_CONDITION,
|
||||
cls.LATERAL_CONTRIBUTION,
|
||||
cls.FRICTION,
|
||||
cls.INITIAL_CONDITION,
|
||||
cls.HYDRAULIC_STRUCTURES,
|
||||
cls.RESERVOIR,
|
||||
cls.SEDIMENT_LAYER,
|
||||
cls.ADDITIONAL_FILES,
|
||||
cls.RESULTS,
|
||||
cls.WINDOW_LIST,
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def all(cls):
|
||||
return ~cls.NONE
|
||||
@@ -93,3 +124,41 @@ class Modules(Flag):
|
||||
cls.RESERVOIR: "Reservoir",
|
||||
cls.SEDIMENT_LAYER: "Sediment layer",
|
||||
}
|
||||
|
||||
def impact(self):
|
||||
res = Modules(0)
|
||||
for mod in self:
|
||||
if mod in _impact:
|
||||
for i in _impact[mod]:
|
||||
res |= i
|
||||
return res
|
||||
|
||||
def impact_set(self):
|
||||
res = []
|
||||
for mod in self:
|
||||
if mod in _impact:
|
||||
res += _impact[mod]
|
||||
|
||||
return set(res)
|
||||
|
||||
|
||||
_impact = {
|
||||
Modules.NETWORK: [
|
||||
Modules.GEOMETRY, Modules.BOUNDARY_CONDITION,
|
||||
Modules.LATERAL_CONTRIBUTION, Modules.FRICTION,
|
||||
Modules.RESERVOIR, Modules.SEDIMENT_LAYER,
|
||||
],
|
||||
Modules.GEOMETRY: [
|
||||
Modules.LATERAL_CONTRIBUTION, Modules.FRICTION,
|
||||
Modules.INITIAL_CONDITION, Modules.SEDIMENT_LAYER,
|
||||
],
|
||||
Modules.BOUNDARY_CONDITION: [
|
||||
Modules.SEDIMENT_LAYER
|
||||
],
|
||||
Modules.LATERAL_CONTRIBUTION: [],
|
||||
Modules.FRICTION: [],
|
||||
Modules.INITIAL_CONDITION: [],
|
||||
Modules.HYDRAULIC_STRUCTURES: [],
|
||||
Modules.RESERVOIR: [],
|
||||
Modules.SEDIMENT_LAYER: [],
|
||||
}
|
||||
+5
-1
@@ -492,6 +492,9 @@ class Mage(CommandLineSolver):
|
||||
if not hs.input_reach.is_enable():
|
||||
continue
|
||||
|
||||
if not hs.enabled:
|
||||
continue
|
||||
|
||||
if hs.input_kp is None:
|
||||
continue
|
||||
|
||||
@@ -507,6 +510,7 @@ class Mage(CommandLineSolver):
|
||||
|
||||
def _export_SIN_bhs(self, study, sin_dict, hs, f):
|
||||
for bhs in hs.basic_structures:
|
||||
if bhs.enabled:
|
||||
reach_id = study.river.get_edge_id(hs.input_reach) + 1
|
||||
param_str = ' '.join(
|
||||
[
|
||||
@@ -799,7 +803,7 @@ class Mage8(Mage):
|
||||
value = "O" if value.lower() == "y" else "N"
|
||||
|
||||
if name == "init_internal":
|
||||
value = ("p" if value.lower() in ["y", "yes", "true"]
|
||||
value = ("p" if value.lower() in ["y", "yes", "true", "o"]
|
||||
else "")
|
||||
|
||||
logger.debug(
|
||||
|
||||
@@ -0,0 +1,441 @@
|
||||
# RubarBE.py -- Pamhyr
|
||||
# Copyright (C) 2024 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import logging
|
||||
import numpy as np
|
||||
|
||||
from tools import timer, trace, old_pamhyr_date_to_timestamp
|
||||
|
||||
from Solver.CommandLine import CommandLineSolver
|
||||
|
||||
from Model.Results.Results import Results
|
||||
from Model.Results.River.River import River, Reach, Profile
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
class RubarBE(CommandLineSolver):
|
||||
_type = "rubarbe"
|
||||
|
||||
def __init__(self, name):
|
||||
super(RubarBE, self).__init__(name)
|
||||
|
||||
self._type = "rubarbe"
|
||||
|
||||
self._cmd_input = ""
|
||||
self._cmd_solver = "@path @input -o @output"
|
||||
self._cmd_output = ""
|
||||
|
||||
@classmethod
|
||||
def default_parameters(cls):
|
||||
lst = super(RubarBE, cls).default_parameters()
|
||||
|
||||
lst += [
|
||||
("rubarbe_cfl", "0.50000E+00"),
|
||||
("rubarbe_condam", "1"),
|
||||
("rubarbe_condav", "3"),
|
||||
("rubarbe_regime", "0"),
|
||||
("rubarbe_iodev", "n"),
|
||||
("rubarbe_iodebord", ""),
|
||||
("rubarbe_iostockage", ""),
|
||||
("rubarbe_iopdt", "y"),
|
||||
("rubarbe_iovis", "n"),
|
||||
("rubarbe_rep", "n"),
|
||||
("rubarbe_tinit", "000:00:00:00"),
|
||||
("rubarbe_tmax", "999:99:99:00"),
|
||||
("rubarbe_tiopdt", "000:00:00:00"),
|
||||
("rubarbe_dt", "3000.0"),
|
||||
("rubarbe_ts", "999:99:99:00"),
|
||||
("rubarbe_dtsauv", "999:99:99:00"),
|
||||
("rubarbe_psave", "999:99:99:00"),
|
||||
("rubarbe_fdeb1", "1"),
|
||||
("rubarbe_fdeb2", "10"),
|
||||
("rubarbe_fdeb3", "100"),
|
||||
("rubarbe_tf_1", "y"),
|
||||
("rubarbe_tf_2", "y"),
|
||||
("rubarbe_tf_3", "y"),
|
||||
("rubarbe_tf_4", "y"),
|
||||
("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"),
|
||||
# Sediment parameters
|
||||
("rubarbe_sediment_ros", "2650.0"),
|
||||
("rubarbe_sediment_por", "0.4"),
|
||||
("rubarbe_sediment_dcharg", "0.0"),
|
||||
("rubarbe_sediment_halfa", "1.0"),
|
||||
("rubarbe_sediment_mult_1", "1.0"),
|
||||
("rubarbe_sediment_mult_2", ""),
|
||||
("rubarbe_sediment_mult_3", ""),
|
||||
("rubarbe_sediment_mult_4", ""),
|
||||
("rubarbe_sediment_mult_5", ""),
|
||||
("rubarbe_sediment_visc", "0.047"),
|
||||
("rubarbe_sediment_opts", "6"),
|
||||
("rubarbe_sediment_odchar", "0"),
|
||||
("rubarbe_sediment_unisol", "1"),
|
||||
("rubarbe_sediment_typdef", "3"),
|
||||
("rubarbe_sediment_depot", "2"),
|
||||
("rubarbe_sediment_choixc", "2"),
|
||||
("rubarbe_sediment_option", "2"),
|
||||
("rubarbe_sediment_capsol", "1"),
|
||||
("rubarbe_sediment_bmiu", "0.85"),
|
||||
("rubarbe_sediment_demix", "0"),
|
||||
("rubarbe_sediment_defond", "1"),
|
||||
("rubarbe_sediment_varcons", "1"),
|
||||
("rubarbe_sediment_dchard", "0.0"),
|
||||
("rubarbe_sediment_dchars", "0.0"),
|
||||
]
|
||||
|
||||
return lst
|
||||
|
||||
@classmethod
|
||||
def checkers(cls):
|
||||
lst = [
|
||||
]
|
||||
|
||||
return lst
|
||||
|
||||
##########
|
||||
# Export #
|
||||
##########
|
||||
|
||||
def cmd_args(self, study):
|
||||
lst = super(RubarBE, self).cmd_args(study)
|
||||
|
||||
return lst
|
||||
|
||||
def input_param(self):
|
||||
name = self._study.name
|
||||
return f"{name}.REP"
|
||||
|
||||
def output_param(self):
|
||||
name = self._study.name
|
||||
return f"{name}.BIN"
|
||||
|
||||
def log_file(self):
|
||||
name = self._study.name
|
||||
return f"{name}.TRA"
|
||||
|
||||
def export(self, study, repertory, qlog=None):
|
||||
self._study = study
|
||||
name = study.name.replace(" ", "_")
|
||||
|
||||
self._export_donnee(study, repertory, qlog, name=name)
|
||||
self._export_ts(study, repertory, qlog, name=name)
|
||||
self._export_geomac_i(study, repertory, qlog, name=name)
|
||||
self._export_mail(study, repertory, qlog, name=name)
|
||||
self._export_tps(study, repertory, qlog, name=name)
|
||||
self._export_stricklers(study, repertory, qlog, name=name)
|
||||
|
||||
def _export_donnee(self, study, repertory, qlog, name="0"):
|
||||
if qlog is not None:
|
||||
qlog.put("Export DONNEE file")
|
||||
|
||||
with open(
|
||||
os.path.join(
|
||||
repertory, f"donnee.{name}"
|
||||
), "w+"
|
||||
) as f:
|
||||
params = filter(
|
||||
lambda p: "rubarbe_sediment_" not in p.name,
|
||||
study.river.get_params(self._type).parameters
|
||||
)
|
||||
|
||||
it = iter(params)
|
||||
|
||||
line = 0
|
||||
while line < 29:
|
||||
param = next(it)
|
||||
name = param.name
|
||||
value = param.value
|
||||
|
||||
if value != "":
|
||||
# Value format
|
||||
if value.count(':') == 3:
|
||||
value = old_pamhyr_date_to_timestamp(value)
|
||||
value = f"{value:>12.5e}".upper()
|
||||
|
||||
if value.count('.') == 1:
|
||||
value = f"{float(value):>12.5e}".upper()
|
||||
|
||||
if value == "y" or value == "n":
|
||||
value = "O" if value == "y" else "N"
|
||||
|
||||
# Write value
|
||||
f.write(f"{name:<50}{value}")
|
||||
|
||||
# Add values of 'rubarbe_iodebord' and
|
||||
# 'rubarbe_iostockage'
|
||||
if name == "rubarbe_iodev":
|
||||
v2 = next(it).value
|
||||
v3 = next(it).value
|
||||
|
||||
f.write(f"{v2}{v3}")
|
||||
|
||||
# New line
|
||||
f.write(f"\n")
|
||||
|
||||
line += 1
|
||||
|
||||
def _export_ts(self, study, repertory, qlog, name="0"):
|
||||
if qlog is not None:
|
||||
qlog.put("Export TS file")
|
||||
|
||||
with open(
|
||||
os.path.join(
|
||||
repertory, f"ts.{name}"
|
||||
), "w+"
|
||||
) as f:
|
||||
def float_format(string):
|
||||
if "." in string:
|
||||
return f"{float(string):>10.0f}"
|
||||
return ""
|
||||
|
||||
params = filter(
|
||||
lambda p: "rubarbe_sediment_" in p.name,
|
||||
study.river.get_params(self.type).parameters
|
||||
)
|
||||
it = iter(params)
|
||||
|
||||
line = 0
|
||||
while line < 20:
|
||||
param = next(it)
|
||||
name = param.name
|
||||
value = param.value
|
||||
|
||||
if value != "":
|
||||
# Value format
|
||||
if value.count('.') == 1:
|
||||
value = f"{float_format(value)}"
|
||||
else:
|
||||
value = f"{value:>10}"
|
||||
|
||||
# Write value
|
||||
f.write(f"{name:<50}{value}")
|
||||
|
||||
# Add values of 'rubarbe_iodebord' and
|
||||
# 'rubarbe_iostockage'
|
||||
if name == "rubarbe_sediment_mult_1":
|
||||
m2 = f"{float_format(next(it).value)}"
|
||||
m3 = f"{float_format(next(it).value)}"
|
||||
m4 = f"{float_format(next(it).value)}"
|
||||
m5 = f"{float_format(next(it).value)}"
|
||||
|
||||
f.write(f"{m2}{m3}{m4}{m5}")
|
||||
|
||||
# New line
|
||||
f.write(f"\n")
|
||||
|
||||
line += 1
|
||||
|
||||
def _export_geomac_i(self, study, repertory, qlog, name="0"):
|
||||
if qlog is not None:
|
||||
qlog.put("Export GEOMAC-i file")
|
||||
|
||||
with open(
|
||||
os.path.join(
|
||||
repertory, f"geomac-i.{name}"
|
||||
), "w+"
|
||||
) as f:
|
||||
for edge in study.river.enable_edges():
|
||||
reach = edge.reach
|
||||
n_profiles = len(reach)
|
||||
time = 0.0
|
||||
|
||||
f.write(f"{n_profiles:>5} {time:>11.3f}\n")
|
||||
|
||||
ind = 1
|
||||
for profile in reach.profiles:
|
||||
kp = profile.kp
|
||||
n_points = len(profile)
|
||||
|
||||
f.write(f"{ind:>4} {kp:>11.3f} {n_points:>4}\n")
|
||||
|
||||
for point in profile.points:
|
||||
label = point.name.lower()
|
||||
if label != "":
|
||||
if label[0] == "r":
|
||||
label = label[1].upper()
|
||||
else:
|
||||
label = lable[0]
|
||||
|
||||
y = point.y
|
||||
z = point.z
|
||||
dcs = 0.001
|
||||
scs = 1.0
|
||||
tmcs = 0.0
|
||||
|
||||
f.write(
|
||||
f"{label} {y:>11.5f}" +
|
||||
f"{z:>13.5f}{dcs:>15.10f}" +
|
||||
f"{scs:>15.10f}{tmcs:>15.5f}" +
|
||||
"\n"
|
||||
)
|
||||
|
||||
ind += 1
|
||||
|
||||
def _export_mail(self, study, repertory, qlog, name="0"):
|
||||
if qlog is not None:
|
||||
qlog.put("Export MAIL file")
|
||||
|
||||
with open(
|
||||
os.path.join(
|
||||
repertory, f"mail.{name}"
|
||||
), "w+"
|
||||
) as f:
|
||||
for edge in study.river.enable_edges():
|
||||
reach = edge.reach
|
||||
lm = len(reach) + 1
|
||||
f.write(f"{lm:>13}\n")
|
||||
|
||||
for mails in [reach.inter_profiles_kp(),
|
||||
reach.get_kp()]:
|
||||
ind = 0
|
||||
for mail in mails:
|
||||
f.write(f"{mail:15.3f}")
|
||||
|
||||
ind += 1
|
||||
if ind % 3 == 0:
|
||||
f.write("\n")
|
||||
|
||||
if ind % 3 != 0:
|
||||
f.write("\n")
|
||||
|
||||
def _export_stricklers(self, study, repertory, qlog, name="0"):
|
||||
self._export_frot(study, repertory, qlog, name=name, version="")
|
||||
self._export_frot(study, repertory, qlog, name=name, version="2")
|
||||
|
||||
def _export_frot(self, study, repertory, qlog, name="0", version=""):
|
||||
if qlog is not None:
|
||||
qlog.put(f"Export FROT{version} file")
|
||||
|
||||
with open(
|
||||
os.path.join(
|
||||
repertory, f"frot{version}.{name}"
|
||||
), "w+"
|
||||
) as f:
|
||||
for edge in study.river.enable_edges():
|
||||
reach = edge.reach
|
||||
lm = len(reach) + 1
|
||||
f.write(f"{lm:>6}\n")
|
||||
|
||||
def get_stricklers_from_kp(kp):
|
||||
return next(
|
||||
map(
|
||||
lambda s: (
|
||||
s.begin_strickler.medium if version == "2"
|
||||
else s.begin_strickler.minor
|
||||
),
|
||||
filter(
|
||||
lambda f: kp in f,
|
||||
edge.frictions.lst
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
ind = 1
|
||||
for mail in edge.reach.inter_profiles_kp():
|
||||
coef = get_stricklers_from_kp(mail)
|
||||
|
||||
f.write(f"{ind:>6} {coef:>12.5f}")
|
||||
|
||||
ind += 1
|
||||
f.write("\n")
|
||||
|
||||
def _export_tps(self, study, repertory, qlog, name="0"):
|
||||
if qlog is not None:
|
||||
qlog.put("Export TPS file")
|
||||
|
||||
with open(
|
||||
os.path.join(
|
||||
repertory, f"tps.{name}"
|
||||
), "w+"
|
||||
) as f:
|
||||
for edge in study.river.enable_edges():
|
||||
reach = edge.reach
|
||||
|
||||
f.write(f"0.0\n")
|
||||
|
||||
ics = study.river.initial_conditions.get(edge)
|
||||
data = self._export_tps_init_data(ics)
|
||||
|
||||
profiles = reach.profiles
|
||||
first = profiles[0]
|
||||
last = profiles[-1]
|
||||
|
||||
if first.kp not in data or last.kp not in data:
|
||||
logger.error(
|
||||
"Study initial condition is not fully defined"
|
||||
)
|
||||
return
|
||||
|
||||
f_h_s = self._export_tps_profile_height_speed(first, data)
|
||||
l_h_s = self._export_tps_profile_height_speed(last, data)
|
||||
|
||||
# First mail
|
||||
f.write(f"{1:>5} {f_h_s[0]} {f_h_s[1]}")
|
||||
|
||||
ind = 2
|
||||
it = iter(profiles)
|
||||
prev = next(it)
|
||||
|
||||
prev_h, prev_s = f_h_s
|
||||
for profile in it:
|
||||
if profile.kp not in data:
|
||||
ind += 1
|
||||
continue
|
||||
|
||||
cur_h, cur_s = self._export_tps_profile_height_speed(
|
||||
profile, data
|
||||
)
|
||||
|
||||
# Mean of height and speed
|
||||
h = (prev_h + cur_h) / 2
|
||||
s = (prev_s + cur_s) / 2
|
||||
|
||||
f.write(f"{ind:>5} {h} {s}\n")
|
||||
|
||||
prev_h, prev_s = cur_h, cur_s
|
||||
ind += 1
|
||||
|
||||
# Last mail
|
||||
f.write(f"{ind:>5} {f_h_s[0]} {f_h_s[1]}")
|
||||
|
||||
def _export_tps_init_data(self, ics):
|
||||
data = {}
|
||||
|
||||
for d in ics.data:
|
||||
data[d['kp']] = (
|
||||
d['elevation'],
|
||||
d['discharge'],
|
||||
)
|
||||
|
||||
return data
|
||||
|
||||
def _export_tps_profile_height_speed(self, profile, data):
|
||||
z = data[profile.kp][0]
|
||||
q = data[profile.kp][1]
|
||||
|
||||
height = z - profile.z_min()
|
||||
speed = profile.speed(q, z)
|
||||
|
||||
return height, speed
|
||||
@@ -22,6 +22,7 @@ from Solver.GenericSolver import GenericSolver
|
||||
from Solver.Mage import (
|
||||
Mage7, Mage8, MageFake7,
|
||||
)
|
||||
from Solver.RubarBE import RubarBE
|
||||
|
||||
_translate = QCoreApplication.translate
|
||||
|
||||
@@ -30,6 +31,7 @@ solver_long_name = {
|
||||
# "mage7": "Mage v7",
|
||||
"mage8": "Mage v8",
|
||||
# "mage_fake7": "Mage fake v7",
|
||||
# "rubarbe": "RubarBE",
|
||||
}
|
||||
|
||||
solver_type_list = {
|
||||
@@ -37,4 +39,5 @@ solver_type_list = {
|
||||
# "mage7": Mage7,
|
||||
"mage8": Mage8,
|
||||
# "mage_fake7": MageFake7,
|
||||
# "rubarbe": RubarBE,
|
||||
}
|
||||
@@ -67,5 +67,5 @@ class AboutWindow(PamhyrDialog):
|
||||
label = f"\n - {author}" + label
|
||||
except StopIteration:
|
||||
label = _translate("About", "Contributors: ") + label
|
||||
label = "Copyright © 2023-2024 INRAE\n" + label
|
||||
label = "Copyright © 2022-2024 INRAE\n" + label
|
||||
self.set_label_text("label_copyright", label)
|
||||
@@ -99,7 +99,7 @@ class TableModel(PamhyrTableModel):
|
||||
logger.info(e)
|
||||
logger.debug(traceback.format_exc())
|
||||
|
||||
self.dataChanged.emit(index, index)
|
||||
self.update()
|
||||
return True
|
||||
|
||||
def add(self, row, parent=QModelIndex()):
|
||||
@@ -112,7 +112,7 @@ class TableModel(PamhyrTableModel):
|
||||
)
|
||||
|
||||
self.endInsertRows()
|
||||
self.layoutChanged.emit()
|
||||
self.update()
|
||||
|
||||
def delete(self, rows, parent=QModelIndex()):
|
||||
self.beginRemoveRows(parent, rows[0], rows[-1])
|
||||
@@ -135,7 +135,7 @@ class TableModel(PamhyrTableModel):
|
||||
)
|
||||
|
||||
self.layoutAboutToBeChanged.emit()
|
||||
self.layoutChanged.emit()
|
||||
self.update()
|
||||
|
||||
def move_up(self, row, parent=QModelIndex()):
|
||||
if row <= 0:
|
||||
@@ -152,7 +152,7 @@ class TableModel(PamhyrTableModel):
|
||||
)
|
||||
|
||||
self.endMoveRows()
|
||||
self.layoutChanged.emit()
|
||||
self.update()
|
||||
|
||||
def move_down(self, index, parent=QModelIndex()):
|
||||
if row > len(self._data):
|
||||
@@ -169,7 +169,7 @@ class TableModel(PamhyrTableModel):
|
||||
)
|
||||
|
||||
self.endMoveRows()
|
||||
self.layoutChanged.emit()
|
||||
self.update()
|
||||
|
||||
def paste(self, row, header, data):
|
||||
if len(data) == 0:
|
||||
@@ -190,4 +190,13 @@ class TableModel(PamhyrTableModel):
|
||||
)
|
||||
|
||||
self.layoutAboutToBeChanged.emit()
|
||||
self.update()
|
||||
|
||||
def auto_sort(self):
|
||||
self.layoutAboutToBeChanged.emit()
|
||||
self._data.sort(key=lambda x: x[0])
|
||||
self.layoutAboutToBeChanged.emit()
|
||||
|
||||
def update(self):
|
||||
# self.auto_sort()
|
||||
self.layoutChanged.emit()
|
||||
@@ -195,6 +195,7 @@ class EditBoundaryConditionWindow(PamhyrWindow):
|
||||
self.find(QAction, "action_sort").triggered.connect(self.sort)
|
||||
|
||||
self._table.dataChanged.connect(self.update)
|
||||
self._table.layoutChanged.connect(self.update)
|
||||
|
||||
if self._is_solid:
|
||||
self._d50sigma.d50Changed.connect(self.d50_changed)
|
||||
|
||||
@@ -48,7 +48,7 @@ class BCTranslate(MainTranslate):
|
||||
|
||||
self._sub_dict["long_types"] = {
|
||||
"ND": self._dict["not_defined"],
|
||||
"PC": _translate("BoundaryCondition", "Ponctual contribution"),
|
||||
"PC": _translate("BoundaryCondition", "Point sources"),
|
||||
"TZ": _translate("BoundaryCondition", "Z(t)"),
|
||||
"TD": _translate("BoundaryCondition", "Q(t)"),
|
||||
"ZD": _translate("BoundaryCondition", "Q(Z)"),
|
||||
|
||||
@@ -46,7 +46,7 @@ class Worker(QObject):
|
||||
self.signalStatus.emit(checker.name)
|
||||
|
||||
# Run checker
|
||||
checker._run(self._study)
|
||||
checker.run(self._study)
|
||||
|
||||
self.signalStatus.emit("progress")
|
||||
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
# PlotKPC.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from PyQt5.QtCore import QCoreApplication
|
||||
from View.HydraulicStructures.PlotKPC import PlotKPC
|
||||
|
||||
_translate = QCoreApplication.translate
|
||||
|
||||
|
||||
class PlotKPZ(PlotKPC):
|
||||
def __init__(self, canvas=None, trad=None, toolbar=None,
|
||||
river=None, reach=None, profile=None,
|
||||
parent=None):
|
||||
super(PlotKPC, self).__init__(
|
||||
canvas=canvas,
|
||||
trad=trad,
|
||||
data=river,
|
||||
toolbar=toolbar,
|
||||
parent=parent
|
||||
)
|
||||
|
||||
self._current_reach = reach
|
||||
self._current_profile = profile
|
||||
|
||||
self.label_x = self._trad["unit_kp"]
|
||||
self.label_y = self._trad["unit_elevation"]
|
||||
|
||||
self._isometric_axis = False
|
||||
|
||||
self._auto_relim_update = True
|
||||
self._autoscale_update = True
|
||||
self.parent = parent
|
||||
|
||||
def onpick(self, event):
|
||||
return
|
||||
@@ -42,7 +42,7 @@ class PlotStricklers(PamhyrPlot):
|
||||
)
|
||||
|
||||
self.label_x = self._trad["kp"]
|
||||
self.label_y = self._trad["stricklers"]
|
||||
self.label_y = self._trad["strickler_plot"]
|
||||
|
||||
self.line_kp_elevation = [None, None]
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ from View.Frictions.Table import (
|
||||
)
|
||||
|
||||
from View.Tools.Plot.PamhyrCanvas import MplCanvas
|
||||
from View.Geometry.PlotKPZ import PlotKPZ
|
||||
from View.Frictions.PlotKPZ import PlotKPZ
|
||||
from View.Frictions.PlotStricklers import PlotStricklers
|
||||
from View.Frictions.translate import FrictionsTranslate
|
||||
|
||||
@@ -135,9 +135,11 @@ class FrictionsWindow(PamhyrWindow):
|
||||
|
||||
self.plot = PlotKPZ(
|
||||
canvas=self.canvas,
|
||||
data=self._reach.reach,
|
||||
reach=self._reach,
|
||||
river=self._study.river,
|
||||
trad=self._trad,
|
||||
toolbar=None,
|
||||
parent=self
|
||||
)
|
||||
self.plot.draw()
|
||||
|
||||
|
||||
@@ -28,6 +28,9 @@ class FrictionsTranslate(MainTranslate):
|
||||
super(FrictionsTranslate, self).__init__()
|
||||
|
||||
self._dict["kp"] = self._dict["unit_kp"]
|
||||
self._dict["strickler_plot"] = _translate(
|
||||
"Frictions", "Strickler ($m^{1/3}/s$)"
|
||||
)
|
||||
self._dict["stricklers"] = _translate(
|
||||
"Frictions", "Stricklers"
|
||||
)
|
||||
@@ -39,8 +42,8 @@ class FrictionsTranslate(MainTranslate):
|
||||
self._sub_dict["table_headers"] = {
|
||||
# "name": self._dict["name"],
|
||||
# "edge": self._dict["reach"],
|
||||
"begin_kp": _translate("Frictions", "Begin kp (m)"),
|
||||
"end_kp": _translate("Frictions", "End kp (m)"),
|
||||
"begin_strickler": _translate("Frictions", "Begin strickler"),
|
||||
"end_strickler": _translate("Frictions", "End strickler"),
|
||||
"begin_kp": _translate("Frictions", "Start (m)"),
|
||||
"end_kp": _translate("Frictions", "End (m)"),
|
||||
"begin_strickler": _translate("Frictions", "Start coefficient"),
|
||||
"end_strickler": _translate("Frictions", "End coefficient"),
|
||||
}
|
||||
@@ -58,6 +58,7 @@ class MeshingDialog(PamhyrDialog):
|
||||
self._end_cs = -1
|
||||
self._begin_dir = "un"
|
||||
self._end_dir = "np"
|
||||
self._origin = self._reach.profile(0)
|
||||
|
||||
self._init_default_values_profiles()
|
||||
self._init_default_values_guidelines()
|
||||
@@ -67,17 +68,6 @@ class MeshingDialog(PamhyrDialog):
|
||||
self._space_step
|
||||
)
|
||||
|
||||
lm_dict = self._trad.get_dict("lm_dict")
|
||||
self.combobox_add_items(
|
||||
"comboBox_lm", list(
|
||||
map(
|
||||
lambda x: lm_dict[x],
|
||||
["1", "2", "3"]
|
||||
)
|
||||
)
|
||||
)
|
||||
self.set_combobox_text("comboBox_lm", self._lm)
|
||||
|
||||
if self._linear:
|
||||
self.set_radio_button("radioButton_linear", True)
|
||||
else:
|
||||
@@ -118,8 +108,8 @@ class MeshingDialog(PamhyrDialog):
|
||||
gl, _ = self._reach.compute_guidelines()
|
||||
gl = list(gl)
|
||||
|
||||
bgl = ['un'] + gl
|
||||
egl = gl + ['np']
|
||||
bgl = ['un'] + gl + ['np']
|
||||
egl = ['un'] + gl + ['np']
|
||||
|
||||
self.combobox_add_items("comboBox_begin_gl", bgl)
|
||||
self.combobox_add_items("comboBox_end_gl", egl)
|
||||
@@ -135,21 +125,17 @@ class MeshingDialog(PamhyrDialog):
|
||||
def lplan(self):
|
||||
return self._lplan
|
||||
|
||||
@property
|
||||
def lm(self):
|
||||
return int(self._lm)
|
||||
|
||||
@property
|
||||
def linear(self):
|
||||
return self._linear
|
||||
|
||||
@property
|
||||
def begin_cs(self):
|
||||
return self._begin_cs + 1
|
||||
return self._begin_cs
|
||||
|
||||
@property
|
||||
def end_cs(self):
|
||||
return self._end_cs + 1
|
||||
return self._end_cs
|
||||
|
||||
@property
|
||||
def begin_dir(self):
|
||||
@@ -174,9 +160,6 @@ class MeshingDialog(PamhyrDialog):
|
||||
self._begin_dir = self.get_combobox_text("comboBox_begin_gl")
|
||||
self._end_dir = self.get_combobox_text("comboBox_end_gl")
|
||||
|
||||
r_lm_dict = self._trad.get_dict("r_lm_dict")
|
||||
self._lm = r_lm_dict[self.get_combobox_text("comboBox_lm")]
|
||||
|
||||
super().accept()
|
||||
|
||||
def reject(self):
|
||||
|
||||
@@ -98,6 +98,7 @@ class PlotAC(PamhyrPlot):
|
||||
label=self.label_next_plot_selected,
|
||||
color=self.color_plot_next, lw=1.6, linestyle='--'
|
||||
)
|
||||
self.canvas.axes.set_visible(False)
|
||||
|
||||
def draw_gl(self):
|
||||
if self._current_data_update:
|
||||
@@ -106,12 +107,16 @@ class PlotAC(PamhyrPlot):
|
||||
profile_id = 0
|
||||
|
||||
profile = self.data.profile(profile_id)
|
||||
if profile is None:
|
||||
return
|
||||
|
||||
station = profile.get_station()
|
||||
elevation = profile.z()
|
||||
gl = profile.names()
|
||||
|
||||
self.annotation = []
|
||||
self.complete_gl, self.incomplete_gl = self.data.compute_guidelines()
|
||||
|
||||
lcomplete = list(self.complete_gl)
|
||||
lincomplete = list(self.incomplete_gl)
|
||||
|
||||
@@ -129,7 +134,7 @@ class PlotAC(PamhyrPlot):
|
||||
|
||||
if txt.strip() in self.complete_gl:
|
||||
color = self.color_complete_gl[
|
||||
lcomplete.index(txt)
|
||||
lcomplete.index(txt) % len(self.color_complete_gl)
|
||||
]
|
||||
else:
|
||||
color = self.color_incomplete_gl[
|
||||
@@ -160,6 +165,9 @@ class PlotAC(PamhyrPlot):
|
||||
self.draw()
|
||||
return
|
||||
|
||||
if self.data is None:
|
||||
return
|
||||
|
||||
self.update_current()
|
||||
self.update_gl()
|
||||
|
||||
@@ -171,6 +179,10 @@ class PlotAC(PamhyrPlot):
|
||||
previous_id = profile_id - 1
|
||||
next_id = profile_id + 1
|
||||
|
||||
self.previous_plot_selected.set_visible(False)
|
||||
self.plot_selected.set_visible(False)
|
||||
self.next_plot_selected.set_visible(False)
|
||||
|
||||
self.previous_plot_selected.set_data([], [])
|
||||
self.plot_selected.set_data([], [])
|
||||
self.next_plot_selected.set_data([], [])
|
||||
@@ -180,18 +192,23 @@ class PlotAC(PamhyrPlot):
|
||||
self.data.profile(previous_id).get_station(),
|
||||
self.data.profile(previous_id).z()
|
||||
)
|
||||
self.previous_plot_selected.set_visible(True)
|
||||
|
||||
if 0 <= profile_id < self.data.number_profiles:
|
||||
self.plot_selected.set_data(
|
||||
self.data.profile(profile_id).get_station(),
|
||||
self.data.profile(profile_id).z()
|
||||
)
|
||||
self.plot_selected.set_visible(True)
|
||||
|
||||
if 0 <= next_id < self.data.number_profiles:
|
||||
self.next_plot_selected.set_data(
|
||||
self.data.profile(next_id).get_station(),
|
||||
self.data.profile(next_id).z()
|
||||
)
|
||||
self.next_plot_selected.set_visible(True)
|
||||
|
||||
self.canvas.axes.set_visible(True)
|
||||
|
||||
def update_full(self):
|
||||
self.draw()
|
||||
|
||||
+220
-117
@@ -20,18 +20,27 @@ import logging
|
||||
|
||||
from tools import timer
|
||||
from View.Tools.PamhyrPlot import PamhyrPlot
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
from PyQt5.QtCore import (
|
||||
Qt, QItemSelectionModel,
|
||||
QItemSelection, QItemSelectionRange,
|
||||
)
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
class PlotKPZ(PamhyrPlot):
|
||||
def __init__(self, canvas=None, trad=None, data=None, toolbar=None,
|
||||
parent=None):
|
||||
def __init__(self, canvas=None, trad=None,
|
||||
study=None, data=None, toolbar=None,
|
||||
table=None, parent=None):
|
||||
self._study = study
|
||||
|
||||
super(PlotKPZ, self).__init__(
|
||||
canvas=canvas,
|
||||
trad=trad,
|
||||
data=data,
|
||||
toolbar=toolbar,
|
||||
table=table,
|
||||
parent=parent
|
||||
)
|
||||
|
||||
@@ -48,6 +57,111 @@ class PlotKPZ(PamhyrPlot):
|
||||
self.before_plot_selected = None
|
||||
self.plot_selected = None
|
||||
self.after_plot_selected = None
|
||||
self.parent = parent
|
||||
self._table = table
|
||||
self._colors = []
|
||||
|
||||
def onpick(self, event):
|
||||
if event.mouseevent.inaxes != self.canvas.axes:
|
||||
return
|
||||
if event.mouseevent.button.value != 1:
|
||||
return
|
||||
|
||||
modifiers = QApplication.keyboardModifiers()
|
||||
if modifiers not in [Qt.ControlModifier,
|
||||
Qt.NoModifier,
|
||||
Qt.ShiftModifier]:
|
||||
return
|
||||
|
||||
ind, point = self._closest_kp(event)
|
||||
if self.parent._table is None:
|
||||
return
|
||||
self.parent._table.blockSignals(True)
|
||||
if modifiers == Qt.ControlModifier:
|
||||
rows = list(
|
||||
set(
|
||||
(i.row() for i in self.parent.tableView.selectedIndexes())
|
||||
)
|
||||
)
|
||||
if ind in rows:
|
||||
rows.remove(ind)
|
||||
else:
|
||||
rows.append(ind)
|
||||
self._select_in_table(rows)
|
||||
elif modifiers == Qt.ShiftModifier:
|
||||
rows = sorted(list(
|
||||
set(
|
||||
(i.row() for i in self.parent.tableView.selectedIndexes())
|
||||
)
|
||||
))
|
||||
if len(rows) > 0:
|
||||
i1 = min(rows[0], rows[-1], ind)
|
||||
i2 = max(rows[0], rows[-1], ind)
|
||||
else:
|
||||
i1 = ind
|
||||
i2 = ind
|
||||
self._select_range_in_table(i1, i2)
|
||||
else:
|
||||
self._select_in_table([ind])
|
||||
self.parent._table.blockSignals(False)
|
||||
|
||||
return
|
||||
|
||||
def _closest_kp(self, event):
|
||||
|
||||
s = event.artist.get_segments()
|
||||
x = [i[0, 0] for i in s]
|
||||
mx = event.mouseevent.xdata
|
||||
points = enumerate(x)
|
||||
|
||||
def dist_mouse(point):
|
||||
x = point[1]
|
||||
d = abs(mx - x)
|
||||
return d
|
||||
|
||||
closest = min(
|
||||
points, key=dist_mouse
|
||||
)
|
||||
|
||||
return closest
|
||||
|
||||
def _select_in_table(self, ind):
|
||||
if self._table is None:
|
||||
return
|
||||
|
||||
self._table.setFocus()
|
||||
selection = self._table.selectionModel()
|
||||
index = QItemSelection()
|
||||
|
||||
if len(ind) == 0:
|
||||
return
|
||||
|
||||
for i in ind:
|
||||
index.append(QItemSelectionRange(self._table.model().index(i, 0)))
|
||||
|
||||
selection.select(
|
||||
index,
|
||||
QItemSelectionModel.Rows |
|
||||
QItemSelectionModel.ClearAndSelect |
|
||||
QItemSelectionModel.Select
|
||||
)
|
||||
|
||||
if len(ind) > 0:
|
||||
self._table.scrollTo(self._table.model().index(ind[-1], 0))
|
||||
|
||||
def _select_range_in_table(self, ind1, ind2):
|
||||
if self._table is not None:
|
||||
self._table.setFocus()
|
||||
selection = self._table.selectionModel()
|
||||
index = QItemSelection(self._table.model().index(ind1, 0),
|
||||
self._table.model().index(ind2, 0))
|
||||
selection.select(
|
||||
index,
|
||||
QItemSelectionModel.Rows |
|
||||
QItemSelectionModel.ClearAndSelect |
|
||||
QItemSelectionModel.Select
|
||||
)
|
||||
self._table.scrollTo(self._table.model().index(ind2, 0))
|
||||
|
||||
@timer
|
||||
def draw(self):
|
||||
@@ -71,10 +185,10 @@ class PlotKPZ(PamhyrPlot):
|
||||
return
|
||||
|
||||
self.draw_z_line()
|
||||
self.draw_z_line_highlight()
|
||||
self.draw_current()
|
||||
self.draw_lr()
|
||||
self.draw_gl()
|
||||
self.draw_bottom()
|
||||
self.draw_profiles_hs(self._data)
|
||||
|
||||
self.idle()
|
||||
self._init = True
|
||||
@@ -84,76 +198,60 @@ class PlotKPZ(PamhyrPlot):
|
||||
z_min = self.data.get_z_min()
|
||||
z_max = self.data.get_z_max()
|
||||
|
||||
self._colors, self._style = self.color_hightlight()
|
||||
|
||||
self.line_kp_zmin_zmax = self.canvas.axes.vlines(
|
||||
x=kp, ymin=z_min, ymax=z_max,
|
||||
color=self.color_plot,
|
||||
lw=1.
|
||||
color=self._colors,
|
||||
linestyle=self._style,
|
||||
lw=1.,
|
||||
picker=10,
|
||||
)
|
||||
|
||||
def draw_z_line_highlight(self):
|
||||
if self._highlight_data is not None:
|
||||
def color_hightlight(self):
|
||||
rows = sorted(list(
|
||||
set(
|
||||
(i.row() for i in self.parent.tableView.selectedIndexes())
|
||||
)
|
||||
))
|
||||
colors = [self.color_plot for row in range(len(self._data))]
|
||||
style = ["-" for row in range(len(self._data))]
|
||||
if len(rows) > 0:
|
||||
for row in rows:
|
||||
colors[row] = self.color_plot_current
|
||||
if rows[0] > 0:
|
||||
colors[rows[0]-1] = self.color_plot_previous
|
||||
style[rows[0]-1] = "--"
|
||||
if rows[-1] < len(self._data)-1:
|
||||
colors[rows[-1]+1] = self.color_plot_next
|
||||
style[rows[-1]+1] = "--"
|
||||
return colors, style
|
||||
|
||||
def draw_lr(self):
|
||||
kp = self.data.get_kp_complete_profiles()
|
||||
z_min = self.data.get_z_min()
|
||||
z_max = self.data.get_z_max()
|
||||
lz = []
|
||||
rz = []
|
||||
|
||||
kp_min, kp_max = self._highlight_data
|
||||
self.line_lr = []
|
||||
for z in self.data.get_z():
|
||||
lz.append(z[0])
|
||||
rz.append(z[-1])
|
||||
|
||||
indexes = list(
|
||||
map(
|
||||
lambda x: x[0],
|
||||
filter(
|
||||
lambda x: kp_min <= x[1] <= kp_max,
|
||||
enumerate(kp)
|
||||
)
|
||||
)
|
||||
line = self.canvas.axes.plot(
|
||||
kp, lz,
|
||||
color=self.color_plot_river_bottom,
|
||||
linestyle="dotted",
|
||||
lw=1.,
|
||||
)
|
||||
self.line_lr.append(line)
|
||||
|
||||
def indexes_filter(data): return list(
|
||||
map(
|
||||
lambda x: x[1],
|
||||
filter(
|
||||
lambda x: x[0] in indexes,
|
||||
enumerate(data)
|
||||
line = self.canvas.axes.plot(
|
||||
kp, rz,
|
||||
color=self.color_plot_river_bottom,
|
||||
linestyle="dotted",
|
||||
lw=1.,
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
ikp = indexes_filter(kp)
|
||||
imin = indexes_filter(z_min)
|
||||
imax = indexes_filter(z_max)
|
||||
|
||||
self.line_kp_zmin_zmax_highlight = self.canvas.axes.vlines(
|
||||
x=ikp,
|
||||
ymin=imin, ymax=imax,
|
||||
color=self.color_plot_highlight,
|
||||
lw=1.
|
||||
)
|
||||
|
||||
def draw_current(self):
|
||||
kp = self.data.get_kp_complete_profiles()
|
||||
z_min = self.data.get_z_min()
|
||||
z_max = self.data.get_z_max()
|
||||
|
||||
self.plot_selected, = self.canvas.axes.plot(
|
||||
(kp[0], kp[0]),
|
||||
(z_min[0], z_max[0]),
|
||||
color=self.color_plot_current, lw=1.5
|
||||
)
|
||||
self.plot_selected.set_visible(False)
|
||||
|
||||
self.before_plot_selected, = self.canvas.axes.plot(
|
||||
(kp[0], kp[0]),
|
||||
(z_min[0], z_max[0]),
|
||||
color=self.color_plot_previous, lw=1.5, linestyle='--'
|
||||
)
|
||||
self.before_plot_selected.set_visible(False)
|
||||
|
||||
self.after_plot_selected, = self.canvas.axes.plot(
|
||||
(kp[0], kp[0]),
|
||||
(z_min[0], z_max[0]),
|
||||
color=self.color_plot_next, lw=1.5, linestyle='--'
|
||||
)
|
||||
self.after_plot_selected.set_visible(False)
|
||||
self.line_lr.append(line)
|
||||
|
||||
def draw_gl(self):
|
||||
kp = self.data.get_kp_complete_profiles()
|
||||
@@ -167,7 +265,9 @@ class PlotKPZ(PamhyrPlot):
|
||||
|
||||
self.line_kp_zgl.append(
|
||||
self.canvas.axes.plot(
|
||||
kp, z, lw=1., color=self.colors[ind]
|
||||
kp, z, lw=1.,
|
||||
color=self.colors[ind % len(self.colors)],
|
||||
linestyle=self.linestyle[ind // len(self.colors)]
|
||||
)
|
||||
)
|
||||
ind += 1
|
||||
@@ -182,12 +282,45 @@ class PlotKPZ(PamhyrPlot):
|
||||
color='lightgrey'
|
||||
)
|
||||
|
||||
def draw_profiles_hs(self, reach):
|
||||
lhs = filter(
|
||||
lambda hs: hs._input_reach.reach is reach,
|
||||
filter(
|
||||
lambda hs: hs._input_reach is not None,
|
||||
self._study.river.hydraulic_structures.lst
|
||||
)
|
||||
)
|
||||
|
||||
for hs in lhs:
|
||||
x = hs.input_kp
|
||||
if x is not None:
|
||||
z_min = reach.get_z_min()
|
||||
z_max = reach.get_z_max()
|
||||
|
||||
self.canvas.axes.plot(
|
||||
[x, x],
|
||||
[min(z_min), max(z_max)],
|
||||
linestyle="--",
|
||||
lw=1.,
|
||||
color=self.color_plot_previous,
|
||||
)
|
||||
|
||||
self.canvas.axes.annotate(
|
||||
" > " + hs.name,
|
||||
(x, max(z_max)),
|
||||
horizontalalignment='left',
|
||||
verticalalignment='top',
|
||||
annotation_clip=True,
|
||||
fontsize=9, color=self.color_plot_previous,
|
||||
)
|
||||
|
||||
@timer
|
||||
def update(self):
|
||||
if not self._init:
|
||||
self.draw()
|
||||
return
|
||||
|
||||
self.update_lr()
|
||||
self.update_gl()
|
||||
self.update_current()
|
||||
|
||||
@@ -195,52 +328,9 @@ class PlotKPZ(PamhyrPlot):
|
||||
|
||||
def update_current(self):
|
||||
if self._current_data_update:
|
||||
ind = self._current_data
|
||||
before = ind - 1
|
||||
after = ind + 1
|
||||
|
||||
self.before_plot_selected.set_visible(False)
|
||||
self.plot_selected.set_visible(False)
|
||||
self.after_plot_selected.set_visible(False)
|
||||
|
||||
if 0 <= before < self.data.number_profiles:
|
||||
profile = self.data.profile(before)
|
||||
if len(profile) > 0:
|
||||
kp_i = profile.kp
|
||||
z_min_i = profile.z_min()
|
||||
z_max_i = profile.z_max()
|
||||
|
||||
self.before_plot_selected.set_data(
|
||||
(kp_i, kp_i),
|
||||
(z_min_i, z_max_i)
|
||||
)
|
||||
self.before_plot_selected.set_visible(True)
|
||||
|
||||
if 0 <= ind < self.data.number_profiles:
|
||||
profile = self.data.profile(ind)
|
||||
if len(profile) > 0:
|
||||
kp_i = profile.kp
|
||||
z_min_i = profile.z_min()
|
||||
z_max_i = profile.z_max()
|
||||
|
||||
self.plot_selected.set_data(
|
||||
(kp_i, kp_i),
|
||||
(z_min_i, z_max_i)
|
||||
)
|
||||
self.plot_selected.set_visible(True)
|
||||
|
||||
if 0 <= after < self.data.number_profiles:
|
||||
profile = self.data.profile(after)
|
||||
if len(profile) > 0:
|
||||
kp_i = profile.kp
|
||||
z_min_i = profile.z_min()
|
||||
z_max_i = profile.z_max()
|
||||
|
||||
self.after_plot_selected.set_data(
|
||||
(kp_i, kp_i),
|
||||
(z_min_i, z_max_i)
|
||||
)
|
||||
self.after_plot_selected.set_visible(True)
|
||||
self._colors, self._style = self.color_hightlight()
|
||||
self.line_kp_zmin_zmax.set_colors(self._colors)
|
||||
self.line_kp_zmin_zmax.set_linestyle(self._style)
|
||||
|
||||
def update_gl(self):
|
||||
if self._current_data_update:
|
||||
@@ -252,12 +342,19 @@ class PlotKPZ(PamhyrPlot):
|
||||
|
||||
self.line_kp_zmin.set_data(kp, z_min)
|
||||
|
||||
self.line_kp_zmin_zmax.remove()
|
||||
self.line_kp_zmin_zmax = self.canvas.axes.vlines(
|
||||
x=kp,
|
||||
ymin=z_min, ymax=z_max,
|
||||
color='r', lw=1.
|
||||
)
|
||||
# TODO comprendre à quoi sert ce bout de code
|
||||
# ========>
|
||||
# self.line_kp_zmin_zmax.remove()
|
||||
# self._colors, self._style = self.color_hightlight()
|
||||
# self.line_kp_zmin_zmax = self.canvas.axes.vlines(
|
||||
# x=kp,
|
||||
# ymin=z_min,
|
||||
# ymax=z_max,
|
||||
# color=self._colors,
|
||||
# linestyle = self._style,
|
||||
# lw=1.
|
||||
# )
|
||||
# <========
|
||||
|
||||
z_complete = self.data.get_guidelines_z()
|
||||
try:
|
||||
@@ -267,3 +364,9 @@ class PlotKPZ(PamhyrPlot):
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to update graphic KPZ: {e}")
|
||||
|
||||
def update_lr(self):
|
||||
for line in self.line_lr:
|
||||
line[0].remove()
|
||||
|
||||
self.draw_lr()
|
||||
+206
-89
@@ -18,22 +18,27 @@
|
||||
|
||||
from tools import timer, trace
|
||||
from View.Tools.PamhyrPlot import PamhyrPlot
|
||||
from matplotlib import collections
|
||||
import numpy as np
|
||||
|
||||
from PyQt5.QtCore import (
|
||||
QCoreApplication
|
||||
QCoreApplication, Qt, QItemSelectionModel,
|
||||
QItemSelection, QItemSelectionRange,
|
||||
)
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
|
||||
_translate = QCoreApplication.translate
|
||||
|
||||
|
||||
class PlotXY(PamhyrPlot):
|
||||
def __init__(self, canvas=None, trad=None, data=None, toolbar=None,
|
||||
parent=None):
|
||||
table=None, parent=None):
|
||||
super(PlotXY, self).__init__(
|
||||
canvas=canvas,
|
||||
trad=trad,
|
||||
data=data,
|
||||
toolbar=toolbar,
|
||||
table=table,
|
||||
parent=parent
|
||||
)
|
||||
|
||||
@@ -46,6 +51,116 @@ class PlotXY(PamhyrPlot):
|
||||
self.before_plot_selected = None
|
||||
self.plot_selected = None
|
||||
self.after_plot_selected = None
|
||||
self.parent = parent
|
||||
self.line_xy_collection = None
|
||||
self._table = table
|
||||
self._colors = []
|
||||
self._style = []
|
||||
|
||||
def onpick(self, event):
|
||||
if event.mouseevent.inaxes != self.canvas.axes:
|
||||
return
|
||||
if event.mouseevent.button.value != 1:
|
||||
return
|
||||
|
||||
modifiers = QApplication.keyboardModifiers()
|
||||
if modifiers not in [Qt.ControlModifier,
|
||||
Qt.NoModifier,
|
||||
Qt.ShiftModifier]:
|
||||
return
|
||||
|
||||
ind, point = self._closest_section(event)
|
||||
if self._table is None:
|
||||
return
|
||||
self._table.blockSignals(True)
|
||||
if modifiers == Qt.ControlModifier:
|
||||
rows = list(
|
||||
set(
|
||||
(i.row() for i in self.parent.tableView.selectedIndexes())
|
||||
)
|
||||
)
|
||||
if ind in rows:
|
||||
rows.remove(ind)
|
||||
self._select_in_table(rows)
|
||||
else:
|
||||
self._select_in_table(rows + [ind])
|
||||
elif modifiers == Qt.ShiftModifier:
|
||||
rows = list(
|
||||
set(
|
||||
(i.row() for i in self.parent.tableView.selectedIndexes())
|
||||
)
|
||||
)
|
||||
if len(rows) > 0:
|
||||
i1 = min(rows[0], rows[-1], ind)
|
||||
i2 = max(rows[0], rows[-1], ind)
|
||||
else:
|
||||
i1 = ind
|
||||
i2 = ind
|
||||
self._select_range_in_table(i1, i2)
|
||||
else:
|
||||
self._select_in_table([ind])
|
||||
self._table.blockSignals(False)
|
||||
|
||||
return
|
||||
|
||||
def _closest_section(self, event):
|
||||
axes = self.canvas.axes
|
||||
mx = event.mouseevent.xdata
|
||||
my = event.mouseevent.ydata
|
||||
bx, by = axes.get_xlim(), axes.get_ylim()
|
||||
ratio = (bx[0] - bx[1]) / (by[0] - by[1])
|
||||
|
||||
segments = event.artist.get_segments()
|
||||
ind = event.ind
|
||||
|
||||
points = []
|
||||
for i in ind:
|
||||
points = points + [[i, j] for j in segments[i]]
|
||||
|
||||
def dist_mouse(point):
|
||||
x, y = point[1]
|
||||
d2 = (((mx - x) / ratio) ** 2) + ((my - y) ** 2)
|
||||
return d2
|
||||
|
||||
closest = min(
|
||||
points, key=dist_mouse
|
||||
)
|
||||
|
||||
return closest
|
||||
|
||||
def _select_in_table(self, ind):
|
||||
if self._table is None:
|
||||
return
|
||||
self._table.setFocus()
|
||||
selection = self._table.selectionModel()
|
||||
index = QItemSelection()
|
||||
if len(ind) == 0:
|
||||
return
|
||||
for i in ind:
|
||||
index.append(QItemSelectionRange(self._table.model().index(i, 0)))
|
||||
selection.select(
|
||||
index,
|
||||
QItemSelectionModel.Rows |
|
||||
QItemSelectionModel.ClearAndSelect |
|
||||
QItemSelectionModel.Select
|
||||
)
|
||||
|
||||
if len(ind) > 0:
|
||||
self._table.scrollTo(self._table.model().index(ind[-1], 0))
|
||||
|
||||
def _select_range_in_table(self, ind1, ind2):
|
||||
if self._table is not None:
|
||||
self._table.setFocus()
|
||||
selection = self._table.selectionModel()
|
||||
index = QItemSelection(self._table.model().index(ind1, 0),
|
||||
self._table.model().index(ind2, 0))
|
||||
selection.select(
|
||||
index,
|
||||
QItemSelectionModel.Rows |
|
||||
QItemSelectionModel.ClearAndSelect |
|
||||
QItemSelectionModel.Select
|
||||
)
|
||||
self._table.scrollTo(self._table.model().index(ind2, 0))
|
||||
|
||||
@timer
|
||||
def draw(self):
|
||||
@@ -61,34 +176,75 @@ class PlotXY(PamhyrPlot):
|
||||
return
|
||||
|
||||
self.draw_xy()
|
||||
self.draw_lr()
|
||||
self.draw_gl()
|
||||
self.draw_current()
|
||||
|
||||
self.idle()
|
||||
self._init = True
|
||||
|
||||
def draw_xy(self):
|
||||
kp = self.data.get_kp_complete_profiles()
|
||||
|
||||
kp_min, kp_max = (-1, -1)
|
||||
if self._highlight_data is not None:
|
||||
kp_min, kp_max = self._highlight_data
|
||||
|
||||
def color_hightlight(kp):
|
||||
if kp_min <= kp <= kp_max:
|
||||
return self.color_plot_highlight
|
||||
return self.color_plot
|
||||
|
||||
self.line_xy = []
|
||||
for x, y, kp in zip(self.data.get_x(),
|
||||
self.data.get_y(),
|
||||
kp):
|
||||
line = self.canvas.axes.plot(
|
||||
x, y,
|
||||
color=color_hightlight(kp),
|
||||
**self.plot_default_kargs
|
||||
for xy in zip(self.data.get_x(), self.data.get_y()):
|
||||
self.line_xy.append(np.column_stack(xy))
|
||||
|
||||
self._colors, self._style = self.color_hightlight()
|
||||
self.line_xy_collection = collections.LineCollection(
|
||||
self.line_xy,
|
||||
colors=self._colors,
|
||||
linestyle=self._style,
|
||||
picker=10
|
||||
)
|
||||
self.line_xy.append(line)
|
||||
self.canvas.axes.add_collection(self.line_xy_collection)
|
||||
|
||||
def color_hightlight(self):
|
||||
rows = sorted(list(
|
||||
set(
|
||||
(i.row() for i in self.parent.tableView.selectedIndexes())
|
||||
)
|
||||
))
|
||||
colors = [self.color_plot for row in range(len(self._data))]
|
||||
style = ["-" for row in range(len(self._data))]
|
||||
if len(rows) > 0:
|
||||
for row in rows:
|
||||
colors[row] = self.color_plot_current
|
||||
if rows[0] > 0:
|
||||
colors[rows[0]-1] = self.color_plot_previous
|
||||
style[rows[0]-1] = "--"
|
||||
if rows[-1] < len(self._data)-1:
|
||||
colors[rows[-1]+1] = self.color_plot_next
|
||||
style[rows[-1]+1] = "--"
|
||||
return colors, style
|
||||
|
||||
def draw_lr(self):
|
||||
lx = []
|
||||
ly = []
|
||||
rx = []
|
||||
ry = []
|
||||
|
||||
self.line_lr = []
|
||||
for x, y in zip(self.data.get_x(),
|
||||
self.data.get_y()):
|
||||
lx.append(x[0])
|
||||
ly.append(y[0])
|
||||
|
||||
rx.append(x[-1])
|
||||
ry.append(y[-1])
|
||||
|
||||
line = self.canvas.axes.plot(
|
||||
lx, ly,
|
||||
color=self.color_plot_river_bottom,
|
||||
linestyle="dotted",
|
||||
lw=1.,
|
||||
)
|
||||
self.line_lr.append(line)
|
||||
|
||||
line = self.canvas.axes.plot(
|
||||
rx, ry,
|
||||
color=self.color_plot_river_bottom,
|
||||
linestyle="dotted",
|
||||
lw=1.,
|
||||
)
|
||||
self.line_lr.append(line)
|
||||
|
||||
def draw_gl(self):
|
||||
x_complete = self.data.get_guidelines_x()
|
||||
@@ -98,38 +254,12 @@ class PlotXY(PamhyrPlot):
|
||||
self.line_gl = []
|
||||
for x, y in zip(x_complete, y_complete):
|
||||
line = self.canvas.axes.plot(
|
||||
x, y, color=self.colors[ind]
|
||||
x, y, color=self.colors[ind % len(self.colors)],
|
||||
linestyle=self.linestyle[ind // len(self.colors)]
|
||||
)
|
||||
self.line_gl.append(line)
|
||||
ind += 1
|
||||
|
||||
def draw_current(self):
|
||||
# Previous profile
|
||||
self.before_plot_selected, = self.canvas.axes.plot(
|
||||
self.data.profile(0).x(),
|
||||
self.data.profile(0).y(),
|
||||
color=self.color_plot_previous, linestyle="--",
|
||||
**self.plot_default_kargs
|
||||
)
|
||||
self.before_plot_selected.set_visible(False)
|
||||
|
||||
# Current profile
|
||||
self.plot_selected, = self.canvas.axes.plot(
|
||||
self.data.profile(0).x(),
|
||||
self.data.profile(0).y(),
|
||||
color=self.color_plot_current, **self.plot_default_kargs
|
||||
)
|
||||
self.plot_selected.set_visible(False)
|
||||
|
||||
# Next profile
|
||||
self.after_plot_selected, = self.canvas.axes.plot(
|
||||
self.data.profile(0).x(),
|
||||
self.data.profile(0).y(),
|
||||
color=self.color_plot_next, linestyle='--',
|
||||
**self.plot_default_kargs
|
||||
)
|
||||
self.after_plot_selected.set_visible(False)
|
||||
|
||||
@timer
|
||||
def update(self):
|
||||
if not self._init:
|
||||
@@ -139,6 +269,7 @@ class PlotXY(PamhyrPlot):
|
||||
if self.data is None:
|
||||
return
|
||||
|
||||
self.update_lr()
|
||||
self.update_gl()
|
||||
self.update_current()
|
||||
|
||||
@@ -149,21 +280,24 @@ class PlotXY(PamhyrPlot):
|
||||
x_complete = list(self.data.get_guidelines_x())
|
||||
y_complete = list(self.data.get_guidelines_y())
|
||||
|
||||
for i in range(self.data.number_profiles):
|
||||
if i < len(self.line_xy):
|
||||
self.line_xy[i][0].set_data(
|
||||
self.data.profile(i).x(),
|
||||
self.data.profile(i).y()
|
||||
)
|
||||
else:
|
||||
self.line_xy.append(
|
||||
self.canvas.axes.plot(
|
||||
self.data.profile(i).x(),
|
||||
self.data.profile(i).y(),
|
||||
color='r',
|
||||
**self.plot_default_kargs
|
||||
)
|
||||
)
|
||||
# TODO comprendre à quoi sert ce bout de code
|
||||
# ========>
|
||||
# for i in range(self.data.number_profiles):
|
||||
# if i < len(self.line_xy):
|
||||
# self.line_xy[i][0].set_data(
|
||||
# self.data.profile(i).x(),
|
||||
# self.data.profile(i).y()
|
||||
# )
|
||||
# else:
|
||||
# self.line_xy.append(
|
||||
# self.canvas.axes.plot(
|
||||
# self.data.profile(i).x(),
|
||||
# self.data.profile(i).y(),
|
||||
# color='r',
|
||||
# **self.plot_default_kargs
|
||||
# )
|
||||
# )
|
||||
# <========
|
||||
|
||||
for i in range(len(x_complete)):
|
||||
if i < len(self.line_gl):
|
||||
@@ -181,29 +315,12 @@ class PlotXY(PamhyrPlot):
|
||||
|
||||
def update_current(self):
|
||||
if self._current_data_update:
|
||||
ind = self._current_data
|
||||
before = ind - 1
|
||||
after = ind + 1
|
||||
self._colors, self._style = self.color_hightlight()
|
||||
self.line_xy_collection.set_colors(self._colors)
|
||||
self.line_xy_collection.set_linestyle(self._style)
|
||||
|
||||
self.before_plot_selected.set_visible(False)
|
||||
self.plot_selected.set_visible(False)
|
||||
self.after_plot_selected.set_visible(False)
|
||||
def update_lr(self):
|
||||
for line in self.line_lr:
|
||||
line[0].remove()
|
||||
|
||||
if 0 <= before < self.data.number_profiles:
|
||||
self.before_plot_selected.set_data(
|
||||
self.data.profile(before).x(),
|
||||
self.data.profile(before).y()
|
||||
)
|
||||
self.before_plot_selected.set_visible(True)
|
||||
|
||||
if 0 <= ind < self.data.number_profiles:
|
||||
self.plot_selected.set_data(self.data.profile(ind).x(),
|
||||
self.data.profile(ind).y())
|
||||
self.plot_selected.set_visible(True)
|
||||
|
||||
if 0 <= after < self.data.number_profiles:
|
||||
self.after_plot_selected.set_data(
|
||||
self.data.profile(after).x(),
|
||||
self.data.profile(after).y()
|
||||
)
|
||||
self.after_plot_selected.set_visible(True)
|
||||
self.draw_lr()
|
||||
@@ -18,14 +18,19 @@
|
||||
|
||||
import logging
|
||||
|
||||
from math import dist, sqrt
|
||||
|
||||
from tools import timer, trace
|
||||
from View.Tools.PamhyrPlot import PamhyrPlot
|
||||
from View.Tools.Plot.OnPickEvent import OnpickEvent
|
||||
|
||||
from PyQt5.QtCore import (
|
||||
QCoreApplication
|
||||
Qt, QCoreApplication, QItemSelectionModel,
|
||||
QItemSelection, QItemSelectionRange,
|
||||
)
|
||||
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
from matplotlib.widgets import RectangleSelector
|
||||
|
||||
_translate = QCoreApplication.translate
|
||||
|
||||
logger = logging.getLogger()
|
||||
@@ -43,6 +48,10 @@ class Plot(PamhyrPlot):
|
||||
)
|
||||
|
||||
self._table = table
|
||||
self._parent = parent
|
||||
self._z_note = None
|
||||
self._z_line = None
|
||||
self._z_fill_between = None
|
||||
|
||||
self.line_xy = []
|
||||
self.line_gl = []
|
||||
@@ -56,6 +65,245 @@ class Plot(PamhyrPlot):
|
||||
|
||||
self._isometric_axis = False
|
||||
|
||||
self.hl_points = []
|
||||
self.highlight = (
|
||||
[], # Points list to highlight
|
||||
None # Hydrolic values (z, wet_area,
|
||||
# wet_preimeter, water_width)
|
||||
)
|
||||
self._onpickevent = None
|
||||
self._rect_select = RectangleSelector(
|
||||
ax=self.canvas.axes,
|
||||
onselect=self.rect_select_callback,
|
||||
useblit=True,
|
||||
button=[1], # don't use middle nor right button
|
||||
minspanx=2.0,
|
||||
minspany=2.0,
|
||||
spancoords='pixels',
|
||||
interactive=False
|
||||
)
|
||||
|
||||
def onrelease(self, event):
|
||||
# we need to do that to prevent conflicst
|
||||
# between onpick and rect_select_callback
|
||||
modifiers = QApplication.keyboardModifiers()
|
||||
points, hyd = self.highlight
|
||||
if self._onpickevent is not None:
|
||||
ind, point = self._closest_point(self._onpickevent)
|
||||
if modifiers == Qt.ControlModifier:
|
||||
rows = self._parent.index_selected_rows()
|
||||
if ind in rows:
|
||||
rows.remove(ind)
|
||||
del (points[ind])
|
||||
self.highlight = (points, hyd)
|
||||
self._select_in_table(rows)
|
||||
else:
|
||||
self.highlight = (points+[point], hyd)
|
||||
self._select_in_table(rows+[ind])
|
||||
elif modifiers == Qt.ShiftModifier:
|
||||
rows = self._parent.index_selected_rows()
|
||||
if len(rows) > 0:
|
||||
i1 = min(rows[0], rows[-1], ind)
|
||||
i2 = max(rows[0], rows[-1], ind)
|
||||
p = [
|
||||
[self.data.points[i].x, self.data.points[i].y]
|
||||
for i in range(i1, i2)
|
||||
]
|
||||
else:
|
||||
i1 = ind
|
||||
i2 = ind
|
||||
p = [point]
|
||||
self.highlight = (p, hyd)
|
||||
self._select_range_in_table(i1, i2)
|
||||
else:
|
||||
self.highlight = ([point], hyd)
|
||||
self._select_in_table([ind])
|
||||
|
||||
self._onpickevent = None
|
||||
|
||||
def onpick(self, event):
|
||||
if event.mouseevent.inaxes != self.canvas.axes:
|
||||
return
|
||||
if event.mouseevent.button.value != 1:
|
||||
return
|
||||
|
||||
modifiers = QApplication.keyboardModifiers()
|
||||
if modifiers not in [Qt.ControlModifier,
|
||||
Qt.NoModifier,
|
||||
Qt.ShiftModifier]:
|
||||
return
|
||||
|
||||
self._onpickevent = event
|
||||
return
|
||||
|
||||
def onclick(self, event):
|
||||
if event.inaxes != self.canvas.axes:
|
||||
return
|
||||
if event.button.value == 1:
|
||||
return
|
||||
|
||||
points, _ = self.highlight
|
||||
|
||||
z = self._get_z_from_click(event)
|
||||
if z < self.data.z_min() or event.button.value == 2:
|
||||
self.highlight = (points, None)
|
||||
self.update()
|
||||
return
|
||||
|
||||
a, p, w = self._compute_hydraulics(z)
|
||||
|
||||
logger.debug(f"{z, a, p, w}")
|
||||
|
||||
self.highlight = (points, (z, a, p, w))
|
||||
|
||||
self.update()
|
||||
return
|
||||
|
||||
def select_points_from_indices(self, indices):
|
||||
data = self.data
|
||||
_, hyd = self.highlight
|
||||
|
||||
points = list(
|
||||
map(
|
||||
lambda e: e[1],
|
||||
filter(
|
||||
lambda e: e[0] in indices,
|
||||
enumerate(
|
||||
zip(data.get_station(), data.z())
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
self.highlight = (points, hyd)
|
||||
self.update()
|
||||
|
||||
def _select_in_table(self, ind):
|
||||
if self._table is not None:
|
||||
self._table.blockSignals(True)
|
||||
self._table.setFocus()
|
||||
selection = self._table.selectionModel()
|
||||
index = QItemSelection()
|
||||
if len(ind) > 0:
|
||||
for i in ind:
|
||||
index.append(QItemSelectionRange(
|
||||
self._table.model().index(i, 0))
|
||||
)
|
||||
selection.select(
|
||||
index,
|
||||
QItemSelectionModel.Rows |
|
||||
QItemSelectionModel.ClearAndSelect |
|
||||
QItemSelectionModel.Select
|
||||
)
|
||||
|
||||
if len(ind) > 0:
|
||||
self._table.scrollTo(self._table.model().index(ind[-1], 0))
|
||||
self._table.blockSignals(False)
|
||||
|
||||
def _select_range_in_table(self, ind1, ind2):
|
||||
if self._table is not None:
|
||||
self._table.blockSignals(True)
|
||||
self._table.setFocus()
|
||||
selection = self._table.selectionModel()
|
||||
index = QItemSelection(self._table.model().index(ind1, 0),
|
||||
self._table.model().index(ind2, 0))
|
||||
selection.select(
|
||||
index,
|
||||
QItemSelectionModel.Rows |
|
||||
QItemSelectionModel.ClearAndSelect |
|
||||
QItemSelectionModel.Select
|
||||
)
|
||||
self._table.scrollTo(self._table.model().index(ind2, 0))
|
||||
self._table.blockSignals(False)
|
||||
|
||||
def _closest_point(self, event):
|
||||
points_ind = event.ind
|
||||
axes = self.canvas.axes
|
||||
bx, by = axes.get_xlim(), axes.get_ylim()
|
||||
ratio = (bx[0] - bx[1]) / (by[0] - by[1])
|
||||
|
||||
x = event.artist.get_xdata()
|
||||
y = event.artist.get_ydata()
|
||||
|
||||
# points = filter(
|
||||
# lambda e: e[0] in points_ind,
|
||||
# enumerate(zip(x, y))
|
||||
# )
|
||||
points = enumerate(zip(x, y))
|
||||
|
||||
mx = event.mouseevent.xdata
|
||||
my = event.mouseevent.ydata
|
||||
|
||||
def dist_mouse(point):
|
||||
x, y = point[1]
|
||||
d2 = ((mx - x) / ratio) ** 2 + ((my - y) ** 2)
|
||||
return d2
|
||||
|
||||
closest = min(
|
||||
points, key=dist_mouse
|
||||
)
|
||||
|
||||
return closest
|
||||
|
||||
def _get_z_from_click(self, event):
|
||||
return event.ydata
|
||||
|
||||
def rect_select_callback(self, eclick, erelease):
|
||||
|
||||
points, hyd = self.highlight
|
||||
x1, y1 = eclick.xdata, eclick.ydata
|
||||
x2, y2 = erelease.xdata, erelease.ydata
|
||||
|
||||
if (max(abs(x1-x2), abs(y1-y2)) < 0.001):
|
||||
return
|
||||
modifiers = QApplication.keyboardModifiers()
|
||||
|
||||
x1, y1 = eclick.xdata, eclick.ydata
|
||||
x2, y2 = erelease.xdata, erelease.ydata
|
||||
|
||||
inds, points2 = self._points_in_rectangle(x1, y1, x2, y2)
|
||||
self._onclickevent = None
|
||||
if modifiers == Qt.ControlModifier:
|
||||
rows = self._parent.index_selected_rows()
|
||||
if all(i in rows for i in inds):
|
||||
for ind in sorted(inds, reverse=True):
|
||||
rows.remove(ind)
|
||||
del (points[ind])
|
||||
self.highlight = (points, hyd)
|
||||
self._select_in_table(rows)
|
||||
else:
|
||||
self.highlight = (points+points2, hyd)
|
||||
self._select_in_table(rows+inds)
|
||||
else:
|
||||
self.highlight = (points2, hyd)
|
||||
self._select_in_table(inds)
|
||||
return
|
||||
|
||||
def _points_in_rectangle(self, x1, y1, x2, y2):
|
||||
# TODO: use lambdas
|
||||
listi = []
|
||||
listp = []
|
||||
station = self.data._get_station(self.data.points)
|
||||
for i, p in enumerate(self.data.points):
|
||||
if (min(x1, x2) < station[i] < max(x1, x2)
|
||||
and min(y1, y2) < p.z < max(y1, y2)):
|
||||
listi.append(i)
|
||||
listp.append((station[i], p.z))
|
||||
return listi, listp
|
||||
|
||||
def _compute_hydraulics(self, z):
|
||||
profile = self.data
|
||||
|
||||
points = profile.wet_points(z)
|
||||
station = profile._get_station(points)
|
||||
width = abs(station[0] - station[-1])
|
||||
|
||||
poly = profile.wet_polygon(z)
|
||||
area = poly.area
|
||||
perimeter = poly.length
|
||||
|
||||
return area, perimeter, width
|
||||
|
||||
@timer
|
||||
def draw(self):
|
||||
self.init_axes()
|
||||
@@ -65,19 +313,24 @@ class Plot(PamhyrPlot):
|
||||
x_carto = self.data.x()
|
||||
y_carto = self.data.y()
|
||||
|
||||
if (len(x_carto) < 3 or len(y_carto) < 3 or
|
||||
len(x) < 3):
|
||||
if (len(x_carto) < 3 or len(y_carto) < 3 or len(x) < 3):
|
||||
# Noting to do in this case
|
||||
return
|
||||
|
||||
gl = map(lambda p: p.name, self.data.points)
|
||||
|
||||
self.profile_line2D, = self.canvas.axes.plot(
|
||||
x, y, color=self.color_plot,
|
||||
lw=1.5, markersize=7, marker='+',
|
||||
picker=30
|
||||
picker=10
|
||||
)
|
||||
|
||||
self.draw_annotation(x, y)
|
||||
self.draw_highligth()
|
||||
|
||||
self.idle()
|
||||
|
||||
def draw_annotation(self, x, y):
|
||||
gl = map(lambda p: p.name, self.data.points)
|
||||
|
||||
# Add label on graph
|
||||
self.annotation = []
|
||||
for i, name in enumerate(list(gl)):
|
||||
@@ -113,30 +366,88 @@ class Plot(PamhyrPlot):
|
||||
self.canvas.axes.set_facecolor('#F9F9F9')
|
||||
self.canvas.figure.patch.set_facecolor('white')
|
||||
|
||||
self.onpick_event = OnpickEvent(
|
||||
self.canvas.axes,
|
||||
x, y, x_carto, y_carto,
|
||||
self._table
|
||||
def draw_highligth(self):
|
||||
points, hyd = self.highlight
|
||||
for p in self.hl_points:
|
||||
p[0].set_data([], [])
|
||||
|
||||
self.hl_points = []
|
||||
|
||||
for x, y in points:
|
||||
self.hl_points.append(
|
||||
self.canvas.axes.plot(
|
||||
x, y,
|
||||
color=self.color_plot_highlight,
|
||||
lw=1.5, markersize=7, marker='+',
|
||||
)
|
||||
self.canvas.figure.canvas\
|
||||
.mpl_connect(
|
||||
'pick_event',
|
||||
self.onpick_event.onpick
|
||||
)
|
||||
|
||||
self.onclick_event = OnpickEvent(
|
||||
self.canvas.axes,
|
||||
x, y, x_carto, y_carto,
|
||||
self._table
|
||||
)
|
||||
self.canvas.figure.canvas\
|
||||
.mpl_connect(
|
||||
'button_press_event',
|
||||
self.onclick_event.onclick
|
||||
if hyd is not None:
|
||||
self.draw_highligth_z_line(*hyd)
|
||||
else:
|
||||
if self._z_note is not None:
|
||||
self._z_note.set_visible(False)
|
||||
self._z_line[0].set_visible(False)
|
||||
self._z_fill_between.set_visible(False)
|
||||
|
||||
def draw_highligth_z_line(self, z, a, p, w):
|
||||
text = (
|
||||
f"Z = {z:.3f} m, " +
|
||||
f"{self._trad['width']} = {w:.3f} m,\n" +
|
||||
f"{self._trad['area']} = {a:.3f} m², " +
|
||||
f"{self._trad['perimeter']} = {p:.3f} m"
|
||||
)
|
||||
|
||||
self.idle()
|
||||
x = self.data.get_station()
|
||||
xlim = (x[0], x[-1])
|
||||
pos = (
|
||||
xlim[0] + (abs(xlim[0] - xlim[1]) * 0.05),
|
||||
z + 0.8
|
||||
)
|
||||
y = self.data.z()
|
||||
|
||||
if self._z_note is None:
|
||||
self.draw_highligth_z_line_fill(x, y, z)
|
||||
|
||||
self._z_line = self.canvas.axes.plot(
|
||||
xlim, [z, z],
|
||||
color=self.color_plot_river_water
|
||||
)
|
||||
self._z_line[0].set_visible(True)
|
||||
|
||||
self._z_note = self.canvas.axes.annotate(
|
||||
text, pos,
|
||||
horizontalalignment='left',
|
||||
verticalalignment='top',
|
||||
annotation_clip=True,
|
||||
color=self.color_plot_river_water,
|
||||
fontsize=9,
|
||||
fontweight='bold',
|
||||
alpha=0.7
|
||||
)
|
||||
self._z_note.set_visible(True)
|
||||
else:
|
||||
self.draw_highligth_z_line_fill(x, y, z)
|
||||
|
||||
self._z_line[0].set_data(xlim, [z, z])
|
||||
self._z_note.set_position(pos)
|
||||
self._z_note.set_text(text)
|
||||
self._z_line[0].set_visible(True)
|
||||
self._z_note.set_visible(True)
|
||||
|
||||
def draw_highligth_z_line_fill(self, x, y, z):
|
||||
if self._z_fill_between is not None:
|
||||
self._z_fill_between.remove()
|
||||
|
||||
self._z_fill_between = self.canvas.axes.fill_between(
|
||||
x, y, z,
|
||||
where=y <= z,
|
||||
facecolor=self.color_plot_river_water_zone,
|
||||
interpolate=True, alpha=0.7
|
||||
)
|
||||
|
||||
@timer
|
||||
def update(self):
|
||||
self.draw()
|
||||
self.draw_highligth()
|
||||
|
||||
self.update_idle()
|
||||
@@ -82,16 +82,6 @@ class GeometryProfileTableModel(PamhyrTableModel):
|
||||
elif value == self._data.z_max():
|
||||
return QColor("blue")
|
||||
|
||||
if role == Qt.ToolTipRole:
|
||||
if value == self._data.z_min():
|
||||
return _translate("MainWindowProfile",
|
||||
"La cote du fond",
|
||||
"Z minimale")
|
||||
elif value == self._data.z_max():
|
||||
return _translate("MainWindowProfile",
|
||||
"La cote maximale",
|
||||
"Z maximale")
|
||||
|
||||
if index.column() == 3:
|
||||
value = self._data.point(index.row()).name
|
||||
|
||||
@@ -104,16 +94,6 @@ class GeometryProfileTableModel(PamhyrTableModel):
|
||||
if role == Qt.ForegroundRole:
|
||||
return QColor("darkRed")
|
||||
|
||||
if role == Qt.ToolTipRole:
|
||||
if value.strip().upper() == "RG":
|
||||
return _translate(
|
||||
"MainWindowProfile", "Rive gauche"
|
||||
)
|
||||
else:
|
||||
return _translate(
|
||||
"MainWindowProfile", "Rive droite"
|
||||
)
|
||||
|
||||
return QVariant()
|
||||
|
||||
def setData(self, index, value, role=Qt.EditRole):
|
||||
@@ -250,6 +230,26 @@ class GeometryProfileTableModel(PamhyrTableModel):
|
||||
self.endMoveRows()
|
||||
self.layoutChanged.emit()
|
||||
|
||||
def purge(self, np_purge):
|
||||
|
||||
self._undo.push(
|
||||
PurgeCommand(
|
||||
self._data, np_purge
|
||||
)
|
||||
)
|
||||
|
||||
self.layoutChanged.emit()
|
||||
|
||||
def reverse(self):
|
||||
|
||||
self._undo.push(
|
||||
ReverseCommand(
|
||||
self._data
|
||||
)
|
||||
)
|
||||
|
||||
self.layoutChanged.emit()
|
||||
|
||||
def paste(self, row, header, data):
|
||||
if row > self._data.number_points:
|
||||
return
|
||||
|
||||
@@ -32,6 +32,10 @@ class GeometryProfileTranslate(GeometryTranslate):
|
||||
"Geometry", "Geometry cross-section"
|
||||
)
|
||||
|
||||
self._dict["width"] = _translate("Geometry", "Width")
|
||||
self._dict["area"] = _translate("Geometry", "Area")
|
||||
self._dict["perimeter"] = _translate("Geometry", "Perimeter")
|
||||
|
||||
self._sub_dict["table_headers"] = {
|
||||
"x": self._dict["x"],
|
||||
"y": self._dict["y"],
|
||||
|
||||
@@ -169,6 +169,34 @@ class MoveCommand(QUndoCommand):
|
||||
self._profile.move_down_point(self._i)
|
||||
|
||||
|
||||
class ReverseCommand(QUndoCommand):
|
||||
def __init__(self, profile):
|
||||
QUndoCommand.__init__(self)
|
||||
|
||||
self._profile = profile
|
||||
|
||||
def undo(self):
|
||||
self._profile.reverse()
|
||||
|
||||
def redo(self):
|
||||
self._profile.reverse()
|
||||
|
||||
|
||||
class PurgeCommand(QUndoCommand):
|
||||
def __init__(self, profile, np_purge):
|
||||
QUndoCommand.__init__(self)
|
||||
|
||||
self._profile = profile
|
||||
self._old = self._profile.points.copy()
|
||||
self._np_purge = np_purge
|
||||
|
||||
def undo(self):
|
||||
self._profile._points = self._old.copy()
|
||||
|
||||
def redo(self):
|
||||
self._profile.purge(self._np_purge)
|
||||
|
||||
|
||||
class PasteCommand(QUndoCommand):
|
||||
def __init__(self, profile, row, points):
|
||||
QUndoCommand.__init__(self)
|
||||
|
||||
@@ -20,8 +20,9 @@ import copy
|
||||
import sys
|
||||
import csv
|
||||
from time import time
|
||||
import logging
|
||||
|
||||
from tools import trace, timer
|
||||
from tools import trace, timer, logger_exception
|
||||
from Modules import Modules
|
||||
|
||||
from PyQt5.QtGui import (
|
||||
@@ -46,9 +47,12 @@ from View.Tools.Plot.PamhyrCanvas import MplCanvas
|
||||
from View.Geometry.Profile.Plot import Plot
|
||||
from View.Geometry.Profile.Table import GeometryProfileTableModel
|
||||
from View.Geometry.Profile.Translate import GeometryProfileTranslate
|
||||
from View.Geometry.PurgeDialog import PurgeDialog
|
||||
|
||||
_translate = QCoreApplication.translate
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
class ProfileWindow(PamhyrWindow):
|
||||
_pamhyr_ui = "GeometryCrossSection"
|
||||
@@ -111,7 +115,8 @@ class ProfileWindow(PamhyrWindow):
|
||||
data=self._profile,
|
||||
trad=self._trad,
|
||||
toolbar=self._toolbar,
|
||||
table=self.find(QTableView, "tableView")
|
||||
table=self.find(QTableView, "tableView"),
|
||||
parent=self
|
||||
)
|
||||
self._plot.draw()
|
||||
|
||||
@@ -125,26 +130,49 @@ class ProfileWindow(PamhyrWindow):
|
||||
"action_down": self.move_down,
|
||||
"action_add": self.add,
|
||||
"action_delete": self.delete,
|
||||
"action_purge": self.purge,
|
||||
"action_reverse": self.reverse,
|
||||
}
|
||||
|
||||
for action in actions:
|
||||
self.find(QAction, action)\
|
||||
.triggered.connect(actions[action])
|
||||
|
||||
table = self.find(QTableView, f"tableView")
|
||||
table.selectionModel()\
|
||||
.selectionChanged\
|
||||
.connect(self.update_points_selection)
|
||||
|
||||
self._tablemodel.dataChanged.connect(self.update)
|
||||
|
||||
def update_points_selection(self):
|
||||
rows = self.index_selected_rows()
|
||||
self._plot.select_points_from_indices(rows)
|
||||
|
||||
def update(self):
|
||||
self.update_plot()
|
||||
self._propagate_update(key=Modules.GEOMETRY)
|
||||
|
||||
def _update(self, redraw=False, propagate=True):
|
||||
if redraw:
|
||||
self.update_plot()
|
||||
if propagate:
|
||||
self._propagate_update(key=Modules.GEOMETRY)
|
||||
|
||||
def update_plot(self):
|
||||
self._tablemodel.blockSignals(True)
|
||||
|
||||
# TODO: Do not rebuild all graph
|
||||
self._plot.update()
|
||||
|
||||
self._tablemodel.blockSignals(False)
|
||||
|
||||
def _propagated_update(self, key=Modules(0)):
|
||||
if Modules.GEOMETRY not in key:
|
||||
return
|
||||
|
||||
self._tablemodel.layoutChanged.emit()
|
||||
self._update(redraw=True, propagate=False)
|
||||
|
||||
def index_selected_row(self):
|
||||
table = self.find(QTableView, "tableView")
|
||||
rows = table.selectionModel()\
|
||||
@@ -154,6 +182,15 @@ class ProfileWindow(PamhyrWindow):
|
||||
|
||||
return rows[0].row()
|
||||
|
||||
def index_selected_rows(self):
|
||||
table = self.find(QTableView, "tableView")
|
||||
return list(
|
||||
map(
|
||||
lambda r: r.row(),
|
||||
table.selectionModel().selectedRows()
|
||||
)
|
||||
)
|
||||
|
||||
def add(self):
|
||||
table = self.find(QTableView, "tableView")
|
||||
if len(table.selectedIndexes()) == 0:
|
||||
@@ -221,6 +258,27 @@ class ProfileWindow(PamhyrWindow):
|
||||
|
||||
self.update()
|
||||
|
||||
def purge(self):
|
||||
self._tablemodel.purge()
|
||||
self.update()
|
||||
|
||||
def purge(self):
|
||||
try:
|
||||
dlg = PurgeDialog(
|
||||
trad=self._trad,
|
||||
parent=self
|
||||
)
|
||||
if dlg.exec():
|
||||
self._tablemodel.purge(dlg.np_purge)
|
||||
self._plot.draw()
|
||||
except Exception as e:
|
||||
logger_exception(e)
|
||||
return
|
||||
|
||||
def reverse(self):
|
||||
self._tablemodel.reverse()
|
||||
self.update()
|
||||
|
||||
def _copy(self):
|
||||
table = self.find(QTableView, "tableView")
|
||||
rows = table.selectionModel().selectedRows()
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
# PurgeDialog.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from View.Tools.PamhyrWindow import PamhyrDialog
|
||||
|
||||
from PyQt5.QtGui import (
|
||||
QKeySequence,
|
||||
)
|
||||
|
||||
from PyQt5.QtCore import (
|
||||
Qt, QVariant, QAbstractTableModel,
|
||||
)
|
||||
|
||||
from PyQt5.QtWidgets import (
|
||||
QUndoStack, QShortcut, QSpinBox,
|
||||
)
|
||||
|
||||
|
||||
class PurgeDialog(PamhyrDialog):
|
||||
_pamhyr_ui = "PurgeOptions"
|
||||
_pamhyr_name = "Purge"
|
||||
|
||||
def __init__(self, trad=None, parent=None):
|
||||
super(PurgeDialog, self).__init__(
|
||||
title=trad[self._pamhyr_name],
|
||||
trad=trad,
|
||||
options=[],
|
||||
parent=parent
|
||||
)
|
||||
|
||||
self._init_default_values()
|
||||
|
||||
def _init_default_values(self):
|
||||
self._np_purge = 24
|
||||
|
||||
@property
|
||||
def np_purge(self):
|
||||
return self._np_purge
|
||||
|
||||
def accept(self):
|
||||
self._np_purge = self.get_spin_box("spinBox_np_purge")
|
||||
super().accept()
|
||||
|
||||
def reject(self):
|
||||
self.close()
|
||||
@@ -240,9 +240,30 @@ class GeometryReachTableModel(PamhyrTableModel):
|
||||
|
||||
self._undo.push(
|
||||
MeshingCommand(
|
||||
self._data, mesher, data
|
||||
self._data, mesher, data, "mesh"
|
||||
)
|
||||
)
|
||||
|
||||
self.layoutAboutToBeChanged.emit()
|
||||
self.layoutChanged.emit()
|
||||
|
||||
def update_kp(self, mesher, data):
|
||||
self.layoutAboutToBeChanged.emit()
|
||||
|
||||
self._undo.push(
|
||||
MeshingCommand(
|
||||
self._data, mesher, data, "update_kp"
|
||||
)
|
||||
)
|
||||
|
||||
self.layoutAboutToBeChanged.emit()
|
||||
self.layoutChanged.emit()
|
||||
|
||||
def purge(self, np_purge):
|
||||
|
||||
self._undo.push(
|
||||
PurgeCommand(
|
||||
self._data, np_purge
|
||||
)
|
||||
)
|
||||
self.layoutChanged.emit()
|
||||
@@ -33,10 +33,12 @@ class GeometryTranslate(MainTranslate):
|
||||
|
||||
self._dict["open_file"] = _translate("Geometry", "Open a file")
|
||||
self._dict["file_st"] = _translate(
|
||||
"Geometry", "File mage geometry (*.ST *.st)")
|
||||
"Geometry", "Mage geometry file (*.ST *.st)")
|
||||
self._dict["file_m"] = _translate(
|
||||
"Geometry", "File mage meshed geometry (*.M *.m)")
|
||||
self._dict["file_all"] = _translate("Geometry", "All file (*)")
|
||||
"Geometry", "Mage meshed geometry file (*.M *.m)")
|
||||
self._dict["file_shp"] = _translate(
|
||||
"Geometry", "Shapefile (*.SHP *.shp)")
|
||||
self._dict["file_all"] = _translate("Geometry", "All files (*)")
|
||||
|
||||
self._dict["cross_section"] = _translate("Geometry", "cross-section")
|
||||
self._dict["cross_sections"] = _translate("Geometry", "cross-sections")
|
||||
@@ -64,9 +66,12 @@ class GeometryTranslate(MainTranslate):
|
||||
}
|
||||
|
||||
self._sub_dict["lm_dict"] = {
|
||||
"1": _translate("Geometry", "First guideline"),
|
||||
"2": _translate("Geometry", "Second guideline"),
|
||||
"3": _translate("Geometry", "Means between the two guideline"),
|
||||
"1": _translate("Geometry", "the first guide-line"),
|
||||
"2": _translate("Geometry", "the second guide-line"),
|
||||
"3": _translate(
|
||||
"Geometry",
|
||||
"the mean over the two guide-lines"
|
||||
),
|
||||
}
|
||||
|
||||
self._sub_dict["r_lm_dict"] = {}
|
||||
|
||||
@@ -19,13 +19,14 @@
|
||||
import logging
|
||||
|
||||
from copy import deepcopy
|
||||
from tools import trace, timer
|
||||
from tools import trace, timer, logger_exception
|
||||
|
||||
from PyQt5.QtWidgets import (
|
||||
QMessageBox, QUndoCommand, QUndoStack,
|
||||
)
|
||||
|
||||
from Model.Geometry import Reach
|
||||
from Model.Except import exception_message_box
|
||||
|
||||
from Meshing.Mage import MeshingWithMage
|
||||
|
||||
@@ -209,20 +210,25 @@ class ImportCommand(QUndoCommand):
|
||||
|
||||
def redo(self):
|
||||
if self._profiles is None:
|
||||
try:
|
||||
self._profiles = self._reach.import_geometry(self._filename)
|
||||
self._profiles.reverse()
|
||||
except Exception as e:
|
||||
logger_exception(e)
|
||||
exception_message_box(e)
|
||||
else:
|
||||
for profile in self._profiles:
|
||||
self._reach.insert_profile(self._row, profile)
|
||||
|
||||
|
||||
class MeshingCommand(QUndoCommand):
|
||||
def __init__(self, reach, mesher, data):
|
||||
def __init__(self, reach, mesher, data, command):
|
||||
QUndoCommand.__init__(self)
|
||||
|
||||
self._reach = reach
|
||||
self._data = data
|
||||
self._mesher = mesher
|
||||
self._command = command
|
||||
|
||||
self._profiles = reach.profiles.copy()
|
||||
self._profiles.reverse()
|
||||
@@ -237,6 +243,12 @@ class MeshingCommand(QUndoCommand):
|
||||
|
||||
def redo(self):
|
||||
if self._new_profiles is None:
|
||||
if self._command == "update_kp":
|
||||
self._mesher.update_kp(
|
||||
self._reach,
|
||||
**self._data
|
||||
)
|
||||
else:
|
||||
self._mesher.meshing(
|
||||
self._reach,
|
||||
**self._data
|
||||
@@ -249,3 +261,23 @@ class MeshingCommand(QUndoCommand):
|
||||
|
||||
for profile in self._new_profiles:
|
||||
self._reach.insert_profile(0, profile)
|
||||
|
||||
|
||||
class PurgeCommand(QUndoCommand):
|
||||
def __init__(self, reach, np_purge):
|
||||
QUndoCommand.__init__(self)
|
||||
|
||||
self._reach = reach
|
||||
self._np_purge = np_purge
|
||||
|
||||
self._old = []
|
||||
for profile in self._reach.profiles:
|
||||
self._old.append(profile.points.copy())
|
||||
|
||||
def undo(self):
|
||||
for i in range(self._reach.number_profiles):
|
||||
self._reach.profiles[i]._points = self._old[i].copy()
|
||||
|
||||
def redo(self):
|
||||
for profile in self._reach._profiles:
|
||||
profile.purge(self._np_purge)
|
||||
@@ -0,0 +1,164 @@
|
||||
# UpdateKPDialog.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from View.Tools.PamhyrWindow import PamhyrDialog
|
||||
|
||||
from PyQt5.QtGui import (
|
||||
QKeySequence,
|
||||
)
|
||||
|
||||
from PyQt5.QtCore import (
|
||||
Qt, QVariant, QAbstractTableModel,
|
||||
)
|
||||
|
||||
from PyQt5.QtWidgets import (
|
||||
QDialogButtonBox, QComboBox, QUndoStack, QShortcut,
|
||||
QDoubleSpinBox, QButtonGroup,
|
||||
)
|
||||
|
||||
|
||||
class UpdateKPDialog(PamhyrDialog):
|
||||
_pamhyr_ui = "UpdateKPOptions"
|
||||
_pamhyr_name = "UpdateKP"
|
||||
|
||||
def __init__(self, reach, trad=None, parent=None):
|
||||
super(UpdateKPDialog, self).__init__(
|
||||
title=trad[self._pamhyr_name],
|
||||
trad=trad,
|
||||
options=[],
|
||||
parent=parent
|
||||
)
|
||||
|
||||
self._reach = reach
|
||||
self._profiles = None
|
||||
|
||||
self._init_default_values()
|
||||
|
||||
def _init_default_values(self):
|
||||
gl, _ = self._reach.compute_guidelines()
|
||||
self._gl = list(gl)
|
||||
|
||||
self._begin_dir = "un"
|
||||
self._end_dir = "np"
|
||||
|
||||
lower_gl = list(map(str.lower, self._gl))
|
||||
for i, gl in enumerate(lower_gl):
|
||||
if gl == "rd":
|
||||
self._begin_dir = self._gl[i]
|
||||
elif gl == "rg":
|
||||
self._end_dir = self._gl[i]
|
||||
|
||||
self._orientation = 0
|
||||
self._origin = self._reach.profile(0)
|
||||
self._origin_value = self._reach.profile(0).kp
|
||||
|
||||
self._init_default_values_profiles()
|
||||
self._init_default_values_guidelines()
|
||||
|
||||
def _init_default_values_profiles(self):
|
||||
profiles = self.profiles
|
||||
|
||||
self.combobox_add_items("comboBox_origin", profiles)
|
||||
|
||||
self.find(QComboBox, "comboBox_origin")\
|
||||
.currentIndexChanged\
|
||||
.connect(
|
||||
self.changed_profile
|
||||
)
|
||||
|
||||
buttonbox = self.find(QButtonGroup, "buttonGroup_orientation")
|
||||
|
||||
for button in buttonbox.buttons():
|
||||
name = button.objectName()
|
||||
i = int(name.split('_')[-1])
|
||||
|
||||
buttonbox.setId(button, i)
|
||||
|
||||
def changed_profile(self):
|
||||
origin = self.get_combobox_text("comboBox_origin")
|
||||
self.set_double_spin_box(
|
||||
"doubleSpinBox_origin",
|
||||
self._reach.profile(self.profiles.index(origin)).kp
|
||||
)
|
||||
|
||||
@property
|
||||
def profiles(self):
|
||||
if self._profiles is None:
|
||||
self._profiles = list(
|
||||
map(
|
||||
lambda p: self._profile_name(p),
|
||||
self._reach.profiles
|
||||
)
|
||||
)
|
||||
|
||||
return self._profiles
|
||||
|
||||
def _profile_name(self, profile):
|
||||
name = profile.name
|
||||
|
||||
if name == "":
|
||||
name = f"{profile.kp}"
|
||||
else:
|
||||
name += f" ({profile.kp})"
|
||||
|
||||
return name
|
||||
|
||||
def _init_default_values_guidelines(self):
|
||||
bgl = ['un'] + self._gl + ['np']
|
||||
egl = ['un'] + self._gl + ['np']
|
||||
|
||||
self.combobox_add_items("comboBox_begin_gl", bgl)
|
||||
self.combobox_add_items("comboBox_end_gl", egl)
|
||||
|
||||
self.set_combobox_text("comboBox_begin_gl", self._begin_dir)
|
||||
self.set_combobox_text("comboBox_end_gl", self._end_dir)
|
||||
|
||||
@property
|
||||
def orientation(self):
|
||||
return self._orientation
|
||||
|
||||
@property
|
||||
def origin(self):
|
||||
return self._origin
|
||||
|
||||
@property
|
||||
def origin_value(self):
|
||||
return self._origin_value
|
||||
|
||||
@property
|
||||
def begin_dir(self):
|
||||
return self._begin_dir
|
||||
|
||||
@property
|
||||
def end_dir(self):
|
||||
return self._end_dir
|
||||
|
||||
def accept(self):
|
||||
origin = self.get_combobox_text("comboBox_origin")
|
||||
self._origin = self.profiles.index(origin)
|
||||
self._origin_value = self.get_double_spin_box("doubleSpinBox_origin")
|
||||
self._begin_dir = self.get_combobox_text("comboBox_begin_gl")
|
||||
self._end_dir = self.get_combobox_text("comboBox_end_gl")
|
||||
self._orientation = self.get_checked_id_button_group(
|
||||
"buttonGroup_orientation"
|
||||
)
|
||||
|
||||
super().accept()
|
||||
|
||||
def reject(self):
|
||||
self.close()
|
||||
+77
-67
@@ -36,7 +36,7 @@ from PyQt5.QtCore import (
|
||||
from PyQt5.QtWidgets import (
|
||||
QApplication, QMainWindow, QFileDialog, QCheckBox,
|
||||
QUndoStack, QShortcut, QTableView, QHeaderView,
|
||||
QAction, QSlider, QPushButton, QVBoxLayout,
|
||||
QAction, QPushButton, QVBoxLayout,
|
||||
QLabel, QAbstractItemView,
|
||||
)
|
||||
|
||||
@@ -56,6 +56,8 @@ from View.Geometry.PlotXY import PlotXY
|
||||
from View.Geometry.PlotAC import PlotAC
|
||||
from View.Geometry.PlotKPZ import PlotKPZ
|
||||
from View.Geometry.MeshingDialog import MeshingDialog
|
||||
from View.Geometry.UpdateKPDialog import UpdateKPDialog
|
||||
from View.Geometry.PurgeDialog import PurgeDialog
|
||||
from View.Geometry.Translate import GeometryTranslate
|
||||
from View.Geometry.Profile.Window import ProfileWindow
|
||||
|
||||
@@ -95,7 +97,6 @@ class GeometryWindow(PamhyrWindow):
|
||||
self.setup_plots()
|
||||
self.setup_statusbar()
|
||||
self.setup_connections()
|
||||
self.changed_slider_value()
|
||||
|
||||
def setup_table(self):
|
||||
table_headers = self._trad.get_dict("table_headers")
|
||||
@@ -156,6 +157,9 @@ class GeometryWindow(PamhyrWindow):
|
||||
|
||||
def _compute_status_label(self):
|
||||
row = self.index_selected_row()
|
||||
if row is None:
|
||||
return
|
||||
|
||||
profile = self._reach.profile(row)
|
||||
|
||||
name = profile.name + " " + str(profile.kp)
|
||||
@@ -188,19 +192,14 @@ class GeometryWindow(PamhyrWindow):
|
||||
"action_delete": self.delete,
|
||||
"action_edit": self.edit_profile,
|
||||
"action_meshing": self.edit_meshing,
|
||||
"action_update_kp": self.update_kp,
|
||||
"action_purge": self.purge,
|
||||
}
|
||||
|
||||
for action in actions:
|
||||
self.find(QAction, action)\
|
||||
.triggered.connect(actions[action])
|
||||
|
||||
self.find(QSlider, "verticalSlider").valueChanged.connect(
|
||||
self.changed_slider_value)
|
||||
self.find(QPushButton, "pushButton_up").clicked.connect(
|
||||
self.decrement_value_slider)
|
||||
self.find(QPushButton, "pushButton_down").clicked.connect(
|
||||
self.increment_value_slider)
|
||||
|
||||
# Profile selection when line change in table
|
||||
self.find(QTableView, "tableView").selectionModel()\
|
||||
.selectionChanged\
|
||||
@@ -221,13 +220,12 @@ class GeometryWindow(PamhyrWindow):
|
||||
self._plot_ac.draw()
|
||||
|
||||
self.select_current_profile()
|
||||
self.changed_slider_value()
|
||||
|
||||
if propagate:
|
||||
self._propagate_update(key=Modules.GEOMETRY)
|
||||
|
||||
def _propagated_update(self, key=Modules(0)):
|
||||
if Modules.NETWORK not in key:
|
||||
if Modules.NETWORK not in key and Modules.GEOMETRY not in key:
|
||||
return
|
||||
|
||||
self._update(propagate=False)
|
||||
@@ -241,6 +239,7 @@ class GeometryWindow(PamhyrWindow):
|
||||
file_types = [
|
||||
self._trad["file_st"],
|
||||
self._trad["file_m"],
|
||||
self._trad["file_shp"],
|
||||
self._trad["file_all"],
|
||||
]
|
||||
|
||||
@@ -296,7 +295,6 @@ class GeometryWindow(PamhyrWindow):
|
||||
"limites": [dlg.begin_cs, dlg.end_cs],
|
||||
"directrices": [dlg.begin_dir, dlg.end_dir],
|
||||
"lplan": dlg.lplan,
|
||||
"lm": dlg.lm,
|
||||
"linear": dlg.linear,
|
||||
}
|
||||
self._edit_meshing(data)
|
||||
@@ -317,10 +315,37 @@ class GeometryWindow(PamhyrWindow):
|
||||
src_except=e
|
||||
)
|
||||
|
||||
pyqtSlot(bool)
|
||||
def update_kp(self):
|
||||
try:
|
||||
dlg = UpdateKPDialog(
|
||||
reach=self._reach,
|
||||
trad=self._trad,
|
||||
parent=self
|
||||
)
|
||||
if dlg.exec():
|
||||
data = {
|
||||
"origin": dlg.origin,
|
||||
"directrices": [dlg.begin_dir, dlg.end_dir],
|
||||
"origin_value": dlg.origin_value,
|
||||
"orientation": dlg.orientation,
|
||||
}
|
||||
self._update_kp(data)
|
||||
except Exception as e:
|
||||
logger_exception(e)
|
||||
return
|
||||
|
||||
def changed_profile_slot(self, status):
|
||||
self.update_view1 = status
|
||||
def _update_kp(self, data):
|
||||
try:
|
||||
mesher = MeshingWithMageMailleurTT()
|
||||
self._table.update_kp(mesher, data)
|
||||
except Exception as e:
|
||||
logger_exception(e)
|
||||
raise ExternFileMissingError(
|
||||
module="mage",
|
||||
filename="MailleurTT",
|
||||
path=MeshingWithMageMailleurTT._path(),
|
||||
src_except=e
|
||||
)
|
||||
|
||||
def plot_xy(self):
|
||||
self.tableView.model().blockSignals(True)
|
||||
@@ -329,7 +354,9 @@ class GeometryWindow(PamhyrWindow):
|
||||
canvas=self._canvas_xy,
|
||||
data=self._reach,
|
||||
trad=self._trad,
|
||||
toolbar=self._toolbar_xy
|
||||
toolbar=self._toolbar_xy,
|
||||
table=self.find(QTableView, "tableView"),
|
||||
parent=self
|
||||
)
|
||||
self._plot_xy.draw()
|
||||
|
||||
@@ -345,9 +372,12 @@ class GeometryWindow(PamhyrWindow):
|
||||
|
||||
self._plot_kpc = PlotKPZ(
|
||||
canvas=self._canvas_kpc,
|
||||
study=self._study,
|
||||
data=self._reach,
|
||||
trad=self._trad,
|
||||
toolbar=self._toolbar_kpc
|
||||
toolbar=self._toolbar_kpc,
|
||||
table=self.find(QTableView, "tableView"),
|
||||
parent=self
|
||||
)
|
||||
self._plot_kpc.draw()
|
||||
|
||||
@@ -373,7 +403,6 @@ class GeometryWindow(PamhyrWindow):
|
||||
|
||||
def update_plot_ac(self, ind: int):
|
||||
self.tableView.model().blockSignals(True)
|
||||
self._plot_ac.current = ind
|
||||
self._plot_ac.update()
|
||||
self.tableView.model().blockSignals(False)
|
||||
|
||||
@@ -401,27 +430,11 @@ class GeometryWindow(PamhyrWindow):
|
||||
self._plot_ac.update()
|
||||
self.tableView.model().blockSignals(False)
|
||||
|
||||
def select_row_profile_slider(self, ind: int = 0):
|
||||
if self.tableView is not None:
|
||||
selectionModel = self.tableView.selectionModel()
|
||||
index = self.tableView.model().index(ind, 0)
|
||||
|
||||
selectionModel.select(
|
||||
index,
|
||||
QItemSelectionModel.Rows |
|
||||
QItemSelectionModel.ClearAndSelect |
|
||||
QItemSelectionModel.Select
|
||||
)
|
||||
|
||||
self.tableView.scrollTo(index)
|
||||
|
||||
def select_current_profile(self):
|
||||
self.tableView.model().blockSignals(True)
|
||||
|
||||
if len(self.tableView.selectedIndexes()) > 0:
|
||||
row = self.index_selected_row()
|
||||
|
||||
self.find(QSlider, "verticalSlider").setValue(row)
|
||||
if row is not None:
|
||||
self.select_plot_xy(row)
|
||||
self.select_plot_kpc(row)
|
||||
self.select_plot_ac(row)
|
||||
@@ -432,33 +445,6 @@ class GeometryWindow(PamhyrWindow):
|
||||
|
||||
self.tableView.model().blockSignals(False)
|
||||
|
||||
def changed_slider_value(self):
|
||||
self.tableView.model().blockSignals(True)
|
||||
|
||||
if self._table.rowCount() != 0:
|
||||
slider = self.find(QSlider, "verticalSlider")
|
||||
slider.setMaximum(self._table.rowCount() - 1)
|
||||
|
||||
slider_value = slider.value()
|
||||
kp = self._reach.profile(slider_value).kp
|
||||
|
||||
self.select_plot_xy(slider_value)
|
||||
self.select_plot_kpc(slider_value)
|
||||
self.select_row_profile_slider(slider_value)
|
||||
self.update_statusbar()
|
||||
|
||||
self.tableView.model().blockSignals(False)
|
||||
|
||||
def increment_value_slider(self):
|
||||
slider = self.find(QSlider, "verticalSlider")
|
||||
if 0 <= slider.value() < self._table.rowCount() - 1:
|
||||
slider.setValue(slider.value() + 1)
|
||||
|
||||
def decrement_value_slider(self):
|
||||
slider = self.find(QSlider, "verticalSlider")
|
||||
if 0 < slider.value() < self._table.rowCount():
|
||||
slider.setValue(slider.value() - 1)
|
||||
|
||||
def add(self):
|
||||
if len(self.tableView.selectedIndexes()) == 0:
|
||||
self._table.add(self._table.rowCount())
|
||||
@@ -479,32 +465,56 @@ class GeometryWindow(PamhyrWindow):
|
||||
self._table.delete(rows)
|
||||
|
||||
def index_selected_row(self):
|
||||
return self.tableView\
|
||||
r = self.tableView\
|
||||
.selectionModel()\
|
||||
.selectedRows()[0]\
|
||||
.row()
|
||||
.selectedRows()
|
||||
|
||||
if len(r) == 1:
|
||||
return r[0].row()
|
||||
else:
|
||||
return None
|
||||
|
||||
def sort_ascending(self):
|
||||
self._table.sort_profiles(False)
|
||||
self.select_current_profile()
|
||||
self.changed_slider_value()
|
||||
|
||||
def sort_descending(self):
|
||||
self._table.sort_profiles(True)
|
||||
|
||||
self.select_current_profile()
|
||||
self.changed_slider_value()
|
||||
|
||||
def move_up(self):
|
||||
row = self.index_selected_row()
|
||||
if row is None:
|
||||
return
|
||||
|
||||
self._table.move_up(row)
|
||||
self.select_current_profile()
|
||||
|
||||
def move_down(self):
|
||||
row = self.index_selected_row()
|
||||
if row is None:
|
||||
return
|
||||
|
||||
self._table.move_down(row)
|
||||
self.select_current_profile()
|
||||
|
||||
def purge(self):
|
||||
self._table.purge()
|
||||
self.update_redraw()
|
||||
|
||||
def purge(self):
|
||||
try:
|
||||
dlg = PurgeDialog(
|
||||
trad=self._trad,
|
||||
parent=self
|
||||
)
|
||||
if dlg.exec():
|
||||
self._table.purge(dlg.np_purge)
|
||||
except Exception as e:
|
||||
logger_exception(e)
|
||||
return
|
||||
|
||||
def duplicate(self):
|
||||
rows = [
|
||||
row.row() for row in
|
||||
|
||||
@@ -28,7 +28,7 @@ class BasicHydraulicStructuresTranslate(MainTranslate):
|
||||
super(BasicHydraulicStructuresTranslate, self).__init__()
|
||||
|
||||
self._dict["Basic Hydraulic Structures"] = _translate(
|
||||
"BasicHydraulicStructures", "Basic Hydraulic Structures"
|
||||
"BasicHydraulicStructures", "Basic Hydraulic Structure"
|
||||
)
|
||||
|
||||
self._dict['msg_type_change_title'] = _translate(
|
||||
@@ -138,7 +138,7 @@ hydraulic structure values?"
|
||||
"BasicHydraulicStructures", "Check valve"
|
||||
),
|
||||
"UD": _translate(
|
||||
"BasicHydraulicStructures", "User defined"
|
||||
"BasicHydraulicStructures", "User-defined"
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
@@ -252,8 +252,9 @@ class BasicHydraulicStructuresWindow(PamhyrWindow):
|
||||
self._checkbox.setChecked(self._hs.basic_structure(row).enabled)
|
||||
|
||||
def _set_basic_structure_state(self):
|
||||
row = self.index_selected_row()
|
||||
|
||||
rows = self.index_selected_rows()
|
||||
if len(rows) != 0:
|
||||
for row in rows:
|
||||
if row is not None:
|
||||
self._table.enabled(
|
||||
row,
|
||||
|
||||
@@ -20,8 +20,9 @@ from tools import timer
|
||||
from View.Tools.PamhyrPlot import PamhyrPlot
|
||||
|
||||
from PyQt5.QtCore import (
|
||||
QCoreApplication
|
||||
QCoreApplication, Qt,
|
||||
)
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
|
||||
from matplotlib.collections import LineCollection
|
||||
|
||||
@@ -50,6 +51,7 @@ class PlotKPC(PamhyrPlot):
|
||||
|
||||
self._auto_relim_update = True
|
||||
self._autoscale_update = True
|
||||
self.parent = parent
|
||||
|
||||
@property
|
||||
def river(self):
|
||||
@@ -91,7 +93,14 @@ class PlotKPC(PamhyrPlot):
|
||||
self.line_kp_zmin, = self.canvas.axes.plot(
|
||||
kp, z_min,
|
||||
color=self.color_plot_river_bottom,
|
||||
lw=1.
|
||||
lw=1.,
|
||||
)
|
||||
|
||||
self.line_kp_zmax, = self.canvas.axes.plot(
|
||||
kp, z_max,
|
||||
color=self.color_plot_river_bottom,
|
||||
lw=1.,
|
||||
alpha=0.0
|
||||
)
|
||||
|
||||
if len(kp) != 0:
|
||||
@@ -99,7 +108,8 @@ class PlotKPC(PamhyrPlot):
|
||||
x=kp,
|
||||
ymin=z_min, ymax=z_max,
|
||||
color=self.color_plot,
|
||||
lw=1.
|
||||
lw=1.,
|
||||
picker=10
|
||||
)
|
||||
|
||||
def draw_current(self):
|
||||
@@ -163,3 +173,40 @@ class PlotKPC(PamhyrPlot):
|
||||
self.profile.set_data([], [])
|
||||
|
||||
self.canvas.figure.canvas.draw_idle()
|
||||
|
||||
def onpick(self, event):
|
||||
if event.mouseevent.inaxes != self.canvas.axes:
|
||||
return
|
||||
if event.mouseevent.button.value != 1:
|
||||
return
|
||||
|
||||
modifiers = QApplication.keyboardModifiers()
|
||||
if modifiers not in [Qt.ControlModifier,
|
||||
Qt.NoModifier,
|
||||
Qt.ShiftModifier]:
|
||||
return
|
||||
|
||||
closest = self._closest_profile(event)
|
||||
index = self.parent.tableView.selectedIndexes()
|
||||
if self.parent._table is not None:
|
||||
self.parent._table.setData(index[2], closest)
|
||||
|
||||
return
|
||||
|
||||
def _closest_profile(self, event):
|
||||
|
||||
s = event.artist.get_segments()
|
||||
x = [i[0, 0] for i in s]
|
||||
mx = event.mouseevent.xdata
|
||||
points = enumerate(x)
|
||||
|
||||
def dist_mouse(point):
|
||||
x = point[1]
|
||||
d = abs(mx - x)
|
||||
return d
|
||||
|
||||
closest = min(
|
||||
points, key=dist_mouse
|
||||
)
|
||||
|
||||
return closest[1]
|
||||
@@ -141,7 +141,8 @@ class HydraulicStructuresWindow(PamhyrWindow):
|
||||
reach=None,
|
||||
profile=None,
|
||||
trad=self._trad,
|
||||
toolbar=self.toolbar
|
||||
toolbar=self.toolbar,
|
||||
parent=self
|
||||
)
|
||||
self.plot_kpc.draw()
|
||||
|
||||
@@ -262,7 +263,9 @@ class HydraulicStructuresWindow(PamhyrWindow):
|
||||
self._checkbox.setChecked(self._hs_lst.get(row).enabled)
|
||||
|
||||
def _set_structure_state(self):
|
||||
row = self.index_selected_row()
|
||||
rows = self.index_selected_rows()
|
||||
if len(rows) != 0:
|
||||
for row in rows:
|
||||
if row is not None:
|
||||
self._table.enabled(
|
||||
row,
|
||||
|
||||
@@ -28,7 +28,7 @@ from PyQt5.QtCore import (
|
||||
|
||||
from PyQt5.QtWidgets import (
|
||||
QDialogButtonBox, QComboBox, QUndoStack, QShortcut,
|
||||
QDoubleSpinBox,
|
||||
QDoubleSpinBox, QCheckBox,
|
||||
)
|
||||
|
||||
|
||||
@@ -45,9 +45,11 @@ class DischargeDialog(PamhyrDialog):
|
||||
)
|
||||
|
||||
self.value = None
|
||||
self.option = None
|
||||
|
||||
def accept(self):
|
||||
self.value = self.find(QDoubleSpinBox, "doubleSpinBox").value()
|
||||
self.option = self.find(QCheckBox, "checkBox").isChecked()
|
||||
super().accept()
|
||||
|
||||
def reject(self):
|
||||
|
||||
@@ -28,7 +28,7 @@ from PyQt5.QtCore import (
|
||||
|
||||
from PyQt5.QtWidgets import (
|
||||
QDialogButtonBox, QComboBox, QUndoStack, QShortcut,
|
||||
QDoubleSpinBox,
|
||||
QDoubleSpinBox, QCheckBox,
|
||||
)
|
||||
|
||||
|
||||
@@ -45,9 +45,11 @@ class HeightDialog(PamhyrDialog):
|
||||
)
|
||||
|
||||
self.value = None
|
||||
self.option = None
|
||||
|
||||
def accept(self):
|
||||
self.value = self.find(QDoubleSpinBox, "doubleSpinBox").value()
|
||||
self.option = self.find(QCheckBox, "checkBox").isChecked()
|
||||
super().accept()
|
||||
|
||||
def reject(self):
|
||||
|
||||
@@ -52,18 +52,41 @@ class PlotDischarge(PamhyrPlot):
|
||||
self._init = True
|
||||
|
||||
def draw_data(self):
|
||||
kp = self.data.reach.reach.get_kp()
|
||||
self.line_discharge = []
|
||||
|
||||
if len(self.data) != 0:
|
||||
kp = self.data.get_kp()
|
||||
discharge = self.data.get_discharge()
|
||||
|
||||
self.line_kp_zmin = self.canvas.axes.plot(
|
||||
line, = self.canvas.axes.plot(
|
||||
kp, discharge,
|
||||
color=self.color_plot,
|
||||
**self.plot_default_kargs
|
||||
)
|
||||
self.line_discharge.append(line)
|
||||
|
||||
@timer
|
||||
def update(self, ind=None):
|
||||
if not self._init:
|
||||
self.draw()
|
||||
|
||||
self.update_data()
|
||||
|
||||
self.update_idle()
|
||||
|
||||
def update_data(self):
|
||||
if len(self.data) == len(self.line_discharge):
|
||||
kp = self.data.get_kp()
|
||||
discharge = self.data.get_discharge()
|
||||
|
||||
line, = self.canvas.axes.plot(
|
||||
kp, discharge,
|
||||
color=self.color_plot,
|
||||
**self.plot_default_kargs
|
||||
)
|
||||
self.line_discharge.append(line)
|
||||
else:
|
||||
for line in self.line_discharge:
|
||||
line.remove()
|
||||
|
||||
self._draw_data()
|
||||
@@ -37,7 +37,7 @@ from View.Tools.PamhyrTable import PamhyrTableModel
|
||||
|
||||
from View.InitialConditions.UndoCommand import (
|
||||
SetCommand, AddCommand, DelCommand,
|
||||
SortCommand, MoveCommand, PasteCommand,
|
||||
SortCommand, MoveCommand, InsertCommand,
|
||||
DuplicateCommand, GenerateCommand,
|
||||
)
|
||||
|
||||
@@ -104,7 +104,18 @@ class InitialConditionTableModel(PamhyrTableModel):
|
||||
row = index.row()
|
||||
column = index.column()
|
||||
|
||||
if self._headers[column] not in ["name", "comment"]:
|
||||
if self._headers[column] is "speed":
|
||||
z = self._lst.get(row)["elevation"]
|
||||
q = self._lst.get(row)["discharge"]
|
||||
profile = self._reach.reach.get_profiles_from_kp(
|
||||
self._lst.get(row)["kp"]
|
||||
)
|
||||
if len(profile) >= 1:
|
||||
speed = profile[0].speed(q, z)
|
||||
return f"{speed:.4f}"
|
||||
|
||||
return ""
|
||||
elif self._headers[column] not in ["name", "comment"]:
|
||||
v = self._lst.get(row)[self._headers[column]]
|
||||
return f"{v:.4f}"
|
||||
else:
|
||||
@@ -186,7 +197,7 @@ class InitialConditionTableModel(PamhyrTableModel):
|
||||
self.endMoveRows()
|
||||
self.layoutChanged.emit()
|
||||
|
||||
def move_down(self, index, parent=QModelIndex()):
|
||||
def move_down(self, row, parent=QModelIndex()):
|
||||
if row > len(self._lst):
|
||||
return
|
||||
|
||||
@@ -203,6 +214,73 @@ class InitialConditionTableModel(PamhyrTableModel):
|
||||
self.endMoveRows()
|
||||
self.layoutChanged.emit()
|
||||
|
||||
def paste(self, index, header, data):
|
||||
if len(header) != 0:
|
||||
logger.error("Unexpected header in IC past data")
|
||||
return
|
||||
|
||||
if len(data) == 0:
|
||||
logger.error("Empty data")
|
||||
return
|
||||
|
||||
if len(data[0]) != 3:
|
||||
logger.error(f"Unexpected data size: [{data[0]}, ...]")
|
||||
return
|
||||
|
||||
self.layoutAboutToBeChanged.emit()
|
||||
|
||||
self._undo.push(
|
||||
InsertCommand(
|
||||
self._lst, index,
|
||||
list(
|
||||
map(
|
||||
lambda d: self._lst.new_from_data(*d),
|
||||
data
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
self.layoutAboutToBeChanged.emit()
|
||||
self.layoutChanged.emit()
|
||||
|
||||
def import_from_results(self, index, results):
|
||||
if results is None:
|
||||
logger.error("No results data")
|
||||
return
|
||||
|
||||
self.layoutAboutToBeChanged.emit()
|
||||
|
||||
ts = max(results.get("timestamps"))
|
||||
res_reach = results.river.get_reach_by_geometry(
|
||||
self._reach.reach
|
||||
)
|
||||
data = list(
|
||||
map(
|
||||
lambda p: [
|
||||
p.geometry.kp,
|
||||
p.get_ts_key(ts, "Q"),
|
||||
p.get_ts_key(ts, "Z"),
|
||||
],
|
||||
res_reach.profiles
|
||||
)
|
||||
)
|
||||
|
||||
self._undo.push(
|
||||
InsertCommand(
|
||||
self._lst, index,
|
||||
list(
|
||||
map(
|
||||
lambda d: self._lst.new_from_data(*d),
|
||||
data
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
self.layoutAboutToBeChanged.emit()
|
||||
self.layoutChanged.emit()
|
||||
|
||||
def undo(self):
|
||||
self._undo.undo()
|
||||
self.layoutChanged.emit()
|
||||
@@ -211,10 +289,10 @@ class InitialConditionTableModel(PamhyrTableModel):
|
||||
self._undo.redo()
|
||||
self.layoutChanged.emit()
|
||||
|
||||
def generate(self, generator, param):
|
||||
def generate(self, generator, param, option):
|
||||
self._undo.push(
|
||||
GenerateCommand(
|
||||
self._lst, generator, param
|
||||
self._lst, generator, param, option
|
||||
)
|
||||
)
|
||||
self.layoutChanged.emit()
|
||||
@@ -139,7 +139,7 @@ class MoveCommand(QUndoCommand):
|
||||
self._ics.move_down(self._i)
|
||||
|
||||
|
||||
class PasteCommand(QUndoCommand):
|
||||
class InsertCommand(QUndoCommand):
|
||||
def __init__(self, ics, row, ic):
|
||||
QUndoCommand.__init__(self)
|
||||
|
||||
@@ -174,11 +174,12 @@ class DuplicateCommand(QUndoCommand):
|
||||
|
||||
|
||||
class GenerateCommand(QUndoCommand):
|
||||
def __init__(self, ics, generator, param):
|
||||
def __init__(self, ics, generator, param, option):
|
||||
QUndoCommand.__init__(self)
|
||||
|
||||
self._ics = ics
|
||||
self._param = param
|
||||
self._option = option
|
||||
self._copy = self._ics.data
|
||||
self._generator = generator
|
||||
|
||||
@@ -187,6 +188,8 @@ class GenerateCommand(QUndoCommand):
|
||||
|
||||
def redo(self):
|
||||
if self._generator == "growing":
|
||||
self._ics.generate_growing_constante_height(self._param)
|
||||
self._ics.generate_growing_constante_height(self._param,
|
||||
self._option)
|
||||
elif self._generator == "discharge":
|
||||
self._ics.generate_discharge(self._param)
|
||||
self._ics.generate_discharge(self._param,
|
||||
self._option)
|
||||
@@ -16,9 +16,10 @@
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import logging
|
||||
|
||||
from tools import trace, timer
|
||||
from tools import trace, timer, logger_exception
|
||||
|
||||
from View.Tools.PamhyrWindow import PamhyrWindow
|
||||
|
||||
@@ -43,7 +44,7 @@ from Modules import Modules
|
||||
|
||||
from View.InitialConditions.UndoCommand import (
|
||||
SetCommand, AddCommand, DelCommand,
|
||||
SortCommand, MoveCommand, PasteCommand,
|
||||
SortCommand, MoveCommand, InsertCommand,
|
||||
DuplicateCommand,
|
||||
)
|
||||
|
||||
@@ -59,6 +60,9 @@ from View.InitialConditions.PlotDischarge import PlotDischarge
|
||||
from View.InitialConditions.translate import ICTranslate
|
||||
from View.InitialConditions.DialogHeight import HeightDialog
|
||||
from View.InitialConditions.DialogDischarge import DischargeDialog
|
||||
from View.Results.ReadingResultsDialog import ReadingResultsDialog
|
||||
|
||||
from Solver.Mage import Mage8
|
||||
|
||||
_translate = QCoreApplication.translate
|
||||
|
||||
@@ -166,6 +170,8 @@ class InitialConditionsWindow(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(QPushButton, "pushButton_generate_1").clicked.connect(
|
||||
self.generate_growing_constante_height
|
||||
@@ -179,12 +185,21 @@ class InitialConditionsWindow(PamhyrWindow):
|
||||
|
||||
def index_selected_row(self):
|
||||
table = self.find(QTableView, f"tableView")
|
||||
return table.selectionModel()\
|
||||
.selectedRows()[0]\
|
||||
.row()
|
||||
rows = table.selectionModel()\
|
||||
.selectedRows()
|
||||
|
||||
if len(rows) == 0:
|
||||
return 0
|
||||
|
||||
return rows[0].row()
|
||||
|
||||
def update(self):
|
||||
self.update(propagate=False)
|
||||
|
||||
def _update(self, propagate=True):
|
||||
self._update_plot()
|
||||
|
||||
if propagate:
|
||||
self._propagate_update(key=Modules.INITIAL_CONDITION)
|
||||
|
||||
def _update_plot(self):
|
||||
@@ -230,6 +245,42 @@ class InitialConditionsWindow(PamhyrWindow):
|
||||
self._table.sort(False)
|
||||
self._update()
|
||||
|
||||
def import_from_file(self):
|
||||
workdir = os.path.dirname(self._study.filename)
|
||||
|
||||
return self.file_dialog(
|
||||
callback=lambda d: self._import_from_file(d[0]),
|
||||
directory=workdir,
|
||||
default_suffix=".BIN",
|
||||
file_filter=["Mage (*.BIN)"],
|
||||
)
|
||||
|
||||
def _import_from_file(self, file_name):
|
||||
solver = Mage8("dummy")
|
||||
name = os.path.basename(file_name)\
|
||||
.replace(".BIN", "")
|
||||
|
||||
def reading():
|
||||
self._tmp_results = solver.results(
|
||||
self._study,
|
||||
os.path.dirname(file_name),
|
||||
name=name
|
||||
)
|
||||
|
||||
dlg = ReadingResultsDialog(
|
||||
reading_fn=reading,
|
||||
parent=self
|
||||
)
|
||||
dlg.exec_()
|
||||
results = self._tmp_results
|
||||
self._import_from_results(results)
|
||||
|
||||
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 move_up(self):
|
||||
row = self.index_selected_row()
|
||||
self._table.move_up(row)
|
||||
@@ -241,11 +292,49 @@ class InitialConditionsWindow(PamhyrWindow):
|
||||
self._update()
|
||||
|
||||
def _copy(self):
|
||||
logger.info("TODO: copy")
|
||||
self._update()
|
||||
rows = list(
|
||||
map(
|
||||
lambda row: row.row(),
|
||||
self.tableView.selectionModel().selectedRows()
|
||||
)
|
||||
)
|
||||
|
||||
table = list(
|
||||
map(
|
||||
lambda eic: list(
|
||||
map(
|
||||
lambda k: eic[1][k],
|
||||
["kp", "discharge", "elevation"]
|
||||
)
|
||||
),
|
||||
filter(
|
||||
lambda eic: eic[0] in rows,
|
||||
enumerate(self._ics.lst())
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
self.copyTableIntoClipboard(table)
|
||||
|
||||
def _paste(self):
|
||||
logger.info("TODO: paste")
|
||||
header, data = self.parseClipboardTable()
|
||||
|
||||
if len(data) + len(header) == 0:
|
||||
return
|
||||
|
||||
logger.debug(
|
||||
"IC: Paste: " +
|
||||
f"header = {header}, " +
|
||||
f"data = {data}"
|
||||
)
|
||||
|
||||
try:
|
||||
row = self.index_selected_row()
|
||||
# self._table.paste(row, header, data)
|
||||
self._table.paste(row, [], data)
|
||||
except Exception as e:
|
||||
logger_exception(e)
|
||||
|
||||
self._update()
|
||||
|
||||
def _undo(self):
|
||||
@@ -260,12 +349,14 @@ class InitialConditionsWindow(PamhyrWindow):
|
||||
dlg = HeightDialog(trad=self._trad, parent=self)
|
||||
if dlg.exec():
|
||||
value = dlg.value
|
||||
self._table.generate("growing", value)
|
||||
compute_discharge = dlg.option
|
||||
self._table.generate("growing", value, compute_discharge)
|
||||
self._update()
|
||||
|
||||
def generate_discharge(self):
|
||||
dlg = DischargeDialog(trad=self._trad, parent=self)
|
||||
if dlg.exec():
|
||||
value = dlg.value
|
||||
self._table.generate("discharge", value)
|
||||
compute_height = dlg.option
|
||||
self._table.generate("discharge", value, compute_height)
|
||||
self._update()
|
||||
@@ -41,9 +41,9 @@ class ICTranslate(MainTranslate):
|
||||
self._sub_dict["table_headers"] = {
|
||||
# "name": _translate("InitialCondition", "Name"),
|
||||
"kp": self._dict["unit_kp"],
|
||||
# "speed": _translate("InitialCondition", "Speed (m/s)"),
|
||||
"discharge": self._dict["unit_discharge"],
|
||||
"elevation": self._dict["unit_elevation"],
|
||||
"height": self._dict["unit_height"],
|
||||
"speed": self._dict["unit_speed"],
|
||||
# "comment": _translate("InitialCondition", "Comment"),
|
||||
}
|
||||
@@ -99,7 +99,7 @@ class TableModel(PamhyrTableModel):
|
||||
logger.info(e)
|
||||
logger.debug(traceback.format_exc())
|
||||
|
||||
self.dataChanged.emit(index, index)
|
||||
self.update()
|
||||
return True
|
||||
|
||||
def add(self, row, parent=QModelIndex()):
|
||||
@@ -112,7 +112,7 @@ class TableModel(PamhyrTableModel):
|
||||
)
|
||||
|
||||
self.endInsertRows()
|
||||
self.layoutChanged.emit()
|
||||
self.update()
|
||||
|
||||
def delete(self, rows, parent=QModelIndex()):
|
||||
self.beginRemoveRows(parent, rows[0], rows[-1])
|
||||
@@ -135,7 +135,7 @@ class TableModel(PamhyrTableModel):
|
||||
)
|
||||
|
||||
self.layoutAboutToBeChanged.emit()
|
||||
self.layoutChanged.emit()
|
||||
self.update()
|
||||
|
||||
def move_up(self, row, parent=QModelIndex()):
|
||||
if row <= 0:
|
||||
@@ -152,7 +152,7 @@ class TableModel(PamhyrTableModel):
|
||||
)
|
||||
|
||||
self.endMoveRows()
|
||||
self.layoutChanged.emit()
|
||||
self.update()
|
||||
|
||||
def move_down(self, index, parent=QModelIndex()):
|
||||
if row > len(self._data):
|
||||
@@ -169,7 +169,7 @@ class TableModel(PamhyrTableModel):
|
||||
)
|
||||
|
||||
self.endMoveRows()
|
||||
self.layoutChanged.emit()
|
||||
self.update()
|
||||
|
||||
def paste(self, row, header, data):
|
||||
if len(data) == 0:
|
||||
@@ -190,4 +190,13 @@ class TableModel(PamhyrTableModel):
|
||||
)
|
||||
|
||||
self.layoutAboutToBeChanged.emit()
|
||||
self.update()
|
||||
|
||||
def auto_sort(self):
|
||||
self.layoutAboutToBeChanged.emit()
|
||||
self._data.sort(key=lambda x: x[0])
|
||||
self.layoutAboutToBeChanged.emit()
|
||||
|
||||
def update(self):
|
||||
# self.auto_sort()
|
||||
self.layoutChanged.emit()
|
||||
@@ -137,6 +137,7 @@ class EditLateralContributionWindow(PamhyrWindow):
|
||||
self.find(QAction, "action_sort").triggered.connect(self.sort)
|
||||
|
||||
self._table.dataChanged.connect(self.update)
|
||||
self._table.layoutChanged.connect(self.update)
|
||||
|
||||
def update(self):
|
||||
self.plot.update()
|
||||
|
||||
@@ -28,7 +28,7 @@ class LCETranslate(LCTranslate):
|
||||
def __init__(self):
|
||||
super(LCETranslate, self).__init__()
|
||||
self._dict["Edit lateral contribution"] = _translate(
|
||||
"LateralContribution", "Edit lateral contribution"
|
||||
"LateralContribution", "Edit lateral sources"
|
||||
)
|
||||
|
||||
self._sub_dict["table_headers"] = {
|
||||
|
||||
@@ -0,0 +1,260 @@
|
||||
# PlotXY.py -- Pamhyr
|
||||
# Copyright (C) 2023-2024 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from tools import timer, trace
|
||||
from View.Tools.PamhyrPlot import PamhyrPlot
|
||||
from matplotlib import collections
|
||||
import numpy as np
|
||||
|
||||
from PyQt5.QtCore import (
|
||||
QCoreApplication, Qt, QItemSelectionModel,
|
||||
QItemSelection, QItemSelectionRange,
|
||||
)
|
||||
from PyQt5.QtWidgets import QApplication, QTableView
|
||||
|
||||
_translate = QCoreApplication.translate
|
||||
|
||||
|
||||
class PlotXY(PamhyrPlot):
|
||||
def __init__(self, canvas=None, trad=None, data=None, toolbar=None,
|
||||
table=None, parent=None):
|
||||
super(PlotXY, self).__init__(
|
||||
canvas=canvas,
|
||||
trad=trad,
|
||||
data=data,
|
||||
toolbar=toolbar,
|
||||
table=table,
|
||||
parent=parent
|
||||
)
|
||||
|
||||
self.line_xy = []
|
||||
self.line_gl = []
|
||||
|
||||
self.label_x = self._trad["x"]
|
||||
self.label_y = self._trad["y"]
|
||||
|
||||
self.before_plot_selected = None
|
||||
self.plot_selected = None
|
||||
self.after_plot_selected = None
|
||||
self.parent = parent
|
||||
self.line_xy_collection = None
|
||||
self._table = table
|
||||
self._colors = []
|
||||
self._style = []
|
||||
|
||||
def onpick(self, event):
|
||||
if event.mouseevent.inaxes != self.canvas.axes:
|
||||
return
|
||||
if event.mouseevent.button.value not in [1, 3]:
|
||||
return
|
||||
|
||||
closest = self._closest_section(event)
|
||||
t = self.parent.current_tab()
|
||||
tableView = self.parent.find(QTableView, f"tableView_{t}")
|
||||
table = self.parent._table[t]
|
||||
index = tableView.selectedIndexes()
|
||||
kp = self.data.get_kp()
|
||||
if self.parent._table is not None:
|
||||
if event.mouseevent.button.value == 1:
|
||||
table.setData(index[3], kp[closest[0]])
|
||||
if event.mouseevent.button.value == 3:
|
||||
table.setData(index[4], kp[closest[0]])
|
||||
|
||||
return
|
||||
|
||||
def _closest_section(self, event):
|
||||
axes = self.canvas.axes
|
||||
mx = event.mouseevent.xdata
|
||||
my = event.mouseevent.ydata
|
||||
bx, by = axes.get_xlim(), axes.get_ylim()
|
||||
ratio = (bx[0] - bx[1]) / (by[0] - by[1])
|
||||
|
||||
segments = event.artist.get_segments()
|
||||
ind = event.ind
|
||||
|
||||
points = []
|
||||
for i in ind:
|
||||
points = points + [[i, j] for j in segments[i]]
|
||||
|
||||
def dist_mouse(point):
|
||||
x, y = point[1]
|
||||
d2 = (((mx - x) / ratio) ** 2) + ((my - y) ** 2)
|
||||
return d2
|
||||
|
||||
closest = min(
|
||||
points, key=dist_mouse
|
||||
)
|
||||
|
||||
return closest
|
||||
|
||||
@timer
|
||||
def draw(self):
|
||||
self.init_axes()
|
||||
|
||||
if self.data is None:
|
||||
self.idle()
|
||||
return
|
||||
|
||||
if self.data.number_profiles == 0:
|
||||
self._init = False
|
||||
self.idle()
|
||||
return
|
||||
|
||||
self.draw_xy()
|
||||
self.draw_lr()
|
||||
self.draw_gl()
|
||||
|
||||
self.idle()
|
||||
self._init = True
|
||||
|
||||
def draw_xy(self):
|
||||
self.line_xy = []
|
||||
for xy in zip(self.data.get_x(), self.data.get_y()):
|
||||
self.line_xy.append(np.column_stack(xy))
|
||||
|
||||
self._colors, self._style = self.color_hightlight()
|
||||
self.line_xy_collection = collections.LineCollection(
|
||||
self.line_xy,
|
||||
colors=self._colors,
|
||||
linestyle=self._style,
|
||||
picker=10
|
||||
)
|
||||
self.canvas.axes.add_collection(self.line_xy_collection)
|
||||
|
||||
def color_hightlight(self):
|
||||
kp_min, kp_max = (-1, -1)
|
||||
if self._highlight_data is not None:
|
||||
kp_min, kp_max = self._highlight_data
|
||||
|
||||
colors = [self.color_plot for row in range(len(self._data))]
|
||||
for i, kp in enumerate(self.data.get_kp_complete_profiles()):
|
||||
if kp_min <= kp <= kp_max:
|
||||
colors[i] = self.color_plot_current
|
||||
style = ["-" for row in range(len(self._data))]
|
||||
|
||||
return colors, style
|
||||
|
||||
def draw_lr(self):
|
||||
lx = []
|
||||
ly = []
|
||||
rx = []
|
||||
ry = []
|
||||
|
||||
self.line_lr = []
|
||||
for x, y in zip(self.data.get_x(),
|
||||
self.data.get_y()):
|
||||
lx.append(x[0])
|
||||
ly.append(y[0])
|
||||
|
||||
rx.append(x[-1])
|
||||
ry.append(y[-1])
|
||||
|
||||
line = self.canvas.axes.plot(
|
||||
lx, ly,
|
||||
color=self.color_plot_river_bottom,
|
||||
linestyle="dotted",
|
||||
lw=1.,
|
||||
)
|
||||
self.line_lr.append(line)
|
||||
|
||||
line = self.canvas.axes.plot(
|
||||
rx, ry,
|
||||
color=self.color_plot_river_bottom,
|
||||
linestyle="dotted",
|
||||
lw=1.,
|
||||
)
|
||||
self.line_lr.append(line)
|
||||
|
||||
def draw_gl(self):
|
||||
x_complete = self.data.get_guidelines_x()
|
||||
y_complete = self.data.get_guidelines_y()
|
||||
|
||||
ind = 0
|
||||
self.line_gl = []
|
||||
for x, y in zip(x_complete, y_complete):
|
||||
line = self.canvas.axes.plot(
|
||||
x, y, color=self.colors[ind % len(self.colors)],
|
||||
linestyle=self.linestyle[ind // len(self.colors)]
|
||||
)
|
||||
self.line_gl.append(line)
|
||||
ind += 1
|
||||
|
||||
@timer
|
||||
def update(self):
|
||||
if not self._init:
|
||||
self.draw()
|
||||
return
|
||||
|
||||
if self.data is None:
|
||||
return
|
||||
|
||||
self.update_lr()
|
||||
self.update_gl()
|
||||
self.update_current()
|
||||
|
||||
self.update_idle()
|
||||
|
||||
def update_gl(self):
|
||||
self.data.compute_guidelines()
|
||||
x_complete = list(self.data.get_guidelines_x())
|
||||
y_complete = list(self.data.get_guidelines_y())
|
||||
|
||||
# TODO comprendre à quoi sert ce bout de code
|
||||
# ========>
|
||||
# for i in range(self.data.number_profiles):
|
||||
# if i < len(self.line_xy):
|
||||
# self.line_xy[i][0].set_data(
|
||||
# self.data.profile(i).x(),
|
||||
# self.data.profile(i).y()
|
||||
# )
|
||||
# else:
|
||||
# self.line_xy.append(
|
||||
# self.canvas.axes.plot(
|
||||
# self.data.profile(i).x(),
|
||||
# self.data.profile(i).y(),
|
||||
# color='r',
|
||||
# **self.plot_default_kargs
|
||||
# )
|
||||
# )
|
||||
# <========
|
||||
|
||||
for i in range(len(x_complete)):
|
||||
if i < len(self.line_gl):
|
||||
self.line_gl[i][0].set_data(
|
||||
x_complete[i],
|
||||
y_complete[i]
|
||||
)
|
||||
else:
|
||||
self.line_gl.append(
|
||||
self.canvas.axes.plot(
|
||||
x_complete[i],
|
||||
y_complete[i]
|
||||
)
|
||||
)
|
||||
|
||||
def update_current(self):
|
||||
if self._current_data_update:
|
||||
self._colors, self._style = self.color_hightlight()
|
||||
self.line_xy_collection.set_colors(self._colors)
|
||||
self.line_xy_collection.set_linestyle(self._style)
|
||||
|
||||
def update_lr(self):
|
||||
for line in self.line_lr:
|
||||
line[0].remove()
|
||||
|
||||
self.draw_lr()
|
||||
@@ -63,6 +63,14 @@ class ComboBoxDelegate(QItemDelegate):
|
||||
self._tab = tab
|
||||
self._trad = trad
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
return self._data
|
||||
|
||||
@data.setter
|
||||
def data(self, data):
|
||||
self._data = data
|
||||
|
||||
def createEditor(self, parent, option, index):
|
||||
self.editor = QComboBox(parent)
|
||||
long_types = self._trad.get_dict("long_types")
|
||||
@@ -78,6 +86,17 @@ class ComboBoxDelegate(QItemDelegate):
|
||||
)
|
||||
)
|
||||
self.editor.addItems(lst)
|
||||
elif self._mode == "kp":
|
||||
if self._data is None:
|
||||
self.editor.addItems(
|
||||
["0"]
|
||||
)
|
||||
else:
|
||||
self.editor.addItems(
|
||||
list(
|
||||
map(str, self._data.reach.get_kp())
|
||||
)
|
||||
)
|
||||
else:
|
||||
self.editor.addItems(
|
||||
[self._trad['not_associated']] +
|
||||
@@ -136,9 +155,9 @@ class TableModel(PamhyrTableModel):
|
||||
return self._trad['not_associated']
|
||||
return n.name
|
||||
elif self._headers[column] == "begin_kp":
|
||||
return self._lst.get(self._tab, row).begin_kp
|
||||
return str(self._lst.get(self._tab, row).begin_kp)
|
||||
elif self._headers[column] == "end_kp":
|
||||
return self._lst.get(self._tab, row).end_kp
|
||||
return str(self._lst.get(self._tab, row).end_kp)
|
||||
|
||||
return QVariant()
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ from View.LateralContribution.Table import (
|
||||
)
|
||||
|
||||
from View.Tools.Plot.PamhyrCanvas import MplCanvas
|
||||
from View.Geometry.PlotXY import PlotXY
|
||||
from View.LateralContribution.PlotXY import PlotXY
|
||||
from View.LateralContribution.translate import (
|
||||
LC_types, LCTranslate,
|
||||
)
|
||||
@@ -83,11 +83,14 @@ class LateralContributionWindow(PamhyrWindow):
|
||||
|
||||
self.setup_table()
|
||||
self.setup_graph()
|
||||
self.tabs = self.find(QTabWidget, "tabWidget")
|
||||
self.setup_connections()
|
||||
|
||||
def setup_table(self):
|
||||
self._table = {}
|
||||
|
||||
self._delegate_kp = []
|
||||
|
||||
for t in ["liquid", "solid", "suspenssion"]:
|
||||
self._delegate_type = ComboBoxDelegate(
|
||||
data=self._study.river,
|
||||
@@ -96,6 +99,16 @@ class LateralContributionWindow(PamhyrWindow):
|
||||
trad=self._trad,
|
||||
parent=self
|
||||
)
|
||||
|
||||
delegate_kp = ComboBoxDelegate(
|
||||
data=None,
|
||||
mode="kp",
|
||||
tab=t,
|
||||
trad=self._trad,
|
||||
parent=self
|
||||
)
|
||||
self._delegate_kp.append(delegate_kp)
|
||||
|
||||
self._delegate_edge = ComboBoxDelegate(
|
||||
data=self._study.river,
|
||||
mode="edge",
|
||||
@@ -112,6 +125,8 @@ class LateralContributionWindow(PamhyrWindow):
|
||||
delegates={
|
||||
"type": self._delegate_type,
|
||||
"edge": self._delegate_edge,
|
||||
"begin_kp": delegate_kp,
|
||||
"end_kp": delegate_kp,
|
||||
},
|
||||
data=self._study.river,
|
||||
undo=self._undo_stack,
|
||||
@@ -134,6 +149,7 @@ class LateralContributionWindow(PamhyrWindow):
|
||||
data=None,
|
||||
trad=self._trad,
|
||||
toolbar=None,
|
||||
parent=self
|
||||
)
|
||||
|
||||
def setup_connections(self):
|
||||
@@ -142,6 +158,8 @@ class LateralContributionWindow(PamhyrWindow):
|
||||
self.find(QAction, "action_edit").triggered.connect(self.edit)
|
||||
self.find(QAction, "action_sort").triggered.connect(self.sort)
|
||||
|
||||
self.tabs.currentChanged.connect(self._set_current_reach)
|
||||
|
||||
for t in ["liquid", "solid", "suspenssion"]:
|
||||
table = self.find(QTableView, f"tableView_{t}")
|
||||
table.selectionModel()\
|
||||
@@ -185,8 +203,7 @@ class LateralContributionWindow(PamhyrWindow):
|
||||
highlight = None
|
||||
|
||||
if len(rows) > 0:
|
||||
edge = self._study\
|
||||
.river\
|
||||
edge = self._study.river\
|
||||
.lateral_contribution\
|
||||
.get(tab, rows[0])\
|
||||
.edge
|
||||
@@ -195,11 +212,15 @@ class LateralContributionWindow(PamhyrWindow):
|
||||
lc = self._lcs.get(tab, rows[0])
|
||||
highlight = (lc.begin_kp, lc.end_kp)
|
||||
|
||||
for delegate in self._delegate_kp:
|
||||
delegate.data = edge
|
||||
|
||||
self.plot = PlotXY(
|
||||
canvas=self.canvas,
|
||||
data=data,
|
||||
trad=self._trad,
|
||||
toolbar=None,
|
||||
parent=self
|
||||
)
|
||||
self.plot.highlight = highlight
|
||||
self.plot.update()
|
||||
|
||||
@@ -39,12 +39,12 @@ class LCTranslate(MainTranslate):
|
||||
super(LCTranslate, self).__init__()
|
||||
|
||||
self._dict["Lateral contribution"] = _translate(
|
||||
"LateralContribution", "Lateral contribution"
|
||||
"LateralContribution", "Lateral sources"
|
||||
)
|
||||
|
||||
self._sub_dict["long_types"] = {
|
||||
"ND": self._dict["not_defined"],
|
||||
"LC": _translate("LateralContribution", "Lateral contribution"),
|
||||
"LC": _translate("LateralContribution", "Lateral sources"),
|
||||
"RA": _translate("LateralContribution", "Rain"),
|
||||
"EV": _translate("LateralContribution", "Evaporation"),
|
||||
}
|
||||
@@ -57,6 +57,6 @@ class LCTranslate(MainTranslate):
|
||||
"name": self._dict["name"],
|
||||
"type": self._dict["type"],
|
||||
"edge": self._dict["reach"],
|
||||
"begin_kp": _translate("LateralContribution", "Begin kp (m)"),
|
||||
"end_kp": _translate("LateralContribution", "End kp (m)")
|
||||
"begin_kp": _translate("LateralContribution", "Start (m)"),
|
||||
"end_kp": _translate("LateralContribution", "End (m)")
|
||||
}
|
||||
+48
-7
@@ -28,7 +28,7 @@ from tools import logger_exception
|
||||
|
||||
from PyQt5 import QtGui
|
||||
from PyQt5.QtGui import (
|
||||
QKeySequence, QDesktopServices,
|
||||
QKeySequence, QDesktopServices, QIcon,
|
||||
)
|
||||
|
||||
from PyQt5.QtCore import (
|
||||
@@ -333,6 +333,15 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
|
||||
self.update_enable_action()
|
||||
# self.showMaximized()
|
||||
|
||||
image = os.path.abspath(
|
||||
os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
"ui", "ressources",
|
||||
"Pamhyr2_logo.png"
|
||||
)
|
||||
)
|
||||
self.setWindowIcon(QIcon(image))
|
||||
|
||||
def set_debug_lvl(self, debug=True):
|
||||
if debug:
|
||||
logger.setLevel(logging.DEBUG)
|
||||
@@ -439,15 +448,32 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
|
||||
if Modules.CONFIG in keys:
|
||||
self._do_update_config()
|
||||
|
||||
if Modules.STUDY in keys:
|
||||
self._tab_widget_info.update()
|
||||
self._do_propagate_update_info_tab(keys)
|
||||
|
||||
logger.debug(f"Propagation of {keys}")
|
||||
for _, window in self.sub_win_list:
|
||||
window._propagated_update(key=keys)
|
||||
self._do_propagate_update_rec(window, keys)
|
||||
|
||||
self._tab_widget_checker.update(modules=keys)
|
||||
|
||||
def _do_propagate_update_info_tab(self, keys):
|
||||
modules = Modules.modelling_list()
|
||||
modules.append(Modules.STUDY)
|
||||
|
||||
has_info_mod = reduce(
|
||||
lambda acc, m: acc or (m in keys),
|
||||
modules, False
|
||||
)
|
||||
|
||||
if has_info_mod:
|
||||
self._tab_widget_info.update()
|
||||
|
||||
def _do_propagate_update_rec(self, window, keys):
|
||||
for _, win in window.sub_win_list:
|
||||
win._propagated_update(key=keys)
|
||||
self._do_propagate_update_rec(win, keys)
|
||||
|
||||
def update(self):
|
||||
self.set_title()
|
||||
|
||||
@@ -553,9 +579,13 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
|
||||
Nothing
|
||||
"""
|
||||
if self._study.filename is None or self._study.filename == "":
|
||||
options = QFileDialog.Options()
|
||||
options |= QFileDialog.DontUseNativeDialog
|
||||
|
||||
file_name, _ = QFileDialog.getSaveFileName(
|
||||
self, "Save File",
|
||||
"", "Pamhyr(*.pamhyr)"
|
||||
"", "Pamhyr(*.pamhyr)",
|
||||
options=options,
|
||||
)
|
||||
|
||||
if file_name.rsplit(".", 1)[-1] == "pamhyr":
|
||||
@@ -604,9 +634,13 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
|
||||
Returns:
|
||||
Nothing
|
||||
"""
|
||||
options = QFileDialog.Options()
|
||||
options |= QFileDialog.DontUseNativeDialog
|
||||
|
||||
file_name, _ = QFileDialog.getSaveFileName(
|
||||
self, "Save File",
|
||||
"", "Pamhyr(*.pamhyr)"
|
||||
"", "Pamhyr(*.pamhyr)",
|
||||
options=options,
|
||||
)
|
||||
|
||||
logger.debug(f"Save study as : {repr(file_name)}")
|
||||
@@ -886,7 +920,10 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
|
||||
Nothing
|
||||
"""
|
||||
if self._study is None:
|
||||
dialog = QFileDialog(self)
|
||||
options = QFileDialog.Options()
|
||||
options |= QFileDialog.DontUseNativeDialog
|
||||
|
||||
dialog = QFileDialog(self, options=options)
|
||||
dialog.setFileMode(QFileDialog.FileMode.ExistingFile)
|
||||
dialog.setDefaultSuffix(".pamhyr")
|
||||
# dialog.setFilter(dialog.filter() | QtCore.QDir.Hidden)
|
||||
@@ -1278,7 +1315,11 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
|
||||
if self._study.filename == "":
|
||||
return
|
||||
|
||||
dialog = QFileDialog(self)
|
||||
options = QFileDialog.Options()
|
||||
options |= QFileDialog.DontUseNativeDialog
|
||||
|
||||
dialog = QFileDialog(self, options=options)
|
||||
|
||||
dialog.setFileMode(QFileDialog.FileMode.ExistingFile)
|
||||
dialog.setDefaultSuffix(".BIN")
|
||||
# dialog.setFilter(dialog.filter() | QtCore.QDir.Hidden)
|
||||
|
||||
@@ -130,8 +130,9 @@ class WidgetChecker(PamhyrWidget):
|
||||
self._study = study
|
||||
|
||||
def _checkers_filtered(self, modules):
|
||||
impacted = modules | modules.impact()
|
||||
return filter(
|
||||
lambda c: c._modules in modules,
|
||||
lambda c: c._modules in impacted,
|
||||
self._checkers
|
||||
)
|
||||
|
||||
|
||||
+188
-33
@@ -120,6 +120,8 @@ class NodeItem(QGraphicsItem):
|
||||
|
||||
|
||||
class EdgeItem(QGraphicsItem):
|
||||
_arc_dist = 0.1
|
||||
|
||||
Type = QGraphicsItem.UserType + 2
|
||||
|
||||
def __init__(self, src_node_item, dest_node_item,
|
||||
@@ -131,6 +133,21 @@ class EdgeItem(QGraphicsItem):
|
||||
self.edge = edge
|
||||
|
||||
self.graph = graph_widget
|
||||
self._ind = 1
|
||||
|
||||
geometry = self.compute_arc_two_point(
|
||||
QPointF(
|
||||
self.src_node.pos().x(),
|
||||
self.src_node.pos().y()
|
||||
),
|
||||
QPointF(
|
||||
self.dest_node.pos().x(),
|
||||
self.dest_node.pos().y()
|
||||
),
|
||||
)
|
||||
|
||||
self._bound_rect = geometry['bound_rect']
|
||||
self._arc = geometry['arc']
|
||||
|
||||
self.setAcceptedMouseButtons(Qt.NoButton)
|
||||
|
||||
@@ -140,32 +157,21 @@ class EdgeItem(QGraphicsItem):
|
||||
def boundingRect(self):
|
||||
# Rectangle of edge for display update
|
||||
pen_width = 2.0
|
||||
extra = (pen_width + 5) / 2.0
|
||||
extra = 0 # (pen_width + 5) / 2.0
|
||||
|
||||
return QRectF(
|
||||
self.src_node.pos(),
|
||||
QSizeF(
|
||||
self.dest_node.pos().x() - self.src_node.pos().x(),
|
||||
self.dest_node.pos().y() - self.src_node.pos().y()
|
||||
return self._bound_rect.normalized().adjusted(
|
||||
-extra, -extra, extra, extra
|
||||
)
|
||||
).normalized().adjusted(-extra, -extra, extra, extra)
|
||||
|
||||
def shape(self):
|
||||
# Shape around edge for mouse selection
|
||||
vec = self.dest_node.pos() - self.src_node.pos()
|
||||
vec = vec * (5 / math.sqrt(QPointF.dotProduct(vec, vec)))
|
||||
extra = QPointF(vec.y(), -vec.x())
|
||||
return self._arc
|
||||
|
||||
result = QPainterPath(self.src_node.pos() - vec + extra)
|
||||
result.lineTo(self.src_node.pos() - vec - extra)
|
||||
result.lineTo(self.dest_node.pos() + vec - extra)
|
||||
result.lineTo(self.dest_node.pos() + vec + extra)
|
||||
result.closeSubpath()
|
||||
|
||||
return result
|
||||
def paint(self, painter, option, widget):
|
||||
# self.paint_line(painter, option, widget)
|
||||
self.paint_arc(painter, option, widget)
|
||||
|
||||
@timer
|
||||
def paint(self, painter, option, widget):
|
||||
def paint_line(self, painter, option, widget):
|
||||
# Draw shape of the edge
|
||||
# color = QColor(Qt.white)
|
||||
# color.setAlpha(128)
|
||||
@@ -191,33 +197,163 @@ class EdgeItem(QGraphicsItem):
|
||||
color, 2, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin
|
||||
)
|
||||
)
|
||||
# Draw the line
|
||||
|
||||
painter.drawLine(line)
|
||||
|
||||
line_center = QPointF(
|
||||
# Draw arrow
|
||||
center = QPointF(
|
||||
(self.src_node.pos().x() + self.dest_node.pos().x()) / 2,
|
||||
(self.src_node.pos().y() + self.dest_node.pos().y()) / 2,
|
||||
)
|
||||
|
||||
# Draw the arrows
|
||||
brush = QBrush()
|
||||
brush.setColor(color)
|
||||
brush.setStyle(Qt.SolidPattern)
|
||||
|
||||
angle = math.acos(line.dx() / line.length())
|
||||
if line.dy() >= 0:
|
||||
angle = (math.pi * 2.0) - angle
|
||||
|
||||
self.paint_arrow(
|
||||
painter, option, widget,
|
||||
color, center, angle
|
||||
)
|
||||
|
||||
@timer
|
||||
def paint_arc(self, painter, option, widget):
|
||||
line = QLineF(self.src_node.pos(), self.dest_node.pos())
|
||||
if line.length() == 0.0:
|
||||
return
|
||||
|
||||
# Select color
|
||||
# color = Qt.darkBlue
|
||||
color = Qt.black
|
||||
if self.graph.selected_item() == self:
|
||||
color = Qt.red
|
||||
elif self.graph.current_edge() == self:
|
||||
color = Qt.blue
|
||||
elif not self.graph.graph.is_enable_edge(self.edge):
|
||||
color = Qt.darkGray
|
||||
|
||||
painter.setPen(
|
||||
QPen(
|
||||
color, 2, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin
|
||||
)
|
||||
)
|
||||
|
||||
geometry = self.compute_arc_two_point(
|
||||
QPointF(
|
||||
self.src_node.pos().x(),
|
||||
self.src_node.pos().y()
|
||||
),
|
||||
QPointF(
|
||||
self.dest_node.pos().x(),
|
||||
self.dest_node.pos().y()
|
||||
),
|
||||
)
|
||||
|
||||
self._bound_rect = geometry['bound_rect']
|
||||
self._arc = geometry['arc']
|
||||
|
||||
painter.drawPath(self._arc)
|
||||
|
||||
line = geometry['line']
|
||||
angle = math.acos(line.dx() / line.length())
|
||||
if line.dy() >= 0:
|
||||
angle = (math.pi * 2.0) - angle
|
||||
|
||||
self.paint_arrow(
|
||||
painter, option, widget,
|
||||
color, geometry['c'], angle,
|
||||
)
|
||||
|
||||
def compute_arc_two_point(self, a, b):
|
||||
# This function is a black magic spell for invoking an arc
|
||||
# path between two points in Qt! (In fact, it's just cryptic
|
||||
# trigonometry functions from stackoverflow:
|
||||
# https://stackoverflow.com/questions/26901540/arc-in-qgraphicsscene)
|
||||
c = self.compute_arc_p3(a, b)
|
||||
|
||||
line_ac = QLineF(a, c)
|
||||
line_bc = QLineF(b, c)
|
||||
line_ab = QLineF(a, b)
|
||||
|
||||
rad = abs(
|
||||
line_bc.length() / (
|
||||
2 * math.sin(
|
||||
math.radians(
|
||||
line_ac.angleTo(line_ab)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
bisector_bc = QLineF(line_bc.pointAt(0.5), line_bc.p2())
|
||||
bisector_bc.setAngle(line_bc.normalVector().angle())
|
||||
|
||||
bisector_ab = QLineF(line_ab.pointAt(0.5), line_ab.p2())
|
||||
bisector_ab.setAngle(line_ab.normalVector().angle())
|
||||
|
||||
center = QPointF(0, 0)
|
||||
bisector_ab.intersect(bisector_bc, center)
|
||||
|
||||
circle = QRectF(
|
||||
center.x() - rad,
|
||||
center.y() - rad,
|
||||
rad * 2, rad * 2
|
||||
)
|
||||
|
||||
line_oa = QLineF(center, a)
|
||||
line_ob = QLineF(center, b)
|
||||
line_oc = QLineF(center, c)
|
||||
|
||||
start = line_ob
|
||||
end = line_oa
|
||||
|
||||
w = 1
|
||||
bounding_rect = circle.adjusted(
|
||||
-w, -w, w, w
|
||||
)
|
||||
|
||||
path = QPainterPath()
|
||||
path.arcMoveTo(circle, start.angle())
|
||||
path.arcTo(circle, start.angle(), start.angleTo(end))
|
||||
|
||||
return {
|
||||
'bound_rect': bounding_rect,
|
||||
'arc': path,
|
||||
'c': c,
|
||||
'line': line_ab,
|
||||
}
|
||||
|
||||
def compute_arc_p3(self, p1, p2):
|
||||
dist_p3 = self._ind * self._arc_dist
|
||||
|
||||
center_p1_p2 = QPointF(
|
||||
(p1.x() + p2.x()) / 2,
|
||||
(p1.y() + p2.y()) / 2
|
||||
)
|
||||
|
||||
# u = (p2.x() - p1.x(), p2.y() - p1.y())
|
||||
v = (p2.y() - p1.y(), p1.x() - p2.x())
|
||||
p3 = QPointF(
|
||||
center_p1_p2.x() + dist_p3 * v[0],
|
||||
center_p1_p2.y() + dist_p3 * v[1]
|
||||
)
|
||||
|
||||
return p3
|
||||
|
||||
def paint_arrow(self, painter, option, widget,
|
||||
color, center, angle):
|
||||
brush = QBrush()
|
||||
brush.setColor(color)
|
||||
brush.setStyle(Qt.SolidPattern)
|
||||
|
||||
size = 10.0
|
||||
arrow_p1 = line_center + QPointF(
|
||||
arrow_p1 = center + QPointF(
|
||||
math.sin(angle - math.pi / 3) * size,
|
||||
math.cos(angle - math.pi / 3) * size
|
||||
)
|
||||
arrow_p2 = line_center + QPointF(
|
||||
arrow_p2 = center + QPointF(
|
||||
math.sin(angle - math.pi + math.pi / 3) * size,
|
||||
math.cos(angle - math.pi + math.pi / 3) * size
|
||||
)
|
||||
poly = QPolygonF([line_center, arrow_p1, arrow_p2])
|
||||
poly = QPolygonF([center, arrow_p1, arrow_p2])
|
||||
path = QPainterPath()
|
||||
path.addPolygon(poly)
|
||||
|
||||
@@ -321,9 +457,13 @@ class GraphWidget(QGraphicsView):
|
||||
self.m_origin_y = 0.0
|
||||
self.clicked = False
|
||||
|
||||
self.setup_scene(min_size, max_size, size)
|
||||
|
||||
def setup_scene(self, min_size, max_size, size):
|
||||
scene = QGraphicsScene(self)
|
||||
scene.setItemIndexMethod(QGraphicsScene.NoIndex)
|
||||
scene.setSceneRect(0, 0, 2000, 2000)
|
||||
|
||||
self.setScene(scene)
|
||||
self.setCacheMode(QGraphicsView.CacheBackground)
|
||||
self.setViewportUpdateMode(QGraphicsView.BoundingRectViewportUpdate)
|
||||
@@ -363,25 +503,40 @@ class GraphWidget(QGraphicsView):
|
||||
|
||||
curr_edge = self.graph.current_reach()
|
||||
|
||||
iedges = []
|
||||
multiple_edges = {}
|
||||
for edge in self.graph.edges():
|
||||
n1 = list(
|
||||
n1 = next(
|
||||
filter(
|
||||
lambda n: n.node.name == edge.node1.name,
|
||||
self.node_items
|
||||
)
|
||||
)
|
||||
|
||||
n2 = list(
|
||||
n2 = next(
|
||||
filter(
|
||||
lambda n: n.node.name == edge.node2.name,
|
||||
self.node_items
|
||||
)
|
||||
)
|
||||
|
||||
iedge = EdgeItem(n1[0], n2[0], edge, self)
|
||||
# Multiple edges counter
|
||||
if (n1, n2) not in multiple_edges:
|
||||
ind = 1
|
||||
else:
|
||||
ind = multiple_edges[(n1, n2)] + 1
|
||||
|
||||
multiple_edges[(n1, n2)] = ind
|
||||
|
||||
iedge = EdgeItem(n1, n2, edge, self)
|
||||
iedge._ind = ind
|
||||
|
||||
if edge == curr_edge:
|
||||
self._current_edge = iedge
|
||||
|
||||
iedges.append(iedge)
|
||||
|
||||
for iedge in reversed(iedges):
|
||||
self.scene().addItem(iedge)
|
||||
self.edge_items.append(iedge)
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ class AddNodeCommand(QUndoCommand):
|
||||
self._node = node
|
||||
|
||||
def undo(self):
|
||||
self._graph.remove_node(self._node.name)
|
||||
self._graph.remove_node(self._node)
|
||||
|
||||
def redo(self):
|
||||
self._graph.insert_node(self._node)
|
||||
@@ -85,9 +85,9 @@ class DelNodeCommand(QUndoCommand):
|
||||
)
|
||||
|
||||
for edge in self._edges:
|
||||
self._graph.remove_edge(edge.name)
|
||||
self._graph.remove_edge(edge)
|
||||
|
||||
self._graph.remove_node(self._node.name)
|
||||
self._graph.remove_node(self._node)
|
||||
|
||||
|
||||
class AddEdgeCommand(QUndoCommand):
|
||||
@@ -98,7 +98,7 @@ class AddEdgeCommand(QUndoCommand):
|
||||
self._edge = edge
|
||||
|
||||
def undo(self):
|
||||
self._graph.remove_edge(self._edge.name)
|
||||
self._graph.remove_edge(self._edge)
|
||||
|
||||
def redo(self):
|
||||
self._graph.insert_edge(self._edge)
|
||||
@@ -115,7 +115,7 @@ class DelEdgeCommand(QUndoCommand):
|
||||
self._graph.insert_edge(self._edge)
|
||||
|
||||
def redo(self):
|
||||
self._graph.remove_edge(self._edge.name)
|
||||
self._graph.remove_edge(self._edge)
|
||||
|
||||
|
||||
class SetCommand(QUndoCommand):
|
||||
|
||||
@@ -54,10 +54,12 @@ class NetworkTranslate(MainTranslate):
|
||||
self._sub_dict["table_headers_node"] = {
|
||||
"name": self._dict['name'],
|
||||
"type": self._dict['type'],
|
||||
"id": _translate("Network", "Index"),
|
||||
}
|
||||
|
||||
self._sub_dict["table_headers_edge"] = {
|
||||
"name": self._dict['name'],
|
||||
"node1": _translate("Network", "Source node"),
|
||||
"node2": _translate("Network", "Destination node"),
|
||||
"id": _translate("Network", "Index"),
|
||||
}
|
||||
@@ -77,9 +77,9 @@ class CustomPlot(PamhyrPlot):
|
||||
kp = reach.geometry.get_kp()
|
||||
z_min = reach.geometry.get_z_min()
|
||||
|
||||
self.canvas.axes.set_xlim(
|
||||
left=min(kp), right=max(kp)
|
||||
)
|
||||
# self.canvas.axes.set_xlim(
|
||||
# left=min(kp), right=max(kp)
|
||||
# )
|
||||
|
||||
meter_axes = self.canvas.axes
|
||||
m3S_axes = self.canvas.axes
|
||||
@@ -88,10 +88,10 @@ class CustomPlot(PamhyrPlot):
|
||||
|
||||
lines = {}
|
||||
if "elevation" in self._y:
|
||||
meter_axes.set_ylim(
|
||||
bottom=min(0, min(z_min)),
|
||||
top=max(z_min) + 1
|
||||
)
|
||||
# meter_axes.set_ylim(
|
||||
# bottom=min(0, min(z_min)),
|
||||
# top=max(z_min) + 1
|
||||
# )
|
||||
|
||||
line = meter_axes.plot(
|
||||
kp, z_min,
|
||||
@@ -108,10 +108,10 @@ class CustomPlot(PamhyrPlot):
|
||||
)
|
||||
)
|
||||
|
||||
meter_axes.set_ylim(
|
||||
bottom=min(0, min(z_min)),
|
||||
top=max(water_z) + 1
|
||||
)
|
||||
# meter_axes.set_ylim(
|
||||
# bottom=min(0, min(z_min)),
|
||||
# top=max(water_z) + 1
|
||||
# )
|
||||
|
||||
line = meter_axes.plot(
|
||||
kp, water_z, lw=1.,
|
||||
@@ -133,10 +133,10 @@ class CustomPlot(PamhyrPlot):
|
||||
)
|
||||
)
|
||||
|
||||
m3s_axes.set_ylim(
|
||||
bottom=min(0, min(q)),
|
||||
top=max(q) + 1
|
||||
)
|
||||
# m3s_axes.set_ylim(
|
||||
# bottom=min(0, min(q)),
|
||||
# top=max(q) + 1
|
||||
# )
|
||||
|
||||
line = m3s_axes.plot(
|
||||
kp, q, lw=1.,
|
||||
@@ -206,9 +206,9 @@ class CustomPlot(PamhyrPlot):
|
||||
ts = list(results.get("timestamps"))
|
||||
ts.sort()
|
||||
|
||||
self.canvas.axes.set_xlim(
|
||||
left=min(ts), right=max(ts)
|
||||
)
|
||||
# self.canvas.axes.set_xlim(
|
||||
# left=min(ts), right=max(ts)
|
||||
# )
|
||||
|
||||
x = ts
|
||||
lines = {}
|
||||
@@ -232,10 +232,10 @@ class CustomPlot(PamhyrPlot):
|
||||
# Water elevation
|
||||
z = profile.get_key("Z")
|
||||
|
||||
meter_axes.set_ylim(
|
||||
bottom=min(0, min(z)),
|
||||
top=max(z) + 1
|
||||
)
|
||||
# meter_axes.set_ylim(
|
||||
# bottom=min(0, min(z)),
|
||||
# top=max(z) + 1
|
||||
# )
|
||||
|
||||
line = meter_axes.plot(
|
||||
ts, z, lw=1.,
|
||||
@@ -260,10 +260,10 @@ class CustomPlot(PamhyrPlot):
|
||||
if "discharge" in self._y:
|
||||
q = profile.get_key("Q")
|
||||
|
||||
m3s_axes.set_ylim(
|
||||
bottom=min(0, min(q)),
|
||||
top=max(q) + 1
|
||||
)
|
||||
# m3s_axes.set_ylim(
|
||||
# bottom=min(0, min(q)),
|
||||
# top=max(q) + 1
|
||||
# )
|
||||
|
||||
line = m3s_axes.plot(
|
||||
ts, q, lw=1.,
|
||||
|
||||
@@ -28,7 +28,7 @@ class CustomPlotTranslate(ResultsTranslate):
|
||||
super(CustomPlotTranslate, self).__init__()
|
||||
|
||||
self._dict["Custom Plot Selection"] = _translate(
|
||||
"CustomPlot", "Custom Plot Selection"
|
||||
"CustomPlot", "Custom plot selection"
|
||||
)
|
||||
|
||||
# Value type
|
||||
@@ -38,13 +38,13 @@ class CustomPlotTranslate(ResultsTranslate):
|
||||
self._dict['water_elevation'] = self._dict["unit_water_elevation"]
|
||||
self._dict['discharge'] = self._dict["unit_discharge"]
|
||||
self._dict['elevation'] = _translate(
|
||||
"CustomPlot", "Bed load elevation (m)"
|
||||
"CustomPlot", "Bed elevation (m)"
|
||||
)
|
||||
|
||||
# Unit corresponding long name (plot axes display)
|
||||
|
||||
self._dict['0-meter'] = _translate(
|
||||
"CustomPlot", "Bed load elevation (m)"
|
||||
"CustomPlot", "Bed elevation (m)"
|
||||
)
|
||||
self._dict['1-m3s'] = self._dict["unit_discharge"]
|
||||
|
||||
|
||||
@@ -112,16 +112,22 @@ class PlotH(PamhyrPlot):
|
||||
)
|
||||
|
||||
def draw_current(self, reach, profile):
|
||||
y = profile.get_key("Q")
|
||||
min_y, max_y = reduce(
|
||||
lambda acc, p: (
|
||||
acc[0] + [min(p.get_key("Q"))],
|
||||
acc[1] + [max(p.get_key("Q"))]
|
||||
),
|
||||
reach.profiles,
|
||||
([], [])
|
||||
)
|
||||
|
||||
kargs = self.plot_default_kargs.copy()
|
||||
kargs["marker"] = "o"
|
||||
self._current, = self.canvas.axes.plot(
|
||||
self._current_timestamp,
|
||||
y[self.ts.index(self._current_timestamp)],
|
||||
[self._current_timestamp, self._current_timestamp],
|
||||
[min(min_y), max(max_y)],
|
||||
# label=self.label_timestamp,
|
||||
color=self.color_plot_current,
|
||||
**kargs
|
||||
color=self.color_plot_river_bottom,
|
||||
linestyle="dashed",
|
||||
lw=1.,
|
||||
)
|
||||
|
||||
def draw_max(self, reach):
|
||||
@@ -175,7 +181,8 @@ class PlotH(PamhyrPlot):
|
||||
|
||||
self._line.set_data(x, y)
|
||||
|
||||
_, min_max = self._current.get_data()
|
||||
self._current.set_data(
|
||||
self._current_timestamp,
|
||||
y[self.ts.index(self._current_timestamp)]
|
||||
min_max
|
||||
)
|
||||
+79
-118
@@ -79,6 +79,7 @@ class PlotKPC(PamhyrPlot):
|
||||
self.draw_water_elevation_max(reach)
|
||||
self.draw_water_elevation_overflow(reach)
|
||||
self.draw_current(reach)
|
||||
self.draw_profiles_hs(reach)
|
||||
|
||||
# self.enable_legend()
|
||||
|
||||
@@ -92,128 +93,101 @@ class PlotKPC(PamhyrPlot):
|
||||
self.draw_bottom_geometry(reach)
|
||||
|
||||
def draw_bottom_with_bedload(self, reach):
|
||||
self._bedrock = self.sl_compute_bedrock(reach)
|
||||
|
||||
kp = reach.geometry.get_kp()
|
||||
z = self.sl_compute_current_z(reach)
|
||||
|
||||
self.line_bottom, = self.canvas.axes.plot(
|
||||
kp, z,
|
||||
linestyle="solid", lw=1.,
|
||||
color=self.color_plot_river_bottom,
|
||||
)
|
||||
|
||||
self._river_bottom = z
|
||||
|
||||
def draw_profiles_hs(self, reach):
|
||||
lhs = filter(
|
||||
lambda hs: hs._input_reach.reach is reach.geometry,
|
||||
filter(
|
||||
lambda hs: hs._input_reach is not None,
|
||||
self.results.study.river.hydraulic_structures.lst
|
||||
)
|
||||
)
|
||||
|
||||
for hs in lhs:
|
||||
x = hs.input_kp
|
||||
z_min = reach.geometry.get_z_min()
|
||||
z_max = reach.geometry.get_z_max()
|
||||
|
||||
initial_sl = self.sl_compute_initial(reach)
|
||||
current_sl = self.sl_compute_current(reach)
|
||||
|
||||
max_sl_num = reduce(
|
||||
lambda acc, sl: max(acc, len(sl)),
|
||||
current_sl, 0
|
||||
)
|
||||
|
||||
sl_init, sl = self.sl_completed_layers(
|
||||
initial_sl, current_sl, max_sl_num
|
||||
)
|
||||
|
||||
z_sl = self.sl_apply_z_min_to_initial(sl_init, z_min)
|
||||
d_sl = self.sl_compute_diff(sl_init, sl)
|
||||
|
||||
final_z_sl = self.sl_apply_diff(reach, z_sl, d_sl)
|
||||
final_z_sl = list(reversed(final_z_sl))
|
||||
|
||||
self.line_kp_sl = []
|
||||
for i, z in enumerate(final_z_sl):
|
||||
self.line_kp_sl.append(None)
|
||||
self.line_kp_sl[i], = self.canvas.axes.plot(
|
||||
kp, z,
|
||||
linestyle="solid" if i == len(final_z_sl) - 1 else "--",
|
||||
self.canvas.axes.plot(
|
||||
[x, x],
|
||||
[min(z_min), max(z_max)],
|
||||
linestyle="--",
|
||||
lw=1.,
|
||||
color=(
|
||||
self.color_plot_river_bottom if i == len(final_z_sl) - 1
|
||||
else self.colors[i]
|
||||
color=self.color_plot_previous,
|
||||
)
|
||||
|
||||
self.canvas.axes.annotate(
|
||||
" > " + hs.name,
|
||||
(x, max(z_max)),
|
||||
horizontalalignment='left',
|
||||
verticalalignment='top',
|
||||
annotation_clip=True,
|
||||
fontsize=9, color=self.color_plot_previous,
|
||||
)
|
||||
|
||||
def sl_compute_bedrock(self, reach):
|
||||
z_min = reach.geometry.get_z_min()
|
||||
sl = self.sl_compute_initial(reach)
|
||||
|
||||
z = list(
|
||||
map(
|
||||
lambda z, sl: reduce(
|
||||
lambda z, h: z - h[0],
|
||||
sl, z
|
||||
),
|
||||
z_min, # Original geometry
|
||||
sl # Original sediment layers
|
||||
)
|
||||
)
|
||||
|
||||
self._initial_sl = initial_sl
|
||||
self._river_bottom = final_z_sl[-1]
|
||||
return z
|
||||
|
||||
def sl_compute_current_z(self, reach):
|
||||
z_br = self._bedrock
|
||||
sl = self.sl_compute_current(reach)
|
||||
|
||||
z = list(
|
||||
map(
|
||||
lambda z, sl: reduce(
|
||||
lambda z, h: z + h[0],
|
||||
sl, z
|
||||
),
|
||||
z_br, # Bedrock elevation
|
||||
sl # Current sediment layers
|
||||
)
|
||||
)
|
||||
|
||||
return z
|
||||
|
||||
def sl_compute_initial(self, reach):
|
||||
"""
|
||||
Get SL list for profile p at initial time (initial data)
|
||||
"""
|
||||
return list(
|
||||
map(
|
||||
return map(
|
||||
lambda p: p.get_ts_key(min(self._timestamps), "sl")[0],
|
||||
reach.profiles
|
||||
)
|
||||
)
|
||||
|
||||
def sl_compute_current(self, reach):
|
||||
"""
|
||||
Get SL list for profile p at current time
|
||||
"""
|
||||
return list(
|
||||
map(
|
||||
return map(
|
||||
lambda p: p.get_ts_key(self._current_timestamp, "sl")[0],
|
||||
reach.profiles
|
||||
)
|
||||
)
|
||||
|
||||
def sl_completed_layers(self, initial_sl, current_sl, max_sl_num):
|
||||
sl = []
|
||||
sl_init = []
|
||||
for i in range(max_sl_num):
|
||||
cur = []
|
||||
cur_init = []
|
||||
for current, initial in zip(current_sl, initial_sl):
|
||||
if i < len(initial_sl):
|
||||
cur.append(current[i][0])
|
||||
cur_init.append(initial[i][0])
|
||||
else:
|
||||
cur.append(0)
|
||||
cur_init.append(0)
|
||||
sl.append(cur)
|
||||
sl_init.append(cur_init)
|
||||
|
||||
return sl_init, sl
|
||||
|
||||
def sl_apply_z_min_to_initial(self, sl_init, z_min):
|
||||
"""
|
||||
Compute sediment layer from initial data in function to profile
|
||||
z_min
|
||||
"""
|
||||
return reduce(
|
||||
lambda acc, v: acc + [
|
||||
list(
|
||||
map(
|
||||
lambda x, y: y - x,
|
||||
v, acc[-1]
|
||||
)
|
||||
)
|
||||
],
|
||||
sl_init, [z_min]
|
||||
)
|
||||
|
||||
def sl_compute_diff(self, sl_init, sl):
|
||||
return list(
|
||||
map(
|
||||
lambda ln0, lni: list(
|
||||
map(
|
||||
lambda z0, zi: z0 - zi,
|
||||
ln0, lni
|
||||
)
|
||||
),
|
||||
sl_init, sl
|
||||
)
|
||||
)
|
||||
|
||||
def sl_apply_diff(self, reach, z_sl, d_sl):
|
||||
f = list(map(lambda p: 0, reach.profiles))
|
||||
|
||||
return list(
|
||||
map(
|
||||
lambda z, d: list(
|
||||
map(
|
||||
lambda zn, dn: zn - dn,
|
||||
z, d
|
||||
)
|
||||
),
|
||||
z_sl, d_sl + [f] # HACK: Add dummy data for last layer
|
||||
)
|
||||
)
|
||||
|
||||
def draw_bottom_geometry(self, reach):
|
||||
kp = reach.geometry.get_kp()
|
||||
@@ -395,27 +369,14 @@ class PlotKPC(PamhyrPlot):
|
||||
def update_bottom_with_bedload(self):
|
||||
reach = self.results.river.reach(self._current_reach_id)
|
||||
kp = reach.geometry.get_kp()
|
||||
z_min = reach.geometry.get_z_min()
|
||||
z = self.sl_compute_current_z(reach)
|
||||
|
||||
initial_sl = self._initial_sl
|
||||
current_sl = self.sl_compute_current(reach)
|
||||
self.line_bottom.remove()
|
||||
|
||||
max_sl_num = reduce(
|
||||
lambda acc, sl: max(acc, len(sl)),
|
||||
current_sl, 0
|
||||
self.line_bottom, = self.canvas.axes.plot(
|
||||
kp, z,
|
||||
linestyle="solid", lw=1.,
|
||||
color=self.color_plot_river_bottom,
|
||||
)
|
||||
|
||||
sl_init, sl = self.sl_completed_layers(
|
||||
initial_sl, current_sl, max_sl_num
|
||||
)
|
||||
|
||||
z_sl = self.sl_apply_z_min_to_initial(sl_init, z_min)
|
||||
d_sl = self.sl_compute_diff(sl_init, sl)
|
||||
|
||||
final_z_sl = self.sl_apply_diff(reach, z_sl, d_sl)
|
||||
final_z_sl = list(reversed(final_z_sl))
|
||||
|
||||
for i, z in enumerate(final_z_sl):
|
||||
self.line_kp_sl[i].set_data(kp, z)
|
||||
|
||||
self._river_bottom = final_z_sl[-1]
|
||||
self._river_bottom = z
|
||||
@@ -231,7 +231,7 @@ class PlotSedReach(PamhyrPlot):
|
||||
return
|
||||
|
||||
self.canvas.axes.set_xlabel(
|
||||
_translate("MainWindow_reach", "Kp (m)"),
|
||||
_translate("MainWindow_reach", "Position (m)"),
|
||||
color='black', fontsize=10
|
||||
)
|
||||
self.canvas.axes.set_ylabel(
|
||||
|
||||
+101
-12
@@ -22,10 +22,14 @@ from functools import reduce
|
||||
|
||||
from tools import timer, trace
|
||||
from View.Tools.PamhyrPlot import PamhyrPlot
|
||||
import numpy as np
|
||||
from matplotlib import collections
|
||||
|
||||
from PyQt5.QtCore import (
|
||||
QCoreApplication
|
||||
QCoreApplication, Qt, QItemSelectionModel,
|
||||
QItemSelection, QItemSelectionRange
|
||||
)
|
||||
from PyQt5.QtWidgets import QApplication, QTableView
|
||||
|
||||
_translate = QCoreApplication.translate
|
||||
|
||||
@@ -57,6 +61,73 @@ class PlotXY(PamhyrPlot):
|
||||
self.label_y = _translate("Results", "Y (m)")
|
||||
|
||||
self._isometric_axis = True
|
||||
self._tablemodel = parent._table["profile"]
|
||||
self._table = parent.find(QTableView, f"tableView_profile")
|
||||
|
||||
def onpick(self, event):
|
||||
if event.mouseevent.inaxes != self.canvas.axes:
|
||||
return
|
||||
if event.mouseevent.button.value != 1:
|
||||
return
|
||||
|
||||
modifiers = QApplication.keyboardModifiers()
|
||||
if modifiers not in [Qt.ControlModifier,
|
||||
Qt.NoModifier,
|
||||
Qt.ShiftModifier]:
|
||||
return
|
||||
|
||||
ind, point = self._closest_section(event)
|
||||
if self._table is None:
|
||||
return
|
||||
self._select_in_table([ind])
|
||||
self._table.blockSignals(False)
|
||||
|
||||
return
|
||||
|
||||
def _closest_section(self, event):
|
||||
axes = self.canvas.axes
|
||||
mx = event.mouseevent.xdata
|
||||
my = event.mouseevent.ydata
|
||||
bx, by = axes.get_xlim(), axes.get_ylim()
|
||||
ratio = (bx[0] - bx[1]) / (by[0] - by[1])
|
||||
|
||||
segments = event.artist.get_segments()
|
||||
ind = event.ind
|
||||
|
||||
points = []
|
||||
for i in ind:
|
||||
points = points + [[i, j] for j in segments[i]]
|
||||
|
||||
def dist_mouse(point):
|
||||
x, y = point[1]
|
||||
d2 = (((mx - x) / ratio) ** 2) + ((my - y) ** 2)
|
||||
return d2
|
||||
|
||||
closest = min(
|
||||
points, key=dist_mouse
|
||||
)
|
||||
|
||||
return closest
|
||||
|
||||
def _select_in_table(self, ind):
|
||||
if self._table is None:
|
||||
return
|
||||
self._table.setFocus()
|
||||
selection = self._table.selectionModel()
|
||||
index = QItemSelection()
|
||||
if len(ind) == 0:
|
||||
return
|
||||
for i in ind:
|
||||
index.append(QItemSelectionRange(self._table.model().index(i, 0)))
|
||||
selection.select(
|
||||
index,
|
||||
QItemSelectionModel.Rows |
|
||||
QItemSelectionModel.ClearAndSelect |
|
||||
QItemSelectionModel.Select
|
||||
)
|
||||
|
||||
if len(ind) > 0:
|
||||
self._table.scrollTo(self._table.model().index(ind[-1], 0))
|
||||
|
||||
@property
|
||||
def results(self):
|
||||
@@ -90,18 +161,36 @@ class PlotXY(PamhyrPlot):
|
||||
self._init = False
|
||||
return
|
||||
|
||||
self.line_xy = [
|
||||
self.canvas.axes.plot(
|
||||
x, y,
|
||||
color=self.color_plot_river_bottom,
|
||||
**self.plot_default_kargs
|
||||
self.line_xy = []
|
||||
for xy in zip(reach.geometry.get_x(),
|
||||
reach.geometry.get_y()):
|
||||
self.line_xy.append(np.column_stack(xy))
|
||||
|
||||
self._colors, self._style = self.color_hightlight()
|
||||
self.line_xy_collection = collections.LineCollection(
|
||||
self.line_xy,
|
||||
colors=self._colors,
|
||||
linestyle=self._style,
|
||||
picker=10
|
||||
)
|
||||
for x, y, kp in zip(
|
||||
reach.geometry.get_x(),
|
||||
reach.geometry.get_y(),
|
||||
reach.geometry.get_kp()
|
||||
)
|
||||
]
|
||||
self.canvas.axes.add_collection(self.line_xy_collection)
|
||||
|
||||
def color_hightlight(self):
|
||||
reach = self.results.river.reach(self._current_reach_id)
|
||||
rows = [self._current_profile_id]
|
||||
colors = [self.color_plot
|
||||
for row in range(reach.geometry.number_profiles)]
|
||||
style = ["-" for row in range(reach.geometry.number_profiles)]
|
||||
if len(rows) > 0:
|
||||
for row in rows:
|
||||
colors[row] = self.color_plot_current
|
||||
if rows[0] > 0:
|
||||
colors[rows[0]-1] = self.color_plot_previous
|
||||
style[rows[0]-1] = "--"
|
||||
if rows[-1] < reach.geometry.number_profiles-1:
|
||||
colors[rows[-1]+1] = self.color_plot_next
|
||||
style[rows[-1]+1] = "--"
|
||||
return colors, style
|
||||
|
||||
def draw_guide_lines(self, reach):
|
||||
x_complete = reach.geometry.get_guidelines_x()
|
||||
|
||||
@@ -86,6 +86,12 @@ class TableModel(PamhyrTableModel):
|
||||
elif self._headers[column] == "discharge":
|
||||
v = self._lst[row].get_ts_key(self._timestamp, "Q")
|
||||
return f"{v:.4f}"
|
||||
elif self._headers[column] == "speed":
|
||||
q = self._lst[row].get_ts_key(self._timestamp, "Q")
|
||||
z = self._lst[row].get_ts_key(self._timestamp, "Q")
|
||||
|
||||
v = self._lst[row].geometry.speed(q, z)
|
||||
return f"{v:.4f}"
|
||||
|
||||
return QVariant()
|
||||
|
||||
|
||||
@@ -176,7 +176,8 @@ class ResultsWindow(PamhyrWindow):
|
||||
profile_id=0,
|
||||
trad=self._trad,
|
||||
toolbar=self.toolbar,
|
||||
display_current=True
|
||||
display_current=True,
|
||||
parent=self
|
||||
)
|
||||
self.plot_xy.draw()
|
||||
|
||||
|
||||
@@ -57,4 +57,5 @@ class ResultsTranslate(MainTranslate):
|
||||
"name": _translate("Results", "Profile"),
|
||||
"water_elevation": self._dict["unit_water_elevation"],
|
||||
"discharge": self._dict["unit_discharge"],
|
||||
"speed": self._dict["unit_speed"],
|
||||
}
|
||||
Loaded 100 of 274 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user