diff --git a/src/Model/BoundaryCondition/BoundaryCondition.py b/src/Model/BoundaryCondition/BoundaryCondition.py index 86e0f1d2048bd4e28c3c9fd981d512c52c2fed5d..d6abb6c9ea8a847ba3c236d517abe5a2f3c4a444 100644 --- a/src/Model/BoundaryCondition/BoundaryCondition.py +++ b/src/Model/BoundaryCondition/BoundaryCondition.py @@ -83,7 +83,8 @@ class BoundaryCondition(SQLSubModel): def _get_ctor_from_type(cls, t): from Model.BoundaryCondition.BoundaryConditionTypes import ( NotDefined, PonctualContribution, - TimeOverZ, TimeOverDischarge, ZOverDischarge + TimeOverZ, TimeOverDischarge, ZOverDischarge, + Solid, ) res = NotDefined @@ -95,6 +96,8 @@ class BoundaryCondition(SQLSubModel): res = TimeOverDischarge elif t == "ZD": res = ZOverDischarge + elif t == "SL": + res = Solid return res @classmethod diff --git a/src/Model/BoundaryCondition/BoundaryConditionList.py b/src/Model/BoundaryCondition/BoundaryConditionList.py index 1a342dc781bd846d47c81bdac4c0321a5c64ee6b..2514f32694a598ee1a2c690cf38dc58e4013d560 100644 --- a/src/Model/BoundaryCondition/BoundaryConditionList.py +++ b/src/Model/BoundaryCondition/BoundaryConditionList.py @@ -26,7 +26,8 @@ from Model.BoundaryCondition.BoundaryCondition import BoundaryCondition from Model.BoundaryCondition.BoundaryConditionTypes import ( NotDefined, PonctualContribution, - TimeOverZ, TimeOverDischarge, ZOverDischarge + TimeOverZ, TimeOverDischarge, ZOverDischarge, + Solid, ) class BoundaryConditionList(SQLSubModel): diff --git a/src/Model/BoundaryCondition/BoundaryConditionTypes.py b/src/Model/BoundaryCondition/BoundaryConditionTypes.py index b2c73f11326b652184cc92d9fd3d01ae361490a8..963e88e72d58463e2c62b84be9d11733acd3e0a3 100644 --- a/src/Model/BoundaryCondition/BoundaryConditionTypes.py +++ b/src/Model/BoundaryCondition/BoundaryConditionTypes.py @@ -83,3 +83,16 @@ class ZOverDischarge(BoundaryCondition): @property def _default_0(self): return 0.0 + + +class Solid(BoundaryCondition): + def __init__(self, id:int = -1, name:str = "", status = None): + super(Solid, self).__init__(id=id, name=name, status=status) + + self._type = "SL" + self._header = ["time", "solid"] + self._types = [TimeOverDischarge.time_convert, float] + + @classmethod + def compatibility(cls): + return ["solid"] diff --git a/src/Model/Geometry/Point.py b/src/Model/Geometry/Point.py index b3b51e7d07179b24c655136455b682f940f60145..cc1971c096db57224da5c4ef046a68c41ec864b8 100644 --- a/src/Model/Geometry/Point.py +++ b/src/Model/Geometry/Point.py @@ -19,12 +19,29 @@ from Model.Except import NotImplementedMethodeError class Point(object): - def __init__(self, name:str = "", status = None): + def __init__(self, name:str = "", profile=None, status = None): super(Point, self).__init__() self._status = status self._name = name + self._profile = profile + self._sl = None + + + @property + def sl(self): + if self._sl is None: + return self._profile.sl + + return self._sl + + @sl.setter + def sl(self, sl): + if sl == self._profile.sl: + self._sl = None + + self._sl = sl @property def name(self): diff --git a/src/Model/Geometry/PointXYZ.py b/src/Model/Geometry/PointXYZ.py index cffcee971f6f2b2251353402bd60dfba1073e2a7..19695c7753c63aa93a81e43d508b64c85208dd7f 100644 --- a/src/Model/Geometry/PointXYZ.py +++ b/src/Model/Geometry/PointXYZ.py @@ -26,8 +26,8 @@ class PointXYZ(Point, SQLSubModel): _sub_classes = [] def __init__(self, x:float = 0.0, y:float = 0.0, z:float = 0.0, - name:str = "", status = None): - super(PointXYZ, self).__init__(name=name, status=status) + name:str = "", profile = None, status = None): + super(PointXYZ, self).__init__(name=name, profile=profile, status=status) self._x = float(x) self._y = float(y) @@ -44,7 +44,9 @@ class PointXYZ(Point, SQLSubModel): y INTEGER NOT NULL, z INTEGER NOT NULL, profile INTEGER NOT NULL, - FOREIGN KEY(profile) REFERENCES profileXYZ(id) + sl INTEGER, + FOREIGN KEY(profile) REFERENCES profileXYZ(id), + FOREIGN KEY(sl) REFERENCES sedimentary_layer(id) ) """) @@ -53,6 +55,17 @@ class PointXYZ(Point, SQLSubModel): @classmethod def _sql_update(cls, execute, version): cls._update_submodel(execute, version) + + major, minor, release = version.strip().split(".") + if major == minor == "0": + if int(release) < 2: + execute( + """ + ALTER TABLE geometry_pointXYZ + ADD COLUMN sl INTEGER + REFERENCES sedimentary_layer(id) + """ + ) return True @classmethod @@ -63,9 +76,9 @@ class PointXYZ(Point, SQLSubModel): profile = data["profile"] table = execute( - "SELECT ind, name, x, y, z " + + "SELECT ind, name, x, y, z, sl " + "FROM geometry_pointXYZ " + - f"WHERE profile = {profile}" + f"WHERE profile = {profile.id}" ) # Create points list @@ -79,13 +92,25 @@ class PointXYZ(Point, SQLSubModel): x = row[2] y = row[3] z = row[4] + sl = row[5] new = cls( name = name, x = x, y = y, z = z, + profile = profile, status = status ) + if sl == -1 or sl == None: + new._sl = None + else: + new._sl = next( + filter( + lambda s: s.id == sl, + data["sediment_layers_list"].sediment_layers + ) + ) + points[ind] = new return points @@ -94,13 +119,15 @@ class PointXYZ(Point, SQLSubModel): profile = data["profile"] ind = data["ind"] + sl = self._sl.id if self._sl is not None else -1 + sql = ( "INSERT OR REPLACE INTO " + - "geometry_pointXYZ(ind, name, x, y, z, profile) "+ + "geometry_pointXYZ(ind, name, x, y, z, profile, sl) "+ "VALUES (" + f"{ind}, '{self._sql_format(self._name)}', " + f"{self.x}, {self.y}, {self.z}, " + - f"{profile}" + + f"{profile.id}, {sl}" + ")" ) execute(sql) @@ -117,7 +144,7 @@ class PointXYZ(Point, SQLSubModel): *data ) else: - valid_header = {'name', 'x', 'y', 'z'} + valid_header = {'name', 'x', 'y', 'z', 'profile'} d = {} for i, v in enumerate(data): h = header[i].strip().lower().split(' ')[0] diff --git a/src/Model/Geometry/Profile.py b/src/Model/Geometry/Profile.py index c95d2282ad7634a2790a42558eaa79c8f50897d9..d81379322e1c990f4a0d33686a6519de7bab59a8 100644 --- a/src/Model/Geometry/Profile.py +++ b/src/Model/Geometry/Profile.py @@ -50,6 +50,7 @@ class Profile(object): self._kp = float(kp) self._name = str(name) self._reach = reach + self._sl = None self._points: List[Point] = [] @@ -63,6 +64,9 @@ class Profile(object): def points(self): return self._points.copy() + def point(self, index): + return self._points[index] + @property def reach(self): return self._reach @@ -136,6 +140,19 @@ class Profile(object): self._name = value.strip() self._status.modified() + @property + def sl(self): + """ + Returns: + Profile sediment layers. + """ + return self._sl + + @sl.setter + def sl(self, value): + self._sl = value + self._status.modified() + @property def profile_type(self): """ @@ -274,6 +291,39 @@ class Profile(object): ) self._status.modified() + # Sediment Layers + + def get_sl(self): + """Get sediment layer height of points + + Get sediment layer of points (without spesific point sl) + + Returns: + List of sediment layers height + """ + res = [] + psl = [point.sl for point in self.points] + + # Compute max number of layers + sl_max = 0 + for sl in psl: + n = 0 if sl is None else len(sl) + sl_max = max(n, sl_max) + + # Create list of height for each sl and each layer + for i in range(0, sl_max): + cur = [] + # Compute new layer line for each sl + for sl in psl: + if sl is not None and i < len(sl): + cur.append(sl.get(i).height) + else: + cur.append(0) + # Add layer line to result + res.append(cur) + + return res + # Abstract method, must be implemented for in non abstract class def get_station(self): raise NotImplementedMethodeError(self, self.get_station) diff --git a/src/Model/Geometry/ProfileXYZ.py b/src/Model/Geometry/ProfileXYZ.py index 9cf15da4ad411161998b1cb6fe10c90b1c6c9b7c..e818eabce44a8d5de19c3fc07fad52245f2ffd57 100644 --- a/src/Model/Geometry/ProfileXYZ.py +++ b/src/Model/Geometry/ProfileXYZ.py @@ -76,7 +76,9 @@ class ProfileXYZ(Profile, SQLSubModel): num INTEGER NOT NULL, code1 INTEGER NOT NULL, code2 INTEGER NOT NULL, - FOREIGN KEY(reach) REFERENCES river_reach(id) + sl INTEGER, + FOREIGN KEY(reach) REFERENCES river_reach(id), + FOREIGN KEY(sl) REFERENCES sedimentary_layer(id) ) """) @@ -84,6 +86,17 @@ class ProfileXYZ(Profile, SQLSubModel): @classmethod def _sql_update(cls, execute, version): + major, minor, release = version.strip().split(".") + if major == minor == "0": + if int(release) < 2: + execute( + """ + ALTER TABLE geometry_profileXYZ + ADD COLUMN sl INTEGER + REFERENCES sedimentary_layer(id) + """ + ) + return cls._update_submodel(execute, version) @classmethod @@ -93,7 +106,7 @@ class ProfileXYZ(Profile, SQLSubModel): reach = data["reach"] table = execute( - "SELECT id, ind, name, kp, num, code1, code2 " + + "SELECT id, ind, name, kp, num, code1, code2, sl " + "FROM geometry_profileXYZ " + f"WHERE reach = {reach}" ) @@ -109,6 +122,7 @@ class ProfileXYZ(Profile, SQLSubModel): num = row[5] code1 = row[5] code2 = row[6] + sl = row[7] new = cls( id=id, num = num, @@ -118,7 +132,17 @@ class ProfileXYZ(Profile, SQLSubModel): status = status ) - data["profile"] = id + if sl == -1 or sl == None: + new._sl = None + else: + new._sl = next( + filter( + lambda s: s.id == sl, + data["sediment_layers_list"].sediment_layers + ) + ) + + data["profile"] = new new._points = PointXYZ._sql_load(execute, data) profiles[ind] = new @@ -129,18 +153,20 @@ class ProfileXYZ(Profile, SQLSubModel): ok = True ind = data["ind"] + sl = self._sl.id if self._sl is not None else -1 + sql = ( "INSERT OR REPLACE INTO " + - "geometry_profileXYZ(id, ind, name, reach, kp, num, code1, code2) "+ + "geometry_profileXYZ(id, ind, name, reach, kp, num, code1, code2, sl) "+ "VALUES (" + f"{self.id}, {ind}, '{self._sql_format(self._name)}', " + f"{self.reach.id}, {self.kp}, {self.num}, " + - f"{self.code1}, {self.code1}" + + f"{self.code1}, {self.code1}, {sl}" + ")" ) execute(sql) - data["profile"] = self.id + data["profile"] = self execute(f"DELETE FROM geometry_pointXYZ WHERE profile = {self.id}") ind = 0 @@ -178,11 +204,11 @@ class ProfileXYZ(Profile, SQLSubModel): try: if len(header) == 0: point = PointXYZ( - *data, status=self._status + *data, profile=self, status=self._status ) else: valid_header = {'name', 'x', 'y', 'z'} - d = {"status": self._status} + d = {"status": self._status, "profile": self} for i, v in enumerate(data): h = header[i].strip().lower().split(' ')[0] if h in valid_header: @@ -235,7 +261,7 @@ class ProfileXYZ(Profile, SQLSubModel): Nothing. """ for point in list_points: - pt = PointXYZ(*point, status=self._status) + pt = PointXYZ(*point, profile=self, status=self._status) self._points.append(pt) self._status.modified() @@ -259,7 +285,7 @@ class ProfileXYZ(Profile, SQLSubModel): Returns: Nothing. """ - point_xyz = PointXYZ(0., 0., 0., status=self._status) + point_xyz = PointXYZ(0., 0., 0., profile=self, status=self._status) self._points.append(point_xyz) self._status.modified() @@ -272,7 +298,7 @@ class ProfileXYZ(Profile, SQLSubModel): Returns: The new point. """ - point = PointXYZ(0., 0., 0., status=self._status) + point = PointXYZ(0., 0., 0., profile=self, status=self._status) self._points.insert(index, point) self._status.modified() return point diff --git a/src/Model/Geometry/Reach.py b/src/Model/Geometry/Reach.py index d2a573f11153b6e037dfcef5cfc8c5ef0c7a3898..0ddf06d8a21510e9f0d89df58889e0bd86430e85 100644 --- a/src/Model/Geometry/Reach.py +++ b/src/Model/Geometry/Reach.py @@ -276,6 +276,39 @@ class Reach(SQLSubModel): return max(self.get_kp()) + # Sediment Layers + + def get_sl(self): + """Get sediment layer height of profile + + Get sediment layer of profile (without spesific point sl) + + Returns: + List of sediment layers height + """ + res = [] + psl = [profile.sl for profile in self.profiles] + + # Compute max number of layers + sl_max = 0 + for sl in psl: + n = 0 if sl is None else len(sl) + sl_max = max(n, sl_max) + + # Create list of height for each sl and each layer + for i in range(0, sl_max): + cur = [] + # Compute new layer line for each sl + for sl in psl: + if sl is not None and i < len(sl): + cur.append(sl.get(i).height) + else: + cur.append(0) + # Add layer line to result + res.append(cur) + + return res + # Guidelines @timer diff --git a/src/Model/Results/River/River.py b/src/Model/Results/River/River.py index 5ae1e90f50f755868c3e24434b289abed3d5ad8c..9f72f7318cb033f79db8113919615b6acd220de4 100644 --- a/src/Model/Results/River/River.py +++ b/src/Model/Results/River/River.py @@ -98,6 +98,9 @@ class River(object): def reach(self, id): return self._reachs[id] + def has_reach(self, id): + return 0 <= id < len(self._reachs) + def add(self, reach_id): reachs = self._study.river.enable_edges() diff --git a/src/Model/River.py b/src/Model/River.py index d0ae5b2293fcea1a075c1f61b7494aa6e08d6f67..91d765ba05f31cc1e779155fab7a5decd0af0693 100644 --- a/src/Model/River.py +++ b/src/Model/River.py @@ -16,6 +16,8 @@ # -*- coding: utf-8 -*- +from tools import flatten + from Model.DB import SQLSubModel from Model.Network.Node import Node @@ -31,6 +33,7 @@ from Model.InitialConditions.InitialConditionsDict import InitialConditionsDict from Model.Stricklers.StricklersList import StricklersList from Model.Friction.FrictionList import FrictionList from Model.SolverParameters.SolverParametersList import SolverParametersList +from Model.SedimentLayer.SedimentLayerList import SedimentLayerList from Solver.Solvers import solver_type_list @@ -208,6 +211,7 @@ class River(Graph, SQLSubModel): InitialConditionsDict, StricklersList, SolverParametersList, + SedimentLayerList, ] def __init__(self, status=None): @@ -223,6 +227,7 @@ class River(Graph, SQLSubModel): self._initial_conditions = InitialConditionsDict(status=self._status) self._stricklers = StricklersList(status=self._status) self._parameters = {} + self._sediment_layers = SedimentLayerList(status=self._status) @classmethod def _sql_create(cls, execute): @@ -246,6 +251,14 @@ class River(Graph, SQLSubModel): ) data["stricklers"] = new._stricklers + + # Initial conditions + new._sediment_layers = SedimentLayerList._sql_load( + execute, + data + ) + data["sediment_layers_list"] = new._sediment_layers + # Network new._nodes = RiverNode._sql_load( execute, @@ -289,6 +302,7 @@ class River(Graph, SQLSubModel): objs.append(self._boundary_condition) objs.append(self._initial_conditions) objs.append(self._lateral_contribution) + objs.append(self._sediment_layers) objs.append(self._stricklers) for solver in self._parameters: @@ -317,6 +331,10 @@ class River(Graph, SQLSubModel): def initial_conditions(self): return self._initial_conditions + @property + def sediment_layers(self): + return self._sediment_layers + @property def stricklers(self): return self._stricklers @@ -366,3 +384,16 @@ class River(Graph, SQLSubModel): def set_current_reach(self, reach): self._current_reach = reach + + def has_sediment(self): + has = len(self._sediment_layers) != 0 + has &= any( + filter( + lambda p: p.sl != None, + flatten( + map(lambda e: e.reach.profiles, self.edges()) + ) + ) + ) + + return has diff --git a/src/Model/SedimentLayer/SedimentLayer.py b/src/Model/SedimentLayer/SedimentLayer.py new file mode 100644 index 0000000000000000000000000000000000000000..638212c82a8cb267a856d9d1795f5f6fc0da5121 --- /dev/null +++ b/src/Model/SedimentLayer/SedimentLayer.py @@ -0,0 +1,339 @@ +# -*- coding: utf-8 -*- + +from tools import trace, timer + +from Model.DB import SQLSubModel +from Model.Except import NotImplementedMethodeError + +class Layer(SQLSubModel): + _sub_classes = [] + _id_cnt = 0 + + def __init__(self, + id:int = -1, name:str = "", + type = "", + height = 0.0, d50 = 0.0, sigma = 0.0, + critical_constraint = 0.0, + sl = None, status = None): + super(Layer, self).__init__() + + self._status = status + + self._name = name + self._type = type + + self._height = height + self._d50 = d50 + self._sigma = sigma + self._critical_constraint = critical_constraint + + if id == -1: + self.id = Layer._id_cnt + else: + self.id = id + + Layer._id_cnt = max(id, Layer._id_cnt+1) + + @property + def name(self): + return self._name + + @name.setter + def name(self, name): + self._name = name + + @property + def type(self): + return self._type + + @type.setter + def type(self, type): + self._type = type + + @property + def height(self): + return self._height + + @height.setter + def height(self, height): + self._height = float(height) + + @property + def d50(self): + return self._d50 + + @d50.setter + def d50(self, d50): + self._d50 = float(d50) + + @property + def sigma(self): + return self._sigma + + @sigma.setter + def sigma(self, sigma): + self._sigma = float(sigma) + + @property + def critical_constraint(self): + return self._critical_constraint + + @critical_constraint.setter + def critical_constraint(self, critical_constraint): + self._critical_constraint = float(critical_constraint) + + @classmethod + def _sql_create(cls, execute): + execute(""" + CREATE TABLE sedimentary_layer_layer( + id INTEGER NOT NULL PRIMARY KEY, + ind INTEGER NOT NULL, + name TEXT NOT NULL, + type TEXT NOT NULL, + height REAL NOT NULL, + d50 REAL NOT NULL, + sigma REAL NOT NULL, + critical_constraint REAL NOT NULL, + sl INTEGER, + FOREIGN KEY(sl) REFERENCES sedimentary_layer(id) + ) + """) + + return cls._create_submodel(execute) + + @classmethod + def _sql_update(cls, execute, version): + major, minor, release = version.strip().split(".") + if major == minor == "0": + if int(release) < 2: + cls._sql_create(execute) + + return True + + @classmethod + def _sql_load(cls, execute, data = None): + new = [] + sl = data["sl"] + + table = execute( + "SELECT id, ind, name, type, height, d50, sigma, critical_constraint " + + "FROM sedimentary_layer_layer " + + f"WHERE sl = {sl}" + ) + + for _ in table: + new.append(None) + + for row in table: + ind = row[1] + + layer = cls( + id = row[0], name = row[2], + type = row[3], height = row[4], + d50 = row[5], sigma = row[6], + critical_constraint = row[7], + sl = sl, status = data['status'] + ) + + new[ind] = layer + + return new + + def _sql_save(self, execute, data = None): + ind = data["ind"] + sl = data["sl"] + + sql = ( + "INSERT INTO " + + "sedimentary_layer_layer(id, ind, name, type, height, d50, sigma, critical_constraint, sl) "+ + "VALUES (" + + f"{self.id}, {ind}, '{self._sql_format(self._name)}', " + + f"'{self._sql_format(self._type)}', {self._height}, " + + f"{self._d50}, {self._sigma}, {self._critical_constraint}, " + + f"{sl.id}" + + ")" + ) + execute(sql) + + return True + + +class SedimentLayer(SQLSubModel): + _sub_classes = [Layer] + _id_cnt = 0 + + def __init__(self, id:int = -1, + name:str = "", comment:str = "", + status = None): + super(SedimentLayer, self).__init__() + + self._status = status + self._name = name + self._comment = comment + self._layers = [] + + if id == -1: + self.id = SedimentLayer._id_cnt + else: + self.id = id + + SedimentLayer._id_cnt = max(id, SedimentLayer._id_cnt+1) + + def __str__(self): + s = f"{self.name} ({len(self)})" + + if self.comment != "": + s += f" - {self.comment}" + + return s + + def __len__(self): + return len(self._layers) + + @property + def layers(self): + return self._layers.copy() + + def height(self): + return list( + map(lambda l: l.height, self._layers) + ) + + @property + def name(self): + return self._name + + @name.setter + def name(self, name): + self._name = name + + def names(self): + return list( + map(lambda l: l.name, self._layers) + ) + + @property + def comment(self): + return self._comment + + @comment.setter + def comment(self, comment): + self._comment = comment + + @classmethod + def _sql_create(cls, execute): + execute(""" + CREATE TABLE sedimentary_layer( + id INTEGER NOT NULL PRIMARY KEY, + name TEXT NOT NULL, + comment TEXT NOT NULL + ) + """) + + return cls._create_submodel(execute) + + @classmethod + def _sql_update(cls, execute, version): + major, minor, release = version.strip().split(".") + if major == minor == "0": + if int(release) < 2: + cls._sql_create(execute) + + return True + + @classmethod + def _sql_load(cls, execute, data = None): + new = [] + + table = execute( + "SELECT id, name, comment " + + "FROM sedimentary_layer " + ) + + for row in table: + sl = cls( + id = row[0], + name = row[1], + comment = row[2], + status = data['status'] + ) + + data["sl"] = sl.id + sl._layers = Layer._sql_load(execute, data) + + new.append(sl) + + return new + + def _sql_save(self, execute, data = None): + if data is None: + data = {} + + sql = ( + "INSERT INTO sedimentary_layer (id, name, comment) " + + f"VALUES ({self.id}, '{self._sql_format(self._name)}', " + + f"'{self._sql_format(self._comment)}')" + ) + execute(sql) + + data["sl"] = self + + ind = 0 + for l in self._layers: + data["ind"] = ind + l._sql_save(execute, data) + ind += 1 + + return True + + def get(self, index): + return self._layers[index] + + def set(self, index, new): + self._layers[index] = new + self._status.modified() + + def insert(self, index, new): + self._layers.insert(index, new) + self._status.modified() + + def new(self, index): + n = Layer(sl=self, status = self._status) + self.insert(index, n) + self._status.modified() + return n + + def delete(self, els): + for el in els: + self._layers.remove(el) + self._status.modified() + + def delete_i(self, indexes): + els = list( + map( + lambda x: x[1], + filter( + lambda x: x[0] in indexes, + enumerate(self._layers) + ) + ) + ) + self.delete(els) + + def move_up(self, index): + if index >= 0: + next = index - 1 + + l = self._layers + l[index], l[next] = l[next], l[index] + + self._status.modified() + + def move_down(self, index): + if index + 1 < len(self._layers): + prev = index + 1 + + l = self._layers + l[index], l[prev] = l[prev], l[index] + + self._status.modified() diff --git a/src/Model/SedimentLayer/SedimentLayerList.py b/src/Model/SedimentLayer/SedimentLayerList.py new file mode 100644 index 0000000000000000000000000000000000000000..d2b662269cef4d574b6c75b493f3a8bc0c6bbe5f --- /dev/null +++ b/src/Model/SedimentLayer/SedimentLayerList.py @@ -0,0 +1,101 @@ +# -*- coding: utf-8 -*- + +from tools import trace, timer + +from Model.DB import SQLSubModel +from Model.Except import NotImplementedMethodeError +from Model.SedimentLayer.SedimentLayer import SedimentLayer + +class SedimentLayerList(SQLSubModel): + _sub_classes = [SedimentLayer] + + def __init__(self, status = None): + super(SedimentLayerList, self).__init__() + + self._status = status + self._sl = [] + + def __len__(self): + return len(self._sl) + + @classmethod + def _sql_create(cls, execute): + return cls._create_submodel(execute) + + @classmethod + def _sql_update(cls, execute, version): + return cls._update_submodel(execute, version) + + @classmethod + def _sql_load(cls, execute, data = None): + new = cls(status = data["status"]) + + new._sl = SedimentLayer._sql_load(execute, data) + + return new + + def _sql_save(self, execute, data = None): + ok = True + + # Delete previous data + execute("DELETE FROM sedimentary_layer") + execute("DELETE FROM sedimentary_layer_layer") + + for sl in self._sl: + ok &= sl._sql_save(execute, data) + + return ok + + @property + def sediment_layers(self): + return self._sl.copy() + + def get(self, index): + return self._sl[index] + + def set(self, index, new): + self._sl[index] = new + self._status.modified() + + def insert(self, index, new): + self._sl.insert(index, new) + self._status.modified() + + def new(self, index): + n = SedimentLayer(status = self._status) + self.insert(index, n) + self._status.modified() + return n + + def delete(self, els): + for el in els: + self._sl.remove(el) + self._status.modified() + + def delete_i(self, indexes): + els = list( + map( + lambda x: x[1], + filter( + lambda x: x[0] in indexes, + enumerate(self._sl) + ) + ) + ) + self.delete(els) + + def move_up(self, index): + if index < len(self._sl): + next = index - 1 + + l = self._sl + l[index], l[next] = l[next], l[index] + self._status.modified() + + def move_down(self, index): + if index >= 0: + prev = index + 1 + + l = self._sl + l[index], l[prev] = l[prev], l[index] + self._status.modified() diff --git a/src/Model/SolverParameters/SolverParametersList.py b/src/Model/SolverParameters/SolverParametersList.py index a647135680c9e71e41d3c2b316302ee67f71f620..18c455296442ecb9c58493caa7130fff558b6f20 100644 --- a/src/Model/SolverParameters/SolverParametersList.py +++ b/src/Model/SolverParameters/SolverParametersList.py @@ -93,6 +93,13 @@ class SolverParametersList(SQLSubModel): @classmethod def _sql_update(cls, execute, version): + major, minor, release = version.strip().split(".") + + if major == minor == "0": + if int(release) < 3: + execute(f"UPDATE solver_parameter SET name='mage_implicitation' WHERE name='mage_implication'") + execute(f"UPDATE solver_parameter SET name='mage_iteration_type' WHERE name='mage_iter_type'") + return cls._update_submodel(execute, version) @classmethod @@ -160,6 +167,17 @@ class SolverParametersList(SQLSubModel): def get(self, index): return self._parameters[index] + def get_by_key(self, key): + try: + return next( + filter( + lambda p: p["name"] == key, + self._parameters + ) + )["value"] + except: + return None + def set(self, index, new): self._parameters[index] = new self._status.modified() diff --git a/src/Model/Study.py b/src/Model/Study.py index af594c90257a258162af72142567703a14c4cd64..5b44d0a7455a17d5795996b30728dd2071431343 100644 --- a/src/Model/Study.py +++ b/src/Model/Study.py @@ -40,7 +40,7 @@ class Study(SQLModel): def __init__(self, filename = None, init_new = True): # Metadata - self._version = "0.0.1" + self._version = "0.0.3" self.creation_date = datetime.now() self.last_modification_date = datetime.now() self.last_save_date = datetime.now() diff --git a/src/Solver/ASolver.py b/src/Solver/ASolver.py index 64cc01cbffb4cb71dddf7c17e4f84b7160bcd43f..186abbbbd9c93a05ceaa748d1a119e130c854f31 100644 --- a/src/Solver/ASolver.py +++ b/src/Solver/ASolver.py @@ -86,6 +86,7 @@ class AbstractSolver(object): ("all_init_time", "000:00:00:00"), ("all_final_time", "999:99:00:00"), ("all_timestep", "300.0"), + ("all_command_line_arguments", ""), ] return lst @@ -153,6 +154,17 @@ class AbstractSolver(object): def export(self, study, repertory, qlog = None): raise NotImplementedMethodeError(self, self.export) + def cmd_args(self, study): + """Return solver command line arguments list + + Returns: + Command line arguments list + """ + params = study.river.get_params(self.type) + args = params.get_by_key("all_command_line_arguments") + + return args.split(" ") + def input_param(self): """Return input command line parameter(s) @@ -190,7 +202,7 @@ class AbstractSolver(object): ) ) - def _format_command(self, cmd, path = ""): + def _format_command(self, study, cmd, path = ""): """Format command line Args: @@ -206,6 +218,7 @@ class AbstractSolver(object): cmd = cmd.replace("@path", path.replace(" ", "\ ")) cmd = cmd.replace("@input", self.input_param()) cmd = cmd.replace("@dir", self._process.workingDirectory()) + cmd = cmd.replace("@args", " ".join(self.cmd_args(study))) logger.debug(f"! {cmd}") @@ -213,23 +226,33 @@ class AbstractSolver(object): # Command line executable path is between " char cmd = cmd.split("\"") exe = cmd[1].replace("\ ", " ") - args = "\"".join(cmd[2:]).split(" ")[1:] + args = list( + filter( + lambda s: s != "", + "\"".join(cmd[2:]).split(" ")[1:] + ) + ) else: # We suppose the command line executable path as no space char cmd = cmd.replace("\ ", "&_&").split(" ") exe = cmd[0].replace("&_&", " ") - args = list(map(lambda s: s.replace("&_&", "\ "), cmd[1:])) + args = list( + filter( + lambda s: s != "", + map(lambda s: s.replace("&_&", "\ "), cmd[1:]) + ) + ) logger.info(f"! {exe} {args}") return exe, args - def run_input_data_fomater(self): + def run_input_data_fomater(self, study): if self._cmd_input == "": - self._run_next() + self._run_next(study) return True cmd = self._cmd_input - exe, args = self._format_command(cmd, self._path_input) + exe, args = self._format_command(study, cmd, self._path_input) if not os.path.exists(exe): error = f"[ERROR] Path {exe} do not exists" @@ -242,13 +265,13 @@ class AbstractSolver(object): return True - def run_solver(self): + def run_solver(self, study): if self._cmd_solver == "": - self._run_next() + self._run_next(study) return True cmd = self._cmd_solver - exe, args = self._format_command(cmd, self._path_solver) + exe, args = self._format_command(study, cmd, self._path_solver) if not os.path.exists(exe): error = f"[ERROR] Path {exe} do not exists" @@ -262,13 +285,13 @@ class AbstractSolver(object): self._status = STATUS.RUNNING return True - def run_output_data_fomater(self): + def run_output_data_fomater(self, study): if self._cmd_output == "": - self._run_next() + self._run_next(study) return True cmd = self._cmd_output - exe, args = self._format_command(cmd, self._path_output) + exe, args = self._format_command(study, cmd, self._path_output) if not os.path.exists(exe): error = f"[ERROR] Path {exe} do not exists" @@ -287,29 +310,29 @@ class AbstractSolver(object): for x in s.split('\n'): self._output.put(x) - def _run_next(self): + def _run_next(self, study): self._step += 1 if self._step < len(self._runs): - res = self._runs[self._step]() + res = self._runs[self._step](study) if res is not True: self._output.put(res) else: self._status = STATUS.STOPED - def _finished(self, exit_code, exit_status): + def _finished(self, study, exit_code, exit_status): if self._output is not None: self._output.put(exit_code) - self._run_next() + self._run_next(study) - def run(self, process = None, output_queue = None): + def run(self, study, process = None, output_queue = None): if process is not None: self._process = process if output_queue is not None: self._output = output_queue self._process.readyRead.connect(self._data_ready) - self._process.finished.connect(self._finished) + self._process.finished.connect(lambda c, s: self._finished(study, c, s)) self._runs = [ self.run_input_data_fomater, @@ -318,7 +341,7 @@ class AbstractSolver(object): ] self._step = 0 # Run first step - res = self._runs[0]() + res = self._runs[0](study) if res is not True: self._output.put(res) @@ -330,14 +353,14 @@ class AbstractSolver(object): self._status = STATUS.STOPED return True - def start(self, process = None): + def start(self, study, process = None): if _signal: if self._status == STATUS.PAUSED: os.kill(self._process.pid(), SIGCONT) self._status = STATUS.RUNNING return True - self.run(process) + self.run(study, process) return True def pause(self): diff --git a/src/Solver/Mage.py b/src/Solver/Mage.py index 6c129f887d717a19d0b88b06fca112451143b7ad..aa68802f1a96868104214559faeefeb2553c9927 100644 --- a/src/Solver/Mage.py +++ b/src/Solver/Mage.py @@ -61,11 +61,11 @@ class Mage(AbstractSolver): ("mage_timestep_tra", "3600"), ("mage_timestep_bin", "0"), # ("mage_timestep_melissa", "0"), - ("mage_implication", "0.70"), + ("mage_implicitation", "0.70"), ("mage_continuity_discretization", "S"), ("mage_qsj_discretization", "B"), ("mage_stop_criterion_iterations", "R"), - ("mage_iter_type", "0"), + ("mage_iteration_type", "0"), ("mage_smooth_coef", "0"), ("mage_cfl_max", "-1."), ("mage_min_height", "0.1"), @@ -97,6 +97,13 @@ class Mage(AbstractSolver): # Export # ########## + def cmd_args(self, study): + l = super(Mage, self).cmd_args(study) + + l.append("-r") + + return l + def input_param(self): return "0.REP" @@ -127,23 +134,47 @@ class Mage(AbstractSolver): with mage_file_open(os.path.join(repertory, f"{name}.ST"), "w+") as f: files.append(f"{name}.ST") + cnt_num = 1 for profile in edge.reach.profiles: - num = f"{profile.num:>6}" + num = f"{cnt_num:>6}" c1 = f"{profile.code1:>6}" c2 = f"{profile.code2:>6}" t = f"{len(profile.points):>6}" kp = f"{profile.kp:>13.4f}" - name = profile.name + pname = profile.name + if profile.name == "": + pname = f"p{profile.id:>3}".replace(" ", "p") + name = f"{pname}" + + sediment = "" + if profile.sl is not None: + if not any(filter(lambda f: ".GRA" in f, files)): + files.append("0.GRA") + + nl = len(profile.sl) + sediment = f" {nl:>3}" + for l in profile.sl.layers: + sediment += f" {l.height:>10} {l.d50:>10} {l.sigma:>10} {l.critical_constraint:>10}" - f.write(f"{num}{c1}{c2}{t}{kp} {name}\n") + f.write(f"{num}{c1}{c2}{t}{kp} {name} {sediment}\n") + cnt_num += 1 for point in profile.points: x = f"{point.x:>13.4f}" y = f"{point.y:>13.4f}" z = f"{point.z:>13.4f}" - n = point.name + n = f"{point.name:<3}" - f.write(f"{x}{y}{z} {n}\n") + sediment = "" + prev = point.z + if point.sl is not None: + nl = len(point.sl) + sediment = f"{nl:>3}" + for l in point.sl.layers: + prev = round(prev - l.height, 5) + sediment += f" {prev:>10} {l.d50:>10} {l.sigma:>10} {l.critical_constraint:>10}" + + f.write(f"{x}{y}{z} {n} {sediment}\n") f.write(f" 999.9990 999.9990 999.9990\n") @@ -170,7 +201,7 @@ class Mage(AbstractSolver): f.write(f"*{header[0]:>9}|{header[1]:>10}\n") for d in bound.data: - f.write(f"{d[0]:10.3f}{d[1]:10.3f}\n") + f.write(f"{d[0]:10}{d[1]:10}\n") return files @@ -182,6 +213,7 @@ class Mage(AbstractSolver): AVA = [] HYD = [] LIM = [] + QSO = [] for tab in ["liquid", "solid", "suspenssion"]: for bound in lst.get_tab(tab): @@ -191,10 +223,13 @@ class Mage(AbstractSolver): HYD.append(bound) elif bound.bctype == "TZ": LIM.append(bound) + elif bound.bctype == "SL": + QSO.append(bound) files = files + self._export_BC("AVA", AVA, repertory, qlog) files = files + self._export_BC("HYD", HYD, repertory, qlog) files = files + self._export_BC("LIM", LIM, repertory, qlog) + files = files + self._export_QSO(QSO, repertory, qlog) return files @@ -332,12 +367,20 @@ class Mage(AbstractSolver): for file in files: EXT = file.split('.')[1] - f.write(f"{EXT} {file}\n") + if EXT not in ["ST", "GRA"]: + f.write(f"{EXT} {file}\n") f.write("* OUTPUT\n") f.write(f"TRA 0.TRA\n") f.write(f"BIN 0.BIN\n") + for file in files: + EXT = file.split('.')[1] + + if EXT in ["GRA"]: + f.write(f"{EXT} {file}\n") + + @timer def export(self, study, repertory, qlog = None): self._export_ST(study, repertory, qlog) @@ -404,6 +447,14 @@ class Mage8(Mage): # Export # ########## + def cmd_args(self, study): + l = super(Mage8, self).cmd_args(study) + + if study.river.has_sediment(): + l.append("-c=3") + + return l + @timer def _export_PAR(self, study, repertory, qlog = None): files = [] @@ -423,7 +474,6 @@ class Mage8(Mage): f.write(f"{name} {value}\n") - return files @timer @@ -459,11 +509,37 @@ class Mage8(Mage): return files + @timer + def _export_QSO(self, bounds, repertory, qlog): + files = [] + + if len(bounds) == 0: + return files + + if qlog is not None: + qlog.put(f"Export QSO file") + + with mage_file_open(os.path.join(repertory, f"0.QSO"), "w+") as f: + files.append(f"0.QSO") + + for bound in bounds: + name = f"{bound.node.id:3}".replace(" ", "x") + f.write(f"* {bound.node.name} ({name}) {bound.bctype}\n") + f.write(f"${name}\n") + header = bound.header + f.write(f"*{header[0]:>9}|{header[1]:>10}\n") + + for d in bound.data: + f.write(f"{d[0]:10.3f}{d[1]:10.3f}\n") + + return files + + @timer def export(self, study, repertory, qlog = None): files = [] - self._export_ST(study, repertory, qlog) + files = self._export_ST(study, repertory, qlog) files = files + self._export_PAR(study, repertory, qlog) files = files + self._export_NET(study, repertory, qlog) files = files + self._export_bound_cond(study, repertory, qlog) @@ -477,6 +553,7 @@ class Mage8(Mage): # RESULTS # ########### + @timer def read_bin(self, study, repertory, results, qlog = None): logger.info(f"read_bin: Start ...") @@ -597,3 +674,162 @@ class Mage8(Mage): logger.debug(reachs[0].profiles[0]._data) results.set("timestamps", ts) logger.info(f"read_bin: ... end with {len(ts)} timestamp read") + + @timer + def read_gra(self, study, repertory, results, qlog = None): + if not study.river.has_sediment(): + return + + logger.info(f"read_gra: Start ...") + + with mage_file_open(os.path.join(repertory, f"0.GRA"), "r") as f: + newline = lambda: np.fromfile(f, dtype=np.int32, count=1) + endline = lambda: np.fromfile(f, dtype=np.int32, count=1) + + read_int = lambda size: np.fromfile(f, dtype=np.int32, count=size) + read_float = lambda size: np.fromfile(f, dtype=np.float32, count=size) + read_float64 = lambda size: np.fromfile(f, dtype=np.float64, count=size) + + # Meta data (1st line) + newline() + + data = read_int(3) + + nb_reach = data[0] + nb_profile = data[1] + mage_version = data[2] + + logger.debug(f"read_gra: nb_reach = {nb_reach}") + logger.debug(f"read_gra: nb_profile = {nb_profile}") + logger.debug(f"read_gra: mage_version = {mage_version}") + + if mage_version <= 80: + msg = ( + "Read GRA files: " + + f"Possible incompatible mage version '{mage_version}', " + + "please check your solver configuration..." + ) + logger.warning(msg) + + if qlog is not None: + qlog.put("[WARNING] " + msg) + + results.set("gra_solver_version", f"Mage8 ({mage_version})") + results.set("gra_nb_reach", f"{nb_reach}") + results.set("gra_nb_profile", f"{nb_profile}") + + endline() + + # Reach information (2nd line) + newline() + + reachs = [] + iprofiles = {} + reach_offset = {} + + data = read_int(2*nb_reach) + + for i in range(nb_reach): + # Get results reach to reach list + r = results.river.reach(i) + reachs.append(r) + + # ID of first and last reach profiles + i1 = data[2*i] - 1 + i2 = data[2*i+1] - 1 + + # Add profile id correspondance to reach + key = (i1, i2) + iprofiles[key] = r + + # Profile ID offset + reach_offset[r] = i1 + + logger.debug(f"read_gra: iprofiles = {iprofiles}") + + endline() + + # X (3rd line) + newline() + _ = read_float(nb_profile) + endline() + + # npts (4th line) + newline() + _ = read_int(nb_profile) + endline() + + # Data + ip_to_r = lambda i: iprofiles[ + next( + filter( + lambda k: k[0] <= i <= k[1], + iprofiles + ) + ) + ] + ip_to_ri = lambda r, i: i - reach_offset[r] + + ts = set() + ind = 0 + end = False + + newline() + while not end: + n = read_int(1)[0] + timestamp = read_float64(1)[0] + + logger.debug(f"read_gra: timestamp = {timestamp} sec") + ts.add(timestamp) + + endline() + + for i in range(n): + newline() + nsl = read_int(1)[0] + endline() + + # Get current profile id + reach = ip_to_r(i) + ri = ip_to_ri(reach, i) + + if nsl > 1: + logger.warning( + "read_gra: " + + "Multiple sediment layers for one profile " + + "is not implemented yet..." + ) + + for j in range(nsl): + newline() + nl = read_int(1)[0] + endline() + + sl = [] + + for k in range(nl): + newline() + data = read_float64(3) + endline() + + h = data[0] + d50 = data[1] + sigma = data[2] + + sl.append((h, d50, sigma)) + + reach.set(ri, timestamp, "sl", sl) + + ind += 1 + end = newline().size <= 0 + + logger.debug(reachs[0].profiles[0]._data) + results.set("timestamps", ts) + logger.info(f"read_gra: ... end with {len(ts)} timestamp read") + + @timer + def results(self, study, repertory, qlog = None): + results = super(Mage8, self).results(study, repertory, qlog) + self.read_gra(study, repertory, results, qlog) + + return results diff --git a/src/View/BoundaryCondition/Edit/translate.py b/src/View/BoundaryCondition/Edit/translate.py index 4c09f5fce4e24180390dfca3cd9d7d6bb46ac5e4..c803979592e1baa36c2e9981b83ec6dab3e6f2f3 100644 --- a/src/View/BoundaryCondition/Edit/translate.py +++ b/src/View/BoundaryCondition/Edit/translate.py @@ -26,5 +26,6 @@ table_headers = { "time": _translate("BoundaryCondition", "Time"), "date": _translate("BoundaryCondition", "Date"), "discharge": _translate("BoundaryCondition", "Discharge (m³/s)"), - "z": _translate("BoundaryCondition", "Z (m)") + "z": _translate("BoundaryCondition", "Z (m)"), + "solid": _translate("BoundaryCondition", "Solid (kg/s)"), } diff --git a/src/View/BoundaryCondition/translate.py b/src/View/BoundaryCondition/translate.py index 661c1ad071a4a475ab3783a53b4474c3b0e0e2f1..c4ff8f8815e620d75bbde9030d82224c43acb160 100644 --- a/src/View/BoundaryCondition/translate.py +++ b/src/View/BoundaryCondition/translate.py @@ -20,7 +20,8 @@ from PyQt5.QtCore import QCoreApplication from Model.BoundaryCondition.BoundaryConditionTypes import ( NotDefined, PonctualContribution, - TimeOverZ, TimeOverDischarge, ZOverDischarge + TimeOverZ, TimeOverDischarge, ZOverDischarge, + Solid ) _translate = QCoreApplication.translate @@ -31,6 +32,7 @@ long_types = { "TZ": _translate("BoundaryCondition", "Time over Z"), "TD": _translate("BoundaryCondition", "Time over Discharge"), "ZD": _translate("BoundaryCondition", "Z over Discharge"), + "SL": _translate("BoundaryCondition", "Solid"), } table_headers = { @@ -44,5 +46,6 @@ BC_types = { "PC": PonctualContribution, "TZ": TimeOverZ, "TD": TimeOverDischarge, - "ZD": ZOverDischarge + "ZD": ZOverDischarge, + "SL": Solid, } diff --git a/src/View/Frictions/Window.py b/src/View/Frictions/Window.py index 81049593c6d8d2c86f6fa80fc84d364ad109ab34..4af3b66c11643f034912b66fbce44bfba56b181b 100644 --- a/src/View/Frictions/Window.py +++ b/src/View/Frictions/Window.py @@ -49,7 +49,7 @@ from View.Frictions.Table import ( ) from View.Plot.MplCanvas import MplCanvas -from View.Geometry.PlotKPC import PlotKPC +from View.Geometry.PlotKPZ import PlotKPZ from View.Frictions.PlotStricklers import PlotStricklers from View.Frictions.translate import * @@ -128,7 +128,7 @@ class FrictionsWindow(ASubMainWindow, ListedSubWindow): self.plot_layout = self.find(QVBoxLayout, "verticalLayout") self.plot_layout.addWidget(self.canvas) - self.plot = PlotKPC( + self.plot = PlotKPZ( canvas = self.canvas, data = self._reach.reach, toolbar = None, @@ -193,7 +193,7 @@ class FrictionsWindow(ASubMainWindow, ListedSubWindow): sec = self._frictions.get(rows[0]) highlight = (sec.begin_kp, sec.end_kp) - self.plot = PlotKPC( + self.plot = PlotKPZ( canvas = self.canvas, data = reach, toolbar = None, diff --git a/src/View/Geometry/PlotKPC.py b/src/View/Geometry/PlotKPZ.py similarity index 97% rename from src/View/Geometry/PlotKPC.py rename to src/View/Geometry/PlotKPZ.py index 7fb31dae60971b3e108413e3aa2bb5fabfc75378..eb7d5e28b8660bf62ef11874522ad3c646290784 100644 --- a/src/View/Geometry/PlotKPC.py +++ b/src/View/Geometry/PlotKPZ.py @@ -29,10 +29,10 @@ _translate = QCoreApplication.translate logger = logging.getLogger() -class PlotKPC(APlot): +class PlotKPZ(APlot): def __init__(self, canvas=None, data=None, toolbar=None, display_current=True): - super(PlotKPC, self).__init__( + super(PlotKPZ, self).__init__( canvas=canvas, data=data, toolbar=toolbar @@ -64,7 +64,7 @@ class PlotKPC(APlot): color='green', fontsize=12 ) self.canvas.axes.set_ylabel( - _translate("MainWindow_reach", "Cote (m)"), + _translate("MainWindow_reach", "Height (m)"), color='green', fontsize=12 ) @@ -234,7 +234,7 @@ class PlotKPC(APlot): kp, z_complete[i] ) except Exception as e: - logger.warning(f"Failed to update graphic KPC: {e}") + logger.warning(f"Failed to update graphic KPZ: {e}") self.canvas.axes.autoscale_view(True, True, True) self.canvas.figure.canvas.draw_idle() diff --git a/src/View/Geometry/Window.py b/src/View/Geometry/Window.py index e33b6d5404a567346f0dcb2cf6734dd97f2501b2..7776d737389c564c89b4bb752cb0dfc600ddc7b8 100644 --- a/src/View/Geometry/Window.py +++ b/src/View/Geometry/Window.py @@ -38,7 +38,7 @@ from PyQt5.QtWidgets import ( ) from View.Geometry.PlotXY import PlotXY -from View.Geometry.PlotKPC import PlotKPC +from View.Geometry.PlotKPZ import PlotKPZ from View.Geometry.PlotAC import PlotAC from View.ASubWindow import ASubMainWindow, WindowToolKit @@ -230,7 +230,7 @@ class GeometryWindow(ASubMainWindow, ListedSubWindow): def plot_kpc(self): self.tableView.model().blockSignals(True) - self._plot_kpc = PlotKPC( + self._plot_kpc = PlotKPZ( canvas = self.ui.canvas_2, data = self._reach, toolbar = self.ui.toolbar_2 diff --git a/src/View/MainWindow.py b/src/View/MainWindow.py index 559601c26cd8a9979504b06581f8658b188012f6..2fc14efab43d6c6622282b8a28f585e1619272aa 100644 --- a/src/View/MainWindow.py +++ b/src/View/MainWindow.py @@ -18,6 +18,7 @@ import os import logging +import subprocess from queue import Queue from PyQt5 import QtGui @@ -49,6 +50,8 @@ from View.LateralContribution.Window import LateralContributionWindow from View.InitialConditions.Window import InitialConditionsWindow from View.Stricklers.Window import StricklersWindow from View.Frictions.Window import FrictionsWindow +from View.SedimentLayers.Window import SedimentLayersWindow +from View.SedimentLayers.Reach.Window import ReachSedimentLayersWindow from View.SolverParameters.Window import SolverParametersWindow from View.RunSolver.Window import SelectSolverWindow, SolverLogWindow from View.CheckList.Window import CheckListWindow @@ -87,7 +90,8 @@ define_model_action = [ "action_menu_edit_network", "action_menu_edit_geometry", "action_menu_boundary_conditions", "action_menu_initial_conditions", "action_menu_edit_friction", "action_menu_edit_lateral_contribution", - "action_menu_run_solver", + "action_menu_run_solver", "action_menu_sediment_layers", + "action_menu_edit_reach_sediment_layers" ] action = ( @@ -170,6 +174,8 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): "action_menu_edit_friction": self.open_frictions, "action_menu_edit_lateral_contribution": self.open_lateral_contrib, "action_menu_run_solver": self.run_solver, + "action_menu_sediment_layers": self.open_sediment_layers, + "action_menu_edit_reach_sediment_layers": self.open_reach_sediment_layers, ## Help "action_menu_about": self.open_about, # ToolBar action @@ -258,15 +264,22 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): self.debug_action.setStatusTip("Debug") self.debug_action.triggered.connect(self.open_debug) + self.debug_sqlite_action = QAction("Debug SQLite", self) + self.debug_sqlite_action.setStatusTip("Open SQLite debuging tool (sqlitebrowser)") + self.debug_sqlite_action.triggered.connect(self.open_sqlite) + if self.conf.debug: menu.addAction(self.debug_action) + menu.addAction(self.debug_sqlite_action) self.set_debug_lvl(debug = True) else: if self.conf.debug: menu.addAction(self.debug_action) + menu.addAction(self.debug_sqlite_action) self.set_debug_lvl(debug = True) else: menu.removeAction(self.debug_action) + menu.removeAction(self.debug_sqlite_action) self.set_debug_lvl(debug = False) ######### @@ -617,6 +630,20 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): else: params.activateWindow() + def open_sediment_layers(self): + sl = SedimentLayersWindow( + study = self.model, + parent = self + ) + sl.show() + + def open_reach_sediment_layers(self): + sl = ReachSedimentLayersWindow( + study = self.model, + parent = self + ) + sl.show() + def run_solver(self): if self.model is None: return @@ -683,6 +710,17 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit): ) repl.show() + def open_sqlite(self): + if self.model is None: + logger.debug("No study open for sql debuging...") + return + + file = self.model.filename + _ = subprocess.Popen( + f"sqlitebrowser {file}", + shell=True + ) + # TODO: Delete me ! ############### # DUMMY STUFF # diff --git a/src/View/Results/PlotH.py b/src/View/Results/PlotH.py index dcc9f7f6c3fdd6e2c11943e004e17e6ec38c1dd8..47c3f1bf48d89a56e3bb69d83ead4332e19bc905 100644 --- a/src/View/Results/PlotH.py +++ b/src/View/Results/PlotH.py @@ -19,6 +19,7 @@ import logging from functools import reduce +from datetime import datetime from tools import timer, trace from View.Plot.APlot import APlot @@ -34,14 +35,14 @@ logger = logging.getLogger() class PlotH(APlot): def __init__(self, canvas=None, results=None, reach_id=0, profile_id=0, - toolbar=None, display_current=True): + toolbar=None): super(PlotH, self).__init__( canvas=canvas, data=results, toolbar=toolbar ) - self.display_current = display_current + self._mode = "time" self._current_timestamp = max(results.get("timestamps")) self._current_reach_id = reach_id @@ -99,6 +100,10 @@ class PlotH(APlot): ) return + self.canvas.axes.set_ylim( + [min(min(y),0), max(y)] + ) + self._line = [ self.canvas.axes.plot( x, y, lw=1., @@ -107,8 +112,47 @@ class PlotH(APlot): ) ] - self.canvas.axes.autoscale_view(True, True, True) - self.canvas.axes.autoscale() + # Custom time display + nb = len(x) + mod = int(nb / 5) + mod = mod if mod > 0 else nb + + fx = list( + map( + lambda x: x[1], + filter( + lambda x: x[0] % mod == 0, + enumerate(x) + ) + ) + ) + + if self._mode == "time": + t0 = datetime.fromtimestamp(0) + xt = list( + map( + lambda v: ( + str( + datetime.fromtimestamp(v) - t0 + ).split(",")[0]\ + .replace("days", _translate("Results", "days"))\ + .replace("day", _translate("Results", "day")) + ), + fx + ) + ) + else: + xt = list( + map( + lambda v: str(datetime.fromtimestamp(v).date()), + fx + ) + ) + + self.canvas.axes.set_xticks(ticks=fx, labels=xt, rotation=45) + + # self.canvas.axes.autoscale_view(True, True, True) + # self.canvas.axes.autoscale() self.canvas.figure.tight_layout() self.canvas.figure.canvas.draw_idle() if self.toolbar is not None: diff --git a/src/View/Results/PlotSedProfile.py b/src/View/Results/PlotSedProfile.py new file mode 100644 index 0000000000000000000000000000000000000000..5b34bb1e2e87fa5eddf0e59b5e12eec9ddd238af --- /dev/null +++ b/src/View/Results/PlotSedProfile.py @@ -0,0 +1,130 @@ +# -*- coding: utf-8 -*- + +import logging + +from functools import reduce + +from tools import timer +from View.Plot.APlot import APlot + +from PyQt5.QtCore import ( + QCoreApplication +) + +_translate = QCoreApplication.translate + +logger = logging.getLogger() + +class PlotSedProfile(APlot): + def __init__(self, canvas=None, study=None, results=None, + reach_id=0, profile_id=0, + toolbar=None): + super(PlotSedProfile, self).__init__( + canvas=canvas, + data=study, + toolbar=toolbar + ) + + self._results = results + + self._current_timestamp = max(results.get("timestamps")) + self._current_reach_id = reach_id + self._current_profile_id = profile_id + + @timer + def draw(self): + self.canvas.axes.cla() + self.canvas.axes.grid(color='grey', linestyle='--', linewidth=0.5) + + if self.data is None: + return + + reach = self._results.river.reach(self._current_reach_id) + profile = reach.profile(self._current_profile_id) + if profile.geometry.number_points == 0: + return + + self.canvas.axes.set_xlabel( + _translate("MainWindow_reach", "X (m)"), + color='green', fontsize=12 + ) + self.canvas.axes.set_ylabel( + _translate("MainWindow_reach", "Height (m)"), + color='green', fontsize=12 + ) + + x = profile.geometry.get_station() + z = profile.geometry.z() + + psl = list( + map( + lambda sl: sl[0], + profile.get_ts_key(self._current_timestamp, "sl") + ) + ) + + sl = [] + for i in range(len(psl)): + cur = [] + for p in range(profile.geometry.number_points): + cur.append(psl[i]) + sl.append(cur) + + # logger.info(sl) + + self.canvas.axes.set_xlim( + left = min(x), right = max(x) + ) + + # Compute sediment layer in function to point z + z_sl = reduce( + lambda acc, v: acc + [ + list( + map(lambda x, y: y - x, v, acc[-1]) + ) + ], + sl, + [z] + ) + + self.line_kp_sl = [] + for i, zsl in enumerate(reversed(z_sl)): + self.line_kp_sl.append(None) + self.line_kp_sl[i], = self.canvas.axes.plot( + x, zsl, + linestyle="solid" if i == len(z_sl) - 1 else "--", + lw=1.8, + color='grey' if i == len(z_sl) - 1 else None + ) + + self.canvas.figure.tight_layout() + self.canvas.figure.canvas.draw_idle() + if self.toolbar is not None: + self.toolbar.update() + + self._init = False + + @timer + def update(self, ind=None): + if self._init == False: + self.draw() + return + + if ind is None: + logger.info("TODO: Update") + + self.canvas.axes.autoscale_view(True, True, True) + self.canvas.figure.canvas.draw_idle() + + def set_reach(self, reach_id): + self._current_reach_id = reach_id + self._current_profile_id = 0 + self.draw() + + def set_profile(self, profile_id): + self._current_profile_id = profile_id + self.draw() + + def set_timestamp(self, timestamp): + self._current_timestamp = timestamp + self.draw() diff --git a/src/View/Results/PlotSedReach.py b/src/View/Results/PlotSedReach.py new file mode 100644 index 0000000000000000000000000000000000000000..892120c60c96a742be65ec0f1edd52179ac881bd --- /dev/null +++ b/src/View/Results/PlotSedReach.py @@ -0,0 +1,134 @@ +# -*- coding: utf-8 -*- + +import logging + +from functools import reduce + +from tools import timer +from View.Plot.APlot import APlot + +from PyQt5.QtCore import ( + QCoreApplication +) + +_translate = QCoreApplication.translate + +logger = logging.getLogger() + +class PlotSedReach(APlot): + def __init__(self, canvas=None, study=None, results=None, + reach_id=0, profile_id=0, + toolbar=None): + super(PlotSedReach, self).__init__( + canvas=canvas, + data=study, + toolbar=toolbar + ) + + self._results = results + + self._current_timestamp = max(results.get("timestamps")) + self._current_reach_id = reach_id + self._current_profile_id = profile_id + + @timer + def draw(self): + self.canvas.axes.cla() + self.canvas.axes.grid(color='grey', linestyle='--', linewidth=0.5) + + if self.data is None: + return + + reach = self._results.river.reach(self._current_reach_id) + if reach.geometry.number_profiles == 0: + return + + self.canvas.axes.set_xlabel( + _translate("MainWindow_reach", "Kp (m)"), + color='green', fontsize=12 + ) + self.canvas.axes.set_ylabel( + _translate("MainWindow_reach", "Height (m)"), + color='green', fontsize=12 + ) + + kp = reach.geometry.get_kp() + z_min = reach.geometry.get_z_min() + z_max = reach.geometry.get_z_max() + + psl = list( + map( + # Get SL list for profile p + lambda p: p.get_ts_key(self._current_timestamp, "sl"), + reach.profiles + ) + ) + + max_sl = reduce( + lambda acc, sl: max(acc, len(sl)), + psl, + 0 + ) + + sl = [] + for i in range(max_sl): + cur = [] + for csl in psl: + if i < len(csl): + cur.append(csl[i][0]) + else: + cur.append(0) + sl.append(cur) + + # logger.info(f"sl = {sl}") + + self.canvas.axes.set_xlim( + left = min(kp), right = max(kp) + ) + + # Compute sediment layer in function to profile z_min + z_sl = reduce( + lambda acc, v: acc + [ + list( + map(lambda x, y: y - x, v, acc[-1]) + ) + ], + sl, + [z_min] + ) + + self.line_kp_sl = [] + for i, z in enumerate(reversed(z_sl)): + self.line_kp_sl.append(None) + self.line_kp_sl[i], = self.canvas.axes.plot( + kp, z, + linestyle="solid" if i == len(z_sl) - 1 else "--", + lw=1.8, + color='grey' if i == len(z_sl) - 1 else None + ) + + self.canvas.figure.tight_layout() + self.canvas.figure.canvas.draw_idle() + if self.toolbar is not None: + self.toolbar.update() + + self._init = False + + @timer + def update(self, ind=None): + if self._init == False: + self.draw() + return + + def set_reach(self, reach_id): + self._current_reach_id = reach_id + self._current_profile_id = 0 + self.draw() + + def set_profile(self, profile_id): + self._current_profile_id = profile_id + self.draw() + + def set_timestamp(self, timestamp): + self._current_timestamp = timestamp + self.draw() diff --git a/src/View/Results/Window.py b/src/View/Results/Window.py index 22d0a2c44e4158ebcf92664774483a6f2b88b461..3882a7ba6277dae7028fd1dd592714431b8b9f5e 100644 --- a/src/View/Results/Window.py +++ b/src/View/Results/Window.py @@ -46,6 +46,8 @@ from View.Results.PlotXY import PlotXY from View.Results.PlotAC import PlotAC from View.Results.PlotKPC import PlotKPC from View.Results.PlotH import PlotH +from View.Results.PlotSedReach import PlotSedReach +from View.Results.PlotSedProfile import PlotSedProfile from View.Results.Table import TableModel from View.Results.translate import * @@ -180,6 +182,38 @@ class ResultsWindow(ASubMainWindow, ListedSubWindow): ) self.plot_h.draw() + self.canvas_5 = MplCanvas(width=5, height=4, dpi=100) + self.canvas_5.setObjectName("canvas_5") + self.plot_layout_5 = self.find(QVBoxLayout, "verticalLayout_sed_reach") + self.plot_layout_5.addWidget(self.canvas_5) + + if self._study.river.has_sediment(): + self.plot_sed_reach = PlotSedReach( + canvas = self.canvas_5, + results = self._results, + study = self._study, + reach_id = 0, + profile_id = 0, + toolbar = None + ) + self.plot_sed_reach.draw() + + self.canvas_6 = MplCanvas(width=5, height=4, dpi=100) + self.canvas_6.setObjectName("canvas_6") + self.plot_layout_6 = self.find(QVBoxLayout, "verticalLayout_sed_profile") + self.plot_layout_6.addWidget(self.canvas_6) + + if self._study.river.has_sediment(): + self.plot_sed_profile = PlotSedProfile( + canvas = self.canvas_6, + results = self._results, + study = self._study, + reach_id = 0, + profile_id = 0, + toolbar = None + ) + self.plot_sed_profile.draw() + def setup_connections(self): self.undo_sc.activated.connect(self.undo) self.redo_sc.activated.connect(self.redo) @@ -210,22 +244,38 @@ class ResultsWindow(ASubMainWindow, ListedSubWindow): self.plot_ac.set_reach(reach_id) self.plot_kpc.set_reach(reach_id) self.plot_h.set_reach(reach_id) + + if self._study.river.has_sediment(): + self.plot_sed_reach.set_reach(reach_id) + self.plot_sed_profile.set_reach(reach_id) if profile_id is not None: self.plot_xy.set_profile(profile_id) self.plot_ac.set_profile(profile_id) self.plot_kpc.set_profile(profile_id) self.plot_h.set_profile(profile_id) + + if self._study.river.has_sediment(): + self.plot_sed_reach.set_profile(profile_id) + self.plot_sed_profile.set_profile(profile_id) if timestamp is not None: self.plot_xy.set_timestamp(timestamp) self.plot_ac.set_timestamp(timestamp) self.plot_kpc.set_timestamp(timestamp) self.plot_h.set_timestamp(timestamp) + if self._study.river.has_sediment(): + self.plot_sed_reach.set_timestamp(timestamp) + self.plot_sed_profile.set_timestamp(timestamp) + self.plot_xy.draw() self.plot_ac.draw() self.plot_kpc.draw() self.plot_h.draw() + if self._study.river.has_sediment(): + self.plot_sed_reach.draw() + self.plot_sed_profile.draw() + def _set_current_reach(self): table = self.find(QTableView, f"tableView_reach") diff --git a/src/View/RunSolver/Window.py b/src/View/RunSolver/Window.py index 872d72ef008dd30db05c20c7ae481c1ddf575f19..c307157af191b3b1d89ad1034098bd1e4b084f52 100644 --- a/src/View/RunSolver/Window.py +++ b/src/View/RunSolver/Window.py @@ -147,6 +147,7 @@ class SolverLogWindow(ASubMainWindow, ListedSubWindow): self._log(f" *** Run solver {self._solver.name}", color="blue") self._solver.run( + study, process = self._process, output_queue = self._output ) @@ -220,7 +221,7 @@ class SolverLogWindow(ASubMainWindow, ListedSubWindow): self._log(" *** Start", color="blue") self._results = None - self._solver.start(self._process) + self._solver.start(self._study, process = self._process) self.find(QAction, "action_start").setEnabled(False) if _signal: diff --git a/src/View/SedimentLayers/Edit/Plot.py b/src/View/SedimentLayers/Edit/Plot.py new file mode 100644 index 0000000000000000000000000000000000000000..e48ec5eb61ea21c32b76cb179754d97c278df00a --- /dev/null +++ b/src/View/SedimentLayers/Edit/Plot.py @@ -0,0 +1,105 @@ +# -*- coding: utf-8 -*- + +import logging + +from functools import reduce + +from tools import timer +from View.Plot.APlot import APlot + +from PyQt5.QtCore import ( + QCoreApplication +) + +_translate = QCoreApplication.translate + +logger = logging.getLogger() + +class Plot(APlot): + def __init__(self, canvas=None, data=None, toolbar=None, + display_current=True): + super(Plot, self).__init__( + canvas=canvas, + data=data, + toolbar=toolbar + ) + + self._display_current = display_current + + self.line_kp_zmin = None + self.line_kp_sl = [] + + @timer + def draw(self): + self.canvas.axes.cla() + self.canvas.axes.grid(color='grey', linestyle='--', linewidth=0.5) + + # self.canvas.axes.set_xlabel( + # _translate("MainWindow_reach", "X (m)"), + # color='green', fontsize=12 + # ) + self.canvas.axes.axes.get_xaxis().set_visible(False) + self.canvas.axes.set_ylabel( + _translate("MainWindow_reach", "Height (m)"), + color='green', fontsize=12 + ) + + if self.data is None: + return + + x = [0,1] + z = [0,0] + sl = self.data.height() + names = ["bottom"] + self.data.names() + + self.canvas.axes.set_xlim( + left = min(x), right = max(x) + ) + + lsl = [] + for i in range(len(sl)): + cur = [] + for _ in x: + cur.append(sl[i]) + lsl.append(cur) + + # Compute sediment layer in function to point z + z_sl = reduce( + lambda acc, v: acc + [ + list( + map(lambda x, y: y - x, v, acc[-1]) + ) + ], + lsl, + [z] + ) + + for i, zsl in enumerate(reversed(z_sl)): + self.line_kp_sl.append(None) + self.line_kp_sl[i], = self.canvas.axes.plot( + x, zsl, + label=names[-(i+1)], + linestyle="solid" if i == len(names) - 1 else "--", + lw=1.8, + color='grey' if i == len(names) - 1 else None + ) + self.canvas.axes.text(x[0] + 0.01, zsl[0] + 0.01, f'{names[-(i+1)]}') + + self.canvas.figure.tight_layout() + self.canvas.figure.canvas.draw_idle() + if self.toolbar is not None: + self.toolbar.update() + + self._init = True + + @timer + def update(self, ind=None): + if self._init == False: + self.draw() + return + + if ind is None: + logger.info("TODO: Update") + + self.canvas.axes.autoscale_view(True, True, True) + self.canvas.figure.canvas.draw_idle() diff --git a/src/View/SedimentLayers/Edit/Table.py b/src/View/SedimentLayers/Edit/Table.py new file mode 100644 index 0000000000000000000000000000000000000000..e1a41cb2970ad921f47f5da7f29544f452718213 --- /dev/null +++ b/src/View/SedimentLayers/Edit/Table.py @@ -0,0 +1,183 @@ +# -*- coding: utf-8 -*- + +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.SedimentLayers.Edit.UndoCommand import * +from View.SedimentLayers.Edit.translate import * + +_translate = QCoreApplication.translate + + +class TableModel(QAbstractTableModel): + def __init__(self, study=None, sl=None, undo=None): + super(QAbstractTableModel, self).__init__() + self._headers = list(table_headers.keys()) + self._study = study + self._undo = undo + self._sl = sl + + def flags(self, index): + options = Qt.ItemIsEnabled | Qt.ItemIsSelectable + options |= Qt.ItemIsEditable + + return options + + def rowCount(self, parent): + return len(self._sl) + + def columnCount(self, parent): + return len(self._headers) + + 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._sl.get(row).name + elif self._headers[column] == "type": + return self._sl.get(row).type + elif self._headers[column] == "height": + return self._sl.get(row).height + elif self._headers[column] == "d50": + return self._sl.get(row).d50 + elif self._headers[column] == "sigma": + return self._sl.get(row).sigma + elif self._headers[column] == "critical_constraint": + return self._sl.get(row).critical_constraint + + return QVariant() + + def headerData(self, friction, orientation, role): + if role == Qt.ItemDataRole.DisplayRole and orientation == Qt.Orientation.Horizontal: + return table_headers[self._headers[friction]] + + 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() + + if self._headers[column] == "name": + self._undo.push( + SetNameCommand( + self._sl, row, value + ) + ) + if self._headers[column] == "type": + self._undo.push( + SetTypeCommand( + self._sl, row, value + ) + ) + if self._headers[column] == "height": + self._undo.push( + SetHeightCommand( + self._sl, row, value + ) + ) + if self._headers[column] == "d50": + self._undo.push( + SetD50Command( + self._sl, row, value + ) + ) + if self._headers[column] == "sigma": + self._undo.push( + SetSigmaCommand( + self._sl, row, value + ) + ) + if self._headers[column] == "critical_constraint": + self._undo.push( + SetCriticalConstraintCommand( + self._sl, row, value + ) + ) + + self.dataChanged.emit(index, index) + return True + + def add(self, row, parent=QModelIndex()): + self.beginInsertRows(parent, row, row - 1) + + self._undo.push( + AddCommand( + self._sl, row + ) + ) + + self.endInsertRows() + self.layoutChanged.emit() + + def delete(self, rows, parent=QModelIndex()): + self.beginRemoveRows(parent, rows[0], rows[-1]) + + self._undo.push( + DelCommand( + self._sl, rows + ) + ) + + self.endRemoveRows() + self.layoutChanged.emit() + + def move_up(self, row, parent=QModelIndex()): + if row <= 0: + return + + target = row + 2 + + self.beginMoveRows(parent, row - 1, row - 1, parent, target) + + self._undo.push( + MoveCommand( + self._sl, "up", row + ) + ) + + self.endMoveRows() + self.layoutChanged.emit() + + def move_down(self, row, parent=QModelIndex()): + if row + 1 >= len(self._sl): + return + + target = row + + self.beginMoveRows(parent, row + 1, row + 1, parent, target) + + self._undo.push( + MoveCommand( + self._sl, "down", row + ) + ) + + self.endMoveRows() + self.layoutChanged.emit() + + def undo(self): + self._undo.undo() + self.layoutChanged.emit() + + def redo(self): + self._undo.redo() + self.layoutChanged.emit() diff --git a/src/View/SedimentLayers/Edit/UndoCommand.py b/src/View/SedimentLayers/Edit/UndoCommand.py new file mode 100644 index 0000000000000000000000000000000000000000..298fa529708b659785d7ac56b0ede3cc98bb80d3 --- /dev/null +++ b/src/View/SedimentLayers/Edit/UndoCommand.py @@ -0,0 +1,157 @@ +# -*- coding: utf-8 -*- + +from copy import deepcopy +from tools import trace, timer + +from PyQt5.QtWidgets import ( + QMessageBox, QUndoCommand, QUndoStack, +) + +from Model.SedimentLayer.SedimentLayer import SedimentLayer +from Model.SedimentLayer.SedimentLayerList import SedimentLayerList + +class SetNameCommand(QUndoCommand): + def __init__(self, sediment_layers, index, new_value): + QUndoCommand.__init__(self) + + self._sediment_layers = sediment_layers + self._index = index + self._old = self._sediment_layers.get(self._index).name + self._new = new_value + + def undo(self): + self._sediment_layers.get(self._index).name = self._old + + def redo(self): + self._sediment_layers.get(self._index).name = self._new + +class SetTypeCommand(QUndoCommand): + def __init__(self, sediment_layers, index, new_value): + QUndoCommand.__init__(self) + + self._sediment_layers = sediment_layers + self._index = index + self._old = self._sediment_layers.get(self._index).type + self._new = new_value + + def undo(self): + self._sediment_layers.get(self._index).type = self._old + + def redo(self): + self._sediment_layers.get(self._index).type = self._new + +class SetHeightCommand(QUndoCommand): + def __init__(self, sediment_layers, index, new_value): + QUndoCommand.__init__(self) + + self._sediment_layers = sediment_layers + self._index = index + self._old = self._sediment_layers.get(self._index).height + self._new = new_value + + def undo(self): + self._sediment_layers.get(self._index).height = self._old + + def redo(self): + self._sediment_layers.get(self._index).height = self._new + +class SetD50Command(QUndoCommand): + def __init__(self, sediment_layers, index, new_value): + QUndoCommand.__init__(self) + + self._sediment_layers = sediment_layers + self._index = index + self._old = self._sediment_layers.get(self._index).d50 + self._new = new_value + + def undo(self): + self._sediment_layers.get(self._index).d50 = self._old + + def redo(self): + self._sediment_layers.get(self._index).d50 = self._new + +class SetSigmaCommand(QUndoCommand): + def __init__(self, sediment_layers, index, new_value): + QUndoCommand.__init__(self) + + self._sediment_layers = sediment_layers + self._index = index + self._old = self._sediment_layers.get(self._index).sigma + self._new = new_value + + def undo(self): + self._sediment_layers.get(self._index).sigma = self._old + + def redo(self): + self._sediment_layers.get(self._index).sigma = self._new + +class SetCriticalConstraintCommand(QUndoCommand): + def __init__(self, sediment_layers, index, new_value): + QUndoCommand.__init__(self) + + self._sediment_layers = sediment_layers + self._index = index + self._old = self._sediment_layers.get(self._index).critical_constraint + self._new = new_value + + def undo(self): + self._sediment_layers.get(self._index).critical_constraint = self._old + + def redo(self): + self._sediment_layers.get(self._index).critical_constraint = self._new + +class AddCommand(QUndoCommand): + def __init__(self, sediment_layers, index): + QUndoCommand.__init__(self) + + self._sediment_layers = sediment_layers + self._index = index + self._new = None + + def undo(self): + self._sediment_layers.delete_i([self._index]) + + def redo(self): + if self._new is None: + self._new = self._sediment_layers.new(self._index) + else: + self._sediment_layers.insert(self._index, self._new) + +class DelCommand(QUndoCommand): + def __init__(self, sediment_layers, rows): + QUndoCommand.__init__(self) + + self._sediment_layers = sediment_layers + self._rows = rows + + self._sl_pos = [] + for row in rows: + self._sl_pos.append((row, self._sediment_layers.get(row))) + self._sl_pos.sort() + + def undo(self): + for row, el in self._sl_pos: + self._sediment_layers.insert(row, el) + + def redo(self): + self._sediment_layers.delete_i(self._rows) + +class MoveCommand(QUndoCommand): + def __init__(self, sediment_layers, up, i): + QUndoCommand.__init__(self) + + self._sediment_layers = sediment_layers + self._up = up == "up" + self._i = i + + def undo(self): + if self._up: + self._sediment_layers.move_up(self._i) + else: + self._sediment_layers.move_down(self._i) + + def redo(self): + if self._up: + self._sediment_layers.move_up(self._i) + else: + self._sediment_layers.move_down(self._i) diff --git a/src/View/SedimentLayers/Edit/Window.py b/src/View/SedimentLayers/Edit/Window.py new file mode 100644 index 0000000000000000000000000000000000000000..965895d24c176f76d42a78b02041560b85ab0681 --- /dev/null +++ b/src/View/SedimentLayers/Edit/Window.py @@ -0,0 +1,170 @@ +# -*- coding: utf-8 -*- + +import logging + +from tools import trace, timer + +from View.ASubWindow import ASubMainWindow +from View.ListedSubWindow import ListedSubWindow + +from PyQt5.QtGui import ( + QKeySequence, +) + +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, QVBoxLayout, QHeaderView, QTabWidget, +) + +from View.Plot.MplCanvas import MplCanvas + +from View.SedimentLayers.Edit.UndoCommand import * +from View.SedimentLayers.Edit.Table import * +from View.SedimentLayers.Edit.Plot import Plot +from View.SedimentLayers.Edit.translate import * + +_translate = QCoreApplication.translate + +logger = logging.getLogger() + +class EditSedimentLayersWindow(ASubMainWindow, ListedSubWindow): + def __init__(self, title="Edit Sediment Layers", + study=None, sl=None, parent=None): + self._study = study + self._sl = sl + + self.setup_title(title) + + super(EditSedimentLayersWindow, self).__init__( + name=self._title, ui="EditSedimentLayers", parent=parent + ) + + self.setup_sc() + self.setup_table() + self.setup_graph() + self.setup_connections() + + self.ui.setWindowTitle(self._title) + + def setup_title(self, title): + name = self._sl.name + if name == "": + name = _translate("SedimentLayers", "(no name)") + + self._title = ( + title + " - " + self._study.name + " - " + name + ) + + def setup_sc(self): + self._undo_stack = QUndoStack() + + self.undo_sc = QShortcut(QKeySequence.Undo, self) + self.redo_sc = QShortcut(QKeySequence.Redo, self) + self.copy_sc = QShortcut(QKeySequence.Copy, self) + self.paste_sc = QShortcut(QKeySequence.Paste, self) + + def setup_table(self): + self._table = {} + + table = self.find(QTableView, f"tableView") + self._table = TableModel( + study = self._study, + sl = self._sl, + undo = self._undo_stack, + ) + table.setModel(self._table) + + table.setSelectionBehavior(QAbstractItemView.SelectRows) + table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) + table.setAlternatingRowColors(True) + + def setup_graph(self): + self.canvas = MplCanvas(width=5, height=4, dpi=100) + self.canvas.setObjectName("canvas") + self.plot_layout = self.find(QVBoxLayout, "verticalLayout") + self.plot_layout.addWidget(self.canvas) + self._set_plot() + + def _set_plot(self): + self.plot = Plot( + canvas = self.canvas, + data = self._sl, + toolbar = None, + display_current = False + ) + self.plot.draw() + + + def setup_connections(self): + self.find(QAction, "action_add").triggered.connect(self.add) + self.find(QAction, "action_del").triggered.connect(self.delete) + self.find(QAction, "action_move_up").triggered.connect(self.move_up) + self.find(QAction, "action_move_down").triggered.connect(self.move_down) + + self.undo_sc.activated.connect(self.undo) + self.redo_sc.activated.connect(self.redo) + self.copy_sc.activated.connect(self.copy) + self.paste_sc.activated.connect(self.paste) + + self._table.dataChanged.connect(self._set_plot) + self._table.layoutChanged.connect(self._set_plot) + + def index_selected_rows(self): + table = self.find(QTableView, f"tableView") + return list( + # Delete duplicate + set( + map( + lambda i: i.row(), + table.selectedIndexes() + ) + ) + ) + + def add(self): + rows = self.index_selected_rows() + if len(self._sl) == 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 move_up(self): + rows = self.index_selected_rows() + if len(rows) == 0: + return + + self._table.move_up(rows[0]) + + def move_down(self): + rows = self.index_selected_rows() + if len(rows) == 0: + return + + self._table.move_down(rows[0]) + + 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() diff --git a/src/View/SedimentLayers/Edit/translate.py b/src/View/SedimentLayers/Edit/translate.py new file mode 100644 index 0000000000000000000000000000000000000000..fe9e44a923ad21bf50cb1051e6868f82c16678a5 --- /dev/null +++ b/src/View/SedimentLayers/Edit/translate.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- + +from PyQt5.QtCore import QCoreApplication + +_translate = QCoreApplication.translate + +table_headers = { + "name": _translate("SedimentLayers", "Name"), + "type": _translate("SedimentLayers", "Type"), + "height": _translate("Sedimentlayers", "Height"), + "d50": _translate("Sedimentlayers", "D50"), + "sigma": _translate("Sedimentlayers", "Sigma"), + "critical_constraint": _translate("Sedimentlayers", "Critical constraint"), +} diff --git a/src/View/SedimentLayers/Reach/Plot.py b/src/View/SedimentLayers/Reach/Plot.py new file mode 100644 index 0000000000000000000000000000000000000000..a312a8ceea6ffd9784cd8d56b83781f029250063 --- /dev/null +++ b/src/View/SedimentLayers/Reach/Plot.py @@ -0,0 +1,102 @@ +# -*- coding: utf-8 -*- + +import logging + +from functools import reduce + +from tools import timer +from View.Plot.APlot import APlot + +from PyQt5.QtCore import ( + QCoreApplication +) + +_translate = QCoreApplication.translate + +logger = logging.getLogger() + +class Plot(APlot): + def __init__(self, canvas=None, data=None, toolbar=None, + display_current=True): + super(Plot, self).__init__( + canvas=canvas, + data=data, + toolbar=toolbar + ) + + self._display_current = display_current + + self.line_kp_zmin = None + self.line_kp_sl = [] + + @timer + def draw(self): + self.canvas.axes.cla() + self.canvas.axes.grid(color='grey', linestyle='--', linewidth=0.5) + + if self.data is None: + return + + if self.data.number_profiles == 0: + return + + self.canvas.axes.set_xlabel( + _translate("MainWindow_reach", "Kp (m)"), + color='green', fontsize=12 + ) + self.canvas.axes.set_ylabel( + _translate("MainWindow_reach", "Height (m)"), + color='green', fontsize=12 + ) + + kp = self.data.get_kp() + sl = self.data.get_sl() + z_min = self.data.get_z_min() + z_max = self.data.get_z_max() + + self.canvas.axes.set_xlim( + left = min(kp), right = max(kp) + ) + + # Compute sediment layer in function to profile z_min + z_sl = reduce( + lambda acc, v: acc + [ + list( + map(lambda x, y: y - x, v, acc[-1]) + ) + ], + sl, + [z_min] + ) + + for i, z in enumerate(reversed(z_sl)): + self.line_kp_sl.append(None) + self.line_kp_sl[i], = self.canvas.axes.plot( + kp, z, + linestyle="solid" if i == len(z_sl) - 1 else "--", + lw=1.8, + color='grey' if i == len(z_sl) - 1 else None + ) + + self.canvas.figure.tight_layout() + self.canvas.figure.canvas.draw_idle() + if self.toolbar is not None: + self.toolbar.update() + + self._init = True + + @timer + def update(self, ind=None): + if self._init == False: + self.draw() + return + + if ind is None: + kp = self.data.get_kp() + z_min = self.data.get_z_min() + z_max = self.data.get_z_max() + + self.line_kp_zmin.set_data(kp, z_min) + + self.canvas.axes.autoscale_view(True, True, True) + self.canvas.figure.canvas.draw_idle() diff --git a/src/View/SedimentLayers/Reach/Profile/Plot.py b/src/View/SedimentLayers/Reach/Profile/Plot.py new file mode 100644 index 0000000000000000000000000000000000000000..50b4bba26c40ebaf4b10c04bbe82d9184407396d --- /dev/null +++ b/src/View/SedimentLayers/Reach/Profile/Plot.py @@ -0,0 +1,97 @@ +# -*- coding: utf-8 -*- + +import logging + +from functools import reduce + +from tools import timer +from View.Plot.APlot import APlot + +from PyQt5.QtCore import ( + QCoreApplication +) + +_translate = QCoreApplication.translate + +logger = logging.getLogger() + +class Plot(APlot): + def __init__(self, canvas=None, data=None, toolbar=None, + display_current=True): + super(Plot, self).__init__( + canvas=canvas, + data=data, + toolbar=toolbar + ) + + self._display_current = display_current + + self.line_kp_zmin = None + self.line_kp_sl = [] + + @timer + def draw(self): + self.canvas.axes.cla() + self.canvas.axes.grid(color='grey', linestyle='--', linewidth=0.5) + + if self.data is None: + return + + if self.data.number_points == 0: + return + + self.canvas.axes.set_xlabel( + _translate("MainWindow_reach", "X (m)"), + color='green', fontsize=12 + ) + self.canvas.axes.set_ylabel( + _translate("MainWindow_reach", "Height (m)"), + color='green', fontsize=12 + ) + + x = self.data.get_station() + z = self.data.z() + sl = self.data.get_sl() + + self.canvas.axes.set_xlim( + left = min(x), right = max(x) + ) + + # Compute sediment layer in function to point z + z_sl = reduce( + lambda acc, v: acc + [ + list( + map(lambda x, y: y - x, v, acc[-1]) + ) + ], + sl, + [z] + ) + + for i, zsl in enumerate(reversed(z_sl)): + self.line_kp_sl.append(None) + self.line_kp_sl[i], = self.canvas.axes.plot( + x, zsl, + linestyle="solid" if i == len(z_sl) - 1 else "--", + lw=1.8, + color='grey' if i == len(z_sl) - 1 else None + ) + + self.canvas.figure.tight_layout() + self.canvas.figure.canvas.draw_idle() + if self.toolbar is not None: + self.toolbar.update() + + self._init = True + + @timer + def update(self, ind=None): + if self._init == False: + self.draw() + return + + if ind is None: + logger.info("TODO: Update") + + self.canvas.axes.autoscale_view(True, True, True) + self.canvas.figure.canvas.draw_idle() diff --git a/src/View/SedimentLayers/Reach/Profile/Table.py b/src/View/SedimentLayers/Reach/Profile/Table.py new file mode 100644 index 0000000000000000000000000000000000000000..102bec3274033651495cb25ab06f78ff4517c3e8 --- /dev/null +++ b/src/View/SedimentLayers/Reach/Profile/Table.py @@ -0,0 +1,155 @@ +# -*- coding: utf-8 -*- + +import logging + +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.SedimentLayers.Reach.Profile.UndoCommand import * +from View.SedimentLayers.Reach.Profile.translate import * + +_translate = QCoreApplication.translate + +logger = logging.getLogger() + +class ComboBoxDelegate(QItemDelegate): + def __init__(self, study=None, parent=None): + super(ComboBoxDelegate, self).__init__(parent) + + self._study = study + + def createEditor(self, parent, option, index): + self.editor = QComboBox(parent) + + self.editor.addItems( + # [_translate("SedimentLayers", "Not defined")] + + list( + map( + lambda sl: str(sl), + self._study.river.sediment_layers.sediment_layers + ) + ) + ) + + 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 and 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(QAbstractTableModel): + def __init__(self, study=None, profile=None, undo=None): + super(QAbstractTableModel, self).__init__() + self._headers = list(table_headers.keys()) + self._study = study + self._undo = undo + self._profile = profile + + def flags(self, index): + column = index.column() + + options = Qt.ItemIsEnabled | Qt.ItemIsSelectable + if self._headers[column] == "sl": + options |= Qt.ItemIsEditable + + return options + + def rowCount(self, parent): + return self._profile.number_points + + def columnCount(self, parent): + return len(self._headers) + + 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._profile.point(row).name + elif self._headers[column] == "sl": + value = self._profile.point(row).sl + if value == None: + text = _translate("SedimentLayers", "Not defined") + return text + return str(value) + elif self._headers[column] == "x": + return self._profile.point(row).x + elif self._headers[column] == "y": + return self._profile.point(row).y + elif self._headers[column] == "z": + return self._profile.point(row).z + + return QVariant() + + def headerData(self, friction, orientation, role): + if role == Qt.ItemDataRole.DisplayRole and orientation == Qt.Orientation.Horizontal: + return table_headers[self._headers[friction]] + + 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() + + if self._headers[column] == "sl": + new = None + if value != _translate("SedimentLayers", "Not defined"): + new = next( + filter( + lambda sl: str(sl) == value, + self._study.river.sediment_layers.sediment_layers + ) + ) + + self._undo.push( + SetSLCommand( + self._profile, row, new + ) + ) + + self.dataChanged.emit(index, index) + return True + + def undo(self): + self._undo.undo() + self.layoutChanged.emit() + + def redo(self): + self._undo.redo() + self.layoutChanged.emit() diff --git a/src/View/SedimentLayers/Reach/Profile/UndoCommand.py b/src/View/SedimentLayers/Reach/Profile/UndoCommand.py new file mode 100644 index 0000000000000000000000000000000000000000..fe3497aede40ac048aa6ea1999e6da1566bce976 --- /dev/null +++ b/src/View/SedimentLayers/Reach/Profile/UndoCommand.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- + +import logging + +from copy import deepcopy +from tools import trace, timer + +from PyQt5.QtWidgets import ( + QMessageBox, QUndoCommand, QUndoStack, +) + +from Model.Geometry.Reach import Reach +from Model.Geometry.Profile import Profile +from Model.SedimentLayer.SedimentLayer import SedimentLayer +from Model.SedimentLayer.SedimentLayerList import SedimentLayerList + +logger = logging.getLogger() + +class SetSLCommand(QUndoCommand): + def __init__(self, profile, index, new_value): + QUndoCommand.__init__(self) + + self._profile = profile + self._index = index + self._old = self._profile.point(self._index).sl + self._new = new_value + + def undo(self): + self._profile.point(self._index).sl = self._old + + def redo(self): + self._profile.point(self._index).sl = self._new diff --git a/src/View/SedimentLayers/Reach/Profile/Window.py b/src/View/SedimentLayers/Reach/Profile/Window.py new file mode 100644 index 0000000000000000000000000000000000000000..d1dd5e691db9cb7579e4f43a2c011a22ecf018b2 --- /dev/null +++ b/src/View/SedimentLayers/Reach/Profile/Window.py @@ -0,0 +1,161 @@ +# -*- coding: utf-8 -*- + +import logging + +from tools import trace, timer + +from View.ASubWindow import ASubMainWindow +from View.ListedSubWindow import ListedSubWindow + +from PyQt5.QtGui import ( + QKeySequence, +) + +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, QVBoxLayout, QHeaderView, QTabWidget, +) + +from View.SedimentLayers.Reach.Profile.UndoCommand import * +from View.SedimentLayers.Reach.Profile.Table import * +from View.SedimentLayers.Reach.Profile.Plot import Plot + +from View.Plot.MplCanvas import MplCanvas +from View.SedimentLayers.Reach.Profile.translate import * + +from View.SedimentLayers.Window import SedimentLayersWindow + +_translate = QCoreApplication.translate + +logger = logging.getLogger() + +class ProfileSedimentLayersWindow(ASubMainWindow, ListedSubWindow): + def __init__(self, title="Profile sediment layers", study=None, profile=None, parent=None): + self._study = study + self._sediment_layers = self._study.river.sediment_layers + self._profile = profile + self._reach = self._study.river.current_reach().reach + + self.setup_title(title) + + super(ProfileSedimentLayersWindow, self).__init__( + name=self._title, ui="ProfileSedimentLayers", parent=parent + ) + + self.setup_sc() + self.setup_table() + self.setup_graph() + self.setup_connections() + + self.ui.setWindowTitle(self._title) + + def setup_title(self, title): + rname = self._reach.name + if rname == "": + rname = _translate("SedimentLayers", "(no name)") + + pname = self._profile.name + if pname == "": + pname = _translate( + "SedimentLayers", + "(no name - @kp)").replace("@kp", str(self._profile.kp) + ) + + self._title = ( + title + " - " + + self._study.name + " - " + + rname + " - " + pname + ) + + def setup_sc(self): + self._undo_stack = QUndoStack() + + self.undo_sc = QShortcut(QKeySequence.Undo, self) + self.redo_sc = QShortcut(QKeySequence.Redo, self) + self.copy_sc = QShortcut(QKeySequence.Copy, self) + self.paste_sc = QShortcut(QKeySequence.Paste, self) + + def setup_table(self): + table = self.find(QTableView, f"tableView") + self._table = TableModel( + study = self._study, + profile = self._profile, + undo = self._undo_stack, + ) + table.setModel(self._table) + + self._delegate_stricklers = ComboBoxDelegate( + study = self._study, + parent=self + ) + + table.setItemDelegateForColumn( + list(table_headers).index("sl"), + self._delegate_stricklers + ) + + table.setSelectionBehavior(QAbstractItemView.SelectRows) + table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) + table.setAlternatingRowColors(True) + + def setup_graph(self): + self.canvas = MplCanvas(width=5, height=4, dpi=100) + self.canvas.setObjectName("canvas") + self.plot_layout = self.find(QVBoxLayout, "verticalLayout") + self.plot_layout.addWidget(self.canvas) + + self._update_plot() + + def _update_plot(self): + self.plot = Plot( + canvas = self.canvas, + data = self._profile, + toolbar = None, + display_current = False + ) + self.plot.draw() + + + def setup_connections(self): + self.undo_sc.activated.connect(self.undo) + self.redo_sc.activated.connect(self.redo) + self.copy_sc.activated.connect(self.copy) + self.paste_sc.activated.connect(self.paste) + + self._table.layoutChanged\ + .connect(self._update_plot) + self._table.dataChanged\ + .connect(self._update_plot) + + + def index_selected_rows(self): + table = self.find(QTableView, f"tableView") + return list( + # Delete duplicate + set( + map( + lambda i: i.row(), + table.selectedIndexes() + ) + ) + ) + + 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() diff --git a/src/View/SedimentLayers/Reach/Profile/translate.py b/src/View/SedimentLayers/Reach/Profile/translate.py new file mode 100644 index 0000000000000000000000000000000000000000..33e204b33552e41cece53621407ab7f0fbe3774c --- /dev/null +++ b/src/View/SedimentLayers/Reach/Profile/translate.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- + +from PyQt5.QtCore import QCoreApplication + +_translate = QCoreApplication.translate + +table_headers = { + "x": _translate("SedimentLayers", "X (m)"), + "y": _translate("SedimentLayers", "Y (m)"), + "z": _translate("SedimentLazers", "Z (m)"), + "name": _translate("SedimentLayers", "Name"), + "sl": _translate("SedimentLayers", "Sediment layers"), +} diff --git a/src/View/SedimentLayers/Reach/SLDialog.py b/src/View/SedimentLayers/Reach/SLDialog.py new file mode 100644 index 0000000000000000000000000000000000000000..ce1e97942547b805c0bfb2d5c328ca87c66137b0 --- /dev/null +++ b/src/View/SedimentLayers/Reach/SLDialog.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- + +from View.ASubWindow import ASubWindow +from View.ListedSubWindow import ListedSubWindow + +from PyQt5.QtGui import ( + QKeySequence, +) + +from PyQt5.QtCore import ( + Qt, QVariant, QAbstractTableModel, +) + +from PyQt5.QtWidgets import ( + QDialogButtonBox, QComboBox, QUndoStack, QShortcut, + QDoubleSpinBox, +) + +from View.SedimentLayers.Reach.translate import * + +_translate = QCoreApplication.translate + +class SLDialog(ASubWindow, ListedSubWindow): + def __init__(self, title="SL", study=None, parent=None): + self._study = study + + super(SLDialog, self).__init__( + name=title, ui="SLDialog", parent=parent + ) + + self.setup_combobox() + + self.value = None + + def setup_combobox(self): + self.combobox_add_items( + "comboBox", + [_translate("SedimentLayers", "Not defined")] + + list( + map( + lambda sl: str(sl), + self._study.river.sediment_layers.sediment_layers + ) + ) + ) + + @property + def sl(self): + if self.value == _translate("SedimentLayers", "Not defined"): + return None + + return next( + filter( + lambda sl: str(sl) == self.value, + self._study.river.sediment_layers.sediment_layers + ) + ) + + def accept(self): + self.value = self.get_combobox_text("comboBox") + super().accept() diff --git a/src/View/SedimentLayers/Reach/Table.py b/src/View/SedimentLayers/Reach/Table.py new file mode 100644 index 0000000000000000000000000000000000000000..8fdb8d7e87099902ccab21d6799edf3a1f65e8ae --- /dev/null +++ b/src/View/SedimentLayers/Reach/Table.py @@ -0,0 +1,159 @@ +# -*- coding: utf-8 -*- + +import logging + +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.SedimentLayers.Reach.UndoCommand import * +from View.SedimentLayers.Reach.translate import * + +_translate = QCoreApplication.translate + +logger = logging.getLogger() + +class ComboBoxDelegate(QItemDelegate): + def __init__(self, study=None, parent=None): + super(ComboBoxDelegate, self).__init__(parent) + + self._study = study + + def createEditor(self, parent, option, index): + self.editor = QComboBox(parent) + + self.editor.addItems( + [_translate("SedimentLayers", "Not defined")] + + list( + map( + lambda sl: str(sl), + self._study.river.sediment_layers.sediment_layers + ) + ) + ) + + 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 and 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(QAbstractTableModel): + def __init__(self, study=None, reach=None, undo=None): + super(QAbstractTableModel, self).__init__() + self._headers = list(table_headers.keys()) + self._study = study + self._undo = undo + self._reach = reach + + def flags(self, index): + column = index.column() + + options = Qt.ItemIsEnabled | Qt.ItemIsSelectable + if self._headers[column] == "sl": + options |= Qt.ItemIsEditable + + return options + + def rowCount(self, parent): + return self._reach.number_profiles + + def columnCount(self, parent): + return len(self._headers) + + 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._reach.profile(row).name + if self._headers[column] == "kp": + return self._reach.profile(row).kp + if self._headers[column] == "sl": + value = self._reach.profile(row).sl + if value == None: + text = _translate("SedimentLayers", "Not defined") + return text + return str(value) + + return QVariant() + + def headerData(self, friction, orientation, role): + if role == Qt.ItemDataRole.DisplayRole and orientation == Qt.Orientation.Horizontal: + return table_headers[self._headers[friction]] + + 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() + + if self._headers[column] == "sl": + new = None + if value != _translate("SedimentLayers", "Not defined"): + new = next( + filter( + lambda sl: str(sl) == value, + self._study.river.sediment_layers.sediment_layers + ) + ) + + self._undo.push( + SetSLCommand( + self._reach, row, new + ) + ) + + self.dataChanged.emit(index, index) + return True + + def apply_sl_each_profile(self, sl): + self._undo.push( + ApplySLCommand( + self._reach, sl + ) + ) + self.layoutChanged.emit() + + def undo(self): + self._undo.undo() + self.layoutChanged.emit() + + def redo(self): + self._undo.redo() + self.layoutChanged.emit() diff --git a/src/View/SedimentLayers/Reach/UndoCommand.py b/src/View/SedimentLayers/Reach/UndoCommand.py new file mode 100644 index 0000000000000000000000000000000000000000..17e1f0f1d308855606e2e8a0b972dc57b48d3983 --- /dev/null +++ b/src/View/SedimentLayers/Reach/UndoCommand.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- + +import logging + +from copy import deepcopy +from tools import trace, timer + +from PyQt5.QtWidgets import ( + QMessageBox, QUndoCommand, QUndoStack, +) + +from Model.Geometry.Reach import Reach +from Model.Geometry.Profile import Profile +from Model.SedimentLayer.SedimentLayer import SedimentLayer +from Model.SedimentLayer.SedimentLayerList import SedimentLayerList + +logger = logging.getLogger() + +class SetSLCommand(QUndoCommand): + def __init__(self, reach, index, new_value): + QUndoCommand.__init__(self) + + self._reach = reach + self._index = index + self._old = self._reach.profile(self._index).sl + self._new = new_value + + def undo(self): + self._reach.profile(self._index).sl = self._old + + def redo(self): + self._reach.profile(self._index).sl = self._new + + +class ApplySLCommand(QUndoCommand): + def __init__(self, reach, new_value): + QUndoCommand.__init__(self) + + self._reach = reach + self._old = [] + for profile in self._reach.profiles: + self._old.append(profile.sl) + self._new = new_value + + def undo(self): + for i, profile in enumerate(self._reach.profiles): + profile.sl = self._old[i] + + def redo(self): + for profile in self._reach.profiles: + profile.sl = self._new diff --git a/src/View/SedimentLayers/Reach/Window.py b/src/View/SedimentLayers/Reach/Window.py new file mode 100644 index 0000000000000000000000000000000000000000..ee477edc15ec87e7500de8388c0bf02d46e2edb5 --- /dev/null +++ b/src/View/SedimentLayers/Reach/Window.py @@ -0,0 +1,190 @@ +# -*- coding: utf-8 -*- + +import logging + +from tools import trace, timer + +from View.ASubWindow import ASubMainWindow +from View.ListedSubWindow import ListedSubWindow + +from PyQt5.QtGui import ( + QKeySequence, +) + +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, QVBoxLayout, QHeaderView, QTabWidget, +) + +from View.SedimentLayers.Reach.UndoCommand import * +from View.SedimentLayers.Reach.Table import * +from View.SedimentLayers.Reach.Plot import Plot +from View.SedimentLayers.Reach.SLDialog import SLDialog + +from View.Plot.MplCanvas import MplCanvas +from View.SedimentLayers.Reach.translate import * + +from View.SedimentLayers.Window import SedimentLayersWindow +from View.SedimentLayers.Reach.Profile.Window import ProfileSedimentLayersWindow + +_translate = QCoreApplication.translate + +logger = logging.getLogger() + +class ReachSedimentLayersWindow(ASubMainWindow, ListedSubWindow): + def __init__(self, title="Reach sediment layers", study=None, parent=None): + self._study = study + self._sediment_layers = self._study.river.sediment_layers + self._reach = self._study.river.current_reach().reach + + self.setup_title(title) + + super(ReachSedimentLayersWindow, self).__init__( + name=self._title, ui="ReachSedimentLayers", parent=parent + ) + + self.setup_sc() + self.setup_table() + self.setup_graph() + self.setup_connections() + + self.ui.setWindowTitle(self._title) + + def setup_title(self, title): + self._title = ( + title + " - " + self._study.name + " - " + self._reach.name + ) + + def setup_sc(self): + self._undo_stack = QUndoStack() + + self.undo_sc = QShortcut(QKeySequence.Undo, self) + self.redo_sc = QShortcut(QKeySequence.Redo, self) + self.copy_sc = QShortcut(QKeySequence.Copy, self) + self.paste_sc = QShortcut(QKeySequence.Paste, self) + + def setup_table(self): + table = self.find(QTableView, f"tableView") + self._table = TableModel( + study = self._study, + reach = self._reach, + undo = self._undo_stack, + ) + table.setModel(self._table) + + self._delegate_stricklers = ComboBoxDelegate( + study = self._study, + parent=self + ) + + table.setItemDelegateForColumn( + list(table_headers).index("sl"), + self._delegate_stricklers + ) + + table.setSelectionBehavior(QAbstractItemView.SelectRows) + table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) + table.setAlternatingRowColors(True) + + def setup_graph(self): + self.canvas = MplCanvas(width=5, height=4, dpi=100) + self.canvas.setObjectName("canvas") + self.plot_layout = self.find(QVBoxLayout, "verticalLayout_2") + self.plot_layout.addWidget(self.canvas) + + self._update_plot() + + def _update_plot(self): + self.plot = Plot( + canvas = self.canvas, + data = self._reach, + toolbar = None, + display_current = False + ) + self.plot.draw() + + + def setup_connections(self): + self.find(QAction, "action_edit").triggered.connect(self.edit_profile) + + self.find(QPushButton, "pushButton_edit")\ + .clicked\ + .connect(self.edit_sl) + self.find(QPushButton, "pushButton_apply")\ + .clicked\ + .connect(self.apply_sl_each_profile) + + self.undo_sc.activated.connect(self.undo) + self.redo_sc.activated.connect(self.redo) + self.copy_sc.activated.connect(self.copy) + self.paste_sc.activated.connect(self.paste) + + self._table.layoutChanged\ + .connect(self._update_plot) + self._table.dataChanged\ + .connect(self._update_plot) + + + def index_selected_rows(self): + table = self.find(QTableView, f"tableView") + return list( + # Delete duplicate + set( + map( + lambda i: i.row(), + table.selectedIndexes() + ) + ) + ) + + def copy(self): + logger.info("TODO: copy") + + def paste(self): + logger.info("TODO: paste") + + def undo(self): + self._table.undo() + self._update_plot() + + def redo(self): + self._table.redo() + self._update_plot() + + def apply_sl_each_profile(self): + slw = SLDialog( + study = self._study, + parent = self + ) + if slw.exec(): + sl = slw.sl + self._table.apply_sl_each_profile(sl) + + self._update_plot() + + def edit_profile(self): + rows = self.index_selected_rows() + + for row in rows: + slw = ProfileSedimentLayersWindow( + study = self._study, + profile = self._reach.profile(row), + parent = self + ) + slw.show() + + + def edit_sl(self): + slw = SedimentLayersWindow( + study = self._study, + parent = self + ) + slw.show() diff --git a/src/View/SedimentLayers/Reach/translate.py b/src/View/SedimentLayers/Reach/translate.py new file mode 100644 index 0000000000000000000000000000000000000000..f6b17cf8a5395417b5c655ab8c3e141397985f4e --- /dev/null +++ b/src/View/SedimentLayers/Reach/translate.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- + +from PyQt5.QtCore import QCoreApplication + +_translate = QCoreApplication.translate + +table_headers = { + "name": _translate("SedimentLayers", "Name"), + "kp": _translate("SedimentLayers", "KP (m)"), + "sl": _translate("SedimentLayers", "Sediment layers"), +} diff --git a/src/View/SedimentLayers/Table.py b/src/View/SedimentLayers/Table.py new file mode 100644 index 0000000000000000000000000000000000000000..f375c3188bba6cf0100b327d38197c1f5a8b0fa4 --- /dev/null +++ b/src/View/SedimentLayers/Table.py @@ -0,0 +1,151 @@ +# -*- coding: utf-8 -*- + +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.SedimentLayers.UndoCommand import * +from View.SedimentLayers.translate import * + +_translate = QCoreApplication.translate + + +class TableModel(QAbstractTableModel): + def __init__(self, study=None, undo=None): + super(QAbstractTableModel, self).__init__() + self._headers = list(table_headers.keys()) + self._study = study + self._undo = undo + self._sl = self._study.river.sediment_layers + + def flags(self, index): + options = Qt.ItemIsEnabled | Qt.ItemIsSelectable + options |= Qt.ItemIsEditable + + return options + + def rowCount(self, parent): + return len(self._sl) + + def columnCount(self, parent): + return len(self._headers) + + 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._sl.get(row).name + elif self._headers[column] == "comment": + return self._sl.get(row).comment + + return QVariant() + + def headerData(self, friction, orientation, role): + if role == Qt.ItemDataRole.DisplayRole and orientation == Qt.Orientation.Horizontal: + return table_headers[self._headers[friction]] + + 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() + + if self._headers[column] == "name": + self._undo.push( + SetNameCommand( + self._sl, row, value + ) + ) + if self._headers[column] == "comment": + self._undo.push( + SetCommentCommand( + self._sl, row, value + ) + ) + + self.dataChanged.emit(index, index) + return True + + def add(self, row, parent=QModelIndex()): + self.beginInsertRows(parent, row, row - 1) + + self._undo.push( + AddCommand( + self._sl, row + ) + ) + + self.endInsertRows() + self.layoutChanged.emit() + + def delete(self, rows, parent=QModelIndex()): + self.beginRemoveRows(parent, rows[0], rows[-1]) + + self._undo.push( + DelCommand( + self._sl, rows + ) + ) + + self.endRemoveRows() + self.layoutChanged.emit() + + def move_up(self, row, parent=QModelIndex()): + if row <= 0: + return + + target = row + 2 + + self.beginMoveRows(parent, row - 1, row - 1, parent, target) + + self._undo_stack.push( + MoveCommand( + self._sl, "up", row + ) + ) + + self.endMoveRows() + self.layoutChanged.emit() + + def move_down(self, index, parent=QModelIndex()): + if row > len(self._sl): + return + + target = row + + self.beginMoveRows(parent, row + 1, row + 1, parent, target) + + self._undo_stack.push( + MoveCommand( + self._sl, "down", row + ) + ) + + self.endMoveRows() + self.layoutChanged.emit() + + def undo(self): + self._undo.undo() + self.layoutChanged.emit() + + def redo(self): + self._undo.redo() + self.layoutChanged.emit() diff --git a/src/View/SedimentLayers/UndoCommand.py b/src/View/SedimentLayers/UndoCommand.py new file mode 100644 index 0000000000000000000000000000000000000000..f785743b4aa0b455a51bdb4228d0f961af3ee013 --- /dev/null +++ b/src/View/SedimentLayers/UndoCommand.py @@ -0,0 +1,97 @@ +# -*- coding: utf-8 -*- + +from copy import deepcopy +from tools import trace, timer + +from PyQt5.QtWidgets import ( + QMessageBox, QUndoCommand, QUndoStack, +) + +from Model.SedimentLayer.SedimentLayer import SedimentLayer +from Model.SedimentLayer.SedimentLayerList import SedimentLayerList + +class SetNameCommand(QUndoCommand): + def __init__(self, sediment_layers_list, index, new_value): + QUndoCommand.__init__(self) + + self._sediment_layers_list = sediment_layers_list + self._index = index + self._old = self._sediment_layers_list.get(self._index).name + self._new = new_value + + def undo(self): + self._sediment_layers_list.get(self._index).name = self._old + + def redo(self): + self._sediment_layers_list.get(self._index).name = self._new + +class SetCommentCommand(QUndoCommand): + def __init__(self, sediment_layers_list, index, new_value): + QUndoCommand.__init__(self) + + self._sediment_layers_list = sediment_layers_list + self._index = index + self._old = self._sediment_layers_list.get(self._index).comment + self._new = new_value + + def undo(self): + self._sediment_layers_list.get(self._index).comment = self._old + + def redo(self): + self._sediment_layers_list.get(self._index).comment = self._new + +class AddCommand(QUndoCommand): + def __init__(self, sediment_layers_list, index): + QUndoCommand.__init__(self) + + self._sediment_layers_list = sediment_layers_list + self._index = index + self._new = None + + def undo(self): + self._sediment_layers_list.delete_i([self._index]) + + def redo(self): + if self._new is None: + self._new = self._sediment_layers_list.new(self._index) + else: + self._sediment_layers_list.insert(self._index, self._new) + +class DelCommand(QUndoCommand): + def __init__(self, sediment_layers_list, rows): + QUndoCommand.__init__(self) + + self._sediment_layers_list = sediment_layers_list + self._rows = rows + + self._sl_pos = [] + for row in rows: + self._sl_pos.append((row, self._sediment_layers_list.get(row))) + self._sl_pos.sort() + + def undo(self): + for row, el in self._sl_pos: + self._sediment_layers_list.insert(row, el) + + def redo(self): + self._sediment_layers_list.delete_i(self._rows) + +class MoveCommand(QUndoCommand): + def __init__(self, sediment_layers_list, up, i): + QUndoCommand.__init__(self) + + self._sediment_layers_list = sediment_layers_list + self._up = up == "up" + self._i = i + + def undo(self): + if self._up: + self._sediment_layers_list.move_up(self._i) + else: + self._sediment_layers_list.move_down(self._i) + + def redo(self): + if self._up: + self._sediment_layers_list.move_up(self._i) + else: + self._sediment_layers_list.move_down(self._i) diff --git a/src/View/SedimentLayers/Window.py b/src/View/SedimentLayers/Window.py new file mode 100644 index 0000000000000000000000000000000000000000..f8b9d2d2676b50b88fbb0de095d9291efdf2f240 --- /dev/null +++ b/src/View/SedimentLayers/Window.py @@ -0,0 +1,170 @@ +# -*- coding: utf-8 -*- + +import logging + +from tools import trace, timer + +from View.ASubWindow import ASubMainWindow +from View.ListedSubWindow import ListedSubWindow + +from PyQt5.QtGui import ( + QKeySequence, +) + +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, QVBoxLayout, QHeaderView, QTabWidget, +) + +from View.SedimentLayers.UndoCommand import * +from View.SedimentLayers.Table import * + +from View.SedimentLayers.Edit.Plot import Plot + +from View.Plot.MplCanvas import MplCanvas +from View.SedimentLayers.translate import * + +from View.SedimentLayers.Edit.Window import EditSedimentLayersWindow + +_translate = QCoreApplication.translate + +logger = logging.getLogger() + +class SedimentLayersWindow(ASubMainWindow, ListedSubWindow): + def __init__(self, title="SedimentLayersList", study=None, parent=None): + self._study = study + self._sediment_layers = self._study.river.sediment_layers + + self.setup_title(title) + + super(SedimentLayersWindow, self).__init__( + name=self._title, ui="SedimentLayersList", parent=parent + ) + + self.setup_sc() + self.setup_table() + self.setup_graph() + self.setup_connections() + + self.ui.setWindowTitle(self._title) + + def setup_title(self, title): + self._title = ( + title + " - " + self._study.name + ) + + def setup_sc(self): + self._undo_stack = QUndoStack() + + self.undo_sc = QShortcut(QKeySequence.Undo, self) + self.redo_sc = QShortcut(QKeySequence.Redo, self) + self.copy_sc = QShortcut(QKeySequence.Copy, self) + self.paste_sc = QShortcut(QKeySequence.Paste, self) + + def setup_table(self): + table = self.find(QTableView, f"tableView") + self._table = TableModel( + study = self._study, + undo = self._undo_stack, + ) + table.setModel(self._table) + + table.setSelectionBehavior(QAbstractItemView.SelectRows) + table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) + table.setAlternatingRowColors(True) + + def setup_graph(self): + self.canvas = MplCanvas(width=5, height=4, dpi=100) + self.canvas.setObjectName("canvas") + self.plot_layout = self.find(QVBoxLayout, "verticalLayout") + self.plot_layout.addWidget(self.canvas) + + def setup_connections(self): + self.find(QAction, "action_add").triggered.connect(self.add) + self.find(QAction, "action_del").triggered.connect(self.delete) + self.find(QAction, "action_edit").triggered.connect(self.edit_sediment_layers) + + self.undo_sc.activated.connect(self.undo) + self.redo_sc.activated.connect(self.redo) + self.copy_sc.activated.connect(self.copy) + self.paste_sc.activated.connect(self.paste) + + table = self.find(QTableView, f"tableView") + table.selectionModel()\ + .selectionChanged\ + .connect(self._set_current_sl) + + self._table.dataChanged\ + .connect(self._set_current_sl) + + def _set_current_sl(self): + rows = self.index_selected_rows() + + if len(rows) == 0: + return + + self.plot = Plot( + canvas = self.canvas, + data = self._sediment_layers.get(rows[0]), + toolbar = None, + display_current = False + ) + self.plot.draw() + + + def index_selected_rows(self): + table = self.find(QTableView, f"tableView") + return list( + # Delete duplicate + set( + map( + lambda i: i.row(), + table.selectedIndexes() + ) + ) + ) + + def add(self): + rows = self.index_selected_rows() + if len(self._sediment_layers) == 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_sediment_layers(self): + rows = self.index_selected_rows() + + for row in rows: + slw = EditSedimentLayersWindow( + study = self._study, + sl = self._sediment_layers.get(row), + parent = self + ) + slw.show() diff --git a/src/View/SedimentLayers/translate.py b/src/View/SedimentLayers/translate.py new file mode 100644 index 0000000000000000000000000000000000000000..6eafe079bbbc07c58e8bad45d5cd9c107bf65c30 --- /dev/null +++ b/src/View/SedimentLayers/translate.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- + +from PyQt5.QtCore import QCoreApplication + +_translate = QCoreApplication.translate + +table_headers = { + "name": _translate("SedimentLayers", "Name"), + "comment": _translate("SedimentLayers", "Comment"), +} diff --git a/src/View/SolverParameters/translate.py b/src/View/SolverParameters/translate.py index 76cb70ac627ce7fd4a9b15a618946a3c1cf30e16..2508629ea0ab0cfdeb70d639b7a1bb9174ab4715 100644 --- a/src/View/SolverParameters/translate.py +++ b/src/View/SolverParameters/translate.py @@ -55,15 +55,16 @@ def init(): "all_init_time": _translate("SolverParameters", "Initial time (jj:hh:mm:ss)"), "all_final_time": _translate("SolverParameters", "Final time (jj:hh:mm:ss)"), "all_timestep": _translate("SolverParameters", "Timestep (second)"), + "all_command_line_arguments": _translate("SolverParameters", "Command line arguments"), # Mage specific parameters "mage_min_timestep": _translate("SolverParameters", "Minimum timestep (second)"), "mage_timestep_tra": _translate("SolverParameters", "Time step of writing on .TRA"), "mage_timestep_bin": _translate("SolverParameters", "Time step of writing on .BIN"), - "mage_implication": _translate("SolverParameters", "Implicitation parameter"), + "mage_implicitation": _translate("SolverParameters", "Implicitation parameter"), "mage_continuity_discretization": _translate("SolverParameters", "Continuity discretization type (S/L)"), "mage_qsj_discretization": _translate("SolverParameters", "QSJ discretization (A/B)"), "mage_stop_criterion_iterations": _translate("SolverParameters", "Stop criterion iterations (G/A/R)"), - "mage_iter_type": _translate("SolverParameters", "Iteration type"), + "mage_iteration_type": _translate("SolverParameters", "Iteration type"), "mage_smooth_coef": _translate("SolverParameters", "Smoothing coefficient"), "mage_cfl_max": _translate("SolverParameters", "Maximun accepted number of CFL"), "mage_min_height": _translate("SolverParameters", "Minimum water height (meter)"), diff --git a/src/View/ui/EditSedimentLayers.ui b/src/View/ui/EditSedimentLayers.ui new file mode 100644 index 0000000000000000000000000000000000000000..6fe4c11b0c00dba95e08413308e8a8782fac1ede --- /dev/null +++ b/src/View/ui/EditSedimentLayers.ui @@ -0,0 +1,114 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>MainWindow</class> + <widget class="QMainWindow" name="MainWindow"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>960</width> + <height>480</height> + </rect> + </property> + <property name="windowTitle"> + <string>MainWindow</string> + </property> + <widget class="QWidget" name="centralwidget"> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0"> + <widget class="QSplitter" name="splitter"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <widget class="QTableView" name="tableView"/> + <widget class="QWidget" name="verticalLayoutWidget"> + <layout class="QVBoxLayout" name="verticalLayout"/> + </widget> + </widget> + </item> + </layout> + </widget> + <widget class="QMenuBar" name="menubar"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>960</width> + <height>22</height> + </rect> + </property> + </widget> + <widget class="QStatusBar" name="statusbar"/> + <widget class="QToolBar" name="toolBar"> + <property name="windowTitle"> + <string>toolBar</string> + </property> + <attribute name="toolBarArea"> + <enum>TopToolBarArea</enum> + </attribute> + <attribute name="toolBarBreak"> + <bool>false</bool> + </attribute> + <addaction name="action_add"/> + <addaction name="action_del"/> + <addaction name="action_move_up"/> + <addaction name="action_move_down"/> + </widget> + <action name="action_add"> + <property name="icon"> + <iconset> + <normaloff>ressources/gtk-add.png</normaloff>ressources/gtk-add.png</iconset> + </property> + <property name="text"> + <string>Add sediment layer</string> + </property> + <property name="toolTip"> + <string>Add a new sediment layer</string> + </property> + <property name="shortcut"> + <string>Ctrl+N</string> + </property> + </action> + <action name="action_del"> + <property name="icon"> + <iconset> + <normaloff>ressources/gtk-remove.png</normaloff>ressources/gtk-remove.png</iconset> + </property> + <property name="text"> + <string>Delete sediment layer</string> + </property> + <property name="toolTip"> + <string>Delete selected sediment layer(s)</string> + </property> + <property name="shortcut"> + <string>Ctrl+D</string> + </property> + </action> + <action name="action_move_up"> + <property name="icon"> + <iconset> + <normaloff>ressources/up.png</normaloff>ressources/up.png</iconset> + </property> + <property name="text"> + <string>Move up</string> + </property> + <property name="toolTip"> + <string>Move up</string> + </property> + </action> + <action name="action_move_down"> + <property name="icon"> + <iconset> + <normaloff>ressources/down.png</normaloff>ressources/down.png</iconset> + </property> + <property name="text"> + <string>Move down</string> + </property> + <property name="toolTip"> + <string>Move down</string> + </property> + </action> + </widget> + <resources/> + <connections/> +</ui> diff --git a/src/View/ui/MainWindow.ui b/src/View/ui/MainWindow.ui index 217bd2b42ab461e2bc7c8990752268ab476cd51a..ea77d698538b0c64532eeb76c8942fe436f55e94 100644 --- a/src/View/ui/MainWindow.ui +++ b/src/View/ui/MainWindow.ui @@ -165,10 +165,18 @@ <addaction name="action_menu_help_mage"/> <addaction name="action_menu_about"/> </widget> + <widget class="QMenu" name="menuSediment"> + <property name="title"> + <string>&Sediment</string> + </property> + <addaction name="action_menu_sediment_layers"/> + <addaction name="action_menu_edit_reach_sediment_layers"/> + </widget> <addaction name="menu_File"/> <addaction name="menu_network"/> <addaction name="menu_geometry"/> <addaction name="menu_Hydraulics"/> + <addaction name="menuSediment"/> <addaction name="menu_run"/> <addaction name="menu_plot"/> <addaction name="menu_cartography"/> @@ -878,6 +886,16 @@ <string>Define initial conditions</string> </property> </action> + <action name="action_menu_sediment_layers"> + <property name="text"> + <string>Sediment layers</string> + </property> + </action> + <action name="action_menu_edit_reach_sediment_layers"> + <property name="text"> + <string>Edit reach sediment layers</string> + </property> + </action> </widget> <resources/> <connections> diff --git a/src/View/ui/ProfileSedimentLayers.ui b/src/View/ui/ProfileSedimentLayers.ui new file mode 100644 index 0000000000000000000000000000000000000000..b9f5106dab3d25190b3d41bd8209e2e89cdd36ed --- /dev/null +++ b/src/View/ui/ProfileSedimentLayers.ui @@ -0,0 +1,95 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>MainWindow</class> + <widget class="QMainWindow" name="MainWindow"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>960</width> + <height>480</height> + </rect> + </property> + <property name="windowTitle"> + <string>MainWindow</string> + </property> + <widget class="QWidget" name="centralwidget"> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0"> + <widget class="QSplitter" name="splitter"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <widget class="QTableView" name="tableView"/> + <widget class="QWidget" name="verticalLayoutWidget"> + <layout class="QVBoxLayout" name="verticalLayout"/> + </widget> + </widget> + </item> + </layout> + </widget> + <widget class="QMenuBar" name="menubar"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>960</width> + <height>22</height> + </rect> + </property> + </widget> + <widget class="QStatusBar" name="statusbar"/> + <widget class="QToolBar" name="toolBar"> + <property name="windowTitle"> + <string>toolBar</string> + </property> + <attribute name="toolBarArea"> + <enum>TopToolBarArea</enum> + </attribute> + <attribute name="toolBarBreak"> + <bool>false</bool> + </attribute> + <addaction name="action_add_sediment_layers"/> + <addaction name="action_delete_sediment_layers"/> + <addaction name="action_edit_sediment_layers"/> + </widget> + <action name="action_add_sediment_layers"> + <property name="icon"> + <iconset> + <normaloff>ressources/gtk-add.png</normaloff>ressources/gtk-add.png</iconset> + </property> + <property name="text"> + <string>Add sediment layers</string> + </property> + <property name="toolTip"> + <string>Add specific sediment layers on selected point(s)</string> + </property> + </action> + <action name="action_delete_sediment_layers"> + <property name="icon"> + <iconset> + <normaloff>ressources/gtk-remove.png</normaloff>ressources/gtk-remove.png</iconset> + </property> + <property name="text"> + <string>Delete sediment layers</string> + </property> + <property name="toolTip"> + <string>Delete specific sediment layers of selected point(s)</string> + </property> + </action> + <action name="action_edit_sediment_layers"> + <property name="icon"> + <iconset> + <normaloff>ressources/edit.png</normaloff>ressources/edit.png</iconset> + </property> + <property name="text"> + <string>Edit sediment layers</string> + </property> + <property name="toolTip"> + <string>Edit sediment layers list</string> + </property> + </action> + </widget> + <resources/> + <connections/> +</ui> diff --git a/src/View/ui/ReachSedimentLayers.ui b/src/View/ui/ReachSedimentLayers.ui new file mode 100644 index 0000000000000000000000000000000000000000..dfe39a823d819abf8351e0cf9cc490438fb209be --- /dev/null +++ b/src/View/ui/ReachSedimentLayers.ui @@ -0,0 +1,89 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>MainWindow</class> + <widget class="QMainWindow" name="MainWindow"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>960</width> + <height>480</height> + </rect> + </property> + <property name="windowTitle"> + <string>MainWindow</string> + </property> + <widget class="QWidget" name="centralwidget"> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0"> + <widget class="QSplitter" name="splitter"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <widget class="QWidget" name="verticalLayoutWidget"> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QTableView" name="tableView"/> + </item> + <item> + <widget class="QPushButton" name="pushButton_edit"> + <property name="text"> + <string>Edit sediment layers list</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="pushButton_apply"> + <property name="text"> + <string>Apply sediment layers on all reach</string> + </property> + </widget> + </item> + </layout> + </widget> + <widget class="QWidget" name="verticalLayoutWidget_2"> + <layout class="QVBoxLayout" name="verticalLayout_2"/> + </widget> + </widget> + </item> + </layout> + </widget> + <widget class="QMenuBar" name="menubar"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>960</width> + <height>22</height> + </rect> + </property> + </widget> + <widget class="QStatusBar" name="statusbar"/> + <widget class="QToolBar" name="toolBar"> + <property name="windowTitle"> + <string>toolBar</string> + </property> + <attribute name="toolBarArea"> + <enum>TopToolBarArea</enum> + </attribute> + <attribute name="toolBarBreak"> + <bool>false</bool> + </attribute> + <addaction name="action_edit"/> + </widget> + <action name="action_edit"> + <property name="icon"> + <iconset> + <normaloff>ressources/edit.png</normaloff>ressources/edit.png</iconset> + </property> + <property name="text"> + <string>Edit profile</string> + </property> + <property name="toolTip"> + <string>Edit profile sediment layer</string> + </property> + </action> + </widget> + <resources/> + <connections/> +</ui> diff --git a/src/View/ui/Results.ui b/src/View/ui/Results.ui index d975641b9d1fd8ba90c54f4a33f7feeb5fd7150f..5b7977242bbdeaec70fa2548687695f6ad45e888 100644 --- a/src/View/ui/Results.ui +++ b/src/View/ui/Results.ui @@ -30,7 +30,7 @@ <widget class="QTableView" name="tableView_reach"/> <widget class="QTableView" name="tableView_profile"/> </widget> - <widget class="QWidget" name=""> + <widget class="QWidget" name="layoutWidget"> <layout class="QGridLayout" name="gridLayout_2"> <item row="0" column="0"> <widget class="QTabWidget" name="tabWidget"> @@ -75,6 +75,32 @@ </item> </layout> </widget> + <widget class="QWidget" name="tab_3"> + <property name="enabled"> + <bool>true</bool> + </property> + <attribute name="title"> + <string>Sediment</string> + </attribute> + <layout class="QGridLayout" name="gridLayout_5"> + <item row="0" column="0"> + <widget class="QSplitter" name="splitter_5"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <widget class="QWidget" name="verticalLayoutWidget_4"> + <layout class="QVBoxLayout" name="verticalLayout_sed_reach"/> + </widget> + <widget class="QWidget" name="verticalLayoutWidget_5"> + <layout class="QVBoxLayout" name="verticalLayout_sed_profile"/> + </widget> + </widget> + </item> + </layout> + </widget> </widget> </item> <item row="0" column="1"> diff --git a/src/View/ui/SLDialog.ui b/src/View/ui/SLDialog.ui new file mode 100644 index 0000000000000000000000000000000000000000..8681418bd621fdbbf0016add2de546e03f1fc947 --- /dev/null +++ b/src/View/ui/SLDialog.ui @@ -0,0 +1,67 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>Dialog</class> + <widget class="QDialog" name="Dialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>194</width> + <height>76</height> + </rect> + </property> + <property name="windowTitle"> + <string>Dialog</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0"> + <widget class="QComboBox" name="comboBox"/> + </item> + <item row="1" column="0"> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>Dialog</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>248</x> + <y>254</y> + </hint> + <hint type="destinationlabel"> + <x>157</x> + <y>274</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>Dialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>316</x> + <y>260</y> + </hint> + <hint type="destinationlabel"> + <x>286</x> + <y>274</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/View/ui/SedimentLayersList.ui b/src/View/ui/SedimentLayersList.ui new file mode 100644 index 0000000000000000000000000000000000000000..399b09953889574a12ecb6ce78373ab7b0389a77 --- /dev/null +++ b/src/View/ui/SedimentLayersList.ui @@ -0,0 +1,95 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>MainWindow</class> + <widget class="QMainWindow" name="MainWindow"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>960</width> + <height>480</height> + </rect> + </property> + <property name="windowTitle"> + <string>MainWindow</string> + </property> + <widget class="QWidget" name="centralwidget"> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0"> + <widget class="QSplitter" name="splitter"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <widget class="QTableView" name="tableView"/> + <widget class="QWidget" name="verticalLayoutWidget"> + <layout class="QVBoxLayout" name="verticalLayout"/> + </widget> + </widget> + </item> + </layout> + </widget> + <widget class="QMenuBar" name="menubar"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>960</width> + <height>22</height> + </rect> + </property> + </widget> + <widget class="QStatusBar" name="statusbar"/> + <widget class="QToolBar" name="toolBar"> + <property name="windowTitle"> + <string>toolBar</string> + </property> + <attribute name="toolBarArea"> + <enum>TopToolBarArea</enum> + </attribute> + <attribute name="toolBarBreak"> + <bool>false</bool> + </attribute> + <addaction name="action_add"/> + <addaction name="action_del"/> + <addaction name="action_edit"/> + </widget> + <action name="action_add"> + <property name="icon"> + <iconset> + <normaloff>ressources/gtk-add.png</normaloff>ressources/gtk-add.png</iconset> + </property> + <property name="text"> + <string>Add sediment layer</string> + </property> + </action> + <action name="action_del"> + <property name="icon"> + <iconset> + <normaloff>ressources/gtk-remove.png</normaloff>ressources/gtk-remove.png</iconset> + </property> + <property name="text"> + <string>Delete sediment layer</string> + </property> + <property name="toolTip"> + <string>Delete sediment layer</string> + </property> + </action> + <action name="action_edit"> + <property name="icon"> + <iconset> + <normaloff>ressources/edit.png</normaloff>ressources/edit.png</iconset> + </property> + <property name="text"> + <string>Edit sediment layer</string> + </property> + <property name="toolTip"> + <string>Edit sediment layer</string> + </property> + <property name="shortcut"> + <string>Ctrl+E</string> + </property> + </action> + </widget> + <resources/> + <connections/> +</ui> diff --git a/src/View/ui/Widgets/extendedTimeEdit.ui b/src/View/ui/Widgets/extendedTimeEdit.ui index 493a96a11eece3431108500c8eff0555f019ea81..eddf38928e30fc06e392a100332342fc74d4ec43 100644 --- a/src/View/ui/Widgets/extendedTimeEdit.ui +++ b/src/View/ui/Widgets/extendedTimeEdit.ui @@ -28,6 +28,9 @@ <property name="alignment"> <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> </property> + <property name="maximum"> + <number>999999</number> + </property> </widget> </item> <item> diff --git a/tests_cases/Saar/Saar.pamhyr b/tests_cases/Saar/Saar.pamhyr index df56a2984353c1c2f9d8f73ffc277b4fab764ba6..711ef7bc929574e1ca841b76442e1262bddd4c8b 100644 Binary files a/tests_cases/Saar/Saar.pamhyr and b/tests_cases/Saar/Saar.pamhyr differ