Le poste de contrôle (PC) / Les graphes /
Edges : 3 - Couleurs, court-circuits, croisements

Customisation des liens


Avant-propos

Refactoring :


Description

Couleurs



Propagation de la couleur. La logique est la suivante :


Pour satisfaire à ces conditions, on imagine quel sera le code :

  1. Au lancement de l'application, lors des instanciations :
    1. Affecter sa couleur à chaque socket_out, dans son setup, depuis le super-dictionnaire od_pkl.
    2. Affecter sa couleur à chaque edge, dans son setup. color = o_socket_out.color
    3. Toujours dans le setup du edge, affecter sa couleur au socket_in d'arrivée. o_socket_in.color = o_edge.color
  2. Lors de la modification de couleur dans le dockable des paramètres :
    1. Affecter la couleur au socket_out concerné.
    2. Propager cette couleur à tous les edges partant de ce socket_out.
    3. Propager cette couleur à tous les socket_in d'arrivée.

Réalisation :

1.1 - Affectation de sa couleur à chaque socket_out, lors de son instanciation.

Attention : certaines classes dérivées ont cette clé 'Couleurs' dans leur méthode get_default(). Il faut la supprimer.
Vérifiez-les toutes : aleatoire, histos, signaux, macd, fft, etc.

Par exemple, signaux.py > Node.get_default() devient :

    def get_default(self):
        d_updated = super().get_default().copy()
        d_updated[self.main_key].update({
            'Sinus': True,
            'Cosinus': False,
            'Carré': False,
            'Triangle': False,
            'Dent de scie m': False,
            'Dent de scie d': False,
            'Amplitude': 20,
            'Points par période': 100,
        })
        return d_updated

 


 


1.2 et 1.3 - Affectation de la couleur à chaque edge, lors de son instanciation. Celui-ci prend la couleur de son socket_out de départ, puis l'impose à son socket_in d'arrivée.

 


2 - Modification des couleurs depuis le dockable des paramètres d'un node.

Faire un court-circuit


Exemple de court-circuit :

Avant                                                                                   Après

Points à respecter :


Étapes pour coder la création des court-circuits :
  1. Chaque type de node a sa liste de faisabilité, vérifiée au démarrage.
  2. Détection des secousses.
  3. Création du court-circuit.

1 - Chaque type de node a sa liste de faisabilité, vérifiée au démarrage.

2 - Détection des secousses.

Retour au plan


3 - Création du court-circuit → Fonction interne shortcircuit() :

Retour au plan


Insertion d'un node dans un edge


Avant                                                                                   Après


Croisement de deux edges


    def cross_edges(self):
        """ Croisement de 2 edges. Conditions :
            - 2 edges sélectionnés.
            - Appui sur la touche 'Tab'.
            - Les extrémités d'un edge à créer appartiennent à des nodes différents.
            - Si un cas est non autorisé (nb d'edges sélectionnés différent de 2 ou bouclage sur un même node) :
                |_ On emet un bip (importer la classe windsound). """
        # à coder ... (Servez-vous des TDD).
A faire : compléter UiView.keyReleaseEvent().

Avant                                                                                   Après
 

Non autorisé car 2 extrémités d'un edge à créer appartiendraient au même node.


Vérification

Les TDD font appel à la méthode CtrlScene.get_gredges(), implémentée au début de ce tuto, dans l'avant-propos. Vérifiez qu'elle existe !
Dans ce tuto, vous devez coder 3 méthodes :
  • CtrlNode.set_colors() : Utilisez les TDD → test_set_colors().
  • UiView.stop_edge() > fonction interne break_edge()Contrôle visuel sans TDD.
  • CtrlScene.cross_edges() : Utilisez les TDD → test_cross_edges().

Le fichier /tests/test_edges.py existe déja. Remplacez tout son contenu par celui-ci.
/tests/test_edges.py :

from PyQt5.QtWidgets import QApplication
from PyQt5.QtCore import QTimer
from pc.main import UiMain
import pytest
import random
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):
        self.remove_files()
        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
    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', f'{graph_folder}{os.sep}params.pkl']
        for file in files:
            if os.path.isfile(file):
                os.remove(file)
        if os.path.isdir(graph_folder):
            os.rmdir(graph_folder)

    @pytest.fixture(scope="session", autouse=True)
    def cleanup(self, request):
        request.addfinalizer(self.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()

    # @pytest.mark.skip
    def test_set_colors(self, setup):
        """ 2 nodes reliés par un edge. Application de la couleur au socket de départ. """
        l_nodes = [
            (90, 'nodes.indicateurs.macd', (-256, -32)),
            (91, 'nodes.operateurs.union', (148, -96)),
        ]
        s_edges = {
            ((90, 0), (91, 4)),
        }
        o_main, o_scene, lo_grnodes, lo_grsockets, lo_gredges = self.build_graph(setup, l_nodes=l_nodes, s_edges=s_edges)
        o_edge = lo_gredges[0].o_edge
        o_socket_out = o_edge.o_socket_out
        o_socket_in = o_edge.o_socket_in
        o_node_from = o_socket_out.o_node

        """ Simulation de modification du paramètre 'Couleurs' -> Socket de sortie du macd. """
        key = ['Node90', "Paramètres du node 'Node90'", 'Couleurs', o_socket_out.label]
        colors = ('#000000', '#ff0000', '#00ff00', '#0000ff', '#ffff00', '#00ffff', '#ff00ff', '#ffffff')
        color = random.choice(colors)
        o_node_from.o_params.od_real.write(key, color)
        o_node_from.o_params.set_params()

        """ Méthode testée : CtrlNode.set_colors(). """
        o_node_from.set_colors(['Couleurs', o_socket_out.label])

        """ Vérification de l'application de la couleur au socket de départ. """
        assert o_socket_out.color == color, "La couleur du socket de départ n'a pas été prise en compte."

        """ Vérification de la propagation vers l'edge. """
        assert o_edge.color == color, "La couleur du socket de départ n'a pas été propagée vers l'edge."

        """ Vérification de la propagation vers le socket d'arrivée. """
        assert o_socket_in.color == color, "La couleur de l'edge n'a pas été propagée vers le socket d'arrivée."

        """ Fin. """
        self.connect(o_main.close, 1000)
        self.g_appli.exec()

    # @pytest.mark.skip
    def test_cross_edges(self, setup):
        """ 3 nodes reliés par des edges. Croisement des edges. """
        l_nodes = [
            (0, 'nodes.operateurs.labo', (-256, -32)),
            (1, 'nodes.operateurs.labo', (-40, -160)),
            (2, 'nodes.operateurs.labo', (180, -48)),
        ]
        s_edges = {
            ((0, 0), (1, 0)),
            ((0, 1), (1, 2)),
            ((0, 0), (2, 1)),
            ((1, 1), (2, 2)),
        }
        o_main, o_scene, lo_grnodes, _, lo_gredges = self.build_graph(setup, l_nodes=l_nodes, s_edges=s_edges)

        """ Test 1 : Vérification du node de type 'Labo'. """
        o_labo = lo_grnodes[0].o_node
        msg = "Pour pouvoir effectuer ces tests, le type 'Labo' doit avoir :" \
              "\n- au moins 3 entrées," \
              "\n- au moins 2 sorties," \
              "\n- aucun séparateur de sockets."
        for o_socket in o_labo.lo_sockets_in + o_labo.lo_sockets_out:
            assert o_socket.__class__.__name__ == 'CtrlSocket', msg

        """ Test 2 : Croisement normal : 2 edges sélectionnés. """
        for o_gredge in lo_gredges:
            t_id = o_gredge.o_edge.get_tid()
            if t_id == ((0, 0), (1, 0)) or t_id == ((0, 1), (1, 2)):
                o_gredge.setSelected(True)
            else:
                o_gredge.setSelected(False)

        o_scene.cross_edges()
        l_tid = sorted([o_gredge.o_edge.get_tid() for o_gredge in o_scene.get_gredges()])
        msg = "Les edges ((0, 0), (1, 0)) et ((0, 1), (1, 2)) auraient dû être croisés, ce n'est pas le cas."
        assert l_tid == [((0, 0), (1, 2)), ((0, 0), (2, 1)), ((0, 1), (1, 0)), ((1, 1), (2, 2))], msg

        """ Test 3 : 'Dé-croisement' des 2 mêmes edges. """
        o_scene.cross_edges()
        l_tid = sorted([o_gredge.o_edge.get_tid() for o_gredge in o_scene.get_gredges()])
        msg = "Les edges ((0, 0), (1, 0)) et ((0, 1), (1, 2)) auraient dû être rétablis, ce n'est pas le cas."
        assert l_tid == [((0, 0), (1, 0)), ((0, 0), (2, 1)), ((0, 1), (1, 2)), ((1, 1), (2, 2))], msg

        """ Test 4 : Un seul edge sélectionné. """
        for o_gredge in o_scene.get_gredges():
            t_id = o_gredge.o_edge.get_tid()
            if t_id == ((0, 0), (1, 0)):
                o_gredge.setSelected(True)
            else:
                o_gredge.setSelected(False)
        o_scene.cross_edges()
        l_tid = sorted([o_gredge.o_edge.get_tid() for o_gredge in o_scene.get_gredges()])
        msg = "Un seul edge sélectionné => Il ne doit y avoir aucun changement."
        assert l_tid == [((0, 0), (1, 0)), ((0, 0), (2, 1)), ((0, 1), (1, 2)), ((1, 1), (2, 2))], msg

        """ Test 5 : Tous les edges sélectionnés. """
        for o_gredge in o_scene.get_gredges():
            o_gredge.setSelected(True)
        o_scene.cross_edges()
        l_tid = sorted([o_gredge.o_edge.get_tid() for o_gredge in o_scene.get_gredges()])
        msg = "Plus de 2 edges sont sélectionnés => Il ne doit y avoir aucun changement."
        assert l_tid == [((0, 0), (1, 0)), ((0, 0), (2, 1)), ((0, 1), (1, 2)), ((1, 1), (2, 2))], msg

        """ Test 6 : Bouclage sur un même node => interdit. """
        for o_gredge in o_scene.get_gredges():
            t_id = o_gredge.o_edge.get_tid()
            if t_id == ((0, 0), (1, 0)) or t_id == ((1, 1), (2, 2)):
                o_gredge.setSelected(True)
            else:
                o_gredge.setSelected(False)
        o_scene.cross_edges()
        l_tid = sorted([o_gredge.o_edge.get_tid() for o_gredge in o_scene.get_gredges()])
        msg = "Le croisement produit un bouclage sur un même node => Cela n'est pas autorisé."
        assert l_tid == [((0, 0), (1, 0)), ((0, 0), (2, 1)), ((0, 1), (1, 2)), ((1, 1), (2, 2))], msg

        """ Fin. """
        self.connect(o_main.close, 1000)
        self.g_appli.exec()

 


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 !