Difference between revisions of "Plugins Tutorial"

From PyMOLWiki
Jump to: navigation, search
m (add see also Plugins)
(pymol2-demo-plugin)
Line 1: Line 1:
==Installing Plugins==
+
This tutorial is about writing your own plugin for PyMOL 2.x.
To install a plugin, simply save the plugin file and load it into PyMol with the ''Plugin -> Install Plugin...'' menu item. Alternatively, plugins can be installed by copying the file into the PYMOLPATH/module/pmg_tk/startup/ folder.
 
  
 +
See the [[plugins]] page for how to install and use exisiting plugins.
  
 
==Writing Plugins: Learn By Example==
 
==Writing Plugins: Learn By Example==
This tutorial is a more sophisticated version of the [[PDB Loader Service]] plugin that is bundled with PyMol. It is a relatively simple plugin, but should prove to be a good starting point for developing new plugins.
+
 
 +
This tutorial shows how to write a PyMOL plugin with PyQt. '''The full source of the demo plugin is [https://github.com/Pymol-Scripts/pymol2-demo-plugin available on github]'''.
 +
 
 +
The demo plugin adds a dialog to render images at a custom resolution.
 +
 
 +
=== Plugin Files ===
 +
 
 +
We will create a plugin which consists of [[PluginArchitecture#More than one file per plugin|multiple files inside a directory]]:
 +
 
 +
<source lang="Python">
 +
pymol2-demo-plugin/
 +
├── demowidget.ui
 +
└── __init__.py (defines "__init_plugin__(app=None)" function)
 +
</source>
  
 
===Registering your Plugin===
 
===Registering your Plugin===
First you must add your plugin to the ''Plugins'' menu. This is most easily done in the ''__init__'' method of your plugin. A callback, fetchPDBDialog, is added.  It is passed a reference to the main Tk app.  It will need this in order to create new content in the main Tkinter loop.  It is not strictly required in this example, but is needed in more complex interfaces.
+
 
 +
First you must add your plugin to the ''Plugins'' menu.
 +
This is done in the <code>__init_plugin__</code> function of your plugin.
 +
A callback (here: <code>run_plugin_gui</code>) is added.
  
 
<source lang="python">
 
<source lang="python">
def __init__(self):
+
# file pymol2-demo-plugin/__init__.py
     self.menuBar.addmenuitem('Plugin', 'command',
+
def __init_plugin__(app=None):
                            'PDB Loader Service',
+
     from pymol.plugins import addmenuitemqt
                            label = 'PDB Loader Service',
+
    addmenuitemqt('Demo "Render" Plugin', run_plugin_gui)
                            command = lambda s=self : fetchPDBDialog(s))
 
 
</source>
 
</source>
  
===Adding Functionality===
+
''Legacy note'': The <code>__init_plugin__</code> function takes one argument, a reference to the main Tk app to [[PluginArchitecture#init_plugin|support legacy plugins witten with Tkinter]] (unused with PyQt plugins).
For this example we are going to dynamically download, from the [http://www.rcsb.org/pdb/home/home.do Protein Data Bank], a molecular structure and then load it into the PyMOL interface.  Here is a simple method which does that, given a PDB-ID (''pdbCode'').
+
 
 +
=== Creating a GUI ===
 +
 
 +
The callback may do arbitrary stuff. Here we're going to create a dialog and show it to the user.
  
 
<source lang="python">
 
<source lang="python">
def remote(pdbCode):
+
def run_plugin_gui():
     pdbCode = pdbCode.upper()
+
     # pymol.Qt provides the PyQt5 interface, but may support PyQt4 and/or PySide as well
     try:
+
     from pymol.Qt import QtWidgets
      pdbFile = urllib2.urlopen('http://www.rcsb.org/pdb/cgi/export.cgi/' +
+
 
                                pdbCode + '.pdb.gz?format=PDB&pdbId=' +
+
    # create a new (empty) Window
                                pdbCode + '&compression=gz')
+
     dialog = QtWidgets.QDialog()
      cmd.read_pdbstr(zlib.decompress(pdbFile.read()[22:], -zlib.MAX_WBITS), pdbCode)
 
     except:
 
      print "Unexpected error:", sys.exc_info()[0]
 
      tkMessageBox.showerror('Invalid Code',
 
                              'You entered an invalid pdb code:' + pdbCode)
 
</source>
 
  
===Creating the Interface===
+
    # TODO: FILL DIALOG WITH WIDGETS HERE
Now we need to write the callback method, ''fetchPDBDialog'', which creates the interface.
 
  
<source lang="python">
+
     dialog.show()
def fetchPDBDialog(app):
 
     pdbCode = tkSimpleDialog.askstring('PDB Loader Service',
 
                                      'Please enter a 4-digit pdb code:',
 
                                      parent=app.root)
 
    remote(pdbCode)
 
 
</source>
 
</source>
  
Pretty simple!  As of now you have a fully functional plugin that will prompt the user for a pdb code, download it from [http://www.rcsb.org/pdb/ Protein Data Bank], and load it into the PyMOL interface.
+
=== Filling the GUI with Widgets ===
  
===Extending Plugins to the Command Line===
+
[[Image:designer-demo-plugin.png|thumb|right|400px|Screenshot of Qt Designer]]
Opening the dialog can be tedious, so let's add a command line callback to make use of this new functionality.
 
  
<source lang="python">
+
The most convenient and maintainable way to create user interfaces with Qt is by using the [http://doc.qt.io/qt-5/qtdesigner-manual.html Qt Designer].
cmd.extend('remote', remote)
+
Follow these steps to get started:
</source>
+
 
 +
# Create a new "Widget" form (New Form &gt; templates/forms &gt; Widget &gt; Create)
 +
# Drag widgets like "Push Button" or "Line Edit" into your form
 +
# Name your widgets ("objectName" in the "Property Editor"), this name will become a Python variable in your code
 +
# Save as a UI file (<code>*.ui</code>) inside the <code>pymol2-demo-plugin</code> directory
  
Now you can type something like this:
+
PyMOL provides a utility function to load a UI file into a parent widget: <code>pymol.Qt.utils.loadUi(filename, widget)</code>
  
<source lang="python">
+
<syntaxhighlight lang="python">
remote 1di9
+
    # filename of our UI file
</source>
+
    uifile = os.path.join(os.path.dirname(__file__), 'demowidget.ui')
  
to load the the corresponding pdb.
+
    # load the UI file into our dialog
 +
    from pymol.Qt.utils import loadUi
 +
    form = loadUi(uifile, dialog)
 +
</syntaxhighlight>
  
==Full Source==
+
=== Make Buttons do something ===
Here is the complete example.  You can save this file and load it into PyMol with the ''Plugin -> Install Plugin...'' menu item.  The example also includes the sample license that Warren provides for plugin developers. It is optional, of course. You can read [http://opensource.org/licenses here] about other open-source licenses you could use for your own project. 
 
  
<source lang="python">
+
We need to connect the [http://doc.qt.io/qt-5/signalsandslots.html signals] of those widgets we created with the Qt Designer, like our buttons' <code>clicked</code> signal.
# Copyright Notice
+
Our example has a "Ray" button (''objectName=button_ray'') that we'll connect to the <code>run()</code> function.
# ================
 
#
 
# The PyMOL Plugin source code in this file is copyrighted, but you can
 
# freely use and copy it as long as you don't change or remove any of
 
# the copyright notices.
 
#
 
# ----------------------------------------------------------------------
 
# This PyMOL Plugin is Copyright (C) 2004 by Charles Moad <cmoad@indiana.edu>
 
#
 
#                        All Rights Reserved
 
#
 
# Permission to use, copy, modify, distribute, and distribute modified
 
# versions of this software and its documentation for any purpose and
 
# without fee is hereby granted, provided that the above copyright
 
# notice appear in all copies and that both the copyright notice and
 
# this permission notice appear in supporting documentation, and that
 
# the name(s) of the author(s) not be used in advertising or publicity
 
# pertaining to distribution of the software without specific, written
 
# prior permission.
 
#
 
# THE AUTHOR(S) DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 
# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN
 
# NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY SPECIAL, INDIRECT OR
 
# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF
 
# USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
 
# OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 
# PERFORMANCE OF THIS SOFTWARE.
 
# ----------------------------------------------------------------------
 
  
import tkSimpleDialog
+
<syntaxhighlight lang="python">
import tkMessageBox
+
    def run():
from pymol import cmd
+
        # get form data
import sys, urllib2, zlib
+
        height = form.input_height.value()
  
def __init__(self):
+
        # some debugging feedback
  self.menuBar.addmenuitem('Plugin', 'command',
+
        print('User entered height', height)
                            'PDB Loader Service',
 
                            label = 'PDB Loader Service',
 
                            command = lambda s=self : fetchPDBDialog(s))
 
  
def remote(pdbCode):
+
        # TODO: DO SOMETHING WITH FORM DATA
  pdbCode = pdbCode.upper()
 
  try:
 
      pdbFile = urllib2.urlopen('http://www.rcsb.org/pdb/cgi/export.cgi/' +
 
                                pdbCode + '.pdb.gz?format=PDB&pdbId=' +
 
                                pdbCode + '&compression=gz')
 
      cmd.read_pdbstr(zlib.decompress(pdbFile.read()[22:], -zlib.MAX_WBITS), pdbCode)
 
  except:
 
      print "Unexpected error:", sys.exc_info()[0]
 
      tkMessageBox.showerror('Invalid Code',
 
                            'You entered an invalid pdb code:' + pdbCode)
 
  
def fetchPDBDialog(app):
+
    form.button_ray.clicked.connect(run)
  pdbCode = tkSimpleDialog.askstring('PDB Loader Service',
+
</syntaxhighlight>
                                      'Please enter a 4-digit pdb code:',
 
                                      parent=app.root)
 
 
 
  remote(pdbCode)
 
  
cmd.extend('remote', remote)
+
=== Deploy the final plugin ===
</source>
 
  
 +
The <code>pymol2-demo-plugin</code> directory can be zipped for deployment (see [[PluginArchitecture#More than one file per plugin]]).
  
==Working with more than one file==
+
=== Full Source ===
Plugins are not limited to one file. It is possible to have more than one .py file or even data files or directories. In order to distribute your multi-file plugin you should put all the necessary files into a folder (named after your plugin's name) and then zip it. You will distribute your plugin as a .zip file. The PyMOL plugin manager is able to install the .zip file.
 
  
Inside the folder with the name of your plugin you should put the followings files:
+
The full source of the demo plugin is [https://github.com/Pymol-Scripts/pymol2-demo-plugin available on github]'''.
* __init__.py (this should be the main file, the one with the instructions to register the plugin)
 
* data files (optional)
 
* directories (optional)
 
* other .py files (optional)
 
  
To locate your data files (if any) add the following line inside the __init__.py file
+
== Extending Plugins to the Command Line ==
  
<source lang="python">
+
While not really applicable to our simple "Render" plugin (it only wraps existing <code>ray</code> and <code>png</code> commands), most plugins should also provide their functionality as new PyMOL commands using [[extend|cmd.extend()]].
path = os.path.dirname(__file__)
 
</source>
 
and the following line if your plugin consist of more that one .py file.
 
  
<source lang="python">
+
== See Also ==
sys.path.append(path)
 
</source>
 
  
=SEE ALSO=
+
* [[PluginArchitecture]]
[[Plugins]]
+
* [[Plugin Manager]]
 +
* [[Plugins]]
 +
* [[extend|cmd.extend()]]
  
 
[[Category:Developers|Plugins_Tutorial]]
 
[[Category:Developers|Plugins_Tutorial]]
 
[[Category:Plugins|Plugins_Tutorial]]
 
[[Category:Plugins|Plugins_Tutorial]]
 
[[Category:Tutorials]]
 
[[Category:Tutorials]]

Revision as of 14:17, 13 November 2018

This tutorial is about writing your own plugin for PyMOL 2.x.

See the plugins page for how to install and use exisiting plugins.

Writing Plugins: Learn By Example

This tutorial shows how to write a PyMOL plugin with PyQt. The full source of the demo plugin is available on github.

The demo plugin adds a dialog to render images at a custom resolution.

Plugin Files

We will create a plugin which consists of multiple files inside a directory:

pymol2-demo-plugin/
├── demowidget.ui
└── __init__.py (defines "__init_plugin__(app=None)" function)

Registering your Plugin

First you must add your plugin to the Plugins menu. This is done in the __init_plugin__ function of your plugin. A callback (here: run_plugin_gui) is added.

# file pymol2-demo-plugin/__init__.py
def __init_plugin__(app=None):
    from pymol.plugins import addmenuitemqt
    addmenuitemqt('Demo "Render" Plugin', run_plugin_gui)

Legacy note: The __init_plugin__ function takes one argument, a reference to the main Tk app to support legacy plugins witten with Tkinter (unused with PyQt plugins).

Creating a GUI

The callback may do arbitrary stuff. Here we're going to create a dialog and show it to the user.

def run_plugin_gui():
    # pymol.Qt provides the PyQt5 interface, but may support PyQt4 and/or PySide as well
    from pymol.Qt import QtWidgets

    # create a new (empty) Window
    dialog = QtWidgets.QDialog()

    # TODO: FILL DIALOG WITH WIDGETS HERE

    dialog.show()

Filling the GUI with Widgets

Screenshot of Qt Designer

The most convenient and maintainable way to create user interfaces with Qt is by using the Qt Designer. Follow these steps to get started:

  1. Create a new "Widget" form (New Form > templates/forms > Widget > Create)
  2. Drag widgets like "Push Button" or "Line Edit" into your form
  3. Name your widgets ("objectName" in the "Property Editor"), this name will become a Python variable in your code
  4. Save as a UI file (*.ui) inside the pymol2-demo-plugin directory

PyMOL provides a utility function to load a UI file into a parent widget: pymol.Qt.utils.loadUi(filename, widget)

    # filename of our UI file
    uifile = os.path.join(os.path.dirname(__file__), 'demowidget.ui')

    # load the UI file into our dialog
    from pymol.Qt.utils import loadUi
    form = loadUi(uifile, dialog)

Make Buttons do something

We need to connect the signals of those widgets we created with the Qt Designer, like our buttons' clicked signal. Our example has a "Ray" button (objectName=button_ray) that we'll connect to the run() function.

    def run():
        # get form data
        height = form.input_height.value()

        # some debugging feedback
        print('User entered height', height)

        # TODO: DO SOMETHING WITH FORM DATA

    form.button_ray.clicked.connect(run)

Deploy the final plugin

The pymol2-demo-plugin directory can be zipped for deployment (see PluginArchitecture#More than one file per plugin).

Full Source

The full source of the demo plugin is available on github.

Extending Plugins to the Command Line

While not really applicable to our simple "Render" plugin (it only wraps existing ray and png commands), most plugins should also provide their functionality as new PyMOL commands using cmd.extend().

See Also