Finalité du poste de contrôle
Avant-propos
Debug :
checkbox
sur les nodes n'ont pas d'effet sur undo & redo
.b_action
en ajoutant cette ligne dans set_checked()
...backup()
, dans CtrlNode.set_checked()
:
self.o_scene.o_ur.b_action = True # Ajout dans l'historique du "Undo-Redo".
Redo
:
Undo
: Tout a l'air correct, l'edge est revenu, les nodes ont été réactivés.Redo
:
infrastructure()
, qui est dans le setup()
de CtrlScene
...CtrlScene.show_items()
, à la fin.setup()
qu'une fois, lors de l'instanciation de la scène ...show_items()
lors de l'instanciation mais aussi lors des Undo & Redo
.Description
Nous voulons obtenir ceci.
plot.py
et plot.png
dans /nodes/afficheurs
.DEBUG
dans CtrlScene
. Au début du fichier → DEBUG = True
.CtrlScene.debug()
par celle-ci :
def debug(self):
""" En cas d'erreur de compilation, passer b_compile à False pour connaître l'erreur. Tester les 2 cas. """
b_compile = True
""" 4 nodes et 3 edges. """
if b_compile:
""" Avec compilation dynamique. """
self.o_pkl.od_pkl.clear()
self.o_pkl.od_pkl.write(['Node0', 'path'], 'nodes.generateurs.histos')
self.o_pkl.od_pkl.write(['Node0', 'position'], (-140, -80)) # Position du centre du node.
self.o_pkl.od_pkl.write(['Node1', 'path'], 'nodes.generateurs.signaux')
self.o_pkl.od_pkl.write(['Node1', 'position'], (-140, 0))
self.o_pkl.od_pkl.write(['Node2', 'path'], 'nodes.generateurs.aleatoire')
self.o_pkl.od_pkl.write(['Node2', 'position'], (-140, 80))
self.o_pkl.od_pkl.write(['Node3', 'path'], 'nodes.afficheurs.plots')
self.o_pkl.od_pkl.write(['Node3', 'position'], (100, -16))
self.o_pkl.od_pkl.write('edges', {((0, 0), (3, 0)), ((1, 0), (3, 1)), ((2, 0), (3, 2))})
self.show_nodes()
self.show_edges()
else:
""" Sans compilation dynamique. """
from nodes.generateurs.histos import Node
o_h = Node(self, 'Node0', (-140, -80)) # Position du coin haut-gauche du node.
from nodes.generateurs.signaux import Node
o_s = Node(self, 'Node1', (-140, 0))
from nodes.generateurs.aleatoire import Node
o_a = Node(self, 'Node2', (-140, 80))
from nodes.afficheurs.plots import Node
o_plots = Node(self, 'Node3', (100, 0))
CtrlEdge(self, o_h.lo_sockets_out[0], o_plots.lo_sockets_in[0])
CtrlEdge(self, o_s.lo_sockets_out[0], o_plots.lo_sockets_in[1])
CtrlEdge(self, o_a.lo_sockets_out[0], o_plots.lo_sockets_in[2])
Si nécessaire, passez b_compile à False
pour voir les erreurs.
Chaque type de node peut surcharger les méthodes fixed_params()
, bundle_params()
et my_params()
.
l_sep_inputs
dans l'init
, avant l'appel du setup()
.plots.py > Node.__init__()
:
self.l_sep_inputs = [2] # Emplacement des séparateurs
Ici, un séparateur après l'entrée 2. Si la liste est vide ⇒ aucun séparateur.
fixed_params()
. La figure ci-dessus nous montre l'emplacement des paramètres dans l'arborescence.plots.py > Node.fixed_params()
:
def fixed_params(self):
""" Valeurs par défaut. """
return {
"Nombre d'entrées": [1, {'step': 1, 'limits': (1, 8)}],
'Fenêtre': {
'Titre': 'Titre de la fenêtre',
'Interface': ['Matplotlib', {'values': ['Matplotlib', 'Pyqtgraph', 'Plotly', 'Bokeh',
'Cufflinks', 'Dash', 'Guiqwt', 'OpenCV', 'Seaborn']}]
}
}
Vous pouvez modifier le nombre d'entrées min et max - ici : (1, 8) - ainsi que la liste d'interfaces.
bundle_params()
.plots.py > Node.bundle_params()
:
def bundle_params(self):
""" Valeurs par défaut. """
return {
'Titre': 'Titre du graphique',
}
CtrlNode.bundle_params()
:
def bundle_params(self):
""" - Renvoie un dictionnaire de paramètres pour chaque entrée.
- Une entrée reçoit un faisceau de signaux (bundle). """
return {}
dynamic_params()
. Remplacer la ligne d_input = dict()
par celle-ci.CtrlNode.dynamic_params()
:
d_input = self.bundle_params() # Paramètres pour cette entrée.
Plots
sont bien affichés comme sur l'image ci-dessus.my_params()
est déja codée.@property ld_inputs
, qui contient actuellement 4 entrées, par celle-ci.plots.py > Node.ld_inputs()
:
@property
def ld_inputs(self):
""" Lecture des paramètres pour connaître le nombre d'entrées. """
nb_inputs = self.get_param(["Nombre d'entrées"], 1) # 1 entrée par défaut.
l_inputs = list()
for k in range(nb_inputs):
""" Ajout des entrées. """
label = 'gr' if nb_inputs == 1 else f'gr {k}'
l_inputs.append({'label': label, 'label_pos': (6, -10)})
""" Ajout des séparateurs (on évite un séparateur en fin de liste). """
if k in self.l_sep_inputs and k < nb_inputs - 1:
l_inputs.append('sep')
return l_inputs
rebuild()
.rebuild()
.refresh()
est appelée. Elle doit donc appeller rebuild()
si le nombre d'entrées a changé.plots.py > Node.refresh()
:
def refresh(self, l_keys):
if l_keys[-1] == "Nombre d'entrées":
self.rebuild()
""" L'entrée ajoutée ou retirée peut modifier l'infrastructure. """
l_keys = ['infrastructure'] + l_keys
super().refresh(l_keys)
plots.py > Node.rebuild()
:
def rebuild(self):
""" Reconstruction du node suite à un changement dynamique du nombre d'entrées. """
""" Suppression des (sockets + edges) de ce node. """
for o_socket in self.lo_sockets_in:
lo_gredges = list(o_socket.get_gredges())
if lo_gredges:
""" Suppression des éventuels edges connectés à ce socket. """
self.o_scene.o_grscene.removeItem(lo_gredges[0])
""" Suppression du socket. """
self.o_scene.o_grscene.removeItem(o_socket.o_grsocket)
""" Nettoyage de la liste des entrées. """
self.lo_sockets_in.clear()
""" Redessine le node avec sa nouvelle hauteur. """
ld_inputs = self.deepcopy(self.ld_inputs)
nb_inputs = len(ld_inputs)
h_title = self.d_display['geometry']['h_title']
first_y = 16 * (1 + (h_title + 4) // 16)
self.height = first_y + nb_inputs * 16 - 4 # Nouvelle hauteur.
self.o_grnode.set_outline()
""" Régénération de sockets. """
for i, d_input in enumerate(ld_inputs):
""" On complète les dictionnaires de base. Pas de séparateurs. """
if isinstance(d_input, dict):
d_input['pos'] = True, i # On ajoute l'emplacement. Tuple (input: True, num_position).
d_input['id'] = self.id, i # On ajoute l'identifiant. Tuple (id_node, num_socket)
self.lo_sockets_in.append(CtrlSocket(self, d_input)) # Socket instancié et ajouté à la liste.
""" Régénération des edges. """
self.o_scene.show_edges()
""" Dockable des paramètres : le nombre d'entrées a changé. """
self.o_params.set_params()
self.dt.delay(self.o_params.show_params, 2000) # delay : Permet de continuer la saisie dans le dockable.
(Importer CtrlSocket)
set_outline()
est appelée dans UiNode
.Cette méthode n'existe pas. Créons-la.UiNode.set_outline()
:
def set_outline(self):
""" Crée ou recrée le cadre total du node, nécessaire lors du changement de taille dynamique (ex : plots). """
n_round = self.o_node.d_display['geometry']['round']
self.path_outline = QPainterPath()
self.path_outline.addRoundedRect(0, 0, self.o_node.width, self.o_node.height, n_round, n_round)
Plots
, c'est un simple bouton, qui permet d'afficher la fenêtre des graphiques.o_grcontent
à CtrlNode
.CtrlNode.__init__()
:
""" Contenu spécifique. """
self.o_grcontent = None
Général - On ajoute la méthode init_content()
à UiNode
.
UiNode.init_content()
:
def init_content(self):
if self.o_node.o_grcontent:
QGraphicsProxyWidget(self).setWidget(self.o_node.o_grcontent)
Général - On appelle cette méthode au lancement.
Ajouter ces lignes avant les flags, dans UiNode.init_ui()
:
""" Contenus spécifiques. """
self.init_content()
Propre à plots
- Classe UiContentPlots
.
Copier cette classe à la fin du fichier plots.py
:
class UiContentPlots(QWidget):
""" Widgets affichés dans le node (ici, un bouton). L'affichage est assuré par 'UiNode.initContent()'. """
def __init__(self, o_parent):
super().__init__()
self.o_parent = o_parent
""" Le style est dans qss """
self.setGeometry(40, 38, 10, 10)
self.layout = QVBoxLayout()
self.layout.setContentsMargins(0, 0, 0, 0) # g, h, d, b
self.setLayout(self.layout)
""" Bouton : le style est dans qss/nodestyle.qss. """
self.button = QPushButton('Voir', self)
self.button.setCursor(Qt.PointingHandCursor)
self.layout.addWidget(self.button)
self.button.clicked.connect(self.o_parent.show_figure)
(Importer Qt, QWidget, QVBoxLayout et QPushButton)
plots
- Instancier cette classe au lancement, avec l'objet o_grcontent
.plots.py > Node.setup()
:
def setup(self, child_file=__file__):
self.o_grcontent = UiContentPlots(self)
super().setup(child_file)
button.clicked
) qui appelle la méthyode show_figure()
, qui n'existe pas. On la crée.plots.py > Node.show_figure()
:
def show_figure(self):
""" Désélection de tous les items de la scene, puis sélection de moi-même. """
for item in self.o_scene.o_grscene.items():
item.setSelected(False)
self.o_grnode.setSelected(True)
ui_fig = self.get_param(['Fenêtre', 'Interface'])
""" Code provisoire. """
print(f'Affichage avec {ui_fig}.')
Vérifier :
Le bouton s'affiche.
En cliquant dessus, on affiche bien le message "Affichage avec ..."
Oui mais ... il n'est pas stylisé : couleurs, débordement ...
Qt
propose une stylisation de ses widgets, grace à du code qss
, qui ressemble au css
./pc/nodestyle.qss
:
/* Ctrl + clic -> https://github.com/Quick-Turn-Studio/CLionSupportForQt */
UiContentPlots QPushButton {
color: #ffaa0000;
background : lightblue;
width: 40px;
height: 12px;
padding-top: 1px;
padding-bottom: 3px;
}
UiContentPlots > QPushButton:hover {
color: white;
background : #800000;
}
setup()
de la première classe du projet : UiMain
.UiMain.setup()
:
""" Feuille de style. """
with open('nodestyle.qss', 'r', encoding='utf-8') as file:
QApplication.instance().setStyleSheet(file.read())
Bonjour les codeurs !