Merge pull request #665 from wolsen/add-print-progress-dialog

[Fixes 637] Update print dialog to allow simulation control
This commit is contained in:
Daniel Williams 2020-05-24 13:46:33 -04:00 committed by GitHub
commit 16300941c7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 269 additions and 30 deletions

View File

@ -115,6 +115,7 @@ AboutDialog.lbl.translatorIcon =
PrintDialog.title = Print or export
PrintDialog.but.previewAndPrint = Preview & Print
PrintDialog.checkbox.showByStage = Show by stage
PrintDialog.checkbox.updateSimulations = Update simulation data
PrintDialog.lbl.selectElements = Select elements to include:
printdlg.but.saveaspdf = Save as PDF
printdlg.but.preview = Preview

View File

@ -50,6 +50,8 @@ import net.sf.openrocket.startup.Application;
*/
public class PrintDialog extends JDialog implements TreeSelectionListener {
private static final long serialVersionUID = 1L;
private static final Logger log = LoggerFactory.getLogger(PrintDialog.class);
private static final Translator trans = Application.getTranslator();
@ -65,6 +67,8 @@ public class PrintDialog extends JDialog implements TreeSelectionListener {
private JButton cancel;
private double rotation = 0d;
private boolean updateSimulations = true;
private final static SwingPreferences prefs = (SwingPreferences) Application.getPreferences();
@ -122,6 +126,19 @@ public class PrintDialog extends JDialog implements TreeSelectionListener {
// Checkboxes and buttons
final JPanel optionsPanel = new JPanel(new MigLayout());
final JCheckBox updateSimulationsCheckbox = new JCheckBox(trans.get("checkbox.updateSimulations"));
updateSimulationsCheckbox.setEnabled(true);
updateSimulationsCheckbox.setSelected(this.updateSimulations);
updateSimulationsCheckbox.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
updateSimulations = updateSimulationsCheckbox.isSelected();
}
});
optionsPanel.add(updateSimulationsCheckbox, "pad 0, grow, wrap");
final JCheckBox sortByStage = new JCheckBox(trans.get("checkbox.showByStage"));
sortByStage.setEnabled(stages > 1);
sortByStage.setSelected(stages > 1);
@ -142,10 +159,10 @@ public class PrintDialog extends JDialog implements TreeSelectionListener {
}
}
});
panel.add(sortByStage, "aligny top, split");
optionsPanel.add(sortByStage);
panel.add(optionsPanel, "pad 0, aligny top, split");
panel.add(new JPanel(), "growx");
panel.add(new JPanel(), "pad 0, aligny top, growx");
JButton settingsButton = new JButton(trans.get("printdlg.but.settings"));
@ -159,8 +176,8 @@ public class PrintDialog extends JDialog implements TreeSelectionListener {
setPrintSettings(settings);
}
});
panel.add(settingsButton, "wrap para");
panel.add(settingsButton, "aligny top, wrap para");
previewButton = new JButton(trans.get("but.previewAndPrint"));
previewButton.addActionListener(new ActionListener() {
@ -286,7 +303,10 @@ public class PrintDialog extends JDialog implements TreeSelectionListener {
*/
private File generateReport(File f, PrintSettings settings) throws IOException {
Iterator<PrintableContext> toBePrinted = currentTree.getToBePrinted();
new PrintController().print(document, toBePrinted, new FileOutputStream(f), settings, rotation);
PrintController controller = new PrintController();
controller.setWindow(this.getOwner());
controller.print(document, toBePrinted, new FileOutputStream(f),
settings, rotation, updateSimulations);
return f;
}

View File

@ -4,8 +4,15 @@
package net.sf.openrocket.gui.print;
import java.awt.Graphics2D;
import java.awt.Window;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -27,6 +34,7 @@ import net.sf.openrocket.formatting.RocketDescriptor;
import net.sf.openrocket.gui.figureelements.FigureElement;
import net.sf.openrocket.gui.figureelements.RocketInfo;
import net.sf.openrocket.gui.scalefigure.RocketPanel;
import net.sf.openrocket.gui.simulation.SimulationRunDialog;
import net.sf.openrocket.masscalc.MassCalculator;
import net.sf.openrocket.masscalc.RigidBody;
import net.sf.openrocket.motor.Motor;
@ -38,6 +46,7 @@ import net.sf.openrocket.rocketcomponent.MotorMount;
import net.sf.openrocket.rocketcomponent.Rocket;
import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.simulation.FlightData;
import net.sf.openrocket.simulation.FlightDataBranch;
import net.sf.openrocket.simulation.exception.SimulationException;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.unit.Unit;
@ -107,6 +116,21 @@ public class DesignReport {
*/
private double rotation = 0d;
/**
* Determines whether or not to run out of date simulations.
*/
private boolean runOutOfDateSimulations = true;
/**
* Determines whether or not to update existing simulations.
*/
private boolean updateExistingSimulations = false;
/**
* Parent window for showing simulation run dialog as necessary
*/
private Window window = null;
/** The displayed strings. */
private static final String STAGES = "Stages: ";
private static final String MASS_WITH_MOTORS = "Mass (with motors): ";
@ -135,17 +159,45 @@ public class DesignReport {
private static final double GRAVITY_CONSTANT = 9.80665d;
/**
* Constructor.
* Creates a new DesignReport in the iTextPDF Document based on the
* OpenRocketDocument specified. All out of date simulations will be
* run as part of generating the iTextPDF report.
*
* This is for backwards API compatibility and will copy existing
* simulations before running them.
*
* @param theRocDoc the OpenRocketDocument which serves as the source
* of the rocket information
* @param theIDoc the iTextPDF Document where the DesignReport is written
* @param figureRotation the rotation of the figure used for displaying
* the profile view.
*/
public DesignReport(OpenRocketDocument theRocDoc, Document theIDoc, Double figureRotation) {
this(theRocDoc, theIDoc, figureRotation, true, false, null);
}
/**
* Creates a new DesignReport in the iTextPDF Document based on the
* OpenRocketDocument specified. Out of date simulations will be run
* when the runOutOfDateSims parameter is set to true.
*
* @param theRocDoc the OR document
* @param theIDoc the iText document
* @param figureRotation the angle the figure is rotated on the screen; printed report will mimic
* @param runOutOfDateSims whether or not to run simulations that are not up to date.
* @param updateExistingSims whether or not to update existing simulations or to copy the simulations.
* Previous behavior was to copy existing simulations.
* @param window the base AWT window to use
*/
public DesignReport(OpenRocketDocument theRocDoc, Document theIDoc, Double figureRotation) {
public DesignReport(OpenRocketDocument theRocDoc, Document theIDoc, Double figureRotation,
boolean runOutOfDateSims, boolean updateExistingSims, Window window) {
document = theIDoc;
rocketDocument = theRocDoc;
panel = new RocketPanel(rocketDocument);
rotation = figureRotation;
this.runOutOfDateSimulations = runOutOfDateSims;
this.updateExistingSimulations = updateExistingSims;
this.window = window;
}
/**
@ -226,7 +278,7 @@ public class DesignReport {
paragraph.setSpacingAfter(heightOfDiagramAndText);
document.add(paragraph);
List<Simulation> simulations = rocketDocument.getSimulations();
List<Simulation> simulations = getSimulations();
boolean firstMotor = true;
for (FlightConfigurationId fcid : rocket.getIds()) {
@ -242,7 +294,7 @@ public class DesignReport {
*/
int leading = (firstMotor) ? 0 : 25;
FlightData flight = findSimulation( fcid, simulations);
FlightData flight = findSimulation(fcid, simulations);
addFlightData(flight, rocket, fcid, parent, leading);
addMotorData(rocket, fcid, parent);
document.add(parent);
@ -456,6 +508,10 @@ public class DesignReport {
// Output the flight data
if (flight != null) {
try {
FlightDataBranch branch = new FlightDataBranch();
if (flight.getBranchCount() > 0) {
branch = flight.getBranch(0);
}
final Unit distanceUnit = UnitGroup.UNITS_DISTANCE.getDefaultUnit();
final Unit velocityUnit = UnitGroup.UNITS_VELOCITY.getDefaultUnit();
final Unit flightTimeUnit = UnitGroup.UNITS_FLIGHT_TIME.getDefaultUnit();
@ -482,7 +538,7 @@ public class DesignReport {
labelTable.addCell(ITextHelper.createCell(flightTimeUnit.toStringUnit(flight.getTimeToApogee()), 2, 2));
labelTable.addCell(ITextHelper.createCell(OPTIMUM_DELAY, 2, 2));
labelTable.addCell(ITextHelper.createCell(flightTimeUnit.toStringUnit(flight.getBranch(0).getOptimumDelay()), 2, 2));
labelTable.addCell(ITextHelper.createCell(flightTimeUnit.toStringUnit(branch.getOptimumDelay()), 2, 2));
labelTable.addCell(ITextHelper.createCell(VELOCITY_OFF_PAD, 2, 2));
labelTable.addCell(ITextHelper.createCell(velocityUnit.toStringUnit(flight.getLaunchRodVelocity()), 2, 2));
@ -520,22 +576,160 @@ public class DesignReport {
private FlightData findSimulation(final FlightConfigurationId motorId, List<Simulation> simulations) {
// Perform flight simulation
FlightData flight = null;
try {
for (int i = 0; i < simulations.size(); i++) {
Simulation simulation = simulations.get(i);
if (Utils.equals(simulation.getId(), motorId)) {
simulation = simulation.copy();
simulation.simulate();
flight = simulation.getSimulatedData();
break;
}
for (int i = 0; i < simulations.size(); i++) {
Simulation simulation = simulations.get(i);
if (Utils.equals(simulation.getId(), motorId)) {
flight = simulation.getSimulatedData();
break;
}
} catch (SimulationException e1) {
// Ignore
}
return flight;
}
/**
* Returns a list of Simulations to use for printing the design report
* for the rocket and optionally re-run out of date simulations.
*
* If the user has selected to not run any simulations, this method will
* simply return the simulations found in the OpenRocketDocument.
*
* If the user has selected to run simulations, this method will identify
* any simulations which are not up to date and re-run them.
*
* @return a list of Simulations to include in the DesignReport.
*/
protected List<Simulation> getSimulations() {
List<Simulation> simulations = rocketDocument.getSimulations();
if (!runOutOfDateSimulations) {
log.debug("Using current simulations for rocket.");
return simulations;
}
ArrayList<Simulation> simulationsToRun = new ArrayList<Simulation>();
ArrayList<Simulation> upToDateSimulations = new ArrayList<Simulation>();
for (Simulation simulation : simulations) {
boolean simulate = false;
boolean copy = !this.updateExistingSimulations;
switch (simulation.getStatus()) {
case CANT_RUN:
log.warn("Simulation " + simulation.getId() + " has no motors, skipping");
// Continue so we don't simulate
continue;
case UPTODATE:
log.trace("Simulation " + simulation.getId() + "is up to date, not running simulation");
simulate = false;
break;
case NOT_SIMULATED:
case OUTDATED:
case LOADED:
log.trace("Running simulation for " + simulation.getId());
simulate = true;
break;
case EXTERNAL:
log.trace("Simulation " + simulation.getId() + " is external. Using data provided");
simulate = false;
break;
default:
log.trace("Running simulation for " + simulation.getId());
simulate = true;
copy = true;
break;
}
if (!simulate) {
upToDateSimulations.add(simulation);
} else if (copy) {
simulationsToRun.add(simulation.copy());
} else {
simulationsToRun.add(simulation);
}
}
/* Run any simulations that are pending a run. This is done via the
* SimulationRunDialog in order to provide user feedback.
*/
if (!simulationsToRun.isEmpty()) {
runSimulations(simulationsToRun);
upToDateSimulations.addAll(simulationsToRun);
}
return upToDateSimulations;
}
/**
* Runs the selected set of simulations. If a valid Window was provided when
* creating this DesignReport, this method will run simulations using the
* SimulationRunDialog in order to present status to the user.
*
* @param simulations a list of Simulations to run
*/
protected void runSimulations(List<Simulation> simulations) {
if (window != null) {
log.debug("Updating " + simulations.size() + "simulations using SimulationRunDialog");
Simulation[] runMe = simulations.toArray(new Simulation[simulations.size()]);
new SimulationRunDialog(window, rocketDocument, runMe).setVisible(true);
} else {
/* This code is left for compatibility with any developers who are
* using the API to generate design reports. This may not be running
* graphically and the SimulationRunDialog may not be available for
* displaying progress information/updating simulations.
*/
log.debug("Updating simulations using thread pool");
int cores = Runtime.getRuntime().availableProcessors();
ThreadPoolExecutor executor = new ThreadPoolExecutor(cores, cores, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
new SimulationRunnerThreadFactory());
for (Simulation simulation : simulations) {
executor.execute(new RunSimulationTask(simulation));
}
executor.shutdown();
try {
/* Arbitrarily wait for at most 5 minutes for the simulation
* to complete. This seems like a long time, but in case there
* is a really long running simulation
*/
executor.awaitTermination(5, TimeUnit.MINUTES);
} catch (InterruptedException ie) {
}
}
}
private static class SimulationRunnerThreadFactory implements ThreadFactory {
private ThreadFactory factory = Executors.defaultThreadFactory();
@Override
public Thread newThread(Runnable r) {
Thread t = factory.newThread(r);
t.setDaemon(true);
return t;
}
}
/**
* The RunSimulationTask is responsible for running simulations within the
* DesignReport when run outside of the SimulationRunDialog.
*/
private static class RunSimulationTask implements Runnable {
private final Simulation simulation;
public RunSimulationTask(final Simulation simulation) {
this.simulation = simulation;
}
@Override
public void run() {
try {
simulation.simulate();
} catch (SimulationException ex) {
log.error("Error simulating " + simulation.getId(), ex);
}
}
}
/**
* Strip [] brackets from a string.
*

View File

@ -20,6 +20,7 @@ import net.sf.openrocket.gui.print.visitor.PageFitPrintStrategy;
import net.sf.openrocket.gui.print.visitor.PartsDetailVisitorStrategy;
import net.sf.openrocket.gui.print.visitor.TransitionStrategy;
import java.awt.Window;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Iterator;
@ -30,6 +31,33 @@ import java.util.Set;
* file.
*/
public class PrintController {
/**
* Used for displaying progress information when the Window
* is not null.
*/
private Window window = null;
/**
* Sets the reference Window for displaying progress information
* for long running print operations.
*
* @param window the reference window
*/
public void setWindow(final Window window) {
this.window = window;
}
/**
* Returns the reference Window for displaying progress information
* for long running print operations.
*
* @return the reference Window for displaying progress. May be null
* if no reference Window has been set.
*/
public Window getWindow() {
return this.window;
}
/**
* Print the selected components to a PDF document.
@ -39,9 +67,10 @@ public class PrintController {
* @param outputFile the file being written to
* @param settings the print settings
* @param rotation the angle the rocket figure is rotated
* @param runSims determines whether to re-run out of date simulations or not
*/
public void print(OpenRocketDocument doc, Iterator<PrintableContext> toBePrinted, OutputStream outputFile,
PrintSettings settings, double rotation) {
PrintSettings settings, double rotation, boolean runSims) {
Document idoc = new Document(getSize(settings));
PdfWriter writer = null;
@ -51,12 +80,7 @@ public class PrintController {
writer.addViewerPreference(PdfName.PRINTSCALING, PdfName.NONE);
writer.addViewerPreference(PdfName.PICKTRAYBYPDFSIZE, PdfBoolean.PDFTRUE);
try {
idoc.open();
Thread.sleep(1000);
}
catch (InterruptedException e) {
}
idoc.open();
// Used to combine multiple components onto fewer sheets of paper
PageFitPrintStrategy pageFitPrint = new PageFitPrintStrategy(idoc, writer);
@ -70,7 +94,7 @@ public class PrintController {
switch (printableContext.getPrintable()) {
case DESIGN_REPORT:
DesignReport dp = new DesignReport(doc, idoc, rotation);
DesignReport dp = new DesignReport(doc, idoc, rotation, runSims, true, this.window);
dp.writeToDocument(writer);
idoc.newPage();
break;