/* Export a model as an image or set of html files. Copyright (c) 2011-2014 The Regents of the University of California. All rights reserved. Permission is hereby granted, without written agreement and without license or royalty fees, to use, copy, modify, and distribute this software and its documentation for any purpose, provided that the above copyright notice and the following two paragraphs appear in all copies of this software. IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. PT_COPYRIGHT_VERSION_2 COPYRIGHTENDKEY */ package ptolemy.vergil.basic.export; import java.awt.Color; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.net.URI; import java.net.URL; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Timer; import java.util.TimerTask; import javax.swing.JFrame; import javax.swing.SwingUtilities; import ptolemy.actor.Director; import ptolemy.actor.Manager; import ptolemy.actor.TypedCompositeActor; import ptolemy.actor.gui.BrowserEffigy; import ptolemy.actor.gui.Configuration; import ptolemy.actor.gui.ConfigurationApplication; import ptolemy.actor.gui.Effigy; import ptolemy.actor.gui.ModelDirectory; import ptolemy.actor.gui.PtolemyEffigy; import ptolemy.actor.gui.Tableau; import ptolemy.actor.gui.TableauFrame; import ptolemy.actor.lib.gui.UsesInvokeAndWait; import ptolemy.data.BooleanToken; import ptolemy.kernel.CompositeEntity; import ptolemy.kernel.Entity; import ptolemy.kernel.util.BasicModelErrorHandler; import ptolemy.kernel.util.IllegalActionException; import ptolemy.util.FileUtilities; import ptolemy.util.StringUtilities; import ptolemy.vergil.basic.BasicGraphFrame; import ptolemy.vergil.basic.ExportParameters; import ptolemy.vergil.basic.export.html.ExportHTMLAction; import ptolemy.vergil.basic.export.web.WebExportParameters; /////////////////////////////////////////////////////////////////// //// ExportModel /** * Export a model as an image or set of html files. * *
The default is to export a .gif file with the same name as the model. * See {@link #main(String[])} for usage.
* *See http://chess.eecs.berkeley.edu/ptexternal/wiki/Main/HTMLExport * for detailed instructions about how to create web pages on the * Ptolemy website for models.
* * @author Christopher Brooks * @version $Id: ExportModel.java 70402 2014-10-23 00:52:20Z cxh $ * @since Ptolemy II 10.0 * @Pt.ProposedRating Red (cxh) * @Pt.AcceptedRating Red (cxh) */ public class ExportModel { /** Export an image of a model to a file or directory. *The image is written to a file or directory with the same name * as the model. If formatName starts with "HTM" or "htm", then * a directory with the same name as the basename of the model is * created. If the formatName is "GIF", "gif", "PNG" or "png", * then a file with the same basename as the basename of the * model is created.
* *The time out defaults to 30 seconds.
* * @param copyJavaScriptFiles True if the javascript files should * be copied. Used only if formatName starts with "htm" * or "HTM". * * @param force If true, then remove the image file or htm * directory to be created in advance before creating the image * file or htm directory. This parameter is primarily used to * avoid prompting the user with questions about overwriting * files after this command is invoked. * * @param formatName The file format of the file to be generated. * One of "GIF", "gif", "HTM", "htm", "PNG", "png". * * @param modelFileName A Ptolemy model in MoML format. * The string may start with $CLASSPATH, $HOME or other formats * suitable for {@link ptolemy.util.FileUtilities#nameToFile(String, URI)}. * * @param run True if the model should be run first. If run * is true, and if formatName starts with "htm" or "HTM", then * the output will include images of any plots. * * @param openComposites True if the CompositeEntities should be * open. The openComposites parameter only has an effect * if formatName starts with "htm" or "HTM". * * @param openResults open the resulting image file or web page. * * @param outputFileOrDirectory If non-null, then the file or directory * in which to generate the file(s). * * @param save True if the model should be saved after being run. * * @param whiteBackground True if the model background should be set to white. * * @exception Exception Thrown if there is a problem reading the model * or exporting the image. */ public void exportModel(final boolean copyJavaScriptFiles, final boolean force, final String formatName, final String modelFileName, final boolean run, final boolean openComposites, final boolean openResults, final String outputFileOrDirectory, final boolean save, final boolean whiteBackground) throws Exception { exportModel(copyJavaScriptFiles, force, formatName, modelFileName, run, openComposites, openResults, outputFileOrDirectory, save, 30000, whiteBackground); } /** Export an image of a model to a file or directory. * The image is written to a file or directory with the same name as the model. * If formatName starts with "HTM" or "htm", then a directory with the * same name as the basename of the model is created. * If the formatName is "GIF", "gif", "PNG" or "png", then a file * with the same basename as the basename of the model is created. * * @param copyJavaScriptFiles True if the javascript files should be copied. * Used only if formatName starts with "htm" or "HTM". * * @param force If true, then remove the image file or htm directory to be created * in advance before creating the image file or htm directory. This parameter * is primarily used to avoid prompting the user with questions about overwriting files * after this command is invoked. * * @param formatName The file format of the file to be generated. * One of "GIF", "gif", "HTM", "htm", "PNG", "png". * * @param modelFileName A Ptolemy model in MoML format. * The string may start with $CLASSPATH, $HOME or other formats * suitable for {@link ptolemy.util.FileUtilities#nameToFile(String, URI)}. * * @param run True if the model should be run first. If run * is true, and if formatName starts with "htm" or "HTM", then * the output will include images of any plots. * * @param openComposites True if the CompositeEntities should be * open. The openComposites parameter only has an effect * if formatName starts with "htm" or "HTM". * * @param openResults open the resulting image file or web page. * * @param outputFileOrDirectory If non-null, then the file or directory * in which to generate the file(s). * * @param save True if the model should be saved after being run. * * @param timeOut Time out in milliseconds. 30000 is a good value. * * @param whiteBackground True if the model background should be set to white. * * @exception Exception Thrown if there is a problem reading the model * or exporting the image. */ public void exportModel(final boolean copyJavaScriptFiles, final boolean force, final String formatName, final String modelFileName, final boolean run, final boolean openComposites, final boolean openResults, final String outputFileOrDirectory, final boolean save, final long timeOut, final boolean whiteBackground) throws Exception { // FIXME: Maybe we should pass an ExportParameter here? // FIXME: this seem wrong: The inner classes are in different // threads and can only access final variables. However, we // use an array as a final variable, but we change the value // of the element of the array. Is this thread safe? // Perhaps we should make this a field? //final TypedCompositeActor[] model = new TypedCompositeActor[1]; final CompositeEntity[] model = new CompositeEntity[1]; ///// // Open the model. // FIXME: Refactor this and KielerLayoutJUnitTest to a common class. Runnable openModelAction = new Runnable() { @Override public void run() { try { model[0] = ConfigurationApplication .openModelOrEntity(modelFileName); } catch (Throwable throwable) { throwable.printStackTrace(); throw new RuntimeException(throwable); } } }; SwingUtilities.invokeAndWait(openModelAction); _sleep(); _basicGraphFrame = BasicGraphFrame.getBasicGraphFrame(model[0]); // Set temporary variables before setting the final versions // for use inside inner classes. File temporaryHTMLDirectory = null; File temporaryImageFile = null; // Use the model name as the basis for the directory containing // the html or as the basis for the image file. final boolean isHTM = formatName.toLowerCase(Locale.getDefault()) .startsWith("htm"); if (isHTM) { if (outputFileOrDirectory != null) { temporaryHTMLDirectory = new File(outputFileOrDirectory); temporaryImageFile = new File(outputFileOrDirectory + File.separator + "index.html"); } else { temporaryHTMLDirectory = new File(model[0].getName()); temporaryImageFile = new File(model[0].getName() + File.separator + "index.html"); } } else { String suffix = "." + formatName.toLowerCase(Locale.getDefault()); if (outputFileOrDirectory != null) { // If the filename does not end in the formatName, // append the format name. if (outputFileOrDirectory.endsWith(formatName .toLowerCase(Locale.getDefault())) || outputFileOrDirectory.endsWith(formatName .toUpperCase(Locale.getDefault()))) { suffix = ""; } temporaryImageFile = new File(outputFileOrDirectory + suffix); } else { // The user did not specify an outputFileOrDirectory, // so use the model name. temporaryImageFile = new File(model[0].getName() + suffix); } } // The directory where an html file would be generated. final File htmlDirectory = temporaryHTMLDirectory; // The name of the index.html file or image file. final File imageFile = temporaryImageFile; // We optionally delete the directory containing the .html file or // delete the image file. Do this after loading the model so that // we can get the directory in which the model resides if (force) { // Delete the directory containing the .html file or // delete the image file. if (isHTM) { if (htmlDirectory.exists() && !FileUtilities.deleteDirectory(htmlDirectory)) { System.err.println("Could not delete \"" + htmlDirectory + "\"."); } } else { // A gif/jpg/png file if (imageFile.exists() && !imageFile.delete()) { System.err.println("Could not delete \"" + imageFile + "\"."); } } } if (run) { if (!_runnable(model[0])) { System.out .println("Model \"" + model[0].getFullName() + "\" contains actors such cannot be run " + " as part of the export process from ExportModel or " + "it has a WebExportParameters value that runBeforeExport set to false. " + "To export run this model and export it, use vergil."); } else { // Optionally run the model. Runnable runAction = new Runnable() { @Override public void run() { try { if (!(model[0] instanceof TypedCompositeActor)) { System.out .println(model[0].getFullName() + " is a " + model[0].getClass().getName() + " not a TypedCompositeActor, so it cannot be run."); return; } TypedCompositeActor composite = (TypedCompositeActor) model[0]; System.out.println("Running " + composite.getFullName()); Manager manager = composite.getManager(); if (manager == null) { manager = new Manager(composite.workspace(), "MyManager"); composite.setManager(manager); } composite .setModelErrorHandler(new BasicModelErrorHandler()); _timer = new Timer(true); final Director finalDirector = composite .getDirector(); TimerTask doTimeToDie = new TimerTask() { @Override public void run() { System.out .println("ExportHTMLTimer went off after " + timeOut + " ms., calling getDirector().finish and getDirector().stopFire()"); // NOTE: This used to call stop() on // the manager, but that's not the // right thing to do. In particular, // this could be used inside a // RunCompositeActor, and it should // only stop the inside execution, not // the outside one. It's also not // correct to call stop() on the // director, because stop() requests // immediate stopping. To give // determinate stopping, this actor // needs to complete the current // iteration. // The Stop actor has similar code. finalDirector.finish(); // To support multithreaded domains, // also have to call stopFire() to // request that all actors conclude // ongoing firings. finalDirector.stopFire(); } }; _timer.schedule(doTimeToDie, timeOut); // Calling finish() and stopFire() is not // sufficient if the model is still // initializing, so we call stop() on the // manager after 2x the timeout. // To replicate: // $PTII/bin/ptinvoke ptolemy.vergil.basic.export.ExportModel -force htm -run -openComposites -timeOut 30000 -whiteBackground ptolemy/domains/ddf/demo/RijndaelEncryption/RijndaelEncryption.xml $PTII/ptolemy/domains/ddf/demo/RijndaelEncryption/RijndaelEncryption final Manager finalManager = manager; _failSafeTimer = new Timer(true); TimerTask doFailSafeTimeToDie = new TimerTask() { @Override public void run() { System.out .println("ExportHTMLTimer went off after " + timeOut * 2 + " ms., calling manager.stop()."); finalManager.stop(); } }; _failSafeTimer.schedule(doFailSafeTimeToDie, timeOut * 2); try { manager.execute(); } finally { _timer.cancel(); _failSafeTimer.cancel(); } } catch (Exception ex) { ex.printStackTrace(); throw new RuntimeException(ex); } } }; SwingUtilities.invokeAndWait(runAction); _sleep(); } } if (save) { // Optionally save the model. // Sadly, running the DOPCenter.xml model does not seem to update the // graph. So, we run it and save it and then open it again. Runnable saveAction = new Runnable() { @Override public void run() { try { System.out.println("Saving " + model[0].getFullName()); ((PtolemyEffigy) _basicGraphFrame.getTableau() .getContainer()).writeFile(new File( modelFileName)); } catch (Exception ex) { ex.printStackTrace(); throw new RuntimeException(ex); } } }; SwingUtilities.invokeAndWait(saveAction); _sleep(); } if (openComposites && !isHTM) { // Optionally open any composites. Runnable openCompositesAction = new Runnable() { @Override public void run() { try { System.out.println("Opening submodels of " + model[0].getFullName()); Configuration configuration = (Configuration) Configuration .findEffigy(model[0].toplevel()).toplevel(); ListNote that the a graphical display must be present, this * code displays the model and executes. To use in a headless * environment under Linux, install Xvfb.
* *Command line arguments are:
*-force
* -copyJavaScriptFiles -open -openComposites htm
.Typical usage:
*To save a gif:
** java -classpath $PTII ptolemy.vergil.basic.export.ExportModel model.xml ** *
or, to save the current view of model in HTML format without any plots:
** java -classpath $PTII ptolemy.vergil.basic.export.ExportModel htm model.xml ** *
or, to run the model and save the current view of model in * HTML format with any plots:
** java -classpath $PTII ptolemy.vergil.basic.export.ExportModel -run htm model.xml ** *
or, to run the model, open any composites and save the * current view of model and the composites HTML format with any * plots:
** java -classpath $PTII ptolemy.vergil.basic.export.ExportModel -run -openComposites htm model.xml ** *
Standard setting for exporting to html can be invoked with -web
,
* which is like -copyJavaScriptFiles -open -openComposites htm
.
* java -classpath $PTII ptolemy.vergil.basic.export.ExportModel -web model.xml ** *
or, to save a png:
** java -classpath $PTII ptolemy.vergil.basic.export.ExportModel png model.xml ** *
or, to run the model and then save a png:
** java -classpath $PTII ptolemy.vergil.basic.export.ExportModel -run png model.xml ** *
To set the background to white, invoke with
* -whiteBackground
.
To export an html version in a format suitable for the * Ptolemy website, set the * "ptolemy.ptII.exportHTML.usePtWebsite" property to true, * perhaps by including the following in the command line:
** -Dptolemy.ptII.exportHTML.usePtWebsite=true **
For example:
** export JAVAFLAGS=-Dptolemy.ptII.exportHTML.usePtWebsite=true * $PTII/bin/ptweb $PTII/ptolemy/moml/demo/modulation.xml ** *
To include a link to a sanitizedModelName.jnlp
file,
* set -Dptolemy.ptII.exportHTML.linkToJNLP=true.
Note that the Ptolemy menus will not appear unless you view * the page with a web server that has Server Side Includes (SSI) * enabled and has the appropriate scripts. Also, the .html * files must be executable.
* *Include a link to the a
* sanitizedModelName.jnlp
file, set the
* "ptolemy.ptII.exportHTML.linkToJNLP" property to true.