diff --git a/src/Meshing/Internal.py b/src/Meshing/Internal.py index e597a339..48a84a94 100644 --- a/src/Meshing/Internal.py +++ b/src/Meshing/Internal.py @@ -57,6 +57,13 @@ class InternalMeshing(AMeshingTool): # we never modify original profiles, we work on the copies m_profiles = self.st_to_m(profiles, guide_list) + + # Adapt the spacing to the kilometre-point direction. The value from + # the dialog is a distance, so only its magnitude is significant. + step = self.signed_step( + step, m_profiles[0].rk, m_profiles[-1].rk + ) + new_profiles = self.interpolate_transversal_step(m_profiles, step) for new_profiles2 in new_profiles: @@ -65,6 +72,12 @@ class InternalMeshing(AMeshingTool): return new_profiles + @staticmethod + def signed_step(step, begin_rk, end_rk): + if end_rk < begin_rk: + return -abs(step) + return abs(step) + def st_to_m(self, profiles, guide_list): guide_list = ["un"] + guide_list + ["np"] max_values = [0] * (len(guide_list) - 1) diff --git a/src/View/BoundaryCondition/Edit/Table.py b/src/View/BoundaryCondition/Edit/Table.py index 5a73fd78..363aa19f 100644 --- a/src/View/BoundaryCondition/Edit/Table.py +++ b/src/View/BoundaryCondition/Edit/Table.py @@ -143,21 +143,43 @@ class TableModel(PamhyrTableModel): self.update() def delete(self, rows, parent=QModelIndex()): - self.beginRemoveRows(parent, rows[0], rows[-1]) + if not rows: + return - rows = list(map( - lambda r: self.get_true_data_row(r), - rows)) + rows = sorted(set(rows)) + rows = [ + self.get_true_data_row(row) + for row in rows + ] - self._undo.push( - DelCommand( - self._data, rows + # A multiple selection may contain non-contiguous rows. A model reset + # accurately represents this atomic undo command, unlike one + # beginRemoveRows() call spanning rows that are not all removed. + self.beginResetModel() + try: + self._undo.push( + DelCommand( + self._data, rows + ) ) - ) - - self.endRemoveRows() + finally: + self.endResetModel() self.update() + def undo(self): + self.beginResetModel() + try: + self._undo.undo() + finally: + self.endResetModel() + + def redo(self): + self.beginResetModel() + try: + self._undo.redo() + finally: + self.endResetModel() + def sort(self, _reverse, parent=QModelIndex()): self.layoutAboutToBeChanged.emit() diff --git a/src/View/BoundaryCondition/Edit/UndoCommand.py b/src/View/BoundaryCondition/Edit/UndoCommand.py index 06b598a1..82c2bca5 100644 --- a/src/View/BoundaryCondition/Edit/UndoCommand.py +++ b/src/View/BoundaryCondition/Edit/UndoCommand.py @@ -97,12 +97,11 @@ class DelCommand(QUndoCommand): QUndoCommand.__init__(self) self._data = data - self._rows = rows - - self._bc = [] - for row in rows: - self._bc.append(self._data.get_i(row)) - self._bc.sort() + self._rows = sorted(set(rows)) + self._bc = [ + self._data.get_i(row) + for row in self._rows + ] def undo(self): for el in self._bc: @@ -120,13 +119,17 @@ class SortCommand(QUndoCommand): self._data = data self._reverse = _reverse - self._old = self._data.data - self._indexes = None + # Keep the order of the complete underlying list, including soft + # deleted items. ``data`` only exposes visible items and therefore + # cannot reliably restore a sort after a delete/undelete operation. + self._old_positions = { + id(item): index + for index, item in enumerate(self._data._data) + } def undo(self): - ll = self._data.data self._data.sort( - key=lambda x: self._indexes[ll.index(x)] + key=lambda item: self._old_positions[id(item)] ) def redo(self): @@ -134,14 +137,6 @@ class SortCommand(QUndoCommand): _reverse=self._reverse, key=lambda x: x[0] ) - if self._indexes is None: - self._indexes = list( - map( - lambda p: self._old.index(p), - self._data.data - ) - ) - self._old = None class MoveCommand(QUndoCommand): diff --git a/src/View/Geometry/Table.py b/src/View/Geometry/Table.py index a27dd360..847722d9 100644 --- a/src/View/Geometry/Table.py +++ b/src/View/Geometry/Table.py @@ -259,6 +259,9 @@ class GeometryReachTableModel(PamhyrTableModel): **data ) + self.apply_meshing(new_profiles, data, tableView) + + def apply_meshing(self, new_profiles, data, tableView): if new_profiles is None: return diff --git a/src/View/Geometry/Window.py b/src/View/Geometry/Window.py index 987245a4..0827a094 100644 --- a/src/View/Geometry/Window.py +++ b/src/View/Geometry/Window.py @@ -350,14 +350,22 @@ class GeometryWindow(PamhyrWindow): tableview = self.tableView def fn(): - self._table.meshing(mesher, data, tableview) - # self._table.meshing(mesher, data, tableview) + return mesher.meshing(self._reach, **data) + dlg2 = WaitingDialog( payload_fn=fn, title="waiting_mesh", parent=self ) dlg2.exec_() + + if dlg2.worker_exception is not None: + raise dlg2.worker_exception + + # Models, undo stacks and selections belong to the GUI thread. + self._table.apply_meshing( + dlg2.worker_result, data, tableview + ) self._table.update() except Exception as e: diff --git a/src/View/WaitingDialog.py b/src/View/WaitingDialog.py index 581d8b2e..4167380e 100644 --- a/src/View/WaitingDialog.py +++ b/src/View/WaitingDialog.py @@ -47,10 +47,16 @@ class Worker(QObject): super(self.__class__, self).__init__(parent) self._payload_fn = payload_fn + self.result = None + self.exception = None def process(self): - self._payload_fn() - self.signalStatus.emit('end') + try: + self.result = self._payload_fn() + except Exception as exception: + self.exception = exception + finally: + self.signalStatus.emit('end') class WaitingDialog(PamhyrDialog): @@ -123,6 +129,14 @@ class WaitingDialog(PamhyrDialog): self._timer = QTimer(self) self._timer.timeout.connect(self.update_spinner) + @property + def worker_result(self): + return self._worker.result + + @property + def worker_exception(self): + return self._worker.exception + def update_spinner(self): self._spinner_step += 1 self._spinner_step %= len(self._spin)