View Javadoc
1   // ******************************************************************************
2   //
3   // Title:       Force Field X.
4   // Description: Force Field X - Software for Molecular Biophysics.
5   // Copyright:   Copyright (c) Michael J. Schnieders 2001-2025.
6   //
7   // This file is part of Force Field X.
8   //
9   // Force Field X is free software; you can redistribute it and/or modify it
10  // under the terms of the GNU General Public License version 3 as published by
11  // the Free Software Foundation.
12  //
13  // Force Field X is distributed in the hope that it will be useful, but WITHOUT
14  // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15  // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
16  // details.
17  //
18  // You should have received a copy of the GNU General Public License along with
19  // Force Field X; if not, write to the Free Software Foundation, Inc., 59 Temple
20  // Place, Suite 330, Boston, MA 02111-1307 USA
21  //
22  // Linking this library statically or dynamically with other modules is making a
23  // combined work based on this library. Thus, the terms and conditions of the
24  // GNU General Public License cover the whole combination.
25  //
26  // As a special exception, the copyright holders of this library give you
27  // permission to link this library with independent modules to produce an
28  // executable, regardless of the license terms of these independent modules, and
29  // to copy and distribute the resulting executable under terms of your choice,
30  // provided that you also meet, for each linked independent module, the terms
31  // and conditions of the license of that module. An independent module is a
32  // module which is not derived from or based on this library. If you modify this
33  // library, you may extend this exception to your version of the library, but
34  // you are not obligated to do so. If you do not wish to do so, delete this
35  // exception statement from your version.
36  //
37  // ******************************************************************************
38  package ffx.ui;
39  
40  import ffx.algorithms.AlgorithmFunctions;
41  import ffx.algorithms.AlgorithmListener;
42  import ffx.algorithms.Terminatable;
43  import ffx.algorithms.cli.AlgorithmsCommand;
44  import ffx.algorithms.dynamics.MolecularDynamics;
45  import ffx.algorithms.dynamics.integrators.IntegratorEnum;
46  import ffx.algorithms.dynamics.thermostats.ThermostatEnum;
47  import ffx.algorithms.optimize.Minimize;
48  import ffx.numerics.Potential;
49  import ffx.potential.ForceFieldEnergy;
50  import ffx.potential.MolecularAssembly;
51  import ffx.potential.bonded.MSNode;
52  import ffx.potential.bonded.RendererCache.ColorModel;
53  import ffx.potential.bonded.RendererCache.ViewModel;
54  import ffx.potential.cli.PotentialCommand;
55  import ffx.potential.utils.PotentialsFunctions;
56  import ffx.utilities.Console;
57  import ffx.utilities.FFXCommand;
58  import ffx.utilities.FFXBinding;
59  import ffx.utilities.GroovyFileFilter;
60  import org.codehaus.groovy.runtime.MethodClosure;
61  import org.graalvm.polyglot.Context;
62  import org.graalvm.polyglot.Engine;
63  import org.graalvm.polyglot.Language;
64  import org.graalvm.polyglot.Source;
65  import org.graalvm.polyglot.Value;
66  import org.graalvm.polyglot.proxy.ProxyArray;
67  
68  import javax.swing.ImageIcon;
69  import javax.swing.JFrame;
70  import javax.swing.JOptionPane;
71  import javax.swing.JScrollPane;
72  import javax.swing.JSplitPane;
73  import javax.swing.JTextPane;
74  import javax.swing.JViewport;
75  import javax.swing.SwingUtilities;
76  import javax.swing.text.Style;
77  import javax.swing.text.StyleConstants;
78  import javax.swing.text.StyleContext;
79  import javax.swing.text.StyledDocument;
80  import java.awt.Color;
81  import java.awt.Component;
82  import java.awt.Dimension;
83  import java.awt.EventQueue;
84  import java.awt.Menu;
85  import java.awt.MenuBar;
86  import java.awt.Point;
87  import java.awt.Rectangle;
88  import java.io.File;
89  import java.net.URL;
90  import java.nio.file.Path;
91  import java.nio.file.Paths;
92  import java.util.ArrayList;
93  import java.util.EventObject;
94  import java.util.List;
95  import java.util.Map;
96  import java.util.logging.Level;
97  import java.util.logging.Logger;
98  
99  /**
100  * The ModelingShell is used to script Multiscale Modeling Routines via the Groovy scripting
101  * language. Functionality available through the modeling shell includes the Force Field X API, Java
102  * API and Groovy extensions.
103  *
104  * @author Michael J. Schnieders
105  */
106 public class ModelingShell extends Console implements AlgorithmListener {
107 
108   /**
109    * The logger for this class.
110    */
111   private static final Logger logger = Logger.getLogger(ModelingShell.class.getName());
112   private static final double toSeconds = 1.0e-9;
113 
114   /**
115    * A reference to the main application container.
116    */
117   private final MainPanel mainPanel;
118   /**
119    * The flag headless is true for the CLI and false for the GUI.
120    */
121   private final boolean headless;
122   /**
123    * The flag interrupted is true if a script is running and the user requests it be canceled.
124    */
125   private boolean interrupted;
126   /**
127    * An algorithm that implements the Terminatable interface can be cleanly terminated before
128    * completion. For example, after completion of an optimization step or MD step.
129    */
130   private Terminatable terminatableAlgorithm = null;
131   /**
132    * Flag to indicate if a script is running.
133    */
134   private boolean scriptRunning;
135   /**
136    * Timing.
137    */
138   private long time;
139 
140   private long subTime;
141   private List<String> args;
142 
143   /**
144    * Constructor for ModelingShell.
145    *
146    * @param mainPanel a reference to the MainPanel.
147    */
148   public ModelingShell(MainPanel mainPanel) {
149     super();
150     this.mainPanel = mainPanel;
151     headless = java.awt.GraphicsEnvironment.isHeadless();
152     FFXBinding binding = new FFXBinding();
153     initContext(binding);
154   }
155 
156   /**
157    * after
158    */
159   public void after() {
160     time = System.nanoTime() - time;
161     scriptRunning = false;
162     if (!interrupted) {
163       appendOutput(String.format("\n Script wall clock time: %6.3f (sec)", time * toSeconds),
164           getPromptStyle());
165     } else {
166       appendOutput(String.format("\n Script interrupted after: %6.3f (sec)", time * toSeconds),
167           getPromptStyle());
168     }
169     mainPanel.getModelingPanel().enableLaunch(true);
170   }
171 
172   /**
173    * {@inheritDoc}
174    */
175   @Override
176   public boolean algorithmUpdate(MolecularAssembly active) {
177     if (interrupted) {
178       return false;
179     }
180 
181     GraphicsCanvas graphics = mainPanel.getGraphics3D();
182     if (graphics != null) {
183       /*
184        Use the blocking graphics update method so that only
185        self-consistent coordinate sets are displayed.
186       */
187       // if (SwingUtilities.isEventDispatchThread()) {
188       graphics.updateSceneWait(active, true, false, null, false, null);
189       // }
190     }
191 
192     // The algorithm could have been interrupted during the graphics update.
193     return !interrupted;
194   }
195 
196   /**
197    * If at exit time, a script is running, the user is given an option to interrupt it first
198    *
199    * <p>{@inheritDoc}
200    */
201   @Override
202   public Object askToInterruptScript() {
203     if (!scriptRunning) {
204       return true;
205     }
206     int rc = JOptionPane.showConfirmDialog(getScrollArea(),
207         "Script executing. Press 'OK' to attempt to interrupt it before exiting.",
208         "Force Field X Shell", JOptionPane.OK_CANCEL_OPTION);
209     if (rc == JOptionPane.OK_OPTION) {
210       doInterrupt();
211       return true;
212     } else {
213       return false;
214     }
215   }
216 
217   /**
218    * {@inheritDoc}
219    *
220    * <p>Return false if user elects to cancel.
221    */
222   @Override
223   public boolean askToSaveFile() {
224     File file = (File) getScriptFile();
225     if (file == null || !getDirty()) {
226       return true;
227     }
228     return switch (JOptionPane.showConfirmDialog((Component) getFrame(),
229         "Save changes to " + file.getName() + "?", "Force Field X Shell",
230         JOptionPane.YES_NO_CANCEL_OPTION)) {
231       case JOptionPane.YES_OPTION -> fileSave();
232       case JOptionPane.NO_OPTION -> true;
233       default -> false;
234     };
235   }
236 
237   /**
238    * before
239    */
240   public void before() {
241     interrupted = false;
242     terminatableAlgorithm = null;
243     time = System.nanoTime();
244     subTime = time;
245     mainPanel.getModelingPanel().enableLaunch(false);
246     scriptRunning = true;
247   }
248 
249   @Override
250   public void clearContext() {
251     super.clearContext();
252     FFXBinding binding = new FFXBinding();
253     initContext(binding);
254   }
255 
256   @Override
257   public void clearContext(EventObject evt) {
258     super.clearContext(evt);
259     FFXBinding binding = new FFXBinding();
260     initContext(binding);
261   }
262 
263   /**
264    * {@inheritDoc}
265    *
266    * <p>Print out the Force Field X promo.
267    */
268   @Override
269   public void clearOutput() {
270     if (!java.awt.GraphicsEnvironment.isHeadless()) {
271       JTextPane output = getOutputArea();
272       output.setText("");
273       appendOutput(
274           MainPanel.border + "\n" + MainPanel.title + MainPanel.aboutString + "\n" + MainPanel.border
275               + "\n", getCommandStyle());
276     }
277   }
278 
279   @Override
280   public void clearOutput(EventObject evt) {
281     clearOutput();
282   }
283 
284   /**
285    * energy
286    *
287    * @return a {@link ffx.potential.ForceFieldEnergy} object.
288    */
289   public ForceFieldEnergy energy() {
290     if (interrupted) {
291       logger.info(" Algorithm interrupted - skipping energy.");
292       return null;
293     }
294     if (terminatableAlgorithm != null) {
295       logger.info(" Algorithm already running - skipping energy.");
296       return null;
297     }
298 
299     MolecularAssembly active = mainPanel.getHierarchy().getActive();
300     if (active != null) {
301       ForceFieldEnergy energy = active.getPotentialEnergy();
302       if (energy == null) {
303         energy = ForceFieldEnergy.energyFactory(active);
304         active.setPotential(energy);
305       }
306       energy.energy(false, true);
307       return energy;
308     }
309     return null;
310   }
311 
312   @Override
313   public void fileNewWindow() {
314     mainPanel.resetShell();
315   }
316 
317   @Override
318   public void fileNewWindow(EventObject evt) {
319     fileNewWindow();
320   }
321 
322   public AlgorithmFunctions getUIAlgorithmUtils() {
323     return new UIUtils(this, mainPanel);
324   }
325 
326   public PotentialsFunctions getUIPotentialsUtils() {
327     return new UIUtils(this, mainPanel);
328   }
329 
330   /**
331    * md
332    *
333    * @param nStep          The number of MD steps.
334    * @param timeStep       a double.
335    * @param printInterval  a double.
336    * @param saveInterval   a double.
337    * @param temperature    a double.
338    * @param initVelocities a boolean.
339    * @param dyn            a {@link java.io.File} object.
340    */
341   public void md(int nStep, double timeStep, double printInterval, double saveInterval,
342                  double temperature, boolean initVelocities, File dyn) {
343     if (interrupted || terminatableAlgorithm != null) {
344       return;
345     }
346     FFXSystem active = mainPanel.getHierarchy().getActive();
347     if (active != null) {
348       MolecularDynamics molecularDynamics = new MolecularDynamics(active,
349           active.getPotentialEnergy(), this, ThermostatEnum.BUSSI, IntegratorEnum.BEEMAN);
350       terminatableAlgorithm = molecularDynamics;
351       molecularDynamics.dynamic(nStep, timeStep, printInterval, saveInterval, temperature,
352           initVelocities, dyn);
353       terminatableAlgorithm = null;
354     }
355   }
356 
357   /**
358    * Configure the Swing GUI for the shell.
359    */
360   @Override
361   public void run() {
362     if (!headless) {
363       if (SwingUtilities.isEventDispatchThread()) {
364         init();
365       } else {
366         try {
367           SwingUtilities.invokeAndWait(this::init);
368         } catch (Exception e) {
369           //
370         }
371       }
372     }
373   }
374 
375   /**
376    * select
377    *
378    * @param node a {@link ffx.potential.bonded.MSNode} object.
379    */
380   public void select(MSNode node) {
381     if (node != null) {
382       mainPanel.getHierarchy().onlySelection(node);
383       sync();
384       logger.info(String.format(" Selected: %s.", node));
385     }
386   }
387 
388   /**
389    * setArgList
390    *
391    * @param argList a {@link java.util.List} object.
392    */
393   public void setArgList(List<String> argList) {
394     args = new ArrayList<>(argList);
395     setVariable("args", argList);
396   }
397 
398   @Override
399   public void showAbout() {
400     mainPanel.about();
401   }
402 
403   @Override
404   public void showAbout(EventObject evt) {
405     showAbout();
406   }
407 
408   /**
409    * time
410    *
411    * @return a {@link java.lang.Double} object.
412    */
413   public Double time() {
414     long current = System.nanoTime();
415     double timer = (current - subTime) * toSeconds;
416     subTime = current;
417     appendOutput(String.format("\n Intermediate time: %8.3f (sec)\n", timer), getPromptStyle());
418     return timer;
419   }
420 
421   /**
422    * {@inheritDoc}
423    */
424   @Override
425   public String toString() {
426     return "Force Field X Shell";
427   }
428 
429   @Override
430   public void updateTitle() {
431     JFrame frame = (JFrame) this.getFrame();
432     File file = (File) getScriptFile();
433     if (file != null) {
434       String name = file.getName();
435       frame.setTitle(name + " - Force Field X Shell");
436     } else {
437       frame.setTitle("Force Field X Shell");
438     }
439   }
440 
441   /**
442    * Initialize access to Force Field X variables and methods from with the Shell.
443    */
444   private void initContext(FFXBinding binding) {
445     binding.setVariable("dat", mainPanel.getHierarchy());
446     binding.setVariable("cmd", mainPanel);
447     binding.setVariable("vis", mainPanel.getGraphics3D());
448     binding.setVariable("sh", this);
449     binding.setVariable("active", mainPanel.getHierarchy().getActive());
450     binding.setVariable("logger", logger);
451 
452     // Timer
453     binding.setVariable("time", new MethodClosure(this, "time"));
454 
455     // File
456     binding.setVariable("open", new MethodClosure(mainPanel, "openWait"));
457     binding.setVariable("convertWait", new MethodClosure(mainPanel, "convertWait"));
458     binding.setVariable("save", new MethodClosure(mainPanel, "saveAsXYZ"));
459     binding.setVariable("saveAsXYZ", new MethodClosure(mainPanel, "saveAsXYZ"));
460     binding.setVariable("saveAsP1", new MethodClosure(mainPanel, "saveAsP1"));
461     binding.setVariable("saveAsPDB", new MethodClosure(mainPanel, "saveAsPDB"));
462     binding.setVariable("close", new MethodClosure(mainPanel, "closeWait"));
463     binding.setVariable("closeAll", new MethodClosure(mainPanel, "closeAll"));
464 
465     // Select
466     binding.setVariable("select", new MethodClosure(this, "select"));
467 
468     // Display and View menus.
469     if (!headless) {
470       GraphicsCanvas graphics = mainPanel.getGraphics3D();
471 
472       // Display
473       int index = 0;
474       for (ViewModel view : ViewModel.values()) {
475         binding.setVariable(view.name(), view);
476         index++;
477         if (index > 8) {
478           break;
479         }
480       }
481       binding.setVariable("view", new MethodClosure(graphics, "viewWait"));
482 
483       // Color
484       index = 0;
485       for (ColorModel color : ColorModel.values()) {
486         binding.setVariable(color.name(), color);
487         index++;
488         if (index > 6) {
489           break;
490         }
491       }
492       binding.setVariable("color", new MethodClosure(graphics, "colorWait"));
493     }
494 
495     // Algorithms
496     binding.setVariable("returnEnergy", new MethodClosure(this, "returnEnergy"));
497     binding.setVariable("energy", new MethodClosure(this, "energy"));
498     binding.setVariable("analyze", new MethodClosure(this, "analyze"));
499     binding.setVariable("minimize", new MethodClosure(this, "minimize"));
500     binding.setVariable("minimize_2", new MethodClosure(this, "minimize_2"));
501     binding.setVariable("md", new MethodClosure(this, "md"));
502     binding.setVariable("potential", new MethodClosure(this, "potential"));
503     binding.setVariable("poledit", new MethodClosure(this, "poledit"));
504     binding.setVariable("superpose", new MethodClosure(this, "superpose"));
505 
506     // Obtain UIUtils object
507     binding.setVariable("functions", new UIUtils(this, mainPanel));
508 
509     // Define a listener variable to send updates back to the GUI.
510     binding.setVariable("listener", this);
511   }
512 
513   /**
514    * Update the shell menu items.
515    */
516   private void initMenus() {
517     JFrame frame = (JFrame) this.getFrame();
518     MenuBar menuBar = frame.getMenuBar();
519     // Remove "Capture Std. Out", "Capture Std. Error" & "Detached Output" from the View menu.
520     Menu menu = menuBar.getMenu(2);
521     menu.remove(5);
522     menu.remove(5);
523     menu.remove(9);
524 
525     // Edit the Script menu.
526     menu = menuBar.getMenu(4);
527     menu.remove(4);
528     menu.remove(4);
529     menu.remove(4);
530     menu.remove(5);
531     menu.remove(7);
532   }
533 
534   /**
535    * Initialize the Shell.
536    */
537   private void init() {
538     try {
539       super.run();
540       // Output JTextPane
541       JTextPane output = getOutputArea();
542       output.setBackground(Color.BLACK);
543       output.setForeground(Color.WHITE);
544       // Input JTextPane
545       JTextPane input = getInputArea();
546       input.setBackground(Color.WHITE);
547       input.setForeground(Color.BLACK);
548       // Output StyledDocument Styles
549       StyledDocument doc = output.getStyledDocument();
550       Style defStyle = StyleContext.getDefaultStyleContext().getStyle(StyleContext.DEFAULT_STYLE);
551       Style regular = doc.addStyle("regular", defStyle);
552       Style prompt = doc.addStyle("prompt", regular);
553       Style command = doc.addStyle("command", regular);
554       Style result = doc.addStyle("result", regular);
555       StyleConstants.setFontFamily(regular, "Monospaced");
556       setPromptStyle(prompt);
557       setCommandStyle(command);
558       setResultStyle(result);
559       StyleConstants.setForeground(prompt, Color.ORANGE);
560       StyleConstants.setForeground(command, Color.GREEN);
561       StyleConstants.setForeground(result, Color.GREEN);
562       StyleConstants.setBackground(result, Color.BLACK);
563       clearOutput();
564       // initMenus();
565 
566       // Set labels and icon for Force Field X.
567       getStatusLabel().setText("Welcome to the Force Field X Shell.");
568       JFrame frame = (JFrame) this.getFrame();
569       frame.setTitle("Force Field X Shell");
570       URL iconURL = getClass().getClassLoader().getResource("ffx/ui/icons/icon64.png");
571       ImageIcon icon = new ImageIcon(iconURL);
572       frame.setIconImage(icon.getImage());
573       frame.setSize(600, 600);
574     } catch (Exception e) {
575       logger.warning(" Exception starting up the FFX console.\n" + e);
576     }
577   }
578 
579   List<String> getArgs() {
580     return new ArrayList<>(args);
581   }
582 
583   /**
584    * runFFXScript - Execute a FFX script.
585    *
586    * @param file    a {@link java.io.File} object.
587    * @param argList List of String inputs to the script.
588    * @return Returns a reference to the executed script.
589    */
590   public FFXCommand runFFXScript(File file, List<String> argList) {
591     GroovyFileFilter groovyFileFilter = new GroovyFileFilter();
592     // Check that the file is a Groovy script.
593     if (!groovyFileFilter.accept(file)) {
594       // If not, assume its a Python script.
595       runNonGroovyScript(file, argList);
596       return null;
597     }
598 
599     logger.info(" Executing external Groovy script: " + file.getAbsolutePath() + "\n");
600     try {
601       before();
602       FFXCommand script = null;
603       try {
604         // Run the file using the current Shell and its Binding.
605         Object o = getShell().run(file, argList);
606 
607         if (o instanceof FFXCommand) {
608           script = (FFXCommand) o;
609         }
610 
611         // Do not destroy the system when using the GUI.
612         if (headless) {
613           if (o instanceof PotentialCommand) {
614             ((PotentialCommand) o).destroyPotentials();
615           } else if (o instanceof AlgorithmsCommand) {
616             ((AlgorithmsCommand) o).destroyPotentials();
617           }
618         }
619       } catch (Exception ex) {
620         logger.log(Level.SEVERE, " Uncaught error: FFX is shutting down.\n", ex);
621       }
622       after();
623       return script;
624     } catch (Exception e) {
625       // Replacing this with a "Multi-Catch" leads to specific Exceptions not present in
626       // some versions of Groovy.
627       String message = "Error evaluating script.";
628       logger.log(Level.WARNING, message, e);
629     }
630 
631     return null;
632   }
633 
634   /**
635    * runPythonScript - Execute a Python script.
636    *
637    * @param file    a {@link java.io.File} object.
638    * @param argList List of String inputs to the script.
639    * @return Returns a reference to the executed script.
640    */
641   public void runNonGroovyScript(File file, List<String> argList) {
642     logger.info(" Attempting to execute Polyglot script:\n  " + file.getAbsolutePath() + "\n");
643 
644     if (logger.isLoggable(Level.FINE)) {
645       logger.fine(" Available languages: ");
646       try (Engine engine = Engine.create()) {
647         Map<String, Language> map = engine.getLanguages();
648         for (Map.Entry<String, Language> entry : map.entrySet()) {
649           logger.fine("  " + entry.getKey());
650         }
651       } catch (Exception ex) {
652         logger.log(Level.SEVERE, " Uncaught error: FFX is shutting down.\n", ex);
653       }
654     }
655 
656     try {
657       before();
658       String language = Source.findLanguage(file);
659       logger.info(" Detected script language: " + language);
660       Source source = Source.newBuilder(language, file).build();
661       try (Context context = getContext(language)) {
662 
663         // Get the bindings for the language.
664         Value bindings = context.getBindings(language);
665         // Use the Polyglot ProxyArray to pass the command line arguments to the script.
666         List<Object> objList = new ArrayList<>(argList);
667         ProxyArray argArray = ProxyArray.fromList(objList);
668         bindings.putMember("args", argArray);
669         // Run the file using the current Shell and its Binding.
670         Value result = context.eval(source);
671         logger.info(" Execution of Polyglot script completed.");
672       } catch (Exception ex) {
673         logger.log(Level.SEVERE, " Uncaught error: FFX is shutting down.\n", ex);
674       }
675       after();
676     } catch (Exception e) {
677       // Replacing this with a "Multi-Catch" leads to specific Exceptions not present in some versions of Groovy.
678       String message = "Error evaluating script.";
679       logger.log(Level.WARNING, message, e);
680     }
681   }
682 
683   /**
684    * Create a Polyglot Context for the specified language.
685    *
686    * @param language a String specifying the language.
687    * @return a Polyglot Context.
688    */
689   private Context getContext(String language) {
690     if (language.equalsIgnoreCase("python")) {
691       // For Python, try to locate the Graal Python executable.
692 
693       // The default location is $FFX_HOME/ffx_venv/bin/graalpy.
694       String FFX_HOME = System.getProperty("basedir");
695       Path graalpy = Paths.get(FFX_HOME, "ffx_venv", "bin", "graalpy");
696 
697       // Override the default location with the -Dgraalpy=path.to.graalpy option.
698       String graalpyString = System.getProperty("graalpy", graalpy.toString());
699       graalpy = Paths.get(graalpyString);
700       if (graalpy.toFile().exists()) {
701         logger.info(" graalpy (-Dgraalpy=path.to.graalpy):             " + graalpy);
702         return Context.newBuilder(language).allowAllAccess(true).
703             option("python.Executable", graalpyString).
704             option("python.PythonPath", ".").build();
705       }
706     }
707     // Fall through to default.
708     return Context.newBuilder(language).allowAllAccess(true).build();
709   }
710 
711   /**
712    * runFFXScript - Execute a compiled FFX command.
713    *
714    * @param className a compiled FFX command.
715    * @param argList   List of String inputs to the command.
716    * @return Returns a reference to the executed command.
717    */
718   public FFXCommand runFFXScript(Class<? extends FFXCommand> className, List<String> argList) {
719     logger.info(" Executing internal command: " + className.getCanonicalName() + "\n");
720     try {
721       before();
722       FFXCommand command = null;
723       try {
724         // Create a Binding for command line arguments and FFX User Interface variables.
725         FFXBinding binding = new FFXBinding();
726         binding.setVariable("args", argList);
727         initContext(binding);
728 
729         // Create a new instance of the script and run it.
730         command = className.getDeclaredConstructor().newInstance();
731         command.setBinding(binding);
732         command.run();
733 
734         // Do not destroy the system when using the GUI.
735         if (headless) {
736           if (command instanceof PotentialCommand) {
737             ((PotentialCommand) command).destroyPotentials();
738           } else if (command instanceof AlgorithmsCommand) {
739             ((AlgorithmsCommand) command).destroyPotentials();
740           }
741         }
742       } catch (Exception ex) {
743         logger.log(Level.SEVERE, " Uncaught error: FFX is shutting down.\n", ex);
744       }
745       after();
746 
747       return command;
748     } catch (Exception e) {
749       // Replacing this with a "Multi-Catch" leads to specific Exceptions not present in
750       // some versions of Groovy.
751       String message = "Error evaluating script.";
752       logger.log(Level.WARNING, message, e);
753     }
754     return null;
755   }
756 
757   /**
758    * returnEnergy
759    *
760    * @return Current system energy (a double).
761    */
762   double returnEnergy() {
763     if (interrupted) {
764       logger.info(" Algorithm interrupted - skipping energy.");
765       return 0.0;
766     }
767     if (terminatableAlgorithm != null) {
768       logger.info(" Algorithm already running - skipping energy.");
769       return 0.0;
770     }
771 
772     MolecularAssembly active = mainPanel.getHierarchy().getActive();
773     if (active != null) {
774       ForceFieldEnergy energy = active.getPotentialEnergy();
775       if (energy == null) {
776         energy = ForceFieldEnergy.energyFactory(active);
777         active.setPotential(energy);
778       }
779       return energy.energy(false, true);
780     }
781     logger.warning(" Energy could not be calculated");
782     return 0.0;
783   }
784 
785   /**
786    * minimize
787    *
788    * @param eps a double.
789    * @return a {@link ffx.numerics.Potential} object.
790    */
791   Potential minimize(double eps) {
792     if (interrupted) {
793       logger.info(" Algorithm interrupted - skipping minimization.");
794       return null;
795     }
796     if (terminatableAlgorithm != null) {
797       logger.info(" Algorithm already running - skipping minimization.");
798       return null;
799     }
800     MolecularAssembly active = mainPanel.getHierarchy().getActive();
801     if (active != null) {
802       Minimize minimize = new Minimize(active, this);
803       terminatableAlgorithm = minimize;
804       Potential potential = minimize.minimize(eps);
805       terminatableAlgorithm = null;
806       return potential;
807     } else {
808       logger.info(" No active system to minimize.");
809     }
810     return null;
811   }
812 
813   /**
814    * Fix up the "Result: " message, then call the original method.
815    *
816    * @param string String to ouput.
817    * @param style  Style to use.
818    */
819   void appendOutputNl(String string, Style style) {
820     if (interrupted) {
821       return;
822     }
823     if (headless) {
824       logger.info(string);
825     }
826     if (string.equals("Result: ")) {
827       string = " Script result: \n";
828     } else if (string.equals("groovy> ")) {
829       string = " ffx> ";
830     }
831 
832     super.appendOutputNl(string, style);
833 
834     if (EventQueue.isDispatchThread()) {
835       scroll();
836     } else {
837       SwingUtilities.invokeLater(this::scroll);
838     }
839   }
840 
841   /**
842    * scroll
843    */
844   private void scroll() {
845     JTextPane output = getOutputArea();
846     JSplitPane splitPane = getSplitPane();
847     JScrollPane scrollPane = (JScrollPane) splitPane.getBottomComponent();
848     JViewport viewport = scrollPane.getViewport();
849     Rectangle visibleSize = viewport.getVisibleRect();
850     Dimension totalSize = output.getSize();
851     Point point = new Point(0, totalSize.height - visibleSize.height);
852     viewport.setViewPosition(point);
853   }
854 
855   /**
856    * appendOutput
857    *
858    * @param string a {@link java.lang.String} object.
859    * @param style  a {@link javax.swing.text.Style} object.
860    */
861   private void appendOutput(String string, Style style) {
862     if (interrupted) {
863       return;
864     }
865 
866     if (headless) {
867       logger.info(string);
868       return;
869     }
870 
871     super.appendOutput(string, style);
872     if (EventQueue.isDispatchThread()) {
873       scroll();
874     } else {
875       SwingUtilities.invokeLater(this::scroll);
876     }
877   }
878 
879   /**
880    * setMeasurement
881    *
882    * @param measurement a {@link java.lang.String} object.
883    * @param d           a double.
884    */
885   void setMeasurement(String measurement, double d) {
886     try {
887       appendOutput(measurement, getOutputStyle());
888     } catch (Exception e) {
889       String message = "Exception appending measurement to Shell.\n";
890       logger.log(Level.WARNING, message, e);
891     }
892   }
893 
894   /**
895    * sync
896    */
897   void sync() {
898     try {
899       setVariable("active", mainPanel.getHierarchy().getActive());
900     } catch (Exception e) {
901       String message = " Exception syncing shell variables.\n";
902       logger.log(Level.WARNING, message, e);
903     }
904   }
905 }