Come fare un plugin in Salome

Questo rapido post fornisce qualche elemento per rispondere ad una domanda fatta da diverse persone durante la scorsa giornata utenti di Code_Aster a Modena: “come fare un plugin in Salome”.

Un plugin (in altri contesti verrebbe chiamato una “Macro”) serve per aggiungere delle funzionalità all’interno dei moduli già esistenti.

Il compito è relativamente semplice, ma richiede alcune conoscenze:

  • programmazione Python di base
  • utilizzo del framework PyQt

In questo esempio verrà creato un semplice plugin di esempio che permette di:

  • selezionare un cerchio (e solo un cerchio) nella geometria
  • creare un vertice al suo centro
  • opzionalmente, creare un sistema di coordinate locali per il cerchio

Per prima cosa, per aggiungere un nuovo plugin a Salome occorre creare un file chiamato “salome_plugins.py”. All’avvio di Salome questo file viene cercato in diversi posti, nel mio caso il file si trova all’interno della mia directory “home”, in “~/.config/salome/Plugins”. Se questa directory non esiste, occorre crearla.

Il contenuto di questo file, che serve a registrare un nuovo plugin, è semplicemente:

import salome_pluginsmanager

from makecenter_plugin import makecenter
salome_pluginsmanager.AddFunction('Make center',
                           'Makes the center of the selected circle',
                           makecenter)

Nota: per aggiungere ulteriori plugin basta aggiungere ulteriori chiamate a “AddFunction” nello stesso file. Da notare come il plugin viene recuperato da un modulo Python chiamato “makecenter_plugin” (che vedremo più avanti in questo post) e in particolare un riferimento ad una sua funzione, chiamata “makecenter”.

Il plugin vero e proprio si trova in altri due file. Il primo di essi contiene tutta la descrizione grafica. Questo file può essere scritto manualmente (come in questo caso) oppure, con tecniche diverse, generato con uno strumento grafico (Qt designer). Lo chiameremo “makecenter_ui.py” (si deve trovare nella directory descritta sopra), il suo contenuto è:

# -*- coding: utf-8 -*-

from PyQt5 import QtCore, QtGui, QtWidgets

class Ui_Dialog(object):
    def setupUi(self, Dialog):
        Dialog.setObjectName("Dialog")
        Dialog.resize(178, 156)
        Dialog.setSizeGripEnabled(True)
        self.gridLayout = QtWidgets.QGridLayout(Dialog)
        self.gridLayout.setObjectName("gridLayout")
        self.label_2 = QtWidgets.QLabel(Dialog)
        self.label_2.setObjectName("label_2")
        self.gridLayout.addWidget(self.label_2, 0, 0, 1, 1)
        self.geom = QtWidgets.QLineEdit(Dialog)
        self.geom.setReadOnly(True)
        self.geom.setObjectName("geom")
        self.gridLayout.addWidget(self.geom, 0, 1, 1, 2)
        self.label_3 = QtWidgets.QLabel(Dialog)
        self.label_3.setObjectName("label_3")
        self.gridLayout.addWidget(self.label_3, 1, 0, 1, 1)
        self.lcs = QtWidgets.QCheckBox(Dialog)
        self.lcs.setObjectName("lcs")
        self.lcs.setChecked(False)
        self.gridLayout.addWidget(self.lcs, 1, 1, 1, 2)
        self.label = QtWidgets.QLabel(Dialog)
        self.label.setObjectName("label")
        self.gridLayout.addWidget(self.label, 2, 0, 1, 1)
        self.center = QtWidgets.QLineEdit(Dialog)
        self.center.setReadOnly(True)
        self.center.setObjectName("center")
        self.gridLayout.addWidget(self.center, 2, 1, 1, 2)
        self.buttonBox = QtWidgets.QDialogButtonBox(Dialog)
        self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
        self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Ok|QtWidgets.QDialogButtonBox.Help)
        self.buttonBox.setObjectName("buttonBox")
        self.gridLayout.addWidget(self.buttonBox, 3, 0, 1, 3)

        self.retranslateUi(Dialog)
        self.buttonBox.rejected.connect(Dialog.OnCancel)
        self.buttonBox.accepted.connect(Dialog.OnClose)
        self.buttonBox.helpRequested.connect(Dialog.helpMessage)
        QtCore.QMetaObject.connectSlotsByName(Dialog)
        Dialog.setTabOrder(self.geom, self.lcs)
        Dialog.setTabOrder(self.lcs, self.center)

    def retranslateUi(self, Dialog):
        _translate = QtCore.QCoreApplication.translate
        Dialog.setWindowTitle(_translate("Dialog", "Get center of circle"))
        self.label_2.setText(_translate("Dialog", "Geom"))
        self.label_3.setText(_translate("Dialog", "LCR"))
        self.label.setText(_translate("Dialog", "Center"))

In questo post non entreremo nel dettaglio di questo contenuto, questi comandi PyQt creano una finestrella grafica che, da sola, non farebbe assolutamente nulla di utile. Qui sotto un esempio della finestra generata da questi comandi.

La finestra del plugin

Resta da creare il plugin vero e proprio, che metteremo in un file chiamato “makecenter_plugin.py”:

from qtsalome import *
import salome
from salome.geom import geomtools
import GEOM
import sys
import SMESH
from makecenter_ui import Ui_Dialog

class MakeCenterDialog(QDialog):

    def __init__(self, parent, sg, study):
        QDialog.__init__(self, parent, Qt.Tool)
        # Set up the user interface from Designer.
        self.ui = Ui_Dialog()
        self.ui.setupUi(self)
        self.sg = sg
        self.study = study
        studyId = study._get_StudyId()
        self.geompy = geomtools.getGeompy(studyId)

        self.clearLineEdit()

        # Connect up the selectionChanged() event of the object browser.
        self.sg.getObjectBrowser().selectionChanged.connect(self.select)

        self.mm = None
        self.select()

    def OnClose(self):
        self.sg.getObjectBrowser().selectionChanged.disconnect(self.select)
        self.add_center_lcs()          
        self.close()

    def OnCancel(self):
        self.sg.getObjectBrowser().selectionChanged.disconnect(self.select)
        self.reject()

    def clearLineEdit(self):
        self.ui.geom.setText("Select a Circle")
        self.ui.geom.setStyleSheet("QLineEdit { color: grey }")
        self.ui.center.setText("")
        self.ui.center.setStyleSheet("QLineEdit { color: grey }")

    def get_circle(self, objId):
        if objId is None:
            print("No objid found")
            return None
        mm = self.study.FindObjectID(objId).GetObject()
        is_circle = False
        if not mm.IsShape():
            print("Object %s is not a shape."%objId)
            return None
        kindvals = self.geompy.KindOfShape(mm)
        if not mm.GetShapeType() == GEOM.EDGE:
            print("Object %s is not an edge but a: %s"%(objId, mm.GetShapeType()))
            self.clearLineEdit()
            return None
        if kindvals[0] != self.geompy.kind.CIRCLE:
            print("Object %s is not an circle but a: %s"%(objId, kindvals[0]))
            self.clearLineEdit()
            return None
        return mm

    def select(self):    
        self.sg.getObjectBrowser().selectionChanged.disconnect(self.select)
        try:
            objId = salome.sg.getSelected(0)
            circle = self.get_circle(objId)
            if circle is None:
                return
            kindvals = self.geompy.KindOfShape(circle)
            self.ui.geom.setText(str(objId))
            cx,cy,cz = kindvals[1:4]
            self.ui.center.setText("%s,%s,%s"%(cx,cy,cz))
        finally:
            self.sg.getObjectBrowser().selectionChanged.connect(self.select)

    def add_center_lcs(self):
        objId = salome.sg.getSelected(0)
        circle = self.get_circle(objId)
        kindvals = self.geompy.KindOfShape(circle)
        cx,cy,cz = kindvals[1:4]
        center = self.geompy.MakeVertex(cx, cy, cz)
        self.geompy.addToStudyInFather(circle, center, "CENTER")
        if self.ui.lcs.isChecked():
            LCS = self.geompy.MakeMarkerFromShape(circle)
            self.geompy.addToStudyInFather(circle, LCS, "LCS")      
        self.sg.updateObjBrowser(True)

    def helpMessage(self):
        QMessageBox.about(None, "About Make Center",
        """
        Make circle center
        ---------------------------------

        This plugin creates the center for a circle and, optionally, a local reference system.
        Inputs:
        - The circle
        - If the local reference system is requested or not
        """)

def makecenter(context):
    studyId = context.studyId
    window = MakeCenterDialog(context.sg.getDesktop(), context.sg, context.study)
    window.show()

Il contenuto del file è un po’ troppo lungo per essere spiegato in un post come questo, ecco alcuni elementi:

  • la funzione “makecenter” (l’ultima in fondo) è quella chiamata da Salome alla creazione del plugin. Le viene passato un parametro context che contiene dei riferimenti allo studio attuale, per poter interagire con i dati creati dall’utente
  • la classe “MakeCenterDialog” rappresenta la finestra, il cui contenuto grafico viene recuperato dal file precedente, messo in un attributo “ui” ed attivato chiamando il metodo “setupUi” nel costruttore. La finestra è creata come non-modale per poter interagire con lo studio.
  • Tramite il contesto passato da Salome, viene registrato (sempre nel costruttore) un gestore degli eventi che resta in ascolto per cambiamenti della selezione (cioè dell’elemento geometrico corrente) fatti dall’utente. Ad ogni cambiamento viene chiamato il metodo “select”.
  • Il metodo “select”, ogni volta che viene chiamato, recupera il primo elemento selezionato dall’utente e verifica che sia un cerchio. In questo caso ne recupera il centro e scrive alcune informazioni nella finestra.
  • Alla pressione del pulsante “Ok” viene recuperato il cerchio corrente, creato un vertice nel suo centro e, se l’utente ha messo la spunta nella casella LCS, creato anche un sistema di riferimento locale che vengono aggiunti come “elementi figli” rispetto al cerchio stesso.

Per richiamare il plugin basta andare nel menu “Tools” quindi “Plugins”, dove troveremo la voce “Make center”:

Il menu di selezione del plugin

Una volta richiamato il plugin, la finestra si apre e possiamo selezionare elementi. Se si tratta di cerchi, il centro viene mostrato nella finestra (assieme all’id del cerchio):

Il plugin dopo la selezione del cerchio

Una volta premuto il pulsante “Ok” la finestra si chiude e si notano i nuovi elementi nell’albero, che possono essere visualizzati rendendoli visibili:

Il risultato dopo la chiusura del plugin: il centro del cerchio e il sistema di riferimento locale

Ovviamente si tratta di un plugin di esempio: per fare un lavoro migliore dovremmo disattivare il pulsante Ok se non è stato selezionato un cerchio, emettere dei messaggi per spiegare all’utente che cosa succede nei vari casi, etc.

In questo post sono presenti gli elementi di base per automatizzare compiti in Salome:

  • aggiungere un nuovo plugin
  • aprire una finestra personalizzata
  • interagire con la selezione dell’utente
  • leggere e scrivere i valori della finestra
  • creare nuovi elementi grafici ed aggiungerli all’albero

Per domande al riguardo, dubbi, difficoltà o altre cose non chiare in questo post è possibile scrivere un commento in questa pagina o aprire una conversazione sul forum del sito.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *