Source code for lib.openbabel

#!/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 with the openbabel functionality, optional module for
graphics representation of chemical compound from its smile code

  * :func:`imageFromSmile`: Generate image file with expanded formulae
  * :class:`ConfBabel`: Openbabel image generation configuration options

'''

import os
import tempfile
from configparser import ConfigParser

from tools.qt import QtCore, QtGui, QtWidgets

try:
    from openbabel.pybel import readstring
except ImportError:
    pass

from UI.widgets import ColorSelector


[docs] def imageFromSmile(smile, fname, conf): """Generate image file with the compound chemical structure Parameters ---------- smile : str Smile code for compound fname : str Filename to save the image conf : configparser.ConfigParser Configuration parameters """ # Get dict parameters for exported image configuration # Parameters available from openbabel documentation # https://openbabel.org/docs/current/FileFormats/PNG_2D_depiction.html # - p <num>: px Image size, default 300 # - w <pixels>: image width (or from image size) # - h <pixels>: image height (or from image size) # - c <num>: number of columns in table # - r <num>: number of rows in table # - N <num>:max number objects to be output # + u: no element-specific atom coloring # - U: can define alternate color to internally-specified # + b: background color # + C: Do not draw terminal C (and H) explicitly # + a: draw all carbon atoms # - d: do not display molecule name # - m: do not add margins to the image # + s: use asymmetric double bonds # + t: use thicker lines # - A: display aliases, if present # - O <format ID>: Format of embedded text. For example, molfile or smi. # If there is no parameter, input format is used. # - y <additional chunk ID>: Write to a chunk with specified ID opt = {} opt["m"] = None opt["B"] = conf.get("Openbabel", 'BondColor') alpha = conf.getboolean("Openbabel", "BackColorTransparent") if alpha: opt["b"] = "none" else: opt["b"] = conf.get("Openbabel", 'BackgroundColor') if not conf.getboolean("Openbabel", 'AtomsColor'): opt["u"] = None if conf.getboolean("Openbabel", 'AtomsAll'): opt["a"] = None elif conf.getboolean("Openbabel", 'AtomsNone'): opt["C"] = None if conf.getboolean("Openbabel", 'TighBond'): opt["t"] = None if conf.getboolean("Openbabel", 'AsymetricDouble'): opt["s"] = None opt["x"] = None mol = readstring("smi", smile) mol.write("_png2", filename=fname, overwrite=True, opt=opt)
[docs] class ConfBabel(QtWidgets.QDialog): """Openbabel image generation configuration options"""
[docs] def __init__(self, config=None, parent=None): super().__init__(parent) layout = QtWidgets.QGridLayout(self) layout.addWidget(QtWidgets.QLabel(self.tr("Bond color:")), 1, 1) self.BondColor = ColorSelector() self.BondColor.valueChanged.connect(self.updateImage) layout.addWidget(self.BondColor, 1, 2) layout.addWidget(QtWidgets.QLabel(self.tr("Background color:")), 2, 1) self.BackColor = ColorSelector() self.BackColor.valueChanged.connect(self.updateImage) layout.addWidget(self.BackColor, 2, 2) self.BackColorTransparent = QtWidgets.QCheckBox( self.tr("Transparent background color")) self.BackColorTransparent.stateChanged.connect(self.updateImage) layout.addWidget(self.BackColorTransparent, 3, 1, 1, 2) layout.addItem(QtWidgets.QSpacerItem( 20, 20, QtWidgets.QSizePolicy.Policy.Fixed, QtWidgets.QSizePolicy.Policy.Fixed), 4, 1, 1, 2) group = QtWidgets.QGroupBox(self.tr("Atom details")) layout.addWidget(group, 5, 1, 1, 2) lyt = QtWidgets.QVBoxLayout(group) self.radioAll = QtWidgets.QRadioButton(self.tr("Show all atoms")) self.radioAll.clicked.connect(self.updateImage) lyt.addWidget(self.radioAll) self.radioEnd = QtWidgets.QRadioButton( self.tr("Show only terminal atoms")) self.radioEnd.clicked.connect(self.updateImage) lyt.addWidget(self.radioEnd) self.radioNone = QtWidgets.QRadioButton(self.tr("Do not show atoms")) self.radioNone.clicked.connect(self.updateImage) lyt.addWidget(self.radioNone) layout.addItem(QtWidgets.QSpacerItem( 20, 20, QtWidgets.QSizePolicy.Policy.Fixed, QtWidgets.QSizePolicy.Policy.Fixed), 6, 1, 1, 2) self.checkColor = QtWidgets.QCheckBox(self.tr("Heteroatom in color")) self.checkColor.stateChanged.connect(self.updateImage) layout.addWidget(self.checkColor, 7, 1, 1, 2) self.checkTighBond = QtWidgets.QCheckBox(self.tr("Thicker bond lines")) self.checkTighBond.stateChanged.connect(self.updateImage) layout.addWidget(self.checkTighBond, 8, 1, 1, 2) self.checkAsym = QtWidgets.QCheckBox(self.tr("Asymetric double bond")) self.checkAsym.stateChanged.connect(self.updateImage) layout.addWidget(self.checkAsym, 9, 1, 1, 2) if os.environ["openbabel"] == "False": self.example = QtWidgets.QLabel( self.tr("Openbabel library don´t found")) self.example.setStyleSheet("color: red") else: self.example = QtWidgets.QLabel() layout.addWidget(self.example, 13, 1, 1, 2, QtCore.Qt.AlignmentFlag.AlignCenter) layout.addItem(QtWidgets.QSpacerItem( 10, 0, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding), 14, 1, 1, 4) if config and config.has_section("Openbabel"): self.BondColor.setColor(config.get("Openbabel", 'BondColor')) self.BackColorTransparent.setChecked( config.getboolean("Openbabel", 'BackColorTransparent')) self.BackColor.setColor( config.get("Openbabel", 'BackgroundColor')) self.checkColor.setChecked( config.getboolean("Openbabel", 'AtomsColor')) self.radioAll.setChecked( config.getboolean("Openbabel", 'AtomsAll')) self.radioEnd.setChecked( config.getboolean("Openbabel", 'AtomsEnd')) self.radioNone.setChecked( config.getboolean("Openbabel", 'AtomsNone')) self.checkTighBond.setChecked( config.getboolean("Openbabel", 'TighBond')) self.checkAsym.setChecked( config.getboolean("Openbabel", 'AsymetricDouble')) self.updateImage(config)
[docs] def updateImage(self, config=None): """Update image shown when changing some of the parameters""" if os.environ["openbabel"] == "False": return if not isinstance(config, ConfigParser): config = ConfigParser() config = self.value(config) with tempfile.NamedTemporaryFile("w", suffix=".png") as imageFile: smile = "C=CN1C=NC2=C1C(=O)N(C(=O)N2C)C" imageFromSmile(smile, imageFile.name, config) self.example.setPixmap(QtGui.QPixmap(imageFile.name))
[docs] def value(self, config): """Update ConfigParser instance with the config""" if not config.has_section("Openbabel"): config.add_section("Openbabel") config.set("Openbabel", "BondColor", self.BondColor.color.name()) config.set("Openbabel", "BackgroundColor", self.BackColor.color.name(QtGui.QColor.NameFormat.HexRgb)) config.set("Openbabel", "BackColorTransparent", str(self.BackColorTransparent.isChecked())) config.set("Openbabel", "AtomsColor", str(self.checkColor.isChecked())) config.set("Openbabel", "AtomsAll", str(self.radioAll.isChecked())) config.set("Openbabel", "AtomsEnd", str(self.radioEnd.isChecked())) config.set("Openbabel", "AtomsNone", str(self.radioNone.isChecked())) config.set("Openbabel", "TighBond", str(self.checkTighBond.isChecked())) config.set("Openbabel", "AsymetricDouble", str(self.checkAsym.isChecked())) return config