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