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