diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties index e1e08101f..3bbd7b2ff 100644 --- a/core/resources/l10n/messages.properties +++ b/core/resources/l10n/messages.properties @@ -1880,7 +1880,7 @@ Warning.RECOVERY_HIGH_SPEED = Recovery device deployment at high speed Warning.NO_RECOVERY_DEVICE = No recovery device defined in the simulation. Warning.TUMBLE_UNDER_THRUST = Stage began to tumble under thrust. Warning.EVENT_AFTER_LANDING = Flight Event occurred after landing: -Warning.ZERO_VOLUME_BODY = Zero-volume bodies may not simulate accurately. +Warning.ZERO_VOLUME_BODY = Zero-volume bodies may not simulate accurately Warning.TUBE_SEPARATION = Space between tube fins may not simulate accurately. Warning.TUBE_OVERLAP = Overlapping tube fins may not simulate accurately. Warning.EMPTY_BRANCH = Simulation branch contains no data diff --git a/core/src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java b/core/src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java index 129f4f17c..bc75f02a6 100644 --- a/core/src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java +++ b/core/src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java @@ -1,5 +1,6 @@ package net.sf.openrocket.aerodynamics; +import static net.sf.openrocket.util.MathUtil.EPSILON; import static net.sf.openrocket.util.MathUtil.pow2; import java.util.*; @@ -324,8 +325,14 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator { .equals(UnitGroup.UNITS_LENGTH.getDefaultUnit().toStringUnit(2.0*prevComp.getAftRadius()))) { warnings.add( Warning.DIAMETER_DISCONTINUITY, prevComp + ", " + sym); } + + // Check for phantom tube + if ((sym.getLength() < MathUtil.EPSILON) || + (sym.getAftRadius() < MathUtil.EPSILON && sym.getForeRadius() < MathUtil.EPSILON)) { + warnings.add(Warning.ZERO_VOLUME_BODY, sym.getName()); + } - // check for gap gap or overlap in airframe. We'll use a textual comparison to see if there is a + // check for gap or overlap in airframe. We'll use a textual comparison to see if there is a // gap or overlap, then use arithmetic comparison to see which it is. This won't be quite as reliable // as the case for radius, since we never actually display the absolute X position diff --git a/core/src/net/sf/openrocket/aerodynamics/WarningSet.java b/core/src/net/sf/openrocket/aerodynamics/WarningSet.java index 6f709e83b..6fcd7b837 100644 --- a/core/src/net/sf/openrocket/aerodynamics/WarningSet.java +++ b/core/src/net/sf/openrocket/aerodynamics/WarningSet.java @@ -76,7 +76,7 @@ public class WarningSet extends AbstractSet implements Cloneable, Monit * */ public boolean add (Warning w, String d) { - return this.add(w.toString() + ": " + d); + return this.add(w.toString() + ": \"" + d + "\""); } @Override diff --git a/core/src/net/sf/openrocket/aerodynamics/barrowman/FinSetCalc.java b/core/src/net/sf/openrocket/aerodynamics/barrowman/FinSetCalc.java index 1bafd4abc..53746d2e6 100644 --- a/core/src/net/sf/openrocket/aerodynamics/barrowman/FinSetCalc.java +++ b/core/src/net/sf/openrocket/aerodynamics/barrowman/FinSetCalc.java @@ -46,10 +46,9 @@ public class FinSetCalc extends RocketComponentCalc { protected final WarningSet geometryWarnings = new WarningSet(); private final double[] poly = new double[6]; - + private final double thickness; private final double bodyRadius; - private final double bodyLength; private final int finCount; private final double cantAngle; private final FinSet.CrossSection crossSection; @@ -62,20 +61,17 @@ public class FinSetCalc extends RocketComponentCalc { ///why not put FinSet in the parameter instead? public FinSetCalc(FinSet component) { super(component); - - FinSet fin = component; - thickness = fin.getThickness(); - bodyLength = component.getParent().getLength(); - bodyRadius = fin.getBodyRadius(); - finCount = fin.getFinCount(); + this.thickness = component.getThickness(); + this.bodyRadius = component.getBodyRadius(); + this.finCount = component.getFinCount(); + + this.cantAngle = component.getCantAngle(); + this.span = component.getSpan(); + this.finArea = component.getPlanformArea(); + this.crossSection = component.getCrossSection(); - cantAngle = fin.getCantAngle(); - span = fin.getSpan(); - finArea = fin.getPlanformArea(); - crossSection = fin.getCrossSection(); - - calculateFinGeometry(fin); + calculateFinGeometry(component); calculatePoly(); calculateInterferenceFinCount(component); } @@ -101,10 +97,7 @@ public class FinSetCalc extends RocketComponentCalc { return; } - if ((bodyLength < EPSILON) || (bodyRadius < EPSILON)) { - // Add warnings: Phantom Body - warnings.add(Warning.ZERO_VOLUME_BODY); - }else if( (0 < bodyRadius) && (thickness > bodyRadius / 2)){ + if ((bodyRadius > 0) && (thickness > bodyRadius / 2)){ // Add warnings (radius/2 == diameter/4) warnings.add(Warning.THICK_FIN); } diff --git a/core/src/net/sf/openrocket/document/Simulation.java b/core/src/net/sf/openrocket/document/Simulation.java index 95a93af50..8d4644450 100644 --- a/core/src/net/sf/openrocket/document/Simulation.java +++ b/core/src/net/sf/openrocket/document/Simulation.java @@ -75,7 +75,7 @@ public class Simulation implements ChangeSource, Cloneable { private String name = ""; - private Status status = Status.NOT_SIMULATED; + private Status status; /** The conditions to use */ // TODO: HIGH: Change to use actual conditions class?? @@ -98,7 +98,7 @@ public class Simulation implements ChangeSource, Cloneable { private SimulationOptions simulatedConditions = null; private String simulatedConfigurationDescription = null; private FlightData simulatedData = null; - private int simulatedRocketID = -1; + private int simulatedConfigurationID = -1; /** @@ -147,7 +147,8 @@ public class Simulation implements ChangeSource, Cloneable { this.options = options; - this.setFlightConfigurationId( rocket.getSelectedConfiguration().getFlightConfigurationID()); + final FlightConfiguration config = rocket.getSelectedConfiguration(); + this.setFlightConfigurationId(config.getFlightConfigurationID()); options.addChangeListener(new ConditionListener()); @@ -160,7 +161,7 @@ public class Simulation implements ChangeSource, Cloneable { simulatedData = data; if (this.status == Status.LOADED) { simulatedConditions = options.clone(); - simulatedRocketID = rocket.getModID(); + simulatedConfigurationID = config.getModID(); } } @@ -308,8 +309,10 @@ public class Simulation implements ChangeSource, Cloneable { */ public Status getStatus() { mutex.verify(); + final FlightConfiguration config = rocket.getFlightConfiguration(this.getId()).clone(); + if (isStatusUpToDate(status)) { - if (rocket.getFunctionalModID() != simulatedRocketID || !options.equals(simulatedConditions)) { + if (config.getModID() != simulatedConfigurationID || !options.equals(simulatedConditions)) { status = Status.OUTDATED; } } @@ -320,8 +323,6 @@ public class Simulation implements ChangeSource, Cloneable { status = Status.CANT_RUN; return status; } - - FlightConfiguration config = rocket.getFlightConfiguration( this.getId()).clone(); //Make sure this simulation has motors. if ( ! config.hasMotors() ){ @@ -338,7 +339,13 @@ public class Simulation implements ChangeSource, Cloneable { public static boolean isStatusUpToDate(Status status) { return status == Status.UPTODATE || status == Status.LOADED || status == Status.EXTERNAL; } - + + /** + * Syncs the modID with its flight configuration. + */ + public void syncModID() { + this.simulatedConfigurationID = getActiveConfiguration().getModID(); + } /** @@ -392,7 +399,7 @@ public class Simulation implements ChangeSource, Cloneable { // Set simulated info after simulation simulatedConditions = options.clone(); simulatedConfigurationDescription = descriptor.format( this.rocket, getId()); - simulatedRocketID = rocket.getFunctionalModID(); + simulatedConfigurationID = getActiveConfiguration().getModID(); status = Status.UPTODATE; fireChangeEvent(); @@ -492,7 +499,7 @@ public class Simulation implements ChangeSource, Cloneable { copy.simulatedConditions = null; copy.simulatedConfigurationDescription = null; copy.simulatedData = null; - copy.simulatedRocketID = -1; + copy.simulatedConfigurationID = -1; return copy; diff --git a/core/src/net/sf/openrocket/file/openrocket/importt/OpenRocketLoader.java b/core/src/net/sf/openrocket/file/openrocket/importt/OpenRocketLoader.java index 5460d3c99..e0bf4717b 100644 --- a/core/src/net/sf/openrocket/file/openrocket/importt/OpenRocketLoader.java +++ b/core/src/net/sf/openrocket/file/openrocket/importt/OpenRocketLoader.java @@ -4,6 +4,7 @@ import java.io.IOException; import java.io.InputStream; import java.util.List; +import net.sf.openrocket.rocketcomponent.ComponentChangeEvent; import net.sf.openrocket.rocketcomponent.FlightConfiguration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -62,6 +63,7 @@ public class OpenRocketLoader extends AbstractRocketLoader { // Deduce suitable time skip double timeSkip = StorageOptions.SIMULATION_DATA_NONE; for (Simulation s : doc.getSimulations()) { + s.syncModID(); // The config's modID can be out of sync with the simulation's after the whole loading process if (s.getStatus() == Simulation.Status.EXTERNAL || s.getStatus() == Simulation.Status.NOT_SIMULATED) continue; diff --git a/core/src/net/sf/openrocket/rocketcomponent/FlightConfiguration.java b/core/src/net/sf/openrocket/rocketcomponent/FlightConfiguration.java index 307b1e2d4..889d0f6d7 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/FlightConfiguration.java +++ b/core/src/net/sf/openrocket/rocketcomponent/FlightConfiguration.java @@ -67,6 +67,7 @@ public class FlightConfiguration implements FlightConfigurableParameter preloadStageActiveness = null; final private Collection activeMotors = new ConcurrentLinkedQueue(); final private InstanceMap activeInstances = new InstanceMap(); + final private InstanceMap extraRenderInstances = new InstanceMap(); // Extra instances to be rendered, besides the active instances private int boundsModID = -1; private BoundingBox cachedBoundsAerodynamic = new BoundingBox(); // Bounding box of all aerodynamic components @@ -159,8 +160,6 @@ public class FlightConfiguration implements FlightConfigurableParameter 0 && + return stage != null && stage.getChildCount() > 0 && // Stages with no children are marked as inactive stages.get(stageNumber) != null && stages.get(stageNumber).active; } @@ -398,6 +389,15 @@ public class FlightConfiguration implements FlightConfigurableParameter stagesBackup = new HashMap<>(this.stages); @@ -893,13 +905,7 @@ public class FlightConfiguration implements FlightConfigurableParameter()); + public void emplace(final RocketComponent component, int number, final Transformation transform) { + if (!containsKey(component)) { + put(component, new ArrayList<>()); } - final InstanceContext context = new InstanceContext(component, number, xform); - get(key).add(context); + final InstanceContext context = new InstanceContext(component, number, transform); + get(component).add(context); } public List getInstanceContexts(final RocketComponent key) { diff --git a/core/src/net/sf/openrocket/rocketcomponent/Rocket.java b/core/src/net/sf/openrocket/rocketcomponent/Rocket.java index fffc0ac1e..0661e39ce 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/Rocket.java +++ b/core/src/net/sf/openrocket/rocketcomponent/Rocket.java @@ -485,17 +485,40 @@ public class Rocket extends ComponentAssembly { listenerList.remove(l); log.trace("Removed ComponentChangeListener " + l + ", current number of listeners is " + listenerList.size()); } - - @Override - protected void fireComponentChangeEvent(ComponentChangeEvent cce) { - if( ! this.eventsEnabled ){ + + /** + * Fires a ComponentChangeEvent of the given type. The source of the event is set to + * this rocket. + * + * @param type Type of event + * @param ids IDs of the flight configurations to update, or null to update all. + * @see #fireComponentChangeEvent(ComponentChangeEvent) + */ + public void fireComponentChangeEvent(int type, final FlightConfigurationId[] ids) { + fireComponentChangeEvent(new ComponentChangeEvent(this, type), ids); + } + + /** + * Fires a ComponentChangeEvent of the given type. The source of the event is set to + * this rocket. + * + * @param type Type of event + * @param id ID of the flight configurations to update, or null to update all. + * @see #fireComponentChangeEvent(ComponentChangeEvent) + */ + public void fireComponentChangeEvent(int type, FlightConfigurationId id) { + fireComponentChangeEvent(type, new FlightConfigurationId[]{ id }); + } + + protected void fireComponentChangeEvent(ComponentChangeEvent cce, final FlightConfigurationId[] ids) { + if (!this.eventsEnabled) { return; } - + mutex.lock("fireComponentChangeEvent"); try { checkState(); - + // Update modification ID's only for normal (not undo/redo) events if (!cce.isUndoChange()) { modID = UniqueID.next(); @@ -505,32 +528,39 @@ public class Rocket extends ComponentAssembly { aeroModID = modID; if (cce.isTreeChange()) treeModID = modID; - if (cce.isFunctionalChange()) + if (cce.isFunctionalChange()) { functionalModID = modID; + updateConfigurationsModID(ids); + } } - + // Check whether frozen if (freezeList != null) { log.debug("Rocket is in frozen state, adding event " + cce + " info freeze list"); freezeList.add(cce); return; } - + // Notify all components first Iterator iterator = this.iterator(true); while (iterator.hasNext()) { RocketComponent next = iterator.next(); next.componentChanged(cce); } - updateConfigurations(); + updateConfigurations(ids); notifyAllListeners(cce); - + } finally { mutex.unlock("fireComponentChangeEvent"); } } + @Override + protected void fireComponentChangeEvent(ComponentChangeEvent cce) { + fireComponentChangeEvent(cce, null); + } + @Override public void update(){ updateStageNumbers(); @@ -555,11 +585,51 @@ public class Rocket extends ComponentAssembly { trackStage(stage); } } - - private void updateConfigurations(){ - for( FlightConfiguration config : configSet ){ - config.update(); + + /** + * Update the modIDs of the supplied flight configurations. + * @param ids IDs of the flight configurations to update, or null to update all. + */ + private void updateConfigurationsModID(FlightConfigurationId[] ids) { + if (ids == null) { + for (FlightConfiguration config : configSet) { + config.updateModID(); + } + return; } + for (FlightConfiguration config : configSet) { + for (FlightConfigurationId id : ids) { + if (config.getId().equals(id)) { + config.updateModID(); + break; + } + } + } + } + + /** + * Update the flight configurations. + * @param ids IDs of the flight configurations to update, or null to update all. + */ + private void updateConfigurations(FlightConfigurationId[] ids) { + if (ids == null) { + for (FlightConfiguration config : configSet) { + config.update(); + } + return; + } + for (FlightConfiguration config : configSet) { + for (FlightConfigurationId id : ids) { + if (config.getId().equals(id)) { + config.update(); + break; + } + } + } + } + + private void updateConfigurations() { + updateConfigurations(null); } @@ -696,8 +766,8 @@ public class Rocket extends ComponentAssembly { } // Get current configuration: - this.configSet.reset( fcid); - fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE); + this.configSet.reset(fcid); + fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE); } diff --git a/core/src/net/sf/openrocket/simulation/SimulationStatus.java b/core/src/net/sf/openrocket/simulation/SimulationStatus.java index f3457b532..4cb132312 100644 --- a/core/src/net/sf/openrocket/simulation/SimulationStatus.java +++ b/core/src/net/sf/openrocket/simulation/SimulationStatus.java @@ -33,8 +33,7 @@ import net.sf.openrocket.util.WorldCoordinate; */ public class SimulationStatus implements Monitorable { - private static final Logger log = LoggerFactory.getLogger(SimulationStatus.class); - + private SimulationConditions simulationConditions; private FlightConfiguration configuration; private FlightDataBranch flightData; diff --git a/swing/src/net/sf/openrocket/gui/components/StageSelector.java b/swing/src/net/sf/openrocket/gui/components/StageSelector.java index f0e9ce289..1609c16d9 100644 --- a/swing/src/net/sf/openrocket/gui/components/StageSelector.java +++ b/swing/src/net/sf/openrocket/gui/components/StageSelector.java @@ -96,8 +96,10 @@ public class StageSelector extends JPanel implements StateChangeListener { setEnabled(true); putValue(SHORT_DESCRIPTION, trans.get("RocketPanel.btn.Stages.Toggle.ttip")); } - rocket.getSelectedConfiguration().toggleStage(stage.getStageNumber()); - rocket.fireComponentChangeEvent(ComponentChangeEvent.AEROMASS_CHANGE | ComponentChangeEvent.MOTOR_CHANGE ); + FlightConfiguration config = rocket.getSelectedConfiguration(); + config.toggleStage(stage.getStageNumber()); + rocket.fireComponentChangeEvent(ComponentChangeEvent.AEROMASS_CHANGE | ComponentChangeEvent.MOTOR_CHANGE, + config.getFlightConfigurationID()); } } diff --git a/swing/src/net/sf/openrocket/gui/main/FlightConfigurationPanel.java b/swing/src/net/sf/openrocket/gui/main/FlightConfigurationPanel.java index d8fa0a279..edec986cc 100644 --- a/swing/src/net/sf/openrocket/gui/main/FlightConfigurationPanel.java +++ b/swing/src/net/sf/openrocket/gui/main/FlightConfigurationPanel.java @@ -2,6 +2,7 @@ package net.sf.openrocket.gui.main; import java.awt.event.ActionEvent; import java.awt.event.MouseEvent; +import java.util.Collections; import java.util.EventObject; import java.util.LinkedHashMap; import java.util.List; @@ -156,8 +157,9 @@ public class FlightConfigurationPanel extends JPanel implements StateChangeListe * @param duplicate if True, then duplicate configuration operation, if False then create a new configuration */ private void newOrDuplicateConfigAction(boolean duplicate) { - addOrDuplicateConfiguration(duplicate); - configurationChanged(ComponentChangeEvent.MOTOR_CHANGE); + Map newConfigs = addOrDuplicateConfiguration(duplicate); + FlightConfigurationId[] ids = newConfigs.keySet().toArray(new FlightConfigurationId[0]); + configurationChanged(ComponentChangeEvent.MOTOR_CHANGE, ids); stateChanged(null); if (!duplicate) { switch (tabs.getSelectedIndex()) { @@ -179,14 +181,16 @@ public class FlightConfigurationPanel extends JPanel implements StateChangeListe * either create or duplicate configuration * set new configuration as current * create simulation for new configuration + * + * @return the new/duplicated configurations */ - private void addOrDuplicateConfiguration(boolean duplicate) { + private Map addOrDuplicateConfiguration(boolean duplicate) { final Map newConfigs = new LinkedHashMap<>(); // create or duplicate configuration if (duplicate) { List oldIds = getSelectedConfigurationIds(); - if (oldIds == null || oldIds.size() == 0) return; + if (oldIds == null || oldIds.size() == 0) return Collections.emptyMap(); for (FlightConfigurationId oldId : oldIds) { final FlightConfiguration oldConfig = rocket.getFlightConfiguration(oldId); @@ -210,7 +214,7 @@ public class FlightConfigurationPanel extends JPanel implements StateChangeListe } OpenRocketDocument doc = BasicFrame.findDocument(rocket); - if (doc == null) return; + if (doc == null) return Collections.emptyMap(); for (Map.Entry config : newConfigs.entrySet()) { // associate configuration with Id and select it @@ -226,6 +230,7 @@ public class FlightConfigurationPanel extends JPanel implements StateChangeListe // Reset to first selected flight config rocket.setSelectedConfiguration((FlightConfigurationId) newConfigs.keySet().toArray()[0]); + return newConfigs; } private void renameConfigurationAction() { @@ -273,10 +278,14 @@ public class FlightConfigurationPanel extends JPanel implements StateChangeListe return duplicateConfigAction; } + private void configurationChanged(int cce, FlightConfigurationId[] ids) { + motorConfigurationPanel.fireTableDataChanged(cce, ids); + recoveryConfigurationPanel.fireTableDataChanged(cce, ids); + separationConfigurationPanel.fireTableDataChanged(cce, ids); + } + private void configurationChanged(int cce) { - motorConfigurationPanel.fireTableDataChanged(cce); - recoveryConfigurationPanel.fireTableDataChanged(cce); - separationConfigurationPanel.fireTableDataChanged(cce); + configurationChanged(cce, null); } private void updateButtonState() { diff --git a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/FlightConfigurablePanel.java b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/FlightConfigurablePanel.java index 69dc1d532..2d3ef491d 100644 --- a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/FlightConfigurablePanel.java +++ b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/FlightConfigurablePanel.java @@ -64,11 +64,12 @@ public abstract class FlightConfigurablePanel } if (update) { - fireTableDataChanged(ComponentChangeEvent.MOTOR_CHANGE); + fireTableDataChanged(ComponentChangeEvent.MOTOR_CHANGE, fcIds.toArray(new FlightConfigurationId[0])); } else { table.requestFocusInWindow(); } @@ -349,7 +349,7 @@ public class MotorConfigurationPanel extends FlightConfigurablePanel } } - fireTableDataChanged(ComponentChangeEvent.MOTOR_CHANGE); + fireTableDataChanged(ComponentChangeEvent.MOTOR_CHANGE, fcIds.toArray(new FlightConfigurationId[0])); } private void selectIgnition() { @@ -396,7 +396,7 @@ public class MotorConfigurationPanel extends FlightConfigurablePanel } if (update) { - fireTableDataChanged(ComponentChangeEvent.MOTOR_CHANGE); + fireTableDataChanged(ComponentChangeEvent.MOTOR_CHANGE, fcIds.toArray(new FlightConfigurationId[0])); } else { table.requestFocusInWindow(); } @@ -426,7 +426,7 @@ public class MotorConfigurationPanel extends FlightConfigurablePanel } if (update) { - fireTableDataChanged(ComponentChangeEvent.MOTOR_CHANGE); + fireTableDataChanged(ComponentChangeEvent.MOTOR_CHANGE, fcIds.toArray(new FlightConfigurationId[0])); } else { table.requestFocusInWindow(); } diff --git a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/RecoveryConfigurationPanel.java b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/RecoveryConfigurationPanel.java index a1e6e8745..3b272018b 100644 --- a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/RecoveryConfigurationPanel.java +++ b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/RecoveryConfigurationPanel.java @@ -209,7 +209,7 @@ public class RecoveryConfigurationPanel extends FlightConfigurablePanel> entry : config.getActiveInstances().entrySet()) { + addShapesFromInstanceEntries(allShapes, config.getActiveInstances().entrySet()); + addShapesFromInstanceEntries(allShapes, config.getExtraRenderInstances().entrySet()); + } + + private void addShapesFromInstanceEntries(PriorityQueue allShapes, Set>> entries) { + for (Entry> entry : entries) { final RocketComponent comp = entry.getKey(); - // Only draw podsets when they are selected - if ((comp instanceof PodSet || comp instanceof ParallelStage) && preferences.isShowMarkers()) { + // Only draw pod sets and boosters when they are selected + if (preferences.isShowMarkers() && (comp instanceof PodSet || comp instanceof ParallelStage)) { boolean selected = false; // Check if component is in the selection @@ -409,7 +414,7 @@ public class RocketFigure extends AbstractScaleFigure { } } } - + /** * Gets the shapes required to draw the component. *