Nécessaires pour l'interconnexion des nodes
Avant-propos
gr_
par gr
ctrl_scene.py > DEBUG
CtrlScene.no_compile()
:
def no_compile(self): # ***** DEBUG ***** DEBUG ***** DEBUG ***** DEBUG ***** DEBUG *****
""" - Les nodes sont créés par compilation dynamique, pour éviter un code conditionnel interminable.
- Mais en cas d'erreur, on obtient le message laconique : 'Erreur de compilation.'
|_ Aucune indication concernant cette erreur n'est affichée.
- Pour pallier à cet inconvénient et pour pouvoir debugger :
|_ Cette méthode crée un ou plusieurs nodes SANS la compilation dynamique.
|_ Elle est une alternative à la méthode show_nodes() lorsque le flag DEBUG est levé.
(Constante DEBUG au début du fichier, code appelant : méthode setup(), à la fin).
|_ En cas d'erreur, celle-ci sera affichée normalement et pourra être corrigée.
"""
from nodes.generateurs.signaux import Node
Node(self, 'Node98', (-140, 0))
from nodes.indicateurs.macd import Node
Node(self, 'Node99', (10, 0))
setup()
: Si DEBUG
→ no_compile()
sinon show_nodes()
:
""" Affichage des nodes mémorisés dans les paramètres. """
self.no_compile() if DEBUG else self.show_nodes()
setup()
de CtrlNode
par celui-ci.CtrlNode.setup()
:
def setup(self, child_file):
""" Code appelant : classe dérivée, on connait donc ses attributs, notamment width et height. """
""" Paramètres. """
self.o_params = Parameters(self)
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))
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.
Amélioration du rendu graphique, lissage des traits.
UiView.init_ui()
ajouter ce flag :
""" Flags. """
self.setRenderHints(QPainter.Antialiasing | QPainter.HighQualityAntialiasing |
QPainter.TextAntialiasing | QPainter.SmoothPixmapTransform)
Description
/pc/ui_socket.py
:
from PyQt5.QtWidgets import QGraphicsItem
from PyQt5.QtCore import QRectF
import pyqtgraph as pg
class UiSocket(QGraphicsItem):
def __init__(self, o_socket):
super().__init__(parent=o_socket.o_node.o_grnode) # Rattaché au node.
self.o_socket = o_socket
def boundingRect(self):
""" Zône de cliquage carrée. """
return QRectF(0, 40, 10, 10)
def paint(self, painter, QStyleOptionGraphicsItem, widget=None):
""" Pour activer cette méthode, l'instance doit être reliée à son parent (o_grnode), dans le super(). """
painter.setBrush(pg.mkBrush(color='#ffff00'))
painter.setPen(pg.mkPen(color='#ff0000', width=2.))
painter.drawEllipse(-4, 40, 10, 10)
/pc/ctrl_socket.py
:
from ui_socket import UiSocket
class CtrlSocket:
def __init__(self, o_node):
self.o_node = o_node
self.o_grsocket = None
self.setup()
def setup(self):
self.o_grsocket = UiSocket(self)
CtrlNode.setup()
:
""" Sockets. """
CtrlSocket(self)
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.triangle = self.get_triangle()
self.init_ui()
UiSocket.get_triangle()
:
def get_triangle(self):
""" triangle = Polygone à 3 côtés. Retourne un QPolygonF. """
# à coder ...
Pas de TDD, contrôle visuel.
UiSocket.init_ui()
:
def init_ui(self):
""" Position du socket par rapport à son parent 'o_grnode'. """
self.setPos(0, 40) # Valeurs provisoires.
UiSocket.boundingRect()
:
def boundingRect(self):
""" Zône de cliquage carrée. Englobe le socket et sa bordure. """
return QRectF(-self.mi_side - self.mi_thick / 2,
-self.mi_side - self.mi_thick / 2,
2 * self.mi_side + self.mi_thick / 2,
2 * self.mi_side + self.mi_thick / 2)
UiSocket.paint()
:
def paint(self, painter, QStyleOptionGraphicsItem, widget=None):
""" Pour activer cette méthode, l'instance doit être reliée à son parent (o_grnode), dans le super(). """
painter.setBrush(pg.mkBrush(color='#ffff00')) # Fond provisoirement jaune.
painter.setPen(QPen(Qt.black, 2*self.mi_thick, join=Qt.RoundJoin)) # Crayon noir, coins arrondis.
painter.drawPolygon(self.triangle)
CtrlNode.__init__()
, à la fin :
""" Sockets. """
self.ld_inputs = list() # Liste de dictionnaires.
self.ld_outputs = list() # Liste de dictionnaires.
self.lo_sockets_in = list() # Liste d'objets 'sockets in' instanciés.
self.lo_sockets_out = list() # Liste d'objets 'sockets out' instanciés.
macd.py > Node.setup()
:
def setup(self, child_file=__file__):
""" Listes des dictionnaires de base attribués aux sockets. """
self.ld_inputs = [{ # Liste de 1 dictionnaire.
'label': 'Entrée',
'label_pos': (6, -10)
}]
self.ld_outputs = [ # Liste de 2 dictionnaires.
{
'label': 'Sortie',
'label_pos': (-38, -10)
},
'sep', # Séparateur.
{
'label': 'Oscillateur',
'label_pos': (-59, -10)
}
]
super().setup(child_file)
CtrlNode.setup()
, à la fin :
""" 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.
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.
self.id
est une propriété. On l'ajoute.
CtrlNode.id()
:
@property
def id(self):
""" Ex : 'Node 14' renvoie 14. """
return int(self.s_id[4:])
De plus, on remarque le dictionnaire ajouté en argument à la classe CtrlSocket
. Il faut donc l'ajouter dans son __init__()
.
CtrlSocket.__init__()
devient :
def __init__(self, o_node, d_datas):
self.o_node = o_node
self.d_datas = d_datas # clés : label, label_pos, id, is_input
self.t_id = self.d_datas['id'] # (c'est une entrée: booléen, num_node: int, num_socket: int) <- tuple.
self.b_input = self.t_id[0]
self.o_grsocket = None
self.setup()
UiSocket.init_ui()
:
def init_ui(self):
""" Position du socket par rapport à son parent 'o_grnode'. """
self.setPos(*self.o_socket.position)
Oui mais ... setPos
a besoin du tuple o_socket.position
, qui n'existe pas. C'est une @property. Créons-la.
CtrlSocket.position()
:
@property
def position(self):
""" Les positions sont optimisées pour une grille de maille 16. """
# à coder ...
return x, y
Par convention, les sockets d'entrée seront sur le bord gauche, ceux de sortie sur la bord droit.
Pour l'instant, 'Signaux' n'a pas de dictionnaires de sockets ⇒ Pas de sockets.
Pas de TDD, contrôle visuel.
CtrlNode
.CtrlNode.setup()
, vers le début :
""" Hauteur automatique du node : elle dépend du nombre de sockets en entrée ou en sortie (le plus grand). """
# à coder ...
Pas de TDD : contrôle visuel.
UiSocket.init_ui()
:
def init_ui(self):
""" Position du socket par rapport à son parent 'o_grnode'. """
self.setPos(*self.o_socket.position)
""" Label. """
self.label_item = QGraphicsTextItem(self)
self.label_item.setDefaultTextColor(pg.mkColor('#ccc')) # Gris clair
self.label_item.setFont(QFont('Arial', 7))
self.label_item.setPlainText(self.o_socket.label)
""" Position du label par rapport au socket. """
self.label_item.setPos(*self.o_socket.t_label_pos)
Ajouter la déclaration self.label_item = None
dans __init__()
.
o_socket
, n'existent pas (label
et t_label_pos
). Créons-les.CtrlSocket.__init__()
:
def __init__(self, o_node, d_datas):
self.o_node = o_node
self.d_datas = d_datas # clés : label, label_pos, id, is_input
self.t_id = self.d_datas['id'] # (c'est une entrée: booléen, num_node: int, num_socket: int) <- tuple.
self.b_input = self.t_id[0]
self.label = self.d_datas['label']
self.t_label_pos = self.d_datas['label_pos']
self.o_grsocket = None
self.setup()
Il y a 2 choses à faire pour réaliser celà :
UiSocket
.show_params()
de CtrlScene
concerne tous les widgets de la scène.UiSocket.init_ui()
, à la fin :
""" Flags. """
self.setFlag(QGraphicsItem.ItemIsSelectable)
self.setAcceptHoverEvents(True)
CtrlScene.show_params()
:
def show_params(self):
"""
Si aucun élément du graphe n'est sélectionné, affiche les paramètres de la vue.
Si un seul élément est sélectionné :
- Si node : affiche ses paramètres.
- Si socket : affiche les paramètres de son node.
Si plusieurs éléménts sont sélectionnés, affiche une image 'multi-sélection'.
:return: NA
"""
l_selected = self.o_grscene.selectedItems()
nb_selected = len(l_selected)
if nb_selected == 0:
self.o_params.show_params()
elif nb_selected == 1:
if l_selected[0].__class__.__name__ == 'UiNode':
""" l_selected[0] est un o_grnode. """
o_node = l_selected[0].o_node
elif l_selected[0].__class__.__name__ == 'UiSocket':
""" l_selected[0] est un o_grsocket. """
o_node = l_selected[0].o_socket.o_node
o_node.o_grnode.setSelected(True) # Sélection du node.
else:
return
o_node.o_params.show_params()
else: # > 1
self.o_params.show_params(b_multi=True)
UiSocket.state_brush()
:
@property
def state_brush(self):
""" Couleur de fond : jaune ou gris. """
color = '#ff0' if self.o_socket.o_node.b_on else self.o_socket.o_node.d_display['col_brush']['off']
return pg.mkBrush(color)
state_brush
, dans UiSocket.paint()
:
def paint(self, painter, QStyleOptionGraphicsItem, widget=None):
""" Pour activer cette méthode, l'instance doit être reliée à son parent (o_grnode), dans le super(). """
painter.setPen(QPen(Qt.black, 2*self.mi_thick, join=Qt.RoundJoin)) # Crayon noir.
painter.setBrush(self.state_brush) # Jaune ou gris.
painter.drawPolygon(self.triangle)
Évidemment, vous choisirez une couleur à votre convenance à la place du jaune.
DEBUG = False
Pour chaque type de node, insérer les dictionnaires dans la méthode Node.setup()
, avant super().setup(child_file)
.
Modifiez les libellés, les positions, les séparateurs à votre convenance.
afficheurs.plot.py
:
self.ld_inputs = [{ # Liste de 1 dictionnaire.
'label': 'Entrée',
'label_pos': (6, -10)
}]
afficheurs.plots.py
:
self.ld_inputs = [ # Liste de 4 dictionnaires.
{
'label': 'Entrée 0',
'label_pos': (6, -10)
},
{
'label': 'Entrée 1',
'label_pos': (6, -10)
},
{
'label': 'Entrée 2',
'label_pos': (6, -10)
},
{
'label': 'Entrée 3',
'label_pos': (6, -10)
}
]
generateurs.aleatoire.py
:
self.ld_outputs = [{
'label': 'Sortie',
'label_pos': (-38, -10)
}]
generateurs.histos.py
:
self.ld_outputs = [{
'label': 'Sortie',
'label_pos': (-38, -10)
}]
generateurs.signaux.py
:
self.ld_outputs = [{
'label': 'Sortie',
'label_pos': (-38, -10)
}]
indicateurs.moy_mobiles.py
:
self.ld_inputs = [{
'label': 'Entrée',
'label_pos': (6, -10)
}]
self.ld_outputs = [{
'label': 'Sortie',
'label_pos': (-38, -10)
}]
operateurs.fft.py
:
self.ld_inputs = [{
'label': 'Entrée',
'label_pos': (6, -12)
}]
self.ld_outputs = [{
'label': 'Sortie',
'label_pos': (-42, -12)
}]
operateurs.labo.py
:
self.ld_inputs = [
{
'label': 'e0',
'label_pos': (6, -10)
},
{
'label': 'e1',
'label_pos': (6, -10)
},
{
'label': 'e2',
'label_pos': (6, -10)
},
]
self.ld_outputs = [
{
'label': 's0',
'label_pos': (-28, -10)
},
{
'label': 's1',
'label_pos': (-28, -10)
},
]
operateurs.union.py
:
self.ld_inputs = [
{
'label': 'Entrée 0',
'label_pos': (6, -10)
},
{
'label': 'Entrée 1',
'label_pos': (6, -10)
},
{
'label': 'Entrée 2',
'label_pos': (6, -10)
},
'sep',
{
'label': 'Entrée 3',
'label_pos': (6, -10)
},
]
self.ld_outputs = [
'sep',
'sep',
{
'label': 'Sortie',
'label_pos': (-38, -10)
},
]
oscillateurs.rsi.py
:
self.ld_inputs = [{
'label': 'Entrée',
'label_pos': (6, -10)
}]
self.ld_outputs = [{
'label': 'Sortie',
'label_pos': (-38, -10)
}]
Vérification
Snippets
Bonjour les codeurs !