Merge branch 'scenarios' into dev_dylan

disable_edition_parent_scenario
Dylan Jeannin 2026-05-22 14:38:52 +02:00
commit f86cd4602c
23 changed files with 207 additions and 86 deletions

View File

@ -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)

View File

@ -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(

View File

@ -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)

View File

@ -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):

View File

@ -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

View File

@ -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()

View File

@ -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}")

View File

@ -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 " +

View File

@ -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

View File

@ -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
)

View File

@ -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()

View File

@ -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(

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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"
)

View File

@ -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,

View File

@ -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
)

View File

@ -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())

View File

@ -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"]

View File

@ -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,

View File

@ -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")

View File

@ -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()