1 package ext.edu.ucsf.rbvi.strucviz2;
5 import java.io.IOException;
6 import java.util.ArrayList;
7 import java.util.Collection;
8 import java.util.HashMap;
12 import org.slf4j.Logger;
13 import org.slf4j.LoggerFactory;
15 import ext.edu.ucsf.rbvi.strucviz2.StructureManager.ModelType;
16 import ext.edu.ucsf.rbvi.strucviz2.port.ListenerThreads;
19 * This object maintains the Chimera communication information.
21 public class ChimeraManager
24 private Process chimera;
26 private ListenerThreads chimeraListenerThreads;
28 private Map<Integer, ChimeraModel> currentModelsMap;
30 private Logger logger = LoggerFactory
31 .getLogger(ext.edu.ucsf.rbvi.strucviz2.ChimeraManager.class);
33 private StructureManager structureManager;
35 public ChimeraManager(StructureManager structureManager)
37 this.structureManager = structureManager;
39 chimeraListenerThreads = null;
40 currentModelsMap = new HashMap<Integer, ChimeraModel>();
44 public List<ChimeraModel> getChimeraModels(String modelName)
46 List<ChimeraModel> models = getChimeraModels(modelName,
48 models.addAll(getChimeraModels(modelName, ModelType.SMILES));
52 public List<ChimeraModel> getChimeraModels(String modelName,
55 List<ChimeraModel> models = new ArrayList<ChimeraModel>();
56 for (ChimeraModel model : currentModelsMap.values())
58 if (modelName.equals(model.getModelName())
59 && modelType.equals(model.getModelType()))
67 public Map<String, List<ChimeraModel>> getChimeraModelsMap()
69 Map<String, List<ChimeraModel>> models = new HashMap<String, List<ChimeraModel>>();
70 for (ChimeraModel model : currentModelsMap.values())
72 String modelName = model.getModelName();
73 if (!models.containsKey(modelName))
75 models.put(modelName, new ArrayList<ChimeraModel>());
77 if (!models.get(modelName).contains(model))
79 models.get(modelName).add(model);
85 public ChimeraModel getChimeraModel(Integer modelNumber,
86 Integer subModelNumber)
88 Integer key = ChimUtils.makeModelKey(modelNumber, subModelNumber);
89 if (currentModelsMap.containsKey(key))
91 return currentModelsMap.get(key);
96 public ChimeraModel getChimeraModel()
98 return currentModelsMap.values().iterator().next();
101 public Collection<ChimeraModel> getChimeraModels()
103 // this method is invoked by the model navigator dialog
104 return currentModelsMap.values();
107 public int getChimeraModelsCount(boolean smiles)
109 // this method is invokes by the model navigator dialog
110 int counter = currentModelsMap.size();
116 for (ChimeraModel model : currentModelsMap.values())
118 if (model.getModelType() == ModelType.SMILES)
126 public boolean hasChimeraModel(Integer modelNubmer)
128 return hasChimeraModel(modelNubmer, 0);
131 public boolean hasChimeraModel(Integer modelNubmer, Integer subModelNumber)
133 return currentModelsMap.containsKey(ChimUtils.makeModelKey(modelNubmer,
137 public void addChimeraModel(Integer modelNumber, Integer subModelNumber,
140 currentModelsMap.put(
141 ChimUtils.makeModelKey(modelNumber, subModelNumber), model);
144 public void removeChimeraModel(Integer modelNumber, Integer subModelNumber)
146 int modelKey = ChimUtils.makeModelKey(modelNumber, subModelNumber);
147 if (currentModelsMap.containsKey(modelKey))
149 currentModelsMap.remove(modelKey);
153 public List<ChimeraModel> openModel(String modelPath, ModelType type)
155 return openModel(modelPath, getFileNameFromPath(modelPath), type);
159 * Overloaded method to allow Jalview to pass in a model name.
166 public List<ChimeraModel> openModel(String modelPath, String modelName,
169 logger.info("chimera open " + modelPath);
171 List<String> response = null;
172 // TODO: [Optional] Handle modbase models
173 if (type == ModelType.MODBASE_MODEL)
175 response = sendChimeraCommand("open modbase:" + modelPath, true);
176 // } else if (type == ModelType.SMILES) {
177 // response = sendChimeraCommand("open smiles:" + modelName, true);
178 // modelName = "smiles:" + modelName;
182 response = sendChimeraCommand("open " + modelPath, true);
184 if (response == null)
186 // something went wrong
187 logger.warn("Could not open " + modelPath);
190 List<ChimeraModel> models = new ArrayList<ChimeraModel>();
191 int[] modelNumbers = null;
192 if (type == ModelType.PDB_MODEL)
194 for (String line : response)
196 if (line.startsWith("#"))
198 modelNumbers = ChimUtils.parseOpenedModelNumber(line);
199 if (modelNumbers != null)
201 int modelNumber = ChimUtils.makeModelKey(modelNumbers[0],
203 if (currentModelsMap.containsKey(modelNumber))
207 ChimeraModel newModel = new ChimeraModel(modelName, type,
208 modelNumbers[0], modelNumbers[1]);
209 currentModelsMap.put(modelNumber, newModel);
210 models.add(newModel);
211 // patch for Jalview - set model name in Chimera
212 sendChimeraCommand("setattr M name " + modelName + " #"
213 + modelNumbers[0], false);
214 // end patch for Jalview
222 // TODO: [Optional] Open smiles from file would fail. Do we need it?
223 // If parsing fails, iterate over all open models to get the right one
224 List<ChimeraModel> openModels = getModelList();
225 for (ChimeraModel openModel : openModels)
227 String openModelName = openModel.getModelName();
228 if (openModelName.endsWith("..."))
230 openModelName = openModelName.substring(0,
231 openModelName.length() - 3);
233 if (modelPath.startsWith(openModelName))
235 openModel.setModelName(modelPath);
236 int modelNumber = ChimUtils
237 .makeModelKey(openModel.getModelNumber(),
238 openModel.getSubModelNumber());
239 if (!currentModelsMap.containsKey(modelNumber))
241 currentModelsMap.put(modelNumber, openModel);
242 models.add(openModel);
248 // assign color and residues to open models
249 for (ChimeraModel newModel : models)
252 Color modelColor = getModelColor(newModel);
253 if (modelColor != null)
255 newModel.setModelColor(modelColor);
258 // Get our properties (default color scheme, etc.)
259 // Make the molecule look decent
260 // chimeraSend("repr stick "+newModel.toSpec());
262 // Create the information we need for the navigator
263 if (type != ModelType.SMILES)
265 addResidues(newModel);
269 sendChimeraCommand("focus", false);
275 * Refactored method to extract the last (or only) element delimited by file
281 private String getFileNameFromPath(String modelPath)
283 String modelName = modelPath;
284 if (modelPath == null)
288 // TODO: [Optional] Convert path to name in a better way
289 if (modelPath.lastIndexOf(File.separator) > 0)
291 modelName = modelPath.substring(modelPath
292 .lastIndexOf(File.separator) + 1);
294 else if (modelPath.lastIndexOf("/") > 0)
296 modelName = modelPath
297 .substring(modelPath.lastIndexOf("/") + 1);
302 public void closeModel(ChimeraModel model)
304 // int model = structure.modelNumber();
305 // int subModel = structure.subModelNumber();
306 // Integer modelKey = makeModelKey(model, subModel);
308 logger.info("chimera close model " + model.getModelName());
309 if (currentModelsMap.containsKey(ChimUtils.makeModelKey(
310 model.getModelNumber(), model.getSubModelNumber())))
312 sendChimeraCommand("close " + model.toSpec(), false);
313 // currentModelNamesMap.remove(model.getModelName());
314 currentModelsMap.remove(ChimUtils.makeModelKey(
315 model.getModelNumber(), model.getSubModelNumber()));
316 // selectionList.remove(chimeraModel);
320 logger.warn("Could not find model " + model.getModelName()
326 public void startListening()
328 sendChimeraCommand("listen start models; listen start select", false);
331 public void stopListening()
333 sendChimeraCommand("listen stop models; listen stop select", false);
337 * Select something in Chimera
340 * the selection command to pass to Chimera
342 public void select(String command)
344 sendChimeraCommand("listen stop select; " + command
345 + "; listen start select", false);
350 sendChimeraCommand("focus", false);
353 public void clearOnChimeraExit()
356 currentModelsMap.clear();
357 chimeraListenerThreads = null;
358 structureManager.clearOnChimeraExit();
361 public void exitChimera()
363 if (isChimeraLaunched() && chimera != null)
365 sendChimeraCommand("stop really", false);
369 } catch (Exception ex)
374 clearOnChimeraExit();
377 public Map<Integer, ChimeraModel> getSelectedModels()
379 Map<Integer, ChimeraModel> selectedModelsMap = new HashMap<Integer, ChimeraModel>();
380 List<String> chimeraReply = sendChimeraCommand(
381 "list selection level molecule", true);
382 if (chimeraReply != null)
384 for (String modelLine : chimeraReply)
386 ChimeraModel chimeraModel = new ChimeraModel(modelLine);
387 Integer modelKey = ChimUtils.makeModelKey(
388 chimeraModel.getModelNumber(),
389 chimeraModel.getSubModelNumber());
390 selectedModelsMap.put(modelKey, chimeraModel);
393 return selectedModelsMap;
396 public List<String> getSelectedResidueSpecs()
398 List<String> selectedResidues = new ArrayList<String>();
399 List<String> chimeraReply = sendChimeraCommand(
400 "list selection level residue", true);
401 if (chimeraReply != null)
403 for (String inputLine : chimeraReply)
405 String[] inputLineParts = inputLine.split("\\s+");
406 if (inputLineParts.length == 5)
408 selectedResidues.add(inputLineParts[2]);
412 return selectedResidues;
415 public void getSelectedResidues(
416 Map<Integer, ChimeraModel> selectedModelsMap)
418 List<String> chimeraReply = sendChimeraCommand(
419 "list selection level residue", true);
420 if (chimeraReply != null)
422 for (String inputLine : chimeraReply)
424 ChimeraResidue r = new ChimeraResidue(inputLine);
425 Integer modelKey = ChimUtils.makeModelKey(r.getModelNumber(),
426 r.getSubModelNumber());
427 if (selectedModelsMap.containsKey(modelKey))
429 ChimeraModel model = selectedModelsMap.get(modelKey);
437 * Return the list of ChimeraModels currently open. Warning: if smiles model
438 * name too long, only part of it with "..." is printed.
441 * @return List of ChimeraModel's
443 // TODO: [Optional] Handle smiles names in a better way in Chimera?
444 public List<ChimeraModel> getModelList()
446 List<ChimeraModel> modelList = new ArrayList<ChimeraModel>();
447 List<String> list = sendChimeraCommand("list models type molecule",
451 for (String modelLine : list)
453 ChimeraModel chimeraModel = new ChimeraModel(modelLine);
454 modelList.add(chimeraModel);
461 * Return the list of depiction presets available from within Chimera. Chimera
462 * will return the list as a series of lines with the format: Preset type
463 * number "description"
465 * @return list of presets
467 public List<String> getPresets()
469 ArrayList<String> presetList = new ArrayList<String>();
470 List<String> output = sendChimeraCommand("preset list", true);
473 for (String preset : output)
475 preset = preset.substring(7); // Skip over the "Preset"
476 preset = preset.replaceFirst("\"", "(");
477 preset = preset.replaceFirst("\"", ")");
478 // string now looks like: type number (description)
479 presetList.add(preset);
485 public boolean isChimeraLaunched()
487 boolean launched = false;
493 // if we get here, process has ended
494 } catch (IllegalThreadStateException e)
496 // ok - not yet terminated
503 public boolean launchChimera(List<String> chimeraPaths)
505 // Do nothing if Chimera is already launched
506 if (isChimeraLaunched())
511 // Try to launch Chimera (eventually using one of the possible paths)
512 String error = "Error message: ";
513 String workingPath = "";
514 // iterate over possible paths for starting Chimera
515 for (String chimeraPath : chimeraPaths)
517 File path = new File(chimeraPath);
518 if (!path.canExecute())
520 error += "File '" + path + "' does not exist.\n";
525 List<String> args = new ArrayList<String>();
526 args.add(chimeraPath);
528 args.add("ReadStdin");
529 ProcessBuilder pb = new ProcessBuilder(args);
530 chimera = pb.start();
532 workingPath = chimeraPath;
533 logger.info("Strarting " + chimeraPath);
535 } catch (Exception e)
537 // Chimera could not be started
538 error += e.getMessage();
541 // If no error, then Chimera was launched successfully
542 if (error.length() == 0)
544 // Initialize the listener threads
545 chimeraListenerThreads = new ListenerThreads(chimera,
547 chimeraListenerThreads.start();
548 // structureManager.initChimTable();
549 structureManager.setChimeraPathProperty(workingPath);
550 // TODO: [Optional] Check Chimera version and show a warning if below 1.8
551 // Ask Chimera to give us updates
556 // Tell the user that Chimera could not be started because of an error
562 * Determine the color that Chimera is using for this model.
565 * the ChimeraModel we want to get the Color for
566 * @return the default model Color for this model in Chimera
568 public Color getModelColor(ChimeraModel model)
570 List<String> colorLines = sendChimeraCommand(
571 "list model spec " + model.toSpec() + " attribute color", true);
572 if (colorLines == null || colorLines.size() == 0)
576 return ChimUtils.parseModelColor(colorLines.get(0));
581 * Get information about the residues associated with a model. This uses the
582 * Chimera listr command. We don't return the resulting residues, but we add
583 * the residues to the model.
586 * the ChimeraModel to get residue information for
589 public void addResidues(ChimeraModel model)
591 int modelNumber = model.getModelNumber();
592 int subModelNumber = model.getSubModelNumber();
593 // Get the list -- it will be in the reply log
594 List<String> reply = sendChimeraCommand(
595 "list residues spec " + model.toSpec(), true);
600 for (String inputLine : reply)
602 ChimeraResidue r = new ChimeraResidue(inputLine);
603 if (r.getModelNumber() == modelNumber
604 || r.getSubModelNumber() == subModelNumber)
611 public List<String> getAttrList()
613 List<String> attributes = new ArrayList<String>();
614 final List<String> reply = sendChimeraCommand("list resattr", true);
617 for (String inputLine : reply)
619 String[] lineParts = inputLine.split("\\s");
620 if (lineParts.length == 2 && lineParts[0].equals("resattr"))
622 attributes.add(lineParts[1]);
629 public Map<ChimeraResidue, Object> getAttrValues(String aCommand,
632 Map<ChimeraResidue, Object> values = new HashMap<ChimeraResidue, Object>();
633 final List<String> reply = sendChimeraCommand("list residue spec "
634 + model.toSpec() + " attribute " + aCommand, true);
637 for (String inputLine : reply)
639 String[] lineParts = inputLine.split("\\s");
640 if (lineParts.length == 5)
642 ChimeraResidue residue = ChimUtils
643 .getResidue(lineParts[2], model);
644 String value = lineParts[4];
647 if (value.equals("None"))
651 if (value.equals("True") || value.equals("False"))
653 values.put(residue, Boolean.valueOf(value));
658 Double doubleValue = Double.valueOf(value);
659 values.put(residue, doubleValue);
660 } catch (NumberFormatException ex)
662 values.put(residue, value);
671 private volatile boolean busy = false;
674 * Send a command to Chimera.
677 * Command string to be send.
679 * Flag indicating whether the method should return the reply from
681 * @return List of Strings corresponding to the lines in the Chimera reply or
684 public List<String> sendChimeraCommand(String command, boolean reply)
686 if (!isChimeraLaunched())
690 // TODO do we need a maximum wait time before aborting?
696 } catch (InterruptedException q)
704 chimeraListenerThreads.clearResponse(command);
705 String text = command.concat("\n");
706 // System.out.println("send command to chimera: " + text);
710 chimera.getOutputStream().write(text.getBytes());
711 chimera.getOutputStream().flush();
712 } catch (IOException e)
714 // logger.info("Unable to execute command: " + text);
715 // logger.info("Exiting...");
716 logger.warn("Unable to execute command: " + text);
717 logger.warn("Exiting...");
718 clearOnChimeraExit();
727 List<String> rsp = chimeraListenerThreads.getResponse(command);
736 public StructureManager getStructureManager()
738 return structureManager;
741 public boolean isBusy()