Création simple et ludique, par drag & drop
Avant-propos
CtrlNode.d_display()
, clé h_title
.Petite erreur d'arithmétique.
CtrlSocket.position()
:
@property
def position(self):
""" Les positions sont optimisées pour une grille de maille 16. """
h_title = self.o_node.d_display['geometry']['h_title']
first_y = 16*(1+(h_title+4)//16) # Multiple de 16
place_y = self.t_id[2]
y = first_y + place_y * 16
x = 0 if self.b_input else self.o_node.width
return x, y
CtrlNode.setup()
:
def setup(self, child_file):
""" Code appelant : classe dérivée, on connait donc ses attributs. """
""" Paramètres. """
self.o_params = Parameters(self)
""" Hauteur automatique du node : elle dépend du nombre de sockets en entrée ou en sortie (le plus grand). """
nb_sockets_max = max(len(self.ld_inputs), len(self.ld_outputs)) # En fait : Sockets + séparateurs.
h_title = self.d_display['geometry']['h_title']
first_y = 16*(1+(h_title+4)//16) # Multiple de 16.
self.height = first_y + nb_sockets_max * 16 - 4
""" Nouveau node par drag & drop. """
if self.pos != (0, 0): # Nouveau node (node dropé).
""" - Cas d'un nouveau node (dropé).
- Son centre apparaît exactement à la position du curseur de la souris.
- On mémorise son path et sa position. """
self.pos = self.pos[0] - self.width // 2, self.pos[1] - self.height // 2
path = 'nodes' + os.path.relpath(child_file).split('nodes')[1][:-3].replace(os.sep, '.')
self.od_pkl.write([self.s_id, 'path'], path)
else:
self.pos = self.od_pkl.read([self.s_id, 'position'], (0, 0))
""" Instanciation de l'UI. """
self.o_grnode = UiNode(self) # Gestion de l'UI (Interface utilisateur) : dessin couleurs, ...
self.o_grnode.save_pos() # Important ! à la fin (contient o_pkl.backup()).
self.o_scene.o_grscene.addItem(self.o_grnode) # Incorporation dans la scène.
""" Sockets. """
for i, d_input in enumerate(self.ld_inputs):
""" On complète les dictionnaires de base. """
if isinstance(d_input, dict): # Si ce n'est pas un dictionnaire, c'est un séparateur.
d_input['id'] = True, self.id, i # On ajoute l'identifiant. Tuple (in ?, id_node, num_socket)
self.lo_sockets_in.append(CtrlSocket(self, d_input)) # Socket instancié et ajouté à la liste.
else:
""" Permet d'éviter les erreurs d'indice. """
self.lo_sockets_in.append('sep') # Séparateur.
for i, d_output in enumerate(self.ld_outputs):
""" On complète les dictionnaires de base. """
if isinstance(d_output, dict): # Si ce n'est pas un dictionnaire, c'est un séparateur.
d_output['id'] = False, self.id, i # On ajoute l'identifiant. Tuple (in ?, id_node, num_socket)
self.lo_sockets_out.append(CtrlSocket(self, d_output)) # Socket instancié et ajouté à la liste.
else:
self.lo_sockets_out.append('sep') # Séparateur.
UiNode.init_ui()
:
def init_ui(self):
""" Paramètres. """
h_title, n_round = self.o_node.d_display['geometry']['h_title'], self.o_node.d_display['geometry']['round']
""" Position. """
self.setPos(*self.o_node.pos)
""" Fond du titre. """
geometry = (0, 0, self.o_node.width, h_title, n_round, n_round)
self.path_title.setFillRule(Qt.WindingFill)
self.path_title.addRoundedRect(*geometry)
self.path_title.addRect(0, h_title - n_round, self.o_node.width, n_round)
# |_ Enlève les arrondis du bas du titre, pour éviter un effet disgracieux (Commenter cette ligne pour le voir)
""" Texte du titre. """
self.title_item.setFont(QFont('Arial', 9)) # Police et taille
y = 0 if self.o_node.type == '' else -3
self.title_item.setPos(0, y)
self.set_title()
""" Texte du type. """
self.type_item.setFont(QFont('Arial', 6, italic=True)) # Police et taille
self.type_item.setDefaultTextColor(Qt.white)
self.type_item.setPlainText(self.o_node.type)
x = self.o_node.width - self.type_item.boundingRect().width()
self.type_item.setPos(x, h_title-14)
""" Dessin du node : rectangle aux coins arrondis. """
self.path_outline.addRoundedRect(0, 0, self.o_node.width, self.o_node.height, n_round, n_round)
""" On / Off """
QGraphicsProxyWidget(self).setWidget(self.on_off)
self.on_off.move(self.o_node.width-10, -4)
self.on_off.setChecked(self.o_node.b_chk)
""" Flags. """
self.setFlag(QGraphicsItem.ItemIsMovable) # Peut être déplacé.
self.setFlag(QGraphicsItem.ItemIsSelectable) # Peut être sélectionné.
self.setAcceptHoverEvents(True) # Accepte le survol.
self.setToolTip(f"{self.o_node.get_param('Titre du node', 'Le titre')}\n{self.o_node.pos}")
""" Événements. """
self.on_off.stateChanged.connect(self.o_node.set_checked)
Description
socket_out
du node_from
.socket_in
du node_to
.QPointF
à la position de la souris.CtrlSocket.t_id
: il contient désormais 2 valeurs au lieu de 3. Cela permet de simplifier le code lors du traitement des edges.CtrlSocket.__init__()
:
def __init__(self, o_node, d_datas):
self.o_node = o_node
self.d_datas = d_datas # Dictionnaire. clés : label, label_pos, id, is_input
self.t_id = self.d_datas['id'][1:] # (id_node: int, num_socket: int) <- tuple.
self.b_input = self.d_datas['id'][0]
self.label = self.d_datas['label']
self.t_label_pos = self.d_datas['label_pos']
self.o_grsocket = None
self.setup()
CtrlSocket.position()
:
@property
def position(self):
""" Les positions sont optimisées pour une grille de maille 16. """
h_title = self.o_node.d_display['geometry']['h_title']
first_y = 16*(1+(h_title+4)//16) # Multiple de 16
place_y = self.t_id[1]
y = first_y + place_y * 16
x = 0 if self.b_input else self.o_node.width
return x, y
Si l'on supprime un node, dans l'état actuel du code, on obtient ceci :
CtrlScene.delete_items()
, qui pour l'instant ne traite que la suppression des nodes.CtrlScene.delete_items()
devient :
def delete_items(self):
""" Les items sont les nodes, les liens et autres widgets de la scène. """
selected = self.o_grscene.selectedItems()
l_grnodes, s_gredges = list(), set() # Le set permet d'éviter les doublons.
for sel in selected:
if sel.__class__.__name__ == 'UiNode':
l_grnodes.append(sel)
elif sel.__class__.__name__ == 'UiEdge':
s_gredges.add(sel)
""" On ajoute à s_gredges les edges accrochés aux nodes sélectionnés. """
for grnode in l_grnodes:
for o_socket in grnode.o_node.lo_sockets_in + grnode.o_node.lo_sockets_out:
if o_socket.__class__.__name__ == 'CtrlSocket':
s_gredges.update(o_socket.get_gredges())
""" On fait les comptes. """
nb_nodes = len(l_grnodes)
nb_edges = len(s_gredges)
if nb_nodes + nb_edges == 0:
return
""" Construction du message. """
s_nodes = f'{nb_nodes} node' + ('s' if nb_nodes > 1 else '') if nb_nodes > 0 else ''
s_edges = f'{nb_edges} edge' + ('s' if nb_edges > 1 else '') if nb_edges > 0 else ''
s_and = '' if nb_nodes * nb_edges == 0 else ' et '
msg = f"Vous êtes sur le point de supprimer {s_nodes}{s_and}{s_edges}.\nAcceptez-vous ?"
if ut.msg_yesno('Confirmation', msg, self):
""" Suppression des edges (et mise à jour du super-dictionnaire pkl). """
for gredge in s_gredges:
self.o_grscene.removeItem(gredge)
self.save_edges()
""" Suppression des nodes (et mise à jour du super-dictionnaire pkl). """
for o_grnode in l_grnodes:
self.remove_node(o_grnode)
""" Enregistrement pkl. """
self.o_pkl.backup()
""" Affichage des paramètres de la scène. """
self.show_params()
get_gredges()
qui n'existe pas. On la créé.CtrlSocket.get_gredges()
:
def get_gredges(self):
""" Retourne un set contenant tous les gredges connectés à ce socket.
Remarque :
- Un socket d'entrée peut être connecté à 0 ou 1 edge.
- Un socket de sortie peut être connecté à 0, 1 ou plusieurs edges.
"""
s_gredges = set()
# à coder ... (servez-vous du TDD).
return s_gredges
save_edges()
qui n'existe pas. On la crée.CtrlScene.save_edges()
:
def save_edges(self):
""" Mise à jour de la clé 'edges' du super-dictionnaire pkl. """
# à coder ... (servez-vous du TDD).
save_edges()
, d'utiliser une méthode que nous nommerons get_tid()
, mais qui n'existe pas. On la crée.CtrlEdge.get_tid()
:
def get_tid(self):
""" Renvoie un tuple de tuples. Exemple : (90, 0), (91, 4) """
# à coder ... (servez-vous du TDD).
UiSocket
: surcharge des méthodes hoverEnterEvent()
et hoverLeaveEvent()
.UiSocket.hoverEnterEvent()
et UiSocket.hoverLeaveEvent()
:
def hoverEnterEvent(self, event):
""" Entrée en survol du edge. Le dessin triangulaire du socket augmente de taille et change de couleur. """
self.b_hover = True
if not self.o_socket.b_input:
self.triangle = self.get_triangle()
self.update() # Rafraîchit l'affichage
def hoverLeaveEvent(self, event):
""" Sortie du survol. Le dessin triangulaire du socket retrouve son état normal. """
self.b_hover = False
if not self.o_socket.b_input:
self.triangle = self.get_triangle()
self.update()
b_hover
doit être déclaré dans __init__()
, avant l'appel à get_triangle()
.UiSocket.__init__()
:
def __init__(self, o_socket):
super().__init__(parent=o_socket.o_node.o_grnode) # Rattaché au node.
self.o_socket = o_socket
self.mi_side = 6. # Moitié du côté.
self.mi_thick = .7 # Moitié de l'épaisseur de la bordure.
self.b_hover = False
self.label_item = None
self.triangle = self.get_triangle()
self.init_ui()
get_triangle()
doit être modifiée pour appliquer sa nouvelle taille au socket.UiSocket.get_triangle()
:
def get_triangle(self):
""" triangle = Polygone à 3 côtés. Retourne un QPolygonF. """
mi_side = 1.5 * self.mi_side if self.b_hover else self.mi_side
p1, p2, p3 = (-mi_side, -mi_side), (-mi_side, mi_side), (mi_side, 0)
return QPolygonF([QPointF(*p1), QPointF(*p2), QPointF(*p3)])
state_brush()
doit également être modifiée pour appliquer sa nouvelle couleur de fond au socket.UiSocket.state_brush()
:
@property
def state_brush(self):
""" Couleur de fond : jaune (on), gris (off), ou blanc (survol). """
color = '#ff0' if self.o_socket.o_node.b_on else self.o_socket.o_node.d_display['col_brush']['off']
color = '#fff' if self.b_hover else color
return pg.mkBrush(color)
Socket de sortie agrandi. Couleur de fond : blanche.
CtrlScene.show_edges()
.o_socket_out
et o_socket_in
.o_socket_out
est connu.o_socket_out
.o_socket_in
.o_socket_out
.start_edge()
lorsqu'on appuie sur le bouton gauche de la souris.UiView.mousePressEvent()
comme ceci :
def mousePressEvent(self, ev):
if ev.button() in [Qt.RightButton, Qt.MiddleButton]:
""" Molette ou bouton droit appuyé. """
self.right_button_pressed(ev)
if ev.button() == Qt.LeftButton:
self.start_edge(ev)
super().mousePressEvent(ev)
start_edge()
est appelée, oui mais ... celle-ci n'existe pas. Créons la.UiView.start_edge()
:
def start_edge(self, ev):
""" Récupération, dans la scène, de l'item sous la souris. """
gr_item = self.itemAt(ev.pos())
if gr_item.__class__.__name__ == 'UiSocket' and not gr_item.o_socket.b_input:
""" Il s'agit bien d'un socket de sortie. """
self.o_mid_edge = CtrlEdge(self.o_scene, gr_item.o_socket, self.mapToScene(ev.pos()))
(Importer CtrlEdge). Si l'item sous la souris n'est pas un socket de sortie, on ne fait rien.
o_mid_edge
n'existe pas. Créons-le.UiView.__init__()
:
""" Construction d'un edge. """
self.o_mid_edge = None # Instance de CtrlEdge en construction.
CtrlEdge
a besoin de 4 arguments, ici, dans start_edge()
, on n'en passe que 3.sockets (out et in)
. Ici on a un o_socket_out
et un QPointF
.CtrlScene.show_edges()
:
""" 7) Ici tout est ok : les 2 nodes existent, les sockets aussi et ce ne sont pas des séparateurs. """
CtrlEdge(self, o_socket_out, o_socket_in)
Suppression d'un argument dans l'instanciation de CtrlEdge
.
CtrlEdge.__init__()
:
def __init__(self, o_scene, o_socket_out, o_socket_in):
"""
:param o_scene: objet CtrlScene
:param o_socket_out: objet CtrlSocket
:param o_socket_in: objet CtrlSocket ou QPointF
"""
self.o_scene = o_scene
self.o_gredge = None
self.o_socket_out = o_socket_out
self.o_socket_in = o_socket_in
self.setup()
Les attributs t_id
, o_node_from
, o_node_to
ont été supprimés, car ils peuvent être déduits.
t_id
a été supprimé. Oui mais... on en a besoin dans UiEdge.init_ui()
pour afficher le tooltip.UiEdge.init_ui()
:
def init_ui(self):
self.setZValue(-1) # Sous le socket.
if self.o_edge.o_socket_in.__class__.__name__ == 'CtrlSocket':
self.setToolTip(f'{self.o_edge.o_socket_out.t_id} → {self.o_edge.o_socket_in.t_id}')
""" Flags. """
self.setAcceptHoverEvents(True)
self.setFlag(QGraphicsPathItem.ItemIsSelectable)
CtrlEdge.end_points()
:
@property
def end_points(self):
""" - Renvoie un tuple : les points de départ et d'arrivée.
- Les coordonnées d'un socket sont relatives à celles de son node.
- Il faut donc les additionner pour obtenir les coordonnées absolues."""
""" Point de départ. """
pos_node_from = self.o_socket_out.o_node.o_grnode.pos() # type QPointF
pos_socket_out = self.o_socket_out.o_grsocket.pos() # type QPointF
""" Point d'arrivée. """
if self.o_socket_in.__class__.__name__ == 'QPointF':
""" Edge en construction : pos_to = position de la souris. """
pos_to = self.o_socket_in # type QPointF
else:
pos_node_to = self.o_socket_in.o_node.o_grnode.pos() # type QPointF
pos_socket_in = self.o_socket_in.o_grsocket.pos() # type QPointF
pos_to = pos_node_to + pos_socket_in
""" Les QPointF peuvent s'additionner. """
return pos_node_from + pos_socket_out, pos_to # Tuple.
self.o_socket_in
: Attention, malgré son nom qui prête à confusion, cet attribut, qui normalement est un o_socket
, est un QPointF
pendant la phase de construction du node !Il apparaît donc un mi-edge, dont les points de départ et d'arrivée sonr pratiquement confondus.
start_edge()
.UiView.mouseReleaseEvent()
:
def mouseReleaseEvent(self, ev):
if ev.button() in [Qt.RightButton, Qt.MiddleButton]:
""" Molette ou bouton droit relâché. """
self.right_button_released(ev)
elif ev.button() == Qt.LeftButton:
""" Affichage des paramètres retardé pour laisser le temps à l'UI de s'actualiser. """
self.dt.delay(self.o_scene.show_params)
self.stop_edge(ev)
super().mouseReleaseEvent(ev)
stop_edge()
qui n'existe pas. Créons-la.UiView.stop_edge()
:
def stop_edge(self, ev):
if self.o_mid_edge.__class__.__name__ == 'CtrlEdge':
""" Reset. """
self.o_scene.o_grscene.removeItem(self.o_mid_edge.o_gredge)
self.o_mid_edge = None
Reset = Suppression de l'affichage + destruction de l'objet o_mid_edge.
mouseMoveEvent()
. Nous allons la surcharger.UiView.mouseMoveEvent()
:
def mouseMoveEvent(self, ev):
if self.o_mid_edge.__class__.__name__ == 'CtrlEdge':
""" Edge en cours de construction. """
self.o_mid_edge.o_socket_in = self.mapToScene(ev.pos()) # Position de la souris
self.o_mid_edge.o_gredge.update() # Rafraîchissement de l'affichage.
super().mouseMoveEvent(ev)
CtrlEdge.o_socket_in
, qui, rappeleons-le n'est pas un socket mais un QPointF
.
UiEdge.state_pen()
:
@property
def state_pen(self):
""" Couleur, épaisseur, style : Choix en fonction d'événements. """
b_building = self.o_edge.o_socket_in.__class__.__name__ == 'QPointF' # Edge en construction.
color = '#faa' if self.isSelected() else '#aaf'
color = '#ffa' if self.b_hover else color
color = Qt.white if b_building else color
line_type = Qt.DashLine if b_building else Qt.SolidLine
line_width = 1 if b_building else 2
return QPen(QColor(color), line_width, line_type)
Importer Qt, modifier les valeur à votre convenance.
Les valeurs choisies sont : blanc, pointillés, épaisseur 1.
mouseMoveEvent()
.UiView.mouseMoveEvent()
:
def mouseMoveEvent(self, ev):
if self.o_mid_edge.__class__.__name__ == 'CtrlEdge':
if self.is_dropable(ev):
self.normal_size_sockets() # Retour à l'état normal de tous les sockets.
""" Changement d'état du socket survolé : couleur et taille. """
gr_item = self.itemAt(ev.pos())
gr_item.b_hover = True
gr_item.triangle = gr_item.get_triangle()
gr_item.update()
""" Edge en cours de construction. """
self.o_mid_edge.o_socket_in = self.mapToScene(ev.pos()) # Position de la souris
self.o_mid_edge.o_gredge.update() # Rafraîchissement de l'affichage.
super().mouseMoveEvent(ev)
is_dropable()
qui n'existe pas.Créons-la.UiView.is_dropable()
:
def is_dropable(self, ev):
gr_item = self.itemAt(ev.pos())
if gr_item.__class__.__name__ == 'UiSocket':
""" ... L'élément survolé est un socket ... """
if gr_item.o_socket.b_input:
""" ... ET c'est un socket d'entrée ... """
if gr_item.o_socket.o_node != self.o_mid_edge.o_socket_out.o_node:
""" ... ET les nodes de départ et d'arrivée sont différents. """
return True
return False
normal_size_sockets()
qui n'existe pas. Créons-la.UiView.normal_size_sockets()
:
def normal_size_sockets(self):
""" Retour à l'état normal de tous les sockets d'entrée. """
l_grsockets_in = self.o_scene.get_grsockets()[0]
for o_grsocket in l_grsockets_in:
o_grsocket.b_hover = False
o_grsocket.triangle = o_grsocket.get_triangle()
o_grsocket.update()
get_grsockets()
qui n'existe pas. Créons-la.CtrlScene.get_grsockets()
:
def get_grsockets(self):
""" Renvoie 2 listes : l_grsockets_in et l_grsockets_out. """
l_grsockets_in, l_grsockets_out = list(), list()
# à coder ... (servez-vous du TDD).
return l_grsockets_in, l_grsockets_out
Drag en cours : l'entrée1 accepte le drop.
stop_edge()
créée précédemment et appelée lors du relâchement du bouton de la souris.UiView.stop_edge()
:
def stop_edge(self, ev):
if self.o_mid_edge.__class__.__name__ == 'CtrlEdge':
if self.is_dropable(ev):
gr_item = self.itemAt(ev.pos())
CtrlEdge(self.o_scene, self.o_mid_edge.o_socket_out, gr_item.o_socket)
""" Reset. """
self.normal_size_sockets()
self.o_scene.o_grscene.removeItem(self.o_mid_edge.o_gredge) # Effacement.
self.o_mid_edge = None # Destruction.
stop_edge()
doit donc être modifiée pour effectuer cette tâche.pkl
→ la partie "backup" effectue cette tâche.UiView.stop_edge()
:
def stop_edge(self, ev):
if self.o_mid_edge.__class__.__name__ == 'CtrlEdge':
""" Reset. """
self.normal_size_sockets() # Rétablit les tailles normales aux sockets.
self.o_scene.o_grscene.removeItem(self.o_mid_edge.o_gredge) # Effacement du mi_edge.
""" Drop = fin du drag. """
if self.is_dropable(ev):
gr_item = self.itemAt(ev.pos()) # UiSocket
""" Suppression d'un éventuel edge connecté à ce socket. """
for o_gredge in gr_item.o_socket.get_gredges():
self.o_scene.o_grscene.removeItem(o_gredge) # Effacement.
""" Création d'un edge complet. """
CtrlEdge(self.o_scene, self.o_mid_edge.o_socket_out, gr_item.o_socket)
""" Backup. """
self.o_scene.save_edges()
self.o_scene.o_pkl.backup()
""" Destruction de l'objet. """
self.o_mid_edge = None
Vérification
/tests/test_edges.py
Retirer le mode DEBUG
.
from PyQt5.QtWidgets import QApplication
from PyQt5.QtCore import QTimer
from pc.main import UiMain
import pytest
import os
import sys
class TestEdges:
g_appli = QApplication(sys.argv)
tempo = QTimer()
tempo.setSingleShot(True)
@pytest.fixture(autouse=True) # scope=session (un seul appel), autouse=True (un appel par test)
def setup(self):
bk_test = os.path.abspath(f"{os.path.dirname(__file__)}/../backups/tests")
os.makedirs(bk_test, exist_ok=True) # Création dossier.
o_main = self.open_window()
o_main.resize(1200, 600)
o_main.open_graph('tests')
o_scene = None
for i in range(o_main.tabGraphs.count()):
if o_main.tabGraphs.widget(i).graph_name == 'tests':
o_scene = o_main.tabGraphs.widget(i)
break
assert o_scene is not None
assert o_scene.__class__.__name__ == 'CtrlScene'
return {
'main': o_main,
'scene': o_scene
}
@staticmethod
@pytest.fixture(scope="session", autouse=True)
def cleanup(request):
def remove_files():
""" Appel final pour effacer les fichiers temporaires créés par les tests. """
tests_folder = os.path.dirname(__file__)
graph_folder = tests_folder.replace('tests', f'backups{os.sep}tests')
files = [f'{tests_folder}{os.sep}params.conf']
for file in files:
if os.path.isfile(file):
os.remove(file)
if os.path.isdir(graph_folder):
os.rmdir(graph_folder)
request.addfinalizer(remove_files)
@staticmethod
def open_window():
win = UiMain()
win.show()
return win
# noinspection PyUnresolvedReferences
def connect(self, function, delay):
try:
self.tempo.timeout.disconnect()
except TypeError:
pass
self.tempo.timeout.connect(function)
self.tempo.start(delay)
@staticmethod
def build_graph(setup, l_nodes=None, s_edges=None):
o_main = setup['main']
o_scene = setup['scene']
o_scene.o_pkl.od_pkl.clear()
""" Valeurs par défaut. """
if l_nodes is None:
l_nodes = [
(90, 'nodes.indicateurs.macd', (-256, -32)),
(91, 'nodes.operateurs.union', (148, -96)),
]
if s_edges is None:
s_edges = {
((90, 0), (91, 4)),
((90, 2), (91, 2))
}
""" Dans od_pkl puis affichage. """
for node in l_nodes:
o_scene.o_pkl.od_pkl.write([f'Node{node[0]}', 'path'], node[1])
o_scene.o_pkl.od_pkl.write([f'Node{node[0]}', 'position'], node[2])
o_scene.o_pkl.od_pkl.write('edges', s_edges)
o_scene.show_nodes()
o_scene.show_edges()
l_items = o_scene.o_grscene.items()
l_grnodes, l_grsockets, l_gredges = list(), list(), list()
for o_gritem in l_items:
if o_gritem.__class__.__name__ == 'UiNode':
l_grnodes.append(o_gritem)
elif o_gritem.__class__.__name__ == 'UiSocket':
l_grsockets.append(o_gritem)
elif o_gritem.__class__.__name__ == 'UiEdge':
l_gredges.append(o_gritem)
return o_main, o_scene, l_grnodes, l_grsockets, l_gredges
# ***************************************** TESTS *****************************************
# @pytest.mark.skip
def test_tid(self, setup):
o_main, o_scene, lo_grnodes, lo_grsockets, lo_gredges = self.build_graph(setup)
for o_gredge in lo_gredges:
assert o_gredge.o_edge.get_tid() == (o_gredge.o_edge.o_socket_out.t_id, o_gredge.o_edge.o_socket_in.t_id)
self.connect(o_main.close, 1000)
self.g_appli.exec()
# @pytest.mark.skip
def test_save_edge(self, setup):
o_main, o_scene, lo_grnodes, lo_grsockets, lo_gredges = self.build_graph(setup)
o_scene.o_grscene.removeItem(lo_gredges[0])
o_scene.save_edges()
assert o_scene.o_pkl.od_pkl.read('edges') == set(o_gredge.o_edge.get_tid() for o_gredge in lo_gredges[1:])
self.connect(o_main.close, 1000)
self.g_appli.exec()
# @pytest.mark.skip
def test_get_gredges(self, setup):
s_edges = {
((90, 0), (91, 4)),
((90, 2), (91, 0)),
((90, 2), (91, 1)),
((90, 2), (91, 2)),
}
o_main, o_scene, lo_grnodes, lo_grsockets, lo_gredges = self.build_graph(setup, s_edges=s_edges)
for o_grsocket in lo_grsockets:
s_gredges = set()
for o_gredge in o_grsocket.o_socket.o_node.o_scene.o_grscene.items():
if o_gredge.__class__.__name__ == 'UiEdge':
""" Choix du socket accroché à la bonne extrémité du edge. """
o_edge = o_gredge.o_edge
o_socket = o_edge.o_socket_in if o_grsocket.o_socket.b_input else o_edge.o_socket_out
if o_socket == o_grsocket.o_socket:
s_gredges.add(o_gredge)
assert o_grsocket.o_socket.get_gredges() == s_gredges # Méthode testée : CtrlSocket.get_gredges()
self.connect(o_main.close, 1000)
self.g_appli.exec()
@pytest.mark.skip
def test_get_grsockets(self, setup):
o_main, o_scene, lo_grnodes, lo_grsockets, lo_gredges = self.build_graph(setup)
l_grsockets_in, l_grsockets_out = list(), list()
for o_grsocket in lo_grsockets:
if o_grsocket.o_socket.b_input:
l_grsockets_in.append(o_grsocket)
else:
l_grsockets_out.append(o_grsocket)
t_grsockets = o_scene.get_grsockets()
assert t_grsockets[0] == l_grsockets_in
assert t_grsockets[1] == l_grsockets_out
self.connect(o_main.close, 1000)
self.g_appli.exec()
Snippets
Bonjour les codeurs !