work on hydraulic structures window

setup.py
Theophile Terraz 2023-12-05 16:46:50 +01:00
parent c17e39e3cf
commit d73e901a96
14 changed files with 688 additions and 4 deletions

View File

@ -376,8 +376,8 @@ class River(Graph, SQLSubModel):
return self._reservoir
@property
def hydraulics_structures(self):
return self._hydraulics_structures
def hydraulic_structures(self):
return self._hydraulic_structures
@property
def parameters(self):

View File

@ -455,7 +455,7 @@ class Mage(CommandLineSolver):
reach_id = study.river.get_edge_id(hs.reach)
params = [p.value for p in hs.basic_hydraulic_structure.param]
param_str = ' '.join([f'{p.value:>10.3f}' for p in hs.basic_hydraulic_structure.param])
f.write(f"{reach_id} {hs.kp:>12.3f} {params} {hs.name}\n")
f.write(f"{hs.basic_hydraulic_structure.type} {reach_id} {hs.kp:>12.3f} {params} {hs.name}\n")
return files

View File

@ -0,0 +1,20 @@
# Table.py -- Pamhyr
# Copyright (C) 2023 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 logging
import traceback

View File

@ -0,0 +1,23 @@
# translate.py -- Pamhyr
# Copyright (C) 2023 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.Tools.PamhyrTranslate import PamhyrTranslate
_translate = QCoreApplication.translate

View File

@ -0,0 +1,17 @@
# UndoCommand.py -- Pamhyr
# Copyright (C) 2023 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 -*-

View File

@ -0,0 +1,17 @@
# Window.py -- Pamhyr
# Copyright (C) 2023 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 -*-

View File

@ -0,0 +1,198 @@
# Table.py -- Pamhyr
# Copyright (C) 2023 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 logging
import traceback
from tools import trace, timer
from PyQt5.QtCore import (
Qt, QVariant, QAbstractTableModel,
QCoreApplication, QModelIndex, pyqtSlot,
QRect,
)
from PyQt5.QtWidgets import (
QDialogButtonBox, QPushButton, QLineEdit,
QFileDialog, QTableView, QAbstractItemView,
QUndoStack, QShortcut, QAction, QItemDelegate,
QComboBox,
)
from View.Tools.PamhyrTable import PamhyrTableModel
from View.HydraulicStructures.UndoCommand import (
SetNameCommand, SetReachCommand, SetKpCommand,
AddCommand, DelCommand,
)
logger = logging.getLogger()
_translate = QCoreApplication.translate
class ComboBoxDelegate(QItemDelegate):
def __init__(self, data=None, trad=None, parent=None, mode="reaches"):
super(ComboBoxDelegate, self).__init__(parent)
self._data = data
self._trad = trad
self._mode = mode
def createEditor(self, parent, option, index):
self.editor = QComboBox(parent)
val = []
if self._mode == "kp":
reach = self._data.hydraulic_structures.get(index.row()).input_reach
if reach is not None:
val = list(
map(
lambda kp: str(kp), reach.reach.get_kp()
)
)
else:
val = list(
map(
lambda n: n.name, self._data.edges()
)
)
self.editor.addItems(
[_translate("Hydraulic structure", "Not associated")] +
val
)
self.editor.setCurrentText(index.data(Qt.DisplayRole))
return self.editor
def setEditorData(self, editor, index):
value = index.data(Qt.DisplayRole)
self.editor.currentTextChanged.connect(self.currentItemChanged)
def setModelData(self, editor, model, index):
text = str(editor.currentText())
model.setData(index, text)
editor.close()
editor.deleteLater()
def updateEditorGeometry(self, editor, option, index):
r = QRect(option.rect)
if self.editor.windowFlags() & Qt.Popup:
if editor.parent() is not None:
r.setTopLeft(self.editor.parent().mapToGlobal(r.topLeft()))
editor.setGeometry(r)
@pyqtSlot()
def currentItemChanged(self):
self.commitData.emit(self.sender())
class TableModel(PamhyrTableModel):
def _setup_lst(self):
self._lst = self._data._hydraulic_structures
def rowCount(self, parent):
return len(self._lst)
def data(self, index, role):
if role != Qt.ItemDataRole.DisplayRole:
return QVariant()
row = index.row()
column = index.column()
if self._headers[column] == "name":
return self._lst.get(row).name
elif self._headers[column] == "reach":
n = self._lst.get(row).input_reach
if n is None:
return _translate("Hydraulic structure", "Not associated")
return n.name
elif self._headers[column] == "kp":
n = self._lst.get(row).input_kp
if n is None:
return _translate("Hydraulic structure", "Not associated")
return n
return QVariant()
def setData(self, index, value, role=Qt.EditRole):
if not index.isValid() or role != Qt.EditRole:
return False
row = index.row()
column = index.column()
try:
if self._headers[column] == "name":
self._undo.push(
SetNameCommand(
self._lst, row, value
)
)
elif self._headers[column] == "reach":
self._undo.push(
SetReachCommand(
self._lst, row, self._data.edge(value)
)
)
elif self._headers[column] == "kp":
self._undo.push(
SetKpCommand(
self._lst, row, value
)
)
except Exception as e:
logger.info(e)
logger.debug(traceback.format_exc())
self.dataChanged.emit(index, index)
return True
def add(self, row, parent=QModelIndex()):
self.beginInsertRows(parent, row, row - 1)
self._undo.push(
AddCommand(
self._lst, row
)
)
self.endInsertRows()
self.layoutChanged.emit()
def delete(self, rows, parent=QModelIndex()):
self.beginRemoveRows(parent, rows[0], rows[-1])
self._undo.push(
DelCommand(
self._lst, rows
)
)
self.endRemoveRows()
self.layoutChanged.emit()
def undo(self):
self._undo.undo()
self.layoutChanged.emit()
def redo(self):
self._undo.redo()
self.layoutChanged.emit()

View File

@ -0,0 +1,34 @@
# translate.py -- Pamhyr
# Copyright (C) 2023 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.Tools.PamhyrTranslate import PamhyrTranslate
_translate = QCoreApplication.translate
class HydraulicStructuresTranslate(PamhyrTranslate):
def __init__(self):
super(HydraulicStructuresTranslate, self).__init__()
self._sub_dict["table_headers"] = {
"name": _translate("HydraulicStructures", "Name"),
"reach": _translate("HydraulicStructures", "Reach"),
"kp": _translate("HydraulicStructures", "Kp"),
}

View File

@ -0,0 +1,139 @@
# UndoCommand.py -- Pamhyr
# Copyright (C) 2023 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 copy import deepcopy
from tools import trace, timer
from PyQt5.QtWidgets import (
QMessageBox, QUndoCommand, QUndoStack,
)
class SetNameCommand(QUndoCommand):
def __init__(self, h_s_lst, index, new_value):
QUndoCommand.__init__(self)
self._h_s_lst = h_s_lst
self._index = index
self._old = self._h_s_lst.get(self._index).name
self._new = str(new_value)
def undo(self):
self._h_s_lst.get(self._index).name = self._old
def redo(self):
self._h_s_lst.get(self._index).name = self._new
class SetReachCommand(QUndoCommand):
def __init__(self, h_s_lst, index, reach):
QUndoCommand.__init__(self)
self._h_s_lst = h_s_lst
self._index = index
self._old = self._h_s_lst.get(self._index).input_reach
self._new = reach
self._old_kp = self._h_s_lst.get(self._index).input_kp
self._new_kp = None
def undo(self):
i = self._h_s_lst.get(self._index)
i.input_reach = self._old
i.input_kp = self._old_kp
def redo(self):
i = self._h_s_lst.get(self._index)
i.input_reach = self._new
i.input_kp = self._new_kp
class SetKpCommand(QUndoCommand):
def __init__(self, h_s_lst, index, kp):
QUndoCommand.__init__(self)
self._h_s_lst = h_s_lst
self._index = index
self._old = self._h_s_lst.get(self._index).input_kp
self._new = kp
def undo(self):
self._h_s_lst.get(self._index).input_kp = self._old
def redo(self):
self._h_s_lst.get(self._index).input_kp = self._new
class AddCommand(QUndoCommand):
def __init__(self, h_s_lst, index):
QUndoCommand.__init__(self)
self._h_s_lst = h_s_lst
self._index = index
self._new = None
def undo(self):
self._h_s_lst.delete_i([self._index])
def redo(self):
if self._new is None:
self._new = self._h_s_lst.new(self._h_s_lst, self._index)
else:
self._h_s_lst.insert(self._index, self._new)
class DelCommand(QUndoCommand):
def __init__(self, h_s_lst, rows):
QUndoCommand.__init__(self)
self._h_s_lst = h_s_lst
self._rows = rows
self._h_s = []
for row in rows:
self._h_s.append((row, self._h_s_lst.get(row)))
self._h_s.sort()
def undo(self):
for row, el in self._h_s:
self._h_s_lst.insert(row, el)
def redo(self):
self._h_s_lst.delete_i(self._rows)
class PasteCommand(QUndoCommand):
def __init__(self, h_s_lst, row, h_s):
QUndoCommand.__init__(self)
self._h_s_lst = h_s_lst
self._row = row
self._h_s = deepcopy(h_s)
self._h_s.reverse()
def undo(self):
self._h_s_lst.delete_i(
self._tab,
range(self._row, self._row + len(self._h_s))
)
def redo(self):
for r in self._h_s:
self._h_s_lst.insert(self._row, r)

View File

@ -0,0 +1,208 @@
# Window.py -- Pamhyr
# Copyright (C) 2023 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 logging
from tools import timer, trace
from View.Tools.PamhyrWindow import PamhyrWindow
from PyQt5 import QtCore
from PyQt5.QtCore import (
Qt, QVariant, QAbstractTableModel, QCoreApplication,
pyqtSlot, pyqtSignal, QItemSelectionModel,
)
from PyQt5.QtWidgets import (
QDialogButtonBox, QPushButton, QLineEdit,
QFileDialog, QTableView, QAbstractItemView,
QUndoStack, QShortcut, QAction, QItemDelegate,
QHeaderView, QDoubleSpinBox, QVBoxLayout, QCheckBox
)
from View.HydraulicStructures.Table import (
TableModel, ComboBoxDelegate
)
from View.Network.GraphWidget import GraphWidget
from View.HydraulicStructures.Translate import HydraulicStructuresTranslate
_translate = QCoreApplication.translate
logger = logging.getLogger()
class HydraulicStructuresWindow(PamhyrWindow):
_pamhyr_ui = "HydraulicStructures"
_pamhyr_name = "Hydraulic Structures"
def __init__(self, study=None, config=None, parent=None):
name = self._pamhyr_name + " - " + study.name
super(HydraulicStructuresWindow, self).__init__(
title=name,
study=study,
config=config,
trad=HydraulicStructuresTranslate(),
parent=parent
)
self._hs_lst = self._study.river._hydraulic_structures
self.setup_table()
self.setup_checkbox()
self.setup_plots()
self.setup_connections()
def setup_table(self):
self._table = None
self._delegate_reach = ComboBoxDelegate(
trad=self._trad,
data=self._study.river,
parent=self,
mode = "reaches"
)
self._delegate_kp = ComboBoxDelegate(
trad=self._trad,
data=self._study.river,
parent=self,
mode = "kp"
)
table = self.find(QTableView, f"tableView")
self._table = TableModel(
table_view=table,
table_headers=self._trad.get_dict("table_headers"),
editable_headers=["name", "reach", "kp"],
delegates={
"reach": self._delegate_reach,
"kp": self._delegate_kp,
},
trad=self._trad,
data=self._study.river,
undo=self._undo_stack,
)
selectionModel = table.selectionModel()
index = table.model().index(0, 0)
selectionModel.select(
index,
QItemSelectionModel.Rows |
QItemSelectionModel.ClearAndSelect |
QItemSelectionModel.Select
)
table.scrollTo(index)
def setup_checkbox(self):
self._checkbox = self.find(QCheckBox, f"checkBox")
self._set_checkbox_state
def setup_plots(self):
return
def setup_connections(self):
self.find(QAction, "action_add").triggered.connect(self.add)
self.find(QAction, "action_delete").triggered.connect(self.delete)
self.find(QAction, "action_edit").triggered.connect(self.edit)
self._checkbox.stateChanged.connect(self._set_structure_state)
table = self.find(QTableView, "tableView")
table.selectionModel()\
.selectionChanged\
.connect(self._set_checkbox_state)
def index_selected_row(self):
table = self.find(QTableView, "tableView")
r = table.selectionModel()\
.selectedRows()
if len(r)>0:
return r[0].row()
else:
return None
def index_selected_rows(self):
table = self.find(QTableView, "tableView")
return list(
# Delete duplicate
set(
map(
lambda i: i.row(),
table.selectedIndexes()
)
)
)
def add(self):
rows = self.index_selected_rows()
if len(self._hs_lst) == 0 or len(rows) == 0:
self._table.add(0)
else:
self._table.add(rows[0])
def delete(self):
rows = self.index_selected_rows()
if len(rows) == 0:
return
self._table.delete(rows)
def _copy(self):
logger.info("TODO: copy")
def _paste(self):
logger.info("TODO: paste")
def _undo(self):
self._table.undo()
def _redo(self):
self._table.redo()
def edit(self):
rows = self.index_selected_rows()
for row in rows:
data = self._hs_lst.get(row)
if self.sub_window_exists(
BasicHydraulicStructuresWindow,
data=[self._study, None, data]
):
continue
win = BasicHydraulicStructuresWindow(
data=data,
study=self._study,
parent=self
)
win.show()
def _set_checkbox_state(self):
row = self.index_selected_row()
if row is None:
self._checkbox.setEnabled(False)
self._checkbox.setChecked(True)
else:
self._checkbox.setEnabled(True)
self._checkbox.setChecked(self._hs_lst.get(row).enabled)
def _set_structure_state(self):
row = self.index_selected_row()
#self._checkbox.setChecked(self._hs_lst.get(row).enabled)
self._hs_lst.get(row).enabled = self._checkbox.isChecked()

View File

@ -48,6 +48,7 @@ from View.Network.Window import NetworkWindow
from View.Geometry.Window import GeometryWindow
from View.BoundaryCondition.Window import BoundaryConditionWindow
from View.Reservoir.Window import ReservoirWindow
from View.HydraulicStructures.Window import HydraulicStructuresWindow
from View.LateralContribution.Window import LateralContributionWindow
from View.InitialConditions.Window import InitialConditionsWindow
from View.Stricklers.Window import StricklersWindow
@ -102,7 +103,8 @@ define_model_action = [
"action_menu_boundary_conditions", "action_menu_initial_conditions",
"action_menu_edit_friction", "action_menu_edit_lateral_contribution",
"action_menu_run_solver", "action_menu_sediment_layers",
"action_menu_edit_reach_sediment_layers", "action_menu_edit_reservoirs"
"action_menu_edit_reach_sediment_layers", "action_menu_edit_reservoirs",
"action_menu_edit_hydraulic_structures"
]
action = (
@ -193,6 +195,7 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
"action_menu_edit_geometry": self.open_geometry,
"action_menu_boundary_conditions": self.open_boundary_cond,
"action_menu_edit_reservoirs": self.open_reservoir,
"action_menu_edit_hydraulic_structures": self.open_hydraulic_structures,
"action_menu_initial_conditions": self.open_initial_conditions,
"action_menu_edit_friction": self.open_frictions,
"action_menu_edit_lateral_contribution": self.open_lateral_contrib,
@ -628,6 +631,16 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
reservoir = ReservoirWindow(study=self._study, parent=self)
reservoir.show()
def open_hydraulic_structures(self):
if self.sub_window_exists(
HydraulicStructuresWindow,
data=[self._study, None]
):
return
hydraulic_structures = HydraulicStructuresWindow(study=self._study, parent=self)
hydraulic_structures.show()
def open_lateral_contrib(self):
if self.sub_window_exists(
LateralContributionWindow,

View File

@ -40,6 +40,9 @@
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="leftMargin">
<number>5</number>
</property>
<item>
<widget class="QCheckBox" name="checkBox">
<property name="text">

View File

@ -40,6 +40,9 @@
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="leftMargin">
<number>5</number>
</property>
<item>
<widget class="QCheckBox" name="checkBox">
<property name="text">

View File

@ -132,6 +132,7 @@
<addaction name="action_menu_edit_friction"/>
<addaction name="action_menu_edit_lateral_contribution"/>
<addaction name="action_menu_edit_reservoirs"/>
<addaction name="action_menu_edit_hydraulic_structures"/>
</widget>
<widget class="QMenu" name="menu_results">
<property name="title">
@ -934,6 +935,14 @@
<string>Edit reservoirs</string>
</property>
</action>
<action name="action_menu_edit_hydraulic_structures">
<property name="text">
<string>Hydraulic structures</string>
</property>
<property name="toolTip">
<string>Edit hydraulic structures</string>
</property>
</action>
</widget>
<resources/>
<connections>