1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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
101
102
103
104
105
106 public class ModelingShell extends Console implements AlgorithmListener {
107
108
109
110
111 private static final Logger logger = Logger.getLogger(ModelingShell.class.getName());
112 private static final double toSeconds = 1.0e-9;
113
114
115
116
117 private final MainPanel mainPanel;
118
119
120
121 private final boolean headless;
122
123
124
125 private boolean interrupted;
126
127
128
129
130 private Terminatable terminatableAlgorithm = null;
131
132
133
134 private boolean scriptRunning;
135
136
137
138 private long time;
139
140 private long subTime;
141 private List<String> args;
142
143
144
145
146
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
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
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
185
186
187
188 graphics.updateSceneWait(active, true, false, null, false, null);
189
190 }
191
192
193 return !interrupted;
194 }
195
196
197
198
199
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
219
220
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
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
265
266
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
286
287
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
332
333
334
335
336
337
338
339
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
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
377
378
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
390
391
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
410
411
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
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
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
453 binding.setVariable("time", new MethodClosure(this, "time"));
454
455
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
466 binding.setVariable("select", new MethodClosure(this, "select"));
467
468
469 if (!headless) {
470 GraphicsCanvas graphics = mainPanel.getGraphics3D();
471
472
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
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
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
507 binding.setVariable("functions", new UIUtils(this, mainPanel));
508
509
510 binding.setVariable("listener", this);
511 }
512
513
514
515
516 private void initMenus() {
517 JFrame frame = (JFrame) this.getFrame();
518 MenuBar menuBar = frame.getMenuBar();
519
520 Menu menu = menuBar.getMenu(2);
521 menu.remove(5);
522 menu.remove(5);
523 menu.remove(9);
524
525
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
536
537 private void init() {
538 try {
539 super.run();
540
541 JTextPane output = getOutputArea();
542 output.setBackground(Color.BLACK);
543 output.setForeground(Color.WHITE);
544
545 JTextPane input = getInputArea();
546 input.setBackground(Color.WHITE);
547 input.setForeground(Color.BLACK);
548
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
565
566
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
585
586
587
588
589
590 public FFXCommand runFFXScript(File file, List<String> argList) {
591 GroovyFileFilter groovyFileFilter = new GroovyFileFilter();
592
593 if (!groovyFileFilter.accept(file)) {
594
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
605 Object o = getShell().run(file, argList);
606
607 if (o instanceof FFXCommand) {
608 script = (FFXCommand) o;
609 }
610
611
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
626
627 String message = "Error evaluating script.";
628 logger.log(Level.WARNING, message, e);
629 }
630
631 return null;
632 }
633
634
635
636
637
638
639
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
664 Value bindings = context.getBindings(language);
665
666 List<Object> objList = new ArrayList<>(argList);
667 ProxyArray argArray = ProxyArray.fromList(objList);
668 bindings.putMember("args", argArray);
669
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
678 String message = "Error evaluating script.";
679 logger.log(Level.WARNING, message, e);
680 }
681 }
682
683
684
685
686
687
688
689 private Context getContext(String language) {
690 if (language.equalsIgnoreCase("python")) {
691
692
693
694 String FFX_HOME = System.getProperty("basedir");
695 Path graalpy = Paths.get(FFX_HOME, "ffx_venv", "bin", "graalpy");
696
697
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
708 return Context.newBuilder(language).allowAllAccess(true).build();
709 }
710
711
712
713
714
715
716
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
725 FFXBinding binding = new FFXBinding();
726 binding.setVariable("args", argList);
727 initContext(binding);
728
729
730 command = className.getDeclaredConstructor().newInstance();
731 command.setBinding(binding);
732 command.run();
733
734
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
750
751 String message = "Error evaluating script.";
752 logger.log(Level.WARNING, message, e);
753 }
754 return null;
755 }
756
757
758
759
760
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
787
788
789
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
815
816
817
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
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
857
858
859
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
881
882
883
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
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 }