Source code for atomeye

# HQ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
# HQ X
# HQ X   quippy: Python interface to QUIP atomistic simulation library
# HQ X
# HQ X   Copyright James Kermode 2010
# HQ X
# HQ X   These portions of the source code are released under the GNU General
# HQ X   Public License, version 2, http://www.gnu.org/copyleft/gpl.html
# HQ X
# HQ X   If you would like to license the source code under different terms,
# HQ X   please contact James Kermode, james.kermode@gmail.com
# HQ X
# HQ X   When using this software, please cite the following reference:
# HQ X
# HQ X   http://www.jrkermode.co.uk/quippy
# HQ X
# HQ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

"""

This module provides the :class:`AtomEyeViewer` class, which is a high-level
interface for interactive visualisation of Atoms objects using a `modified version
<http://www.jrkermode.co.uk/AtomEye>`_ of the `AtomEye <http://mt.seas.upenn.edu/Archive/Graphics/A/>`_
atomistic configuration viewer.

:class:`~quippy.atoms.Atoms` and :class:`~quippy.io.AtomsList`
objects can also be visualised with the :mod:`qlab` module.

For example, to create and visualise and 8-atom silicon bulk cell::

   from quippy.structures import diamond, supercell
   from atomeye import AtomEyeViewer

   d = diamond(5.43, 14)
   viewer = AtomEyeViewer(d)

A window will pop up containing the silicon unit cell, which you can manipulate
with the mouse, by issuing commands in the the python console or
with a variety of AtomEye `keyboard shortcuts
<http://li.mit.edu/Archive/Graphics/A3/A3.html#keybinding>`_. To save an image in PNG format::

   viewer.capture('si8.png')

.. image:: si8.png
   :align: center

Then, to change the display to a :math:`2\times2\times2` supercell of
bulk silicon, change the background colour to black, set the size and
save an image you'd do the following::

   at = supercell(d, 2, 2, 2)
   viewer.show(at)

   viewer.change_bgcolor((0, 0, 0))
   viewer.resize(400,300)
   viewer.capture('si2x2x2.png')

.. image:: si2x2x2.png
   :align: center

Module attributes:

.. attribute:: viewers

   Dictionary mapping `window_id` to :class:`AtomEyeView` instances. There
   is one entry for each currently open AtomEye view window.

.. attribute:: default_state

   Dictionary of key/value pairs passed to :meth:`AtomEyeView.update` when
   a new window is created. Change this dictionary to modfiy the properties
   of new windows. The initial value is as follows::

     default_state = {
	 'variables' : {'n->xtal_mode': 1,
			'n->suppress_printout': 1,
			'n->bond_mode': 1,
			'n->atom_r_ratio': 0.5,
			'n->small_cell_err_handler': 1,
			'key->BackSpace': 'load_config_backward'
			},
	 'commands': ['xtal_origin_goto 0.5 0.5 0.5',
		      'toggle_parallel_projection'],
	 'cutoff_lengths': []
     }
"""

import sys
import time
import imp
import os
import os.path
import shutil
import sys
import tempfile
import atexit
from math import ceil, log10
import numpy as np

__all__ = ['AtomEyeViewer', 'view', 'redraw', 'run_command', 'run_script', 'close',
           'setp', 'save_script', 'toggle_coordination_coloring', 'translate',
           'shift_xtal', 'rotate', 'advance', 'shift_cutting_plane', 'change_bgcolor',
           'change_atom_r_ratio', 'change_bond_radius', 'change_view_angle_amplification',
           'toggle_parallel_projection', 'toggle_bond_mode', 'toggle_small_cell_mode',
           'normal_coloring', 'aux_property_coloring', 'central_symmetry_coloring',
           'change_aux_property_threshold', 'reset_aux_property_thresholds',
           'toggle_aux_property_thresholds_saturation', 'toggle_aux_property_thresholds_rigid',
           'rcut_patch', 'select_gear', 'cutting_plane', 'shift_cutting_plane_to_anchor',
           'delete_cutting_plane', 'flip_cutting_plane', 'capture', 'change_wireframe_mode',
           'change_cutting_plane_wireframe_mode', 'get_frame', 'set_frame', 'get_delta',
           'set_delta', 'first', 'last', 'forward', 'backward', 'load_atom_color',
           'load_aux', 'look_at_the_anchor', 'observer_goto', 'xtal_origin_goto',
           'find_atom', 'resize', 'change_aux_colormap', 'draw_arrows', 'wait', 'display']

try:
    # try to use same fortran_indexing setting as quippy, if it's installed
    from quippy import get_fortran_indexing, set_fortran_indexing
    
except ImportError:
    # quippy is not available, so we define our own fortran_indexing setting

    _fortran_indexing = False
    
    def get_fortran_indexing():
        global _fortran_indexing
        return _fortran_indexing

    def set_fortran_indexing(fortran_indexing):
        global _fortran_indexing
        _fortran_indexing = fortran_indexing

    __all__.extend(['get_fortran_indexing', 'set_fortran_indexing'])
                             

def _tmp_pkg(dir=None):
    """
    Create a temporary package.

    Returns (name, path)
    """
    while True:
        path = tempfile.mkdtemp(dir=dir)
        name = os.path.basename(path)
        try:
            modinfo = imp.find_module(name)
            # if name is found, delete and try again
            os.rmdir(path)
        except:
            break
    init = file(os.path.join(path, '__init__.py'), 'w')
    init.close()

    # remove directory at exit
    atexit.register(shutil.rmtree, path)
    
    return name, path


class MultipleExtMod(object):
    """
    Load a unique copy of a module that can be treated as a "class instance".

    Adapted from code posted by Tod. A. Smith <Tod.A.Smith@aadc.com> to
    http://cens.ioc.ee/pipermail/f2py-users/2004-September/000921.html
    """

    count = {}

    def __init__(self, name):
        self.count = MultipleExtMod.count[name] = MultipleExtMod.count.setdefault(name, 0) + 1
        self.name = name
        # first find the "real" module on the "real" syspath
        srcfile, srcpath, srcdesc = imp.find_module(name)
        # now create a temp directory for the bogus package
        self._pkgname, self._pkgdir = _tmp_pkg()

        # add parent directory to sys.path if necessary
        if os.path.dirname(self._pkgdir) not in sys.path:
            sys.path.append(os.path.dirname(self._pkgdir))
        
        # copy the original module to the new package
        shutil.copy(srcpath, self._pkgdir)
        # import the module
        # __import__ returns the package, not the sub-module
        self._pkg = __import__(self._pkgname, globals(), locals(), [self.name])
        # return the module object
        self._module = getattr(self._pkg, self.name)
        # now add the module's stuff to this class instance
        self.__dict__.update(self._module.__dict__)


default_state = {
    'variables' : {'n->xtal_mode': 1,
                   'n->suppress_printout': 1,
                   'n->bond_mode': 1,
                   'n->small_cell_err_handler': 1,
                   'key->BackSpace': 'load_config_backward'
                   },
    'commands': ['xtal_origin_goto 0.5 0.5 0.5',
                 #'toggle_parallel_projection',
                 'resize 800 600',
                 'change_atom_r_ratio -2.0'],
    'cutoff_lengths': []
}

name_map = {'positions': 'pos',
            'masses'   : 'mass',
            'numbers'  : 'Z' }

viewers = {}

[docs]class AtomEyeViewer(object): """ View an atomic configuration or trajectory with AtomEye Class to represent an AtomEye viewer window. Each viewer class communicates with one AtomEye thread. There are wrapper methods for most of the `AtomEye 3 commands <http://mt.seas.upenn.edu/Archive/Graphics/A3/A3.html#commands>`_. The names of these methods match the names of the correspondning commands, and the arguments follow the syntax given on the AtomEye 3 help page. Additional keyword arguments are passed along to the :meth:`show` method. Import :class:`AtomEyeView` attributes: .. attribute:: atoms :class:`Atoms` object or sequence being viewed. This will be set to ``None`` if this instance was created without an ``obj`` parameter, which means we're viewing the ``A3`` logo. .. attribute:: frame Current frame, in range 1 to `len(self.atoms)`. .. attribute:: delta Frame increment rate when :kbd:`Delete` and :kbd:`Insert` are preseed. Equivalent to AtomEye ``n->glob_advance`` setting. .. attribute:: echo If set to True, echo all AtomEye commands to stdout .. attribute:: block If set to True, wait for all AtomEye command to finish executing before returning from function calls. Default is False. .. attribute:: verbose If set to True (default), print frame paramters on each :meth:`redraw`, and print information about each atom when it is right clicked. Parameters ---------- atoms : :class:`quippy.atoms.Atoms` or :class:`ase.atoms.Atoms` object, or a list of objects. Configuration or trajectory to view. viewer_id : integer or None If None, open a new viewer. Otherwise call the :meth:show() method in the existing viewer with this ID. copy : integer or None Viewer ID of another viewer from which to copy the viewpoint and other default settings. frame : integer Initial frame to show (should be in range ``0..len(atoms)-1``) delta : integer Increment/decrement rate for frames when [Insert] and [Delete] are pressed nowindow : bool If True, open AtomEye without a visible window. Useful for faster rendering of movies echo : bool If True, echo all commands to the screen. Useful mainly for debugging. block : bool If True, wait for commands to finish executing in AtomEye thread before returning (i.e. run asynchronously) verbose : bool If True, print information when changing frame and when an atom is clicked """ CONFIG_MAX_AUXILIARY = 64 def __init__(self, atoms=None, viewer_id=None, copy=None, frame=0, delta=1, nowindow=False, echo=False, block=False, verbose=False, **showargs): self.atoms = atoms self._current_atoms = None self._previous_atoms = None self._frame = frame self.delta = delta self.echo = echo self.block = block self.verbose = verbose self.is_alive = False if viewer_id is None: self.start(copy, nowindow) else: self._viewer_id = viewer_id self._window_id = viewer_id[1] self.is_alive = True viewers[self._viewer_id] = self self.show(**showargs)
[docs] def start(self, copy=None, nowindow=False): """ Start the AtomEye thread, wait for it to load and apply default commands. Parameters ---------- copy : integer Viewer ID of another AtomEye window from which to copy settings nowindow : bool If True, create an AtomEye thread without a visible window. Useful for rendering movies. """ if self.is_alive: return icopy = -1 if copy is not None: if isinstance(copy, AtomEye): icopy = copy._window_id elif isinstance(copy, int): icopy = copy else: raise TypeError('copy should be either an int or an AtomEye instance') # create our own unique version of the _atomeye extension module self._atomeye = MultipleExtMod('_atomeye') self._atomeye.set_handlers(AtomEyeViewer.on_click, AtomEyeViewer.on_close, AtomEyeViewer.on_advance, AtomEyeViewer.on_new_window, AtomEyeViewer.on_redraw) self.is_alive = False self._module_id = self._atomeye.count self._window_id = len([ viewer for viewer in viewers if viewer[0] == self._module_id ]) self._viewer_id = (self._module_id, self._window_id) print 'Initialising AtomEyeViewer with module_id %d and window id %s' % self._viewer_id viewers[self._viewer_id] = self atomeye_window_id = self._atomeye.open_window(self._module_id, icopy, nowindow) #assert atomeye_window_id == self._window_id while not self.is_alive: time.sleep(0.1) time.sleep(0.3) self.set_state(default_state) self.wait()
def _click_hook(self, at, idx): pass def _enter_hook(self, at): pass def _exit_hook(self, at): pass def _close_hook(self): pass def _redraw_hook(self, at): pass def _property_hook(self, at, auxprop): return auxprop @staticmethod def on_click(mod, iw, idx): if (mod,iw) not in viewers: raise RuntimeError('Unexpected module id %d or window id %d' % (mod, iw)) self = viewers[(mod,iw)] at = self.gcat() if at is None: return if idx >= len(at): idx = idx % len(at) if get_fortran_indexing(): idx = idx + 1 # atomeye uses zero based indices self._click_hook(at, idx) @staticmethod def on_advance(mod, iw, mode): if (mod,iw) not in viewers: raise RuntimeError('Unexpected window id %d' % iw) self = viewers[(mod, iw)] if not hasattr(self.atoms,'__iter__'): return if mode not in ['forward', 'backward', 'first', 'last']: raise RuntimeError('Unexpected advance mode "%s"' % mode) getattr(self, mode)() @staticmethod def on_close(mod, iw): if (mod, iw) not in viewers: raise RuntimeError('Unexpected window id %d' % iw) self = viewers[(mod,iw)] self.is_alive = False del viewers[self._viewer_id] self._close_hook() @staticmethod def on_new_window(mod, iw): if (mod,iw) in viewers: viewers[(mod,iw)].is_alive = True else: new_viewer = AtomEyeViewer(viewer_id=(mod,iw)) @staticmethod def on_redraw(mod, iw): if (mod, iw) not in viewers: raise RuntimeError('Unexpected window id %d' % iw) self = viewers[(mod,iw)] # keep a reference to old atoms around so memory doesn't get free'd prematurely self._previous_atoms = self._current_atoms if self._previous_atoms is not None: self._exit_hook(self._previous_atoms) self._current_atoms = None if self.atoms is None: title = '(null)' n_atom = 0 cell = None arrays = None redraw = 0 else: name = 'Atoms' if hasattr(self._current_atoms, 'filename') and self._current_atoms.filename is not None: name = self._current_atoms.filename if hasattr(self._current_atoms, 'name') and self._current_atoms.name is not None: name = self._current_atoms.name self._current_atoms = self.gcat(update=True) if hasattr(self.atoms, '__iter__'): fmt = "%%0%dd" % ceil(log10(len(self.atoms)+1)) title = '%s frame %s length %s' % (name, fmt % self._frame, fmt % len(self.atoms)) else: title = name self._enter_hook(self._current_atoms) n_atom = len(self._current_atoms) cell = self._current_atoms.get_cell() pbc = self._current_atoms.get_pbc() pos = self._current_atoms.positions for i, p in enumerate(pbc): if not p: cell[i,i] = max(cell[i,i], max(1.0, 2*(pos[:,i].max()-pos[:,i].min()))) try: arrays = self._current_atoms.properties except AttributeError: arrays = {} for key,value in self._current_atoms.arrays.iteritems(): arrays[name_map.get(key,key)] = value redraw = 1 # FIXME, we should decide if we really have to redraw here if redraw and self.verbose: print '-'*len(title) print title print '-'*len(title) print 'Number of atoms: %d' % n_atom print 'Fortran indexing: %r' % get_fortran_indexing() print 'Unit cell:' print cell self._redraw_hook(self._current_atoms) print '\n' sys.stdout.flush() return (redraw, title, n_atom, cell, arrays)
[docs] def gcat(self, update=False): """Get current atoms - return Atoms object currently being visualised. If update=False (the default), we return what is currently being visualised, even if this is not in sync with self.atoms[self.frame].""" if not update and self._current_atoms is not None: return self._current_atoms else: if hasattr(self.atoms, '__iter__'): return self.atoms[self._frame % len(self.atoms)] else: return self.atoms
[docs] def scat(self, atoms, frame=None): """Set current atoms (and optionally also current frame)""" if atoms is not None: self.atoms = atoms if frame is not None and hasattr(self.atoms, '__iter__'): self._frame = frame % len(self.atoms) self.redraw()
[docs] def show(self, atoms=None, property=None, frame=None, arrows=None): """ Update what is shown in this AtomEye viewer window. When called with no arguments, :meth:`show` is equivalent to :meth:`redraw`. Parameters ---------- atoms : class:`quippy.atoms.Atoms or `ase.atoms.Atoms` instance, or a list of instances property : name of the quippy :``~quippy.atoms.properties`` entry or ASE :attr:`ase.atoms.arrays` entry used to colour the atoms (e.g. ``"charge"``) frame : Zero-based index of the frame to show (applies only when `atoms` is a list of Atoms objects) arrows : is the name of a vector property to use to draw arrows on the atoms (e.g. ``"force"``) """ if not self.is_alive: raise RuntimeError('is_alive is False') self.scat(atoms, frame) if property is not None: self.aux_property_coloring(property) if arrows is not None: self.draw_arrows(arrows) self.redraw()
[docs] def redraw(self): """ Redraw this AtomEye window, keeping Atoms and settings the same. """ self._atomeye.redraw(self._window_id)
[docs] def run_command(self, command): """ Run a command in this AtomEye thread. The command is queued for later execution, unless :attr:`block` is True. This functionality is also available by calling an instance directly, i.e. the following commands are equivalent:: viewer.run_command('toggle_coordination_coloring') viewer('toggle_coordination_coloring') Parameters ---------- command : string The command to pass to AtomEye """ if not self.is_alive: raise RuntimeError('is_alive is False') if self.echo: print command.strip() try: self._atomeye.run_command(self._window_id, command) except RuntimeError as err: if str(err) == 'atomeyelib_queueevent: too many atomeyelib events.': self.wait() self._atomeye.run_command(self._window_id, command) else: raise if self.block: self.wait()
[docs] def run_script(self, script): """ Run commands from the file script, in a blocking fashion. """ if type(script) == type(''): script = open(script) for line in script: self.run_command(line) self.wait()
def __call__(self, command): self.run_command(command)
[docs] def close(self): """ Close this viewer window. """ self.run_command('close')
[docs] def setp(self, key, value): """ Run the AtomEye command "set key value". """ self.run_command("set %s %s" % (str(key), str(value)))
[docs] def save(self, filename): """ Save AtomEye viewer settings to a file. """ self.run_command("save %s" % str(filename))
[docs] def update(self, state): """ Update settings from the dictionary `state`. Runs the AtomEye command ``set key value`` for each pair. Valid settings are listed on the `AtomEye 3 settings help page <http://mt.seas.upenn.edu/Archive/Graphics/A3/A3.html#redraw>`_ """ for k, v in state.iteritems(): self.setp(k, v)
def set_state(self, state): for key, value in state.iteritems(): if key == 'variables': self.update(value) elif key == 'commands': for command in value: self.run_command(command) elif key == 'cutoff_lengths': for (sym1, sym2, cutoff) in value: self.rcut_patch(sym1, sym2, float(cutoff), absolute=True) else: setattr(self, key, value) self.wait() self.redraw() def get_state(self): fd, name = tempfile.mkstemp() os.close(fd) self.save(name) self.wait() time.sleep(0.2) fh = open(name, 'r') lines = fh.readlines() variables = {} commands = [] for line in lines: line = line.strip() if line.startswith('set'): dummy, key, value = line.split(None,2) variables[key] = value else: commands.append(line) fh.close() os.unlink(name) state = {} state['variables'] = variables state['commands'] = commands odict = self.__dict__.copy() for key in odict.keys(): if key.startswith('_'): del odict[key] del odict['atoms'] state.update(odict) return state def __getstate__(self): return self.get_state() def __setstate__(self, state): self.set_state(state)
[docs] def load_script(self, filename): """ Load AtomEye viewer settings from a file using the AtomEye ``load_script`` command :meth:`run_script` is more robust as the script is run line by line in a blocking sense. """ self.run_command("load_script %s" % str(filename))
[docs] def key(self, key): """ Simulate pressing `key` on the keyboard. The syntax for keystrokes is described on the `AtomEye 3 commands help page <http://mt.seas.upenn.edu/Archive/Graphics/A3/A3.html#commands>`_ """ self.run_command("key %s" % key)
[docs] def toggle_coordination_coloring(self): """ Turn on or off colouring by coordination number (key "k") """ self.run_command("toggle_coordination_coloring")
[docs] def translate(self, axis, delta): """ Translate system along `axis` by an amount `delta` (key "Ctrl+left/right/up/down") """ self.run_command("translate %d %f " % (axis, delta))
[docs] def shift_xtal(self, axis, delta): """ Shift crystal within periodic boundaries along `axis` by `delta` (key "Shift+left/right/up/down"). """ self.run_command("shift_xtal %d %f" % (axis, delta))
[docs] def rotate(self, axis, theta): """ Rotate around `axis` by angle `theta`. """ self.run_command("rotate %d %f" % (axis, theta))
[docs] def advance(self, delta): """ Move the camera forward by `delta`. """ self.run_command("advance %f" % delta)
[docs] def shift_cutting_plane(self, delta): """ Shift the current cutting plane by an amount `delta`. """ self.run_command("shift_cutting_plane %f" % delta)
[docs] def change_bgcolor(self, color): """ Change the viewer background colour to `color`, which should be a RGB tuple with three floats in range 0..1. """ r, g, b = color self.run_command("change_bgcolor %f %f %f" % (r, g, b))
[docs] def change_atom_r_ratio(self, delta): """ Change the size of the balls used to draw the atoms by `delta`. """ self.run_command("change_atom_r_ratio %f" % delta)
[docs] def change_bond_radius(self, delta): """ Change the radius of the cylinders used the draw bonds by `delta`. """ self.run_command("change_bond_radius %f" % delta)
def change_view_angle_amplification(self, delta): self.run_command("change_view_angle_amplification %f" % delta)
[docs] def toggle_parallel_projection(self): """ Toggle between parallel and perspective projections. """ self.run_command("toggle_parallel_projection")
[docs] def toggle_bond_mode(self): """ Turn on or off bonds. """ self.run_command("toggle_bond_mode" )
[docs] def toggle_small_cell_mode(self): """ Toggle between two different behaviours for when cell is smaller than r_cut/2: 1. clip cell - some neigbours may be lost (default) 2. replicate cell along narrow directions """ self.run_command("toggle_small_cell_mode") self.redraw()
[docs] def normal_coloring(self): """ Return to normal colouring of the atoms (key "o"). """ self.run_command("normal_coloring")
[docs] def aux_property_coloring(self, auxprop): """ Colour the atoms by the auxiliary property with name or index `auxprop`. """ auxprop = self._property_hook(self.gcat(), auxprop) self.redraw() # ensure auxprop is available self.run_command("aux_property_coloring %s" % str(auxprop))
[docs] def central_symmetry_coloring(self): """ Colour atoms by centro-symmetry parameter. """ self.run_command("central_symmetry_coloring")
[docs] def change_aux_property_threshold(self, lower, upper): """ Change the lower and upper aux property thresholds. """ self.run_command("change_aux_property_threshold lower %f" % lower) self.run_command("change_aux_property_threshold upper %f" % upper)
[docs] def reset_aux_property_thresholds(self): """ Reset aux property thresholds to automatic values. """ self.run_command("reset_aux_property_thresholds")
[docs] def toggle_aux_property_thresholds_saturation(self): """ Toggle between saturated colouring and invisibility for values outside aux prop thresholds. """ self.run_command("toggle_aux_property_thresholds_saturation")
[docs] def toggle_aux_property_thresholds_rigid(self): """ Toggle between floating and rigid aux property thresholds when moving between frames """ self.run_command("toggle_aux_property_thresholds_rigid")
[docs] def rcut_patch(self, sym1, sym2, value, absolute=False): """ Change the cutoff distance for `sym1`--`sym2` bonds by `delta`. e.g. to increase cutoff for Si-Si bonds by 0.5 A use:: viewer.rcut_patch('Si', 'Si', 0.5) With `absolute` set to True, `value` is used to set the absolute cutoff distance for `sym1`--`sym2` bonds, e.g.:: viewer.rcut_patch('Si', 'Si', 2.50, True) """ self.run_command("rcut_patch start %s %s" % (sym1,sym2)) cmd = "rcut_patch %s" % value if absolute: cmd += " absolute" self.run_command(cmd) self.run_command("rcut_patch finish")
def select_gear(self, gear): self.run_command("select_gear %d" % gear)
[docs] def cutting_plane(self, n, d, s): """ Create a new cutting plane with index `n`, normal `d`, and fractional displacement `s`. """ da, db, dc = d sa, sb, sc = s self.run_command("cutting_plane %d %f %f %f %f %f %f" % \ (n, da, db, dc, sa, sb, sc))
def shift_cutting_plane_to_anchor(self, n): self.run_command("shift_cutting_plane_to_anchor %d" % n) def delete_cutting_plane(self, n): self.run_command("delete_cutting_plane %d" % n) def flip_cutting_plane(self, n): self.run_command("flip_cutting_plane %d" % n)
[docs] def capture(self, filename, resolution=None): """ Render the current view to image `filename` Format is determined from file extension: .png, .jpeg, or .eps. """ if resolution is None: resolution = "" format = filename[filename.rindex('.')+1:] self.wait() self.run_command("capture %s %s %s" % (format, filename, resolution))
def change_wireframe_mode(self, ): self.run_command("change_wireframe_mode") def change_cutting_plane_wireframe_mode(self): self.run_command("change_cutting_plane_wireframe_mode") def get_frame(self): return self._frame def set_frame(self, frame): self._frame = frame % len(self.atoms) self.redraw() frame = property(get_frame, set_frame, doc="Get or set the current frame")
[docs] def first(self): """ Show the first frame (frame 0). """ self.frame = 0
[docs] def last(self): """ Show the last frame, i.e. len(self.atoms)-1 """ self.frame = len(self.atoms)-1
[docs] def forward(self, delta=None): """ Move forward by `delta` frames (default value is self.delta). """ delta = delta or self.delta self.frame = ((self.frame + delta) % len(self.atoms))
[docs] def backward(self, delta=None): """ Move backward by `delta` frames (default values is self.delta). """ delta = delta or self.delta self.frame = (self.frame - delta) % len(self.atoms)
[docs] def load_atom_color(self, filename): """ Load atom colours from a .clr file. """ self.run_command("load_atom_color %s" % filename)
[docs] def load_aux(self, filename): """ Load aux property values from a .aux file. """ self.run_command("load_aux %s" % filename)
def look_at_the_anchor(self): self.run_command("look_at_the_anchor") def observer_goto(self): self.run_command("observer_goto")
[docs] def xtal_origin_goto(self, s): """ Move the crystal origin to fractional coordinates `s` For example, use ``s=[0.5, 0.5, 0.5]`` to shift by half the cell along the :math:`\mathbf{a}`, :math:`\mathbf{b}` and :math:`\mathbf{c}` lattice vectors. """ sa, sb, sc = s self.run_command("xtal_origin_goto %f %f %f" % (sa, sb, sc))
[docs] def find_atom(self, i): """ Set the anchor to the atom with index `i`. """ if get_fortran_indexing(): i = i-1 self.run_command("find_atom %d" % i)
[docs] def resize(self, width, height): """ Resize the current window to `width` x `height` pixels. """ self.run_command("resize %d %d" % (width, height))
[docs] def change_aux_colormap(self, n): """ Select the `n`-th auxiliary property colourmap. """ self.run_command("change_aux_colormap %d" % n)
[docs] def draw_arrows(self, property, scale_factor=0.0, head_height=0.1, head_width=0.05, up=(0.0,1.0,0.0)): """ Draw arrows on each atom, based on a vector property Parameters ---------- property : string Name of the array to use for arrow vectors. Use ``None`` to turn off previous arrows. scale_factor : float Override length of arrows. 1 unit = 1 Angstrom; default value of 0.0 means autoscale. head_height : float Specify height of arrow heads in Angstrom. head_width : float up : 3-vector (tuple, list or array) Specify the plane in which the arrow heads are drawn. Arrows are drawn in the plane which is common to their direction and this vector. Default is ``[0.,1.,0.]``. """ if property is None: self.run_command('draw_arrows off') else: property = self._property_hook(self.gcat(), property) up1, up2, up3 = up self.redraw() # ensure property is available self.run_command('draw_arrows %s %f %f %f %f %f %f' % (str(property), scale_factor, head_height, head_width, up1, up2, up3))
[docs] def wait(self): """Sleep until this AtomEye viewer has finished processing all queued events.""" if not self.is_alive: raise RuntimeError('is_alive is False') self._atomeye.wait(self._window_id)
[docs] def get_visible(self): """Return list of indices of atoms currently visible in this viewer.""" indices = self._atomeye.get_visible() at = self.gcat() if np.any(indices > len(at)): # atoms duplicated due to narrow cell (n->small_cell_err_handler == 1) indices = list(set([idx % len(at) for idx in indices ])) if get_fortran_indexing(): indices = [idx+1 for idx in indices] return indices
[docs] def get_size_pixels(self, state=None): """ Return (width, height) in pixels of this viewer """ if state is None: state = self.get_state() for command in state['commands']: if command.startswith('resize'): break else: raise ValueError('cannot find "resize" entry in state["commands"]') resize, width, height = command.split() width = int(width) height = int(height) return (width, height)
[docs] def get_size_angstrom(self, state=None): """ Return (width, height) in Angstrom of currently projected view Assumes object lies in plane z=0 """ if state is None: state = self.get_state() W, H = self.get_size_pixels(state) # camera position cx, cy, cz = np.array([float(f) for f in state['variables']['AX_3D->x'].split()]) # conversion factor from view angle to pixels k = float(state['variables']['AX_3D->k']) w = abs(W*cz/k) h = abs(H*cz/k) return (w, h)
[docs] def display(self): """ Display snapshot from AtomEye session in IPython notebook """ fd,fname = tempfile.mkstemp(suffix='.png') os.close(fd) self.capture(fname) self.wait() from IPython.display import Image, display display(Image(filename=fname)) os.unlink(fname)
_viewer = None def gcv(): return _viewer def scv(viewer): _viewer = viewer
[docs]def view(atoms, **kwargs): """ Convenience wrapper to create/reuse a default `AtomEyeViewer` """ global _viewer if _viewer is None: _viewer = AtomEyeViewer(atoms, **kwargs) else: _viewer.show(atoms, **kwargs) return _viewer
[docs]def redraw(): """ Redraw current AtomEye window, keeping Atoms and settings the same. """ gcv().redraw()
[docs]def run_command(command): """ Run a command in current AtomEye thread. The command is queued for later execution, unless :attr:`block` is True. Parameters ---------- command : string The command to pass to AtomEye """ gcv().run_command(command)
[docs]def run_script(script): """ Run commands from the file script, in a blocking fashion. """ gcv().run_script(script)
[docs]def close(): """ Close the current viewer window. """ gcv().close()
[docs]def setp(self, key, value): """ Run the AtomEye command "set key value" """ gcv().setp(key, value)
[docs]def save_script(filename): """ Save AtomEye viewer settings to a file. """ gcv().save(filename)
[docs]def toggle_coordination_coloring(): """ Turn on or off colouring by coordination number (key "k") """ gcv().toggle_coordination_coloring()
[docs]def translate(axis, delta): """ Translate system along `axis` by an amount `delta` (key "Ctrl+left/right/up/down") """ gcv().translate(axis, delta)
[docs]def shift_xtal(axis, delta): """ Shift crystal within periodic boundaries along `axis` by `delta` (key "Shift+left/right/up/down"). """ gcv().shift_xtal(axis, delta)
[docs]def rotate(axis, theta): """ Rotate around `axis` by angle `theta`. """ gcv().rotate(axis, theta)
[docs]def advance(delta): """ Move the camera forward by `delta`. """ gcv().advance(delta)
[docs]def shift_cutting_plane(delta): """ Shift the current cutting plane by an amount `delta`. """ gcv().shift_cutting_plane(delta)
[docs]def change_bgcolor(color): """ Change the viewer background colour to `color`, which should be a RGB tuple with three floats in range 0..1. """ gcv().change_bgcolor(color)
[docs]def change_atom_r_ratio(delta): """ Change the size of the balls used to draw the atoms by `delta`. """ gcv().change_atom_r_ratio(delta)
[docs]def change_bond_radius(delta): """ Change the radius of the cylinders used the draw bonds by `delta`. """ gcv().change_bond_radius(delta)
[docs]def change_view_angle_amplification(delta): """ Change the amplification of the view angle by `delta`. """ gcv().change_view_angle_amplification(delta)
[docs]def toggle_parallel_projection(): """ Toggle between parallel and perspective projections. """ gcv().toggle_parallel_projection()
[docs]def toggle_bond_mode(): """ Turn on or off bonds. """ gcv().toggle_bond_mode()
[docs]def toggle_small_cell_mode(): """ Toggle between two different behaviours for when cell is smaller than r_cut/2: 1. clip cell - some neigbours may be lost (default) 2. replicate cell along narrow directions """ gcv().toggle_small_cell_mode()
[docs]def normal_coloring(): """ Return to normal colouring of the atoms (key "o"). """ gcv().normal_coloring()
[docs]def aux_property_coloring(auxprop): """ Colour the currently viewed atoms according to `auxprop`. Overloaded to allow See :ref:`qlab_atom_coloring` for more details and examples. Parameters ---------- auxprop : str, array_like, int or list Values to use to colour the atoms. Should be either the name of a scalar field entry in :attr:`~.Atoms.properties` (or equivalently, :attr:`~Atoms.arrays`) such as ``"charge"``, a float, int or bool array of shape ``(len(gcat()),)``, or an atom index or list of atom indices to highlight particular atoms. """ gcv().aux_property_coloring(auxprop)
[docs]def central_symmetry_coloring(): """ Colour atoms by centro-symmetry parameter. """ gcv().central_symmetry_coloring()
[docs]def change_aux_property_threshold(lower, upper): """ Change the lower and upper aux property thresholds. """ gcv().change_aux_property_threshold(lower, upper)
[docs]def reset_aux_property_thresholds(): """ Reset aux property thresholds to automatic values. """ gcv().reset_aux_property_thresholds()
[docs]def toggle_aux_property_thresholds_saturation(): """ Toggle between saturated colouring and invisibility for values outside aux prop thresholds. """ gcv().toggle_aux_property_thresholds_saturation()
[docs]def toggle_aux_property_thresholds_rigid(): """ Toggle between floating and rigid aux property thresholds when moving between frames """ gcv().toggle_aux_property_thresholds_rigid()
[docs]def rcut_patch(sym1, sym2, value, absolute=False): """ Change the cutoff distance for `sym1`--`sym2` bonds by `delta`. e.g. to increase cutoff for Si-Si bonds by 0.5 A use:: viewer.rcut_patch('Si', 'Si', 0.5) With `absolute` set to True, `value` is used to set the absolute cutoff distance for `sym1`--`sym2` bonds, e.g.:: viewer.rcut_patch('Si', 'Si', 2.50, True) """ gcv().rcut_patch(sym1, sym2, value, absolute)
[docs]def select_gear(gear): """ Change the AtomEye gear to `gear` Equivalent to pressing the one of the numeric keys 0..9 """ gcv().select_gear(gear)
[docs]def cutting_plane(n, d, s): """ Create a new cutting plane with index `n`, normal `d`, and fractional displacement `s`. """ gcv().cutting_plane(n, d, s)
[docs]def shift_cutting_plane_to_anchor(n): """ Move the cutting plane with index `n` to the anchor """ gcv().shift_cutting_plane_to_anchor(n)
[docs]def delete_cutting_plane(n): """ Delete the cutting plane with index `n` """ gcv().delete_cutting_plane(n)
[docs]def flip_cutting_plane(n): """ Flip the cutting plane with index `n` """ gcv().flip_cutting_plane(n)
[docs]def capture(filename, resolution=None): """ Render the current view to image `filename` Format is determined from file extension: .png, .jpeg, or .eps. """ gcv().capture(filename, resolution)
[docs]def change_wireframe_mode(): """ Change the display mode for the unit cell box. Equivalent to pressing the `i` key. """ gcv().change_wireframe_mode()
[docs]def change_cutting_plane_wireframe_mode(): """ Change the display mode for cutting planes """ gcv().change_cutting_plane_wireframe_mode()
[docs]def get_frame(): """ Get index of frame currently being viewed """ return gcv().frame
[docs]def set_frame(frame): """ Set current frame index to `frame` """ gcv().frame = frame
[docs]def get_delta(): """ Get frame increment rate """ return gcv().delta
[docs]def set_delta(delta): """ Set frame increment rate """ gcv().delta = delta
[docs]def first(): """ Show the first frame (frame 0). """ gcv().first()
[docs]def last(): """ Show the last frame, i.e. len(gcv())-1 """ gcv().last()
[docs]def forward(delta=None): """ Move forward by `delta` frames (default value is gcv().delta). """ gcv().forward(delta)
[docs]def backward(delta=None): """ Move backward by `delta` frames (default values is gcv().delta). """ gcv().backward(delta)
[docs]def load_atom_color(filename): """ Load atom colours from a .clr file. """ gcv().load_atom_color(filename)
[docs]def load_aux(filename): """ Load aux property values from a .aux file. """ gcv().load_aux(filename)
[docs]def look_at_the_anchor(): """ Equivalent to pressing the `a` key """ gcv().look_at_the_anchor()
[docs]def observer_goto(): """ Prompt for fractional position and move the observer there Equivalent to pressing the `g` key. """ gcv().observer_goto()
[docs]def xtal_origin_goto(s): """ Move the crystal origin to fractional coordinates `s` For example, use ``s=[0.5, 0.5, 0.5]`` to shift by half the cell along the :math:`\mathbf{a}`, :math:`\mathbf{b}` and :math:`\mathbf{c}` lattice vectors. """ gcv().xtal_origin_goto(s)
[docs]def find_atom(i): """ Set the anchor to the atom with index `i`. """ gcv().find_atom(i)
[docs]def resize(width, height): """ Resize the current window to `width` x `height` pixels. """ gcv().resize(width, height)
[docs]def change_aux_colormap(n): """ Select the `n`\ -th auxiliary property colourmap. """ gcv().change_aux_colormap(n)
[docs]def draw_arrows(property, scale_factor=0.0, head_height=0.1, head_width=0.05, up=(0.0,1.0,0.0)): """ Draw arrows on each atom, based on a vector property Parameters ---------- property : string Name of the array to use for arrow vectors. Use ``None`` to turn off previous arrows. scale_factor : float Override length of arrows. 1 unit = 1 Angstrom; default value of 0.0 means autoscale. head_height : float Specify height of arrow heads in Angstrom. head_width : float up : 3-vector (tuple, list or array) Specify the plane in which the arrow heads are drawn. Arrows are drawn in the plane which is common to their direction and this vector. Default is ``[0.,1.,0.]``. """ gcv().draw_arrows(property, scale_factor, head_height, head_width, up)
[docs]def wait(): """Sleep until current AtomEye viewer has finished processing all queued events.""" gcv().wait()
[docs]def display(): """ Display snapshot from current viewer in Jupyter notebook """ gcv().display()