diff --git a/src/Meshing/Internal.py b/src/Meshing/Internal.py index 2134b8c4..7a14bad5 100644 --- a/src/Meshing/Internal.py +++ b/src/Meshing/Internal.py @@ -214,7 +214,7 @@ class InternalMeshing(AMeshingTool): ratio = (alpha[j0] - beta[j0-1]) \ / (beta[j0] - beta[j0-1]) if ratio < 0.0: - print(f"ratio négatif {ratio}") + logger.warning(f"ratio négatif {ratio}") # on double le point a gauche p = sect2.point(start2+j0-1).copy() sect2.insert_point(start2+j0-1, p) diff --git a/src/Model/BoundaryConditionsAdisTS/BoundaryConditionAdisTS.py b/src/Model/BoundaryConditionsAdisTS/BoundaryConditionAdisTS.py index b33c1a03..b38c8ae2 100644 --- a/src/Model/BoundaryConditionsAdisTS/BoundaryConditionAdisTS.py +++ b/src/Model/BoundaryConditionsAdisTS/BoundaryConditionAdisTS.py @@ -304,8 +304,10 @@ class BoundaryConditionAdisTS(SQLSubModel): if pol_id not in pid_pol: # ⚠️ cas important : probablement déjà migré - print(f"[WARN] pol_id {pol_id} not in pid_pol " + - f"→ probably already migrated") + logger.warning( + f"pol_id {pol_id} not in pid_pol " + + "→ probably already migrated" + ) continue execute( diff --git a/src/Model/Geometry/PointXYZ.py b/src/Model/Geometry/PointXYZ.py index 3215d009..4c28a07a 100644 --- a/src/Model/Geometry/PointXYZ.py +++ b/src/Model/Geometry/PointXYZ.py @@ -338,6 +338,13 @@ class PointXYZ(Point): self._z = float(value) self.modified() + def shift(self, x, y, z): + self.x += x + self.y += y + self.z += z + + self.modified() + def get_coordinate(self): return (self._x, self._y, self._z) diff --git a/src/Model/Geometry/ProfileXYZ.py b/src/Model/Geometry/ProfileXYZ.py index 2ccb75fd..d928fdd1 100644 --- a/src/Model/Geometry/ProfileXYZ.py +++ b/src/Model/Geometry/ProfileXYZ.py @@ -24,7 +24,7 @@ from typing import List from functools import reduce from dataclasses import dataclass -from tools import timer, flatten +from tools import trace, timer, flatten from shapely import geometry from Model.Tools.PamhyrDB import SQLSubModel @@ -396,7 +396,6 @@ class ProfileXYZ(Profile, SQLSubModel): for point in self._points: p = point.copy() - print(p) new_p._points.append(p) new_p.modified() @@ -1074,16 +1073,10 @@ class ProfileXYZ(Profile, SQLSubModel): def shift(self, x, y, z): for p in self._points: - p.x = p.x + x - p.y = p.y + y - p.z = p.z + z + p.shift(x, y, z) self.modified() - def modified(self): - self.tab_up_to_date = False - self.station_up_to_date = False - def add_npoints(self, npoints): # add npoints in a profile for k in range(npoints): diff --git a/src/Model/Geometry/Reach.py b/src/Model/Geometry/Reach.py index 318adcb8..668ac5bc 100644 --- a/src/Model/Geometry/Reach.py +++ b/src/Model/Geometry/Reach.py @@ -52,6 +52,11 @@ class Reach(SQLSubModel): self._guidelines_is_valid = False self._guidelines = {} + super(Reach, self).__init__( + id=parent.pamhyr_id, + status=status, + ) + @property def pamhyr_id(self): return self._parent.pamhyr_id @@ -60,6 +65,10 @@ class Reach(SQLSubModel): def _pamhyr_id(self): return self._parent.pamhyr_id + @_pamhyr_id.setter + def _pamhyr_id(self, id): + return + @classmethod def _db_create(cls, execute): return cls._create_submodel(execute) @@ -103,6 +112,9 @@ class Reach(SQLSubModel): predicate=lambda obj, data: True, modifier=lambda obj, data: None, data={}): + if predicate(self, data): + modifier(self, data) + for p in self._profiles: p._data_traversal(predicate, modifier, data) @@ -199,13 +211,12 @@ class Reach(SQLSubModel): else: gi = self.get_global_profil_index(index) profile.num = gi - print(f"gi = {gi}") self._profiles.insert(gi, profile) self.modified() def delete(self, indexes): - """Delete some elements in profile list + """Set some elements as deleted in profile list Args: indexes: The list of index to delete @@ -247,7 +258,7 @@ class Reach(SQLSubModel): self.modified() def delete_profiles(self, profiles): - """Delete some elements in profile list + """Set profiles list as deleted Args: profiles: The list of profile to delete diff --git a/src/Model/HydraulicStructures/Basic/HydraulicStructures.py b/src/Model/HydraulicStructures/Basic/HydraulicStructures.py index 6df03953..757d8bfb 100644 --- a/src/Model/HydraulicStructures/Basic/HydraulicStructures.py +++ b/src/Model/HydraulicStructures/Basic/HydraulicStructures.py @@ -183,7 +183,6 @@ class BasicHS(SQLSubModel): bhs._data = BHSValue._db_load( execute, data ) - print(f"{bhs_pid} : {deleted}") if deleted: bhs.set_as_deleted() diff --git a/src/Model/InitialConditions/InitialConditions.py b/src/Model/InitialConditions/InitialConditions.py index bbec0492..fc299a77 100644 --- a/src/Model/InitialConditions/InitialConditions.py +++ b/src/Model/InitialConditions/InitialConditions.py @@ -588,8 +588,6 @@ class InitialConditions(SQLSubModel): for data in self._data: data_height[data["rk"].rk] = data["height"] - print(data_height) - incline = self._reach.reach.get_incline_median_mean() logger.debug(f"incline = {incline}") diff --git a/src/Model/Reservoir/Reservoir.py b/src/Model/Reservoir/Reservoir.py index c093691c..e120f9ec 100644 --- a/src/Model/Reservoir/Reservoir.py +++ b/src/Model/Reservoir/Reservoir.py @@ -263,8 +263,6 @@ class Reservoir(SQLSubModel): cls._db_update_to_0_2_0_set_node_pid(execute, table, nodes) Scenario.update_db_add_scenario(execute, table) - print(execute(f"SELECT * FROM {table}")) - cls._db_create(execute, ext="_tmp") execute( f"INSERT INTO {table}_tmp " + diff --git a/src/Model/Scenario.py b/src/Model/Scenario.py index 1bcf38b2..cc4c01f4 100644 --- a/src/Model/Scenario.py +++ b/src/Model/Scenario.py @@ -19,7 +19,7 @@ import os import logging -from tools import logger_exception +from tools import logger_exception, timer, flatten from Model.Tools.PamhyrDB import SQLSubModel @@ -225,10 +225,13 @@ class Scenario(SQLSubModel): return aux(self, []) - def drop_deleted_data(self, execute): + @timer + def clean_deleted_data(self, execute): tables = self.tables_with_deleted_column branch = self.get_parent_branch() + all_ids = [] + for table in tables: if self.parent is None: # This scenario is the default scenario, so we can @@ -257,17 +260,23 @@ class Scenario(SQLSubModel): if ids is None or len(ids) == 0: continue + ids = flatten(ids) + logger.debug( - f"(s{self.id}) Drop deleted data into '{table}' : {ids}" + f"({self.name}) Drop deleted data into '{table}' : {ids}" ) execute( f"DELETE FROM {table} " + f"WHERE scenario = {self.id} " + "AND pamhyr_id IN " + - f"({', '.join(map(lambda x: str(x[0]), ids))})" + f"({', '.join(map(str, ids))})" ) + all_ids += ids + + return all_ids + @property def id(self): return self._id diff --git a/src/Model/Scenarios.py b/src/Model/Scenarios.py index a2db1f98..e37a504e 100644 --- a/src/Model/Scenarios.py +++ b/src/Model/Scenarios.py @@ -16,6 +16,8 @@ # -*- coding: utf-8 -*- +from functools import reduce + from tools import logger_exception from Model.Tools.PamhyrDict import PamhyrModelDict @@ -78,3 +80,12 @@ class Scenarios(PamhyrModelDict): self._dict[key].set_as_deleted() self._status.modified() + + def is_leaf(self, scenario): + return not reduce( + lambda acc, s: ( + acc or (not s.is_deleted() and s.parent is scenario) + ), + self._dict.values(), + False + ) diff --git a/src/Model/Study.py b/src/Model/Study.py index 2ec031a7..e5ecdbbf 100644 --- a/src/Model/Study.py +++ b/src/Model/Study.py @@ -24,12 +24,21 @@ from datetime import datetime from tools import timer, timestamp from functools import reduce +from Model.Tools.PamhyrListExt import PamhyrModelList +from Model.Tools.PamhyrDict import PamhyrModelDict from Model.Tools.PamhyrDB import SQLModel from Model.Scenarios import Scenarios from Model.Scenario import Scenario from Model.Status import StudyStatus from Model.Except import NotImplementedMethodeError from Model.River import River +from Model.Geometry.Reach import Reach +from Model.HydraulicStructures.HydraulicStructures import ( + HydraulicStructure +) +from Model.HydraulicStructures.Basic.HydraulicStructures import ( + BasicHS +) from Checker.Study import * @@ -450,14 +459,59 @@ class Study(SQLModel): data=progress ) - # Clear DB for each scenarios - for scenar in self.scenarios.lst: - scenar.drop_deleted_data(self.execute) + # Clear DB for current scenario + ids = self.status.scenario.clean_deleted_data(self.execute) + self.memory_clean(ids) progress() self.commit() + @timer + def memory_clean(self, ids): + if len(ids) == 0: + return + + reach_class = Reach + hs_classes = [HydraulicStructure, BasicHS] + list_classes = set(PamhyrModelList.__subclasses__()) + dict_classes = set(PamhyrModelDict.__subclasses__()) + + def modifier(obj, data): + t = type(obj) + + if t is reach_class: + obj._profiles = list( + filter( + lambda el: el.id not in ids, + obj._profiles + ) + ) + elif t in hs_classes: + obj._data = list( + filter( + lambda el: el.id not in ids, + obj._data + ) + ) + elif t in dict_classes: + new = {} + for key in obj._dict: + if obj._dict[key].id not in ids: + new[key] = obj._dict[key] + obj._dict = new + elif t in list_classes: + obj._lst = list( + filter( + lambda el: el.id not in ids, + obj._lst + ) + ) + + self.river._data_traversal( + lambda o, d: True, modifier, {} + ) + def sql_save_request_count(self, *args, **kargs): return self._count() diff --git a/src/Model/Tools/PamhyrDB.py b/src/Model/Tools/PamhyrDB.py index 9ca7257c..3ee72f0c 100644 --- a/src/Model/Tools/PamhyrDB.py +++ b/src/Model/Tools/PamhyrDB.py @@ -26,6 +26,8 @@ from functools import reduce from SQL import SQL from Model.Except import NotImplementedMethodeError +from tools import trace + from Model.Tools.PamhyrID import PamhyrID logger = logging.getLogger() @@ -48,11 +50,9 @@ class SQLModel(SQL): self._cur = self._db.cursor() if is_new: - logger.info("Create database") self._create() # Create db # self._save() # Save else: - logger.info("Update database") self._update() # Update db scheme if necessary # self._load() # Load data @@ -313,8 +313,10 @@ class SQLSubModel(PamhyrID): if node_id not in nodes: # ⚠️ cas important : probablement déjà migré - print(f"[WARN] node_id {node_id} not in nodes " + - f"→ probably already migrated") + logger.warning( + f"node_id {node_id} not in nodes " + + "→ probably already migrated" + ) continue execute( diff --git a/src/Model/Tools/PamhyrDict.py b/src/Model/Tools/PamhyrDict.py index 52ac2e3b..7bddc1c8 100644 --- a/src/Model/Tools/PamhyrDict.py +++ b/src/Model/Tools/PamhyrDict.py @@ -97,6 +97,9 @@ class PamhyrModelDict(SQLSubModel): Nothing """ + if predicate(self, data): + modifier(self, data) + for key in self._dict: self._dict[key]\ ._data_traversal(predicate, modifier, data) diff --git a/src/Model/Tools/PamhyrList.py b/src/Model/Tools/PamhyrList.py index 5d735443..a97f869a 100644 --- a/src/Model/Tools/PamhyrList.py +++ b/src/Model/Tools/PamhyrList.py @@ -100,6 +100,9 @@ class PamhyrModelList(SQLSubModel): Nothing """ + if predicate(self, data): + modifier(self, data) + for el in self._lst: el._data_traversal(predicate, modifier, data) diff --git a/src/Model/Tools/PamhyrListExt.py b/src/Model/Tools/PamhyrListExt.py index 07d0ed58..187d437f 100644 --- a/src/Model/Tools/PamhyrListExt.py +++ b/src/Model/Tools/PamhyrListExt.py @@ -107,6 +107,9 @@ class PamhyrModelList(SQLSubModel): Returns: Nothing """ + if predicate(self, data): + modifier(self, data) + for el in self._lst: el._data_traversal(predicate, modifier, data) diff --git a/src/Solver/RubarBE.py b/src/Solver/RubarBE.py index f33b04dc..e92ca2cd 100644 --- a/src/Solver/RubarBE.py +++ b/src/Solver/RubarBE.py @@ -426,7 +426,6 @@ class Rubar3(CommandLineSolver): coeff = coeff_min else: for s in lst: - print(s.begin_rk, s.end_rk) if (rk >= s.begin_rk and rk <= s.end_rk or rk <= s.begin_rk and rk >= s.end_rk): coeff = s.begin_strickler # TODO: inerpolate @@ -473,7 +472,6 @@ class Rubar3(CommandLineSolver): last = profiles[-1] if first not in data or last not in data: - print(data) logger.error( "Study initial condition is not fully defined" ) diff --git a/src/View/Geometry/Table.py b/src/View/Geometry/Table.py index 0c2f5552..d51d1f22 100644 --- a/src/View/Geometry/Table.py +++ b/src/View/Geometry/Table.py @@ -251,7 +251,6 @@ class GeometryReachTableModel(PamhyrTableModel): self.layoutChanged.emit() def meshing(self, mesher, data, tableView): - new_profiles = mesher.meshing( self._data, **data @@ -287,7 +286,6 @@ class GeometryReachTableModel(PamhyrTableModel): self.layoutChanged.emit() def purge(self, np_purge): - self._undo.push( PurgeCommand( self._data, np_purge @@ -296,7 +294,6 @@ class GeometryReachTableModel(PamhyrTableModel): self.layoutChanged.emit() def shift(self, rows, dx, dy, dz): - self._undo.push( ShiftCommand( self._data, rows, dx, dy, dz @@ -305,7 +302,6 @@ class GeometryReachTableModel(PamhyrTableModel): self.layoutChanged.emit() def change_reach(self, new_reach, parent): - self._undo.push( ChangeReachCommand( new_reach, diff --git a/src/View/Geometry/UndoCommand.py b/src/View/Geometry/UndoCommand.py index 3a5da35b..5414f358 100644 --- a/src/View/Geometry/UndoCommand.py +++ b/src/View/Geometry/UndoCommand.py @@ -411,13 +411,13 @@ class ShiftCommand(QUndoCommand): def undo(self): for i in self._rows: profile = self._reach.profiles[i] - self._reach.profiles[i].shift( + profile.shift( -self._dx, -self._dy, -self._dz ) def redo(self): for i in self._rows: profile = self._reach.profiles[i] - self._reach.profiles[i].shift( + profile.shift( self._dx, self._dy, self._dz ) diff --git a/src/View/Network/GraphWidget.py b/src/View/Network/GraphWidget.py index 83aa8014..4646a011 100644 --- a/src/View/Network/GraphWidget.py +++ b/src/View/Network/GraphWidget.py @@ -925,9 +925,11 @@ class GraphWidget(QGraphicsView): self.scale(scaleFactor, scaleFactor) def mousePressEvent(self, event): - if self._only_display or self.graph._status.is_read_only(): + if self._only_display: return + locked = self.graph._status.is_read_only() + pos = self.mapToScene(event.pos()) self.clicked = True @@ -936,9 +938,6 @@ class GraphWidget(QGraphicsView): super(GraphWidget, self).mousePressEvent(event) return - if self.graph._status.is_read_only(): - return - # Move item and select edge item if self._state == "move": self._selected_new_edge_src_node = None @@ -948,39 +947,41 @@ class GraphWidget(QGraphicsView): edge = items[0] if edge: self.set_current_edge(edge) - elif items and type(items[0]) is NodeItem: - self._mouse_origin_x = pos.x() - self._mouse_origin_y = pos.y() - self._current_moved_node = items[0] + if not locked: + if items and type(items[0]) is NodeItem: + self._mouse_origin_x = pos.x() + self._mouse_origin_y = pos.y() + self._current_moved_node = items[0] - # Add nodes and edges - elif self._state == "add": - items = self.items(event.pos()) - nodes = list(filter(lambda i: type(i) is NodeItem, items)) - if not nodes: - self.add_node(pos) - self.set_selected_new_edge_src_node(None) - else: - if self.selected_new_edge_src_node() is None: - self.set_selected_new_edge_src_node(nodes[0]) + if not locked: + # Add nodes and edges + if self._state == "add": + items = self.items(event.pos()) + nodes = list(filter(lambda i: type(i) is NodeItem, items)) + if not nodes: + self.add_node(pos) + self.set_selected_new_edge_src_node(None) else: - self.add_edge(self.selected_new_edge_src_node(), nodes[0]) + if self.selected_new_edge_src_node() is None: + self.set_selected_new_edge_src_node(nodes[0]) + else: + self.add_edge(self.selected_new_edge_src_node(), nodes[0]) - # Delete nodes and edges - elif self._state == "del": - self._selected_new_edge_src_node = None - items = list( - filter( - lambda i: type(i) is NodeItem or type(i) is EdgeItem, - self.items(event.pos()) + # Delete nodes and edges + elif self._state == "del": + self._selected_new_edge_src_node = None + items = list( + filter( + lambda i: type(i) is NodeItem or type(i) is EdgeItem, + self.items(event.pos()) + ) ) - ) - if len(items) > 0: - item = items[0] - if type(item) is NodeItem: - self.del_node(item) - elif type(item) is EdgeItem: - self.del_edge(item) + if len(items) > 0: + item = items[0] + if type(item) is NodeItem: + self.del_node(item) + elif type(item) is EdgeItem: + self.del_edge(item) self.update() super(GraphWidget, self).mousePressEvent(event) @@ -988,10 +989,12 @@ class GraphWidget(QGraphicsView): def mouseReleaseEvent(self, event): self.clicked = False - if self._only_display or self.graph._status.is_read_only(): + if self._only_display: return - if self._state == "move": + locked = self.graph._status.is_read_only() + + if not locked and self._state == "move": if self._current_moved_node is not None: pos = self.mapToScene(event.pos()) self._undo.push( @@ -1007,10 +1010,8 @@ class GraphWidget(QGraphicsView): super(GraphWidget, self).mouseReleaseEvent(event) def mouseMoveEvent(self, event): - if self.graph._status.is_read_only(): - return - pos = self.mapToScene(event.pos()) + locked = self.graph._status.is_read_only() # Selecte item on the fly items = self.items(event.pos()) diff --git a/src/View/Scenarios/ContextMenu.py b/src/View/Scenarios/ContextMenu.py index 319c0d92..1a733082 100644 --- a/src/View/Scenarios/ContextMenu.py +++ b/src/View/Scenarios/ContextMenu.py @@ -57,6 +57,8 @@ class DefaultMenu(AbstractMenu): class ScenarioMenu(AbstractMenu): def run(self): item = self._items[0] + scenarios = item.graph._study.scenarios + current_scenario = item.graph._study.status.scenario.id select = self._menu.addAction(self._trad["menu_select_scenario"]) @@ -64,7 +66,9 @@ class ScenarioMenu(AbstractMenu): delete = None if item.scenario.id != 0: - delete = self._menu.addAction(self._trad["menu_del_scenario"]) + if scenarios.is_leaf(item.scenario): + delete = self._menu.addAction(self._trad["menu_del_scenario"]) + if item.scenario.id == current_scenario: duplicate = self._menu.addAction( self._trad["menu_dup_scenario"] diff --git a/src/View/Scenarios/GraphWidget.py b/src/View/Scenarios/GraphWidget.py index 0fff07d3..bba78edd 100644 --- a/src/View/Scenarios/GraphWidget.py +++ b/src/View/Scenarios/GraphWidget.py @@ -426,30 +426,35 @@ class GraphWidget(QGraphicsView): dlg.setWindowTitle(self._trad["mb_save_title"]) dlg.setText(self._trad["mb_save_msg"]) - opt = QMessageBox.Save | QMessageBox.Cancel + opt = QMessageBox.Cancel | QMessageBox.Save | QMessageBox.Discard dlg.setStandardButtons(opt) dlg.setIcon(QMessageBox.Warning) dlg.button(QMessageBox.Save).setText(self._trad["Save"]) + dlg.button(QMessageBox.Discard).setText(self._trad["Don't save"]) dlg.button(QMessageBox.Cancel).setText(self._trad["Cancel"]) res = dlg.exec() if res == QMessageBox.Save: - return True + return "Save" elif res == QMessageBox.Cancel: - return False + return "Cancel" + else: + return "Discard" def select_scenario(self, item): if type(item) is not ScenarioItem: return - must_saved = self.dialog_save() + must_save = self.dialog_save() + if must_save == "Cancel": + return def fn(): self._close_other_window() - if must_saved: + if must_save == "Save": self._study.save() self._undo.push( @@ -465,9 +470,16 @@ class GraphWidget(QGraphicsView): self.changeScenario.emit(self.sender()) def new_scenario(self, pos): + must_save = self.dialog_save() + if must_save == "Cancel": + return + def fn(): self._close_other_window() - self._study.save() + + if must_save == "Save": + self._study.save() + self._undo.push( AddScenariosCommand( self._study, @@ -495,9 +507,16 @@ class GraphWidget(QGraphicsView): self.changeScenario.emit(self.sender()) def duplicate_scenario(self, item): + must_save = self.dialog_save() + if must_save == "Cancel": + return + def fn(): self._close_other_window() - # self._study.save() + + if must_save == "Save": + self._study.save() + self._undo.push( DuplicateScenariosCommand( self._study, diff --git a/src/View/Translate.py b/src/View/Translate.py index 82f29b50..c0872a74 100644 --- a/src/View/Translate.py +++ b/src/View/Translate.py @@ -281,6 +281,7 @@ class MainTranslate(UnitTranslate): self._dict["No"] = _translate("MainWindow", "No") self._dict["Cancel"] = _translate("MainWindow", "Cancel") self._dict["Save"] = _translate("MainWindow", "Save") + self._dict["Don't save"] = _translate("MainWindow", "Don't save") self._dict["Close"] = _translate("MainWindow", "Close") self._dict["Solver"] = _translate("MainWindow", "Solver") diff --git a/src/View/WaitingDialog.py b/src/View/WaitingDialog.py index b61228e6..b9ee9947 100644 --- a/src/View/WaitingDialog.py +++ b/src/View/WaitingDialog.py @@ -90,6 +90,7 @@ class WaitingDialog(PamhyrDialog): options=[], parent=parent ) + self._to_close = False self._payload_fn = payload_fn @@ -131,7 +132,7 @@ class WaitingDialog(PamhyrDialog): ) def end_worker(self): - self._worker_thread.terminate() + self._worker_thread.quit() self._worker_thread.wait() def close(self): @@ -141,4 +142,12 @@ class WaitingDialog(PamhyrDialog): except Exception as e: logger_exception(e) + self._to_close = True + super().close() + + def closeEvent(self, event): + if self._to_close: + super().closeEvent(event) + else: + event.ignore()