Pamhyr2/src/View/Geometry/Table.py

314 lines
8.1 KiB
Python

# Table.py -- Pamhyr
# Copyright (C) 2023-2025 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 time
import logging
import traceback
from tools import timer, trace
from PyQt5.QtGui import (
QKeySequence, QColor
)
from PyQt5.QtCore import (
Qt, QAbstractTableModel, QModelIndex,
QVariant, pyqtSlot, QCoreApplication,
)
from PyQt5.QtWidgets import (
QMessageBox, QUndoCommand, QUndoStack,
QStyledItemDelegate, QLineEdit, QAbstractItemView,
QComboBox,
)
from View.Tools.PamhyrTable import PamhyrTableModel
from Model.Geometry import Reach
from Model.Geometry.ProfileXYZ import ProfileXYZ
from View.Geometry.UndoCommand import *
logger = logging.getLogger()
_translate = QCoreApplication.translate
class GeometryReachTableModel(PamhyrTableModel):
def get_true_data_row(self, row):
profile = self._data.profile(row)
return next(
map(
lambda e: e[0],
filter(
lambda e: e[1] == profile,
enumerate(self._data._profiles)
)
), 0
)
def data(self, index, role=Qt.DisplayRole):
if not index.isValid():
return QVariant()
if role == Qt.DisplayRole and index.column() == 0:
return self._data.profile(index.row()).name
if role == Qt.DisplayRole and index.column() == 1:
rk = self._data.profile(index.row()).rk
return f"{rk:.4f}"
if role == Qt.DisplayRole and index.column() == 2:
return str(self._data.profile(index.row()).nb_points)
if role == Qt.TextAlignmentRole:
return Qt.AlignHCenter | Qt.AlignVCenter
if role == Qt.ForegroundRole and index.column() == 0:
name = self._data.profile(index.row()).name\
.strip()\
.lower()
if (name == "upstream" or name == "up" or
name == _translate("Geometry", "upstream")):
return QColor("Green")
elif (name == "downstream" or name == "down" or
name == _translate("Geometry", "downstream")):
return QColor("Red")
return QVariant()
def setData(self, index, value, role=Qt.EditRole):
row = index.row()
column = index.column()
if role == Qt.EditRole and index.column() != 2:
if self.is_same_data(index, value):
return False
try:
if index.column() == 0:
self._undo.push(
SetNameCommand(
self._data, index.row(),
self._data.profile(index.row()).name,
value
)
)
if index.column() == 1:
self._undo.push(
SetRKCommand(
self._data, index.row(),
self._data.profile(index.row()).rk,
value
)
)
except Exception as e:
logger.info(e)
logger.debug(traceback.format_exc())
self.dataChanged.emit(index, index)
self.layoutChanged.emit()
return True
self.dataChanged.emit(index, index)
self.layoutChanged.emit()
return False
# @QtCore.pyqtSlot()
def add(self, row, parent=QModelIndex()):
self.beginInsertRows(parent, row, row - 1)
self._undo.push(
AddCommand(
self._data, row
)
)
self.endInsertRows()
self.layoutChanged.emit()
def delete(self, rows, parent=QModelIndex()):
self.beginRemoveRows(parent, rows[0], rows[-1])
self._undo.push(
DelCommand(
self._data, rows
)
)
self.endRemoveRows()
self.layoutChanged.emit()
def sort_profiles(self, _reverse):
self.layoutAboutToBeChanged.emit()
self._undo.push(
SortCommand(
self._data, _reverse
)
)
self.layoutAboutToBeChanged.emit()
self.layoutChanged.emit()
def move_up(self, row, parent=QModelIndex()):
if row <= 0:
return
target = row + 1
self.beginMoveRows(parent, row - 1, row - 1, parent, target)
row = self.get_true_data_row(row)
self._undo.push(
MoveCommand(
self._data, "up", row
)
)
self.endMoveRows()
self.layoutChanged.emit()
def move_down(self, row, parent=QModelIndex()):
if row >= self._data.number_profiles-1:
return
target = row
self.beginMoveRows(parent, row + 1, row + 1, parent, target)
row = self.get_true_data_row(row)
self._undo.push(
MoveCommand(
self._data, "down", row
)
)
self.endMoveRows()
self.layoutChanged.emit()
def import_geometry(self, row, filename):
self.layoutAboutToBeChanged.emit()
self._undo.push(
ImportCommand(
self._data, row,
filename
)
)
self.layoutAboutToBeChanged.emit()
self.layoutChanged.emit()
def duplicate(self, rows, profiles):
self.layoutAboutToBeChanged.emit()
self._undo.push(
DuplicateCommand(
self._data, rows,
profiles
)
)
self.layoutAboutToBeChanged.emit()
self.layoutChanged.emit()
def paste(self, row, header, data):
if row > len(self._data._profiles):
return
if len(data) == 0:
return
self.layoutAboutToBeChanged.emit()
true_row = self.get_true_data_row(row)
self._undo.push(
PasteCommand(
self._data, true_row,
list(
map(
lambda d: ProfileXYZ.from_data(header, d),
data
)
)
)
)
self.layoutAboutToBeChanged.emit()
self.layoutChanged.emit()
def meshing(self, mesher, data, tableView):
new_profiles = mesher.meshing(
self._data,
**data
)
if new_profiles is None:
return
if len(new_profiles) == 0:
return
self.layoutAboutToBeChanged.emit()
self._undo.push(
MeshingCommand(
self._data, new_profiles, data, tableView
)
)
self.layoutAboutToBeChanged.emit()
self.layoutChanged.emit()
def update_rk(self, mesher, data):
self.layoutAboutToBeChanged.emit()
self._undo.push(
UpdateRKCommand(
self._data, mesher, data
)
)
self.layoutAboutToBeChanged.emit()
self.layoutChanged.emit()
def purge(self, np_purge):
self._undo.push(
PurgeCommand(
self._data, np_purge
)
)
self.layoutChanged.emit()
def shift(self, rows, dx, dy, dz):
self._undo.push(
ShiftCommand(
self._data, rows, dx, dy, dz
)
)
self.layoutChanged.emit()
def change_reach(self, new_reach, parent):
self._undo.push(
ChangeReachCommand(
new_reach,
parent
)
)