Merge: Merge branch 'sediment' with sediment export and result for mage.

This commit is contained in:
Pierre-Antoine Rouby committed 2023-08-22 11:44:04 +02:00
commit 7429952168
57 files changed
+3995 -76

No files matched your search

@@ -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
@@ -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):
@@ -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"]
+18 -1
View File
@@ -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):
+35 -8
View File
@@ -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]
+50
View File
@@ -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)
+37 -11
View File
@@ -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
+33
View File
@@ -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
+3
View File
@@ -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()
+31
View File
@@ -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
+339
View File
@@ -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()
@@ -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()
@@ -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()
+1 -1
View File
@@ -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()
+44 -21
View File
@@ -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):
+247 -11
View File
@@ -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}"
f.write(f"{num}{c1}{c2}{t}{kp} {name}\n")
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} {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
+2 -1
View File
@@ -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)"),
}
+5 -2
View File
@@ -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,
}
+3 -3
View File
@@ -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,
@@ -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()
+2 -2
View File
@@ -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
+39 -1
View File
@@ -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 #
+48 -4
View File
@@ -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:
+130
View File
@@ -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()
+134
View File
@@ -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()
+50
View File
@@ -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")
+2 -1
View File
@@ -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:
+105
View File
@@ -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()
+183
View File
@@ -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()
+157
View File
@@ -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)
+170
View File
@@ -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()
+14
View File
@@ -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"),
}
+102
View File
@@ -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()
@@ -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()
@@ -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()
@@ -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
@@ -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()
@@ -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"),
}
+61
View File
@@ -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()
+159
View File
@@ -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()
@@ -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
+190
View File
@@ -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()
@@ -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"),
}
+151
View File
@@ -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()
+97
View File
@@ -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)
+170
View File
@@ -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()
+10
View File
@@ -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"),
}
+3 -2
View File
@@ -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)"),
+114
View File
@@ -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>
+18
View File
@@ -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>&amp;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>
+95
View File
@@ -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>
+89
View File
@@ -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>
+27 -1
View File
@@ -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">
+67
View File
@@ -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>
+95
View File
@@ -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>
+3
View File
@@ -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>
Binary file not shown.