Source code for UI.newComponent

#!/usr/bin/python3
# -*- coding: utf-8 -*-

'''Pychemqt, Chemical Engineering Process simulator
Copyright (C) 2009-2025, Juan José Gómez Romera <jjgomera@gmail.com>

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.


Module to implement new component by group contribution methods UI interfaces

    * :class:`newComponent`: Main dialog class with common functionality
    * :class:`UI_Contribution`: Definition for group contribution
    * :class:`View_Contribution`: Dialog to show the properties of a group \
    contribution
'''


import os
from functools import partial

from tools.qt import QtCore, QtGui, QtWidgets

from lib.config import IMAGE_PATH
from lib import sql
from lib.newComponent import _methods
from lib.unidades import (Enthalpy, Pressure, SolubilityParameter,
                          SpecificVolume, Temperature)
from UI.delegate import SpinEditor
from UI.viewComponents import View_Component
from UI.widgets import Entrada_con_unidades, Status


[docs] class newComponent(QtWidgets.QDialog): """Main dialog class with common functionality"""
[docs] def loadUI(self): """Define common widget for chid class""" layoutBottom = QtWidgets.QHBoxLayout() self.status = Status() layoutBottom.addWidget(self.status) self.buttonShowDetails = QtWidgets.QPushButton(self.tr("Show Details")) self.buttonShowDetails.clicked.connect(self.showDetails) self.buttonShowDetails.setEnabled(False) layoutBottom.addWidget(self.buttonShowDetails) self.btonBox = QtWidgets.QDialogButtonBox( QtWidgets.QDialogButtonBox.StandardButton.Cancel | QtWidgets.QDialogButtonBox.StandardButton.Save) self.btonBox.button( QtWidgets.QDialogButtonBox.StandardButton.Save).setEnabled(False) self.btonBox.accepted.connect(self.save) self.btonBox.rejected.connect(self.reject) layoutBottom.addWidget(self.btonBox) self.layout().addLayout(layoutBottom)
[docs] def save(self): """Save new componente in user database""" elemento = self.unknown.export2Component() sql.inserElementsFromArray(sql.databank_Custom_name, [elemento]) dlg = View_Component(10001+sql.N_comp_Custom) dlg.show() QtWidgets.QDialog.accept(self)
[docs] def changeParams(self, parametro, valor): """Update new component when change any parameters""" self.calculo(**{parametro: valor})
[docs] def calculo(self, **kwargs): """Does UI changes when make new component calculation""" self.status.setState(4) self.unknown(**kwargs) self.status.setState(self.unknown.status, self.unknown.msg) self.buttonShowDetails.setEnabled(self.unknown.status) self.btonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Save).setEnabled( self.unknown.status)
[docs] def showDetails(self): """Show details of new component""" dialog = self.ViewDetails(self.unknown) dialog.exec()
[docs] class View_Contribution(QtWidgets.QDialog): """Dialog to show the properties of a group contribution"""
[docs] def __init__(self, cmp=None, parent=None): """Constructor cmp: optional new component to show the properties""" super().__init__(parent) self.setWindowTitle(self.tr("Group Contribution new component")) layout = QtWidgets.QGridLayout(self) self.name = QtWidgets.QLabel() layout.addWidget(self.name, 1, 1, 1, 5) label = QtWidgets.QLabel("M") label.setToolTip(self.tr("Molecular Weight")) layout.addWidget(label, 2, 1) self.M = Entrada_con_unidades(float, textounidad="g/mol") layout.addWidget(self.M, 2, 2) label = QtWidgets.QLabel("Tb") label.setToolTip(self.tr("Boiling Temperature")) layout.addWidget(label, 3, 1) self.Tb = Entrada_con_unidades(Temperature) layout.addWidget(self.Tb, 3, 2) label = QtWidgets.QLabel("Tm") label.setToolTip(self.tr("Melting Temperature")) layout.addWidget(label, 4, 1) self.Tf = Entrada_con_unidades(Temperature) layout.addWidget(self.Tf, 4, 2) layout.addWidget(QtWidgets.QLabel("Tc"), 5, 1) self.Tc = Entrada_con_unidades(Temperature) layout.addWidget(self.Tc, 5, 2) layout.addWidget(QtWidgets.QLabel("Pc"), 6, 1) self.Pc = Entrada_con_unidades(Pressure) layout.addWidget(self.Pc, 6, 2) layout.addWidget(QtWidgets.QLabel("Vc"), 7, 1) self.Vc = Entrada_con_unidades(SpecificVolume) layout.addWidget(self.Vc, 7, 2) label = QtWidgets.QLabel("ΔHf") label.setToolTip(self.tr("Enthalpy of formation of ideal gas")) layout.addWidget(label, 8, 1) self.Hf = Entrada_con_unidades(Enthalpy) layout.addWidget(self.Hf, 8, 2) label = QtWidgets.QLabel("ΔGf") label.setToolTip(self.tr("Gibbs free energy of formation of ideal gas")) layout.addWidget(label, 9, 1) self.Gf = Entrada_con_unidades(Enthalpy) layout.addWidget(self.Gf, 9, 2) label = QtWidgets.QLabel("ΔHf") label.setToolTip(self.tr("Enthalpy of fusion")) layout.addWidget(label, 2, 4) self.Hm = Entrada_con_unidades(Enthalpy) layout.addWidget(self.Hm, 2, 5) label = QtWidgets.QLabel("ΔHv") label.setToolTip(self.tr("Enthalpy of vaporization")) layout.addWidget(label, 3, 4) self.Hv = Entrada_con_unidades(Enthalpy) layout.addWidget(self.Hv, 3, 5) layout.addWidget(QtWidgets.QLabel("Cpa"), 4, 4) self.cpa = Entrada_con_unidades(float) layout.addWidget(self.cpa, 4, 5) layout.addWidget(QtWidgets.QLabel("Cpb"), 5, 4) self.cpb = Entrada_con_unidades(float) layout.addWidget(self.cpb, 5, 5) layout.addWidget(QtWidgets.QLabel("Cpc"), 6, 4) self.cpc = Entrada_con_unidades(float) layout.addWidget(self.cpc, 6, 5) layout.addWidget(QtWidgets.QLabel("Cpd"), 7, 4) self.cpd = Entrada_con_unidades(float) layout.addWidget(self.cpd, 7, 5) layout.addWidget(QtWidgets.QLabel("μa"), 8, 4) self.mua = Entrada_con_unidades(float) layout.addWidget(self.mua, 8, 5) layout.addWidget(QtWidgets.QLabel("μb"), 9, 4) self.mub = Entrada_con_unidades(float) layout.addWidget(self.mub, 9, 5) label = QtWidgets.QLabel("SG") label.setToolTip(self.tr("Specific gravity at 60ºF")) layout.addWidget(label, 2, 7) self.gravity = Entrada_con_unidades(float) layout.addWidget(self.gravity, 2, 8) label = QtWidgets.QLabel("API") label.setToolTip(self.tr("API Specific gravity")) layout.addWidget(label, 3, 7) self.API = Entrada_con_unidades(float) layout.addWidget(self.API, 3, 8) label = QtWidgets.QLabel("Kw") label.setToolTip(self.tr("Watson characterization factor")) layout.addWidget(label, 4, 7) self.watson = Entrada_con_unidades(float) layout.addWidget(self.watson, 4, 8) label = QtWidgets.QLabel("w") label.setToolTip(self.tr("Acentric factor")) layout.addWidget(label, 5, 7) self.f_acent = Entrada_con_unidades(float) layout.addWidget(self.f_acent, 5, 8) label = QtWidgets.QLabel("Ra") label.setToolTip(self.tr("Rackett constant")) layout.addWidget(label, 6, 7) self.rackett = Entrada_con_unidades(float) layout.addWidget(self.rackett, 6, 8) label = QtWidgets.QLabel("Vliq") label.setToolTip(self.tr("Volume Liquid Constant")) layout.addWidget(label, 7, 7) self.Vliq = Entrada_con_unidades(float) layout.addWidget(self.Vliq, 7, 8) label = QtWidgets.QLabel("Sol") label.setToolTip(self.tr("Solubility Parameter")) layout.addWidget(label, 8, 7) self.SolubilityParameter = Entrada_con_unidades(SolubilityParameter) layout.addWidget(self.SolubilityParameter, 8, 8) layout.addItem(QtWidgets.QSpacerItem( 20, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding), 15, 8) btn = QtWidgets.QDialogButtonBox( QtWidgets.QDialogButtonBox.StandardButton.Close) btn.rejected.connect(self.accept) layout.addWidget(btn, 16, 1, 1, 8) self.setReadOnly(True) if cmp: self.fill(cmp)
[docs] def setReadOnly(self, boolean): """Set readonly state for child widgets""" self.M.setReadOnly(boolean) self.Tb.setReadOnly(boolean) self.Tf.setReadOnly(boolean) self.Tc.setReadOnly(boolean) self.Pc.setReadOnly(boolean) self.Vc.setReadOnly(boolean) self.Hf.setReadOnly(boolean) self.Gf.setReadOnly(boolean) self.Hm.setReadOnly(boolean) self.Hv.setReadOnly(boolean) self.cpa.setReadOnly(boolean) self.cpb.setReadOnly(boolean) self.cpc.setReadOnly(boolean) self.cpd.setReadOnly(boolean) self.mua.setReadOnly(boolean) self.mub.setReadOnly(boolean) self.gravity.setReadOnly(boolean) self.API.setReadOnly(boolean) self.watson.setReadOnly(boolean) self.f_acent.setReadOnly(boolean) self.rackett.setReadOnly(boolean) self.Vliq.setReadOnly(boolean) self.SolubilityParameter.setReadOnly(boolean)
[docs] def fill(self, cmp): """Add cmp properties in widgets""" self.name.setText(cmp.name) self.M.setValue(cmp.M) self.Tb.setValue(cmp.Tb) self.Tf.setValue(cmp.Tf) self.Tc.setValue(cmp.Tc) self.Pc.setValue(cmp.Pc) self.Vc.setValue(cmp.Vc) self.Hf.setValue(cmp.Hf) self.Gf.setValue(cmp.Gf) self.Hm.setValue(cmp.Hm) self.Hv.setValue(cmp.Hv) self.cpa.setValue(cmp.cp[0]) self.cpb.setValue(cmp.cp[1]) self.cpc.setValue(cmp.cp[2]) self.cpd.setValue(cmp.cp[3]) self.gravity.setValue(cmp.SG) self.API.setValue(cmp.API) self.watson.setValue(cmp.Kw) self.f_acent.setValue(cmp.f_acent) self.rackett.setValue(cmp.rackett) self.Vliq.setValue(cmp.Vliq) self.SolubilityParameter.setValue(cmp.Parametro_solubilidad)
[docs] class Ui_Contribution(newComponent): """Dialog to define hypotethical new component with several group contribucion methods""" ViewDetails = View_Contribution
[docs] def __init__(self, metodo, parent=None): """Metodo: name of group contribution method: Joback, Constantinou, Wilson, Marrero, Elliott, Ambrose, ... """ super().__init__(parent) # Initialization variables self.grupo = [] self.indices = [] self.contribucion = [] func = {} for f in _methods: func[f.__name__] = f self.unknown = func[metodo]() title = self.unknown.__title__ title += " " + self.tr("new component definition") self.setWindowTitle(title) lyt = QtWidgets.QVBoxLayout(self) widget = QtWidgets.QWidget() layout = QtWidgets.QGridLayout(widget) layout.addWidget(QtWidgets.QLabel(self.tr("Name")), 1, 0) self.name = QtWidgets.QLineEdit() self.name.textChanged.connect(partial(self.changeParams, "name")) layout.addWidget(self.name, 1, 1, 1, 3) self.Group = QtWidgets.QTableWidget() self.Group.verticalHeader().hide() self.Group.setRowCount(0) self.Group.setColumnCount(2) self.Group.setHorizontalHeaderItem(0, QtWidgets.QTableWidgetItem("Nk")) self.Group.setHorizontalHeaderItem(1, QtWidgets.QTableWidgetItem( self.tr("Group"))) self.Group.setSelectionBehavior( QtWidgets.QAbstractItemView.SelectionBehavior.SelectRows) self.Group.setSortingEnabled(True) self.Group.horizontalHeader().setStretchLastSection(True) self.Group.setColumnWidth(0, 50) self.Group.setItemDelegateForColumn(0, SpinEditor(self)) self.Group.cellChanged.connect(self.cellChanged) self.Group.setEditTriggers( QtWidgets.QAbstractItemView.EditTrigger.AllEditTriggers) layout.addWidget(self.Group, 2, 0, 3, 3) self.Formula = QtWidgets.QLabel() font = QtGui.QFont() font.setPointSize(12) self.Formula.setFont(font) self.Formula.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter | QtCore.Qt.AlignmentFlag.AlignVCenter) self.Formula.setFixedHeight(50) layout.addWidget(self.Formula, 2, 3) self.btnDelete = QtWidgets.QPushButton(QtGui.QIcon(QtGui.QPixmap( os.path.join(IMAGE_PATH, "button", "editDelete.png"))), self.tr("Delete")) self.btnDelete.clicked.connect(self.borrar) layout.addWidget(self.btnDelete, 3, 3) self.btnClear = QtWidgets.QPushButton(QtGui.QIcon(QtGui.QPixmap( os.path.join(IMAGE_PATH, "button", "clear.png"))), self.tr("Clear")) self.btnClear.clicked.connect(self.clear) layout.addWidget(self.btnClear, 4, 3) self.line = QtWidgets.QFrame() self.line.setFrameShape(QtWidgets.QFrame.Shape.HLine) self.line.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken) layout.addWidget(self.line, 5, 0, 1, 4) self.groupContributions = QtWidgets.QListWidget() self.groupContributions.currentItemChanged.connect(self.selectChanged) self.groupContributions.itemDoubleClicked.connect(self.add) layout.addWidget(self.groupContributions, 6, 0, 6, 3) self.btnAdd = QtWidgets.QPushButton(QtGui.QIcon(QtGui.QPixmap( os.path.join(IMAGE_PATH, "button", "add.png"))), self.tr("Add")) self.btnAdd.setDisabled(True) self.btnAdd.clicked.connect(self.add) layout.addWidget(self.btnAdd, 6, 3) layout.addItem(QtWidgets.QSpacerItem( 20, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding), 7, 1) # Show selection for method with several order contributions if self.unknown.SecondOrder: self.Order1 = QtWidgets.QRadioButton(self.tr("1st order")) self.Order1.setChecked(True) self.Order1.toggled.connect(self.Order) layout.addWidget(self.Order1, 9, 3) self.Order2 = QtWidgets.QRadioButton(self.tr("2nd order")) self.Order2.toggled.connect(self.Order) layout.addWidget(self.Order2, 10, 3) if self.unknown.ThirdOrder: self.Order3 = QtWidgets.QRadioButton(self.tr("3rd order")) layout.addWidget(self.Order3, 11, 3) self.Order3.toggled.connect(self.Order) layout.addItem(QtWidgets.QSpacerItem( 10, 10, QtWidgets.QSizePolicy.Policy.Fixed, QtWidgets.QSizePolicy.Policy.Fixed), 12, 1) labelTb = QtWidgets.QLabel("Tb") labelTb.setToolTip(self.tr("Experimental Boiling Temperature")) layout.addWidget(labelTb, 13, 0) self.Tb = Entrada_con_unidades(Temperature) self.Tb.valueChanged.connect(partial(self.changeParams, "Tb")) layout.addWidget(self.Tb, 13, 1) labelM = QtWidgets.QLabel("M") labelM.setToolTip(self.tr("Molecular Weight")) layout.addWidget(labelM, 14, 0) self.M = Entrada_con_unidades(float, textounidad="g/mol") self.M.valueChanged.connect(partial(self.changeParams, "M")) layout.addWidget(self.M, 14, 1) label = QtWidgets.QLabel("SG") label.setToolTip(self.tr("Experimental Specific Gravity at 60ºF")) layout.addWidget(label, 15, 0) self.SG = Entrada_con_unidades(float) self.SG.valueChanged.connect(partial(self.changeParams, "SG")) layout.addWidget(self.SG, 15, 1) lyt.addWidget(widget) # Show widget for specific method if metodo == "Constantinou": # Disable Tb as input parameter labelTb.setEnabled(False) self.Tb.setEnabled(False) elif metodo in ("Lydersen", "Li"): self.Tb.setResaltado(True) elif metodo == "Wilson": self.Tb.setResaltado(True) layout.addWidget(QtWidgets.QLabel(self.tr("Rings")), 16, 0) self.ring = QtWidgets.QSpinBox() self.ring.setFixedWidth(80) self.ring.valueChanged.connect(partial(self.changeParams, "ring")) layout.addWidget(self.ring, 16, 1) elif metodo == "Elliott": self.M.setResaltado(True) elif metodo == "Ambrose": self.Tb.setResaltado(True) layout.addWidget(QtWidgets.QLabel(self.tr("Platt number")), 16, 0) self.plat = QtWidgets.QSpinBox() self.plat.setFixedWidth(80) t = "The Platt number is the number of pairs of carbon atoms " t += "which are separated by three carbon-carbon bonds and is an " t += "indicator of the degree of branching in the molecule. The " t += "Platt number of an n-alkane is equal to the number of " t += "carbons minus three" self.plat.setToolTip(self.tr(t)) self.plat.valueChanged.connect(partial(self.changeParams, "platt")) layout.addWidget(self.plat, 16, 1) elif metodo == "Klincewicz": self.Tb.setResaltado(True) self.nogroupKlincewicz = QtWidgets.QCheckBox( self.tr("Use the simple no group contribution method")) self.nogroupKlincewicz.toggled.connect(self.nogroupCheckToggled) layout.addWidget(self.nogroupKlincewicz, 16, 0, 1, 4) layout.addWidget(QtWidgets.QLabel(self.tr("Atoms")), 17, 0) self.atoms = QtWidgets.QSpinBox() self.atoms.setFixedWidth(80) self.atoms.valueChanged.connect( partial(self.changeParams, "atoms")) layout.addWidget(self.atoms, 17, 1) newComponent.loadUI(self) for name in self.unknown.__coeff__["txt"]: self.groupContributions.addItem(name[0]) if self.unknown.SecondOrder: self.Order() if metodo == "Klincewicz": self.nogroupCheckToggled(False)
[docs] def Order(self): """Show/Hide group of undesired order""" for i in range(self.unknown.FirstOrder): item = self.groupContributions.item(i) item.setHidden(not self.Order1.isChecked()) for i in range(self.unknown.FirstOrder, self.unknown.SecondOrder): item = self.groupContributions.item(i) item.setHidden(not self.Order2.isChecked()) if self.unknown.ThirdOrder: for i in range(self.unknown.SecondOrder, self.unknown.ThirdOrder): item = self.groupContributions.item(i) item.setHidden(not self.Order3.isChecked())
[docs] def nogroupCheckToggled(self, boolean): """Set advanced properties input status for Klincewitcz method""" self.changeParams("nogroup", boolean) self.M.setResaltado(boolean) self.atoms.setEnabled(boolean) self.Group.setDisabled(boolean) self.Formula.setDisabled(boolean) self.btnDelete.setDisabled(boolean) self.btnClear.setDisabled(boolean) self.btnAdd.setDisabled(boolean) self.groupContributions.setDisabled(boolean)
[docs] def borrar(self, indice=None): """Remove some group contribution from added group list""" if not indice: indice = self.Group.currentRow() if indice != -1: self.Group.removeRow(indice) del self.grupo[indice] del self.indices[indice] del self.contribucion[indice] self.calculo(**{"group": self.indices, "contribution": self.contribucion})
[docs] def clear(self): """Clear widgets from dialog""" self.Group.clearContents() self.Group.setRowCount(0) self.grupo = [] self.indices = [] self.contribucion = [] self.Formula.clear() self.M.clear() self.name.clear() self.Tb.clear() self.SG.clear() self.unknown.clear() self.status.setState(self.unknown.status, self.unknown.msg)
[docs] def cellChanged(self, i, j): """Process the user manual count of group contribution changed""" if j == 0: valor = int(self.Group.item(i, j).text()) if valor <= 0: self.borrar(i) else: self.contribucion[i] = int(valor) kw = {"group": self.indices, "contribution": self.contribucion} self.calculo(**kw)
[docs] def selectChanged(self, i): """The add button is only enabled when the group list have any selected row""" self.btnAdd.setEnabled(i != -1)
[docs] def add(self): """Add the current selected item to the list of group""" indice = self.Group.rowCount() grupo = self.groupContributions.currentItem().text() if grupo not in self.grupo: self.grupo.append(grupo) self.indices.append(self.groupContributions.currentRow()) self.contribucion.append(1) self.Group.setRowCount(indice+1) self.Group.setItem(indice, 0, QtWidgets.QTableWidgetItem("1")) self.Group.item(indice, 0).setTextAlignment( QtCore.Qt.AlignmentFlag.AlignRight | QtCore.Qt.AlignmentFlag.AlignVCenter) self.Group.setItem(indice, 1, QtWidgets.QTableWidgetItem(grupo)) self.Group.item(indice, 1).setFlags( QtCore.Qt.ItemFlag.ItemIsSelectable | QtCore.Qt.ItemFlag.ItemIsEnabled) self.Group.setRowHeight(indice, 20) else: indice = self.grupo.index(grupo) self.contribucion[indice] += 1 self.Group.item(indice, 0).setText(str(int( self.Group.item(indice, 0).text())+1)) kw = {"group": self.indices, "contribution": self.contribucion} self.calculo(**kw)
[docs] def calculo(self, **kwargs): """Calculate function""" newComponent.calculo(self, **kwargs) if self.unknown.status in (1, 3): self.Formula.setText(self.unknown.formula)
if __name__ == "__main__": import sys app = QtWidgets.QApplication(sys.argv) Dialog = Ui_Contribution("Constantinou") Dialog.show() sys.exit(app.exec())