Merge pull request #952 from SiboVG/issue-927

[fixes #927 & #771] Fix auto-run simulation NA values
This commit is contained in:
Joe Pfeiffer 2021-09-28 14:40:13 -06:00 committed by GitHub
commit a513c676de
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 325 additions and 107 deletions

View File

@ -810,7 +810,7 @@ public class OpenRocketDocument implements ComponentChangeListener {
listeners.remove(listener); listeners.remove(listener);
} }
protected void fireDocumentChangeEvent(DocumentChangeEvent event) { public void fireDocumentChangeEvent(DocumentChangeEvent event) {
DocumentChangeListener[] array = listeners.toArray(new DocumentChangeListener[0]); DocumentChangeListener[] array = listeners.toArray(new DocumentChangeListener[0]);
for (DocumentChangeListener l : array) { for (DocumentChangeListener l : array) {
l.documentChanged(event); l.documentChanged(event);

View File

@ -18,10 +18,20 @@ public abstract class InterpolatingAtmosphericModel implements AtmosphericModel
if (levels == null) if (levels == null)
computeLayers(); computeLayers();
if (altitude <= 0) if (altitude <= 0) {
// TODO: LOW: levels[0] returned null in some cases, see GitHub issue #952 for more information
if (levels[0] == null) {
computeLayers();
}
return levels[0]; return levels[0];
if (altitude >= DELTA * (levels.length - 1)) }
if (altitude >= DELTA * (levels.length - 1)) {
// TODO: LOW: levels[levels.length - 1] returned null in some cases, see GitHub issue #952 for more information
if (levels[levels.length - 1] == null) {
computeLayers();
}
return levels[levels.length - 1]; return levels[levels.length - 1];
}
int n = (int) (altitude / DELTA); int n = (int) (altitude / DELTA);
double d = (altitude - n * DELTA) / DELTA; double d = (altitude - n * DELTA) / DELTA;

View File

@ -115,6 +115,8 @@ public class MotorConfiguration implements FlightConfigurableParameter<MotorConf
public void useDefaultIgnition() { public void useDefaultIgnition() {
this.ignitionOveride = false; this.ignitionOveride = false;
setIgnitionDelay(0);
setIgnitionEvent(IgnitionEvent.AUTOMATIC);
} }
public double getIgnitionDelay() { public double getIgnitionDelay() {

View File

@ -8,6 +8,8 @@ import net.sf.openrocket.unit.UnitGroup;
import net.sf.openrocket.util.MathUtil; import net.sf.openrocket.util.MathUtil;
import net.sf.openrocket.util.Pair; import net.sf.openrocket.util.Pair;
import java.util.Objects;
public class DeploymentConfiguration implements FlightConfigurableParameter<DeploymentConfiguration> { public class DeploymentConfiguration implements FlightConfigurableParameter<DeploymentConfiguration> {
@ -155,4 +157,16 @@ public class DeploymentConfiguration implements FlightConfigurableParameter<Depl
public void update(){ public void update(){
} }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
DeploymentConfiguration that = (DeploymentConfiguration) o;
return Double.compare(that.deployAltitude, deployAltitude) == 0 && Double.compare(that.deployDelay, deployDelay) == 0 && deployEvent == that.deployEvent;
}
@Override
public int hashCode() {
return Objects.hash(deployEvent, deployAltitude, deployDelay);
}
} }

View File

@ -5,6 +5,8 @@ import net.sf.openrocket.simulation.FlightEvent;
import net.sf.openrocket.startup.Application; import net.sf.openrocket.startup.Application;
import net.sf.openrocket.util.MathUtil; import net.sf.openrocket.util.MathUtil;
import java.util.Objects;
public class StageSeparationConfiguration implements FlightConfigurableParameter<StageSeparationConfiguration> { public class StageSeparationConfiguration implements FlightConfigurableParameter<StageSeparationConfiguration> {
public static enum SeparationEvent { public static enum SeparationEvent {
@ -145,6 +147,18 @@ public class StageSeparationConfiguration implements FlightConfigurableParameter
return clone; return clone;
} }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
StageSeparationConfiguration that = (StageSeparationConfiguration) o;
return Double.compare(that.separationDelay, separationDelay) == 0 && separationEvent == that.separationEvent;
}
@Override
public int hashCode() {
return Objects.hash(separationEvent, separationDelay);
}
private void fireChangeEvent() { private void fireChangeEvent() {

View File

@ -300,7 +300,7 @@ public class BasicEventSimulationEngine implements SimulationEngine {
// Ignore events for components that are no longer attached to the rocket // Ignore events for components that are no longer attached to the rocket
if (event.getSource() != null && event.getSource().getParent() != null && if (event.getSource() != null && event.getSource().getParent() != null &&
!currentStatus.getConfiguration().isComponentActive(event.getSource())) { !currentStatus.getConfiguration().isComponentActive(event.getSource())) {
log.trace("Ignoring event from unattached componenent"); log.trace("Ignoring event from unattached component");
continue; continue;
} }
@ -332,6 +332,7 @@ public class BasicEventSimulationEngine implements SimulationEngine {
// Check for recovery device deployment, add events to queue // Check for recovery device deployment, add events to queue
// TODO: LOW: check if deprecated function getActiveComponents needs to be replaced
for (RocketComponent c : currentStatus.getConfiguration().getActiveComponents()) { for (RocketComponent c : currentStatus.getConfiguration().getActiveComponents()) {
if (!(c instanceof RecoveryDevice)) if (!(c instanceof RecoveryDevice))
continue; continue;
@ -533,6 +534,7 @@ public class BasicEventSimulationEngine implements SimulationEngine {
} }
// TODO : FUTURE : do not hard code the 1200 (maybe even make it configurable by the user)
if( 1200 < currentStatus.getSimulationTime() ){ if( 1200 < currentStatus.getSimulationTime() ){
ret = false; ret = false;
log.error("Simulation hit max time (1200s): aborting."); log.error("Simulation hit max time (1200s): aborting.");

View File

@ -220,7 +220,7 @@ public class FlightData {
timeToApogee = Double.NaN; timeToApogee = Double.NaN;
// Launch rod velocity // Launch rod velocity + deployment velocity + ground hit velocity
for (FlightEvent event : branch.getEvents()) { for (FlightEvent event : branch.getEvents()) {
if (event.getType() == FlightEvent.Type.LAUNCHROD) { if (event.getType() == FlightEvent.Type.LAUNCHROD) {
double t = event.getTime(); double t = event.getTime();

View File

@ -0,0 +1,29 @@
package net.sf.openrocket.simulation.listeners.system;
import net.sf.openrocket.simulation.FlightEvent;
import net.sf.openrocket.simulation.SimulationStatus;
import net.sf.openrocket.simulation.listeners.AbstractSimulationListener;
/**
* A simulation listeners that ends the simulation when the ground is hit.
*
* @author Sibo Van Gool <sibo.vangool@hotmail.com>
*/
public class GroundHitListener extends AbstractSimulationListener {
public static final GroundHitListener INSTANCE = new GroundHitListener();
@Override
public boolean handleFlightEvent(SimulationStatus status, FlightEvent event) {
if (event.getType() == FlightEvent.Type.GROUND_HIT) {
status.getEventQueue().add(new FlightEvent(FlightEvent.Type.SIMULATION_END, status.getSimulationTime()));
}
return true;
}
@Override
public boolean isSystemListener() {
return true;
}
}

View File

@ -221,7 +221,7 @@ public class BasicFrame extends JFrame {
// Bottom segment, rocket figure // Bottom segment, rocket figure
rocketpanel = new RocketPanel(document); rocketpanel = new RocketPanel(document, this);
vertical.setBottomComponent(rocketpanel); vertical.setBottomComponent(rocketpanel);
rocketpanel.setSelectionModel(tree.getSelectionModel()); rocketpanel.setSelectionModel(tree.getSelectionModel());
@ -1099,6 +1099,9 @@ public class BasicFrame extends JFrame {
tabbedPane.setSelectedIndex(tab); tabbedPane.setSelectedIndex(tab);
} }
public int getSelectedTab() {
return tabbedPane.getSelectedIndex();
}
private void openAction() { private void openAction() {

View File

@ -576,7 +576,7 @@ public class SimulationPanel extends JPanel {
public void documentChanged(DocumentChangeEvent event) { public void documentChanged(DocumentChangeEvent event) {
if (!(event instanceof SimulationChangeEvent)) if (!(event instanceof SimulationChangeEvent))
return; return;
simulationTableModel.fireTableDataChanged(); fireMaintainSelection();
} }
}); });

View File

@ -54,10 +54,14 @@ public abstract class FlightConfigurablePanel<T extends FlightConfigurableCompon
synchronizeConfigurationSelection(); synchronizeConfigurationSelection();
} }
public void fireTableDataChanged() { /**
* Update the data in the table, with component change event type {cce}
* @param cce index of the ComponentChangeEvent to use (e.g. ComponentChangeEvent.NONFUNCTIONAL_CHANGE)
*/
public void fireTableDataChanged(int cce) {
int selectedRow = table.getSelectedRow(); int selectedRow = table.getSelectedRow();
int selectedColumn = table.getSelectedColumn(); int selectedColumn = table.getSelectedColumn();
this.rocket.fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE); this.rocket.fireComponentChangeEvent(cce);
((AbstractTableModel)table.getModel()).fireTableDataChanged(); ((AbstractTableModel)table.getModel()).fireTableDataChanged();
restoreSelection(selectedRow,selectedColumn); restoreSelection(selectedRow,selectedColumn);
updateButtonState(); updateButtonState();

View File

@ -15,6 +15,7 @@ import net.sf.openrocket.document.Simulation;
import net.sf.openrocket.gui.dialogs.flightconfiguration.RenameConfigDialog; import net.sf.openrocket.gui.dialogs.flightconfiguration.RenameConfigDialog;
import net.sf.openrocket.gui.main.BasicFrame; import net.sf.openrocket.gui.main.BasicFrame;
import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.rocketcomponent.ComponentChangeEvent;
import net.sf.openrocket.rocketcomponent.FlightConfigurableComponent; import net.sf.openrocket.rocketcomponent.FlightConfigurableComponent;
import net.sf.openrocket.rocketcomponent.FlightConfiguration; import net.sf.openrocket.rocketcomponent.FlightConfiguration;
import net.sf.openrocket.rocketcomponent.FlightConfigurationId; import net.sf.openrocket.rocketcomponent.FlightConfigurationId;
@ -79,7 +80,7 @@ public class FlightConfigurationPanel extends JPanel implements StateChangeListe
int lastCol = motorConfigurationPanel.table.getColumnCount() - 1; int lastCol = motorConfigurationPanel.table.getColumnCount() - 1;
motorConfigurationPanel.table.setRowSelectionInterval(lastRow, lastRow); motorConfigurationPanel.table.setRowSelectionInterval(lastRow, lastRow);
motorConfigurationPanel.table.setColumnSelectionInterval(lastCol, lastCol); motorConfigurationPanel.table.setColumnSelectionInterval(lastCol, lastCol);
configurationChanged(); configurationChanged(ComponentChangeEvent.MOTOR_CHANGE);
} }
}); });
@ -91,7 +92,7 @@ public class FlightConfigurationPanel extends JPanel implements StateChangeListe
@Override @Override
public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) {
renameConfiguration(); renameConfiguration();
configurationChanged(); configurationChanged(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
} }
}); });
this.add(renameConfButton,"gapright para"); this.add(renameConfButton,"gapright para");
@ -101,7 +102,7 @@ public class FlightConfigurationPanel extends JPanel implements StateChangeListe
@Override @Override
public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) {
removeConfiguration(); removeConfiguration();
configurationChanged(); configurationChanged(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
} }
}); });
this.add(removeConfButton,"gapright para"); this.add(removeConfButton,"gapright para");
@ -111,7 +112,7 @@ public class FlightConfigurationPanel extends JPanel implements StateChangeListe
@Override @Override
public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) {
addOrCopyConfiguration(true); addOrCopyConfiguration(true);
configurationChanged(); configurationChanged(ComponentChangeEvent.MOTOR_CHANGE);
} }
}); });
this.add(copyConfButton, "wrap"); this.add(copyConfButton, "wrap");
@ -172,13 +173,13 @@ public class FlightConfigurationPanel extends JPanel implements StateChangeListe
if (currentId == null) if (currentId == null)
return; return;
document.removeFlightConfigurationAndSimulations(currentId); document.removeFlightConfigurationAndSimulations(currentId);
configurationChanged(); configurationChanged(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
} }
private void configurationChanged() { private void configurationChanged(int cce) {
motorConfigurationPanel.fireTableDataChanged(); motorConfigurationPanel.fireTableDataChanged(cce);
recoveryConfigurationPanel.fireTableDataChanged(); recoveryConfigurationPanel.fireTableDataChanged(cce);
separationConfigurationPanel.fireTableDataChanged(); separationConfigurationPanel.fireTableDataChanged(cce);
} }
private void updateButtonState() { private void updateButtonState() {

View File

@ -29,10 +29,12 @@ import net.sf.openrocket.gui.widgets.SelectColorButton;
import net.sf.openrocket.motor.IgnitionEvent; import net.sf.openrocket.motor.IgnitionEvent;
import net.sf.openrocket.motor.Motor; import net.sf.openrocket.motor.Motor;
import net.sf.openrocket.motor.MotorConfiguration; import net.sf.openrocket.motor.MotorConfiguration;
import net.sf.openrocket.rocketcomponent.ComponentChangeEvent;
import net.sf.openrocket.rocketcomponent.FlightConfiguration; import net.sf.openrocket.rocketcomponent.FlightConfiguration;
import net.sf.openrocket.rocketcomponent.FlightConfigurationId; import net.sf.openrocket.rocketcomponent.FlightConfigurationId;
import net.sf.openrocket.rocketcomponent.MotorMount; import net.sf.openrocket.rocketcomponent.MotorMount;
import net.sf.openrocket.rocketcomponent.Rocket; import net.sf.openrocket.rocketcomponent.Rocket;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.unit.UnitGroup; import net.sf.openrocket.unit.UnitGroup;
import net.sf.openrocket.util.Chars; import net.sf.openrocket.util.Chars;
@ -201,20 +203,25 @@ public class MotorConfigurationPanel extends FlightConfigurablePanel<MotorMount>
throw new IllegalStateException("Attempting to set a motor on the default FCID."); throw new IllegalStateException("Attempting to set a motor on the default FCID.");
} }
double initDelay = curMount.getMotorConfig(fcid).getEjectionDelay();
motorChooserDialog.setMotorMountAndConfig( fcid, curMount ); motorChooserDialog.setMotorMountAndConfig( fcid, curMount );
motorChooserDialog.setVisible(true); motorChooserDialog.setVisible(true);
Motor mtr = motorChooserDialog.getSelectedMotor(); Motor mtr = motorChooserDialog.getSelectedMotor();
double d = motorChooserDialog.getSelectedDelay(); double d = motorChooserDialog.getSelectedDelay();
if (mtr != null) { if (mtr != null) {
if (mtr == curMount.getMotorConfig(fcid).getMotor() && d == initDelay) {
return;
}
final MotorConfiguration templateConfig = curMount.getMotorConfig(fcid); final MotorConfiguration templateConfig = curMount.getMotorConfig(fcid);
final MotorConfiguration newConfig = new MotorConfiguration( curMount, fcid, templateConfig); final MotorConfiguration newConfig = new MotorConfiguration( curMount, fcid, templateConfig);
newConfig.setMotor(mtr); newConfig.setMotor(mtr);
newConfig.setEjectionDelay(d); newConfig.setEjectionDelay(d);
curMount.setMotorConfig( newConfig, fcid); curMount.setMotorConfig( newConfig, fcid);
}
fireTableDataChanged(); fireTableDataChanged(ComponentChangeEvent.MOTOR_CHANGE);
}
} }
private void removeMotor() { private void removeMotor() {
@ -226,7 +233,7 @@ public class MotorConfigurationPanel extends FlightConfigurablePanel<MotorMount>
curMount.setMotorConfig( null, fcid); curMount.setMotorConfig( null, fcid);
fireTableDataChanged(); fireTableDataChanged(ComponentChangeEvent.MOTOR_CHANGE);
} }
private void selectIgnition() { private void selectIgnition() {
@ -236,6 +243,10 @@ public class MotorConfigurationPanel extends FlightConfigurablePanel<MotorMount>
return; return;
} }
MotorConfiguration curInstance = curMount.getMotorConfig(fcid);
IgnitionEvent initialIgnitionEvent = curInstance.getIgnitionEvent();
double initialIgnitionDelay = curInstance.getIgnitionDelay();
// this call also performs the update changes // this call also performs the update changes
IgnitionSelectionDialog ignitionDialog = new IgnitionSelectionDialog( IgnitionSelectionDialog ignitionDialog = new IgnitionSelectionDialog(
SwingUtilities.getWindowAncestor(this.flightConfigurationPanel), SwingUtilities.getWindowAncestor(this.flightConfigurationPanel),
@ -243,7 +254,9 @@ public class MotorConfigurationPanel extends FlightConfigurablePanel<MotorMount>
curMount); curMount);
ignitionDialog.setVisible(true); ignitionDialog.setVisible(true);
fireTableDataChanged(); if (!initialIgnitionEvent.equals(curInstance.getIgnitionEvent()) || (initialIgnitionDelay != curInstance.getIgnitionDelay())) {
fireTableDataChanged(ComponentChangeEvent.MOTOR_CHANGE);
}
} }
@ -254,10 +267,14 @@ public class MotorConfigurationPanel extends FlightConfigurablePanel<MotorMount>
return; return;
} }
MotorConfiguration curInstance = curMount.getMotorConfig(fcid); MotorConfiguration curInstance = curMount.getMotorConfig(fcid);
IgnitionEvent initialIgnitionEvent = curInstance.getIgnitionEvent();
double initialIgnitionDelay = curInstance.getIgnitionDelay();
curInstance.useDefaultIgnition(); curInstance.useDefaultIgnition();
fireTableDataChanged(); if (!initialIgnitionEvent.equals(curInstance.getIgnitionEvent()) || (initialIgnitionDelay != curInstance.getIgnitionDelay())) {
fireTableDataChanged(ComponentChangeEvent.MOTOR_CHANGE);
}
} }

View File

@ -99,22 +99,31 @@ public class RecoveryConfigurationPanel extends FlightConfigurablePanel<Recovery
private void selectDeployment() { private void selectDeployment() {
RecoveryDevice c = getSelectedComponent(); RecoveryDevice c = getSelectedComponent();
if (c == null) { FlightConfigurationId fcid = getSelectedConfigurationId();
if ((c == null) || (fcid == null)) {
return; return;
} }
DeploymentConfiguration initialConfig = c.getDeploymentConfigurations().get(fcid).copy(fcid);
JDialog d = new DeploymentSelectionDialog(SwingUtilities.getWindowAncestor(this), rocket, c); JDialog d = new DeploymentSelectionDialog(SwingUtilities.getWindowAncestor(this), rocket, c);
d.setVisible(true); d.setVisible(true);
fireTableDataChanged(); if (!initialConfig.equals(c.getDeploymentConfigurations().get(fcid))) {
fireTableDataChanged(ComponentChangeEvent.AERODYNAMIC_CHANGE);
}
} }
private void resetDeployment() { private void resetDeployment() {
RecoveryDevice c = getSelectedComponent(); RecoveryDevice c = getSelectedComponent();
if (c == null) { FlightConfigurationId fcid = getSelectedConfigurationId();
if ((c == null) || (fcid == null)) {
return; return;
} }
DeploymentConfiguration initialConfig = c.getDeploymentConfigurations().get(fcid).copy(fcid);
FlightConfigurationId id = rocket.getSelectedConfiguration().getFlightConfigurationID(); FlightConfigurationId id = rocket.getSelectedConfiguration().getFlightConfigurationID();
c.getDeploymentConfigurations().reset(id); c.getDeploymentConfigurations().reset(id);
fireTableDataChanged(); if (!initialConfig.equals(c.getDeploymentConfigurations().get(fcid))) {
fireTableDataChanged(ComponentChangeEvent.AERODYNAMIC_CHANGE);
}
} }
public void updateButtonState() { public void updateButtonState() {

View File

@ -17,6 +17,7 @@ import net.sf.openrocket.formatting.RocketDescriptor;
import net.sf.openrocket.gui.dialogs.flightconfiguration.SeparationSelectionDialog; import net.sf.openrocket.gui.dialogs.flightconfiguration.SeparationSelectionDialog;
import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.rocketcomponent.AxialStage; import net.sf.openrocket.rocketcomponent.AxialStage;
import net.sf.openrocket.rocketcomponent.ComponentChangeEvent;
import net.sf.openrocket.rocketcomponent.FlightConfigurationId; import net.sf.openrocket.rocketcomponent.FlightConfigurationId;
import net.sf.openrocket.rocketcomponent.Rocket; import net.sf.openrocket.rocketcomponent.Rocket;
import net.sf.openrocket.rocketcomponent.StageSeparationConfiguration; import net.sf.openrocket.rocketcomponent.StageSeparationConfiguration;
@ -46,7 +47,7 @@ public class SeparationConfigurationPanel extends FlightConfigurablePanel<AxialS
selectSeparationButton.addActionListener(new ActionListener() { selectSeparationButton.addActionListener(new ActionListener() {
@Override @Override
public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) {
selectDeployment(); selectSeparation();
} }
}); });
this.add(selectSeparationButton, "split, align right, sizegroup button"); this.add(selectSeparationButton, "split, align right, sizegroup button");
@ -57,7 +58,7 @@ public class SeparationConfigurationPanel extends FlightConfigurablePanel<AxialS
resetDeploymentButton.addActionListener(new ActionListener() { resetDeploymentButton.addActionListener(new ActionListener() {
@Override @Override
public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) {
resetDeployment(); resetSeparation();
} }
}); });
this.add(resetDeploymentButton, "sizegroup button, wrap"); this.add(resetDeploymentButton, "sizegroup button, wrap");
@ -86,7 +87,7 @@ public class SeparationConfigurationPanel extends FlightConfigurablePanel<AxialS
updateButtonState(); updateButtonState();
if (e.getClickCount() == 2) { if (e.getClickCount() == 2) {
// Double-click edits // Double-click edits
selectDeployment(); selectSeparation();
} }
} }
}); });
@ -95,27 +96,35 @@ public class SeparationConfigurationPanel extends FlightConfigurablePanel<AxialS
return separationTable; return separationTable;
} }
private void selectDeployment() { private void selectSeparation() {
AxialStage stage = getSelectedComponent(); AxialStage stage = getSelectedComponent();
if (stage == null) { FlightConfigurationId fcid = getSelectedConfigurationId();
if ((stage == null) || (fcid == null)) {
return; return;
} }
StageSeparationConfiguration initialConfig = stage.getSeparationConfigurations().get(fcid).copy(fcid);
JDialog d = new SeparationSelectionDialog(SwingUtilities.getWindowAncestor(this), rocket, stage); JDialog d = new SeparationSelectionDialog(SwingUtilities.getWindowAncestor(this), rocket, stage);
d.setVisible(true); d.setVisible(true);
fireTableDataChanged(); if (!initialConfig.equals(stage.getSeparationConfigurations().get(fcid))) {
fireTableDataChanged(ComponentChangeEvent.AEROMASS_CHANGE);
}
} }
private void resetDeployment() { private void resetSeparation() {
AxialStage stage = getSelectedComponent(); AxialStage stage = getSelectedComponent();
if (stage == null) { FlightConfigurationId fcid = getSelectedConfigurationId();
if ((stage == null) || (fcid == null)) {
return; return;
} }
StageSeparationConfiguration initialConfig = stage.getSeparationConfigurations().get(fcid).copy(fcid);
// why? // why?
FlightConfigurationId id = rocket.getSelectedConfiguration().getFlightConfigurationID(); FlightConfigurationId id = rocket.getSelectedConfiguration().getFlightConfigurationID();
stage.getSeparationConfigurations().reset(id); stage.getSeparationConfigurations().reset(id);
fireTableDataChanged(); if (!initialConfig.equals(stage.getSeparationConfigurations().get(fcid))) {
fireTableDataChanged(ComponentChangeEvent.AEROMASS_CHANGE);
}
} }
public void updateButtonState() { public void updateButtonState() {
boolean componentSelected = getSelectedComponent() != null; boolean componentSelected = getSelectedComponent() != null;

View File

@ -8,22 +8,15 @@ import java.awt.Point;
import java.awt.event.InputEvent; import java.awt.event.InputEvent;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.EventListener; import java.util.EventListener;
import java.util.EventObject; import java.util.EventObject;
import java.util.List; import java.util.List;
import java.util.concurrent.Executor; import java.util.LinkedList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadFactory;
import javax.swing.ComboBoxModel; import javax.swing.*;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.JViewport;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener; import javax.swing.event.ChangeListener;
import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionEvent;
@ -38,6 +31,7 @@ import net.sf.openrocket.aerodynamics.FlightConditions;
import net.sf.openrocket.aerodynamics.WarningSet; import net.sf.openrocket.aerodynamics.WarningSet;
import net.sf.openrocket.document.OpenRocketDocument; import net.sf.openrocket.document.OpenRocketDocument;
import net.sf.openrocket.document.Simulation; import net.sf.openrocket.document.Simulation;
import net.sf.openrocket.document.events.SimulationChangeEvent;
import net.sf.openrocket.gui.adaptors.DoubleModel; import net.sf.openrocket.gui.adaptors.DoubleModel;
import net.sf.openrocket.gui.components.BasicSlider; import net.sf.openrocket.gui.components.BasicSlider;
import net.sf.openrocket.gui.components.ConfigurationComboBox; import net.sf.openrocket.gui.components.ConfigurationComboBox;
@ -49,6 +43,7 @@ import net.sf.openrocket.gui.figureelements.CGCaret;
import net.sf.openrocket.gui.figureelements.CPCaret; import net.sf.openrocket.gui.figureelements.CPCaret;
import net.sf.openrocket.gui.figureelements.Caret; import net.sf.openrocket.gui.figureelements.Caret;
import net.sf.openrocket.gui.figureelements.RocketInfo; import net.sf.openrocket.gui.figureelements.RocketInfo;
import net.sf.openrocket.gui.main.BasicFrame;
import net.sf.openrocket.gui.main.componenttree.ComponentTreeModel; import net.sf.openrocket.gui.main.componenttree.ComponentTreeModel;
import net.sf.openrocket.gui.simulation.SimulationWorker; import net.sf.openrocket.gui.simulation.SimulationWorker;
import net.sf.openrocket.gui.util.SwingPreferences; import net.sf.openrocket.gui.util.SwingPreferences;
@ -63,10 +58,12 @@ import net.sf.openrocket.rocketcomponent.Rocket;
import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.rocketcomponent.SymmetricComponent; import net.sf.openrocket.rocketcomponent.SymmetricComponent;
import net.sf.openrocket.simulation.FlightData; import net.sf.openrocket.simulation.FlightData;
import net.sf.openrocket.simulation.SimulationStatus;
import net.sf.openrocket.simulation.customexpression.CustomExpression; import net.sf.openrocket.simulation.customexpression.CustomExpression;
import net.sf.openrocket.simulation.customexpression.CustomExpressionSimulationListener; import net.sf.openrocket.simulation.customexpression.CustomExpressionSimulationListener;
import net.sf.openrocket.simulation.exception.SimulationException;
import net.sf.openrocket.simulation.listeners.SimulationListener; import net.sf.openrocket.simulation.listeners.SimulationListener;
import net.sf.openrocket.simulation.listeners.system.ApogeeEndListener; import net.sf.openrocket.simulation.listeners.system.GroundHitListener;
import net.sf.openrocket.simulation.listeners.system.InterruptListener; import net.sf.openrocket.simulation.listeners.system.InterruptListener;
import net.sf.openrocket.startup.Application; import net.sf.openrocket.startup.Application;
import net.sf.openrocket.unit.UnitGroup; import net.sf.openrocket.unit.UnitGroup;
@ -75,6 +72,8 @@ import net.sf.openrocket.util.Chars;
import net.sf.openrocket.util.Coordinate; import net.sf.openrocket.util.Coordinate;
import net.sf.openrocket.util.MathUtil; import net.sf.openrocket.util.MathUtil;
import net.sf.openrocket.util.StateChangeListener; import net.sf.openrocket.util.StateChangeListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** /**
@ -87,6 +86,7 @@ import net.sf.openrocket.util.StateChangeListener;
public class RocketPanel extends JPanel implements TreeSelectionListener, ChangeSource { public class RocketPanel extends JPanel implements TreeSelectionListener, ChangeSource {
private static final Translator trans = Application.getTranslator(); private static final Translator trans = Application.getTranslator();
private static final Logger log = LoggerFactory.getLogger(RocketPanel.class);
public enum VIEW_TYPE { public enum VIEW_TYPE {
SideView(false, RocketFigure.VIEW_SIDE), SideView(false, RocketFigure.VIEW_SIDE),
@ -148,13 +148,16 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
private List<EventListener> listeners = new ArrayList<EventListener>(); private List<EventListener> listeners = new ArrayList<EventListener>();
// Store the basic frame to know which tab is selected (Rocket design, Motors & Configuration, Flight simulations)
private final BasicFrame basicFrame;
/** /**
* The executor service used for running the background simulations. * The executor service used for running the background simulations.
* This uses a fixed-sized thread pool for all background simulations * This uses a fixed-sized thread pool for all background simulations
* with all threads in daemon mode and with minimum priority. * with all threads in daemon mode and with minimum priority.
*/ */
private static final Executor backgroundSimulationExecutor; private static final ExecutorService backgroundSimulationExecutor;
static { static {
backgroundSimulationExecutor = Executors.newFixedThreadPool(SwingPreferences.getMaxThreadCount(), backgroundSimulationExecutor = Executors.newFixedThreadPool(SwingPreferences.getMaxThreadCount(),
new ThreadFactory() { new ThreadFactory() {
@ -170,13 +173,17 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
}); });
} }
public OpenRocketDocument getDocument(){ public OpenRocketDocument getDocument(){
return this.document; return this.document;
} }
public RocketPanel(OpenRocketDocument document) { public RocketPanel(OpenRocketDocument document) {
this(document, null);
}
public RocketPanel(OpenRocketDocument document, BasicFrame basicFrame) {
this.document = document; this.document = document;
this.basicFrame = basicFrame;
Rocket rkt = document.getRocket(); Rocket rkt = document.getRocket();
@ -216,6 +223,7 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
rkt.addComponentChangeListener(new ComponentChangeListener() { rkt.addComponentChangeListener(new ComponentChangeListener() {
@Override @Override
public void componentChanged(ComponentChangeEvent e) { public void componentChanged(ComponentChangeEvent e) {
updateExtras();
if (is3d) { if (is3d) {
if (e.isTextureChange()) { if (e.isTextureChange()) {
figure3d.flushTextureCaches(); figure3d.flushTextureCaches();
@ -557,7 +565,7 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
/** /**
* Updates the extra data included in the figure. Currently this includes * Updates the extra data included in the figure. Currently this includes
* the CP and CG carets. * the CP and CG carets. Also start the background simulator.
*/ */
private WarningSet warnings = new WarningSet(); private WarningSet warnings = new WarningSet();
@ -683,35 +691,120 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
return; return;
} }
// Start calculation process // Update simulations
if(((SwingPreferences) Application.getPreferences()).computeFlightInBackground()){ if (Application.getPreferences().getAutoRunSimulations()) {
extraText.setCalculatingData(true); // Update only current flight config simulation when you are not in the simulations tab
updateSims(this.basicFrame != null && this.basicFrame.getSelectedTab() == BasicFrame.SIMULATION_TAB);
}
else {
// Always update the simulation of the current configuration
updateSims(false);
}
Rocket duplicate = (Rocket) document.getRocket().copy(); // Update flight data and add flight data update trigger upon simulation changes
// find a Simulation based on the current flight configuration
FlightConfigurationId curID = curConfig.getFlightConfigurationID();
Simulation simulation = null;
for (Simulation sim : document.getSimulations()) { for (Simulation sim : document.getSimulations()) {
if (sim.getFlightConfigurationId().compareTo(curID) == 0) { sim.addChangeListener(new StateChangeListener() {
simulation = sim; @Override
public void stateChanged(EventObject e) {
if (updateFlightData(sim)) {
updateFigures();
}
}
});
if (updateFlightData(sim)) {
break; break;
} }
} }
}
/**
* Updates the simulations. If *currentConfig* is false, only update the simulation of the current flight
* configuration. If it is true, update all the simulations.
*
* @param updateAllSims flag to check whether to update all the simulations (true) or only the current
* flight config sim (false)
*/
private void updateSims(boolean updateAllSims) {
// Stop previous computation (if any)
stopBackgroundSimulation();
FlightConfigurationId curID = document.getSelectedConfiguration().getFlightConfigurationID();
extraText.setCalculatingData(true);
Rocket duplicate = (Rocket)document.getRocket().copy();
// Re-run the present simulation(s)
List<Simulation> sims = new LinkedList<>();
for (Simulation sim : document.getSimulations()) {
if (sim.getStatus() == Simulation.Status.UPTODATE || sim.getStatus() == Simulation.Status.LOADED
|| !document.getRocket().getFlightConfiguration(sim.getFlightConfigurationId()).hasMotors())
continue;
// Find a Simulation based on the current flight configuration
if (!updateAllSims) {
if (sim.getFlightConfigurationId().compareTo(curID) == 0) {
sims.add(sim);
break;
}
}
else {
sims.add(sim);
}
}
runBackgroundSimulations(sims, duplicate);
}
/**
* Update the flight data text with the data of {sim}. Only update if sim is the simulation of the current flight
* configuration.
* @param sim: simulation from which the flight data is taken
* @return true if the flight data was updated, false if not
*/
private boolean updateFlightData(Simulation sim) {
FlightConfigurationId curID = document.getSelectedConfiguration().getFlightConfigurationID();
if (sim.getFlightConfigurationId().compareTo(curID) == 0) {
if (sim.hasSimulationData()) {
extraText.setFlightData(sim.getSimulatedData());
} else {
extraText.setFlightData(FlightData.NaN_DATA);
}
return true;
}
return false;
}
/**
* Runs a new background simulation for simulations *sims*. It will run all the simulations in sims sequentially
* in the background.
*
* @param sims simulations which should be run
* @param rkt rocket for which the simulations are run
*/
private void runBackgroundSimulations(List<Simulation> sims, Rocket rkt) {
if (sims.size() == 0) {
extraText.setCalculatingData(false);
for (Simulation sim : document.getSimulations()) {
if (updateFlightData(sim)) {
return;
}
}
extraText.setFlightData(FlightData.NaN_DATA);
return;
}
// I *think* every FlightConfiguration has at least one associated simulation; just in case I'm wrong, // I *think* every FlightConfiguration has at least one associated simulation; just in case I'm wrong,
// if there isn't one we'll create a new simulation to update the statistics in the panel using the // if there isn't one we'll create a new simulation to update the statistics in the panel using the
// default simulation conditions // default simulation conditions
if (simulation == null) { for (Simulation sim : sims) {
System.out.println("creating new simulation"); if (sim == null) {
simulation = ((SwingPreferences) Application.getPreferences()).getBackgroundSimulation(duplicate); log.info("creating new simulation");
simulation.setFlightConfigurationId( document.getSelectedConfiguration().getId()); sim = ((SwingPreferences) Application.getPreferences()).getBackgroundSimulation(rkt);
sim.setFlightConfigurationId(document.getSelectedConfiguration().getId());
} else } else
System.out.println("using pre-existing simulation"); log.info("using pre-existing simulation");
backgroundSimulationWorker = new BackgroundSimulationWorker(document, simulation);
backgroundSimulationExecutor.execute(backgroundSimulationWorker);
} }
backgroundSimulationWorker = new BackgroundSimulationWorker(document, sims);
backgroundSimulationExecutor.execute(backgroundSimulationWorker);
} }
/** /**
@ -732,16 +825,20 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
private class BackgroundSimulationWorker extends SimulationWorker { private class BackgroundSimulationWorker extends SimulationWorker {
private final CustomExpressionSimulationListener exprListener; private final CustomExpressionSimulationListener exprListener;
private final OpenRocketDocument doc;
private List<Simulation> sims;
public BackgroundSimulationWorker(OpenRocketDocument doc, Simulation sim) { public BackgroundSimulationWorker(OpenRocketDocument doc, List<Simulation> sims) {
super(sim); super(sims.get(0));
this.sims = sims;
this.doc = doc;
List<CustomExpression> exprs = doc.getCustomExpressions(); List<CustomExpression> exprs = doc.getCustomExpressions();
exprListener = new CustomExpressionSimulationListener(exprs); exprListener = new CustomExpressionSimulationListener(exprs);
} }
@Override @Override
protected FlightData doInBackground() { protected FlightData doInBackground() {
extraText.setCalculatingData(true);
// Pause a little while to allow faster UI reaction // Pause a little while to allow faster UI reaction
try { try {
Thread.sleep(300); Thread.sleep(300);
@ -749,7 +846,6 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
} }
if (isCancelled() || backgroundSimulationWorker != this) if (isCancelled() || backgroundSimulationWorker != this)
return null; return null;
return super.doInBackground(); return super.doInBackground();
} }
@ -758,19 +854,27 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
// Do nothing if cancelled // Do nothing if cancelled
if (isCancelled() || backgroundSimulationWorker != this) if (isCancelled() || backgroundSimulationWorker != this)
return; return;
backgroundSimulationWorker = null; backgroundSimulationWorker = null;
extraText.setFlightData(simulation.getSimulatedData());
// Only set the flight data information of the current flight configuration
extraText.setCalculatingData(false); extraText.setCalculatingData(false);
figure.repaint(); figure.repaint();
figure3d.repaint(); figure3d.repaint();
document.fireDocumentChangeEvent(new SimulationChangeEvent(simulation));
// Run the new simulation after this one has ended
this.sims.remove(0);
if (this.sims.size() > 0) {
backgroundSimulationWorker = new BackgroundSimulationWorker(this.doc, this.sims);
backgroundSimulationExecutor.execute(backgroundSimulationWorker);
}
} }
@Override @Override
protected SimulationListener[] getExtraListeners() { protected SimulationListener[] getExtraListeners() {
return new SimulationListener[] { return new SimulationListener[] {
InterruptListener.INSTANCE, InterruptListener.INSTANCE,
ApogeeEndListener.INSTANCE, GroundHitListener.INSTANCE,
exprListener }; exprListener };
} }