2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
21 package jalview.ext.rbvi.chimera;
23 import jalview.api.AlignViewportI;
24 import jalview.api.AlignmentViewPanel;
25 import jalview.api.structures.JalviewStructureDisplayI;
26 import jalview.bin.Cache;
27 import jalview.datamodel.AlignmentI;
28 import jalview.datamodel.HiddenColumns;
29 import jalview.datamodel.PDBEntry;
30 import jalview.datamodel.SearchResultMatchI;
31 import jalview.datamodel.SearchResultsI;
32 import jalview.datamodel.SequenceFeature;
33 import jalview.datamodel.SequenceI;
34 import jalview.httpserver.AbstractRequestHandler;
35 import jalview.io.DataSourceType;
36 import jalview.schemes.ColourSchemeI;
37 import jalview.schemes.ResidueProperties;
38 import jalview.structure.AtomSpec;
39 import jalview.structure.StructureMappingcommandSet;
40 import jalview.structure.StructureSelectionManager;
41 import jalview.structures.models.AAStructureBindingModel;
42 import jalview.util.MessageManager;
44 import java.awt.Color;
46 import java.io.FileOutputStream;
47 import java.io.IOException;
48 import java.io.PrintWriter;
49 import java.net.BindException;
50 import java.util.ArrayList;
51 import java.util.BitSet;
52 import java.util.Collections;
53 import java.util.Hashtable;
54 import java.util.LinkedHashMap;
55 import java.util.List;
58 import ext.edu.ucsf.rbvi.strucviz2.ChimeraManager;
59 import ext.edu.ucsf.rbvi.strucviz2.ChimeraModel;
60 import ext.edu.ucsf.rbvi.strucviz2.StructureManager;
61 import ext.edu.ucsf.rbvi.strucviz2.StructureManager.ModelType;
63 public abstract class JalviewChimeraBinding extends AAStructureBindingModel
65 public static final String CHIMERA_FEATURE_GROUP = "Chimera";
67 // Chimera clause to exclude alternate locations in atom selection
68 private static final String NO_ALTLOCS = "&~@.B-Z&~@.2-9";
70 private static final String COLOURING_CHIMERA = MessageManager
71 .getString("status.colouring_chimera");
73 private static final boolean debug = false;
75 private static final String PHOSPHORUS = "P";
77 private static final String ALPHACARBON = "CA";
79 private Hashtable<String, String> chainFile = new Hashtable<>();
82 * Object through which we talk to Chimera
84 private ChimeraManager viewer;
87 * Object which listens to Chimera notifications
89 private AbstractRequestHandler chimeraListener;
92 * set if chimera state is being restored from some source - instructs binding
93 * not to apply default display style when structure set is updated for first
96 private boolean loadingFromArchive = false;
99 * flag to indicate if the Chimera viewer should ignore sequence colouring
100 * events from the structure manager because the GUI is still setting up
102 private boolean loadingFinished = true;
105 * Map of ChimeraModel objects keyed by PDB full local file name
107 private Map<String, List<ChimeraModel>> chimeraMaps = new LinkedHashMap<>();
109 String lastHighlightCommand;
112 * incremented every time a load notification is successfully handled -
113 * lightweight mechanism for other threads to detect when they can start
114 * referring to new structures.
116 private long loadNotifiesHandled = 0;
118 private Thread chimeraMonitor;
121 * Open a PDB structure file in Chimera and set up mappings from Jalview.
123 * We check if the PDB model id is already loaded in Chimera, if so don't
124 * reopen it. This is the case if Chimera has opened a saved session file.
129 public boolean openFile(PDBEntry pe)
131 String file = pe.getFile();
134 List<ChimeraModel> modelsToMap = new ArrayList<>();
135 List<ChimeraModel> oldList = viewer.getModelList();
136 boolean alreadyOpen = false;
139 * If Chimera already has this model, don't reopen it, but do remap it.
141 for (ChimeraModel open : oldList)
143 if (open.getModelName().equals(pe.getId()))
146 modelsToMap.add(open);
151 * If Chimera doesn't yet have this model, ask it to open it, and retrieve
152 * the model name(s) added by Chimera.
156 viewer.openModel(file, pe.getId(), ModelType.PDB_MODEL);
157 List<ChimeraModel> newList = viewer.getModelList();
158 // JAL-1728 newList.removeAll(oldList) does not work
159 for (ChimeraModel cm : newList)
161 if (cm.getModelName().equals(pe.getId()))
168 chimeraMaps.put(file, modelsToMap);
170 if (getSsm() != null)
172 getSsm().addStructureViewerListener(this);
175 } catch (Exception q)
177 log("Exception when trying to open model " + file + "\n"
192 public JalviewChimeraBinding(StructureSelectionManager ssm,
193 PDBEntry[] pdbentry, SequenceI[][] sequenceIs,
194 DataSourceType protocol)
196 super(ssm, pdbentry, sequenceIs, protocol);
197 viewer = new ChimeraManager(new StructureManager(true));
201 * Starts a thread that waits for the Chimera process to finish, so that we
202 * can then close the associated resources. This avoids leaving orphaned
203 * Chimera viewer panels in Jalview if the user closes Chimera.
205 protected void startChimeraProcessMonitor()
207 final Process p = viewer.getChimeraProcess();
208 chimeraMonitor = new Thread(new Runnable()
217 JalviewStructureDisplayI display = getViewer();
220 display.closeViewer(false);
222 } catch (InterruptedException e)
224 // exit thread if Chimera Viewer is closed in Jalview
228 chimeraMonitor.start();
232 * Start a dedicated HttpServer to listen for Chimera notifications, and tell
233 * it to start listening
235 public void startChimeraListener()
239 chimeraListener = new ChimeraListener(this);
240 viewer.startListening(chimeraListener.getUri());
241 } catch (BindException e)
244 "Failed to start Chimera listener: " + e.getMessage());
249 * Close down the Jalview viewer and listener, and (optionally) the associated
252 public void closeViewer(boolean closeChimera)
254 getSsm().removeStructureViewerListener(this, this.getStructureFiles());
257 viewer.exitChimera();
259 if (this.chimeraListener != null)
261 chimeraListener.shutdown();
262 chimeraListener = null;
266 if (chimeraMonitor != null)
268 chimeraMonitor.interrupt();
270 releaseUIResources();
274 public void colourByChain()
276 colourBySequence = false;
277 sendAsynchronousCommand("rainbow chain", COLOURING_CHIMERA);
281 * Constructs and sends a Chimera command to colour by charge
283 * <li>Aspartic acid and Glutamic acid (negative charge) red</li>
284 * <li>Lysine and Arginine (positive charge) blue</li>
285 * <li>Cysteine - yellow</li>
286 * <li>all others - white</li>
290 public void colourByCharge()
292 colourBySequence = false;
293 String command = "color white;color red ::ASP;color red ::GLU;color blue ::LYS;color blue ::ARG;color yellow ::CYS";
294 sendAsynchronousCommand(command, COLOURING_CHIMERA);
301 public String superposeStructures(AlignmentI[] _alignment,
302 int[] _refStructure, HiddenColumns[] _hiddenCols)
304 StringBuilder allComs = new StringBuilder(128);
305 String[] files = getStructureFiles();
307 if (!waitForFileLoad(files))
313 StringBuilder selectioncom = new StringBuilder(256);
314 for (int a = 0; a < _alignment.length; a++)
316 int refStructure = _refStructure[a];
317 AlignmentI alignment = _alignment[a];
318 HiddenColumns hiddenCols = _hiddenCols[a];
320 if (refStructure >= files.length)
322 System.err.println("Ignoring invalid reference structure value "
328 * 'matched' bit i will be set for visible alignment columns i where
329 * all sequences have a residue with a mapping to the PDB structure
331 BitSet matched = new BitSet();
332 for (int m = 0; m < alignment.getWidth(); m++)
334 if (hiddenCols == null || hiddenCols.isVisible(m))
340 SuperposeData[] structures = new SuperposeData[files.length];
341 for (int f = 0; f < files.length; f++)
343 structures[f] = new SuperposeData(alignment.getWidth());
347 * Calculate the superposable alignment columns ('matched'), and the
348 * corresponding structure residue positions (structures.pdbResNo)
350 int candidateRefStructure = findSuperposableResidues(alignment,
351 matched, structures);
352 if (refStructure < 0)
355 * If no reference structure was specified, pick the first one that has
356 * a mapping in the alignment
358 refStructure = candidateRefStructure;
361 int nmatched = matched.cardinality();
364 return MessageManager.formatMessage("label.insufficient_residues",
369 * Generate select statements to select regions to superimpose structures
371 String[] selcom = new String[files.length];
372 for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
374 String chainCd = "." + structures[pdbfnum].chain;
377 StringBuilder molsel = new StringBuilder();
379 int nextColumnMatch = matched.nextSetBit(0);
380 while (nextColumnMatch != -1)
382 int pdbResNum = structures[pdbfnum].pdbResNo[nextColumnMatch];
383 if (lpos != pdbResNum - 1)
386 * discontiguous - append last residue now
390 molsel.append(String.valueOf(lpos));
391 molsel.append(chainCd);
399 * extending a contiguous run
404 * start the range selection
406 molsel.append(String.valueOf(lpos));
412 nextColumnMatch = matched.nextSetBit(nextColumnMatch + 1);
416 * and terminate final selection
420 molsel.append(String.valueOf(lpos));
421 molsel.append(chainCd);
423 if (molsel.length() > 1)
425 selcom[pdbfnum] = molsel.toString();
426 selectioncom.append("#").append(String.valueOf(pdbfnum))
428 selectioncom.append(selcom[pdbfnum]);
429 selectioncom.append(" ");
430 if (pdbfnum < files.length - 1)
432 selectioncom.append("| ");
437 selcom[pdbfnum] = null;
441 StringBuilder command = new StringBuilder(256);
442 for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
444 if (pdbfnum == refStructure || selcom[pdbfnum] == null
445 || selcom[refStructure] == null)
449 if (command.length() > 0)
455 * Form Chimera match command, from the 'new' structure to the
456 * 'reference' structure e.g. (50 residues, chain B/A, alphacarbons):
458 * match #1:1-30.B,81-100.B@CA #0:21-40.A,61-90.A@CA
461 * https://www.cgl.ucsf.edu/chimera/docs/UsersGuide/midas/match.html
463 command.append("match ").append(getModelSpec(pdbfnum))
465 command.append(selcom[pdbfnum]);
466 command.append("@").append(
467 structures[pdbfnum].isRna ? PHOSPHORUS : ALPHACARBON);
468 // JAL-1757 exclude alternate CA locations
469 command.append(NO_ALTLOCS);
470 command.append(" ").append(getModelSpec(refStructure)).append(":");
471 command.append(selcom[refStructure]);
472 command.append("@").append(
473 structures[refStructure].isRna ? PHOSPHORUS : ALPHACARBON);
474 command.append(NO_ALTLOCS);
476 if (selectioncom.length() > 0)
480 System.out.println("Select regions:\n" + selectioncom.toString());
482 "Superimpose command(s):\n" + command.toString());
484 allComs/*.append("~display all; chain @CA|P; ribbon ")
485 .append(selectioncom.toString())*/
486 .append(";" + command.toString());
491 if (selectioncom.length() > 0)
493 // TODO: visually distinguish regions that were superposed
494 if (selectioncom.substring(selectioncom.length() - 1).equals("|"))
496 selectioncom.setLength(selectioncom.length() - 1);
500 System.out.println("Select regions:\n" + selectioncom.toString());
502 allComs.append("; ~display "); // all");
503 if (!isShowAlignmentOnly())
505 allComs.append("; ribbon; chain @CA|P");
509 allComs.append("; ~ribbon");
511 allComs.append("; ribbon ").append(selectioncom.toString())
513 List<String> chimeraReplies = sendChimeraCommand(allComs.toString(),
515 for (String reply : chimeraReplies)
517 String lowerCase = reply.toLowerCase();
518 if (lowerCase.contains("unequal numbers of atoms")
519 || lowerCase.contains("at least"))
529 * Helper method to construct model spec in Chimera format:
531 * <li>#0 (#1 etc) for a PDB file with no sub-models</li>
532 * <li>#0.1 (#1.1 etc) for a PDB file with sub-models</li>
534 * Note for now we only ever choose the first of multiple models. This
535 * corresponds to the hard-coded Jmol equivalent (compare {1.1}). Refactor in
536 * future if there is a need to select specific sub-models.
542 public String getModelSpec(int pdbfnum)
544 if (pdbfnum < 0 || pdbfnum >= getPdbCount())
550 * For now, the test for having sub-models is whether multiple Chimera
551 * models are mapped for the PDB file; the models are returned as a response
552 * to the Chimera command 'list models type molecule', see
553 * ChimeraManager.getModelList().
555 List<ChimeraModel> maps = chimeraMaps.get(getStructureFiles()[pdbfnum]);
556 boolean hasSubModels = maps != null && maps.size() > 1;
557 String spec = "#" + String.valueOf(pdbfnum);
558 return hasSubModels ? spec + ".1" : spec;
562 * Launch Chimera, unless an instance linked to this object is already
563 * running. Returns true if Chimera is successfully launched, or already
564 * running, else false.
568 public boolean launchChimera()
570 if (viewer.isChimeraLaunched())
575 boolean launched = viewer
576 .launchChimera(StructureManager.getChimeraPaths());
579 startChimeraProcessMonitor();
583 log("Failed to launch Chimera!");
589 * Answers true if the Chimera process is still running, false if ended or not
594 public boolean isChimeraRunning()
596 return viewer.isChimeraLaunched();
600 * Send a command to Chimera, and optionally log and return any responses.
602 * Does nothing, and returns null, if the command is the same as the last one
608 public List<String> sendChimeraCommand(final String command,
613 // ? thread running after viewer shut down
616 List<String> reply = null;
617 viewerCommandHistory(false);
618 if (true /*lastCommand == null || !lastCommand.equals(command)*/)
620 // trim command or it may never find a match in the replyLog!!
621 List<String> lastReply = viewer.sendChimeraCommand(command.trim(),
628 log("Response from command ('" + command + "') was:\n"
633 viewerCommandHistory(true);
639 * Send a Chimera command asynchronously in a new thread. If the progress
640 * message is not null, display this message while the command is executing.
645 protected abstract void sendAsynchronousCommand(String command,
649 * Sends a set of colour commands to the structure viewer
654 protected void colourBySequence(String[] commands)
656 for (String command : commands)
658 sendAsynchronousCommand(command, COLOURING_CHIMERA);
663 * Computes and returns a set of commands to colour residues in Chimera the same
664 * as mapped residues in the alignment
671 protected String[] getColourBySequenceCommands(
672 String[] files, AlignmentViewPanel viewPanel)
674 Map<Object, AtomSpecModel> colourMap = buildColoursMap(viewPanel);
676 return ChimeraCommands.getColourBySequenceCommand(colourMap, this);
682 protected void executeWhenReady(String command)
685 sendChimeraCommand(command, false);
689 private void waitForChimera()
691 while (viewer != null && viewer.isBusy())
696 } catch (InterruptedException q)
702 // End StructureListener
703 // //////////////////////////
706 * instruct the Jalview binding to update the pdbentries vector if necessary
707 * prior to matching the viewer's contents to the list of structure files
708 * Jalview knows about.
710 public abstract void refreshPdbEntries();
713 * map between index of model filename returned from getPdbFile and the first
714 * index of models from this file in the viewer. Note - this is not trimmed -
715 * use getPdbFile to get number of unique models.
717 private int _modelFileNameMap[];
719 // ////////////////////////////////
720 // /StructureListener
722 public synchronized String[] getStructureFiles()
726 return new String[0];
729 return chimeraMaps.keySet()
730 .toArray(modelFileNames = new String[chimeraMaps.size()]);
734 * Construct and send a command to highlight zero, one or more atoms. We do
735 * this by sending an "rlabel" command to show the residue label at that
739 public void highlightAtoms(List<AtomSpec> atoms)
741 if (atoms == null || atoms.size() == 0)
746 StringBuilder cmd = new StringBuilder(128);
747 boolean first = true;
748 boolean found = false;
750 for (AtomSpec atom : atoms)
752 int pdbResNum = atom.getPdbResNum();
753 String chain = atom.getChain();
754 String pdbfile = atom.getPdbFile();
755 List<ChimeraModel> cms = chimeraMaps.get(pdbfile);
756 if (cms != null && !cms.isEmpty())
760 cmd.append("rlabel #").append(cms.get(0).getModelNumber())
768 cmd.append(pdbResNum);
769 if (!chain.equals(" "))
771 cmd.append(".").append(chain);
776 String command = cmd.toString();
779 * avoid repeated commands for the same residue
781 if (command.equals(lastHighlightCommand))
787 * unshow the label for the previous residue
789 if (lastHighlightCommand != null)
791 viewer.sendChimeraCommand("~" + lastHighlightCommand, false);
795 viewer.sendChimeraCommand(command, false);
797 this.lastHighlightCommand = command;
801 * Query Chimera for its current selection, and highlight it on the alignment
803 public void highlightChimeraSelection()
806 * Ask Chimera for its current selection
808 List<String> selection = viewer.getSelectedResidueSpecs();
811 * Parse model number, residue and chain for each selected position,
812 * formatted as #0:123.A or #1.2:87.B (#model.submodel:residue.chain)
814 List<AtomSpec> atomSpecs = convertStructureResiduesToAlignment(
818 * Broadcast the selection (which may be empty, if the user just cleared all
821 getSsm().mouseOverStructure(atomSpecs);
825 * Converts a list of Chimera atomspecs to a list of AtomSpec representing the
826 * corresponding residues (if any) in Jalview
828 * @param structureSelection
831 protected List<AtomSpec> convertStructureResiduesToAlignment(
832 List<String> structureSelection)
834 List<AtomSpec> atomSpecs = new ArrayList<>();
835 for (String atomSpec : structureSelection)
839 AtomSpec spec = AtomSpec.fromChimeraAtomspec(atomSpec);
840 String pdbfilename = getPdbFileForModel(spec.getModelNumber());
841 spec.setPdbFile(pdbfilename);
843 } catch (IllegalArgumentException e)
845 System.err.println("Failed to parse atomspec: " + atomSpec);
855 protected String getPdbFileForModel(int modelId)
858 * Work out the pdbfilename from the model number
860 String pdbfilename = modelFileNames[0];
861 findfileloop: for (String pdbfile : this.chimeraMaps.keySet())
863 for (ChimeraModel cm : chimeraMaps.get(pdbfile))
865 if (cm.getModelNumber() == modelId)
867 pdbfilename = pdbfile;
875 private void log(String message)
877 System.err.println("## Chimera log: " + message);
880 private void viewerCommandHistory(boolean enable)
882 // log("(Not yet implemented) History "
883 // + ((debug || enable) ? "on" : "off"));
886 public long getLoadNotifiesHandled()
888 return loadNotifiesHandled;
892 public void setJalviewColourScheme(ColourSchemeI cs)
894 colourBySequence = false;
901 // Chimera expects RGB values in the range 0-1
902 final double normalise = 255D;
903 viewerCommandHistory(false);
904 StringBuilder command = new StringBuilder(128);
906 List<String> residueSet = ResidueProperties.getResidues(isNucleotide(),
908 for (String resName : residueSet)
910 char res = resName.length() == 3
911 ? ResidueProperties.getSingleCharacterCode(resName)
913 Color col = cs.findColour(res, 0, null, null, 0f);
914 command.append("color ")
915 .append(String.valueOf(col.getRed() / normalise)).append(",")
916 .append(String.valueOf(col.getGreen() / normalise))
917 .append(",").append(String.valueOf(col.getBlue() / normalise))
918 .append(" ::").append(resName).append(";");
921 sendAsynchronousCommand(command.toString(), COLOURING_CHIMERA);
922 viewerCommandHistory(true);
926 * called when the binding thinks the UI needs to be refreshed after a Chimera
927 * state change. this could be because structures were loaded, or because an
928 * error has occurred.
930 public abstract void refreshGUI();
933 public void setLoadingFromArchive(boolean loadingFromArchive)
935 this.loadingFromArchive = loadingFromArchive;
940 * @return true if Chimeral is still restoring state or loading is still going
941 * on (see setFinsihedLoadingFromArchive)
944 public boolean isLoadingFromArchive()
946 return loadingFromArchive && !loadingFinished;
950 * modify flag which controls if sequence colouring events are honoured by the
951 * binding. Should be true for normal operation
953 * @param finishedLoading
956 public void setFinishedLoadingFromArchive(boolean finishedLoading)
958 loadingFinished = finishedLoading;
962 * Send the Chimera 'background solid <color>" command.
965 * ://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/background
970 public void setBackgroundColour(Color col)
972 viewerCommandHistory(false);
973 double normalise = 255D;
974 final String command = "background solid " + col.getRed() / normalise
975 + "," + col.getGreen() / normalise + ","
976 + col.getBlue() / normalise + ";";
977 viewer.sendChimeraCommand(command, false);
978 viewerCommandHistory(true);
982 * Ask Chimera to save its session to the given file. Returns true if
983 * successful, else false.
988 public boolean saveSession(String filepath)
990 if (isChimeraRunning())
992 List<String> reply = viewer.sendChimeraCommand("save " + filepath,
994 if (reply.contains("Session written"))
1001 .error("Error saving Chimera session: " + reply.toString());
1008 * Ask Chimera to open a session file. Returns true if successful, else false.
1009 * The filename must have a .py extension for this command to work.
1014 public boolean openSession(String filepath)
1016 sendChimeraCommand("open " + filepath, true);
1017 // todo: test for failure - how?
1022 * Send a 'focus' command to Chimera to recentre the visible display
1024 public void focusView()
1026 sendChimeraCommand("focus", false);
1030 * Send a 'show' command for all atoms in the currently selected columns
1032 * TODO: pull up to abstract structure viewer interface
1036 public void highlightSelection(AlignmentViewPanel vp)
1038 List<Integer> cols = vp.getAlignViewport().getColumnSelection()
1040 AlignmentI alignment = vp.getAlignment();
1041 StructureSelectionManager sm = getSsm();
1042 for (SequenceI seq : alignment.getSequences())
1045 * convert selected columns into sequence positions
1047 int[] positions = new int[cols.size()];
1049 for (Integer col : cols)
1051 positions[i++] = seq.findPosition(col);
1053 sm.highlightStructure(this, seq, positions);
1058 * Constructs and send commands to Chimera to set attributes on residues for
1059 * features visible in Jalview
1064 public int sendFeaturesToViewer(AlignmentViewPanel avp)
1066 // TODO refactor as required to pull up to an interface
1067 AlignmentI alignment = avp.getAlignment();
1069 String[] files = getStructureFiles();
1075 StructureMappingcommandSet commandSet = ChimeraCommands
1076 .getSetAttributeCommandsForFeatures(avp, this);
1077 String[] commands = commandSet.commands;
1078 if (commands.length > 10)
1080 sendCommandsByFile(commands);
1084 for (String command : commands)
1086 sendAsynchronousCommand(command, null);
1089 return commands.length;
1093 * Write commands to a temporary file, and send a command to Chimera to open
1094 * the file as a commands script. For use when sending a large number of
1095 * separate commands would overload the REST interface mechanism.
1099 protected void sendCommandsByFile(String[] commands)
1103 File tmp = File.createTempFile("chim", ".com");
1105 PrintWriter out = new PrintWriter(new FileOutputStream(tmp));
1106 for (String command : commands)
1108 out.println(command);
1112 String path = tmp.getAbsolutePath();
1113 sendAsynchronousCommand("open cmd:" + path, null);
1114 } catch (IOException e)
1116 System.err.println("Sending commands to Chimera via file failed with "
1122 * Get Chimera residues which have the named attribute, find the mapped
1123 * positions in the Jalview sequence(s), and set as sequence features
1126 * @param alignmentPanel
1128 public void copyStructureAttributesToFeatures(String attName,
1129 AlignmentViewPanel alignmentPanel)
1131 // todo pull up to AAStructureBindingModel (and interface?)
1134 * ask Chimera to list residues with the attribute, reporting its value
1136 // this alternative command
1137 // list residues spec ':*/attName' attr attName
1138 // doesn't report 'None' values (which is good), but
1139 // fails for 'average.bfactor' (which is bad):
1141 String cmd = "list residues attr '" + attName + "'";
1142 List<String> residues = sendChimeraCommand(cmd, true);
1144 boolean featureAdded = createFeaturesForAttributes(attName, residues);
1147 alignmentPanel.getFeatureRenderer().featuresAdded();
1152 * Create features in Jalview for the given attribute name and structure
1156 * The residue list should be 0, 1 or more reply lines of the format:
1157 * residue id #0:5.A isHelix -155.000836316 index 5
1159 * residue id #0:6.A isHelix None
1166 protected boolean createFeaturesForAttributes(String attName,
1167 List<String> residues)
1169 boolean featureAdded = false;
1170 String featureGroup = getViewerFeatureGroup();
1172 for (String residue : residues)
1174 AtomSpec spec = null;
1175 String[] tokens = residue.split(" ");
1176 if (tokens.length < 5)
1180 String atomSpec = tokens[2];
1181 String attValue = tokens[4];
1184 * ignore 'None' (e.g. for phi) or 'False' (e.g. for isHelix)
1186 if ("None".equalsIgnoreCase(attValue)
1187 || "False".equalsIgnoreCase(attValue))
1194 spec = AtomSpec.fromChimeraAtomspec(atomSpec);
1195 } catch (IllegalArgumentException e)
1197 System.err.println("Problem parsing atomspec " + atomSpec);
1201 String chainId = spec.getChain();
1202 String description = attValue;
1203 float score = Float.NaN;
1206 score = Float.valueOf(attValue);
1207 description = chainId;
1208 } catch (NumberFormatException e)
1210 // was not a float value
1213 String pdbFile = getPdbFileForModel(spec.getModelNumber());
1214 spec.setPdbFile(pdbFile);
1216 List<AtomSpec> atoms = Collections.singletonList(spec);
1219 * locate the mapped position in the alignment (if any)
1221 SearchResultsI sr = getSsm()
1222 .findAlignmentPositionsForStructurePositions(atoms);
1225 * expect one matched alignment position, or none
1226 * (if the structure position is not mapped)
1228 for (SearchResultMatchI m : sr.getResults())
1230 SequenceI seq = m.getSequence();
1231 int start = m.getStart();
1232 int end = m.getEnd();
1233 SequenceFeature sf = new SequenceFeature(attName, description,
1234 start, end, score, featureGroup);
1235 // todo: should SequenceFeature have an explicit property for chain?
1236 // note: repeating the action shouldn't duplicate features
1237 featureAdded |= seq.addSequenceFeature(sf);
1240 return featureAdded;
1244 * Answers the feature group name to apply to features created in Jalview from
1245 * Chimera attributes
1249 protected String getViewerFeatureGroup()
1251 // todo pull up to interface
1252 return CHIMERA_FEATURE_GROUP;
1255 public Hashtable<String, String> getChainFile()
1260 public List<ChimeraModel> getChimeraModelByChain(String chain)
1262 return chimeraMaps.get(chainFile.get(chain));
1265 public int getModelNoForChain(String chain)
1267 List<ChimeraModel> foundModels = getChimeraModelByChain(chain);
1268 if (foundModels != null && !foundModels.isEmpty())
1270 return foundModels.get(0).getModelNumber();
1276 public void showStructures(AlignViewportI av, boolean refocus)
1278 StringBuilder cmd = new StringBuilder(128);
1279 cmd.append("~display; ~ribbon;");
1281 AtomSpecModel model = getShownResidues(av);
1282 String atomSpec = ChimeraCommands.getAtomSpec(model, this);
1284 cmd.append("ribbon ").append(atomSpec);
1285 if (!isShowAlignmentOnly())
1287 cmd.append("chain @CA|P; ribbon");
1291 cmd.append("; focus");
1293 sendChimeraCommand(cmd.toString(), false);