Merge pull request #2237 from SiboVG/issue-1629

[#1629] Show calculated mass, CG & CD in override tab if not overridden
This commit is contained in:
Joe Pfeiffer 2023-07-05 13:41:17 -06:00 committed by GitHub
commit 60e44e9b16
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 183 additions and 79 deletions

View File

@ -860,7 +860,7 @@ componentanalysisdlg.lbl.reflenght = Reference length:
componentanalysisdlg.lbl.refarea = Reference area: componentanalysisdlg.lbl.refarea = Reference area:
!componentanalysisdlg.But.close =Close !componentanalysisdlg.But.close =Close
componentanalysisdlg.TabStability.Col.Component = Component componentanalysisdlg.TabStability.Col.Component = Component
componentanalysisdlg.TOTAL = Total componentanalysisdlg.TOTAL = Total (Rocket)
componentanalysisdlg.noWarnings = <html><i><font color=\"gray\">No warnings.</font></i> componentanalysisdlg.noWarnings = <html><i><font color=\"gray\">No warnings.</font></i>
! Custom Material dialog ! Custom Material dialog

View File

@ -7,8 +7,16 @@ import java.util.EventObject;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
import net.sf.openrocket.aerodynamics.AerodynamicCalculator;
import net.sf.openrocket.aerodynamics.AerodynamicForces;
import net.sf.openrocket.aerodynamics.BarrowmanCalculator;
import net.sf.openrocket.aerodynamics.FlightConditions;
import net.sf.openrocket.logging.WarningSet;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.startup.Preferences;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -563,6 +571,9 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab
*/ */
public final double getOverrideMass() { public final double getOverrideMass() {
mutex.verify(); mutex.verify();
if (!isMassOverridden()) {
overrideMass = getComponentMass();
}
return overrideMass; return overrideMass;
} }
@ -613,6 +624,12 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab
} }
checkState(); checkState();
massOverridden = o; massOverridden = o;
// If mass not overridden, set override mass to the component mass
if (!massOverridden) {
overrideMass = getComponentMass();
}
updateChildrenMassOverriddenBy(); updateChildrenMassOverriddenBy();
fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE); fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
} }
@ -628,6 +645,9 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab
*/ */
public final Coordinate getOverrideCG() { public final Coordinate getOverrideCG() {
mutex.verify(); mutex.verify();
if (!isCGOverridden()) {
overrideCGX = getComponentCG().x;
}
return getComponentCG().setX(overrideCGX); return getComponentCG().setX(overrideCGX);
} }
@ -638,6 +658,9 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab
*/ */
public final double getOverrideCGX() { public final double getOverrideCGX() {
mutex.verify(); mutex.verify();
if (!isCGOverridden()) {
overrideCGX = getComponentCG().x;
}
return overrideCGX; return overrideCGX;
} }
@ -688,10 +711,53 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab
} }
checkState(); checkState();
cgOverridden = o; cgOverridden = o;
// If CG not overridden, set override CG to the component CG
if (!cgOverridden) {
overrideCGX = getComponentCG().x;
}
updateChildrenCGOverriddenBy(); updateChildrenCGOverriddenBy();
fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE); fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
} }
/**
* Calculates and returns the CD of the component.
* TODO: LOW: should this value be cached instead of recalculated every time?
* @param AOA angle of attack to use in the calculations (in radians)
* @param theta wind direction to use in the calculations (in radians)
* @param mach mach number to use in the calculations
* @param rollRate roll rate to use in the calculations (in radians per second)
* @return the CD of the component
*/
public double getComponentCD(double AOA, double theta, double mach, double rollRate) {
Rocket rocket;
try {
rocket = getRocket();
} catch (IllegalStateException e) {
// This can happen due to a race condition when a loadFrom() action is performed of the rocket (after
// an undo operation) but the rocket is not yet fully loaded (the sustainer does not yet have the rocket as
// its parent => getRocket() will not return the rocket, but the sustainer). In that case, just return 0 and
// hope that a future call of this method will succeed.
return 0;
}
final FlightConfiguration configuration = rocket.getSelectedConfiguration();
FlightConditions conditions = new FlightConditions(configuration);
WarningSet warnings = new WarningSet();
AerodynamicCalculator aerodynamicCalculator = new BarrowmanCalculator();
conditions.setAOA(AOA);
conditions.setTheta(theta);
conditions.setMach(mach);
conditions.setRollRate(rollRate);
Map<RocketComponent, AerodynamicForces> aeroData = aerodynamicCalculator.getForceAnalysis(configuration, conditions, warnings);
AerodynamicForces forces = aeroData.get(this);
if (forces != null) {
return forces.getCD();
}
return 0;
}
/** Return the current override CD. The CD is not necessarily overridden. /** Return the current override CD. The CD is not necessarily overridden.
* *
@ -699,6 +765,10 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab
*/ */
public final double getOverrideCD() { public final double getOverrideCD() {
mutex.verify(); mutex.verify();
if (!isCDOverridden()) {
Preferences preferences = Application.getPreferences();
overrideCD = getComponentCD(0, 0, preferences.getDefaultMach(), 0);
}
return overrideCD; return overrideCD;
} }
@ -750,7 +820,7 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab
listener.setCDOverridden(o); listener.setCDOverridden(o);
} }
if(cdOverridden == o) { if (cdOverridden == o) {
return; return;
} }
checkState(); checkState();
@ -764,6 +834,11 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab
if (isSubcomponentsOverriddenCD()) { if (isSubcomponentsOverriddenCD()) {
overrideSubcomponentsCD(o); overrideSubcomponentsCD(o);
} }
if (!cdOverridden) {
Preferences preferences = Application.getPreferences();
overrideCD = getComponentCD(0, 0, preferences.getDefaultMach(), 0);
}
fireComponentChangeEvent(ComponentChangeEvent.AERODYNAMIC_CHANGE); fireComponentChangeEvent(ComponentChangeEvent.AERODYNAMIC_CHANGE);
} }
@ -2614,7 +2689,7 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab
*/ */
protected List<RocketComponent> copyFrom(RocketComponent src) { protected List<RocketComponent> copyFrom(RocketComponent src) {
checkState(); checkState();
List<RocketComponent> toInvalidate = new ArrayList<RocketComponent>(); List<RocketComponent> toInvalidate = new ArrayList<>();
if (this.parent != null) { if (this.parent != null) {
throw new UnsupportedOperationException("copyFrom called for non-root component, parent=" + throw new UnsupportedOperationException("copyFrom called for non-root component, parent=" +

View File

@ -11,6 +11,7 @@ import java.awt.event.ActionEvent;
import java.awt.event.ActionListener; import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter; import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent; import java.awt.event.WindowEvent;
import java.text.DecimalFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.EventObject; import java.util.EventObject;
import java.util.List; import java.util.List;
@ -31,9 +32,10 @@ import javax.swing.JToggleButton;
import javax.swing.ListSelectionModel; import javax.swing.ListSelectionModel;
import javax.swing.SwingConstants; import javax.swing.SwingConstants;
import javax.swing.SwingUtilities; import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener; import javax.swing.event.ChangeListener;
import javax.swing.table.TableCellRenderer; import javax.swing.table.DefaultTableCellRenderer;
import net.miginfocom.swing.MigLayout; import net.miginfocom.swing.MigLayout;
import net.sf.openrocket.aerodynamics.AerodynamicCalculator; import net.sf.openrocket.aerodynamics.AerodynamicCalculator;
@ -213,7 +215,7 @@ public class ComponentAnalysisDialog extends JDialog implements StateChangeListe
@Override @Override
public Object getValueAt(int row) { public Object getValueAt(int row) {
return unit.toString(stabData.get(row).eachMass); return unit.toUnit(stabData.get(row).eachMass);
} }
}, },
new Column(trans.get("componentanalysisdlg.TabStability.Col.AllMass") + " (" + UnitGroup.UNITS_MASS.getDefaultUnit().getUnit() + ")") { new Column(trans.get("componentanalysisdlg.TabStability.Col.AllMass") + " (" + UnitGroup.UNITS_MASS.getDefaultUnit().getUnit() + ")") {
@ -221,7 +223,7 @@ public class ComponentAnalysisDialog extends JDialog implements StateChangeListe
@Override @Override
public Object getValueAt(int row) { public Object getValueAt(int row) {
return unit.toString(stabData.get(row).cm.weight); return unit.toUnit(stabData.get(row).cm.weight);
} }
}, },
new Column(trans.get("componentanalysisdlg.TabStability.Col.CG") + " (" + UnitGroup.UNITS_LENGTH.getDefaultUnit().getUnit() + ")") { new Column(trans.get("componentanalysisdlg.TabStability.Col.CG") + " (" + UnitGroup.UNITS_LENGTH.getDefaultUnit().getUnit() + ")") {
@ -229,7 +231,7 @@ public class ComponentAnalysisDialog extends JDialog implements StateChangeListe
@Override @Override
public Object getValueAt(int row) { public Object getValueAt(int row) {
return unit.toString(stabData.get(row).cm.x); return unit.toUnit(stabData.get(row).cm.x);
} }
}, },
new Column(trans.get("componentanalysisdlg.TabStability.Col.CP") + " (" + UnitGroup.UNITS_LENGTH.getDefaultUnit().getUnit() + ")") { new Column(trans.get("componentanalysisdlg.TabStability.Col.CP") + " (" + UnitGroup.UNITS_LENGTH.getDefaultUnit().getUnit() + ")") {
@ -237,13 +239,13 @@ public class ComponentAnalysisDialog extends JDialog implements StateChangeListe
@Override @Override
public Object getValueAt(int row) { public Object getValueAt(int row) {
return unit.toString(stabData.get(row).cpx); return unit.toUnit(stabData.get(row).cpx);
} }
}, },
new Column("<html>C<sub>N<sub>" + ALPHA + "</sub></sub>") { new Column("<html>C<sub>N<sub>" + ALPHA + "</sub></sub>") {
@Override @Override
public Object getValueAt(int row) { public Object getValueAt(int row) {
return NOUNIT.toString(stabData.get(row).cna); return NOUNIT.toUnit(stabData.get(row).cna);
} }
} }
@ -261,14 +263,10 @@ public class ComponentAnalysisDialog extends JDialog implements StateChangeListe
}; };
table = new ColumnTable(longitudeStabilityTableModel); table = new ColumnTable(longitudeStabilityTableModel);
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); table.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
table.setSelectionBackground(Color.LIGHT_GRAY);
table.setSelectionForeground(Color.BLACK);
longitudeStabilityTableModel.setColumnWidths(table.getColumnModel()); longitudeStabilityTableModel.setColumnWidths(table.getColumnModel());
table.setDefaultRenderer(Object.class, new CustomCellRenderer()); table.setDefaultRenderer(Object.class, new StabilityCellRenderer());
// table.setShowHorizontalLines(false);
// table.setShowVerticalLines(true);
JScrollPane scrollpane = new JScrollPane(table); JScrollPane scrollpane = new JScrollPane(table);
scrollpane.setPreferredSize(new Dimension(600, 200)); scrollpane.setPreferredSize(new Dimension(600, 200));
@ -339,14 +337,10 @@ public class ComponentAnalysisDialog extends JDialog implements StateChangeListe
table = new JTable(dragTableModel); table = new JTable(dragTableModel);
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); table.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
table.setSelectionBackground(Color.LIGHT_GRAY);
table.setSelectionForeground(Color.BLACK);
dragTableModel.setColumnWidths(table.getColumnModel()); dragTableModel.setColumnWidths(table.getColumnModel());
table.setDefaultRenderer(Object.class, new DragCellRenderer(new Color(0.5f, 1.0f, 0.5f))); table.setDefaultRenderer(Object.class, new DragCellRenderer());
// table.setShowHorizontalLines(false);
// table.setShowVerticalLines(true);
scrollpane = new JScrollPane(table); scrollpane = new JScrollPane(table);
scrollpane.setPreferredSize(new Dimension(600, 200)); scrollpane.setPreferredSize(new Dimension(600, 200));
@ -406,10 +400,8 @@ public class ComponentAnalysisDialog extends JDialog implements StateChangeListe
table = new JTable(rollTableModel); table = new JTable(rollTableModel);
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); table.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
table.setSelectionBackground(Color.LIGHT_GRAY); table.setDefaultRenderer(Object.class, new RollDynamicsCellRenderer());
table.setSelectionForeground(Color.BLACK);
table.setDefaultRenderer(Object.class, new CustomCellRenderer());
scrollpane = new JScrollPane(table); scrollpane = new JScrollPane(table);
scrollpane.setPreferredSize(new Dimension(600, 200)); scrollpane.setPreferredSize(new Dimension(600, 200));
@ -622,94 +614,131 @@ public class ComponentAnalysisDialog extends JDialog implements StateChangeListe
rollTableModel.fireTableDataChanged(); rollTableModel.fireTableDataChanged();
} }
private class CustomCellRenderer extends JLabel implements TableCellRenderer { /**
/** * Default cell renderer for the tables.
* */
*/ private class CustomCellRenderer extends DefaultTableCellRenderer {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private final Font normalFont; protected final Font normalFont;
private final Font boldFont; protected final Font boldFont;
public CustomCellRenderer() { private final List<?> data;
protected final int decimalPlaces;
public CustomCellRenderer(List<?> data, int decimalPlaces) {
super(); super();
normalFont = getFont(); this.decimalPlaces = decimalPlaces;
boldFont = normalFont.deriveFont(Font.BOLD); this.data = data;
this.normalFont = getFont();
this.boldFont = normalFont.deriveFont(Font.BOLD);
} }
@Override @Override
public Component getTableCellRendererComponent(JTable table, Object value, public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column) { boolean isSelected, boolean hasFocus, int row, int column) {
JLabel label = (JLabel) super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
this.setText(value == null ? null : value.toString()); if (value instanceof Double) {
label.setText(formatDouble((Double) value));
if ((row < 0) || (row >= stabData.size()))
return this;
if ( 0 == row ) {
this.setFont(boldFont);
} else { } else {
this.setFont(normalFont); // Other
label.setText(value != null ? value.toString() : null);
} }
return this;
label.setOpaque(true);
label.setBackground(Color.WHITE);
label.setHorizontalAlignment(SwingConstants.LEFT);
if ((row < 0) || (row >= data.size()))
return label;
// Set selected color
if (isSelected) {
label.setBackground(table.getSelectionBackground());
label.setForeground((Color) UIManager.get("Table.selectionForeground"));
} else {
label.setForeground(table.getForeground());
}
return label;
}
protected String formatDouble(Double value) {
DecimalFormat df = new DecimalFormat("0." + "#".repeat(Math.max(0, decimalPlaces)));
return df.format(value);
} }
} }
private class StabilityCellRenderer extends CustomCellRenderer {
public StabilityCellRenderer() {
super(stabData, 3);
}
private class DragCellRenderer extends JLabel implements TableCellRenderer { @Override
/** public Component getTableCellRendererComponent(JTable table, Object value,
* boolean isSelected, boolean hasFocus, int row, int column) {
*/ JLabel label = (JLabel) super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
if (row == 0) {
this.setFont(boldFont);
} else {
this.setFont(normalFont);
}
return label;
}
}
private class DragCellRenderer extends CustomCellRenderer {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private final Font normalFont;
private final Font boldFont;
public DragCellRenderer(Color baseColor) { public DragCellRenderer() {
super(); super(dragData, 3);
normalFont = getFont();
boldFont = normalFont.deriveFont(Font.BOLD);
} }
@Override @Override
public Component getTableCellRendererComponent(JTable table, Object value, public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column) { boolean isSelected, boolean hasFocus, int row, int column) {
JLabel label = (JLabel) super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
if (value instanceof Double) { if (!isSelected && (value instanceof Double)) {
final double totalCD = dragData.get(0).getCD();
// A drag coefficient
double cd = (Double) value; double cd = (Double) value;
this.setText(String.format("%.3f (%.0f%%)", cd, 100 * cd / totalCD));
float r = (float) (cd / 1.5); float r = (float) (cd / 1.5);
float hue = MathUtil.clamp(0.3333f * (1 - 2.0f * r), 0, 0.3333f); float hue = MathUtil.clamp(0.3333f * (1 - 2.0f * r), 0, 0.3333f);
float sat = MathUtil.clamp(0.8f * r + 0.1f * (1 - r), 0, 1); float sat = MathUtil.clamp(0.8f * r + 0.1f * (1 - r), 0, 1);
float val = 1.0f; float val = 1.0f;
this.setBackground(Color.getHSBColor(hue, sat, val)); label.setBackground(Color.getHSBColor(hue, sat, val));
this.setOpaque(true);
this.setHorizontalAlignment(SwingConstants.CENTER);
} else {
// Other
this.setText(value.toString());
this.setOpaque(false);
this.setHorizontalAlignment(SwingConstants.LEFT);
} }
if ((row < 0) || (row >= dragData.size())) if ((row < 0) || (row >= dragData.size()))
return this; return label;
if ((dragData.get(row).getComponent() instanceof Rocket) || (column == 4)) { if ((dragData.get(row).getComponent() instanceof Rocket) || (column == 4)) {
this.setFont(boldFont); label.setFont(boldFont);
} else { } else {
this.setFont(normalFont); label.setFont(normalFont);
} }
return this;
return label;
}
@Override
protected String formatDouble(Double cd) {
final double totalCD = dragData.get(0).getCD();
DecimalFormat df = new DecimalFormat("0." + "#".repeat(Math.max(0, decimalPlaces)));
String cdFormatted = df.format(cd);
return String.format(cdFormatted + " (%.0f%%)", cd, 100 * cd / totalCD);
}
}
private class RollDynamicsCellRenderer extends CustomCellRenderer {
public RollDynamicsCellRenderer() {
super(rollData, 3);
} }
} }

View File

@ -363,9 +363,9 @@ public abstract class FlightConfigurablePanel<T extends FlightConfigurableCompon
} }
} }
private final void setSelected( JComponent c, JTable table, boolean isSelected, boolean hasFocus ) { private void setSelected(JComponent c, JTable table, boolean isSelected, boolean hasFocus) {
c.setOpaque(true); c.setOpaque(true);
if ( isSelected) { if (isSelected) {
c.setBackground(table.getSelectionBackground()); c.setBackground(table.getSelectionBackground());
c.setForeground((Color)UIManager.get("Table.selectionForeground")); c.setForeground((Color)UIManager.get("Table.selectionForeground"));
} else { } else {
@ -373,11 +373,11 @@ public abstract class FlightConfigurablePanel<T extends FlightConfigurableCompon
c.setForeground(c.getForeground()); c.setForeground(c.getForeground());
} }
Border b = null; Border b = null;
if ( hasFocus ) { if (hasFocus) {
if (isSelected) { if (isSelected) {
b = UIManager.getBorder("Table.focusSelectedCellHighlightBorder"); b = UIManager.getBorder("Table.focusSelectedCellHighlightBorder");
} else { } else {
b = UIManager.getBorder("Table.focusCellHighligtBorder"); b = UIManager.getBorder("Table.focusCellHighlightBorder");
} }
} else { } else {
b = new EmptyBorder(1,1,1,1); b = new EmptyBorder(1,1,1,1);