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.jmol;
23 import java.awt.Container;
24 import java.awt.event.ComponentEvent;
25 import java.awt.event.ComponentListener;
28 import java.util.ArrayList;
29 import java.util.List;
31 import java.util.StringTokenizer;
32 import java.util.Vector;
34 import javax.swing.SwingUtilities;
36 import org.jmol.adapter.smarter.SmarterJmolAdapter;
37 import org.jmol.api.JmolAppConsoleInterface;
38 import org.jmol.api.JmolSelectionListener;
39 import org.jmol.api.JmolStatusListener;
40 import org.jmol.api.JmolViewer;
41 import org.jmol.c.CBK;
42 import org.jmol.modelset.Atom;
43 import org.jmol.modelset.Model;
44 import org.jmol.viewer.Viewer;
46 import jalview.api.AlignmentViewPanel;
47 import jalview.api.FeatureRenderer;
48 import jalview.api.FeatureSettingsModelI;
49 import jalview.api.SequenceRenderer;
50 import jalview.bin.Cache;
51 import jalview.datamodel.PDBEntry;
52 import jalview.datamodel.SequenceI;
53 import jalview.gui.AppJmol;
54 import jalview.gui.IProgressIndicator;
55 import jalview.gui.StructureViewer.ViewerType;
56 import jalview.io.DataSourceType;
57 import jalview.io.StructureFile;
58 import jalview.structure.AtomSpec;
59 import jalview.structure.StructureCommand;
60 import jalview.structure.StructureCommandI;
61 import jalview.structure.StructureSelectionManager;
62 import jalview.structures.models.AAStructureBindingModel;
63 import jalview.ws.dbsources.Pdb;
64 import javajs.util.BS;
66 public abstract class JalviewJmolBinding extends AAStructureBindingModel
67 implements JmolStatusListener, JmolSelectionListener,
70 private String lastMessage;
73 * when true, try to search the associated datamodel for sequences that are
74 * associated with any unknown structures in the Jmol view.
76 private boolean associateNewStructs = false;
78 private Vector<String> atomsPicked = new Vector<>();
80 private String lastCommand;
82 private boolean loadedInline;
84 private StringBuffer resetLastRes = new StringBuffer();
86 public Viewer jmolViewer;
88 public JalviewJmolBinding(StructureSelectionManager ssm,
89 PDBEntry[] pdbentry, SequenceI[][] sequenceIs,
90 DataSourceType protocol)
92 super(ssm, pdbentry, sequenceIs, protocol);
93 setStructureCommands(new JmolCommands());
95 * viewer = JmolViewer.allocateViewer(renderPanel, new SmarterJmolAdapter(),
96 * "jalviewJmol", ap.av.applet .getDocumentBase(), ap.av.applet.getCodeBase(),
99 * jmolpopup = JmolPopup.newJmolPopup(viewer, true, "Jmol", true);
103 public JalviewJmolBinding(StructureSelectionManager ssm,
104 SequenceI[][] seqs, Viewer theViewer)
108 jmolViewer = theViewer;
109 jmolViewer.setJmolStatusListener(this);
110 jmolViewer.addSelectionListener(this);
111 setStructureCommands(new JmolCommands());
115 * construct a title string for the viewer window based on the data jalview
120 public String getViewerTitle()
122 return getViewerTitle("Jmol", true);
125 private String jmolScript(String script)
127 Cache.log.debug(">>Jmol>> " + script);
128 String s = jmolViewer.evalStringQuiet(script); // scriptWait(script); BH
129 Cache.log.debug("<<Jmol<< " + s);
135 public List<String> executeCommand(StructureCommandI command,
142 String cmd = command.getCommand();
144 if (lastCommand == null || !lastCommand.equals(cmd))
146 jmolScript(cmd + "\n");
153 public void createImage(String file, String type, int quality)
155 System.out.println("JMOL CREATE IMAGE");
159 public String createImage(String fileName, String type,
160 Object textOrBytes, int quality)
162 System.out.println("JMOL CREATE IMAGE");
167 public String eval(String strEval)
169 // System.out.println(strEval);
170 // "# 'eval' is implemented only for the applet.";
174 // End StructureListener
175 // //////////////////////////
178 public float[][] functionXY(String functionName, int x, int y)
184 public float[][][] functionXYZ(String functionName, int nx, int ny,
187 // TODO Auto-generated method stub
192 * map between index of model filename returned from getPdbFile and the first
193 * index of models from this file in the viewer. Note - this is not trimmed -
194 * use getPdbFile to get number of unique models.
196 private int _modelFileNameMap[];
199 public synchronized String[] getStructureFiles()
201 if (jmolViewer == null)
203 return new String[0];
206 if (modelFileNames == null)
208 int modelCount = jmolViewer.ms.mc;
209 String filePath = null;
210 List<String> mset = new ArrayList<>();
211 for (int i = 0; i < modelCount; ++i)
214 * defensive check for null as getModelFileName can return null even when model
217 filePath = jmolViewer.ms.getModelFileName(i);
218 if (filePath != null && !mset.contains(filePath))
225 modelFileNames = mset.toArray(new String[mset.size()]);
229 return modelFileNames;
233 * map from string to applet
236 public Map<String, Object> getRegistryInfo()
238 // TODO Auto-generated method stub
242 // ///////////////////////////////
243 // JmolStatusListener
245 public void handlePopupMenu(int x, int y)
247 // jmolpopup.show(x, y);
248 // jmolpopup.jpiShow(x, y);
251 private List<AtomSpec> lastHighlight = new ArrayList<AtomSpec>();
252 private List<AtomSpec> resetLastHighlight = new ArrayList<AtomSpec>();
254 * Highlight zero, one or more atoms on the structure
257 public void highlightAtoms(List<AtomSpec> atoms)
261 // make sure we ignore the next few selection events for these
262 lastHighlight.addAll(resetLastHighlight);
263 lastHighlight.addAll(atoms);
264 resetLastHighlight.clear();
265 resetLastHighlight.addAll(atoms);
266 if (resetLastRes.length() > 0)
268 jmolScript(resetLastRes.toString());
269 resetLastRes.setLength(0);
271 for (AtomSpec atom : atoms)
273 highlightAtom(atom.getAtomIndex(), atom.getPdbResNum(),
274 atom.getChain(), atom.getPdbFile());
280 public void highlightAtom(int atomIndex, int pdbResNum, String chain,
283 String modelId = getModelIdForFile(pdbfile);
284 if (modelId.isEmpty())
291 StringBuilder selection = new StringBuilder(32);
292 StringBuilder cmd = new StringBuilder(64);
293 // save existing selection.
295 selection.append("select ").append(String.valueOf(pdbResNum));
296 selection.append(":");
297 if (!chain.equals(" "))
299 selection.append(chain);
301 selection.append(" /").append(modelId);
303 cmd.append(selection).append(";wireframe 100;").append(selection)
304 .append(" and not hetero;").append("spacefill 200;select none");
306 resetLastRes.append(selection).append(";wireframe 0;").append(selection)
307 .append(" and not hetero; spacefill 0;");
309 jmolScript(cmd.toString());
313 private boolean debug = true;
315 private void jmolHistory(boolean enable)
317 jmolScript("History " + ((debug || enable) ? "on" : "off"));
320 public void loadInline(String string)
324 // viewer.loadInline(strModel, isAppend);
326 // construct fake fullPathName and fileName so we can identify the file
328 // Then, construct pass a reader for the string to Jmol.
329 // ((org.jmol.Viewer.Viewer) viewer).loadModelFromFile(fullPathName,
330 // fileName, null, reader, false, null, null, 0);
331 jmolViewer.openStringInline(string);
334 protected void mouseOverStructure(int atomIndex, final String strInfo)
337 int alocsep = strInfo.indexOf("^");
338 int mdlSep = strInfo.indexOf("/");
339 int chainSeparator = strInfo.indexOf(":"), chainSeparator1 = -1;
341 if (chainSeparator == -1)
343 chainSeparator = strInfo.indexOf(".");
344 if (mdlSep > -1 && mdlSep < chainSeparator)
346 chainSeparator1 = chainSeparator;
347 chainSeparator = mdlSep;
350 // handle insertion codes
353 pdbResNum = Integer.parseInt(
354 strInfo.substring(strInfo.indexOf("]") + 1, alocsep));
359 pdbResNum = Integer.parseInt(
360 strInfo.substring(strInfo.indexOf("]") + 1, chainSeparator));
364 if (strInfo.indexOf(":") > -1)
366 chainId = strInfo.substring(strInfo.indexOf(":") + 1,
367 strInfo.indexOf("."));
374 String pdbfilename = modelFileNames[0]; // default is first model
377 if (chainSeparator1 == -1)
379 chainSeparator1 = strInfo.indexOf(".", mdlSep);
381 String mdlId = (chainSeparator1 > -1)
382 ? strInfo.substring(mdlSep + 1, chainSeparator1)
383 : strInfo.substring(mdlSep + 1);
386 // recover PDB filename for the model hovered over.
387 int mnumber = Integer.valueOf(mdlId).intValue() - 1;
388 if (_modelFileNameMap != null)
390 int _mp = _modelFileNameMap.length - 1;
392 while (mnumber < _modelFileNameMap[_mp])
396 pdbfilename = modelFileNames[_mp];
400 if (mnumber >= 0 && mnumber < modelFileNames.length)
402 pdbfilename = modelFileNames[mnumber];
405 if (pdbfilename == null)
407 pdbfilename = new File(jmolViewer.ms.getModelFileName(mnumber))
411 } catch (Exception e)
417 * highlight position on alignment(s); if some text is returned, show this as a
418 * second line on the structure hover tooltip
420 String label = getSsm().mouseOverStructure(pdbResNum, chainId,
424 // change comma to pipe separator (newline token for Jmol)
425 label = label.replace(',', '|');
426 StringTokenizer toks = new StringTokenizer(strInfo, " ");
427 StringBuilder sb = new StringBuilder();
428 sb.append("select ").append(String.valueOf(pdbResNum)).append(":")
429 .append(chainId).append("/1");
430 sb.append(";set hoverLabel \"").append(toks.nextToken()).append(" ")
431 .append(toks.nextToken());
432 sb.append("|").append(label).append("\"");
433 executeCommand(new StructureCommand(sb.toString()), false);
437 public void notifyAtomHovered(int atomIndex, String strInfo, String data)
439 if (strInfo.equals(lastMessage))
443 lastMessage = strInfo;
446 System.err.println("Ignoring additional hover info: " + data
447 + " (other info: '" + strInfo + "' pos " + atomIndex + ")");
449 mouseOverStructure(atomIndex, strInfo);
453 * { if (history != null && strStatus != null &&
454 * !strStatus.equals("Script completed")) { history.append("\n" + strStatus); }
458 public void notifyAtomPicked(int atomIndex, String strInfo,
462 * this implements the toggle label behaviour copied from the original
463 * structure viewer, mc_view
467 System.err.println("Ignoring additional pick data string " + strData);
469 int chainSeparator = strInfo.indexOf(":");
471 if (chainSeparator == -1)
473 chainSeparator = strInfo.indexOf(".");
476 String picked = strInfo.substring(strInfo.indexOf("]") + 1,
478 String mdlString = "";
479 if ((p = strInfo.indexOf(":")) > -1)
481 picked += strInfo.substring(p, strInfo.indexOf("."));
484 if ((p = strInfo.indexOf("/")) > -1)
486 mdlString += strInfo.substring(p, strInfo.indexOf(" #"));
488 picked = "((" + picked + ".CA" + mdlString + ")|(" + picked + ".P"
492 if (!atomsPicked.contains(picked))
494 jmolScript("select " + picked + ";label %n %r:%c");
495 atomsPicked.addElement(picked);
499 jmolViewer.evalString("select " + picked + ";label off");
500 atomsPicked.removeElement(picked);
503 // TODO: in application this happens
505 // if (scriptWindow != null)
507 // scriptWindow.sendConsoleMessage(strInfo);
508 // scriptWindow.sendConsoleMessage("\n");
514 public void notifyCallback(CBK type, Object[] data)
517 * ensure processed in AWT thread to avoid risk of deadlocks
519 SwingUtilities.invokeLater(new Runnable()
525 processCallback(type, data);
531 * Processes one callback notification from Jmol
536 protected void processCallback(CBK type, Object[] data)
543 notifyFileLoaded((String) data[1], (String) data[2],
544 (String) data[3], (String) data[4],
545 ((Integer) data[5]).intValue());
549 notifyAtomPicked(((Integer) data[2]).intValue(), (String) data[1],
551 // also highlight in alignment
552 // deliberate fall through
554 notifyAtomHovered(((Integer) data[2]).intValue(), (String) data[1],
558 notifyScriptTermination((String) data[2],
559 ((Integer) data[3]).intValue());
562 sendConsoleEcho((String) data[1]);
566 (data == null) ? ((String) null) : (String) data[1]);
569 // System.err.println("Ignoring error callback.");
580 "Unhandled callback " + type + " " + data[1].toString());
583 } catch (Exception e)
585 System.err.println("Squashed Jmol callback handler error:");
591 public boolean notifyEnabled(CBK callbackPick)
593 switch (callbackPick)
609 // incremented every time a load notification is successfully handled -
610 // lightweight mechanism for other threads to detect when they can start
611 // referrring to new structures.
612 private long loadNotifiesHandled = 0;
614 public long getLoadNotifiesHandled()
616 return loadNotifiesHandled;
619 public void notifyFileLoaded(String fullPathName, String fileName2,
620 String modelName, String errorMsg, int modelParts)
622 if (errorMsg != null)
624 fileLoadingError = errorMsg;
628 // TODO: deal sensibly with models loaded inLine:
629 // modelName will be null, as will fullPathName.
631 // the rest of this routine ignores the arguments, and simply interrogates
632 // the Jmol view to find out what structures it contains, and adds them to
633 // the structure selection manager.
634 fileLoadingError = null;
635 String[] oldmodels = modelFileNames;
636 modelFileNames = null;
637 boolean notifyLoaded = false;
638 String[] modelfilenames = getStructureFiles();
639 if (modelfilenames == null)
641 // Jmol is still loading files!
644 // first check if we've lost any structures
645 if (oldmodels != null && oldmodels.length > 0)
648 for (int i = 0; i < oldmodels.length; i++)
650 for (int n = 0; n < modelfilenames.length; n++)
652 if (modelfilenames[n] == oldmodels[i])
658 if (oldmodels[i] != null)
665 String[] oldmfn = new String[oldm];
667 for (int i = 0; i < oldmodels.length; i++)
669 if (oldmodels[i] != null)
671 oldmfn[oldm++] = oldmodels[i];
674 // deregister the Jmol instance for these structures - we'll add
675 // ourselves again at the end for the current structure set.
676 getSsm().removeStructureViewerListener(this, oldmfn);
680 for (int modelnum = 0; modelnum < modelfilenames.length; modelnum++)
682 String fileName = modelfilenames[modelnum];
683 boolean foundEntry = false;
684 StructureFile pdb = null;
685 String pdbfile = null;
686 // model was probably loaded inline - so check the pdb file hashcode
689 // calculate essential attributes for the pdb data imported inline.
690 // prolly need to resolve modelnumber properly - for now just use our
692 pdbfile = jmolViewer.getData(
693 "" + (1 + _modelFileNameMap[modelnum]) + ".0", "PDB");
695 // search pdbentries and sequences to find correct pdbentry for this
697 for (int pe = 0; pe < getPdbCount(); pe++)
699 boolean matches = false;
700 addSequence(pe, getSequence()[pe]);
701 if (fileName == null)
704 // see JAL-623 - need method of matching pasted data up
706 pdb = getSsm().setMapping(getSequence()[pe], getChains()[pe],
707 pdbfile, DataSourceType.PASTE, getIProgressIndicator());
708 getPdbEntry(modelnum).setFile("INLINE" + pdb.getId());
715 File fl = new File(getPdbEntry(pe).getFile());
716 matches = fl.equals(new File(fileName));
720 // TODO: Jmol can in principle retrieve from CLASSLOADER but
723 // to be tested. See mantis bug
724 // https://mantis.lifesci.dundee.ac.uk/view.php?id=36605
725 DataSourceType protocol = DataSourceType.URL;
730 protocol = DataSourceType.FILE;
732 } catch (Exception e)
737 // Explicitly map to the filename used by Jmol ;
738 pdb = getSsm().setMapping(getSequence()[pe], getChains()[pe],
739 fileName, protocol, getIProgressIndicator());
740 // pdbentry[pe].getFile(), protocol);
746 stashFoundChains(pdb, fileName);
751 if (!foundEntry && associateNewStructs)
753 // this is a foreign pdb file that jalview doesn't know about - add
754 // it to the dataset and try to find a home - either on a matching
755 // sequence or as a new sequence.
756 String pdbcontent = jmolViewer.getData("/" + (modelnum + 1) + ".1",
758 // parse pdb file into a chain, etc.
759 // locate best match for pdb in associated views and add mapping to
761 // if properly registered then
767 // so finally, update the jmol bits and pieces
768 // if (jmolpopup != null)
770 // // potential for deadlock here:
771 // // jmolpopup.updateComputedMenus();
773 if (!isLoadingFromArchive())
776 "model *; select backbone;restrict;cartoon;wireframe off;spacefill off");
778 // register ourselves as a listener and notify the gui that it needs to
780 getSsm().addStructureViewerListener(this);
783 FeatureRenderer fr = getFeatureRenderer(null);
786 FeatureSettingsModelI colours = new Pdb().getFeatureColourScheme();
787 ((AppJmol) getViewer()).getAlignmentPanel().av
788 .applyFeaturesStyle(colours);
791 loadNotifiesHandled++;
793 setLoadingFromArchive(false);
796 protected IProgressIndicator getIProgressIndicator()
801 public void notifyNewPickingModeMeasurement(int iatom, String strMeasure)
803 notifyAtomPicked(iatom, strMeasure, null);
806 public abstract void notifyScriptTermination(String strStatus,
810 * display a message echoed from the jmol viewer
814 public abstract void sendConsoleEcho(String strEcho); /*
815 * { showConsole(true);
817 * history.append("\n" + strEcho); }
820 // /End JmolStatusListener
821 // /////////////////////////////
825 * status message - usually the response received after a script
828 public abstract void sendConsoleMessage(String strStatus);
831 public void setCallbackFunction(String callbackType,
832 String callbackFunction)
834 System.err.println("Ignoring set-callback request to associate "
835 + callbackType + " with function " + callbackFunction);
839 public void showHelp()
841 showUrl("http://wiki.jmol.org"
842 // BH 2018 "http://jmol.sourceforge.net/docs/JmolUserGuide/"
847 * open the URL somehow
851 public abstract void showUrl(String url, String target);
854 * called to show or hide the associated console window container.
858 public abstract void showConsole(boolean show);
860 public static Viewer getJmolData(JmolParser jmolParser)
862 return (Viewer) JmolViewer.allocateViewer(null, null, null, null, null,
863 "-x -o -n", jmolParser);
872 * - when true will initialise jmol's file IO system (should be false
875 * @param documentBase
877 * @param commandOptions
879 public void allocateViewer(Container renderPanel, boolean jmolfileio,
880 String htmlName, URL documentBase, URL codeBase,
881 String commandOptions)
883 allocateViewer(renderPanel, jmolfileio, htmlName, documentBase,
884 codeBase, commandOptions, null, null);
891 * - when true will initialise jmol's file IO system (should be false
894 * @param documentBase
896 * @param commandOptions
897 * @param consolePanel
898 * - panel to contain Jmol console
899 * @param buttonsToShow
900 * - buttons to show on the console, in order
902 public void allocateViewer(Container renderPanel, boolean jmolfileio,
903 String htmlName, URL documentBase, URL codeBase,
904 String commandOptions, final Container consolePanel,
905 String buttonsToShow)
908 System.err.println("Allocating Jmol Viewer: " + commandOptions);
910 if (commandOptions == null)
914 jmolViewer = (Viewer) JmolViewer.allocateViewer(renderPanel,
915 (jmolfileio ? new SmarterJmolAdapter() : null),
916 htmlName + ((Object) this).toString(), documentBase, codeBase,
917 commandOptions, this);
919 jmolViewer.setJmolStatusListener(this); // extends JmolCallbackListener
921 jmolViewer.addSelectionListener(new JmolSelectionListener()
925 public void selectionChanged(BS arg0)
927 if (!isLoadingFinished())
931 List<AtomSpec> lastH = lastHighlight;
932 lastHighlight = new ArrayList<AtomSpec>();
933 final AtomSpec[] lastHighlighted = lastH.toArray(new AtomSpec[0]); // copy of the int[] array
934 SwingUtilities.invokeLater(new Runnable() {
938 processselectionChanged(arg0, lastHighlighted);
945 console = createJmolConsole(consolePanel, buttonsToShow);
946 } catch (Throwable e)
948 System.err.println("Could not create Jmol application console. "
952 if (consolePanel != null)
954 consolePanel.addComponentListener(this);
960 protected void processselectionChanged(BS arg0, AtomSpec[] lastHighlighted)
962 int atom = arg0.nextSetBit(0);
963 int modelNo=-1,resNo=-1;
964 String chainCode="NOCHAIN";
965 List<AtomSpec> toSelect = new ArrayList<AtomSpec>();
966 while (atom>-1 && arg0.get(atom)){
968 Model atomModel = jmolViewer.getModelForAtomIndex(atom);
969 Atom firstAtom = atomModel.ms.getAtom(atom);
970 String chcode = firstAtom.getChainIDStr();
971 if (atomModel.modelIndex!=modelNo || resNo != firstAtom.getResno() || !chainCode.equals(chcode))
973 resNo = firstAtom.getResno();
974 modelNo = atomModel.modelIndex;
976 boolean ignore=false;
977 String modelFile = getPdbEntry(modelNo).getFile();
978 if (lastHighlighted!=null)
981 for (AtomSpec hgh:lastHighlighted)
983 if (hgh.getPdbResNum() == resNo && chainCode.equals(hgh.getChain()) && hgh.getPdbFile().equals(modelFile))
992 AtomSpec atomspec = new AtomSpec(modelFile, chainCode, resNo, atomModel.firstAtomIndex);
993 toSelect.add(atomspec);
996 // look for next selected atom
997 atom = arg0.nextSetBit(++atom);
999 if (toSelect.size()>0 || arg0.isEmpty())
1001 // if we have ignored everything we don't clear the existing highlight
1002 getSsm().mouseOverStructure(toSelect);
1007 protected abstract JmolAppConsoleInterface createJmolConsole(
1008 Container consolePanel, String buttonsToShow);
1010 // BH 2018 -- Jmol console is not working due to problems with styled
1013 protected org.jmol.api.JmolAppConsoleInterface console = null;
1016 public int[] resizeInnerPanel(String data)
1018 // Jalview doesn't honour resize panel requests
1025 protected void closeConsole()
1027 if (console != null)
1031 console.setVisible(false);
1034 } catch (Exception x)
1043 * ComponentListener method
1046 public void componentMoved(ComponentEvent e)
1051 * ComponentListener method
1054 public void componentResized(ComponentEvent e)
1059 * ComponentListener method
1062 public void componentShown(ComponentEvent e)
1068 * ComponentListener method
1071 public void componentHidden(ComponentEvent e)
1077 protected String getModelIdForFile(String pdbFile)
1079 if (modelFileNames == null)
1083 for (int i = 0; i < modelFileNames.length; i++)
1085 if (modelFileNames[i].equalsIgnoreCase(pdbFile))
1087 return String.valueOf(i + 1);
1094 protected ViewerType getViewerType()
1096 return ViewerType.JMOL;
1100 protected String getModelId(int pdbfnum, String file)
1102 return String.valueOf(pdbfnum + 1);
1106 * Returns ".spt" - the Jmol session file extension
1109 * @see https://chemapps.stolaf.edu/jmol/docs/#writemodel
1112 public String getSessionFileExtension()
1118 public void selectionChanged(BS arg0)
1120 // TODO Auto-generated method stub
1125 public SequenceRenderer getSequenceRenderer(AlignmentViewPanel avp)
1127 return new jalview.gui.SequenceRenderer(avp.getAlignViewport());
1131 public String getHelpURL()
1133 return "http://wiki.jmol.org"; // BH 2018