def filter_cb(self, event = None): from VolumeViewer import active_volume v = active_volume() if v is None: return ftype = self.filter_type.get() uv,r,params = self.unfiltered_volume(v, ftype) if uv: self.update_filtered_volume(v, ftype) else: step, subreg = self.step_and_subregion() try: fv, params = self.apply_filter(v, ftype, step, subreg) except MemoryError: from chimera.replyobj import warning warning('Out of memory calculating %s filter of %s' % (ftype, v.name)) return if fv is None: return self.update_thresholds(fv, v) fv.show() r = v.subregion(step, subreg) fv.filtered_volume = (ftype, v, r, params)
def show_saxs_profile(molecules, selected_only, epath, expath, dialog = None): if len(molecules) <= 2: name = ', '.join([m.name for m in molecules]) else: name = '%d models' % len(molecules) import tempfile, os fd, pdbpath = tempfile.mkstemp(suffix = '.pdb') os.close(fd) import Midas Midas.write(molecules, molecules[0], pdbpath, selOnly = selected_only) if not epath: # Use web services if no executable is given if dialog is None: dialog = PlotDialog() ProfileOpalService(pdbpath, expath, name, dialog.figure) else: cmd = '%s %s %s' % (epath, pdbpath, expath) from chimera.replyobj import info, warning info('Executing command: %s\n' % cmd) status = os.system(cmd) if status != 0: warning('Error %d executing command "%s"' % (status, cmd)) return dialog if expath: from os.path import basename ppath = pdbpath[:-4] + '_' + basename(expath) p = read_profile(ppath, 3) else: ppath = pdbpath + '.dat' p = read_profile(ppath, 2) plot_profile(p, name, dialog.figure) return dialog
def get_parameters_value(self): """get_parameters_value(): Output: dist distance marker1d marker 1 location marker2d marker 2 location Get parameters - distance and location of markers. """ if self.volume_path_dialog == None: msg = 'No marker set present!\n' self.status(msg, color='red', blankAfter=15) replyobj.warning(msg) return None,None,None linked_list = self.volume_path_dialog.selected_links() if len(linked_list) == 0: msg = 'No links present/selected!\n' self.status(msg, color='red', blankAfter=15) replyobj.warning(msg) return None, None, None link = linked_list[0] dist = self.find_link_distance(link) marker1d, marker2d = self.find_marker_location(link) return dist, marker1d, marker2d
def state_from_manager(self, volume_manager): self.data_and_regions_state = [] unsaved_data = [] for data, drlist in volume_manager.data_to_regions.items(): if data.path == '': unsaved_data.append(data) continue # Do not save data sets with no path ds = Data_State() ds.state_from_data(data) drslist = [] for dr in drlist: drs = Volume_State() drs.state_from_data_region(dr) drslist.append(drs) self.data_and_regions_state.append((ds, drslist)) if unsaved_data: names = ', '.join([d.name for d in unsaved_data]) from chimera.replyobj import warning warning('Volume data sets\n\n' + '\t%s\n\n' % names + 'were not saved in the session. To have them included\n' + 'in the session they must be saved in separate volume\n' + 'files before the session is saved. The session file only\n' + 'records file system paths to the volume data.')
def pairAlign(chains, cutoff, gapChar, statusPrefix=""): chain1, chain2 = chains # go through chain 1 and put each residue's principal # atom in a spatial tree from chimera.misc import principalAtom from CGLutil.AdaptiveTree import AdaptiveTree xyzs = [] data = [] for i in range(len(chain1)): res = chain1.residues[i] pa = principalAtom(res) if not pa: replyobj.warning("Cannot determine principal" " atom for residue %s\n" % res.oslIdent()) continue xyzs.append(pa.xformCoord().data()) data.append((i, pa.xformCoord())) tree = AdaptiveTree(xyzs, data, cutoff) # initialize score array from numpy import zeros scores = zeros((len(chain1),len(chain2)), float) scores -= 1.0 # find matches and update score array for i2 in range(len(chain2)): res = chain2.residues[i2] pa = principalAtom(res) if not pa: replyobj.warning("Cannot determine principal" " atom for residue %s\n" % res.oslIdent()) continue coord2 = pa.xformCoord() matches = tree.searchTree(coord2.data(), cutoff) for i1, coord1 in matches: dist = coord1.distance(coord2) if dist > cutoff: continue scores[i1][i2] = cutoff - dist # use NeedlemanWunsch to establish alignment from NeedlemanWunsch import nw score, seqs = nw(chain1, chain2, scoreMatrix=scores, gapChar=gapChar, returnSeqs=True, scoreGap=0, scoreGapOpen=0) smallest = min(len(chain1), len(chain2)) minDots = max(len(chain1), len(chain2)) - smallest extraDots = len(seqs[0]) - smallest - minDots numMatches = smallest - extraDots replyobj.status("%s%d residue pairs aligned\n" % (statusPrefix, numMatches), log=True) if numMatches == 0: from chimera import UserError raise UserError("Cannot generate alignment because no" " residues within cutoff distance") return score, seqs
def warnAddStd(mols, cm): ur, ua = addStandardCharges(models=mols, chargeModel=cm, status=replyobj.status) warning = unchargedAtomsWarning(ua) if warning: replyobj.warning(warning)
def __init__(self, topology, trajFileName): # since we need to be able to do seeks, can't use osOpen # which might return an unseekable stream self.topology = topology from OpenSave import osUncompressedPath path = osUncompressedPath(trajFileName) import os self.trajFileSize = os.stat(path).st_size self.traj = open(path, "rb") from xdrlib import Unpacker self.fileString = FileString(self.traj, 0, self.trajFileSize) self.xdr = Unpacker(self.fileString) self.crdStarts = [] while True: replyobj.status("Reading frame %d header\n" % ( len(self.crdStarts) + 1)) try: crdStart, endFrame = self._readHeader() except ValueError, e: raise ValueError("Frame %d: %s" % (len(self.crdStarts) + 1, str(e))) if endFrame > self.trajFileSize: if not self.crdStarts: raise ValueError("Computed size of" " first frame (%d) greater than" " trajectory file size (%s)" % (endFrame, self.trajFileSize)) replyobj.warning("Truncated trajectory file;" " skipping last partial frame.\n") else: self.crdStarts.append(crdStart) if endFrame == self.trajFileSize: break self.xdr.set_position(endFrame)
def state_from_manager(self, volume_manager): self.data_and_regions_state = [] unsaved_data = [] for data, drlist in volume_manager.data_to_regions.items(): if data.path == '': unsaved_data.append(data) continue # Do not save data sets with no path ds = Data_State() ds.state_from_data(data) drslist = [] for dr in drlist: drs = Volume_State() drs.state_from_data_region(dr) drslist.append(drs) self.data_and_regions_state.append((ds, drslist)) if unsaved_data: names = ', '.join([d.name for d in unsaved_data]) from chimera.replyobj import warning warning( 'Volume data sets\n\n' + '\t%s\n\n' % names + 'were not saved in the session. To have them included\n' + 'in the session they must be saved in separate volume\n' + 'files before the session is saved. The session file only\n' + 'records file system paths to the volume data.')
def _processObj(self, obj): if not hasattr(obj, 'bonds'): subobjs = obj.bondedUnits() if subobjs == [obj]: if hasattr(obj, "atoms") and obj.atoms: raise ValueError("Don't know how to" " handle MMTK object %s" % str(obj)) replyobj.warning("Skipping unknown MMTK object:" " %s\n" % str(obj)) return for so in subobjs: self._processObj(so) return if hasattr(obj, 'residues'): residues = obj.residues() resNames = [r.name[:3] for r in residues] else: residues = [obj] resNames = ["UNK"] self.resNames.extend(resNames) self.bonds.extend([(self.atomIndices[b.a1], self.atomIndices[b.a2]) for b in obj.bonds]) for res in residues: self.ipres.append(self.ipres[-1] + len(res.atoms))
def show_saxs_profile(molecules, selected_only, epath, expath, dialog=None): if len(molecules) <= 2: name = ', '.join([m.name for m in molecules]) else: name = '%d models' % len(molecules) import tempfile, os fd, pdbpath = tempfile.mkstemp(suffix='.pdb') os.close(fd) import Midas Midas.write(molecules, molecules[0], pdbpath, selOnly=selected_only) if not epath: # Use web services if no executable is given if dialog is None: dialog = PlotDialog() ProfileOpalService(pdbpath, expath, name, dialog.figure) else: cmd = '%s %s %s' % (epath, pdbpath, expath) from chimera.replyobj import info, warning info('Executing command: %s\n' % cmd) status = os.system(cmd) if status != 0: warning('Error %d executing command "%s"' % (status, cmd)) return dialog if expath: from os.path import basename ppath = pdbpath[:-4] + '_' + basename(expath) p = read_profile(ppath, 3) else: ppath = pdbpath + '.dat' p = read_profile(ppath, 2) plot_profile(p, name, dialog.figure) return dialog
def __init__(self, topology, trajFileName): # since we need to be able to do seeks, can't use osOpen # which might return an unseekable stream self.topology = topology from OpenSave import osUncompressedPath path = osUncompressedPath(trajFileName) import os self.trajFileSize = os.stat(path).st_size self.traj = open(path, "rb") from xdrlib import Unpacker self.fileString = FileString(self.traj, 0, self.trajFileSize) self.xdr = Unpacker(self.fileString) self.crdStarts = [] while True: replyobj.status("Reading frame %d header\n" % (len(self.crdStarts) + 1)) try: crdStart, endFrame = self._readHeader() except ValueError, e: raise ValueError("Frame %d: %s" % (len(self.crdStarts) + 1, str(e))) if endFrame > self.trajFileSize: if not self.crdStarts: raise ValueError("Computed size of" " first frame (%d) greater than" " trajectory file size (%s)" % (endFrame, self.trajFileSize)) replyobj.warning("Truncated trajectory file;" " skipping last partial frame.\n") else: self.crdStarts.append(crdStart) if endFrame == self.trajFileSize: break self.xdr.set_position(endFrame)
def warn_if_models_open(self): n = len(chimera.openModels.list()) if n > 0: msg = '%d models are open. Running benchmarks with models opened will produce inaccurate lower scores.' % n from chimera.replyobj import warning warning(msg) self.show_result('\n\n' + msg + '\n\n', color = 'red')
def warn_if_models_open(self): n = len(chimera.openModels.list()) if n > 0: msg = '%d models are open. Running benchmarks with models opened will produce inaccurate lower scores.' % n from chimera.replyobj import warning warning(msg) self.show_result('\n\n' + msg + '\n\n', color='red')
def _saveCB(okayed, dialog): if not okayed: return paths = dialog.getPaths() if not paths: replyobj.warning("No save file selected; aborting save.\n") return saveSession(paths[0]) chimera.setLastSession(paths[0])
def doneCB(ur, ua, cb=cb, status=status, models=models): if status: status("Done adding charges\n") if models is None: models = chimera.openModels.list(modelTypes=[chimera.Molecule]) warnMsg = "" if ur: warnMsg += "Correct charges are unknown for %d"\ " non-standard residue types\n\n" % len(ur) replyobj.info("Non-standard residue types:\n") for t, rs in ur.items(): info = ", ".join([str(r) for r in rs[:3]]) if len(rs) > 3: info += " + %d others" % (len(rs) - 3) replyobj.info("\t%s (%s)\n" % (t, info)) warnMsg = unchargedAtomsWarning(ua, warning=warnMsg) if warnMsg: warnMsg += "Charges of 0.0 were assigned to the" \ " unknown atoms\n\n" nonIntegral = [] from math import floor numNImodels = 0 def isNonIntegral(val): return abs(floor(val + 0.5) - val) > 0.0005 for m in models: totCharge = 0.0 for r in m.residues: resCharge = 0.0 for a in r.atoms: resCharge += getattr(a, 'charge', 0.0) totCharge += resCharge if isNonIntegral(resCharge): nonIntegral.append((r, resCharge)) tChargeMsg = "Total charge for %s: %.4f\n" % (str(m), totCharge) replyobj.info(tChargeMsg) if status: status(tChargeMsg) if isNonIntegral(totCharge): numNImodels += 1 if nonIntegral: if numNImodels: warnMsg += "%d model(s) had non-integral total"\ " charge\n" % numNImodels replyobj.info("The following residues had non-integral" " charges:\n") for r, charge in nonIntegral: replyobj.info("\t%s %g\n" % (str(r), charge)) if warnMsg: warnMsg += "Details in reply log\n" replyobj.warning( warnMsg, help="ContributedSoftware/addcharge/addcharge.html#warnings") if cb: cb(ur, ua)
def doneCB(ur, ua, cb=cb, status=status, models=models): if status: status("Done adding charges\n") if models is None: models = chimera.openModels.list( modelTypes=[chimera.Molecule]) warnMsg = "" if ur: warnMsg += "Correct charges are unknown for %d"\ " non-standard residue types\n\n" % len(ur) replyobj.info("Non-standard residue types:\n") for t, rs in ur.items(): info = ", ".join([str(r) for r in rs[:3]]) if len(rs) > 3: info += " + %d others" % ( len(rs) - 3) replyobj.info("\t%s (%s)\n" % (t, info)) warnMsg = unchargedAtomsWarning(ua, warning=warnMsg) if warnMsg: warnMsg += "Charges of 0.0 were assigned to the" \ " unknown atoms\n\n" nonIntegral = [] from math import floor numNImodels = 0 def isNonIntegral(val): return abs(floor(val+0.5) - val) > 0.0005 for m in models: totCharge = 0.0 for r in m.residues: resCharge = 0.0 for a in r.atoms: resCharge += getattr(a, 'charge', 0.0) totCharge += resCharge if isNonIntegral(resCharge): nonIntegral.append((r, resCharge)) tChargeMsg = "Total charge for %s: %.4f\n" % (str(m), totCharge) replyobj.info(tChargeMsg) if status: status(tChargeMsg) if isNonIntegral(totCharge): numNImodels += 1 if nonIntegral: if numNImodels: warnMsg += "%d model(s) had non-integral total"\ " charge\n" % numNImodels replyobj.info("The following residues had non-integral" " charges:\n") for r, charge in nonIntegral: replyobj.info("\t%s %g\n" % (str(r), charge)) if warnMsg: warnMsg += "Details in reply log\n" replyobj.warning(warnMsg, help="ContributedSoftware/addcharge/addcharge.html#warnings") if cb: cb(ur, ua)
def have_phantom(self): try: import _phantomcursor except: from chimera.replyobj import warning warning('Could not load Chimera _phantomcursor library.\n' + 'This library is only available on Windows.\n' + 'You must have the Sensable OpenHaptics libraries installed.\n') return 0 return 1
def run_mscalc(xyzr, probe_radius=1.4, vertex_density=2.0, all_components=True, fallback_to_single_component=True): from CGLutil.findExecutable import findExecutable mscalc_path = findExecutable('mscalc') if mscalc_path is None: raise Surface_Calculation_Error, 'No mscalc executable found' if all_components: allc = '1' else: allc = '0' args = (mscalc_path, '%f' % probe_radius, '%f' % vertex_density, allc) from numpy import array, intc xyzrs = array(len(xyzr), intc).tostring() + xyzr.tostring() cmd = ' '.join(args) from chimera.replyobj import info info(cmd + '\n') msout, mserr, status = run_shell_command(args, xyzrs) if status is None: raise Surface_Calculation_Error, 'Starting mscalc failed.' if mserr: info(mserr) try: if status != 0: raise Surface_Calculation_Error, 'Surface calculation failed, mscalc returned code %d' % status vfloat, vint, tri, atomareas, compareas = parse_mscalc_output(msout) if atomareas is None and len(xyzr) > 0: raise Surface_Calculation_Error, 'Surface calculation failed, produced empty surface.\n' except Surface_Calculation_Error: if fallback_to_single_component and all_components: from chimera.replyobj import warning warning( 'Calculation of some surface components failed.\nFalling back to single-component calculation.\n' ) return run_mscalc(xyzr, probe_radius, vertex_density, all_components=False) raise return vfloat, vint, tri, atomareas, compareas, all_components
def create_object(self, gdcache): data = self.data_state.create_object(gdcache) if data == None: from chimera import replyobj replyobj.warning('Could not restore %s\n' % self.name) return None for rs in self.data_region_state: drlist = rs.create_object(data) # Data_Set class is obsolete -- only Volume objects created. return None
def transform_cb(self, event=None): m = self.molecule_menu.getvalue() if m == None: from chimera.replyobj import warning warning('No molecule selected in Transform Molecule dialog') return try: ea = map(float, self.euler_angles.get().split()) t = map(float, self.translation.get().split()) except ValueError: from chimera.replyobj import warning warning('Error parsing Euler angle or translation number') return if len(ea) != 3: from chimera.replyobj import warning warning('Require 3 Euler angles separated by spaces') return if len(t) != 3: from chimera.replyobj import warning warning('Require 3 translation values separated by spaces') return from MoleculeTransform import euler_xform, transform_atom_coordinates xf = euler_xform(ea, t) transform_atom_coordinates(m.atoms, xf) self.record_xform(m, xf)
def save(self, filename=None): """Save preferences to file. Only the differences from the default values are saved.""" if self._ignoreSave: return if not filename: filename = self._filename if self._readonly and filename: return if not filename: from chimera import replyobj replyobj.warning('Cannot find writable directory for ' 'saving preferences file\n') return saveDict = {} for name, cat in self._category.items(): catDict = cat.save() if catDict: saveDict[name] = catDict for name, optDict in self._loadDict.items(): if self._category.has_key(name): continue saveDict[name] = optDict if not saveDict: if filename == self._filename: try: os.remove(filename) except OSError: pass else: from chimera import replyobj replyobj.warning('All options using ' 'default values\n') return try: f = open(filename, 'w') except IOError: try: os.makedirs(os.path.dirname(filename)) f = open(filename, 'w') except (IOError, OSError): from chimera import replyobj replyobj.error( 'Cannot write preferences file: %s' % filename) return pprint.pprint(saveDict, f) f.close()
def have_phantom(self): try: import _phantomcursor except: from chimera.replyobj import warning warning( 'Could not load Chimera _phantomcursor library.\n' + 'This library is only available on Windows.\n' + 'You must have the Sensable OpenHaptics libraries installed.\n' ) return 0 return 1
def transform_cb(self, event = None): m = self.molecule_menu.getvalue() if m == None: from chimera.replyobj import warning warning('No molecule selected in Transform Molecule dialog') return try: ea = map(float, self.euler_angles.get().split()) t = map(float, self.translation.get().split()) except ValueError: from chimera.replyobj import warning warning('Error parsing Euler angle or translation number') return if len(ea) != 3: from chimera.replyobj import warning warning('Require 3 Euler angles separated by spaces') return if len(t) != 3: from chimera.replyobj import warning warning('Require 3 translation values separated by spaces') return from MoleculeTransform import euler_xform, transform_atom_coordinates xf = euler_xform(ea, t) transform_atom_coordinates(m.atoms, xf) self.record_xform(m, xf)
def get_simple_morph_param(self): """get_simple_morph_param() - simple morph parameters Output: thr filt frac Returns parameters to be used for simple morph, after checking. """ frac_value = 1.0 thr_value = None filt_value = None # read fraction frac = str(self.sm_fraction_value.variable.get()) try: frac_value = float(frac) except: frac_value = 1.0 msg = 'Reset fraction value!\n' self.status(msg, color='red', blankAfter=15) replyobj.warning(msg) self.sm_fraction_value.variable.set('%5.2f' % frac_value) if ((frac_value < 0.1) or (frac_value > 1.0)): frac_value = 1.0 msg = 'Reset fraction value!\n' self.status(msg, color='red', blankAfter=15) replyobj.warning(msg) self.sm_fraction_value.variable.set('%5.2f' % frac_value) # read threshold if self.sm_threshold_button.variable.get(): thr = str(self.sm_threshold_value.variable.get()) try: thr_value = float(thr) except: thr_value = 0.0 msg = 'Reset threshold value!\n' self.status(msg, color='red', blankAfter=15) replyobj.warning(msg) self.sm_threshold_value.variable.set('%5.2f' % thr_value) # read filter value if self.sm_filter_button.variable.get(): filt = str(self.sm_filter_value.variable.get()) try: filt_value = float(filt) except: filt_value = 0.0 msg = 'Reset filter value!\n' self.status(msg, color='red', blankAfter=15) replyobj.warning(msg) self.sm_filter_value.variable.set('%5.2f' % filt_value) return frac_value, thr_value, filt_value
def search_emdb(text): from WebServices.emdb_client import EMDB_WS ws = EMDB_WS() rt = ws.getResultSetXMLByTitle(text) ra = ws.getResultSetXMLByAuthor(text) ri = ws.getResultSetXMLByID(text) results = ws.rowValues(rt) + ws.rowValues(ra) + ws.rowValues(ri) # remove duplicates results = dict([(r['accessionCode'],r) for r in results]).values() if results: d = EMDBResultsDialog(results) d.enter() else: from chimera.replyobj import warning warning('No results for EMDB search "%s"' % text) return results
def save(self, filename=None): """Save preferences to file. Only the differences from the default values are saved.""" if self._ignoreSave: return if not filename: filename = self._filename if self._readonly and filename: return if not filename: from chimera import replyobj replyobj.warning('Cannot find writable directory for ' 'saving preferences file\n') return saveDict = {} for name, cat in self._category.items(): catDict = cat.save() if catDict: saveDict[name] = catDict for name, optDict in self._loadDict.items(): if self._category.has_key(name): continue saveDict[name] = optDict if not saveDict: if filename == self._filename: try: os.remove(filename) except OSError: pass else: from chimera import replyobj replyobj.warning('All options using ' 'default values\n') return try: f = open(filename, 'w') except IOError: try: os.makedirs(os.path.dirname(filename)) f = open(filename, 'w') except (IOError, OSError): from chimera import replyobj replyobj.error('Cannot write preferences file: %s' % filename) return pprint.pprint(saveDict, f) f.close()
def setResidueDisplay(val): mset = set() for r in selResidues(implied = True, create = val): r.ribbonDisplay = val mset.add(r.molecule) anyRibbon = False for m in mset: if m.updateRibbonData(): anyRibbon = True if not anyRibbon: from chimera import replyobj replyobj.warning("no residues with ribbons found") global _firstRibbonDraw if _firstRibbonDraw and val: from chimera import replyobj _firstRibbonDraw = False replyobj.status("Ribbon appearance can be fine-tuned with Tools/Depiction/Ribbon Style Editor")
def update_data_menu_entry(self, data_item): """update_data_menu_entry(data_item) Update data menu entry name with data item's new full_name. """ try: index = self.data_items.index(data_item) new_name = data_item.full_name self.data_menu.remove_entry(index) self.data_menu.insert_entry(index, new_name) except: msg = 'While updating output data menu entry name,\n' + \ 'could not find data item in data items list!\n' replyobj.warning(msg) return
def marker_boxes_show_cb(self): """marker_boxes_show_cb() Show boxes around selected markers, with box center at marker center and box size equal to marker diameter, with a transform matching data size. """ self.marker_boxes_hide_cb() ms = self.marker_set if ms == None: return mssm = ms.selected_markers() # check if any markers selected if len(mssm) == 0: msg = 'No selected markers!\n' self.status(msg, color='red', blankAfter=15) replyobj.warning(msg) return # check input data exists if self.data_item == None: msg = 'Input data not found!\n' self.status(msg, color='red', blankAfter=15) replyobj.warning(msg) return #VU 1.2129 # xform = self.data_item.region.transform() import SegmentMenu from SegmentMenu import datamenuseg data_region = self.data_item.region #VU 1.2179 # xform = datamenuseg.get_data_region_xform(data_region) xform, tf = datamenuseg.get_data_region_xform(data_region) xyz_to_ijk = self.data_item.data.xyz_to_ijk #VU 1.2179 #for im in mssm: # self.marker_boxes.append(self.marker_box_create(im,xform)) for im in mssm: self.marker_boxes.append( self.marker_box_create(im,tf,xform, xyz_to_ijk)) return
def save_fits_cb(self): lfits = self.selected_listbox_fits() mlist = sum([f.fit_molecules() for f in lfits], []) if len(mlist) == 0: from chimera.replyobj import warning warning('No fits of molecules chosen from list.') return idir = ifile = None vlist = [f.volume for f in lfits] pmlist = [m for m in mlist + vlist if hasattr(m, 'openedAs')] if pmlist: for m in pmlist: import os.path dpath, fname = os.path.split(m.openedAs[0]) base, suf = os.path.splitext(fname) if ifile is None: suffix = '_fit%d.pdb' if len(lfits) > 1 else '_fit.pdb' ifile = base + suffix if dpath and idir is None: idir = dpath def save(okay, dialog, lfits=lfits): if okay: paths = dialog.getPaths() if paths: path = paths[0] import Midas if len(lfits) > 1 and path.find('%d') == -1: base, suf = os.path.splitext(path) path = base + '_fit%d' + suf for i, fit in enumerate(lfits): p = path if len(lfits) == 1 else path % (i + 1) fit.place_models(self.session) Midas.write(fit.fit_molecules(), relModel=fit.volume, filename=p) from OpenSave import SaveModeless SaveModeless(title='Save Fit Molecules', filters=[('PDB', '*.pdb', '.pdb')], initialdir=idir, initialfile=ifile, command=save)
def __init__(self, ensemble, **kw): self.title = "MD Movie: %s" % ensemble.name self.ensemble = ensemble self.model = Trajectory.Ensemble(self.ensemble) self.model.CreateMolecule() endFrame = ensemble.endFrame if endFrame == "pipe": fn = 1 while True: replyobj.status("Loading trajectory from pipe:" " frame %d\n" % fn) try: self.model.LoadFrame(fn, makeCurrent=False) except NoFrameError: break fn += 1 replyobj.status("Loading trajectory from pipe: done\n") endFrame = fn-1 if ensemble.startFrame == None: self.startFrame = 1 else: self.startFrame = ensemble.startFrame if self.startFrame > len(ensemble): replyobj.warning("Start frame > number of" " trajectory frames; changing start" " frame to 1\n") self.startFrame = 1 if endFrame == None: self.endFrame = len(ensemble) else: self.endFrame = endFrame if self.endFrame > len(ensemble): replyobj.warning("End frame > number of" " trajectory frames; changing end" " frame to last frame\n") self.endFrame = len(ensemble) self.molTrigID = chimera.triggers.addHandler("Molecule", self._molTrigCB, None) self.openOpts = kw self._inTriggerCB = False ModelessDialog.__init__(self) del self.openOpts chimera.extension.manager.registerInstance(self)
def run_mscalc(xyzr, probe_radius = 1.4, vertex_density = 2.0, all_components = True, fallback_to_single_component = True): from CGLutil.findExecutable import findExecutable mscalc_path = findExecutable('mscalc') if mscalc_path is None: raise Surface_Calculation_Error, 'No mscalc executable found' if all_components: allc = '1' else: allc = '0' args = (mscalc_path, '%f' % probe_radius, '%f' % vertex_density, allc) from numpy import array, intc xyzrs = array(len(xyzr),intc).tostring() + xyzr.tostring() cmd = ' '.join(args) from chimera.replyobj import info info(cmd + '\n') msout, mserr, status = run_shell_command(args, xyzrs) if status is None: raise Surface_Calculation_Error, 'Starting mscalc failed.' if mserr: info(mserr) try: if status != 0: raise Surface_Calculation_Error, 'Surface calculation failed, mscalc returned code %d' % status vfloat, vint, tri, atomareas, compareas = parse_mscalc_output(msout) if atomareas is None and len(xyzr) > 0: raise Surface_Calculation_Error, 'Surface calculation failed, produced empty surface.\n' except Surface_Calculation_Error: if fallback_to_single_component and all_components: from chimera.replyobj import warning warning('Calculation of some surface components failed.\nFalling back to single-component calculation.\n') return run_mscalc(xyzr, probe_radius, vertex_density, all_components = False) raise return vfloat, vint, tri, atomareas, compareas, all_components
def loadDirectory(self, path, basename="Chimera"): "Search a path for package Python tools" from chimera.misc import stringToAttr if path not in sys.path: sys.path.append(path) self.categoryChanged = {} self.newCategoryAdded = 0 module = basename + "Extension" try: files = os.listdir(path) except OSError: from chimera import replyobj replyobj.warning("Cannot load tools directory " "\"%s\"\n" % path) return 0 for f in os.listdir(path): if stringToAttr(f) != f: # Skip anything that isn't a legal Python # identifier since it can't be used for # imports anyway continue emoPath = os.path.join(path, f, module) for s in self.Suffixes: if os.path.isfile(emoPath + s): break else: # No ChimeraExtension.py* continue if self.findExtensionPackage(f): from chimera import replyobj replyobj.warning("Package \"%s\" " "already exists and tool(s) in " "it will not be loaded\n" % f) continue self._importExtension(path, f, module) if self.menu: if self.newCategoryAdded: self.remakeToolsMenu() elif self.categoryChanged: for cat in self.categoryChanged.iterkeys(): self.remakeCategoryMenu(cat) return self.categoryChanged or self.newCategoryAdded
def setResidueDisplay(val): mset = set() for r in selResidues(implied=True, create=val): r.ribbonDisplay = val mset.add(r.molecule) anyRibbon = False for m in mset: if m.updateRibbonData(): anyRibbon = True if not anyRibbon: from chimera import replyobj replyobj.warning("no residues with ribbons found") global _firstRibbonDraw if _firstRibbonDraw and val: from chimera import replyobj _firstRibbonDraw = False replyobj.status( "Ribbon appearance can be fine-tuned with Tools/Depiction/Ribbon Style Editor" )
def create_volume_plane_surface(volume, height, interpolate = 'cubic', mesh = 'isotropic', colormap = 'rainbow', smoothing_factor = 0.3, smoothing_iterations = 0, color = (.7, .7, .7, 1), replace = True): m = volume.matrix() axes = [a for a in range(3) if m.shape[2-a] == 1] if len(axes) != 1: from chimera.replyobj import warning warning('Volume %s has more than one plane shown (%d,%d,%d)' % ((volume.name,) + tuple(reversed(m.shape)))) return axis = axes[0] m = m.squeeze() # Convert 3d array to 2d tf = volume.matrix_indices_to_xyz_transform() perm = {0: ((0,0,1,0),(1,0,0,0),(0,1,0,0)), # 2d matrix xyh -> 3d yzx 1: ((1,0,0,0),(0,0,1,0),(0,1,0,0)), # 2d matrix xyh -> 3d xzy 2: ((1,0,0,0),(0,1,0,0),(0,0,1,0))}[axis] from Matrix import multiply_matrices tf = multiply_matrices(tf, perm) s = create_surface(m, height, tf, color, interpolate, mesh, smoothing_factor, smoothing_iterations) s.name = volume.name + ' height' if colormap == 'rainbow': invert = not height is None and height < 0 tf = volume.data.ijk_to_xyz_transform normal = [tf[i][axis] for i in range(3)] colormap_surface(s, normal, rainbow_colormap(invert)) from chimera import openModels if replace: openModels.close([m for m in openModels.list() if getattr(m, 'topography_volume', None) == volume]) openModels.add([s]) s.openState.xform = volume.model_transform() s.topography_volume = volume return s
def __init__(self, ensemble, **kw): self.title = "MD Movie: %s" % ensemble.name self.ensemble = ensemble self.model = Trajectory.Ensemble(self.ensemble) self.model.CreateMolecule() endFrame = ensemble.endFrame if endFrame == "pipe": fn = 1 while True: replyobj.status("Loading trajectory from pipe:" " frame %d\n" % fn) try: self.model.LoadFrame(fn, makeCurrent=False) except NoFrameError: break fn += 1 replyobj.status("Loading trajectory from pipe: done\n") endFrame = fn - 1 if ensemble.startFrame == None: self.startFrame = 1 else: self.startFrame = ensemble.startFrame if self.startFrame > len(ensemble): replyobj.warning("Start frame > number of" " trajectory frames; changing start" " frame to 1\n") self.startFrame = 1 if endFrame == None: self.endFrame = len(ensemble) else: self.endFrame = endFrame if self.endFrame > len(ensemble): replyobj.warning("End frame > number of" " trajectory frames; changing end" " frame to last frame\n") self.endFrame = len(ensemble) self.molTrigID = chimera.triggers.addHandler("Molecule", self._molTrigCB, None) self.openOpts = kw self._inTriggerCB = False ModelessDialog.__init__(self) del self.openOpts chimera.extension.manager.registerInstance(self)
def state_from_maps(data_maps, include_unsaved_volumes = True, session_path = None): dvlist = [] unsaved_data = [] for data, volumes in data_maps.items(): if data.path == '' and not include_unsaved_volumes: unsaved_data.append(data) continue # Do not save data sets with no path dvlist.append((state_from_grid_data(data, session_path), [state_from_map(v) for v in volumes])) if unsaved_data: names = ', '.join([d.name for d in unsaved_data]) from chimera.replyobj import warning warning('Volume data sets\n\n' + '\t%s\n\n' % names + 'were not saved in the session. To have them included\n' + 'in the session they must be saved in separate volume\n' + 'files before the session is saved. The session file only\n' + 'records file system paths to the volume data.') return dvlist
def compute_profile(self): from chimera.replyobj import warning molecules, selected_only = self.chosen_atoms() if len(molecules) == 0: warning('No atoms selected for SAXS profile computation.') return expath = self.experimental_profile.get() from os.path import isfile if expath and not isfile(expath): warning('Experimental profile "%s" does not exist' % expath) expath = '' epath = self.executable_path() p = None if self.new_plot.get() else self.plot import saxs p = saxs.show_saxs_profile(molecules, selected_only, epath, expath, p) self.plot = p self.plot.raiseWindow()
def checkRegistration(nag=1): "Check registration status." pf = chimera.pathFinder() filenames = pf.allExistingFiles('', RegistrationFile) if not filenames: if nag: return _checkUsage(pf) else: return import x509 store = x509.CAStore() smime = x509.SMIME(store) for filename in filenames: try: text, signers = smime.verifyFile(filename) except x509.error, emsg: if emsg == 'Verify error:Certificate has expired': replyobj.warning('Registration file "%s" ' 'has expired.\n' % filename) continue valid = 0 for cert in signers: fromUC = 0 fromCGL = 0 fields = string.split(cert.subject(), '/') for f in fields: if f == 'O=University of California, San Francisco': fromUC = 1 elif f == 'OU=Computer Graphics Laboratory': fromCGL = 1 if fromUC and fromCGL: valid = 1 break if not valid: replyobj.warning('Registration file "%s" ' 'is not signed by CGL.\n' % filename) lines = string.split(text, '\r\n') param = {} for line in lines: try: key, value = map(string.strip, string.split(line, ':')) except ValueError: pass else: param[key] = value if param.has_key('Expires') \ and time.time() > float(param['Expires']): replyobj.warning('Registration file "%s" ' 'has expired.\n' % filename) # Other parameter-dependent processing can go here return param
def transform(model_path, fixed_model_path, ea = (0.0, 0.0, 0.0), t = (0.0, 0.0, 0.0), move_atoms=True): """Translates and/or rotates a model. Parameters ---------- model_path : str The path to the file containing the model that will be moved. fixed_model_path : str The path to the file containing the model that will remain in place. ea : tuple 3-tuple of Euler angles for rotating the model. t : tuple 3-tuple of xyz displacements for translating the model. move_atoms : bool Determines whether the atoms or the coordinate system should be moved. """ model = open_volume_file(model_path)[0] fixed_model = open_volume_file(fixed_model_path)[0] try: ea = map(float, ea) t = map(float, t) except ValueError: from chimera.replyobj import warning warning('Error parsing Euler angle or translation number') return if len(ea) != 3: from chimera.replyobj import warning warning('Requires 3 Euler angles.') return if len(t) != 3: from chimera.replyobj import warning warning('Requires 3 translation values.') return xf = mt.euler_xform(ea, t) if isinstance(model, Molecule) and move_atoms: mt.transform_atom_coordinates(m.atoms, xf) else: mt.transform_coordinate_axes(model, xf) print('Transformation matrix:') print(xf)
def parse(fileName): IN_HEADER = 0 START_ATTRS = 1 IN_ATTRS = 2 IN_FEATURES = 3 IN_SEQ = 4 state = IN_HEADER from OpenSave import osOpen f = osOpen(fileName, "r") sequences = [] lineNum = 0 hasOffset = 0 longest = None fileAttrs = {} for line in f: line = line.rstrip() # remove trailing whitespace/newline lineNum += 1 if lineNum == 1: if line.startswith("!!RICH_SEQUENCE"): continue raise WrongFileTypeError() if state == IN_HEADER: if line.strip() == "..": state = START_ATTRS continue if "comments" in fileAttrs: fileAttrs["comments"] += "\n" + line else: fileAttrs["comments"] = line continue if not line.strip(): continue if state == START_ATTRS: if line.strip() == "{": state = IN_ATTRS curAttr = None attrs = {} elif line: raise FormatSyntaxError("Unexpected text before" " start of sequence on line %d" % lineNum) continue if state == IN_ATTRS or state == IN_FEATURES: if line.strip() == "sequence" and line[0] == "s": if "RSF name" not in attrs: raise FormatSyntaxError("sequence on " "line %d has no name" % lineNum) state = IN_SEQ seq = Sequence(makeReadable(attrs["RSF name"])) del attrs["RSF name"] seq.attrs = attrs if "RSF descrip" in attrs: attrs["description"] = attrs[ "RSF descrip"] del attrs["RSF descrip"] sequences.append(seq) if "RSF offset" in attrs: seq.extend("." * int( attrs["RSF offset"])) hasOffset = 1 del attrs["RSF offset"] continue if line.startswith("feature"): if state == IN_ATTRS: attrs["RSF features"] = [[line[8:]]] else: attrs["RSF features"].append([line[8:]]) state = IN_FEATURES continue if state == IN_ATTRS: if line[0].isspace(): # continuation if not curAttr: raise FormatSyntaxError("Bogus " "indentation at line %d" % lineNum) if attrs[curAttr]: attrs[curAttr] += "\n" + line else: attrs[curAttr] = line continue if " " in line.strip(): curAttr, val = line.split(None, 1) curAttr.replace("_", " ") curAttr = "RSF " + curAttr attrs[curAttr] = val.strip() else: curAttr = "RSF " + line.strip().replace("_", " ") attrs[curAttr] = "" continue if state == IN_FEATURES: attrs["RSF features"][-1].append(line) continue if line.strip() == "}": state = START_ATTRS if not longest: longest = len(seq) else: if len(seq) < longest: seq.extend("." * (longest - len(seq))) elif len(seq) > longest: longest = len(seq) for s in sequences[:-1]: s.extend("." * (longest - len(s))) continue seq.extend(line.strip()) if not seq[0].isalpha(): hasOffset = 1 f.close() if state == IN_HEADER: raise FormatSyntaxError( "No end to header (i.e. '..' line) found") if state == IN_ATTRS or state == IN_FEATURES: if "RSF name" in attrs: raise FormatSyntaxError( "No sequence data found for sequence %s" % attrs["RSF name"]) raise FormatSyntaxError("Sequence without sequence data") if state == IN_SEQ: raise FormatSyntaxError("No terminating brace for sequence %s" % attrs["RSF name"]) if not sequences: raise FormatSyntaxError("No sequences found") if not hasOffset: from chimera import replyobj replyobj.warning("No offset fields in RSF file;" " assuming zero offset\n") return sequences, fileAttrs, {}
def writePrmtop(m, topfile, parmset, unchargedAtoms=None): import os import chimera from chimera import replyobj from WriteMol2 import writeMol2 from tempfile import mkdtemp status = replyobj.status if unchargedAtoms and parmset.lower().endswith("ua"): # united atom replyobj.warning("Some uncharged/untyped protons expected due" " to use of united-atom force field.\n") unchargedHeavy = [] skip = [] for uncharged in unchargedAtoms.values(): for uc in uncharged: if uc.element.number == 1: skip.append(uc) else: unchargedHeavy.append(uc) unchargedAtoms = unchargedHeavy else: skip = [] if unchargedAtoms: if chimera.nogui: raise ValueError("Some atoms don't have charges/types") from chimera.baseDialog import AskYesNoDialog d = AskYesNoDialog("Some atoms don't have charges/types" " assigned. Write prmtop anyway?") if d.run(chimera.tkgui.app) == "no": return tempDir = mkdtemp() def _clean(): for fn in os.listdir(tempDir): os.unlink(os.path.join(tempDir, fn)) os.rmdir(tempDir) sleapIn = os.path.join(tempDir, "sleap.in.mol2") writeMol2([m], sleapIn, status=status, gaffType=True, skip=skip) leaprc = os.path.join(tempDir, "solvate.cmd") writeLeaprc(tempDir, topfile, parmset, leaprc) chimeraRoot = os.environ["CHIMERA"] amberHome = os.path.join(chimeraRoot, "bin", "amber10") acHome = os.path.join(chimeraRoot, "bin", "antechamber") command = [os.path.join(amberHome, "exe", "sleap"), "-f", leaprc] print 'command: ', command if status: status("Running sleap" ) from subprocess import Popen, STDOUT, PIPE # For some reason on Windows, if shell==False then antechamber # cannot run bondtype via system(). import sys if sys.platform == "win32": shell = True else: shell = False replyobj.info("Running sleap command: %s\n" % " ".join(command)) import os os.environ["AMBERHOME"]=amberHome os.environ["ACHOME"]=acHome sleapMessages = Popen(command, stdin=PIPE, stdout=PIPE, stderr=STDOUT, cwd=tempDir, shell=shell, bufsize=1).stdout while True: line = sleapMessages.readline() if not line: break replyobj.status("(writeprmtop) %s" % line, log=True) if not os.path.exists(topfile): _clean() from chimera import NonChimeraError raise NonChimeraError("Failure running sleap \n" "Check reply log for details\n") else: replyobj.status("Wrote parmtop file %s\n" % topfile, log=True)
def parse(fileName): from OpenSave import osOpen from chimera import replyobj f = osOpen(fileName, "r") lineNum = 0 fileAttrs = {} fileMarkups = {} seqAttrs = {} seqMarkups = {} sequences = {} seqSequence = [] for line in f: line = line[:-1] # drop newline lineNum += 1 if lineNum == 1: if line.startswith("# STOCKHOLM"): continue raise WrongFileTypeError() if not line: continue if line.startswith('#='): markupType = line[2:4] markup = line[5:].strip() def trySplit(numSplit): fields = markup.split(None, numSplit) if len(fields) == numSplit: # value is empty fields.append("") if len(fields) != numSplit + 1: raise FormatSyntaxError("Not enough" " arguments after #=%s markup" " on line %d" % (markupType, lineNum)) return fields if markupType == "GF": tag, val = trySplit(1) tag = tag.replace("_", " ") tag = genericFileAttrs.get(tag, "Stockholm " + tag) if tag in fileAttrs: fileAttrs[tag] += '\n' + val else: fileAttrs[tag] = val elif markupType == "GS": seqName, tag, val = trySplit(2) tag = tag.replace("_", " ") attrs = seqAttrs.setdefault(seqName, {}) tag = genericSeqAttrs.get(tag, "Stockholm " + tag) if tag in attrs: attrs[tag] += '\n' + val else: attrs[tag] = val elif markupType == "GC": tag, val = trySplit(1) tag = tag.replace("_", " ") fileMarkups[tag] = fileMarkups.get(tag, "") + val elif markupType == "GR": seqName, tag, val = trySplit(2) tag = tag.replace("_", " ") seqMarkups.setdefault(seqName, {}).setdefault(tag, "") seqMarkups[seqName][tag] += val # ignore other types continue elif line.startswith('#'): # unstructured comment if 'comments' in fileAttrs: fileAttrs['comments'] += "\n" + line[1:] else: fileAttrs['comments'] = line[1:] continue elif line.strip() == "//": # end of sequence alignment blocks, but comments # may follow this, so keep going... continue # sequence info... try: seqName, block = line.split(None, 1) except ValueError: raise FormatSyntaxError("Sequence info not in name/" "contents format on line %d" % lineNum) if seqName not in sequences: sequences[seqName] = Sequence(makeReadable(seqName)) seqSequence.append(seqName) sequences[seqName].extend(block) f.close() if not sequences: raise FormatSyntaxError("No sequences found") for seqName, seq in sequences.items(): if seqName in seqAttrs: seq.attrs = seqAttrs[seqName] if seqName in seqMarkups: seq.markups = seqMarkups[seqName] for tag, markup in seq.markups.items(): if len(markup) != len(seq): replyobj.warning("Markup %s for" " sequence %s is wrong length;" " ignoring\n" % (tag, seqName)) del seq.markups[tag] for seqInfo, label in [(seqAttrs, "sequence"), (seqMarkups, "residue")]: for seqName in seqInfo.keys(): if seqName in sequences: continue # might be sequence name without trailing '/start-end' for fullName in sequences.keys(): if fullName.startswith(seqName) \ and fullName[len(seqName)] == '/' \ and '/' not in fullName[len(seqName)+1:]: break else: raise FormatSyntaxError( "%s annotations " "provided for non-existent sequence %s" % (label.capitalize(), seqName)) replyobj.info("Updating %s %s annotions with %s " "annotations\n" % (fullName, label, seqName)) seqInfo[fullName].update(seqInfo[seqName]) del seqInfo[seqName] for tag, markup in fileMarkups.items(): if len(markup) != len(sequences[seqSequence[0]]): raise FormatSyntaxError("Column annotation %s is" " wrong length" % tag) return map(lambda name: sequences[name], seqSequence), \ fileAttrs, fileMarkups
def buried_area(operation, atoms1, atoms2, probeRadius=1.4, vertexDensity=2.0): xyzr = atom_xyzr(atoms1 + atoms2) n1 = len(atoms1) xyzr1 = xyzr[:n1] xyzr2 = xyzr[n1:] xyzr12 = xyzr failed = False import MoleculeSurface as ms try: s1 = ms.xyzr_surface_geometry(xyzr1, probeRadius, vertexDensity) s2 = ms.xyzr_surface_geometry(xyzr2, probeRadius, vertexDensity) s12 = ms.xyzr_surface_geometry(xyzr12, probeRadius, vertexDensity) except ms.Surface_Calculation_Error: failed = True if failed or not s1[6] or not s2[6] or not s12[6]: # All component calculation failed. try: s1 = ms.xyzr_surface_geometry(xyzr1, probeRadius, vertexDensity, all_components=False) s2 = ms.xyzr_surface_geometry(xyzr2, probeRadius, vertexDensity, all_components=False) s12 = ms.xyzr_surface_geometry(xyzr12, probeRadius, vertexDensity, all_components=False) except ms.Surface_Calculation_Error: raise CommandError, 'Surface calculation failed.' from chimera import replyobj replyobj.warning( 'Calculation of some surface components failed. Using only single surface component. This may give inaccurate areas if surfaces of either set of atoms or the combined set are disconnected.\n' ) # Assign per-atom buried areas. aareas1, aareas2, aareas12 = s1[3], s2[3], s12[3] ases121 = asas121 = ases122 = asas122 = 0 for ai, a in enumerate(atoms1): a.buriedSESArea = aareas1[ai, 0] - aareas12[ai, 0] a.buriedSASArea = aareas1[ai, 1] - aareas12[ai, 1] ases121 += aareas12[ai, 0] asas121 += aareas12[ai, 1] for ai, a in enumerate(atoms2): a.buriedSESArea = aareas2[ai, 0] - aareas12[n1 + ai, 0] a.buriedSASArea = aareas2[ai, 1] - aareas12[n1 + ai, 1] ases122 += aareas12[n1 + ai, 0] asas122 += aareas12[n1 + ai, 1] careas1, careas2, careas12 = s1[4], s2[4], s12[4] ases1, asas1 = area_sums(careas1) ases2, asas2 = area_sums(careas2) ases12, asas12 = area_sums(careas12) bsas1 = asas1 - asas121 bsas2 = asas2 - asas122 bsas = 0.5 * (bsas1 + bsas2) bses1 = ases1 - ases121 bses2 = ases2 - ases122 bses = 0.5 * (bses1 + bses2) # TODO: include atomspec's in output message. msg = ('Buried solvent accessible surface area\n' ' B1SAS = %.6g, B2SAS = %.6g, BaveSAS = %.6g\n' ' (A1 = %.6g, A2 = %.6g, A12 = %.6g = %.6g + %.6g)\n' % (bsas1, bsas2, bsas, asas1, asas2, asas12, asas121, asas122) + 'Buried solvent excluded surface area\n ' + ' B1SES = %.6g, B2SES = %.6g, BaveSES = %.6g\n' ' (A1 = %.6g, A2 = %.6g, A12 = %.6g = %.6g + %.6g)\n' % (bses1, bses2, bses, ases1, ases2, ases12, ases121, ases122)) from chimera import replyobj replyobj.info(msg) smsg = 'Buried areas: SAS = %.6g, SES = %.6g\n' % (bsas, bses) replyobj.status(smsg)
if os.access(filename, os.X_OK): Executable = filename break if Executable: break import sys if not Executable and sys.platform == "win32": drive = os.path.splitdrive(sys.executable)[0] filename = os.path.join(drive, os.sep, "Program Files", "Delphi", "delphi.exe") if os.access(filename, os.X_OK): Executable = filename if not Executable: Executable = "/usr/local/bin/delphi" from chimera import replyobj replyobj.warning("Cannot find Delphi executable. " "Please set manually.") # Option lists for displaying GUI MoleculeInputOptions = [MoleculeInputOption(help=InputOptionsHelp['pdb'])] DelPhiInputOptions = [ # These three are used for saving preferences, so # don't change their order unless you change savePrefs() ExecFileOption('DelPhi Executable', 'exe', Executable, required=1, filetypes=[('All Files', '*')], help=InputOptionsHelp['exe']), InputFileOption('Atomic Radii File',
class MSMSModel(SurfaceModel): def __init__(self, molecule, category, probeRadius=1.4, allComponents=True, vertexDensity=2.0): SurfaceModel.__init__(self) name = 'MSMS %s surface of %s' % (category, molecule.name) SurfaceModel.__setattr__(self, 'name', name) SurfaceModel.__setattr__(self, 'piecesAreSelectable', True) SurfaceModel.__setattr__(self, 'oneTransparentLayer', True) init = { 'molecule': molecule, 'colorMode': self.ByAtom, 'visibilityMode': self.ByAtom, 'density': vertexDensity, 'probeRadius': probeRadius, 'category': category, 'allComponents': allComponents, 'atomMap': None, # vertex number to Atom 'surface_piece': None, 'srf': None, # MSMS surface object 'triData': None, # MSMS triangle data 'areaSES': 0.0, 'areaSAS': 0.0, 'calculationFailed': False, } self.__dict__.update(init) from _surface import SurfacePiece self.surface_piece_defaults = { 'drawMode': self.Filled, 'lineWidth': 1.0, 'pointSize': 1.0, 'useLighting': True, 'twoSidedLighting': True, 'smoothLines': False, 'transparencyBlendMode': SurfacePiece.SRC_ALPHA_DST_1_MINUS_ALPHA, } self.update_surface() # Detect changes in atom color, atom surfaceColor, atom surfaceOpacity, # molecule color, atom surfaceDisplay. from chimera import triggers as t self.molecule_handler = t.addHandler('Molecule', self.molecule_cb, None) # Detect when surface or molecule deleted. from chimera import addModelClosedCallback addModelClosedCallback(self, self.surface_closed_cb) addModelClosedCallback(self.molecule, self.molecule_closed_cb) import Surface Surface.set_coloring_method('msms', self, self.custom_coloring) Surface.set_visibility_method('msms', self, self.custom_visibility) # ------------------------------------------------------------------------- # Draw mode values. # Filled = 0 Mesh = 1 Dot = 2 # ------------------------------------------------------------------------- # Color mode values # ByMolecule = 0 ByAtom = 1 Custom = 2 # ------------------------------------------------------------------------- # Change reasons. # DRAW_MODE_CHANGED = 'drawMode changed' LINE_WIDTH_CHANGED = 'lineWidth changed' POINT_SIZE_CHANGED = 'pointSize changed' USE_LIGHTING_CHANGED = 'useLighting changed' TWO_SIDED_LIGHTING_CHANGED = 'twoSidedLighting changed' SMOOTH_LINES_CHANGED = 'smoothLines changed' TRANSPARENCY_BLEND_MODE_CHANGED = 'transparencyBlendMode changed' COLOR_MODE_CHANGED = 'colorMode changed' VISIBILITY_MODE_CHANGED = 'visibilityMode changed' DENSITY_CHANGED = 'density changed' PROBE_RADIUS_CHANGED = 'probeRadius changed' CATEGORY_CHANGED = 'category changed' ALLCOMPONENTS_CHANGED = 'allComponents changed' CUSTOM_COLORS_CHANGED = 'customColors changed' CUSTOM_RGBA_CHANGED = 'customRGBA changed' # Need to hold references to reasons or crash occurs apparently because # TrackChanges is not incrementing the reason reference count. change_reason = { 'drawMode': DRAW_MODE_CHANGED, 'lineWidth': LINE_WIDTH_CHANGED, 'pointSize': POINT_SIZE_CHANGED, 'useLighting': USE_LIGHTING_CHANGED, 'twoSidedLighting': TWO_SIDED_LIGHTING_CHANGED, 'smoothLines': SMOOTH_LINES_CHANGED, 'transparencyBlendMode': TRANSPARENCY_BLEND_MODE_CHANGED, 'colorMode': COLOR_MODE_CHANGED, 'visibilityMode': VISIBILITY_MODE_CHANGED, 'density': DENSITY_CHANGED, 'probeRadius': PROBE_RADIUS_CHANGED, 'category': CATEGORY_CHANGED, 'allComponents': ALLCOMPONENTS_CHANGED, 'customColors': CUSTOM_COLORS_CHANGED, 'customRGBA': CUSTOM_RGBA_CHANGED, } # ------------------------------------------------------------------------- # def __setattr__(self, attrname, value): if attrname in ('drawMode', 'lineWidth', 'pointSize', 'useLighting', 'twoSidedLighting', 'smoothLines', 'transparencyBlendMode'): # SurfacePiece attributes if value == getattr(self, attrname): return elif attrname in ('colorMode', 'visibilityMode', 'density', 'probeRadius', 'category', 'allComponents'): if value == getattr(self, attrname): return self.__dict__[attrname] = value elif attrname not in ('customColors', 'customRGBA'): SurfaceModel.__setattr__(self, attrname, value) return if attrname in ('density', 'probeRadius', 'category', 'allComponents'): self.update_surface() else: p = self.surface_piece if p is None or p.__destroyed__: return if attrname == 'drawMode': style = { self.Filled: p.Solid, self.Mesh: p.Mesh, self.Dot: p.Dot, }[value] p.displayStyle = style elif attrname == 'lineWidth': p.lineThickness = value elif attrname == 'pointSize': p.dotSize = value elif attrname in ('useLighting', 'twoSidedLighting', 'smoothLines', 'transparencyBlendMode'): setattr(p, attrname, value) elif attrname == 'colorMode': if value != self.Custom: import Surface Surface.set_coloring_method('msms', self, self.custom_coloring) self.update_coloring() elif attrname == 'visibilityMode': if value == self.ByAtom: import Surface Surface.set_visibility_method('msms', self, self.custom_visibility) self.update_visibility() elif attrname == 'customColors': self.set_custom_colors(value) elif attrname == 'customRGBA': self.set_custom_rgba(value) self.report_change(self.change_reason[attrname]) # ------------------------------------------------------------------------- # def __getattr__(self, attrname): if attrname == 'customColors': vrgba = self.customRGBA if vrgba is None: return None from chimera import MaterialColor ccolors = [MaterialColor(*rgba) for rgba in vrgba] return ccolors elif attrname == 'customRGBA': p = self.surface_piece if p is None or p.__destroyed__: return None vcolors = p.vertexColors return vcolors elif attrname == 'drawMode': p = self.surface_piece if p is None or p.__destroyed__: return self.surface_piece_defaults[attrname] return { p.Solid: self.Filled, p.Mesh: self.Mesh, p.Dot: self.Dot }[p.displayStyle] elif attrname == 'lineWidth': p = self.surface_piece if p is None or p.__destroyed__: return self.surface_piece_defaults[attrname] return p.lineThickness elif attrname == 'pointSize': p = self.surface_piece if p is None or p.__destroyed__: return self.surface_piece_defaults[attrname] return p.dotSize elif attrname in ('useLighting', 'twoSidedLighting', 'smoothLines', 'transparencyBlendMode'): p = self.surface_piece if p is None or p.__destroyed__: return self.surface_piece_defaults[attrname] return getattr(p, attrname) elif attrname == 'atoms': return self.category_atoms(primary_atoms=False) elif attrname == 'bonds': return self.category_bonds(primary_atoms=False) elif attrname == 'vertexCount': if self.triData is None: return 0 return len(self.triData[0]) else: raise AttributeError, "MSMSModel has no attribute '%s'" % attrname # ------------------------------------------------------------------------- # Called when an external coloring method is being used. # def custom_coloring(self, m): self.colorMode = self.Custom # ------------------------------------------------------------------------- # Called when an external visibility method is being used. # def custom_visibility(self, m): self.visibilityMode = self.Custom # ------------------------------------------------------------------------- # Generate TrackChanges event so dialogs such as selection inspector or # model inspector can update displayed surface parameter values. # def report_change(self, reason): from chimera import TrackChanges t = TrackChanges.get() t.addModified(self, reason) # ------------------------------------------------------------------------- # def molecule_cb(self, trigger_name, closure, trigger_data): m = self.molecule if m and m in trigger_data.modified: r = trigger_data.reasons if 'surface major' in r or 'atoms moved' in r: self.update_surface() elif 'surface minor' in r: self.update_coloring() self.update_visibility() # ------------------------------------------------------------------------- # def update_surface(self, set_visibility=True): if self.calculate_surface(): self.update_coloring() if set_visibility: # When surface first created atom.surfaceDisplay = False self.update_visibility() # ------------------------------------------------------------------------- # def calculate_surface(self): self.atomMap = None m = self.molecule if m is None: return False alist = self.category_atoms() from calcsurf import msms_geometry, Surface_Calculation_Error try: vfloat, vint, tri, atomareas, compareas, srf, allcomp = \ msms_geometry(alist, probe_radius = self.probeRadius, vertex_density = self.density, all_components = self.allComponents, separate_process = True) except Surface_Calculation_Error, e: self.calculationFailed = True from chimera.replyobj import error error(str(e) + '\n') return False self.calculationFailed = False self.triData = (vfloat, vint, tri) self.srf = srf from numpy import array aa = array(alist) vatom = vint[:, 1] self.atomMap = aa[vatom] varray = vfloat[:, :3] narray = vfloat[:, 3:6] tarray = tri[:, :3] p = self.surface_piece if p is None or p.__destroyed__: self.surface_piece = p = self.newPiece() p.geometry = varray, tarray p.normals = narray # Set per-atom and per-residue surface areas if len(atomareas) == len(alist): for a, atom in enumerate(alist): ases, asas = [float(area) for area in atomareas[a]] atom.areaSES, atom.areaSAS = ases, asas for r in set([atom.residue for atom in alist]): r.areaSES = r.areaSAS = 0.0 for a, atom in enumerate(alist): ases, asas = atomareas[a] r = atom.residue r.areaSES += ases r.areaSAS += asas self.areaSES = float(atomareas[:, 0].sum()) self.areaSAS = float(atomareas[:, 1].sum()) self.report_surface_areas(compareas) elif len(atomareas) > 0: from chimera.replyobj import warning warning('Incorrect number of atom areas reported.\n' '%d areas reported for %d atoms' % (len(atomareas), len(alist))) if allcomp != self.allComponents: self.__dict__['allComponents'] = allcomp self.report_change(self.change_reason['allComponents']) return True
if os.access(filename, os.X_OK): Executable = filename break if Executable: break import sys if not Executable and sys.platform == "win32": drive = os.path.splitdrive(sys.executable)[0] filename = os.path.join(drive, os.sep, "Program Files", "Delphi", "delphi.exe") if os.access(filename, os.X_OK): Executable = filename if not Executable: Executable = "/usr/local/bin/delphi" from chimera import replyobj replyobj.warning("Cannot find Delphi executable. " "Please set manually.") # Option lists for displaying GUI MoleculeInputOptions = [ MoleculeInputOption(help=InputOptionsHelp['pdb']) ] DelPhiInputOptions = [ # These three are used for saving preferences, so # don't change their order unless you change savePrefs() ExecFileOption('DelPhi Executable', 'exe', Executable, required=1, filetypes=[('All Files', '*')], help=InputOptionsHelp['exe']),
def f(filename=filename): from chimera import replyobj replyobj.warning('Using read-only preferences file. ' 'Change access permission for "%s" ' 'to enable saving preferences.\n' % filename)
def _addAttr(attrFile, models, log, raiseAttrDialog): setAttrs = {} colors = {} from CGLutil.annotatedDataFile import readDataFile control = { 'match mode': _MATCH_MODE_ANY, 'recipient': "atoms" } for rawControl, data in readDataFile(attrFile): control.update(rawControl) legalNames = ["attribute", "match mode", "recipient"] for name in control.keys(): if name not in legalNames: raise SyntaxError("Unknown name part of control" " line: '%s'\nMust be one of: %s" % ( name, ", ".join(legalNames))) if "attribute" not in control: raise SyntaxError("No attribute name specified in file") attrName = control["attribute"] if not attrName.replace("_", "").isalnum() \ or attrName[0].isdigit(): raise SyntaxError("Attribute name (%s) is bad.\n" "It must be strictly alphanumeric characters or" " underscores with no spaces and must not start" " with a digit." % control["attribute"]) colorAttr = attrName.lower().endswith("color") matchMode = control["match mode"] if matchMode not in _matchModes: raise SyntaxError("Unknown match mode (%s) specified.\n" "It must be one of: %s" % ( control["match mode"], ", ".join(_matchModes))) recipient = control["recipient"] if not hasattr(selection.ItemizedSelection, recipient): raise SyntaxError("Unknown attribute recipient (%s)" " specified.\nSuggest using atoms, residues, or" " molecules" % control["recipient"]) dataErrors = [] for lineNum, d in enumerate(data): try: selector, value = d except ValueError: dataErrors.append("Data line %d of file either" " not selector/value or not" " tab-delimited" % (lineNum+1)) continue try: sel = specifier.evalSpec(selector, models) except: import sys dataErrors.append("Mangled selector (%s) on" " data line %d: %s" % (selector, lineNum+1, sys.exc_value)) continue matches = getattr(sel, recipient)() # restrict to models; the "models" argument to evalSpec # only works if the selector is an OSL if matches and models is not None: md = set(models) level = matches[0].oslLevel() if level == selection.SelGraph: filterFunc = lambda x, md=md: x in md elif level == selection.SelEdge: filterFunc = lambda x, md=md: \ x.atoms[0].molecule in md else: filterFunc = lambda x, md=md: \ x.molecule in md matches = filter(filterFunc, matches) if not matches: if matchMode != _MATCH_MODE_ANY: dataErrors.append("Selector (%s) on" " data line %d matched nothing" % (selector, lineNum+1)) continue elif len(matches) > 1: if matchMode == _MATCH_MODE_1_TO_1: dataErrors.append("Selector (%s) on" " data line %d matched multiple" " items: %s" % (selector, lineNum+1, ", ".join(map(lambda m: m.oslIdent(), matches)))) if log: replyobj.info("Selector %s matched " % selector + ", ".join(map(misc.chimeraLabel, matches)) + "\n") if colorAttr: if value[0].isalpha(): # color name from chimera.colorTable \ import getColorByName try: value = getColorByName(value) except KeyError: dataErrors.append("Unknown" " color name on data" " line %d: '%s'" % (lineNum+1, value)) continue else: try: rgba = tuple(map(float, value.split())) except ValueError: dataErrors.append( "Unrecognizable color" " value on data line" " %d: '%s'; Must be" " either color name or" " 3 or 4 numbers" " between 0 and 1" " (RGBA)" % (lineNum+1)) continue if max(rgba) > 1.0 or min(rgba) < 0.0: dataErrors.append("Color" " component values on" " data line %d not" " in the range 0 to 1" % (lineNum+1)) continue if rgba in colors: value = colors[rgba] elif len(rgba) in [3, 4]: value = chimera.MaterialColor( *rgba) colors[rgba] = value else: dataErrors.append("Bad number" " of color components" " on data line %d; Must" " be either 3 or 4 (was" " %d)" % (lineNum+1, len(rgba))) continue else: value = convertType(value) for match in matches: setattr(match, attrName, value) if matches and not colorAttr: setAttrs[(recipient, attrName)] = value recipMapping = { "molecules": chimera.Molecule, "residues": chimera.Residue, "bonds": chimera.Bond, "atoms": chimera.Atom } from SimpleSession import registerAttribute for recipient, attrName in setAttrs: if recipient in recipMapping: registerAttribute(recipMapping[recipient], attrName) if setAttrs and not chimera.nogui and raiseAttrDialog: from ShowAttr import ShowAttrDialog, screenedAttrs from chimera import dialogs showableAttr = False reasons = [] for recipient, attrName in setAttrs.keys(): if recipient == "molecules": recipient = "models" validRecipients = ["atoms", "residues", "models"] if recipient not in validRecipients: reasons.append("%s not assigned to atoms," " residues, or models" % attrName) continue key = [chimera.Atom, chimera.Residue, chimera.Model][ validRecipients.index(recipient)] if attrName in screenedAttrs[key]: reasons.append("%s automatically screened out" " by Render By Attribute" % attrName) continue if attrName[0] == '_': reasons.append("%s considered to be a private" " variable" % attrName) continue if attrName[0].isupper(): reasons.append("%s considered to be a symbolic" " constant due to initial capital" " letter" % attrName) continue showableAttr = True break if showableAttr: if models is None: ms = chimera.openModels.list() else: ms = models if colorAttr: an = None mode = None else: an = attrName from types import StringTypes if type(setAttrs[(recipient, attrName)]) in ( bool,) + StringTypes: mode = "Select" else: mode = "Render" d = dialogs.display(ShowAttrDialog.name) d.configure(models=[m for m in ms if isinstance(m, chimera.Molecule)], attrsOf=recipient, attrName=an, mode=mode) else: replyobj.warning("No attributes usable by Render dialog" " were defined:\n" + "\n".join(reasons) + "\n") if dataErrors: raise SyntaxError, "\n".join(dataErrors) return setAttrs
def buried_area(operation, atoms1, atoms2, probeRadius = 1.4, vertexDensity = 2.0): xyzr = atom_xyzr(atoms1 + atoms2) n1 = len(atoms1) xyzr1 = xyzr[:n1] xyzr2 = xyzr[n1:] xyzr12 = xyzr failed = False import MoleculeSurface as ms try: s1 = ms.xyzr_surface_geometry(xyzr1, probeRadius, vertexDensity) s2 = ms.xyzr_surface_geometry(xyzr2, probeRadius, vertexDensity) s12 = ms.xyzr_surface_geometry(xyzr12, probeRadius, vertexDensity) except ms.Surface_Calculation_Error: failed = True if failed or not s1[6] or not s2[6] or not s12[6]: # All component calculation failed. try: s1 = ms.xyzr_surface_geometry(xyzr1, probeRadius, vertexDensity, all_components = False) s2 = ms.xyzr_surface_geometry(xyzr2, probeRadius, vertexDensity, all_components = False) s12 = ms.xyzr_surface_geometry(xyzr12, probeRadius, vertexDensity, all_components = False) except ms.Surface_Calculation_Error: raise CommandError, 'Surface calculation failed.' from chimera import replyobj replyobj.warning('Calculation of some surface components failed. Using only single surface component. This may give inaccurate areas if surfaces of either set of atoms or the combined set are disconnected.\n') # Assign per-atom buried areas. aareas1, aareas2, aareas12 = s1[3], s2[3], s12[3] ases121 = asas121 = ases122 = asas122 = 0 for ai,a in enumerate(atoms1): a.buriedSESArea = aareas1[ai,0] - aareas12[ai,0] a.buriedSASArea = aareas1[ai,1] - aareas12[ai,1] ases121 += aareas12[ai,0] asas121 += aareas12[ai,1] for ai,a in enumerate(atoms2): a.buriedSESArea = aareas2[ai,0] - aareas12[n1+ai,0] a.buriedSASArea = aareas2[ai,1] - aareas12[n1+ai,1] ases122 += aareas12[n1+ai,0] asas122 += aareas12[n1+ai,1] careas1, careas2, careas12 = s1[4], s2[4], s12[4] ases1, asas1 = area_sums(careas1) ases2, asas2 = area_sums(careas2) ases12, asas12 = area_sums(careas12) bsas1 = asas1 - asas121 bsas2 = asas2 - asas122 bsas = 0.5 * (bsas1 + bsas2) bses1 = ases1 - ases121 bses2 = ases2 - ases122 bses = 0.5 * (bses1 + bses2) # TODO: include atomspec's in output message. msg = ('Buried solvent accessible surface area\n' ' B1SAS = %.6g, B2SAS = %.6g, BaveSAS = %.6g\n' ' (A1 = %.6g, A2 = %.6g, A12 = %.6g = %.6g + %.6g)\n' % (bsas1, bsas2, bsas,asas1, asas2, asas12, asas121, asas122) + 'Buried solvent excluded surface area\n ' + ' B1SES = %.6g, B2SES = %.6g, BaveSES = %.6g\n' ' (A1 = %.6g, A2 = %.6g, A12 = %.6g = %.6g + %.6g)\n' % (bses1, bses2, bses,ases1, ases2, ases12, ases121, ases122)) from chimera import replyobj replyobj.info(msg) smsg = 'Buried areas: SAS = %.6g, SES = %.6g\n' % (bsas, bses) replyobj.status(smsg)
def initiateSolvate(models, method, solvent, extent, center, status): import os import chimera from chimera import replyobj from chimera.molEdit import addAtom from WriteMol2 import writeMol2 from tempfile import mkdtemp for m in models: tempDir = mkdtemp() print 'tempDir: ', tempDir def _clean(): for fn in os.listdir(tempDir): os.unlink(os.path.join(tempDir, fn)) os.rmdir(tempDir) sleapIn = os.path.join(tempDir, "sleap.in.mol2") sleapOut= os.path.join(tempDir, "sleap.out.mol2") writeMol2([m], sleapIn, status=status) leaprc = os.path.join(tempDir, "solvate.cmd") writeLeaprc(tempDir, method, solvent, extent, center, leaprc) chimeraRoot = os.environ["CHIMERA"] amberHome = os.path.join(chimeraRoot, "bin", "amber10") command = [os.path.join(amberHome, "exe", "sleap"), "-f", leaprc] print 'command: ', command if status: status("Running sleap" ) from subprocess import Popen, STDOUT, PIPE # For some reason on Windows, if shell==False then antechamber # cannot run bondtype via system(). import sys if sys.platform == "win32": shell = True else: shell = False replyobj.info("Running sleap command: %s\n" % " ".join(command)) import os os.environ["AMBERHOME"]=amberHome sleapMessages = Popen(command, stdin=PIPE, stdout=PIPE, stderr=STDOUT, cwd=tempDir, shell=shell, bufsize=1).stdout while True: line = sleapMessages.readline() if not line: break replyobj.status("(solvate) %s" % line, log=True) if not os.path.exists(sleapOut): _clean() from chimera import NonChimeraError raise NonChimeraError("Failure running sleap \n" "Check reply log for details\n") if status: status("Reading sleap output\n") from chimera import Mol2io, defaultMol2ioHelper mol2io = Mol2io(defaultMol2ioHelper) mols = mol2io.readMol2file(sleapOut) if not mol2io.ok(): _clean() raise IOError(mol2io.error()) if not mols: _clean() raise RuntimeError("No molecules in sleap output") assert len(mols)==1 outm = mols[0] natom = len(m.atoms) nresd = len(m.residues) inAtoms = m.atoms outAtoms = outm.atoms # sort in coordIndex (i.e. input) order # (due to deletions, coordIndex values need _not_ be consecutive) serialSort = lambda a1, a2: cmp(a1.coordIndex, a2.coordIndex) inAtoms.sort(serialSort) outAtoms.sort(serialSort) if status: status("Translating %d atoms" % len(inAtoms)) for inA, outA in zip(inAtoms, outAtoms[:len(inAtoms)]): inA.setCoord(outA.coord()) # added solvent hydrogens may not have been categorized yet, so use # this less obvious way of gathering solvent atoms... existingSolvent = set() from chimera.elements import metals, alkaliMetals nonAlkaliMetals = metals - alkaliMetals for r in m.residues: if len(r.atoms) == 1 and r.atoms[0].element in nonAlkaliMetals: continue for a in r.atoms: if a.surfaceCategory in ["solvent", "ions"]: existingSolvent.update(r.atoms) break # copy mol2 comment which contain the info of the solvent: shape, size, etc if hasattr( outm, "mol2comments" ) and len(outm.mol2comments) > 0: m.solventInfo = outm.mol2comments[0] print "solvent info: ", m.solventInfo if existingSolvent: solventCharges = {} for r in outm.residues[nresd:]: solventNum = r.id.position - nresd if status: status("Creating solvent residue %d " % solventNum ) atomMap = {} nr = m.newResidue(r.type, ' ', solventNum, ' ') # mark residue for exclusion by AddCharge... nr._solvateCharged = True for a in r.atoms: na = addAtom(a.name, a.element, nr, a.coord(), serialNumber=a.serialNumber) na.charge = a.charge na.gaffType = a.mol2type atomMap[a] = na if a.name[0]=="H": na.element = 1 if a.name[0]=="C": na.element = 6 if a.name[0]=="N": na.element = 7 if a.name[0]=="O": na.element = 8 if a.name[0]=="P": na.element = 15 if a.name[0]=="S": na.element = 16 if a.name[0:2]=="Cl": na.element = 17 if existingSolvent: solventCharges[(r.type, a.name)] = a.charge if r.type == "WAT": solventCharges[ ("HOH", a.name)] = a.charge for a in r.atoms: na = atomMap[a] for n in a.neighbors: assert n.residue == r nn = atomMap[n] if nn in na.bondsMap: continue m.newBond(na, nn) if existingSolvent: unknowns = set() for sa in existingSolvent: key = (sa.residue.type, sa.name) try: sa.charge = solventCharges[key] except KeyError: unknowns.add(key) sa.residue._solvateCharged = True if unknowns: replyobj.warning("Could not determine charges for" " pre-existing solvent/ions from added solvent" "/ions for: " + ", ".join([" ".join(x) for x in unknowns])) _clean() from Midas import window window(models)