示例#1
0
    def __init__(self, session, *args, **kwargs):
        # 1.2 adds an __init__ to ProviderManager that uses the manager name for things
        # this is not the case in 1.1

        self.session = session
        self.local_jobs = []
        self.remote_jobs = []
        self.unknown_status_jobs = []
        self.paused = False
        self._thread = None
        self.initialized = False
        self.queue_dict = {}
        self.formats = {}

        self.triggers = TriggerSet()
        self.triggers.add_trigger(JOB_FINISHED)
        self.triggers.add_handler(JOB_FINISHED, self.job_finished)
        self.triggers.add_trigger(JOB_STARTED)
        self.triggers.add_handler(JOB_STARTED, self.job_started)
        self.triggers.add_trigger(JOB_QUEUED)
        self.triggers.add_handler(JOB_QUEUED, self.check_queue)
        self.triggers.add_handler(JOB_QUEUED, self.write_json)

        params = signature(super().__init__).parameters
        if any("name" in param for param in params):
            super().__init__(*args, **kwargs)
        else:
            super().__init__()
示例#2
0
 def __init__(self, session):
     #  Just for good form.  Base class currently has no __init__.
     super().__init__()
     self.schemes = set()
     from chimerax.core.triggerset import TriggerSet
     self.triggers = TriggerSet()
     self.triggers.add_trigger("html schemes changed")
 def __init__(self,
              session,
              grid_step,
              radius,
              atoms=None,
              transforms=None,
              transform_indices=None,
              coords=None,
              pad=None,
              interpolation_threshold=0.75):
     if pad is None:
         pad = radius
     self.structure = None
     self._symmetry_map = {}
     self._atoms = atoms
     if atoms is not None:
         self.structure = self._unique_structure(atoms)
     self._structure_change_handler = None
     self.session = session
     self._step = grid_step
     self._radius = radius
     self._coords = coords
     self._pad = pad
     self.threshold = interpolation_threshold
     self._mask = None
     self._update_needed = False
     self._resize_box = True
     from chimerax.core.triggerset import TriggerSet
     self.triggers = TriggerSet()
     self.triggers.add_trigger('atom coords updated')
示例#4
0
 def __init__(self, session):
     self.session = session
     self._formats = {}
     self._suffix_to_formats = {}
     from chimerax.core.triggerset import TriggerSet
     self.triggers = TriggerSet()
     self.triggers.add_trigger("data formats changed")
示例#5
0
    def __init__(self, manager, name):
        super().__init__(name, manager.session)
        self._mgr = manager

        if not hasattr(self, 'triggers'):
            from chimerax.core.triggerset import TriggerSet
            self.triggers = TriggerSet()

        trigger_names = (
            'map box changed',
            'map box moved',
        )
        for t in trigger_names:
            self.triggers.add_trigger(t)

        mh = self._mgr_handlers = []
        mh.append((manager,
            manager.triggers.add_handler('spotlight moved',
                self._box_moved_cb)))
        mh.append((manager,
            manager.triggers.add_handler('spotlight changed',
                self._box_changed_cb)))
        mh.append((manager,
            manager.triggers.add_handler('cover coords',
                self._cover_coords_cb)))
示例#6
0
 def __init__(self, session):
     self.session = session
     self._openers = {}
     self._fetchers = {}
     from chimerax.core.triggerset import TriggerSet
     self.triggers = TriggerSet()
     self.triggers.add_trigger("open command changed")
示例#7
0
 def __init__(self, session, bundle_info):
     self.bond_rotations = {} # bond -> BondRotation
     self.bond_rotaters = {} # ident -> BondRotater
     self.session = session
     from chimerax.core.triggerset import TriggerSet
     self.triggers = TriggerSet()
     for trig_name in self.trigger_names:
         self.triggers.add_trigger(trig_name)
示例#8
0
 def __init__(self, session):
     self.session = session
     self.rot_libs = None
     from chimerax.core.triggerset import TriggerSet
     self.triggers = TriggerSet()
     self.triggers.add_trigger("rotamer libs changed")
     self._library_info = {}
     self.settings = _RotamerManagerSettings(session, "rotamer lib manager")
     self._uninstalled_suffix = " [not installed]"
示例#9
0
    def __init__(self, crystal_manager, spotlight_radius=12, default_oversampling_rate=2.0,
            auto_add = True):
        cm = self._mgr = crystal_manager
        super().__init__('Map Manager', cm.session)
        self._default_oversampling_rate=default_oversampling_rate

        self._zone_mgr = None

        if not hasattr(self, 'triggers'):
            from chimerax.core.triggerset import TriggerSet
            self.triggers = TriggerSet()

        trigger_names = (
            # Deprecated
            'map box changed',
            # Ask each MapSet to expand its volumes to cover an arbitrary set
            # of coordinates as efficiently as possible.
            'cover coords',
            # Change the radius of the "spotlight" sphere. It is up to each
            # MapSet to work out how to accommodate it
            'spotlight changed',
            'spotlight moved',    # Just changed the centre of the box
        )
        for t in trigger_names:
            self.triggers.add_trigger(t)

        self._max_voxels_for_live_remask = self.DEFAULT_MAX_VOXELS

        # Handler for live box update
        self._box_update_handler = None

        # Is the map box moving with the centre of rotation?
        self._spotlight_center = None

        self._initialize_zone_mgr()

        # Radius of the sphere in which the map will be displayed when
        # in live-scrolling mode
        self.spotlight_radius = spotlight_radius


        if self.spotlight_mode:
            self._start_spotlight_mode()

        # self.display=False
        self._rezone_pending = False
        # Apply the surface mask

        mh = self._mgr_handlers = []
        mh.append((cm, cm.triggers.add_handler('mode changed',
            self._spotlight_mode_changed_cb)))



        # self.session.triggers.add_handler('frame drawn', self._first_init_cb)
        if auto_add:
            cm.add([self])
示例#10
0
 def __init__(self, alignment, **kw):
     self.alignment = alignment
     self.triggers = TriggerSet()
     self.triggers.add_trigger("seqs changed")
     alignment.add_observer(self)
     super().__init__(list_func=lambda aln=alignment: alignment.seqs,
         key_func=lambda seq, aln=alignment: aln.seqs.index(seq),
         item_text_func=lambda seq: seq.name,
         trigger_info=[
             (self.triggers, "seqs changed"),
         ],
         **kw)
示例#11
0
class _RotamerStateManager(StateManager):
    def __init__(self, session, base_residue, rotamers):
        self.init_state_manager(session, "residue rotamers")
        self.session = session
        self.base_residue = base_residue
        self.rotamers = list(
            rotamers)  # don't want auto-shrinking of a Collection
        self.group = session.models.add_group(
            rotamers,
            name="%s rotamers" % base_residue.string(omit_structure=True),
            parent=base_residue.structure)
        from chimerax.atomic import get_triggers
        self.handler = get_triggers().add_handler('changes', self._changes_cb)
        from chimerax.core.triggerset import TriggerSet
        self.triggers = TriggerSet()
        self.triggers.add_trigger('fewer rotamers')  # but not zero
        self.triggers.add_trigger('self destroyed')

    def destroy(self):
        self.handler.remove()
        if self.group.id is not None:
            self.session.models.close([self.group])
        self.group = self.base_residue = self.rotamers = self.session = None
        super().destroy()

    def reset_state(self, session):
        self.triggers.activate_trigger('self destroyed', self)
        self.destroy()

    @classmethod
    def restore_snapshot(cls, session, data):
        return cls(session, data['base residue'], data['rotamers'])

    def take_snapshot(self, session, flags):
        data = {'base residue': self.base_residue, 'rotamers': self.rotamers}
        return data

    def _changes_cb(self, trigger_name, changes):
        if changes.num_deleted_residues() == 0:
            return
        remaining = [rot for rot in self.rotamers if not rot.deleted]
        if self.base_residue.deleted:
            self.triggers.activate_trigger('self destroyed', self)
            self.destroy()
            return
        remaining = [rot for rot in self.rotamers if not rot.deleted]
        if len(remaining) < len(self.rotamers):
            if remaining:
                self.rotamers = remaining
                self.triggers.activate_trigger('fewer rotamers', self)
            else:
                self.triggers.activate_trigger('self destroyed', self)
                self.destroy()
示例#12
0
class SchemesManager(ProviderManager):
    """Manager for html schemes used by all bundles"""
    def __init__(self, session):
        #  Just for good form.  Base class currently has no __init__.
        super().__init__()
        self.schemes = set()
        from chimerax.core.triggerset import TriggerSet
        self.triggers = TriggerSet()
        self.triggers.add_trigger("html schemes changed")

    def add_provider(self, bundle_info, name, **kw):
        self.schemes.add(name)

        def is_true(value):
            return value and value.casefold() in ('true', '1', 'on')

        from PyQt5.QtWebEngineCore import QWebEngineUrlScheme
        scheme = QWebEngineUrlScheme(name.encode('utf-8'))
        port = kw.get('defaultPort', None)
        if port is not None:
            scheme.setDefaultPort(int(port))
        syntax = kw.get('syntax', None)
        if syntax == "Path":
            scheme.setSyntax(QWebEngineUrlScheme.Syntax.Path)
        elif syntax == "Host":
            scheme.setSyntax(QWebEngineUrlScheme.Syntax.Host)
        elif syntax == "HostAndPort":
            scheme.setSyntax(QWebEngineUrlScheme.Syntax.HostAndPort)
        elif syntax == "HostPortAndUserInformation":
            scheme.setSyntax(
                QWebEngineUrlScheme.Syntax.HostPortAndUserInformation)
        flags = 0
        if is_true(kw.get("SecureScheme", None)):
            flags |= QWebEngineUrlScheme.SecureScheme
        if is_true(kw.get("LocalScheme", None)):
            flags |= QWebEngineUrlScheme.LocalScheme
        if is_true(kw.get("LocalAccessAllowed", None)):
            flags |= QWebEngineUrlScheme.LocalAccessAllowed
        if is_true(kw.get("NoAccessAllowed", None)):
            flags |= QWebEngineUrlScheme.NoAccessAllowed
        if is_true(kw.get("ServiceWorkersAllowed", None)):
            flags |= QWebEngineUrlScheme.ServiceWorkersAllowed
        if is_true(kw.get("ViewSourceAllowed", None)):
            flags |= QWebEngineUrlScheme.ViewSourceAllowed
        if is_true(kw.get("ContentSecurityPolicyIgnored", None)):
            flags |= QWebEngineUrlScheme.ContentSecurityPolicyIgnored
        if flags:
            scheme.setFlags(flags)
        QWebEngineUrlScheme.registerScheme(scheme)

    def end_providers(self):
        self.triggers.activate_trigger("html schemes changed", self)
示例#13
0
 def __init__(self, session):
     self.session = session
     self._toolbar = {}
     return
     # TODO:
     from . import settings
     settings.settings = settings._ToolbarSettings(session, "toolbar")
     from chimerax.core.triggerset import TriggerSet
     self.triggers = TriggerSet()
     self.triggers.add_trigger("toolbar changed")
     if session.ui.is_gui:
         session.ui.triggers.add_handler('ready',
                                         lambda *arg, ses=session: settings.
                                         register_settings_options(session))
示例#14
0
 def __init__(self, session, bundle_info):
     # Just for good form.  Neither base class currently defines __init__.
     super().__init__()
     self._alignments = {}
     # bundle_info needed for session save
     self.bundle_info = bundle_info
     self.session = session
     self.viewer_info = {'alignment': {}, 'sequence': {}}
     self.viewer_to_subcommand = {}
     from chimerax.core.triggerset import TriggerSet
     self.triggers = TriggerSet()
     self.triggers.add_trigger("new alignment")
     self.triggers.add_trigger("destroy alignment")
     self._installed_headers = {}
     self._installed_viewers = {}
示例#15
0
 def __init__(self, session):
     self.session = session
     from . import settings
     settings.settings = settings._PresetsSettings(session, "presets")
     settings.settings.triggers.add_handler("setting changed",
                                            self._new_custom_folder_cb)
     self._presets = {}
     from chimerax.core.triggerset import TriggerSet
     self.triggers = TriggerSet()
     self.triggers.add_trigger("presets changed")
     self._new_custom_folder_cb()
     if session.ui.is_gui:
         session.ui.triggers.add_handler('ready',
                                         lambda *arg, ses=session: settings.
                                         register_settings_options(session))
示例#16
0
 def __init__(self, session, base_residue, rotamers):
     self.init_state_manager(session, "residue rotamers")
     self.session = session
     self.base_residue = base_residue
     self.rotamers = list(
         rotamers)  # don't want auto-shrinking of a Collection
     self.group = session.models.add_group(
         rotamers,
         name="%s rotamers" % base_residue.string(omit_structure=True),
         parent=base_residue.structure)
     from chimerax.atomic import get_triggers
     self.handler = get_triggers().add_handler('changes', self._changes_cb)
     from chimerax.core.triggerset import TriggerSet
     self.triggers = TriggerSet()
     self.triggers.add_trigger('fewer rotamers')  # but not zero
     self.triggers.add_trigger('self destroyed')
示例#17
0
def get_triggers(session=None):
    """Get the atomic triggers (prior implementation used 'session' arg)"""
    global _triggers
    if _triggers is None:
        from chimerax.core.triggerset import TriggerSet
        _triggers = TriggerSet()
        _triggers.add_trigger("atoms transformed")
        _triggers.add_trigger("changes")
        _triggers.add_trigger("changes done")
    return _triggers
示例#18
0
    def __init__(self, session, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.triggers = TriggerSet()
        self.triggers.add_trigger(FILEREADER_CHANGE)
        self.triggers.add_trigger(FILEREADER_ADDED)
        self.triggers.add_trigger(FILEREADER_REMOVED)
        self.triggers.add_trigger(ADD_FILEREADER)

        session.triggers.add_handler(REMOVE_MODELS, self.remove_models)
        session.triggers.add_handler(ADD_MODELS, self.apply_preset)
        session.triggers.add_handler(ADD_MODELS, self.trigger_fr_add)
        self.triggers.add_handler(ADD_FILEREADER, self.add_filereader)

        #list of models with an associated FileReader object
        self.models = []
        self.filereaders = []
        self.waiting_models = []
        self.waiting_filereaders = []
示例#19
0
    def __init__(self, session, *args, **kwargs):
        super().__setattr__("initialized", False)
        self.session = session
        self.local_jobs = []
        self.remote_jobs = []
        self.unknown_status_jobs = []
        self.paused = False
        self._thread = None
        self.queue_dict = {}
        self.formats = {}

        self.triggers = TriggerSet()
        self.triggers.add_trigger(JOB_FINISHED)
        self.triggers.add_handler(JOB_FINISHED, self.job_finished)
        self.triggers.add_trigger(JOB_STARTED)
        self.triggers.add_handler(JOB_STARTED, self.job_started)
        self.triggers.add_trigger(JOB_QUEUED)
        self.triggers.add_handler(JOB_QUEUED, self.check_queue)
        self.triggers.add_handler(JOB_QUEUED, self.write_json)

        super().__init__(*args, **kwargs)
示例#20
0
class AlignSeqMenuButton(ItemMenuButton):
    def __init__(self, alignment, **kw):
        self.alignment = alignment
        self.triggers = TriggerSet()
        self.triggers.add_trigger("seqs changed")
        alignment.add_observer(self)
        super().__init__(list_func=lambda aln=alignment: alignment.seqs,
            key_func=lambda seq, aln=alignment: aln.seqs.index(seq),
            item_text_func=lambda seq: seq.name,
            trigger_info=[
                (self.triggers, "seqs changed"),
            ],
            **kw)

    def destroy(self):
        self.alignment.remove_observer(self)
        super().destroy()

    def alignment_notification(self, note_name, note_data):
        if note_name == "add or remove seqs":
            self.triggers.activate_trigger("seqs changed", note_data)
示例#21
0
class AlignmentsManager(StateManager, ProviderManager):
    """Manager for sequence alignments"""
    def __init__(self, session, bundle_info):
        # Just for good form.  Neither base class currently defines __init__.
        super().__init__()
        self._alignments = {}
        # bundle_info needed for session save
        self.bundle_info = bundle_info
        self.session = session
        self.viewer_info = {'alignment': {}, 'sequence': {}}
        self.viewer_to_subcommand = {}
        from chimerax.core.triggerset import TriggerSet
        self.triggers = TriggerSet()
        self.triggers.add_trigger("new alignment")
        self.triggers.add_trigger("destroy alignment")
        self._installed_headers = {}
        self._installed_viewers = {}

    def add_provider(self, bundle_info, name, *, type=None,
            synonyms=[], subcommand_name=None, sequence_viewer=True, alignment_viewer=True, **kw):
        """Register an alignment header, or an alignment/sequence viewer and its associated subcommand.

        Common Parameters
        ----------
        name : str
            Header or viewer name.
        type : str
            "header" or "viewer"

        Header Parameters
        ----------
        (none)

        'run_provider' for headers should return the header's class object.

        Viewer Parameters
        ----------
        sequence_viewer : bool
            Can this viewer show single sequences
        alignment_viewer : bool
            Can this viewer show sequence alignments
        synonyms : list of str
           Shorthands that the user could type instead of standard_name to refer to your tool
           in commands.  Example:  ['sv']
        subcommand_name : str
            If the viewer can be controlled by a subcommand of the 'sequence' command, the subcommand
            word to use after 'sequence'.

        'run_provider' for viewers should will receive an 'alignment' keyword argument with the alignment
            to view and should return the viewer instance.
        """
        if type == "header":
            self._installed_headers[name] = bundle_info
        elif type == "viewer":
            if subcommand_name:
                if subcommand_name in _builtin_subcommands:
                    raise ValueError("Viewer subcommand '%s' is already a builtin"
                        " 'sequence' subcommand name" % subcommand_name)
                if subcommand_name in _viewer_subcommands:
                    raise ValueError("Viewer subcommand name '%s' is already taken" % subcommand_name)
                _viewer_subcommands.add(subcommand_name)
                self.viewer_to_subcommand[name] = subcommand_name
                if _commands_registered:
                    _register_viewer_subcommand(self.session.logger, subcommand_name)
            if synonyms:
                # comma-separated text -> list
                synonyms = [x.strip() for x in synonyms.split(',')]
            if sequence_viewer:
                self.viewer_info['sequence'][name] = synonyms
            if alignment_viewer:
                self.viewer_info['alignment'][name] = synonyms
            self._installed_viewers[name] = bundle_info
        elif type is None:
            raise ValueError("Provider failed to specify type to alignments manager")
        else:
            raise ValueError("Alignments manager does not handle provider type '%s'" % type)

    @property
    def alignments(self):
        return list(self._alignments.values())

    @property
    def alignments_map(self):
        return {k:v for k,v in self._alignments.items()}

    def destroy_alignment(self, alignment):
        if alignment.ident is not False:
            del self._alignments[alignment.ident]
        self.triggers.activate_trigger("destroy alignment", alignment)
        alignment._destroy()

    def header(self, name):
        if name in self._installed_headers:
            bundle_info = self._installed_headers[name]
            return bundle_info.run_provider(self.session, name, self)
        # not installed
        #TODO

    def headers(self, *, installed_only=True):
        hdrs = []
        if installed_only:
            for name, info in self._installed_headers.items():
                hdrs.append(self.header(name))
        else:
            for name in self.header_names(installed_only=False):
                hdrs.append(self.header(name))
        return hdrs

    def header_names(self, *, installed_only=True):
        names = []
        if installed_only:
            for name, info in self._installed_headers.items():
                bundle_info = info
                names.append(name)
        else:
            #TODO
            pass
        return names

    def new_alignment(self, seqs, identify_as, attrs=None, markups=None, auto_destroy=None,
            align_viewer=None, seq_viewer=None, auto_associate=True, name=None, intrinsic=False, **kw):
        """Create new alignment from 'seqs'

        Parameters
        ----------
        seqs : list of :py:class:`~chimerax.atomic.Sequence` instances
            Contents of alignment
        identify_as : a text string (or None or False) used to identify the alignment in commands.
            If the string is already in use by another alignment, that alignment will be destroyed
            and replaced.  If identify_as is None, then a unique identifer will be generated and
            used.  The string cannot contain the ':' character, since that is used to indicate
            sequences within the alignment to commands.  Any such characters will be replaced
            with '/'.
            If False, then a "private" alignment will be returned that will not be shown in
            a viewer nor affected by any commands.
        auto_destroy : boolean or None
            Whether to automatically destroy the alignment when the last viewer for it
            is closed.  If None, then treated as False if the value of the 'viewer' keyword
            results in no viewer being launched, else True.
        align_viewer/seq_viewer : str, False or None
           What alignment/sequence viewer to launch.  If False, do not launch a viewer.  If None,
           use the current preference setting for the user.  The string must either be
           the viewer's tool display_name or a synonym registered by the viewer (during
           its register_viewer call).
        auto_associate : boolean or None
            Whether to automatically associate structures with the alignment.   A value of None
            is the same as False except that any StructureSeqs in the alignment will be associated
            with their structures.
        name : string or None
            Descriptive name of the alignment to use in viewer titles and so forth.  If not
            provided, same as identify_as.
        intrinsic : boolean
            If True, then the alignment is treated as "coupled" to the structures associated with
            it in that if all associations are removed then the alignment is destroyed.

        Returns the created Alignment
        """
        if self.session.ui.is_gui and identify_as is not False:
            if len(seqs) > 1:
                viewer_text = align_viewer
                attr = 'align_viewer'
                type_text = "alignment"
            else:
                viewer_text = seq_viewer
                attr = 'seq_viewer'
                type_text = "sequence"
            if viewer_text is None:
                from .settings import settings
                viewer_text = getattr(settings, attr).lower()
            if viewer_text:
                viewer_text = viewer_text.lower()
                for name, syms in self.viewer_info[type_text].items():
                    if name == viewer_text:
                        viewer_name = name
                        break
                    if viewer_text in syms:
                        viewer_name = name
                        break
                else:
                    self.session.logger.warning("No registered %s viewer corresponds to '%s'"
                        % (type_text, viewer_text))
                    viewer_text = False
        else:
            viewer_text = False
        if auto_destroy is None and viewer_text:
            auto_destroy = True

        from .alignment import Alignment
        if identify_as is None:
            i = 1
            while str(i) in self._alignments:
                i += 1
            identify_as = str(i)
        elif identify_as is not False and ':' in identify_as:
            self.session.logger.info(
                "Illegal ':' character in alignment identifier replaced with '/'")
            identify_as = identify_as.replace(':', '/')
        if identify_as in self._alignments:
            self.session.logger.info(
                "Destroying pre-existing alignment with identifier %s" % identify_as)
            self.destroy_alignment(self._alignments[identify_as])

        if name is None:
            from chimerax.atomic import StructureSeq
            if len(seqs) == 1 and isinstance(seqs[0], StructureSeq):
                sseq = seqs[0]
                if sseq.description:
                    description = "%s (%s)" % (sseq.description, sseq.full_name)
                else:
                    description = sseq.full_name
            else:
                description = identify_as
        elif identify_as is False:
            description = "private"
        else:
            description = name
        if identify_as:
            self.session.logger.info("Alignment identifier is %s" % identify_as)
        alignment = Alignment(self.session, seqs, identify_as, attrs, markups, auto_destroy,
            auto_associate, description, intrinsic, **kw)
        if identify_as:
            self._alignments[identify_as] = alignment
        if viewer_text:
            self._installed_viewers[viewer_name].run_provider(self.session, viewer_name, self,
                alignment=alignment)
        self.triggers.activate_trigger("new alignment", alignment)
        return alignment

    @property
    def registered_viewers(self, seq_or_align):
        """Return the registered viewers of type 'seq_or_align'
            (which must be "sequence"  or "alignent")

           The return value is a list of tool names.
        """
        return list(self.viewer_info[seq_or_align].keys())

    def reset_state(self, session):
        for alignment in self._alignments.values():
            alignment._destroy()
        self._alignments.clear()

    @staticmethod
    def restore_snapshot(session, data):
        mgr = session.alignments
        mgr._ses_restore(data)
        return mgr

    def take_snapshot(self, session, flags):
        # viewer_info is "session independent"
        return {
            'version': 1,

            'alignments': self._alignments,
        }

    def _ses_restore(self, data):
        for am in self._alignments.values():
            am.close()
        self._alignments = data['alignments']
示例#22
0
class ZoneMgr:
    def __init__(self,
                 session,
                 grid_step,
                 radius,
                 atoms=None,
                 transforms=None,
                 transform_indices=None,
                 coords=None,
                 pad=None,
                 interpolation_threshold=0.75):
        if pad is None:
            pad = radius
        self.structure = None
        self._symmetry_map = {}
        self._atoms = atoms
        if atoms is not None:
            self.structure = self._unique_structure(atoms)
        self._structure_change_handler = None
        self.session = session
        self._step = grid_step
        self._radius = radius
        self._coords = coords
        self._pad = pad
        self.threshold = interpolation_threshold
        self._mask = None
        self._update_needed = False
        self._resize_box = True
        from chimerax.core.triggerset import TriggerSet
        self.triggers = TriggerSet()
        self.triggers.add_trigger('atom coords updated')

    @property
    def coords(self):
        if self._atoms is not None:
            if not len(self._symmetry_map):
                return self._atoms.coords
            else:
                import numpy
                coords = numpy.concatenate([
                    tf * (atoms.coords)
                    for atoms, tf in self._symmetry_map.values()
                ])
                # print('Transformed coords: {}'.format(coords)')
                return coords
        return self._coords

    def block_remask_on_coord_updates(self):
        if not self.triggers.is_trigger_blocked('atom coords updated'):
            self.triggers.manual_block('atom coords updated')

    def allow_remask_on_coord_updates(self):
        if self.triggers.is_trigger_blocked('atom coords updated'):
            self.triggers.manual_release('atom coords updated')

    @coords.setter
    def coords(self, coords):
        self._symmetry_map.clear()
        self.stop_tracking_changes()
        if self.coords is not None:
            prev_coord_len = len(self.coords)
        else:
            prev_coord_len = None
        self._atoms = None
        self.structure = None
        self._coords = coords
        if len(coords) == 1 and prev_coord_len == 1:
            self.update_needed(resize_box=False)
        else:
            self.update_needed(resize_box=True)

        # Clear all handlers
        self.triggers.delete_trigger('atom coords updated')
        self.triggers.add_trigger('atom coords updated')

    @property
    def atoms(self):
        return self._atoms

    @atoms.setter
    def atoms(self, atoms):
        self._coords = None
        self._atoms = atoms
        self._symmetry_map.clear()
        self.stop_tracking_changes()
        self.structure = self._unique_structure(atoms)
        self.start_tracking_changes()
        self._transforms = None
        self._transform_indices = None
        self.start_tracking_changes()
        self.update_needed(resize_box=True)

    @property
    def symmetry_map(self):
        return self._symmetry_map

    def set_symmetry_map(self, atoms, transforms, transform_indices):
        self._coords = None
        self._atoms = atoms
        import numpy
        unique_indices = numpy.unique(transform_indices)
        self._symmetry_map.clear()
        for i in unique_indices:
            mask = (transform_indices == i)
            self._symmetry_map[i] = (atoms[mask], transforms[i])
        self.stop_tracking_changes()
        self.structure = self._unique_structure(atoms)
        self.start_tracking_changes()
        self.update_needed(resize_box=True)

    @property
    def radius(self):
        return self._radius

    @radius.setter
    def radius(self, radius):
        self._radius = radius
        self.update_needed(resize_box=True)

    @property
    def grid_step(self):
        return self._step

    @grid_step.setter
    def grid_step(self, step):
        self._step = step
        self.update_needed(resize_box=True)

    @property
    def pad(self):
        return self._pad

    @pad.setter
    def pad(self, pad):
        self._pad = pad
        self.update_needed(resize_box=True)

    def update_needed(self, resize_box=False):
        self._update_needed = True
        if not self._resize_box:
            self._resize_box = resize_box

    @property
    def mask(self):
        if self._mask is None:
            coords = self.coords
            if coords is None:
                raise TypeError('Must provide either atoms or coordinates!')
            self._mask = VolumeMask(self.session,
                                    coords,
                                    self.grid_step,
                                    self.radius,
                                    pad=self.pad)
        return self._mask

    def _update_mask(self):
        # from time import time
        # start_time = time()
        update_origin = (len(self.coords) == 1)
        self.mask.generate_mask(self.coords,
                                self.radius,
                                reuse_existing=not self._resize_box,
                                update_origin=update_origin,
                                step=self.grid_step,
                                pad=self.pad)
        self._update_needed = False
        self._resize_box = False
        # print('Updating mask took {} ms.'.format((time()-start_time)*1000))

    def get_vertex_mask(self, vertices):
        if self._update_needed:
            self._update_mask()
        return (self.mask.interpolated_values(vertices) >= self.threshold)

    def _unique_structure(self, atoms):
        us = atoms.unique_structures
        if len(us) != 1:
            raise TypeError(
                'All atoms for zone mask must be from a single model!')
        return us[0]

    def start_tracking_changes(self):
        if self.structure is None or self._atoms is None:
            raise RuntimeError(
                'Live zone mask updating is only valid for atoms!')
        self._structure_change_handler = self.structure.triggers.add_handler(
            'changes', self._model_changes_cb)

    def stop_tracking_changes(self):
        if self._structure_change_handler is not None:
            if self.structure is not None:
                self.structure.triggers.remove_handler(
                    self._structure_change_handler)
            self._structure_change_handler = None

    def _model_changes_cb(self, trigger_name, changes):
        if self._atoms is None:
            self._structure_change_handler = None
            from chimerax.core.triggerset import DEREGISTER
            return DEREGISTER
        if 'coord changed' in changes[1].atom_reasons():
            self.update_needed()
            self.triggers.activate_trigger('atom coords updated', None)
示例#23
0
class MapSetBase(Model):
    '''
    Base class for XmapSet_Live, XmapSet_Static and NXmapSet. Provides basic
    methods for visualisation, masking etc.
    '''
    # Default contour levels and colours for generic maps. Override in the
    # derived class if you wish

    STANDARD_LOW_CONTOUR = numpy.array([1.5])
    STANDARD_HIGH_CONTOUR = numpy.array([2.5])
    STANDARD_DIFFERENCE_MAP_CONTOURS = numpy.array([-3.0, 3.0])

    DEFAULT_MESH_MAP_COLOR = [0,1.0,1.0,1.0] # Solid cyan
    DEFAULT_SOLID_MAP_COLOR = [0,1.0,1.0,0.4] # Transparent cyan
    DEFAULT_DIFF_MAP_COLORS = [[1.0,0,0,1.0],[0,1.0,0,1.0]] #Solid red and green

    def __init__(self, manager, name):
        super().__init__(name, manager.session)
        self._mgr = manager

        if not hasattr(self, 'triggers'):
            from chimerax.core.triggerset import TriggerSet
            self.triggers = TriggerSet()

        trigger_names = (
            'map box changed',
            'map box moved',
        )
        for t in trigger_names:
            self.triggers.add_trigger(t)

        mh = self._mgr_handlers = []
        mh.append((manager,
            manager.triggers.add_handler('spotlight moved',
                self._box_moved_cb)))
        mh.append((manager,
            manager.triggers.add_handler('spotlight changed',
                self._box_changed_cb)))
        mh.append((manager,
            manager.triggers.add_handler('cover coords',
                self._cover_coords_cb)))

    # @property
    # def triggers(self):
    #     return self._triggers

    @property
    def master_map_mgr(self):
        return self._mgr

    @property
    def box_center(self):
        return self.master_map_mgr.box_center

    @property
    def crystal_mgr(self):
        return self.master_map_mgr.crystal_mgr

    @property
    def structure(self):
        return self.crystal_mgr.structure

    @property
    def hklinfo(self):
        return self.crystal_mgr.hklinfo

    @property
    def spacegroup(self):
        return self.crystal_mgr.spacegroup

    @property
    def cell(self):
        return self.crystal_mgr.cell

    @property
    def grid(self):
        return self.crystal_mgr.grid

    @property
    def display_radius(self):
        '''Get/set the radius (in Angstroms) of the live map display sphere.'''
        return self.master_map_mgr.spotlight_radius

    def __getitem__(self, name_or_index):
        '''Get one of the child maps by name or index.'''
        if type(name_or_index) == str:
            for m in self.child_models():
                if m.name == name_or_index:
                    return m
            raise KeyError('No map with the name "{}"!'.format(name_or_index))
        else:
            return self.child_models()[name_or_index]

    @property
    def all_maps(self):
        from chimerax.map import Volume
        return [v for v in self.child_models() if isinstance(v, Volume)]

    @property
    def spotlight_mode(self):
        return self.master_map_mgr.spotlight_mode

    @spotlight_mode.setter
    def spotlight_mode(self, switch):
        raise NotImplementedError('Spotlight mode can only be enabled/disabled '
            'via the master symmetry manager!')

    @property
    def spotlight_center(self):
        return self.master_map_mgr.spotlight_center

    @spotlight_center.setter
    def spotlight_center(self, *_):
        raise NotImplementedError('Spotlight centre can only be changed '
            'via the master symmetry manager!')


    def expand_to_cover_coords(self, coords, padding):
        raise NotImplementedError('Function not defined in the base class!')

    # Callbacks

    def _cover_coords_cb(self, trigger_name, data):
        coords, padding = data
        self.expand_to_cover_coords(coords, padding)

    def _box_changed_cb(self, trigger_name, data):
        '''
        By default, just re-fires with the same data. Override in derived class
        if more complex handling is needed
        '''
        self.triggers.activate_trigger('map box changed', data)

    def _box_moved_cb(self, trigger_name, data):
        '''
        By default, just re-fires with the same data. Override in derived class
        if more complex handling is needed
        '''
        self.triggers.activate_trigger('map box moved', data)

    def delete(self):
        for (mgr, h) in self._mgr_handlers:
            try:
                mgr.triggers.remove_handler(h)
            except:
                continue
        super().delete()
示例#24
0
class FileReaderManager(ProviderManager):
    """keeps track of frequency files that have been opened"""

    # XML_TAG ChimeraX :: Manager :: filereader_manager
    def __init__(self, session, *args, **kwargs):
        params = signature(super().__init__).parameters
        if any("name" in param for param in params):
            super().__init__(*args, **kwargs)
        else:
            super().__init__()

        self.triggers = TriggerSet()
        self.triggers.add_trigger(FILEREADER_CHANGE)
        self.triggers.add_trigger(FILEREADER_ADDED)
        self.triggers.add_trigger(FILEREADER_REMOVED)
        self.triggers.add_trigger(ADD_FILEREADER)

        session.triggers.add_handler(REMOVE_MODELS, self.remove_models)
        session.triggers.add_handler(ADD_MODELS, self.apply_preset)
        session.triggers.add_handler(ADD_MODELS, self.trigger_fr_add)
        self.triggers.add_handler(ADD_FILEREADER, self.add_filereader)

        #list of models with an associated FileReader object
        self.models = []
        self.filereaders = []
        self.waiting_models = []
        self.waiting_filereaders = []

    def trigger_fr_add(self, trigger_name, models):
        """FILEREADER_ADDED should not get triggered until the model is loaded"""
        filereaders = []
        for fr, mdl in zip(self.waiting_filereaders, self.waiting_models):
            if mdl in models:
                filereaders.append(fr)

        if len(filereaders) > 0:
            self.triggers.activate_trigger(FILEREADER_ADDED, filereaders)

    def apply_preset(self, trigger_name, models):
        """if a graphical preset is set in SEQCROW settings, apply that preset to models"""
        for model in models:
            if model in self.models:
                if model.session.ui.is_gui:
                    apply_seqcrow_preset(model)

            apply_non_seqcrow_preset(model)

    def add_filereader(self, trigger_name, models_and_filereaders):
        """add models with filereader data to our list"""
        models, filereaders = models_and_filereaders
        wait = False
        for model, filereader in zip(models, filereaders):
            self.models.append(model)
            self.filereaders.append(filereader)
            if model.atomspec == '#':
                wait = True
                self.waiting_models.append(model)
                self.waiting_filereaders.append(filereader)

        if not wait:
            self.triggers.activate_trigger(FILEREADER_ADDED, filereaders)

        self.triggers.activate_trigger(FILEREADER_CHANGE, filereaders)

    def remove_filereader(self, trigger_name, models_and_filereaders):
        models, filereaders = models_and_filereaders
        for model, filereader in zip(models, filereaders):
            self.models.remove(model)
            self.filereaders.remove(filereader)

        self.triggers.activate_trigger(FILEREADER_CHANGE, filereaders)
        self.triggers.activate_trigger(FILEREADER_REMOVED, filereaders)

    def remove_models(self, trigger_name, models):
        """remove models with filereader data from our list when they are closed"""
        removed_frs = []
        for model in models:
            while model in self.models:
                ndx = self.models.index(model)
                removed_frs.append(self.filereaders.pop(ndx))
                self.models.remove(model)

        if len(removed_frs) > 0:
            self.triggers.activate_trigger(FILEREADER_REMOVED, removed_frs)

    def add_provider(self, bundle_info, name, **kw):
        #*buzz lightyear* ah yes, the models are models
        self.models = self.models

    def get_model(self, fr):
        dict = self.filereader_dict
        for mdl in dict:
            if fr in dict[mdl]:
                return mdl

    def list(self, other=None):
        if other is None:
            return [fr for fr in self.filereaders]
        else:
            return [
                fr for fr in self.filereaders
                if all(x in fr.other for x in other)
            ]

    @property
    def frequency_models(self):
        """returns a list of models with frequency data"""
        return [
            model for model in self.filereader_dict.keys() if any(
                'frequency' in fr.other for fr in self.filereader_dict[model])
        ]

    @property
    def energy_models(self):
        """returns a list of models with frequency data"""
        return [
            model for model in self.filereader_dict.keys()
            if any('energy' in fr.other for fr in self.filereader_dict[model])
        ]

    @property
    def filereader_dict(self):
        """returns a dictionary with atomic structures:FileReader pairs"""
        out = {}
        for mdl in self.models:
            out[mdl] = []
            for i, fr in enumerate(self.filereaders):
                if self.models[i] is mdl:
                    out[mdl].append(fr)

        return out
示例#25
0
    def __init__(self, session, isolde, atoms):
        '''
        Initialise the object, including creating the splines from the current
        coordinates. No restraints are applied at this stage.

        Args:
            * session:
                - the ChimeraX master session object
            * isolde:
                - the :class:`Isolde` session
            * atoms:
                - a :class:`chimerax.AtomicStructure` instance. All atoms must
                  be mobile, and from a single contiguous stretch of peptide
                  chain. All unique residues in the selection will be chosen for
                  shifting
        '''
        if not is_continuous_protein_chain(atoms):
            raise TypeError(
                'Selection must be atoms from a continuous peptide chain!')

        self.spring_constant =\
           isolde.sim_params.position_restraint_spring_constant.value_in_unit(
               defaults.OPENMM_SPRING_UNIT
           ) # kJ/mol/A2

        # Number of GUI update steps between each residue along the spline
        self.spline_steps_per_residue = 10
        self._spline_step = 1 / self.spline_steps_per_residue
        # Current value of the spline parameter
        self._current_position_on_spline = 0

        from chimerax.core.triggerset import TriggerSet
        triggers = self.triggers = TriggerSet()
        for t in (
                'register shift started',
                'register shift finished',
                'register shift released',
        ):
            triggers.add_trigger(t)

        self.finished = False

        # Trigger handler to update position along spline
        self._handler = None

        self.session = session
        self.isolde = isolde
        from .. import session_extensions as sx
        self._pr_mgr = sx.get_position_restraint_mgr(isolde.selected_model)
        self.polymer = find_polymer(atoms)
        residues = atoms.unique_residues
        from chimerax.atomic import Residues
        residues = self.residues = Residues(
            sorted(residues,
                   key=lambda r: (r.chain_id, r.number, r.insertion_code)))
        self._extended_atoms = None

        nres = len(residues)
        # We need to build up the array of atoms procedurally here, since
        # we want to know where there's a gap in the CB positions.
        atoms = self._make_atom_arrays(residues)
        coords = []
        n_atoms = self._n_atoms = Atoms(atoms[:, 0])
        ncoords = n_atoms.coords

        ca_atoms = self._ca_atoms = Atoms(atoms[:, 1])
        cacoords = ca_atoms.coords

        c_atoms = self._c_atoms = Atoms(atoms[:, 2])
        ccoords = c_atoms.coords

        nocb = numpy.equal(atoms[:, 3], None)
        nocb_indices = numpy.argwhere(nocb).ravel()
        cb_indices = self._cb_indices = numpy.argwhere(
            numpy.invert(nocb)).ravel()
        cbcoords = numpy.empty([nres, 3])
        cb_atoms = self._cb_atoms = Atoms(atoms[cb_indices, 3])
        cbcoords[cb_indices] = cb_atoms.coords

        # Fill in the missing CB positions. If CB is missing we'll assume
        # we're dealing with a glycine and fudge it by using the HA3
        # position

        glyres = residues[nocb_indices]
        glyha3 = glyres.atoms.filter(glyres.atoms.names == 'HA3')
        cbcoords[nocb_indices] = glyha3.coords

        u_vals = numpy.arange(0, nres)

        # Prepare the splines
        nspl = self.n_spline = interpolate.splprep(ncoords.transpose(),
                                                   u=u_vals)
        caspl = self._ca_spline = interpolate.splprep(cacoords.transpose(),
                                                      u=u_vals)
        cspl = self._c_spline = interpolate.splprep(ccoords.transpose(),
                                                    u=u_vals)
        cbspl = self._cb_spline = interpolate.splprep(cbcoords.transpose(),
                                                      u=u_vals)
示例#26
0
class FormatsManager(ProviderManager):
    """
    Manager for data formats.
        Manager can also be used as if it were a { format-name -> data format } dictionary.
    """

    CAT_SCRIPT = "Command script"
    CAT_SESSION = "Session"
    CAT_GENERAL = "General"

    def __init__(self, session):
        self.session = session
        self._formats = {}
        self._suffix_to_formats = {}
        from chimerax.core.triggerset import TriggerSet
        self.triggers = TriggerSet()
        self.triggers.add_trigger("data formats changed")

    def add_format(self,
                   name,
                   category,
                   *,
                   suffixes=None,
                   nicknames=None,
                   bundle_info=None,
                   mime_types=None,
                   reference_url=None,
                   insecure=None,
                   encoding=None,
                   synopsis=None,
                   allow_directory=False,
                   raise_trigger=True):
        def convert_arg(arg, default=None):
            if arg and isinstance(arg, str):
                return arg.split(',')
            return [] if default is None else default

        suffixes = convert_arg(suffixes)
        nicknames = convert_arg(nicknames, [name.lower()])
        mime_types = convert_arg(mime_types)
        insecure = category == self.CAT_SCRIPT if insecure is None else insecure

        logger = self.session.logger
        if name in self._formats:
            registrant = lambda bi: "unknown registrant" \
                if bi is None else "%s bundle" % bi.name
            logger.info(
                "Replacing data format '%s' as defined by %s with definition"
                " from %s" % (name, registrant(
                    self._formats[name][0]), registrant(bundle_info)))
        from .format import DataFormat
        data_format = DataFormat(name, category, suffixes, nicknames,
                                 mime_types, reference_url, insecure, encoding,
                                 synopsis, allow_directory)
        for suffix in suffixes:
            self._suffix_to_formats.setdefault(suffix, []).append(data_format)
        self._formats[name] = (bundle_info, data_format)
        if raise_trigger:
            self.triggers.activate_trigger("data formats changed", self)

    def add_provider(self,
                     bundle_info,
                     name,
                     *,
                     category=None,
                     suffixes=None,
                     nicknames=None,
                     mime_types=None,
                     reference_url=None,
                     insecure=None,
                     encoding=None,
                     synopsis=None,
                     allow_directory=False,
                     **kw):
        logger = self.session.logger
        if kw:
            logger.warning(
                "Data format provider '%s' supplied unknown keywords with format"
                " description: %s" % (name, repr(kw)))
        if category is None:
            logger.warning(
                "Data format provider '%s' didn't specify a category."
                "  Using catch-all category '%s'" % (name, self.CAT_GENERAL))
            category = self.CAT_GENERAL
        self.add_format(name,
                        category,
                        suffixes=suffixes,
                        nicknames=nicknames,
                        bundle_info=bundle_info,
                        mime_types=mime_types,
                        reference_url=reference_url,
                        insecure=insecure,
                        encoding=encoding,
                        synopsis=synopsis,
                        allow_directory=allow_directory,
                        raise_trigger=False)

    def open_format_from_suffix(self, suffix):
        """
        Given a file suffix (starting with a '.'), return the corresponding openable data format.
            Returns None if there is no such format.
        """
        from chimerax.open_command import NoOpenerError
        return self._format_from_suffix(self.session.open_command.open_info,
                                        NoOpenerError, suffix)

    def open_format_from_file_name(self, file_name):
        """
        Given a file name, return the corresponding openable data format.
            Raises NoFormatError if there is no such format.
        """
        "Return data format based on file_name's suffix, ignoring compression suffixes"
        return self._format_from_filename(self.open_format_from_suffix,
                                          file_name)

    def qt_file_filter(self, fmt):
        """
        Given a data format 'fmt', return a string usable as a member of the list argument
        used with the setNameFilters() method of a Qt file dialog.
        """
        return "%s (%s)" % (fmt.synopsis, "*" + " *".join(fmt.suffixes))

    def save_format_from_suffix(self, suffix):
        """
        Given a file suffix (starting with a '.'), return the corresponding savable data format.
            Returns None if there is no such format.
        """
        from chimerax.save_command import NoSaverError
        return self._format_from_suffix(self.session.save_command.save_info,
                                        NoSaverError, suffix)

    def save_format_from_file_name(self, file_name):
        """
        Given a file name, return the corresponding saveable data format.
            Raises NoFormatError if there is no such format.
        """
        "Return data format based on file_name's suffix, ignoring compression suffixes"
        return self._format_from_filename(self.save_format_from_suffix,
                                          file_name)

    @property
    def formats(self):
        """ Returns a list of all known data formats """
        return [info[1] for info in self._formats.values()]

    def end_providers(self):
        self.triggers.activate_trigger("data formats changed", self)

    def __getitem__(self, key):
        if not isinstance(key, str):
            raise TypeError("Data format key is not a string")
        if key in self._formats:
            return self._formats[key][1]
        for bi, format_data in self._formats.values():
            if key in format_data.nicknames:
                return format_data
        raise KeyError("No known data format '%s'" % key)

    def __len__(self):
        return len(self._formats)

    def __iter__(self):
        '''iterator over models'''
        return iter(self.formats)

    def _format_from_filename(self, suffix_func, file_name):
        if '.' in file_name:
            from chimerax import io
            base_name = io.remove_compression_suffix(file_name)
            from os.path import splitext
            root, ext = splitext(base_name)
            if not ext:
                raise NoFormatError(
                    "'%s' has only compression suffix; cannot determine"
                    " format from suffix" % file_name)
            data_format = suffix_func(ext)
            if not data_format:
                raise NoFormatError(
                    "No known data format for file suffix '%s'" % ext)
        else:
            raise NoFormatError("Cannot determine format for '%s'" % file_name)
        return data_format

    def _format_from_suffix(self, info_func, error_type, suffix):
        if '#' in suffix:
            suffix = suffix[:suffix.index('#')]
        try:
            formats = self._suffix_to_formats[suffix]
        except KeyError:
            return None

        for fmt in formats:
            try:
                info_func(fmt)
            except error_type:
                pass
            else:
                return fmt
        return None
示例#27
0
class RotamerLibManager(ProviderManager):
    """Manager for rotmer libraries"""
    def __init__(self, session):
        self.session = session
        self.rot_libs = None
        from chimerax.core.triggerset import TriggerSet
        self.triggers = TriggerSet()
        self.triggers.add_trigger("rotamer libs changed")
        self._library_info = {}
        self.settings = _RotamerManagerSettings(session, "rotamer lib manager")
        self._uninstalled_suffix = " [not installed]"

    def library(self, name):
        try:
            lib_info = self._library_info[name]
        except KeyError:
            raise NoRotamerLibraryError("No rotamer library named %s" % name)
        from . import RotamerLibrary
        if not isinstance(lib_info, RotamerLibrary):
            self._library_info[name] = lib_info = lib_info.run_provider(
                self.session, name, self)
        return lib_info

    def library_names(self, *, installed_only=False):
        if not installed_only:
            return list(self._library_info.keys())
        from . import RotamerLibrary
        lib_names = []
        for name, info in self._library_info.items():
            if isinstance(info, RotamerLibrary) or info.installed:
                lib_names.append(name)
        return lib_names

    def library_name_menu(self,
                          *,
                          initial_lib=None,
                          installed_only=False,
                          callback=None):
        from PyQt5.QtWidgets import QPushButton, QMenu
        menu_button = QPushButton()
        if initial_lib is None:
            lib_name = self.settings.gui_lib_name
        else:
            lib_name = initial_lib
        if lib_name not in self.library_names(installed_only=installed_only):
            lib_name = self.default_command_library_name
        menu_button.setText(lib_name)
        menu = QMenu()
        menu_button.setMenu(menu)
        menu.aboutToShow.connect(lambda menu=menu, installed=installed_only:
                                 self._menu_show_cb(menu, installed))
        menu.triggered.connect(lambda action, button=menu_button, cb=callback:
                               self._menu_choose_cb(action, button, cb))
        return menu_button

    def library_name_option(self, *, installed_only=False):
        from chimerax.ui.options import Option

        class RotLibOption(Option):
            def _make_widget(self,
                             *,
                             mgr=self,
                             installed_only=installed_only,
                             **kw):
                self.widget = mgr.library_name_menu(
                    initial_lib=self.default,
                    installed_only=installed_only,
                    callback=self.make_callback)

            def get_value(self):
                return self.widget.text()

            def set_value(self, val):
                self.widget.setText(val)

            value = property(get_value, set_value)

            def set_multiple(self):
                self.widget.setText(self.multiple_value)

        return RotLibOption

    @property
    def default_command_library_name(self):
        available_libs = self.library_names()
        for lib_name in available_libs:
            if "Dunbrack" in lib_name:
                lib = lib_name
                break
        else:
            if available_libs:
                lib = list(available_libs)[0]
            else:
                raise LimitationError("No rotamer libraries installed")
        return lib

    def add_provider(self, bundle_info, name, **kw):
        self._library_info[name] = bundle_info

    def end_providers(self):
        self.triggers.activate_trigger("rotamer libs changed", self)

    def _menu_choose_cb(self, action, button, callback):
        menu_text = action.text()
        if menu_text.endswith(self._uninstalled_suffix):
            lib_name = menu_text[:-len(self._uninstalled_suffix)]
        else:
            lib_name = menu_text
        button.setText(lib_name)
        self.settings.gui_lib_name = lib_name
        if callback:
            callback()

    def _menu_show_cb(self, menu, installed_only):
        menu.clear()
        names = self.library_names(installed_only=installed_only)
        if not names:
            raise LimitationError(
                "No rotamer libraries %s!" %
                ("installed" if installed_only else "available"))
        names.sort()
        installed = set(self.library_names(installed_only=True))
        for name in names:
            if name in installed:
                menu.addAction(name)
            else:
                menu.addAction(name + self._uninstalled_suffix)
示例#28
0
class JobManager(ProviderManager):
    def __init__(self, session, *args, **kwargs):
        # 1.2 adds an __init__ to ProviderManager that uses the manager name for things
        # this is not the case in 1.1

        self.session = session
        self.local_jobs = []
        self.remote_jobs = []
        self.unknown_status_jobs = []
        self.paused = False
        self._thread = None
        self.initialized = False
        self.queue_dict = {}
        self.formats = {}

        self.triggers = TriggerSet()
        self.triggers.add_trigger(JOB_FINISHED)
        self.triggers.add_handler(JOB_FINISHED, self.job_finished)
        self.triggers.add_trigger(JOB_STARTED)
        self.triggers.add_handler(JOB_STARTED, self.job_started)
        self.triggers.add_trigger(JOB_QUEUED)
        self.triggers.add_handler(JOB_QUEUED, self.check_queue)
        self.triggers.add_handler(JOB_QUEUED, self.write_json)

        params = signature(super().__init__).parameters
        if any("name" in param for param in params):
            super().__init__(*args, **kwargs)
        else:
            super().__init__()

    def __setattr__(self, attr, val):
        if attr == "paused":
            if val:
                print("paused SEQCROW queue")
            else:
                print("resumed SEQCROW queue")

        super().__setattr__(attr, val)

    @property
    def jobs(self):
        return self.local_jobs + self.remote_jobs

    @property
    def has_job_running(self):
        return any([job.isRunning() for job in self.local_jobs])

    def add_provider(self, bundle_info, name):
        print("adding %s format" % name)
        if name in self.formats:
            self.session.logger.warning(
                "local job type %s from %s supplanted that from %s" %
                (name, bundle_info.name, self.formats[name].name))
        self.formats[name] = bundle_info

    def init_queue(self):
        """reads cached job list to fill in the queue"""
        scr_dir = os.path.abspath(
            self.session.seqcrow_settings.settings.SCRATCH_DIR)
        self.jobs_list_filename = os.path.join(scr_dir, "job_list-3.json")
        if os.path.exists(self.jobs_list_filename):
            with open(self.jobs_list_filename, 'r') as f:
                queue_dict = load(f, cls=ATDecoder)

            for section in ["check", "queued", "finished", "error", "killed"]:
                if section not in queue_dict:
                    continue
                for job in queue_dict[section]:
                    if job['server'] == 'local':
                        for name, bi in self.formats.items():
                            if name == job["format"]:
                                job_cls = bi.run_provider(
                                    self.session, name, self)
                                local_job = job_cls(
                                    job['name'],
                                    self.session,
                                    job['theory'],
                                    geometry=job['geometry'],
                                    auto_update=job['auto_update'],
                                    auto_open=job['auto_open'],
                                )

                                if section == "check":
                                    if 'output' in job and os.path.exists(
                                            job['output']):
                                        fr = FileReader(job['output'],
                                                        just_geom=False)
                                        if 'finished' in fr.other and fr.other[
                                                'finished']:
                                            local_job.isFinished = lambda *args, **kwargs: True
                                        else:
                                            local_job.isRunning = lambda *args, **kwargs: True
                                            self.unknown_status_jobs.append(
                                                local_job)

                                elif section == "finished":
                                    #shh it's finished
                                    local_job.isFinished = lambda *args, **kwargs: True
                                    local_job.output_name = job['output']
                                    local_job.scratch_dir = job['scratch']

                                elif section == "error":
                                    #shh it's finished
                                    local_job.isFinished = lambda *args, **kwargs: True
                                    local_job.error = True
                                    local_job.output_name = job['output']
                                    local_job.scratch_dir = job['scratch']

                                elif section == "killed":
                                    local_job.isFinished = lambda *args, **kwargs: True
                                    local_job.killed = True
                                    local_job.output_name = job['output']
                                    local_job.scratch_dir = job['scratch']

                                local_job.output_name = job['output']
                                local_job.scratch_dir = job['scratch']

                                self.local_jobs.append(local_job)
                                self.session.logger.info(
                                    "added %s (%s job) from previous session" %
                                    (job['name'], job['format']))
                                break
                        else:
                            self.session.logger.warning(
                                "local job provider for %s jobs is no longer installed,"
                                % job["format"] +
                                "job named '%s' will be removed from the queue"
                                % job["name"])

                            self.local_jobs.append(local_job)

            self.paused = queue_dict['job_running']

            if len(queue_dict['queued']) > 0:
                self.check_queue()

            if self.paused:
                self.session.logger.warning(
                    "SEQCROW's queue has been paused because a local job was running when ChimeraX was closed. The queue can be resumed with SEQCROW's job manager tool"
                )

        self.initialized = True

    def write_json(self, *args, **kwargs):
        """updates the list of cached jobs"""
        d = {
            'finished': [],
            'queued': [],
            'check': [],
            'error': [],
            'killed': []
        }
        job_running = False
        for job in self.jobs:
            if not job.killed:
                if not job.isFinished() and not job.isRunning():
                    d['queued'].append(job.get_json())

                elif job.isFinished() and not job.error:
                    d['finished'].append(job.get_json())

                elif job.isFinished() and job.error:
                    d['error'].append(job.get_json())

                elif job.isRunning():
                    d['check'].append(job.get_json())
                    job_running = True

            elif job.isFinished():
                d['killed'].append(job.get_json())

        d['job_running'] = job_running

        if not self.initialized:
            self.init_queue()

        #check if SEQCROW scratch directory exists before trying to write json
        if not os.path.exists(os.path.dirname(self.jobs_list_filename)):
            os.makedirs(os.path.dirname(self.jobs_list_filename))

        with open(self.jobs_list_filename, 'w') as f:
            dump(d, f, cls=ATEncoder, indent=4)

    def job_finished(self, trigger_name, job):
        """when a job is finished, open or update the structure as requested"""
        if self.session.seqcrow_settings.settings.JOB_FINISHED_NOTIFICATION == \
          'log and popup notifications' and self.session.ui.is_gui:
            #it's just an error message for now
            #TODO: make my own logger
            self.session.logger.error("%s: %s" % (trigger_name, job))

        else:
            job.session.logger.info("%s: %s" % (trigger_name, job))

        if isinstance(job, LocalJob):
            self._thread = None
            if not hasattr(job, "output_name") or \
               not os.path.exists(job.output_name):
                job.error = True

            else:
                fr = FileReader(job.output_name, just_geom=False)
                #XXX: finished is not added to the FileReader for ORCA and Psi4 when finished = False
                if 'finished' not in fr.other or not fr.other['finished']:
                    job.error = True

        if job.auto_update and (
                job.theory.geometry.chix_atomicstructure is not None
                and not job.theory.geometry.chix_atomicstructure.deleted):
            if os.path.exists(job.output_name):
                finfo = job.output_name
                try:
                    finfo = (job.output_name, job.format_name, None)

                    fr = FileReader(finfo, get_all=True, just_geom=False)
                    if len(fr.atoms) > 0:
                        job.session.filereader_manager.triggers.activate_trigger(
                            ADD_FILEREADER,
                            ([job.theory.geometry.chix_atomicstructure], [fr]))

                        rescol = ResidueCollection(fr)
                        rescol.update_chix(
                            job.theory.geometry.chix_atomicstructure)

                except:
                    job.update_structure()

            if fr.all_geom is not None and len(fr.all_geom) > 1:
                coordsets = rescol.all_geom_coordsets(fr)

                job.theory.geometry.chix_atomicstructure.remove_coordsets()
                job.theory.geometry.chix_atomicstructure.add_coordsets(
                    coordsets)

                for i, coordset in enumerate(coordsets):
                    job.theory.geometry.chix_atomicstructure.active_coordset_id = i + 1

                    for atom, coord in zip(
                            job.theory.geometry.chix_atomicstructure.atoms,
                            coordset):
                        atom.coord = coord

                job.theory.geometry.chix_atomicstructure.active_coordset_id = job.theory.geometry.chix_atomicstructure.num_coordsets

        elif job.auto_open or job.auto_update:
            if hasattr(job, "output_name") and os.path.exists(job.output_name):
                if job.format_name:
                    run(
                        job.session, "open \"%s\" coordsets true format %s" %
                        (job.output_name, job.format_name))
                else:
                    run(job.session,
                        "open \"%s\" coordsets true" % job.output_name)
            else:
                self.session.logger.error("could not open output of %s" %
                                          repr(job))

        self.triggers.activate_trigger(JOB_QUEUED, trigger_name)
        pass

    def job_started(self, trigger_name, job):
        """prints 'job started' notification to log"""
        job.session.logger.info("%s: %s" % (trigger_name, job))
        pass

    def add_job(self, job):
        """add job (LocalJob instance) to the queue"""
        if not self.initialized:
            self.init_queue()
        if isinstance(job, LocalJob):
            self.local_jobs.append(job)
            self.triggers.activate_trigger(JOB_QUEUED, job)

    def increase_priotity(self, job):
        """move job (LocalJob) up one position in the queue"""
        if isinstance(job, LocalJob):
            ndx = self.local_jobs.index(job)
            if ndx != 0:
                self.local_jobs.remove(job)
                new_ndx = 0
                for i in range(min(ndx - 1, len(self.local_jobs) - 1), -1, -1):
                    if not self.local_jobs[i].killed and \
                            not self.local_jobs[i].isFinished() and \
                            not self.local_jobs[i].isRunning():
                        new_ndx = i
                        break

                self.local_jobs.insert(new_ndx, job)

                self.triggers.activate_trigger(JOB_QUEUED, job)

    def decrease_priotity(self, job):
        """move job (LocalJob) down one position in the queue"""
        if isinstance(job, LocalJob):
            ndx = self.local_jobs.index(job)
            if ndx != (len(self.local_jobs) - 1):
                self.local_jobs.remove(job)
                new_ndx = len(self.local_jobs)
                for i in range(ndx, len(self.local_jobs)):
                    if not self.local_jobs[i].killed and \
                            not self.local_jobs[i].isFinished() and \
                            not self.local_jobs[i].isRunning():
                        new_ndx = i + 1
                        break

                self.local_jobs.insert(new_ndx, job)

                self.triggers.activate_trigger(JOB_QUEUED, job)

    def check_queue(self, *args):
        """check to see if a waiting job can run"""
        for job in self.unknown_status_jobs:
            if isinstance(job, LocalJob):
                fr = FileReader(job.output_name, just_geom=False)
                if 'finished' in fr.other and fr.other['finished']:
                    job.isFinished = lambda *args, **kwargs: True
                    job.isRunning = lambda *args, **kwargs: False
                    self.unknown_status_jobs.remove(job)
                    self.triggers.activate_trigger(JOB_FINISHED, job)
                    return

        if not self.has_job_running:
            unstarted_local_jobs = []
            for job in self.local_jobs:
                if not job.isFinished() and not job.killed:
                    unstarted_local_jobs.append(job)

            if len(unstarted_local_jobs) > 0 and not self.paused:
                start_job = unstarted_local_jobs.pop(0)

                self._thread = start_job
                start_job.finished.connect(
                    lambda data=start_job: self.triggers.activate_trigger(
                        JOB_FINISHED, data))
                start_job.started.connect(lambda data=start_job: self.triggers.
                                          activate_trigger(JOB_STARTED, data))
                start_job.start()

            else:
                self._thread = None
示例#29
0
class OpenManager(ProviderManager):
    """Manager for open command"""
    def __init__(self, session):
        self.session = session
        self._openers = {}
        self._fetchers = {}
        from chimerax.core.triggerset import TriggerSet
        self.triggers = TriggerSet()
        self.triggers.add_trigger("open command changed")

    def add_provider(self,
                     bundle_info,
                     name,
                     *,
                     type="open",
                     want_path=False,
                     check_path=True,
                     batch=False,
                     format_name=None,
                     is_default=True,
                     synopsis=None,
                     example_ids=None,
                     **kw):
        logger = self.session.logger

        bundle_name = _readable_bundle_name(bundle_info)
        is_default = bool_cvt(is_default, name, bundle_name, "is_default")
        want_path = bool_cvt(want_path, name, bundle_name, "want_path")
        check_path = bool_cvt(check_path, name, bundle_name, "check_path")
        if batch or not check_path:
            want_path = True
        type_description = "Open-command" if type == "open" else type.capitalize(
        )
        if kw:
            logger.warning(
                "%s provider '%s' supplied unknown keywords in provider"
                " description: %s" % (type_description, name, repr(kw)))
        if type == "open":
            try:
                data_format = self.session.data_formats[name]
            except KeyError:
                logger.warning(
                    "Open-command provider in bundle %s specified unknown"
                    " data format '%s';"
                    " skipping" % (bundle_name, name))
                return
            if data_format in self._openers:
                logger.warning(
                    "Replacing opener for '%s' from %s bundle with that from"
                    " %s bundle" %
                    (data_format.name,
                     _readable_bundle_name(
                         self._openers[data_format].bundle_info), bundle_name))
            self._openers[data_format] = OpenerProviderInfo(
                bundle_info, name, want_path, check_path, batch)
        elif type == "fetch":
            if not name:
                raise ValueError("Database fetch in bundle %s has empty name" %
                                 bundle_name)
            if len(name) == 1:
                raise ValueError(
                    "Database fetch '%s' in bundle %s has single-character name which is"
                    " disallowed to avoid confusion with Windows drive letters"
                    % (name, bundle_name))
            if format_name is None:
                raise ValueError(
                    "Database fetch '%s' in bundle %s failed to specify"
                    " file format name" % (name, bundle_name))
            try:
                data_format = self.session.data_formats[format_name]
            except KeyError:
                raise ValueError(
                    "Database-fetch provider '%s' in bundle %s specified"
                    " unknown data format '%s'" %
                    (name, bundle_name, format_name))
            if name in self._fetchers and format_name in self._fetchers[name]:
                logger.warning(
                    "Replacing fetcher for '%s' and format %s from %s bundle"
                    " with that from %s bundle" %
                    (name, format_name,
                     _readable_bundle_name(
                         self._fetchers[name][format_name].bundle_info),
                     bundle_name))
            if example_ids:
                example_ids = ",".split(example_ids)
            else:
                example_ids = []
            if synopsis is None:
                synopsis = "%s (%s)" % (name.capitalize(), format_name)
            self._fetchers.setdefault(name,
                                      {})[format_name] = FetcherProviderInfo(
                                          bundle_info, is_default, example_ids,
                                          synopsis)
            if is_default and len([
                    fmt for fmt, info in self._fetchers[name].items()
                    if info.is_default
            ]) > 1:
                logger.warning(
                    "Multiple default formats declared for database fetch"
                    " '%s'" % name)
        else:
            logger.warning(
                "Unknown provider type '%s' with name '%s' from bundle %s" %
                (type, name, bundle_name))

    def database_info(self, database_name):
        try:
            return self._fetchers[database_name]
        except KeyError:
            raise NoOpenerError("No such database '%s'" % database_name)

    @property
    def database_names(self):
        return list(self._fetchers.keys())

    def end_providers(self):
        self.triggers.activate_trigger("open command changed", self)

    def fetch_args(self, database_name, *, format_name=None):
        try:
            db_formats = self._fetchers[database_name]
        except KeyError:
            raise NoOpenerError("No such database '%s'" % database_name)
        from chimerax.core.commands import commas
        if format_name:
            try:
                provider_info = db_formats[format_name]
            except KeyError:
                # for backwards compatibility, try the nicknames of the format
                try:
                    df = self.session.data_formats[format_name]
                except KeyError:
                    nicks = []
                else:
                    nicks = df.nicknames + df.name
                for nick in nicks:
                    try:
                        provider_info = db_formats[nick]
                        format_name = nick
                    except KeyError:
                        continue
                    break
                else:
                    raise NoOpenerError(
                        "Format '%s' not supported for database '%s'."
                        "  Supported formats are: %s" %
                        (format_name, database_name,
                         commas([dbf for dbf in db_formats])))
        else:
            for format_name, provider_info in db_formats.items():
                if provider_info.is_default:
                    break
            else:
                raise NoOpenerError(
                    "No default format for database '%s'."
                    "  Possible formats are: %s" %
                    (database_name, commas([dbf for dbf in db_formats])))
        try:
            args = self.open_args(self.session.data_formats[format_name])
        except NoOpenerError:
            # fetch-only type (e.g. cellPACK)
            args = {}
        args.update(
            provider_info.bundle_info.run_provider(self.session, database_name,
                                                   self).fetch_args)
        return args

    def open_data(self, path, **kw):
        """
        Given a file path and possibly format-specific keywords, return a (models, status message)
        tuple.  The models will not have been opened in the session.

        The format name can be provided with the 'format' keyword if the filename suffix of the path
        does not correspond to those for the desired format.
        """
        from .cmd import provider_open
        return provider_open(self.session, [path],
                             _return_status=True,
                             _add_models=False,
                             **kw)

    @property
    def open_data_formats(self):
        """
        The data formats for which an opener function has been registered.
        """
        return list(self._openers.keys())

    def open_args(self, data_format):
        try:
            provider_info = self._openers[data_format]
        except KeyError:
            raise NoOpenerError("No opener registered for format '%s'" %
                                data_format.name)
        return provider_info.bundle_info.run_provider(self.session,
                                                      provider_info.name,
                                                      self).open_args

    def open_info(self, data_format):
        try:
            provider_info = self._openers[data_format]
            return (provider_info.bundle_info.run_provider(
                self.session, provider_info.name, self), provider_info)
        except KeyError:
            raise NoOpenerError("No opener registered for format '%s'" %
                                data_format.name)
示例#30
0
class PresetsManager(ProviderManager):
    """Manager for presets"""
    def __init__(self, session):
        self.session = session
        from . import settings
        settings.settings = settings._PresetsSettings(session, "presets")
        settings.settings.triggers.add_handler("setting changed",
                                               self._new_custom_folder_cb)
        self._presets = {}
        from chimerax.core.triggerset import TriggerSet
        self.triggers = TriggerSet()
        self.triggers.add_trigger("presets changed")
        self._new_custom_folder_cb()
        if session.ui.is_gui:
            session.ui.triggers.add_handler('ready',
                                            lambda *arg, ses=session: settings.
                                            register_settings_options(session))

    @property
    def presets_by_category(self):
        return {
            cat: [name for name in info.keys()]
            for cat, info in self._presets.items()
        }

    def preset_function(self, category, preset_name):
        return self._presets[category][preset_name]

    def remove_presets(self, category, preset_names):
        for name in preset_names:
            del self._presets[category][name]
        self.triggers.activate_trigger("presets changed", self)

    def add_presets(self, category, preset_info):
        """'preset_info' should be a dictionary of preset-name -> callback-function/command-string"""
        self._add_presets(category, preset_info)
        self.triggers.activate_trigger("presets changed", self)

    def add_provider(self,
                     bundle_info,
                     name,
                     order=None,
                     category="General",
                     **kw):
        from chimerax.core.utils import CustomSortString
        if order is None:
            cname = name
        else:
            cname = CustomSortString(name, sort_val=int(order))

        def cb(name=name, mgr=self, bi=bundle_info):
            bi.run_provider(self.session, name, self)

        try:
            self._presets[category][cname] = cb
        except KeyError:
            self._presets[category] = {cname: cb}

    def end_providers(self):
        self.triggers.activate_trigger("presets changed", self)

    def execute(self, preset):
        if callable(preset):
            preset()
            self.session.logger.info(
                "Preset implemented in Python; no expansion to individual ChimeraX"
                " commands available.")
        else:
            from chimerax.core.commands import run
            num_lines = 0
            with self.session.undo.aggregate("preset"):
                for line in preset.splitlines():
                    run(self.session, line, log=False)
                    num_lines += 1
            if num_lines == 1:
                parts = [p.strip() for p in preset.split(';')]
                display_lines = '\n'.join(parts)
            else:
                display_lines = preset
            self.session.logger.info(
                'Preset expands to these ChimeraX commands: '
                '<div style="padding-left:4em;padding-top:0px;margin-top:0px">'
                '<pre style="margin:0;padding:0">%s</pre></div>' %
                display_lines,
                is_html=True)

    def _add_presets(self, category, preset_info):
        self._presets.setdefault(category, {}).update({
            name: lambda p=preset: self.execute(p)
            for name, preset in preset_info.items()
        })

    def _gather_presets(self, folder):
        import os, os.path
        preset_info = {}
        subfolders = []
        for entry in os.listdir(folder):
            entry_path = os.path.join(folder, entry)
            if os.path.isdir(entry_path):
                subfolders.append(entry)
                continue
            if entry.endswith(".cxc"):
                f = open(entry_path, "r")
                preset_info[entry[:-4].replace('_', ' ')] = f.read()
                f.close()
            elif entry.endswith(".py"):
                from chimerax.core.commands import run, FileNameArg
                preset_info[entry[:-3].replace('_', ' ')] = lambda p=FileNameArg.unparse(entry_path), \
                    run=run, ses=self.session: run(ses, "open " + p, log=False)
        return preset_info, subfolders

    def _new_custom_folder_cb(self, *args):
        from .settings import settings
        if not settings.folder:
            return
        import os.path
        if not os.path.exists(settings.folder):
            self.session.logger.warning(
                "Custom presets folder '%s' does not exist" % settings.folder)
        presets_added = False
        preset_info, subfolders = self._gather_presets(settings.folder)
        if preset_info:
            self._add_presets("Custom", preset_info)
            presets_added = True
        for subfolder in subfolders:
            subpath = os.path.join(settings.folder, subfolder)
            preset_info, subsubfolders = self._gather_presets(subpath)
            if preset_info:
                self._add_presets(subfolder.replace('_', ' '), preset_info)
                presets_added = True
            else:
                self.session.logger.warning(
                    "No presets found in custom preset folder %s" % subpath)
        if args:
            # actual trigger callback, rather than startup call
            if presets_added:
                self.triggers.activate_trigger("presets changed", self)
        if not presets_added:
            self.session.logger.warning(
                "No presets found in custom preset folder %s" %
                settings.folder)