Le poste de contrôle (PC) / Les graphes /
Edges : 1 - Interconnexions des nodes.

Permet la mise en cascade de calculs


Avant-propos

Correction d'un bug :



Bon coding et bon courage !

Correction d'une erreur conceptuelle :

Le dessin d'un node, confié à la méthode UiNode.paint() , est mal fait :

Le problème est corrigé, en mettant 3 calques au lieu de 2.

UiNode.paint() :

    def paint(self, painter, QStyleOptionGraphicsItem, widget=None):
        """ Le dessin du node est une superposition de plusieurs calques. """
        """ Calque 0 (au fond) : Contenu sans bordure. """
        painter.setPen(Qt.NoPen)
        painter.setBrush(self.state_brush)
        painter.drawPath(self.path_outline.simplified())

        """ Calque 1 : Titre sans bordure. """
        painter.setBrush(pg.mkBrush(color='#bbbbbbaa'))
        painter.drawPath(self.path_title.simplified())

        """ Calque 2 (au dessus) : Bordure totale seule, sans couleur de fond. """
        painter.setPen(self.state_pen)
        painter.setBrush(Qt.NoBrush)
        painter.drawPath(self.path_outline.simplified())

Optionnel : Ajout de l'affichage du type de node :
  1. Ajouter l'attribut type à CtrlNode.
    Dans CtrlNode.__init__(), ajouter cette ligne :
            self.type = ''
    

    Cet attribut doit être surchargé par toutes les classes dérivées.

  2. Dans UiNode :
    1. Ajouter l'attribut type_item.
      Dans UiNode.__init__(), ajouter cette ligne :
              self.type_item = QGraphicsTextItem(self)
      

       

    2. modifier la méthode 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, 10)
      
              """ 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)
      
  3. Dans chaque classe dérivée de CtrlNode, déclarer et affecter self.type.
    • Exemple pour signaux : dans Node.__init__() → self.type = 'Signaux'


Description

Plan du tuto :
  1. Mode DEBUG : 2 nodes, 2 edges.
  2. Affichage provisoire des 2 edges.
  3. Accrochage des edges aux sockets.
  4. Passer les edges au 2ème plan, sous les nodes.
  5. Tooltip et couleurs de survol et de sélection.
  6. Changement de forme : courbe de Bézier.

1 - Mode DEBUG : Déclaration du super-dictionnaire avec la description de 2 nodes et de 2 edges.

Retour au plan.

Node90: ---------------------------------------------------- <class 'dict'>
    path: nodes.indicateurs.macd --------------------------- <class 'str'>
    position: (-144, -32) ---------------------------------- <class 'tuple'>
Node91: ---------------------------------------------------- <class 'dict'>
    path: nodes.operateurs.union --------------------------- <class 'str'>
    position: (48, -96) ------------------------------------ <class 'tuple'>
edges: {((90, 0), (91, 4)), ((90, 2), (91, 2))} ------------ <class 'set'>
On aurait pu utiliser une liste au lieu d'un set, mais le set présente l'avantage d'éviter les doublons.

2 - Affichage sommaire des edges :

Retour au plan.

Comme pour les nodes et les sockets, les edges nécessitent 2 classes : CtrlEdge et UiEdge, respectivement dans les fichiers ctrl_edge.py et ui_edge.py.


Code minimum pour afficher les edges :


A faire :

/pc/ui_edge.py :

from PyQt5.QtWidgets import QGraphicsPathItem
from PyQt5.QtGui import QPainterPath, QPen, QColor


class UiEdge(QGraphicsPathItem):
    def __init__(self, o_edge):
        super().__init__()
        self.o_edge = o_edge

    def paint(self, painter, QStyleOptionGraphicsItem, widget=None):
        t_ends = self.o_edge.end_points
        point_from, point_to = t_ends[0], t_ends[1]
        path = QPainterPath(point_from)         # Point de départ.
        path.lineTo(point_to)                   # Point d'arrivée.
        self.setPath(path)

        painter.setPen(QPen(QColor('#aaf'), 3))
        painter.drawPath(self.path())

/pc/ctrl_edge.py :

from pc.ui_edge import UiEdge


class CtrlEdge:
    def __init__(self, o_scene):
        """
        :param o_scene: objet CtrlScene
        """
        self.o_scene = o_scene
        self.o_gredge = None
        self.end_points = self.two_points()     # A supprimer
        self.setup()

    def setup(self):
        self.o_gredge = UiEdge(self)                    # Dessin de l'edge dans l'UI.
        self.o_scene.o_grscene.addItem(self.o_gredge)   # Ajout de l'edge dans la scène.

    @staticmethod
    def two_points():
        """ Code provisoire. Retourne des valeurs aléatoires. """
        from PyQt5.QtCore import QPointF
        import random
        p_from = random.randint(-100, 100), random.randint(-100, 100)
        p_to = random.randint(-100, 100), random.randint(-100, 100)
        return QPointF(*p_from), QPointF(*p_to)

Créer la méthode sommaire show_edges().
CtrlScene.show_edges() :

    def show_edges(self):
        """ Code provisoire permettant la mise au point. """
        edges = self.o_pkl.od_pkl.read('edges', {})     # Lecture de la clé 'edges' du pkl.
        for _ in edges:
            CtrlEdge(self)

(Décommenter son appel dans le setup et importer CtrlEdge.)

A chaque lancement les segments sont différents.


3 - Accrochage des liens (edges) aux ports (sockets).

Retour au plan.

3.1 - A faire dans CtrlNode :
3.2 - A faire dans CtrlScene :

Code complet de la méthode show_edges()

3.3 - A faire dans CtrlEdge :

En déplaçant les nodes, les edges restent accrochés aux sockets.


4 - Afficher les edges sous les nodes :

Retour au plan.

 


5 - Tooltip et couleurs (survol, sélection) :

Retour au plan.

 

Adaptez les couleurs et l'épaisseur à votre convenance.


6 - Courbes de Bézier :

Retour au plan.

 

Le dessin de la courbe de Bézier nécessite les coordonnées x et y de 4 points (P0 à P3).
 


Snippets

Essayez de résoudre cette fonctionnalité par vous-même.
Consultez les réponses (snippets) seulement si vous n'avez pas trop de temps.

Bonjour les codeurs !