This is a read-only mirror of pymolwiki.org

Difference between revisions of "PluginArchitecture"

From PyMOL Wiki
Jump to navigation Jump to search
 
m (1 revision)
 
(22 intermediate revisions by 5 users not shown)
Line 1: Line 1:
This page concerns the development for new PyMOL Plugin Architecture. We are requesting ideas, code, etc on how to best implement a successful plug in system.
+
This page describes PyMOL's plugin architechture since version 1.5.0.6.
  
= Review of Current Plugin System =
+
== API Entry Points ==
  
= Ideas/Features for the New Plugin System =
+
A plugin is a Python module which uses PyMOL's API. The following entrypoints add functionality to PyMOL:
 +
 
 +
# [[extend|<code>pymol.cmd.extend(func)</code>]] registers a function as a PyMOL command
 +
# <code>pymol.plugins.addmenuitemqt(label, callback)</code> adds a plugin menu item, should be called inside [[#__init__plugin__|<code>__init_plugin__</code>]]
 +
# [[#__init__plugin__|<code>MyPlugin.__init_plugin__(app)</code>]] is called during plugin initialization (if defined by the plugin)
 +
 
 +
== __init_plugin__ ==
 +
 
 +
A plugin can implement an <code>__init_plugin__(app)</code> function (''previously <code>__init__</code>, deprecated'') which is called during plugin initialization. It is passed an object which is compatible with the legacy <code>PMGApp</code> instance and provides access to the Tkinter parent (<code>app.root</code>).
 +
 
 +
=== Example for a PyQt5 GUI (PyMOL 2.x) ===
 +
 
 +
''See also the [[Plugins Tutorial]]''
 +
 
 +
<syntaxhighlight lang="python">
 +
def __init_plugin__(app=None):
 +
    from pymol.plugins import addmenuitemqt
 +
    addmenuitemqt('My Qt Plugin', myqtdialog)
 +
 
 +
def myqtdialog():
 +
    from pymol.Qt import QtWidgets
 +
    QtWidgets.QMessageBox.information(None, 'My Plugin Title', 'Hello World')
 +
</syntaxhighlight>
 +
 
 +
=== Example for a legacy Tkinter GUI (PyMOL 1.x) ===
 +
 
 +
It is important that all Tkinter objects are created with the <code>app.root</code> parent, otherwise legacy plugins won't work in PyMOL 2.0.
 +
 
 +
<syntaxhighlight lang="python">
 +
def __init_plugin__(app):
 +
    app.menuBar.addmenuitem('Plugin', 'command',
 +
        label='My Tk Plugin',
 +
        command=lambda: mytkdialog(app.root))
 +
 
 +
def mytkdialog(parent):
 +
    try:
 +
        import tkMessageBox  # Python 2
 +
    except ImportError:
 +
        import tkinter.messagebox as tkMessageBox  # Python 3
 +
    tkMessageBox.showinfo(parent=parent, title='My Plugin Title', message='Hello World')
 +
</syntaxhighlight>
  
 
== More than one file per plugin ==
 
== More than one file per plugin ==
  
I'd like plugins that behave like proper Python modules with the ability to span multiple files/directories. One simple way to do this would be to distribute plugins as zip files. E.g. if we have a directory Foo that contains
+
Plugins can be either a single Python file, or a directory with an <code>__init__.py</code> file.
 +
 
 +
'''Single file layout:'''
 +
 
 +
<syntaxhighlight lang="python">
 +
MyPlugin.py (defines "__init_plugin__(app=None)" function)
 +
</syntaxhighlight>
 +
 
 +
'''Directory layout:'''
  
 
<source lang="Python">
 
<source lang="Python">
MyMod
+
MyPlugin/
    __init__.py
+
├── data
          """Some documentation here"""
+
│   ├── datasheet.txt
    Foo.py
+
│   └── image.png
        def doit():
+
├── submodule1.py
            print "It is done!"
+
├── submodule2.py
 +
└── __init__.py (defines "__init_plugin__(app=None)" function)
 
</source>
 
</source>
  
We can zip it up like
+
They can be zipped (<code>.zip</code>) for distribution, the [[Plugin Manager]] handles the unzipping during installation.
  
<source lang="bash"
+
<source lang="bash">
zip MyMod.zip Foo/*
+
zip -r MyPlugin-1.0.zip MyPlugin
 
</source>
 
</source>
  
If you stick MyMod.zip in a plugins directory like /blahblah/Pymol/plugins/MyMod.zip
+
== Config files ==
you can then say this:
+
 
 +
PyMOL offers a basic API to store custom settings in <code>~/.pymolpluginsrc.py</code>.
 +
Only basic types (<code>str</code>,
 +
<code>int</code>,
 +
<code>float</code>,
 +
<code>list</code>,
 +
<code>tuple</code>,
 +
<code>dict</code>,
 +
etc.)
 +
are suppored (those who produce executable code with <code>repr()</code>).
  
<source lang="Python">
+
'''Store:'''
sys.path.append('/blahblah/Pymol/plugins/MyMod.zip')
+
 
from MyMod import Foo
+
<syntaxhighlight lang="python">
Foo.doit()
+
pymol.plugins.pref_set(key, value)
</source>
 
  
Python does not impose the restriction that the module/directory name (MyMod) should be the same as the zip file name (MyMod.zip), but we might want to.
+
# needed if pymol.plugins.pref_get("instantsave") == False
 +
pymol.plugins.pref_save()
 +
</syntaxhighlight>
  
Then we need to formally spell out the API by which the Plugin is launched.
+
'''Load:'''
  
This makes it very easy to install/uninstall plugins, and PyMOL can just have a simple loop that iterates over the plugins directory, adds the appropriate things to sys.path, verifies that the resulting objects support the API, and adds them to the menu.
+
<syntaxhighlight lang="python">
 +
value = pymol.plugins.pref_get(key, default=None)
 +
</syntaxhighlight>
  
== Config files ==
+
'''Example:'''
  
Some sort of persistent config files where users can store things like paths to relevant files, settings, etc. would be very useful. The APBS plugin, for instance, is a bit of a bother to use when you have APBS installed in a non-standard location. It would be very convenient if you could set the location once and have it persist.
+
<syntaxhighlight lang="python">
 +
apbsbinary = pymol.plugins.pref_get("APBS_BINARY_LOCATION", "/usr/bin/apbs")
 +
</syntaxhighlight>
  
= Liked Plugin Systems =
+
== PyMOL OS Fellowship Project 2011/2012 ==
* [https://addons.mozilla.org/en-US/developers FireFox Devel/Docs]
 
* Google Chrome -- URL?
 
  
= Disliked Plugin Systems =
+
[[User:Speleo3|Thomas Holder]] was working on the new plugin system as part of his [http://pymol.org/fellowship 2011-2012 Fellowship]. The improvements were incorporated into PyMOL 1.5.0.5.
  
 +
* graphical [[Plugin Manager]]
 +
* multiple files per plugin/module (see [[#More than one file per plugin]])
 +
* user plugin directory
 +
* rarely used plugins can be disabled for better performance and enabled on demand
 +
* metadata support (version, author, description, citation, tags, ...)
 +
* install from online repositories
 +
* settings API for plugins (see [[#Config files]])
  
= The Name =
+
== See Also ==
* Plugins, Plug-Ins?
 
* Add-ons?
 
* Extensions?
 
  
 +
* [[Plugin Manager]]
 +
* [[Plugins Tutorial]]
 +
* [[Plugins]]
  
 +
[[Category:Plugins]]
 
[[Category:Development]]
 
[[Category:Development]]
 
[[Category:Developers]]
 
[[Category:Developers]]

Latest revision as of 03:24, 10 December 2018

This page describes PyMOL's plugin architechture since version 1.5.0.6.

API Entry Points

A plugin is a Python module which uses PyMOL's API. The following entrypoints add functionality to PyMOL:

  1. pymol.cmd.extend(func) registers a function as a PyMOL command
  2. pymol.plugins.addmenuitemqt(label, callback) adds a plugin menu item, should be called inside __init_plugin__
  3. MyPlugin.__init_plugin__(app) is called during plugin initialization (if defined by the plugin)

__init_plugin__

A plugin can implement an __init_plugin__(app) function (previously __init__, deprecated) which is called during plugin initialization. It is passed an object which is compatible with the legacy PMGApp instance and provides access to the Tkinter parent (app.root).

Example for a PyQt5 GUI (PyMOL 2.x)

See also the Plugins Tutorial

def __init_plugin__(app=None):
    from pymol.plugins import addmenuitemqt
    addmenuitemqt('My Qt Plugin', myqtdialog)

def myqtdialog():
    from pymol.Qt import QtWidgets
    QtWidgets.QMessageBox.information(None, 'My Plugin Title', 'Hello World')

Example for a legacy Tkinter GUI (PyMOL 1.x)

It is important that all Tkinter objects are created with the app.root parent, otherwise legacy plugins won't work in PyMOL 2.0.

def __init_plugin__(app):
    app.menuBar.addmenuitem('Plugin', 'command',
        label='My Tk Plugin',
        command=lambda: mytkdialog(app.root))

def mytkdialog(parent):
    try:
        import tkMessageBox  # Python 2
    except ImportError:
        import tkinter.messagebox as tkMessageBox  # Python 3
    tkMessageBox.showinfo(parent=parent, title='My Plugin Title', message='Hello World')

More than one file per plugin

Plugins can be either a single Python file, or a directory with an __init__.py file.

Single file layout:

MyPlugin.py (defines "__init_plugin__(app=None)" function)

Directory layout:

MyPlugin/
├── data
   ├── datasheet.txt
   └── image.png
├── submodule1.py
├── submodule2.py
└── __init__.py (defines "__init_plugin__(app=None)" function)

They can be zipped (.zip) for distribution, the Plugin Manager handles the unzipping during installation.

zip -r MyPlugin-1.0.zip MyPlugin

Config files

PyMOL offers a basic API to store custom settings in ~/.pymolpluginsrc.py. Only basic types (str, int, float, list, tuple, dict, etc.) are suppored (those who produce executable code with repr()).

Store:

pymol.plugins.pref_set(key, value)

# needed if pymol.plugins.pref_get("instantsave") == False
pymol.plugins.pref_save()

Load:

value = pymol.plugins.pref_get(key, default=None)

Example:

apbsbinary = pymol.plugins.pref_get("APBS_BINARY_LOCATION", "/usr/bin/apbs")

PyMOL OS Fellowship Project 2011/2012

Thomas Holder was working on the new plugin system as part of his 2011-2012 Fellowship. The improvements were incorporated into PyMOL 1.5.0.5.

  • graphical Plugin Manager
  • multiple files per plugin/module (see #More than one file per plugin)
  • user plugin directory
  • rarely used plugins can be disabled for better performance and enabled on demand
  • metadata support (version, author, description, citation, tags, ...)
  • install from online repositories
  • settings API for plugins (see #Config files)

See Also