49c4ec6503469fd436f5aaf0e166031302b00ee1
[jalview.git] / src / jalview / ext / pymol / PymolCommands.java
1 package jalview.ext.pymol;
2
3 import java.awt.Color;
4 import java.util.ArrayList;
5 import java.util.Arrays;
6 import java.util.List;
7 import java.util.Map;
8
9 import jalview.structure.AtomSpecModel;
10 import jalview.structure.StructureCommand;
11 import jalview.structure.StructureCommandI;
12 import jalview.structure.StructureCommandsBase;
13
14 /**
15  * A class that generates commands to send to PyMol over its XML-RPC interface.
16  * <p>
17  * Note that because the xml-rpc interface can only accept one command at a
18  * time, we can't concatenate commands, and must instead form and send them
19  * individually.
20  * 
21  * @see https://pymolwiki.org/index.php/Category:Commands
22  * @see https://pymolwiki.org/index.php/RPC
23  */
24 public class PymolCommands extends StructureCommandsBase
25 {
26   // https://pymol.org/dokuwiki/doku.php?id=command:zoom
27   // not currently documented on
28   // https://pymolwiki.org/index.php/Category:Commands
29   private static final StructureCommand FOCUS_VIEW = new StructureCommand(
30           "zoom");
31
32   // https://pymolwiki.org/index.php/Quit
33   private static final StructureCommand CLOSE_PYMOL = new StructureCommand(
34           "quit");
35
36   // not currently documented on
37   // https://pymolwiki.org/index.php/Category:Commands
38   private static final StructureCommand COLOUR_BY_CHAIN = new StructureCommand(
39           "spectrum", "chain");
40
41   private static final List<StructureCommandI> COLOR_BY_CHARGE = Arrays
42           .asList(new StructureCommand("color", "white", "*"),
43                   new StructureCommand("color", "red", "resn ASP resn GLU"),
44                   new StructureCommand("color", "blue",
45                           "resn LYS resn ARG"),
46                   new StructureCommand("color", "yellow", "resn CYS"));
47
48   private static final List<StructureCommandI> SHOW_BACKBONE = Arrays
49           .asList(new StructureCommand("hide", "everything"),
50                   new StructureCommand("show", "ribbon"));
51
52   @Override
53   public StructureCommandI colourByChain()
54   {
55     return COLOUR_BY_CHAIN;
56   }
57
58   @Override
59   public List<StructureCommandI> colourByCharge()
60   {
61     return COLOR_BY_CHARGE;
62   }
63
64   @Override
65   public StructureCommandI setBackgroundColour(Color col)
66   {
67     // https://pymolwiki.org/index.php/Bg_Color
68     return new StructureCommand("bg_color", getColourString(col));
69   }
70
71   /**
72    * Returns a colour formatted suitable for use in viewer command syntax. For
73    * example, red is {@code "0xff0000"}.
74    * 
75    * @param c
76    * @return
77    */
78   protected String getColourString(Color c)
79   {
80     return String.format("0x%02x%02x%02x", c.getRed(), c.getGreen(),
81             c.getBlue());
82   }
83
84   @Override
85   public StructureCommandI focusView()
86   {
87     return FOCUS_VIEW;
88   }
89
90   @Override
91   public List<StructureCommandI> showChains(List<String> toShow)
92   {
93     // https://pymolwiki.org/index.php/Show
94     List<StructureCommandI> commands = new ArrayList<>();
95     commands.add(new StructureCommand("hide", "everything"));
96     commands.add(new StructureCommand("show", "lines"));
97     StringBuilder chains = new StringBuilder();
98     for (String chain : toShow)
99     {
100       chains.append(" chain ").append(chain);
101     }
102     commands.add(
103             new StructureCommand("show", "cartoon", chains.toString()));
104     return commands;
105   }
106
107   @Override
108   public List<StructureCommandI> superposeStructures(AtomSpecModel refAtoms,
109           AtomSpecModel atomSpec, AtomSpecType specType)
110   {
111
112     // https://pymolwiki.org/index.php/Super
113     List<StructureCommandI> commands = new ArrayList<>();
114     String refAtomsAlphaOnly = "(" + getAtomSpec(refAtoms, specType)
115             + " and (altloc '' or altloc 'a'))";
116     String atomSpec2AlphaOnly = "(" + getAtomSpec(atomSpec, specType)
117             + " and (altloc '' or altloc 'a'))";
118     // pair_fit mobile -> reference
119     // crashes when undo is enabled on 2.5.2 (incentive)
120     commands.add(new StructureCommand("undo_disable"));
121     commands.add(new StructureCommand("pair_fit", atomSpec2AlphaOnly,
122             refAtomsAlphaOnly));
123     commands.add(new StructureCommand("undo_enable"));
124
125     /*
126      * and show superposed residues as cartoon
127      */
128     String refAtomsAll = getAtomSpec(refAtoms, AtomSpecType.RESIDUE_ONLY);
129     String atomSpec2All = getAtomSpec(atomSpec, AtomSpecType.RESIDUE_ONLY);
130     commands.add(new StructureCommand("show", "cartoon",
131             refAtomsAll + " " + atomSpec2All));
132
133     return commands;
134   }
135
136   @Override
137   public StructureCommandI openCommandFile(String path)
138   {
139     // https://pymolwiki.org/index.php/Run
140     return new StructureCommand("run", path); // should be .pml
141   }
142
143   @Override
144   public StructureCommandI saveSession(String filepath)
145   {
146     // https://pymolwiki.org/index.php/Save#EXAMPLES
147     return new StructureCommand("save", filepath); // should be .pse
148   }
149
150   /**
151    * Returns a selection string in PyMOL 'selection macro' format:
152    * 
153    * <pre>
154    * modelId// chain/residues/
155    * </pre>
156    * 
157    * If more than one chain, makes a selection expression for each, and they are
158    * separated by spaces.
159    * 
160    * @see https://pymolwiki.org/index.php/Selection_Macros
161    */
162   @Override
163   public String getAtomSpec(AtomSpecModel model, AtomSpecType specType)
164   {
165     StringBuilder sb = new StringBuilder(64);
166     boolean first = true;
167     for (String modelId : model.getModels())
168     {
169       for (String chain : model.getChains(modelId))
170       {
171         if (!first)
172         {
173           sb.append(" ");
174         }
175         first = false;
176         List<int[]> rangeList = model.getRanges(modelId, chain);
177         chain = chain.trim();
178         sb.append(modelId).append("//").append(chain).append("/");
179         boolean firstRange = true;
180         for (int[] range : rangeList)
181         {
182           if (!firstRange)
183           {
184             sb.append("+");
185           }
186           firstRange = false;
187           sb.append(String.valueOf(range[0]));
188           if (range[0] != range[1])
189           {
190             sb.append("-").append(String.valueOf(range[1]));
191           }
192         }
193         sb.append("/");
194         if (specType == AtomSpecType.ALPHA)
195         {
196           sb.append("CA");
197         }
198         if (specType == AtomSpecType.PHOSPHATE)
199         {
200           sb.append("P");
201         }
202       }
203     }
204     return sb.toString();
205   }
206
207   @Override
208   public List<StructureCommandI> showBackbone()
209   {
210     return SHOW_BACKBONE;
211   }
212
213   @Override
214   protected StructureCommandI colourResidues(String atomSpec, Color colour)
215   {
216     // https://pymolwiki.org/index.php/Color
217     return new StructureCommand("color", getColourString(colour), atomSpec);
218   }
219
220   @Override
221   protected String getResidueSpec(String residue)
222   {
223     // https://pymolwiki.org/index.php/Selection_Algebra
224     return "resn " + residue;
225   }
226
227   @Override
228   public StructureCommandI loadFile(String file)
229   {
230     return new StructureCommand("load", file);
231   }
232
233   /**
234    * Overrides the default implementation (which generates concatenated
235    * commands) to generate one per colour (because the XML-RPC interface to
236    * PyMOL only accepts one command at a time)
237    * 
238    * @param colourMap
239    * @return
240    */
241   @Override
242   public List<StructureCommandI> colourBySequence(
243           Map<Object, AtomSpecModel> colourMap)
244   {
245     List<StructureCommandI> commands = new ArrayList<>();
246     for (Object key : colourMap.keySet())
247     {
248       Color colour = (Color) key;
249       final AtomSpecModel colourData = colourMap.get(colour);
250       commands.add(getColourCommand(colourData, colour));
251     }
252
253     return commands;
254   }
255
256   /**
257    * Returns a viewer command to set the given atom property value on atoms
258    * specified by the AtomSpecModel, for example
259    * 
260    * <pre>
261    * iterate 4zho//B/12-34,48-55/CA,jv_chain='primary'
262    * </pre>
263    * 
264    * @param attributeName
265    * @param attributeValue
266    * @param atomSpecModel
267    * @return
268    */
269   protected StructureCommandI setAttribute(String attributeName,
270           String attributeValue, AtomSpecModel atomSpecModel)
271   {
272     StringBuilder sb = new StringBuilder(128);
273     sb.append("p.").append(attributeName).append("='")
274             .append(attributeValue).append("'");
275     String atomSpec = getAtomSpec(atomSpecModel, AtomSpecType.RESIDUE_ONLY);
276     return new StructureCommand("iterate", atomSpec, sb.toString());
277   }
278
279   /**
280    * Traverse the map of features/values/models/chains/positions to construct a
281    * list of 'set property' commands (one per distinct feature type and value).
282    * The values are stored in the 'p' dictionary of user-defined properties of
283    * each atom.
284    * <p>
285    * The format of each command is
286    * 
287    * <pre>
288    * <blockquote> iterate atomspec, p.featureName='value' 
289    * e.g. iterate 4zho//A/23,28-29/CA, p.jv_Metal='Fe'
290    * </blockquote>
291    * </pre>
292    * 
293    * @param featureMap
294    * @return
295    */
296   @Override
297   public List<StructureCommandI> setAttributes(
298           Map<String, Map<Object, AtomSpecModel>> featureMap)
299   {
300     List<StructureCommandI> commands = new ArrayList<>();
301     for (String featureType : featureMap.keySet())
302     {
303       String attributeName = makeAttributeName(featureType);
304
305       /*
306        * todo: clear down existing attributes for this feature?
307        */
308       // commands.add(new StructureCommand("iterate", "all",
309       // "p."+attributeName+"='None'"); //?
310
311       Map<Object, AtomSpecModel> values = featureMap.get(featureType);
312       for (Object value : values.keySet())
313       {
314         /*
315          * for each distinct value recorded for this feature type,
316          * add a command to set the attribute on the mapped residues
317          * Put values in single quotes, encoding any embedded single quotes
318          */
319         AtomSpecModel atomSpecModel = values.get(value);
320         String featureValue = value.toString();
321         featureValue = featureValue.replaceAll("\\'", "&#39;");
322         StructureCommandI cmd = setAttribute(attributeName, featureValue,
323                 atomSpecModel);
324         commands.add(cmd);
325       }
326     }
327
328     return commands;
329   }
330
331   @Override
332   public StructureCommandI openSession(String filepath)
333   {
334     // https://pymolwiki.org/index.php/Load
335     // this version of the command has no dependency on file extension
336     return new StructureCommand("load", filepath, "", "0", "pse");
337   }
338
339   @Override
340   public StructureCommandI closeViewer()
341   {
342     // https://pymolwiki.org/index.php/Quit
343     return CLOSE_PYMOL;
344   }
345
346 }