#A* -------------------------------------------------------------------
#B* This file contains source code for the PyMOL computer program
#C* Copyright (c) Schrodinger, LLC.
#D* -------------------------------------------------------------------
#E* It is unlawful to modify or remove this copyright notice.
#F* -------------------------------------------------------------------
#G* Please see the accompanying LICENSE file for further information.
#H* -------------------------------------------------------------------
#I* Additional authors of this source file include:
#-*
#-*
#-*
#Z* -------------------------------------------------------------------

from __future__ import print_function

from .constants import CURRENT_STATE, ALL_STATES

if True:

    import time
    from . import selector
    import pymol
    cmd = __import__("sys").modules["pymol.cmd"]
    from .cmd import _cmd,lock,unlock,Shortcut, \
          _feedback,fb_module,fb_mask,is_list, \
          DEFAULT_ERROR, DEFAULT_SUCCESS, _raising, is_ok, is_error

    def auto_measure(_self=cmd):
        lst = _self.get_names("selections")
        if "pk1" in lst:
            if "pk2" in lst:
                if "pk3" in lst:
                    if "pk4" in lst:
                        _self.dihedral()
                    else:
                        _self.angle()
                else:
                    _self.distance()
        _self.unpick()

    def get_volume_field(objName, state=1, copy=1, _self=cmd):
        '''
DESCRIPTION

    EXPERIMENTAL AND SUBJECT TO CHANGE - DO NOT USE
    API only. Get the raw data of a map or volume object.

ARGUMENTS

    objName = str: object name

    state = int: state index {default: 1}

    copy = 0/1: {default: 1} WARNING: only use copy=0 if you know what you're
    doing. copy=0 will return a numpy array which is a wrapper of the internal
    memory. If the internal memory gets freed or reallocated, this wrapper
    will become invalid.
        '''
        with _self.lockcm:
            r = _self._cmd.get_volume_field(_self._COb, objName, int(state) - 1, int(copy))
        return r

    def get_volume_histogram(objName, bins=64, range=None, _self=cmd):
        '''
DESCRIPTION

    API ONLY.  Get min, max, mean, stdev and histogram of a map or volume
    object as a list of length bins + 4.
        '''
        with _self.lockcm:
            r = _self._cmd.get_volume_histogram(_self._COb,objName,
                    int(bins), range or (0., 0.))
        return r

    def get_unused_name(prefix="tmp",alwaysnumber=1,_self=cmd):
        with _self.lockcm:
            r = _cmd.get_unused_name(_self._COb, prefix, alwaysnumber)
        return r

    def get_modal_draw(_self=cmd,quiet=1):
        with _self.lockcm:
            r = _cmd.get_modal_draw(_self._COb)
        return r

    def get_drag_object_name(_self=cmd):
        with _self.lockcm:
            r = _cmd.get_drag_object_name(_self._COb)
        return r

    def get_object_matrix(object,state=1, incl_ttt=1, _self=cmd):
        '''
DESCRIPTION

    "get_object_matrix" is an unsupported command that may have
    something to do with querying the transformation matrices
    associated with an object
        '''
        object = str(object)
        with _self.lockcm:
            r = _cmd.get_object_matrix(_self._COb,str(object), int(state)-1, int(incl_ttt))
        return r

    def get_object_ttt(object, quiet=1, _self=cmd):
        '''
DESCRIPTION

    "get_object_ttt" is an unsupported command
        '''
        quiet = int(quiet)
        with _self.lockcm:
            r = _cmd.get_object_ttt(_self._COb, str(object), -1, quiet)
        if not quiet:
            if r is None:
                print('TTT is None')
            else:
                for i in range(4):
                    if i == 3:
                        print('TTT ---------------------------+---------')
                    print('TTT %8.2f %8.2f %8.2f | %8.2f' % tuple(r[i * 4:i * 4 + 4]))
        return r

    def get_object_settings(object, state=ALL_STATES, quiet=1, _self=cmd):
        '''
DESCRIPTION

    "get_object_settings" is an unsupported command
        '''
        with _self.lockcm:
            r = _cmd.get_object_settings(_self._COb, str(object), int(state) - 1)
        return r

    def get_object_list(selection="(all)", quiet=1, _self=cmd):
        '''
        
DESCRIPTION

    "get_object_list" is an unsupported command that may have
    something to do with querying the objects covered by a selection.
    '''
        selection = selector.process(selection)
        with _self.lockcm:
            r = _cmd.get_object_list(_self._COb,str(selection))
            if not quiet:
                if(is_list(r)):
                    print(" get_object_list: ",str(r))
        return r

    def get_symmetry(selection="(all)",state=CURRENT_STATE,quiet=1,_self=cmd):
        '''
DESCRIPTION

    "get_symmetry" can be used to obtain the crystal
    and spacegroup parameters for a molecule or map.

USAGE

    get_symmetry object-name-or-selection

PYMOL API

    cmd.get_symmetry(string selection, int state, int quiet)


        '''
        selection = selector.process(selection)
        with _self.lockcm:
            r = _cmd.get_symmetry(_self._COb,str(selection),int(state) - 1)
        if not quiet:
            if r:
                        print(" get_symmetry: A     = %7.3f B    = %7.3f C     = %7.3f"%tuple(r[0:3]))
                        print(" get_symmetry: Alpha = %7.3f Beta = %7.3f Gamma = %7.3f"%tuple(r[3:6]))
                        print(" get_symmetry: SpaceGroup = %s"%r[6])
            else:
                        print(" get_symmetry: No symmetry defined.")
        return r

    def get_title(object, state, quiet=1, _self=cmd):
        '''
DESCRIPTION

    "get_title" retrieves a text string to the state of a particular
    object which will be displayed when the state is active.

USAGE

    set_title object, state

PYMOL API

    cmd.set_title(string object, int state, string text)

    '''
        with _self.lockcm:
            r = _cmd.get_title(_self._COb,str(object),int(state)-1)
        if not quiet:
                if r is not None:
                    print(" get_title: %s"%r)
        return r


    def angle(name=None, selection1="(pk1)", selection2="(pk2)",
	      selection3="(pk3)", mode=None, label=1, reset=0,
	      zoom=0, state=ALL_STATES, quiet=1, _self=cmd,
              state1=-3, state2=-3, state3=-3):

        '''
DESCRIPTION

    "angle" shows the angle formed between any three atoms.

USAGE

    angle [ name [, selection1 [, selection2 [, selection3 ]]]]

NOTES

    "angle" alone will show the angle angle formed by selections (pk1),
    (pk2), (pk3) which can be set using the "PkAt" mouse action
    (typically, Ctrl-middle-click)

PYMOL API

    cmd.angle(string name, string selection1, string selection2,
              string selection3)

SEE ALSO

    distance, dihedral
    '''

        r = DEFAULT_SUCCESS
        if selection1=="(pk1)":
            if "pk1" not in _self.get_names('selections'):
                if _feedback(fb_module.cmd,fb_mask.errors,_self):
                    print("cmd-Error: The 'pk1' selection is undefined.")
                r = DEFAULT_ERROR
        if selection2=="(pk2)":
            if "pk2" not in _self.get_names('selections'):
                if _feedback(fb_module.cmd,fb_mask.errors,_self):
                    print("cmd-Error: The 'pk2' selection is undefined.")
                r = DEFAULT_ERROR
        if selection3=="(pk3)":
            if "pk3" not in _self.get_names('selections'):
                if _feedback(fb_module.cmd,fb_mask.errors,_self):
                    print("cmd-Error: The 'pk3' selection is undefined.")
                r = DEFAULT_ERROR
        if is_ok(r):
            r = DEFAULT_ERROR

            # if unlabeled, then get next name in series

            if name is not None:
                nam=name
            else:
                try:
                    _self.lock(_self)
                    cnt = _self.get_setting_int("dist_counter") + 1
                    r = _self.set("dist_counter", cnt)
                    nam = "angle%02.0f" % cnt
                finally:
                    _self.unlock(r,_self)

            # defaults
            if mode is None:
                mode = 0
            # preprocess selections
            selection1 = selector.process(selection1)
            selection2 = selector.process(selection2)
            selection3 = selector.process(selection3)
            # now do the deed
            try:
                _self.lock(_self)
                if selection2!="same":
                    selection2 = "("+selection2+")"
                if selection3!="same":
                    selection3 = "("+selection3+")"
                r = _cmd.angle(_self._COb,str(nam),"("+str(selection1)+")",
                               str(selection2),
                               str(selection3),
                               int(mode),int(label),int(reset),
                               int(zoom),int(quiet),int(state)-1,
                               int(state1)-1, int(state2)-1, int(state3)-1)
            finally:
                _self.unlock(r,_self)
        if _raising(r,_self): raise pymol.CmdException
        return r

    def dihedral(name=None, selection1="(pk1)", selection2="(pk2)",
		 selection3="(pk3)", selection4="(pk4)", mode=None,
		 label=1, reset=0, zoom=0, state=ALL_STATES, quiet=1, _self=cmd):
        '''
DESCRIPTION

    "dihedral" shows dihedral angles formed between any four atoms.

USAGE

    dihedral [ name [, selection1 [, selection2 [, selection3 [, selection4 ]]]]]


NOTES

    "dihedral" alone will show the dihedral angle formed by selections
    (pk1), (pk2), (pk3), and (pk4), which can be set using the "PkAt"
    mouse action (typically, Ctrl-middle-click)

PYMOL API

    cmd.dihedral(string name, string selection1, string selection2,
                 string selection3, string selection4)

SEE ALSO

    distance, angle
    '''
        r = DEFAULT_SUCCESS
        if selection1=="(pk1)":
            if "pk1" not in _self.get_names('selections'):
                if _feedback(fb_module.cmd,fb_mask.errors,_self):
                    print("cmd-Error: The 'pk1' selection is undefined.")
                r = DEFAULT_ERROR
        if selection2=="(pk2)":
            if "pk2" not in _self.get_names('selections'):
                if _feedback(fb_module.cmd,fb_mask.errors,_self):
                    print("cmd-Error: The 'pk2' selection is undefined.")
                r = DEFAULT_ERROR
        if selection3=="(pk3)":
            if "pk3" not in _self.get_names('selections'):
                if _feedback(fb_module.cmd,fb_mask.errors,_self):
                    print("cmd-Error: The 'pk3' selection is undefined.")
                r = DEFAULT_ERROR
        if selection3=="(pk4)":
            if "pk4" not in _self.get_names('selections'):
                if _feedback(fb_module.cmd,fb_mask.errors,_self):
                    print("cmd-Error: The 'pk4' selection is undefined.")
                r = DEFAULT_ERROR
        if is_ok(r):
            r = DEFAULT_ERROR
            # if unlabeled, then get next name in series

            if name is not None:
                nam=name
            else:
                try:
                    _self.lock(_self)
                    cnt = _self.get_setting_int("dist_counter") + 1
                    r = _self.set("dist_counter", cnt)
                    nam = "dihedral%02.0f" % cnt
                finally:
                    _self.unlock(r,_self)

            # defaults
            if mode is None:
                mode = 0
            # preprocess selections
            selection1 = selector.process(selection1)
            selection2 = selector.process(selection2)
            selection3 = selector.process(selection3)
            selection4 = selector.process(selection4)
            # now do the deed
            try:
                _self.lock(_self)
                if selection2!="same":
                    selection2 = "("+selection2+")"
                if selection3!="same":
                    selection3 = "("+selection3+")"
                if selection4!="same":
                    selection4 = "("+selection4+")"
                r = _cmd.dihedral(_self._COb,str(nam),"("+str(selection1)+")",
                                  str(selection2),
                                  str(selection3),
                                  str(selection4),
                                  int(mode),int(label),
                                  int(reset),int(zoom),
                                  int(quiet),int(state)-1)
            finally:
                _self.unlock(r,_self)
        if _raising(r,_self): raise pymol.CmdException
        return r

    def distance(name=None, selection1="(pk1)", selection2="(pk2)",
		 cutoff=None, mode=None, zoom=0, width=None, length=None,
                 gap=None, label=1, quiet=1, reset=0, state=ALL_STATES,
                 state1=-3, state2=-3, _self=cmd):

        '''
DESCRIPTION

    "distance" creates a new distance object between two selections.

USAGE
    
    distance [name [, selection1 [, selection2 [, cutoff [, mode ]]]]]

ARGUMENTS

    name = string: name of the distance object to create

    selection1 = string: first atom selection

    selection2 = string: second atom selection

    cutoff = float: longest distance to show 
    
    mode = 0: all interatomic distances

    mode = 1: only bond distances

    mode = 2: only show polar contact distances

    mode = 3: like mode=0, but use distance_exclusion setting

    mode = 4: distance between centroids (does not support
              dynamic_measures; new in PyMOL 1.8.2)

    mode = 5: pi-pi and pi-cation interactions

    mode = 6: pi-pi interactions

    mode = 7: pi-cation interactions

    mode = 8: like mode=3, but cutoff is the ratio between
              distance and sum of VDW radii

    state = int: object state to create the measurement object in
    and to get coordinates from {default: 0 (all states)}

    state1, state2 = int: overrule 'state' argument to measure distances
    between different states {default: use state}

EXAMPLES

    distance mydist, 14/CA, 29/CA

    distance hbonds, all, all, 3.2, mode=2

NOTES

    The distance wizard makes measuring distances easier than using
    the "dist" command for real-time operations.

    "dist" alone will show distances between selections (pk1) and (pk1),
    which can be set using the PkAt mouse action (usually CTRL-middle-click).

PYMOL API

    cmd.distance(string name, string selection1, string selection2,
                 string cutoff, string mode )

    '''
        # handle unnamed distance
        r = DEFAULT_SUCCESS
        if name is not None:
            if len(name):
                if name[0]=='(' or ' ' in name or '/' in name: # we're one argument off...
                    if cutoff is not None:
                        mode = cutoff
                    if selection2!="(pk2)":
                        cutoff = selection2
                    if selection1!="(pk1)":
                        selection2 = selection1
                    selection1=name
                    name = None

        if selection1=="(pk1)":
            if "pk1" not in _self.get_names('selections'):
                if _feedback(fb_module.cmd,fb_mask.errors,_self):
                    print("cmd-Error: The 'pk1' selection is undefined.")
                r = DEFAULT_ERROR
        if selection2=="(pk2)":
            if "pk2" not in _self.get_names('selections'):
                if _feedback(fb_module.cmd,fb_mask.errors,_self):
                    print("cmd-Error: The 'pk2' selection is undefined.")
                r = DEFAULT_ERROR
        if is_ok(r):
            r = DEFAULT_ERROR

            # if unlabeled, then get next name in series

            if name is not None:
                nam=name
            else:
                try:
                    _self.lock(_self)
                    cnt = _self.get_setting_int("dist_counter") + 1
                    r = _self.set("dist_counter", cnt)
                    nam = "dist%02.0f" % cnt
                finally:
                    _self.unlock(r,_self)

            # defaults
            if mode is None:
                mode = 0
            if cutoff is None:
                cutoff = -1.0
            # preprocess selections
            selection1 = selector.process(selection1)
            selection2 = selector.process(selection2)
            # now do the deed
            try:
                _self.lock(_self)
                if selection2!="same":
                    selection2 = "("+selection2+")"
                r = _cmd.dist(_self._COb,str(nam),"("+str(selection1)+")",
                              str(selection2),int(mode),float(cutoff),
                              int(label),int(quiet),int(reset),
                              int(state)-1,int(zoom),
                              int(state1)-1, int(state2)-1)
                if width is not None:
                    _self.set("dash_width",width,nam)
                if length is not None:
                    _self.set("dash_length",length,nam)
                if gap is not None:
                    _self.set("dash_gap",gap,nam)
            finally:
                _self.unlock(r,_self)
        if (r<0.0) and (not quiet):
            # a negative value is an warning signal from PyMOL...
            r = DEFAULT_ERROR
        if _raising(r,_self): raise pymol.CmdException
        return r

    # LEGACY support for cmd.dist
    dist = distance

    def pi_interactions(name="",
                        selection1="all",
                        selection2="same",
                        state=ALL_STATES,
                        state1=-3,
                        state2=-3,
                        quiet=1,
                        reset=0,
                        _self=cmd):
        '''
DESCRIPTION

    Find pi-pi and pi-cation interactions.

    Identical to cmd.distance(..., mode=5, label=0)

SEE ALSO

    distance
        '''
        raise pymol.IncentiveOnlyException()

    def get_povray(_self=cmd):
        '''
DESCRIPTION

    "get_povray" returns a tuple corresponding to strings for a PovRay
    input file.

PYMOL API

    cmd.get_povray()

        '''
        with _self.lockcm:
            r = _cmd.get_povray(_self._COb)
        return r

    def get_idtf(quiet=1, _self=cmd):
        '''
DESCRIPTION

    "get_idft" is under development, but should eventually return an
    idtf file containing multiple objects and scenes.

PYMOL API

    cmd.idtf()

        '''
        with _self.lockcm:
            r = _cmd.get_idtf(_self._COb)

        if not quiet:
            fov = _self.get_setting_float("field_of_view")
            dist = _self.get_view()[11]
            print(" 3Daac=%3.1f, 3Droll=0, 3Dc2c=0 0 1, 3Droo=%1.2f, 3Dcoo=0 0 %1.2f" % (fov, -dist, dist))

        return r

    def get_mtl_obj(_self=cmd):
        '''
DESCRIPTION

    NOTE: this is an incomplete and unsupported feature.

    "get_mtl_obj" returns a tuple containing mtl and obj input files
    for use with Maya.

PYMOL API

    cmd.get_obj_mtl()

        '''
        with _self.lockcm:
            r = _cmd.get_mtl_obj(_self._COb)
        return r

    def get_version(quiet=1,_self=cmd):
        '''
DESCRIPTION
 
    "get_version" returns a tuple of length six containing text,
    floating point, and integer representations of the current PyMOL
    version number, build date as unix timestamp, GIT SHA and SVN
    code revision so far available.
   
PYMOL API

    cmd.get_version(int quiet)

        '''
        # get_version doesn't need the _COb and doesn't require a lock
        r = _cmd.get_version()
        if True:
            quiet = int(quiet)
            if quiet < 1 and _feedback(fb_module.cmd, fb_mask.results, _self):
                import re
                p = pymol.get_version_message(r)
                print(re.sub(r'^', ' ', p, re.M))
                if quiet < 0:
                    if r[3]:
                        print(' build date:', time.strftime('%c %Z', time.localtime(r[3])))
                    if r[4]:
                        print(' git sha:', r[4])
        return r

    def get_vrml(version=2,_self=cmd):
        '''
DESCRIPTION

    "get_vrml" returns a VRML2 string representing the content
    currently displayed.

PYMOL API

    cmd.get_vrml()

        '''
        with _self.lockcm:
            r = _cmd.get_vrml(_self._COb,int(version))
        return r

    def get_collada(version=2, _self=cmd):
        '''
DESCRIPTION

    "get_collada" returns a COLLADA string representing the content
    currently displayed.

PYMOL API

    cmd.get_collada()

        '''
        with _self.lockcm:
            r = _cmd.get_collada(_self._COb,int(version))
        return r

    def get_gltf(filename, quiet=1, _self=cmd):
        '''
DESCRIPTION

    "get_gltf" saves a gltf file representing the content
    currently displayed.

PYMOL API

    cmd.get_gltf()

        '''

        from distutils.spawn import find_executable
        exe = find_executable('collada2gltf')
        if exe is None:
            raise pymol.CmdException('could not find collada2gltf')

        COLLADA_VERSION = 2
        r = _self.get_collada(COLLADA_VERSION)

        # write collada file
        with open(filename, 'w') as handle:
            handle.write(r)

        import subprocess

        result = subprocess.call([exe, '-i', filename, '-o', filename])
                # convert collada file to gltf by using collada2gltf binary

        if not quiet:
            if result == 0:
                print(' Save: wrote "' + filename + '".')
            else:
                print(' Save-Error: no file written')

        return result

    def count_states(selection="(all)", quiet=1, _self=cmd):
        '''
DESCRIPTION

    "count_states" returns the number of states in the selection.

USAGE

    count_states
    
PYMOL API

    cmd.count_states(string selection)

SEE ALSO

    frame
    '''
        # preprocess selection
        selection = selector.process(selection)
        #
        with _self.lockcm:
            r = _cmd.count_states(_self._COb,selection)
        if not quiet:
                print(" cmd.count_states: %d states."%r)
        return r

    def get_movie_length(quiet=1, images=-1, _self=cmd):
        '''
DESCRIPTION

    "get_movie_length" returns the number of frames explicitly defined
    in the movie, not including molecular states.

PYMOL API

    cmd.count_frames()

SEE ALSO

    frame, count_states, count_frames
    '''
        with _self.lockcm:
            r = _cmd.get_movie_length(_self._COb)
            if r<0:
                if images==0:
                    r = 0
                elif images<0:
                    r = -r
            if images == 1:
                if r>0:
                    r = 0
            if r>=0 and not quiet:
                print(" cmd.get_movie_length: %d frames"%r)
        return r

    def count_frames(quiet=1, _self=cmd):
        '''
DESCRIPTION

    "count_frames" returns the number of frames defined for the PyMOL
    movie.

USAGE

    count_frames
    
PYMOL API

    cmd.count_frames()

SEE ALSO

    frame, count_states
    '''
        with _self.lockcm:
            r = _cmd.count_frames(_self._COb)
            if not quiet: print(" cmd.count_frames: %d frames"%r)
        return r

    def overlap(selection1, selection2, state1=1, state2=1, adjust=0.0, quiet=1, _self=cmd):
        '''
DESCRIPTION

    "overlap" is an unsupported command that sums up
    [(VDWi + VDWj) - distance_ij]/2 between pairs of
    selected atoms.

PYMOL API

    cmd.overlap(string selection1, string selection2 [, int state1=1, int state2=1, float adjust=0.0])

NOTES

    It does not compute the volume overlap,
    selections with more atoms will have a larger
    sum.

    '''

        # preprocess selections
        selection1 = selector.process(selection1)
        selection2 = selector.process(selection2)
        #
        with _self.lockcm:
            r = _cmd.overlap(_self._COb,str(selection1),str(selection2),
                                  int(state1)-1,int(state2)-1,
                                  float(adjust))
            if not quiet: print(" cmd.overlap: %5.3f Angstroms."%r)
        return r

    def get_movie_locked(_self=cmd):
        with _self.lockcm:
            r = _cmd.get_movie_locked(_self._COb)
        return r

    def get_object_color_index(name,_self=cmd):
        name = str(name)
        with _self.lockcm:
            r = _cmd.get_object_color_index(_self._COb,name)
        return r

    def get_color_tuple(name,mode=0,_self=cmd):
        '''Get the RGB color tuple for a color (range 0.0 to 1.0)
        :param name: color name or index
        :param mode: don't use (mode 4 returns negative R for special colors)
        '''
        name=str(name)
        mod = int(mode)
        if mode in (1, 2):
            print(' Warning: use get_color_indices instead of get_color_tuple(mode={})'.format(mode))
        elif mode == 3:
            print(' Warning: use get_color_index instead of get_color_tuple(mode=3)')
        with _self.lockcm:
            r = _cmd.get_color(_self._COb,name,mode)
            if r is None:
                if _feedback(fb_module.cmd,fb_mask.errors,_self):
                    print("cmd-Error: Unknown color '%s'."%name)
        return r

    def get_color_indices(all=0,_self=cmd):
        with _self.lockcm:
            if all:
                r = _cmd.get_color(_self._COb,'',2)
            else:
                r = _cmd.get_color(_self._COb,'',1)
        return r

    def get_color_index(color,_self=cmd):
        with _self.lockcm:
            r = _cmd.get_color(_self._COb,str(color),3)
        return r

    def get_renderer(quiet=1, _self=cmd):
        '''
DESCRIPTION

    Prints OpenGL renderer information.
        '''
        with _self.lockcm:
            r = _cmd.get_renderer(_self._COb)

        if not int(quiet):
            print(" OpenGL graphics engine:")
            print("  GL_VENDOR:   ", r[0])
            print("  GL_RENDERER: ", r[1])
            print("  GL_VERSION:  ", r[2])

        return r

    def get_phipsi(selection="(name CA)",state=CURRENT_STATE,_self=cmd):
        # preprocess selections
        selection = selector.process(selection)
        #
        with _self.lockcm:
            r = _cmd.get_phipsi(_self._COb,"("+str(selection)+")",int(state)-1)
        return r

    def get_atom_coords(selection, state=ALL_STATES,quiet=1,_self=cmd):
        '''
DESCRIPTION

    "get_atom_coords" returns the 3D coordinates of a single atom.
    
    '''
        # low performance way to get coords for a single atom
        selection = selector.process(selection)
        with _self.lockcm:
            r = _cmd.get_atom_coords(_self._COb,str(selection),int(state)-1,int(quiet))
        if not quiet:
            for a in r:
                print(" cmd.get_atom_coords: [%8.3f,%8.3f,%8.3f]"%(a))
        return r

    def get_coords(selection='all', state=1, quiet=1, _self=cmd):
        '''
DESCRIPTION

    API only. Get selection coordinates as numpy array.

ARGUMENTS

    selection = str: atom selection {default: all}

    state = int: state index or all states if state=0 {default: 1}
        '''
        selection = selector.process(selection)
        with _self.lockcm:
            r = _cmd.get_coords(_self._COb, selection, int(state) - 1)
            return r

    def get_coordset(name, state=1, copy=1, quiet=1, _self=cmd):
        '''
DESCRIPTION

    API only. Get object coordinates as numpy array.

ARGUMENTS

    selection = str: atom selection {default: all}

    state = int: state index {default: 1}

    copy = 0/1: {default: 1} WARNING: only use copy=0 if you know what you're
    doing. copy=0 will return a numpy array which is a wrapper of the internal
    coordinate set memory. If the internal memory gets freed or reallocated,
    this wrapper will become invalid.
        '''
        with _self.lockcm:
            r = _cmd.get_coordset(_self._COb, name, int(state) - 1, int(copy))
            return r


    def get_position(quiet=1, _self=cmd):
        '''
DESCRIPTION

    "get_position" returns the 3D coordinates of the center of the
    viewer window.

    '''

        with _self.lockcm:
            r = _cmd.get_position(_self._COb)
        if not quiet:
            print(" cmd.get_position: [%8.3f,%8.3f,%8.3f]"%(r[0],r[1],r[2]))
        return r

    def get_distance(atom1="pk1",atom2="pk2",state=CURRENT_STATE,quiet=1,_self=cmd):
        '''
DESCRIPTION

    "get_distance" returns the distance between two atoms.  By default, the
    coordinates used are from the current state, however an alternate
    state identifier can be provided.

USAGE

    get_distance atom1, atom2, [,state ]

EXAMPLES

    get_distance 4/n,4/c
    get_distance 4/n,4/c,state=4
    
PYMOL API

    cmd.get_distance(atom1="pk1",atom2="pk2",state=-1)

        '''
        # preprocess selections
        atom1 = selector.process(atom1)
        atom2 = selector.process(atom2)
        #
        with _self.lockcm:
            r = _cmd.get_distance(_self._COb,str(atom1),str(atom2),int(state)-1)
        if not quiet:
            print(" cmd.get_distance: %5.3f Angstroms."%r)
        return r

    def get_angle(atom1="pk1",atom2="pk2",atom3="pk3",state=CURRENT_STATE,quiet=1,_self=cmd):
        '''
DESCRIPTION

    "get_angle" returns the angle between three atoms.  By default, the
    coordinates used are from the current state, however an alternate
    state identifier can be provided.

USAGE

    get_angle atom1, atom2, atom3, [,state ]

EXAMPLES

    get_angle 4/n,4/c,4/ca
    get_angle 4/n,4/c,4/ca,state=4

PYMOL API

    cmd.get_angle(atom1="pk1",atom2="pk2",atom3="pk3",state=-1)

        '''
        # preprocess selections
        atom1 = selector.process(atom1)
        atom2 = selector.process(atom2)
        atom3 = selector.process(atom3)
        #
        with _self.lockcm:
            r = _cmd.get_angle(_self._COb,str(atom1),str(atom2),str(atom3),int(state)-1)
        if not quiet:
            print(" cmd.get_angle: %5.3f degrees."%r)
        return r

    def get_dihedral(atom1="pk1",atom2="pk2",atom3="pk3",atom4="pk4",state=CURRENT_STATE,quiet=1,_self=cmd):
        '''
DESCRIPTION

    "get_dihedral" returns the dihedral angle between four atoms.  By
    default, the coordinates used are from the current state, however
    an alternate state identifier can be provided.

    By convention, positive dihedral angles are right-handed
    (looking down the atom2-atom3 axis).

USAGE

    get_dihedral atom1, atom2, atom3, atom4 [,state ]

EXAMPLES

    get_dihedral 4/n,4/c,4/ca,4/cb
    get_dihedral 4/n,4/c,4/ca,4/cb,state=4

PYMOL API

    cmd.get_dihedral(atom1,atom2,atom3,atom4,state=-1)

        '''
        # preprocess selections
        atom1 = selector.process(atom1)
        atom2 = selector.process(atom2)
        atom3 = selector.process(atom3)
        atom4 = selector.process(atom4)
        #
        with _self.lockcm:
            r = _cmd.get_dihe(_self._COb,str(atom1),str(atom2),str(atom3),str(atom4),int(state)-1)
        if not quiet:
            print(" cmd.get_dihedral: %5.3f degrees."%r)
        return r

    def get_model(selection="(all)",state=1,ref='',ref_state=0,_self=cmd):
        '''
DESCRIPTION

    "get_model" returns a ChemPy "Indexed" format model from a selection.

PYMOL API

    cmd.get_model(string selection [,int state] )

        '''
        # preprocess selection
        selection = selector.process(selection)
        #
        with _self.lockcm:
            r = _cmd.get_model(_self._COb,"("+str(selection)+")",int(state)-1,str(ref),int(ref_state)-1)
        return r

    def get_bonds(selection="(all)", state=CURRENT_STATE, _self=cmd):
        '''
DESCRIPTION

    Get a list of (atm1, atm2, order) tuples for bonds with coordinates in
    the given state (same logic as cmd.get_model()).

    WARNING: atm1/atm2 are 0-based indices! They enumerate the atoms in the
    selection and do not necessarily correspond to the "index" atom property.

    To get a atm1/atm2 to index mapping, you can do:
    >>> stored.indices = []
    >>> cmd.iterate_state(state, selection, "stored.indices.append(index)")

SEE ALSO

    cmd.get_model().bond
        '''
        with _self.lockcm:
            return _cmd.get_bonds(_self._COb, selection, int(state) - 1)

    def get_area(selection="(all)",state=1,load_b=0,quiet=1,_self=cmd):
        '''
DESCRIPTION

    Get the surface area of an selection. Depends on the "dot_solvent"
    setting. With "dot_solvent=off" (default) it calculates the solvent
    excluded surface area, else the surface accessible surface.

USAGE

    get_area [ selection [, state [, load_b ]]]

ARGUMENTS

    load_b = bool: store per-atom surface area in b-factors {default: 0}

SEE ALSO

    "dot_solvent" setting, "dots" representation (show dots)
        '''
        # preprocess selection
        selection = selector.process(selection)
        #
        with _self.lockcm:
            r = _cmd.get_area(_self._COb,"("+str(selection)+")",int(state)-1,int(load_b))
        if not quiet:
            print(" cmd.get_area: %5.3f Angstroms^2."%r)
        return r

    def get_chains(selection="(all)",state=ALL_STATES,quiet=1,_self=cmd):
        '''
DESCRIPTION

    Print the list of chain identifiers in the given selection.

USAGE

    get_chains [ selection [, state ]]

ARGUMENTS

    selection = str: atom selection {default: all}

    state = int: CURRENTLY IGNORED
        '''
        # preprocess selection
        selection = selector.process(selection)
        #
        with _self.lockcm:
            r = _cmd.get_chains(_self._COb,"("+str(selection)+")",int(state)-1)
        if r is None:
            return []
        if not quiet:
            print(" cmd.get_chains: ",str(r))
        return r


    def get_names(type='public_objects',enabled_only=0,selection="",_self=cmd):
        '''
DESCRIPTION

    "get_names" returns a list of object and/or selection names.

PYMOL API

    cmd.get_names( [string: "objects"|"selections"|"all"|"public_objects"|"public_selections"] )

NOTES

    The default behavior is to return only object names.

SEE ALSO

    get_type, count_atoms, count_states
        '''
        selection = selector.process(selection)
        # this needs to be replaced with a flag & masking scheme...
        if type=='objects':
            mode = 1
        elif type=='selections':
            mode = 2
        elif type=='all':
            mode = 0
        elif type=='public':
            mode = 3
        elif type=='public_objects':
            mode = 4
        elif type=='public_selections':
            mode = 5
        elif type=='public_nongroup_objects':
            mode = 6
        elif type=='public_group_objects':
            mode = 7
        elif type=='nongroup_objects':
            mode = 8
        elif type=='group_objects':
            mode = 9
        else:
            raise pymol.CmdException("unknown type: '{}'".format(type))
        with _self.lockcm:
            r = _cmd.get_names(_self._COb,int(mode),int(enabled_only),str(selection))
        return r

    def get_legal_name(name,_self=cmd):
        with _self.lockcm:
            r = _cmd.get_legal_name(_self._COb,str(name))
        return r

    def get_type(name,quiet=1,_self=cmd):
        '''
DESCRIPTION

    "get_type" returns a string describing the named object or
     selection or the string "nonexistent" if the name in unknown.

PYMOL API

    cmd.get_type(string object-name)

NOTES

    Possible return values are

    "object:molecule"
    "object:map"
    "object:mesh"
    "object:slice"
    "object:surface"
    "object:measurement"
    "object:cgo"
    "object:group"
    "object:volume"
    "selection"

SEE ALSO

    get_names
        '''
        with _self.lockcm:
            r = _cmd.get_type(_self._COb,str(name))
        if not quiet:
            print(r)
        return r

    def id_atom(selection,mode=0,quiet=1,_self=cmd):
        '''
DESCRIPTION

    "id_atom" returns the original source id of a single atom, or
    raises and exception if the atom does not exist or if the selection
    corresponds to multiple atoms.

PYMOL API

    list = cmd.id_atom(string selection)
        '''
        r = DEFAULT_ERROR
        selection = str(selection)
        l = identify(selection, mode)
        ll = len(l)
        if not ll:
            if _feedback(fb_module.cmd,fb_mask.errors,_self):
                print("cmd-Error: atom %s not found by id_atom." % selection)
            if _self._raising(_self=_self): raise pymol.CmdException
        elif ll>1:
            if _feedback(fb_module.cmd,fb_mask.errors,_self):
                print("cmd-Error: multiple atoms %s found by id_atom." % selection)
            if _self._raising(_self=_self): raise pymol.CmdException
        else:
            r = l[0]
            if not quiet:
                if mode:
                    print(" cmd.id_atom: (%s and id %d)"%(r[0],r[1]))
                else:
                    print(" cmd.id_atom: (id %d)"%r)
        if _raising(r,_self): raise pymol.CmdException
        return r

    def identify(selection="(all)",mode=0,quiet=1,_self=cmd):
        '''
DESCRIPTION

    "identify" returns a list of atom IDs corresponding to the ID code
    of atoms in the selection.

PYMOL API

    list = cmd.identify(string selection="(all)",int mode=0)

NOTES

    mode 0: only return a list of identifiers (default)
    mode 1: return a list of tuples of the object name and the identifier

        '''
        # preprocess selection
        selection = selector.process(selection)
        #
        with _self.lockcm:
            r = _cmd.identify(_self._COb,"("+str(selection)+")",int(mode)) # 0 = default mode
        if is_list(r):
            if len(r):
                if not quiet:
                    if mode:
                        for a in r:
                            print(" cmd.identify: (%s and id %d)"%(a[0],a[1]))
                    else:
                        for a in r:
                            print(" cmd.identify: (id %d)"%a)
        return r

    def index(selection="(all)",quiet=1,_self=cmd):
        '''
DESCRIPTION

    "index" returns a list of tuples corresponding to the
    object name and index of the atoms in the selection.

PYMOL API

    list = cmd.index(string selection="(all)")

NOTE

  Atom indices are fragile and will change as atoms are added
  or deleted.  Whenever possible, use integral atom identifiers
  instead of indices.

        '''
        # preprocess selection
        selection = selector.process(selection)
        #
        with _self.lockcm:
            r = _cmd.index(_self._COb,"("+str(selection)+")",0) # 0 = default mode
        if not quiet:
            if is_list(r):
                if len(r):
                    for a in r:
                        print(" cmd.index: (%s`%d)"%(a[0],a[1]))
        return r

    def find_pairs(selection1,selection2,state1=1,state2=1,cutoff=3.5,mode=0,angle=45,_self=cmd):
        '''
DESCRIPTION

    API only function. Returns a list of atom pairs. Atoms are represented as
    (model,index) tuples.

    Can be restricted to hydrogen-bonding-like contacts. WARNING: Only checks
    atom orientation, not atom type (so would hydrogen bond between carbons for
    example), so make sure to provide appropriate atom selections.

ARGUMENTS

    selection1, selection2 = string: atom selections

    state1, state2 = integer: state-index (only positive values allowed) {default: 1}

    cutoff = float: distance cutoff {default: 3.5}

    mode = integer: if mode=1, do coarse hydrogen bonding assessment {default: 0}

    angle = float: hydrogen bond angle cutoff, only if mode=1 {default: 45.0}

NOTE

    Although this does a similar job like "distance", it uses a completely
    different routine and the "mode" argument has different meanings!
        '''
        # preprocess selection
        selection1 = selector.process(selection1)
        selection2 = selector.process(selection2)
        #
        with _self.lockcm:
            r = _cmd.find_pairs(_self._COb,"("+str(selection1)+")",
                                      "("+str(selection2)+")",
                                      int(state1)-1,int(state2)-1,
                                      int(mode),float(cutoff),float(angle))
        return r

    def get_extent(selection="(all)",state=ALL_STATES,quiet=1,_self=cmd):
        '''
DESCRIPTION

    "get_extent" returns the minimum and maximum XYZ coordinates of a
    selection as an array:
     [ [ min-X , min-Y , min-Z ],[ max-X, max-Y , max-Z ]]

PYMOL API

    cmd.get_extent(string selection="(all)", state=0 )

        '''
        # preprocess selection
        selection = selector.process(selection)
        #
        with _self.lockcm:
            r = _cmd.get_min_max(_self._COb,str(selection),int(state)-1)
        if not quiet:
            print(" cmd.extent: min: [%8.3f,%8.3f,%8.3f]"%(r[0][0],r[0][1],r[0][2]))
            print(" cmd.extent: max: [%8.3f,%8.3f,%8.3f]"%(r[1][0],r[1][1],r[1][2]))
        return r

    def phi_psi(selection="(byres pk1)", quiet=1, _self=cmd):
        '''
DESCRIPTION

    "phi_psi" return the phi and psi angles for a protein atom
    selection.
    
USAGE
        '''

        r = cmd.get_phipsi(selection)
        if not quiet and isinstance(r, dict):
            for a in sorted(r):
                    _self.iterate("(%s`%d)"%a,"print(' %-9s "+
                                ("( %6.1f, %6.1f )"%r[a])+
                                "'%(resn+'-'+resi+':'))")
        return r


    def count_atoms(selection="(all)",quiet=1,state=ALL_STATES,domain='',_self=cmd):
        '''
DESCRIPTION

    "count_atoms" returns a count of atoms in a selection.

USAGE

    count_atoms [ selection [, quiet [, state ]]]
        '''
        r = DEFAULT_ERROR
        # preprocess selection
        selection = selector.process(selection)
        #
        try:
            _self.lock(_self)
            r = _cmd.select(_self._COb,"_count_tmp","("+str(selection)+")",1,int(state)-1,str(domain))
            _cmd.delete(_self._COb,"_count_tmp")
        finally:
            _self.unlock(r,_self)
        if not quiet: print(" count_atoms: %d atoms"%r)
        if _raising(r,_self): raise pymol.CmdException
        return r

    def count_discrete(selection, quiet=1, _self=cmd):
        '''
DESCRIPTION

    Count the number of discrete objects in selection.

USAGE

    count_discrete selection
        '''
        with _self.lockcm:
            r = _cmd.count_discrete(_self._COb, str(selection))
            if not int(quiet):
                print(' count_discrete: %d' % r)
            return r

    def get_names_of_type(type,public=1,_self=cmd):
        """
DESCRIPTION

    "get_names_of_type" will return a list of names for the given type.

        """
        obj_type = "public_objects" if public==1 else "objects"
        types = []
        mix = []
        obj = None
        try:
            obj = _self.get_names(obj_type)
        except:
            pass

        if obj:
            try:
                types = list(map(_self.get_type,obj))
                mix = list(zip(obj,types))
            except:
                pass
        lst = []
        for a in mix:
            if a[1]==type:
                lst.append(a[0])
        return lst


    def get_raw_alignment(name='',active_only=0,_self=cmd):
        '''
DESCRIPTION

    "get_raw_alignment" returns a list of lists of (object,index)
    tuples containing the raw per-atom alignment relationships

PYMOL API

    cmd.get_raw_alignment(name)

        '''
        with _self.lockcm:
            r = _cmd.get_raw_alignment(_self._COb,str(name),int(active_only))
        return r

    def get_object_state(name, _self=cmd):
        '''
DESCRIPTION

    Returns the effective object state.
        '''
        states = _self.count_states('%' + name)
        if states < 2 and _self.get_setting_boolean('static_singletons'):
            return 1
        if _self.get_setting_int('all_states', name):
            return 0
        state = _self.get_setting_int('state', name)
        if state > states:
            raise pymol.CmdException('Invalid state %d for object %s' % (state, name))
        return state

    def get_selection_state(selection, _self=cmd):
        '''
DESCRIPTION

    Returns the effective object state for all objects in given selection.
    Raises exception if objects are in different states.
        '''
        state_set = set(map(_self.get_object_state,
            _self.get_object_list('(' + selection + ')')))
        if len(state_set) != 1:
            if len(state_set) == 0:
                return 1
            raise pymol.CmdException('Selection spans multiple object states')
        return state_set.pop()

    def centerofmass(selection='(all)', state=CURRENT_STATE, quiet=1, _self=cmd):
        '''
DESCRIPTION

    Calculates the center of mass. Considers atom mass and occupancy.

ARGUMENTS

    selection = string: atom selection {default: all}

    state = integer: object state, -1 for current state, 0 for all states
    {default: -1}

NOTES

    If occupancy is 0.0 for an atom, set it to 1.0 for the calculation
    (assume it was loaded from a file without occupancy information).

SEE ALSO

    get_extent
        '''
        from chempy import cpv
        state, quiet = int(state), int(quiet)

        if state < 0:
            state = _self.get_selection_state(selection)
        if state == 0:
            states = list(range(1, _self.count_states(selection)+1))
        else:
            states = [state]

        com = cpv.get_null()
        totmass = 0.0

        for state in states:
            model = _self.get_model(selection, state)
            for a in model.atom:
                m = a.get_mass() * (a.q or 1.0)
                com = cpv.add(com, cpv.scale(a.coord, m))
                totmass += m

        if not totmass:
            raise pymol.CmdException('mass is zero')

        com = cpv.scale(com, 1./totmass)
        if not quiet:
            print(' Center of Mass: [%8.3f,%8.3f,%8.3f]' % tuple(com))
        return com

    def cif_get_array(name, key, dtype="s", quiet=1, _self=cmd):
        '''
DESCRIPTION

    EXPERIMENTAL AND SUBJECT TO CHANGE!

ARGUMENTS

    name = string: object name

    key = CIF data item name in lower case

    dtype = str: "s" (str), "i" (int) or "f" (float)
        '''
        with _self.lockcm:
            r = _cmd.cif_get_array(_self._COb, name, key, dtype)
        if r and not int(quiet):
            n = len(r)
            r_print = r if n < 10 else (r[:9] + ['... (%d more items)' % (n - 9)])
            print(" %s:" % (key), ', '.join(map(str, r_print)))
        return r

    def get_assembly_ids(name, quiet=1, _self=cmd):
        '''
DESCRIPTION

    EXPERIMENTAL AND SUBJECT TO CHANGE!
    Get the list of assembly ids for an object loaded from mmCIF.
        '''
        r = cif_get_array(name, "_pdbx_struct_assembly.id", _self=_self)
        if r and not int(quiet):
            print(" Assembly IDs: %s" % ', '.join(r))
        return r
