DGP - convenience computation of fin tab depth, length, and offset
This commit is contained in:
parent
85b6d7358b
commit
ffccde50fa
@ -583,6 +583,7 @@ FinSetConfig.but.Converttofreeform.ttip = Convert this fin set into a freeform f
|
||||
FinSetConfig.Convertfinset = Convert fin set
|
||||
FinSetConfig.but.Splitfins = Split fins
|
||||
FinSetConfig.but.Splitfins.ttip = Split the fin set into separate fins
|
||||
FinSetConfig.but.Calcheight = Calculate Height
|
||||
FinSetConfig.lbl.Through-the-wall = Through-the-wall fin tabs:
|
||||
FinSetConfig.lbl.Tablength = Tab length:
|
||||
FinSetConfig.ttip.Tablength = The length of the fin tab.
|
||||
|
@ -1,15 +1,5 @@
|
||||
package net.sf.openrocket.gui.configdialog;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JComboBox;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JSpinner;
|
||||
import javax.swing.SwingUtilities;
|
||||
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import net.sf.openrocket.gui.SpinnerEditor;
|
||||
import net.sf.openrocket.gui.adaptors.DoubleModel;
|
||||
@ -20,199 +10,425 @@ import net.sf.openrocket.gui.components.StyledLabel.Style;
|
||||
import net.sf.openrocket.gui.components.UnitSelector;
|
||||
import net.sf.openrocket.l10n.Translator;
|
||||
import net.sf.openrocket.logging.LogHelper;
|
||||
import net.sf.openrocket.rocketcomponent.CenteringRing;
|
||||
import net.sf.openrocket.rocketcomponent.Coaxial;
|
||||
import net.sf.openrocket.rocketcomponent.FinSet;
|
||||
import net.sf.openrocket.rocketcomponent.FreeformFinSet;
|
||||
import net.sf.openrocket.rocketcomponent.InnerTube;
|
||||
import net.sf.openrocket.rocketcomponent.RocketComponent;
|
||||
import net.sf.openrocket.startup.Application;
|
||||
import net.sf.openrocket.unit.UnitGroup;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
public abstract class FinSetConfig extends RocketComponentConfig {
|
||||
private static final LogHelper log = Application.getLogger();
|
||||
private static final Translator trans = Application.getTranslator();
|
||||
private static final LogHelper log = Application.getLogger();
|
||||
private static final Translator trans = Application.getTranslator();
|
||||
|
||||
private JButton split = null;
|
||||
|
||||
public FinSetConfig(RocketComponent component) {
|
||||
super(component);
|
||||
|
||||
//// Fin tabs and Through-the-wall fin tabs
|
||||
tabbedPane.insertTab(trans.get("FinSetConfig.tab.Fintabs"), null, finTabPanel(),
|
||||
trans.get("FinSetConfig.tab.Through-the-wall"), 0);
|
||||
}
|
||||
|
||||
|
||||
private JButton split = null;
|
||||
|
||||
protected void addFinSetButtons() {
|
||||
JButton convert = null;
|
||||
|
||||
//// Convert buttons
|
||||
if (!(component instanceof FreeformFinSet)) {
|
||||
//// Convert to freeform
|
||||
convert = new JButton(trans.get("FinSetConfig.but.Converttofreeform"));
|
||||
//// Convert this fin set into a freeform fin set
|
||||
convert.setToolTipText(trans.get("FinSetConfig.but.Converttofreeform.ttip"));
|
||||
convert.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
log.user("Converting " + component.getComponentName() + " into freeform fin set");
|
||||
|
||||
// Do change in future for overall safety
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
//// Convert fin set
|
||||
ComponentConfigDialog.addUndoPosition(trans.get("FinSetConfig.Convertfinset"));
|
||||
RocketComponent freeform =
|
||||
FreeformFinSet.convertFinSet((FinSet) component);
|
||||
ComponentConfigDialog.showDialog(freeform);
|
||||
}
|
||||
});
|
||||
|
||||
ComponentConfigDialog.hideDialog();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//// Split fins
|
||||
split = new JButton(trans.get("FinSetConfig.but.Splitfins"));
|
||||
//// Split the fin set into separate fins
|
||||
split.setToolTipText(trans.get("FinSetConfig.but.Splitfins.ttip"));
|
||||
split.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
log.user("Splitting " + component.getComponentName() + " into separate fins, fin count=" +
|
||||
((FinSet) component).getFinCount());
|
||||
|
||||
// Do change in future for overall safety
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
RocketComponent parent = component.getParent();
|
||||
int index = parent.getChildPosition(component);
|
||||
int count = ((FinSet) component).getFinCount();
|
||||
double base = ((FinSet) component).getBaseRotation();
|
||||
if (count <= 1)
|
||||
return;
|
||||
|
||||
ComponentConfigDialog.addUndoPosition("Split fin set");
|
||||
parent.removeChild(index);
|
||||
for (int i = 0; i < count; i++) {
|
||||
FinSet copy = (FinSet) component.copy();
|
||||
copy.setFinCount(1);
|
||||
copy.setBaseRotation(base + i * 2 * Math.PI / count);
|
||||
copy.setName(copy.getName() + " #" + (i + 1));
|
||||
parent.addChild(copy, index + i);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ComponentConfigDialog.hideDialog();
|
||||
}
|
||||
});
|
||||
split.setEnabled(((FinSet) component).getFinCount() > 1);
|
||||
|
||||
if (convert == null)
|
||||
addButtons(split);
|
||||
else
|
||||
addButtons(split, convert);
|
||||
|
||||
}
|
||||
|
||||
public JPanel finTabPanel() {
|
||||
JPanel panel = new JPanel(
|
||||
new MigLayout("align 50% 20%, fillx, gap rel unrel, ins 20lp 10% 20lp 10%",
|
||||
"[150lp::][65lp::][30lp::][200lp::]", ""));
|
||||
// JPanel panel = new JPanel(new MigLayout("fillx, align 20% 20%, gap rel unrel",
|
||||
// "[40lp][80lp::][30lp::][100lp::]",""));
|
||||
|
||||
//// Through-the-wall fin tabs:
|
||||
panel.add(new StyledLabel(trans.get("FinSetConfig.lbl.Through-the-wall"), Style.BOLD),
|
||||
"spanx, wrap 30lp");
|
||||
|
||||
JLabel label;
|
||||
DoubleModel m;
|
||||
DoubleModel length;
|
||||
DoubleModel length2;
|
||||
DoubleModel length_2;
|
||||
JSpinner spin;
|
||||
|
||||
length = new DoubleModel(component, "Length", UnitGroup.UNITS_LENGTH, 0);
|
||||
length2 = new DoubleModel(component, "Length", 0.5, UnitGroup.UNITS_LENGTH, 0);
|
||||
length_2 = new DoubleModel(component, "Length", -0.5, UnitGroup.UNITS_LENGTH, 0);
|
||||
|
||||
register(length);
|
||||
register(length2);
|
||||
register(length_2);
|
||||
|
||||
//// Tab length
|
||||
//// Tab length:
|
||||
label = new JLabel(trans.get("FinSetConfig.lbl.Tablength"));
|
||||
//// The length of the fin tab.
|
||||
label.setToolTipText(trans.get("FinSetConfig.ttip.Tablength"));
|
||||
panel.add(label, "gapleft para, gapright 40lp, growx 1");
|
||||
|
||||
m = new DoubleModel(component, "TabLength", UnitGroup.UNITS_LENGTH, 0);
|
||||
|
||||
spin = new JSpinner(m.getSpinnerModel());
|
||||
spin.setEditor(new SpinnerEditor(spin));
|
||||
panel.add(spin, "growx 1");
|
||||
|
||||
panel.add(new UnitSelector(m), "growx 1");
|
||||
panel.add(new BasicSlider(m.getSliderModel(DoubleModel.ZERO, length)),
|
||||
"w 100lp, growx 5, wrap");
|
||||
|
||||
public FinSetConfig(RocketComponent component) {
|
||||
super(component);
|
||||
|
||||
//// Tab length
|
||||
//// Tab height:
|
||||
label = new JLabel(trans.get("FinSetConfig.lbl.Tabheight"));
|
||||
//// The spanwise height of the fin tab.
|
||||
label.setToolTipText(trans.get("FinSetConfig.ttip.Tabheight"));
|
||||
panel.add(label, "gapleft para");
|
||||
|
||||
m = new DoubleModel(component, "TabHeight", UnitGroup.UNITS_LENGTH, 0);
|
||||
|
||||
spin = new JSpinner(m.getSpinnerModel());
|
||||
spin.setEditor(new SpinnerEditor(spin));
|
||||
panel.add(spin, "growx");
|
||||
|
||||
panel.add(new UnitSelector(m), "growx");
|
||||
panel.add(new BasicSlider(m.getSliderModel(DoubleModel.ZERO, length2)),
|
||||
"w 100lp, growx 5, wrap para");
|
||||
|
||||
//// Fin tabs and Through-the-wall fin tabs
|
||||
tabbedPane.insertTab(trans.get("FinSetConfig.tab.Fintabs"), null, finTabPanel(),
|
||||
trans.get("FinSetConfig.tab.Through-the-wall"), 0);
|
||||
}
|
||||
|
||||
//// Tab position:
|
||||
label = new JLabel(trans.get("FinSetConfig.lbl.Tabposition"));
|
||||
//// The position of the fin tab.
|
||||
label.setToolTipText(trans.get("FinSetConfig.ttip.Tabposition"));
|
||||
panel.add(label, "gapleft para");
|
||||
|
||||
m = new DoubleModel(component, "TabShift", UnitGroup.UNITS_LENGTH);
|
||||
|
||||
spin = new JSpinner(m.getSpinnerModel());
|
||||
spin.setEditor(new SpinnerEditor(spin));
|
||||
panel.add(spin, "growx");
|
||||
|
||||
panel.add(new UnitSelector(m), "growx");
|
||||
panel.add(new BasicSlider(m.getSliderModel(length_2, length2)), "w 100lp, growx 5, wrap");
|
||||
|
||||
|
||||
//// relative to
|
||||
label = new JLabel(trans.get("FinSetConfig.lbl.relativeto"));
|
||||
panel.add(label, "right, gapright unrel");
|
||||
|
||||
EnumModel<FinSet.TabRelativePosition> em =
|
||||
new EnumModel<FinSet.TabRelativePosition>(component, "TabRelativePosition");
|
||||
|
||||
panel.add(new JComboBox(em), "spanx 3, growx");
|
||||
|
||||
return panel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFields() {
|
||||
super.updateFields();
|
||||
if (split != null)
|
||||
split.setEnabled(((FinSet) component).getFinCount() > 1);
|
||||
}
|
||||
protected void addFinSetButtons() {
|
||||
JButton convert = null;
|
||||
|
||||
//// Convert buttons
|
||||
if (!(component instanceof FreeformFinSet)) {
|
||||
//// Convert to freeform
|
||||
convert = new JButton(trans.get("FinSetConfig.but.Converttofreeform"));
|
||||
//// Convert this fin set into a freeform fin set
|
||||
convert.setToolTipText(trans.get("FinSetConfig.but.Converttofreeform.ttip"));
|
||||
convert.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
log.user("Converting " + component.getComponentName() + " into freeform fin set");
|
||||
|
||||
// Do change in future for overall safety
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
//// Convert fin set
|
||||
ComponentConfigDialog.addUndoPosition(trans.get("FinSetConfig.Convertfinset"));
|
||||
RocketComponent freeform =
|
||||
FreeformFinSet.convertFinSet((FinSet) component);
|
||||
ComponentConfigDialog.showDialog(freeform);
|
||||
}
|
||||
});
|
||||
|
||||
ComponentConfigDialog.hideDialog();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//// Split fins
|
||||
split = new JButton(trans.get("FinSetConfig.but.Splitfins"));
|
||||
//// Split the fin set into separate fins
|
||||
split.setToolTipText(trans.get("FinSetConfig.but.Splitfins.ttip"));
|
||||
split.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
log.user("Splitting " + component.getComponentName() + " into separate fins, fin count=" +
|
||||
((FinSet) component).getFinCount());
|
||||
|
||||
// Do change in future for overall safety
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
RocketComponent parent = component.getParent();
|
||||
int index = parent.getChildPosition(component);
|
||||
int count = ((FinSet) component).getFinCount();
|
||||
double base = ((FinSet) component).getBaseRotation();
|
||||
if (count <= 1)
|
||||
return;
|
||||
|
||||
ComponentConfigDialog.addUndoPosition("Split fin set");
|
||||
parent.removeChild(index);
|
||||
for (int i = 0; i < count; i++) {
|
||||
FinSet copy = (FinSet) component.copy();
|
||||
copy.setFinCount(1);
|
||||
copy.setBaseRotation(base + i * 2 * Math.PI / count);
|
||||
copy.setName(copy.getName() + " #" + (i + 1));
|
||||
parent.addChild(copy, index + i);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ComponentConfigDialog.hideDialog();
|
||||
}
|
||||
});
|
||||
split.setEnabled(((FinSet) component).getFinCount() > 1);
|
||||
|
||||
if (convert == null)
|
||||
addButtons(split);
|
||||
else
|
||||
addButtons(split, convert);
|
||||
|
||||
}
|
||||
|
||||
public JPanel finTabPanel() {
|
||||
JPanel panel = new JPanel(
|
||||
new MigLayout("align 50% 20%, fillx, gap rel unrel, ins 20lp 10% 20lp 10%",
|
||||
"[150lp::][65lp::][30lp::][200lp::]", ""));
|
||||
// JPanel panel = new JPanel(new MigLayout("fillx, align 20% 20%, gap rel unrel",
|
||||
// "[40lp][80lp::][30lp::][100lp::]",""));
|
||||
|
||||
//// Through-the-wall fin tabs:
|
||||
panel.add(new StyledLabel(trans.get("FinSetConfig.lbl.Through-the-wall"), Style.BOLD),
|
||||
"spanx, wrap 30lp");
|
||||
|
||||
JLabel label;
|
||||
DoubleModel length;
|
||||
DoubleModel length2;
|
||||
DoubleModel length_2;
|
||||
JSpinner spin;
|
||||
JButton calcHeight;
|
||||
|
||||
length = new DoubleModel(component, "Length", UnitGroup.UNITS_LENGTH, 0);
|
||||
length2 = new DoubleModel(component, "Length", 0.5, UnitGroup.UNITS_LENGTH, 0);
|
||||
length_2 = new DoubleModel(component, "Length", -0.5, UnitGroup.UNITS_LENGTH, 0);
|
||||
|
||||
register(length);
|
||||
register(length2);
|
||||
register(length_2);
|
||||
|
||||
//// Tab length
|
||||
//// Tab length:
|
||||
label = new JLabel(trans.get("FinSetConfig.lbl.Tablength"));
|
||||
//// The length of the fin tab.
|
||||
label.setToolTipText(trans.get("FinSetConfig.ttip.Tablength"));
|
||||
panel.add(label, "gapleft para, gapright 40lp, growx 1");
|
||||
|
||||
final DoubleModel mtl = new DoubleModel(component, "TabLength", UnitGroup.UNITS_LENGTH, 0);
|
||||
|
||||
spin = new JSpinner(mtl.getSpinnerModel());
|
||||
spin.setEditor(new SpinnerEditor(spin));
|
||||
panel.add(spin, "growx 1");
|
||||
|
||||
panel.add(new UnitSelector(mtl), "growx 1");
|
||||
panel.add(new BasicSlider(mtl.getSliderModel(DoubleModel.ZERO, length)),
|
||||
"w 100lp, growx 5, wrap");
|
||||
|
||||
|
||||
//// Tab length
|
||||
//// Tab height:
|
||||
label = new JLabel(trans.get("FinSetConfig.lbl.Tabheight"));
|
||||
//// The spanwise height of the fin tab.
|
||||
label.setToolTipText(trans.get("FinSetConfig.ttip.Tabheight"));
|
||||
panel.add(label, "gapleft para");
|
||||
|
||||
final DoubleModel mth = new DoubleModel(component, "TabHeight", UnitGroup.UNITS_LENGTH, 0);
|
||||
|
||||
spin = new JSpinner(mth.getSpinnerModel());
|
||||
spin.setEditor(new SpinnerEditor(spin));
|
||||
panel.add(spin, "growx");
|
||||
|
||||
panel.add(new UnitSelector(mth), "growx");
|
||||
panel.add(new BasicSlider(mth.getSliderModel(DoubleModel.ZERO, length2)),
|
||||
"w 100lp, growx 5, wrap para");
|
||||
|
||||
//// Tab position:
|
||||
label = new JLabel(trans.get("FinSetConfig.lbl.Tabposition"));
|
||||
//// The position of the fin tab.
|
||||
label.setToolTipText(trans.get("FinSetConfig.ttip.Tabposition"));
|
||||
panel.add(label, "gapleft para");
|
||||
|
||||
final DoubleModel mts = new DoubleModel(component, "TabShift", UnitGroup.UNITS_LENGTH);
|
||||
|
||||
spin = new JSpinner(mts.getSpinnerModel());
|
||||
spin.setEditor(new SpinnerEditor(spin));
|
||||
panel.add(spin, "growx");
|
||||
|
||||
panel.add(new UnitSelector(mts), "growx");
|
||||
panel.add(new BasicSlider(mts.getSliderModel(length_2, length2)), "w 100lp, growx 5, wrap");
|
||||
|
||||
|
||||
//// relative to
|
||||
label = new JLabel(trans.get("FinSetConfig.lbl.relativeto"));
|
||||
panel.add(label, "right, gapright unrel");
|
||||
|
||||
final EnumModel<FinSet.TabRelativePosition> em =
|
||||
new EnumModel<FinSet.TabRelativePosition>(component, "TabRelativePosition");
|
||||
|
||||
panel.add(new JComboBox(em), "spanx 3, growx");
|
||||
|
||||
calcHeight = new JButton(trans.get("FinSetConfig.but.Calcheight"));
|
||||
|
||||
// Calculate fin tab height, length, and position
|
||||
calcHeight.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
log.user("Computing " + component.getComponentName() + " tab height.");
|
||||
|
||||
// Do change in future for overall safety
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
RocketComponent parent = component.getParent();
|
||||
if (parent instanceof Coaxial) {
|
||||
List<RocketComponent> children = parent.getChildren();
|
||||
List<CenteringRing> rings = new ArrayList<CenteringRing>();
|
||||
|
||||
ComponentConfigDialog.addUndoPosition("Compute fin tab height");
|
||||
for (int i = 0; i < children.size(); i++) {
|
||||
RocketComponent rocketComponent = children.get(i);
|
||||
if (rocketComponent instanceof InnerTube) {
|
||||
InnerTube it = (InnerTube) rocketComponent;
|
||||
if (it.isMotorMount()) {
|
||||
double depth = ((Coaxial) parent).getOuterRadius() - it.getOuterRadius();
|
||||
//Set fin tab depth
|
||||
if (depth >= 0.0d) {
|
||||
mth.setValue(depth);
|
||||
mth.setCurrentUnit(UnitGroup.UNITS_LENGTH.getDefaultUnit());
|
||||
}
|
||||
}
|
||||
} else if (rocketComponent instanceof CenteringRing) {
|
||||
rings.add((CenteringRing) rocketComponent);
|
||||
}
|
||||
}
|
||||
//Figure out position and length of the fin tab
|
||||
if (!rings.isEmpty()) {
|
||||
FinSet.TabRelativePosition temp = (FinSet.TabRelativePosition) em.getSelectedItem();
|
||||
em.setSelectedItem(FinSet.TabRelativePosition.FRONT);
|
||||
double len = computeFinTabLength(rings, component.asPositionValue(RocketComponent.Position.TOP),
|
||||
component.getLength(), mts);
|
||||
mtl.setValue(len);
|
||||
//Be nice to the user and set the tab relative position enum back the way they had it.
|
||||
em.setSelectedItem(temp);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
panel.add(calcHeight, "right, gapright unrel");
|
||||
|
||||
return panel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scenarios:
|
||||
* <p/>
|
||||
* 1. All rings ahead of start of fin.
|
||||
* 2. First ring ahead of start of fin. Second ring ahead of end of fin.
|
||||
* 3. First ring ahead of start of fin. Second ring behind end of fin.
|
||||
* 4. First ring equal or behind start of fin. Second ring ahead of, or equal to, end of fin.
|
||||
* 5. First ring equal or behind start of fin. Second ring behind end of fin.
|
||||
* 6. All rings behind end of fin.
|
||||
*
|
||||
* @param rings an unordered list of centering rings attached to the parent of the fin set
|
||||
* @param finPositionFromTop the position from the top of the parent of the start of the fin set root
|
||||
* @param finLength the length of the root chord
|
||||
* @param mts the model for the tab shift (position); the model's value is modified as a result of this method call
|
||||
* @return the length of the fin tab
|
||||
*/
|
||||
private static double computeFinTabLength(List<CenteringRing> rings, Double finPositionFromTop, Double finLength, DoubleModel mts) {
|
||||
List<SortableRing> positionsFromTop = new ArrayList<SortableRing>();
|
||||
|
||||
//Fin tabs will be computed between the last two rings that meet the criteria, represented by top and bottom here.
|
||||
SortableRing top = null;
|
||||
SortableRing bottom = null;
|
||||
|
||||
if (rings != null) {
|
||||
//Sort rings from top of parent to bottom
|
||||
Collections.sort(rings, new Comparator<CenteringRing>() {
|
||||
@Override
|
||||
public int compare(CenteringRing centeringRing, CenteringRing centeringRing1) {
|
||||
return (int) (1000d * (centeringRing.asPositionValue(RocketComponent.Position.TOP) -
|
||||
centeringRing1.asPositionValue(RocketComponent.Position.TOP)));
|
||||
}
|
||||
});
|
||||
|
||||
for (int i = 0; i < rings.size(); i++) {
|
||||
CenteringRing centeringRing = rings.get(i);
|
||||
//Handle centering rings that overlap or are adjacent by synthetically merging them into one virtual ring.
|
||||
if (!positionsFromTop.isEmpty() &&
|
||||
positionsFromTop.get(positionsFromTop.size() - 1).bottomSidePositionFromTop() >= centeringRing.asPositionValue(RocketComponent.Position.TOP)) {
|
||||
SortableRing adjacent = positionsFromTop.get(positionsFromTop.size() - 1);
|
||||
adjacent.merge(centeringRing);
|
||||
} else {
|
||||
positionsFromTop.add(new SortableRing(centeringRing));
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < positionsFromTop.size(); i++) {
|
||||
SortableRing sortableRing = positionsFromTop.get(i);
|
||||
if (top == null) {
|
||||
top = sortableRing;
|
||||
} else if (sortableRing.bottomSidePositionFromTop() <= finPositionFromTop) {
|
||||
top = sortableRing;
|
||||
bottom = null;
|
||||
} else if (top.bottomSidePositionFromTop() <= finPositionFromTop) {
|
||||
if (bottom == null) {
|
||||
//If the current ring is in the upper half of the root chord, make it the top ring
|
||||
if (sortableRing.bottomSidePositionFromTop() < finPositionFromTop + finLength / 2d) {
|
||||
top = sortableRing;
|
||||
} else {
|
||||
bottom = sortableRing;
|
||||
}
|
||||
}
|
||||
//Is the ring even with or above the end of the root chord? If so, make the existing bottom the top ring,
|
||||
//and the current ring the bottom
|
||||
else if (sortableRing.positionFromTop() <= finPositionFromTop + finLength) {
|
||||
top = bottom;
|
||||
bottom = sortableRing;
|
||||
}
|
||||
} else {
|
||||
if (bottom == null) {
|
||||
bottom = sortableRing;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Edge case where there are no centering rings or for some odd reason top and bottom are identical.
|
||||
if (top == null || top == bottom) {
|
||||
mts.setValue(0);
|
||||
return finLength;
|
||||
}
|
||||
|
||||
if (bottom == null) {
|
||||
// If there is no bottom ring and the top ring's bottom edge is within the span of the root chord, then
|
||||
// set the position of the fin tab starting at the bottom side of the top ring.
|
||||
if (top.bottomSidePositionFromTop() >= finPositionFromTop) {
|
||||
mts.setValue(top.bottomSidePositionFromTop() - finPositionFromTop);
|
||||
return (finPositionFromTop + finLength - top.bottomSidePositionFromTop());
|
||||
} else {
|
||||
// Otherwise the top ring is outside the span of the root chord so set the tab length to be the entire
|
||||
// root chord.
|
||||
mts.setValue(0);
|
||||
return finLength;
|
||||
}
|
||||
}
|
||||
// If the bottom edge of the top centering ring is above the start of the fin's root chord, then make the
|
||||
// fin tab align with the start of the root chord.
|
||||
if (top.bottomSidePositionFromTop() < finPositionFromTop) {
|
||||
mts.setValue(0);
|
||||
return bottom.positionFromTop - finPositionFromTop;
|
||||
} else {
|
||||
// Otherwise the rings are within the span of the root chord. Place the tab between them.
|
||||
mts.setValue(top.bottomSidePositionFromTop() - finPositionFromTop);
|
||||
return (bottom.positionFromTop() - top.bottomSidePositionFromTop());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFields() {
|
||||
super.updateFields();
|
||||
if (split != null)
|
||||
split.setEnabled(((FinSet) component).getFinCount() > 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* A container class to store pertinent info about centering rings. This is used in the computation to figure
|
||||
* out tab length and position.
|
||||
*/
|
||||
static class SortableRing {
|
||||
|
||||
/**
|
||||
* The length of the ring (more commonly called the thickness).
|
||||
*/
|
||||
private double thickness;
|
||||
/**
|
||||
* The position of the ring from the top of the parent.
|
||||
*/
|
||||
private double positionFromTop;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param r the source centering ring
|
||||
*/
|
||||
SortableRing(CenteringRing r) {
|
||||
thickness = r.getLength();
|
||||
positionFromTop = r.asPositionValue(RocketComponent.Position.TOP);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge an adjacent ring.
|
||||
*
|
||||
* @param adjacent the adjacent ring
|
||||
*/
|
||||
public void merge(CenteringRing adjacent) {
|
||||
double v = adjacent.asPositionValue(RocketComponent.Position.TOP);
|
||||
if (positionFromTop < v) {
|
||||
thickness = (v + adjacent.getLength()) - positionFromTop;
|
||||
} else {
|
||||
double tmp = positionFromTop + thickness;
|
||||
positionFromTop = v;
|
||||
thickness = tmp - v;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the position of the bottom edge of the ring, relative to the top of the parent.
|
||||
*
|
||||
* @return the distance from the top of the parent to the bottom edge of the ring
|
||||
*/
|
||||
public double bottomSidePositionFromTop() {
|
||||
return positionFromTop + thickness;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the position of the top edge of the ring, relative to the top of the parent.
|
||||
*
|
||||
* @return the distance from the top of the parent to the top edge of the ring
|
||||
*/
|
||||
public double positionFromTop() {
|
||||
return positionFromTop;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,5 @@
|
||||
package net.sf.openrocket.rocketcomponent;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Collection;
|
||||
import java.util.Deque;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
import javax.swing.event.ChangeListener;
|
||||
|
||||
import net.sf.openrocket.l10n.Translator;
|
||||
import net.sf.openrocket.logging.LogHelper;
|
||||
import net.sf.openrocket.preset.ComponentPreset;
|
||||
@ -24,6 +14,15 @@ import net.sf.openrocket.util.MathUtil;
|
||||
import net.sf.openrocket.util.SafetyMutex;
|
||||
import net.sf.openrocket.util.UniqueID;
|
||||
|
||||
import javax.swing.event.ChangeListener;
|
||||
import java.awt.*;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Collection;
|
||||
import java.util.Deque;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
|
||||
public abstract class RocketComponent implements ChangeSource, Cloneable, Iterable<RocketComponent> {
|
||||
private static final LogHelper log = Application.getLogger();
|
||||
@ -839,10 +838,44 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab
|
||||
this.relativePosition = position;
|
||||
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Determine position relative to given position argument. Note: This is a side-effect free method. No state
|
||||
* is modified. It's exactly like setRelativePosition without the 'set'.
|
||||
*
|
||||
* @param thePosition the relative position to be used as the basis for the computation
|
||||
*
|
||||
* @return double position of the component relative to the parent, with respect to <code>position</code>
|
||||
*/
|
||||
public double asPositionValue (Position thePosition) {
|
||||
if (this.relativePosition == thePosition) {
|
||||
return this.position;
|
||||
}
|
||||
double result = this.position;
|
||||
if (this.parent != null) {
|
||||
double thisPos = this.toRelative(Coordinate.NUL, this.parent)[0].x;
|
||||
|
||||
switch (thePosition) {
|
||||
case ABSOLUTE:
|
||||
result = this.toAbsolute(Coordinate.NUL)[0].x;
|
||||
break;
|
||||
case TOP:
|
||||
result = thisPos;
|
||||
break;
|
||||
case MIDDLE:
|
||||
result = thisPos - (this.parent.length - this.length) / 2;
|
||||
break;
|
||||
case BOTTOM:
|
||||
result = thisPos - (this.parent.length - this.length);
|
||||
break;
|
||||
default:
|
||||
throw new BugException("Unknown position type: " + thePosition);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the position value of the component. The exact meaning of the value is
|
||||
* dependent on the current relative positioning.
|
||||
|
210
test/net/sf/openrocket/gui/configdialog/FinSetConfigTest.java
Normal file
210
test/net/sf/openrocket/gui/configdialog/FinSetConfigTest.java
Normal file
@ -0,0 +1,210 @@
|
||||
package net.sf.openrocket.gui.configdialog;
|
||||
|
||||
import net.sf.openrocket.gui.adaptors.DoubleModel;
|
||||
import net.sf.openrocket.rocketcomponent.CenteringRing;
|
||||
import org.junit.Assert;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class FinSetConfigTest {
|
||||
|
||||
static Method method;
|
||||
|
||||
@BeforeClass
|
||||
public static void classSetup() throws Exception {
|
||||
method = FinSetConfig.class.getDeclaredMethod("computeFinTabLength", List.class, Double.class, Double.class, DoubleModel.class);
|
||||
Assert.assertNotNull(method);
|
||||
method.setAccessible(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test no centering rings.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
public void testComputeFinTabLength() throws Exception {
|
||||
DoubleModel dm = new DoubleModel(1d);
|
||||
List<CenteringRing> rings = new ArrayList<CenteringRing>();
|
||||
|
||||
Double result = (Double)method.invoke(null, rings, 10d, 11d, dm);
|
||||
Assert.assertEquals(0.0001, 11d, result.doubleValue());
|
||||
result = (Double)method.invoke(null, null, 10d, 11d, dm);
|
||||
Assert.assertEquals(11d, result.doubleValue(), 0.0001);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test 2 rings both ahead of the fin.
|
||||
*/
|
||||
@Test
|
||||
public void testCompute2LeadingRings() throws Exception {
|
||||
DoubleModel dm = new DoubleModel(1d);
|
||||
List<CenteringRing> rings = new ArrayList<CenteringRing>();
|
||||
|
||||
CenteringRing ring1 = new CenteringRing();
|
||||
ring1.setLength(0.004);
|
||||
ring1.setPositionValue(0.43);
|
||||
CenteringRing ring2 = new CenteringRing();
|
||||
ring2.setLength(0.004);
|
||||
ring2.setPositionValue(0.45);
|
||||
rings.add(ring1);
|
||||
rings.add(ring2);
|
||||
|
||||
Double result = (Double)method.invoke(null, rings, 0.47d, 0.01, dm);
|
||||
Assert.assertEquals(0.01, result.doubleValue(), 0.0001);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Test one ring, ahead of the fin.
|
||||
*/
|
||||
@Test
|
||||
public void testCompute1Ring() throws Exception {
|
||||
DoubleModel dm = new DoubleModel(1d);
|
||||
List<CenteringRing> rings = new ArrayList<CenteringRing>();
|
||||
|
||||
CenteringRing ring1 = new CenteringRing();
|
||||
ring1.setLength(0.004);
|
||||
ring1.setPositionValue(0.43);
|
||||
rings.add(ring1);
|
||||
|
||||
Double result = (Double)method.invoke(null, rings, 0.47d, 0.01, dm);
|
||||
Assert.assertEquals(0.01, result.doubleValue(), 0.0001);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test one ring ahead of the fin, one ring within the root chord.
|
||||
*/
|
||||
@Test
|
||||
public void testComputeOneLeadingOneRingWithinRoot() throws Exception {
|
||||
DoubleModel dm = new DoubleModel(1d);
|
||||
List<CenteringRing> rings = new ArrayList<CenteringRing>();
|
||||
|
||||
CenteringRing ring1 = new CenteringRing();
|
||||
ring1.setLength(0.004);
|
||||
ring1.setPositionValue(0.43);
|
||||
CenteringRing ring2 = new CenteringRing();
|
||||
ring2.setLength(0.004);
|
||||
ring2.setPositionValue(0.45);
|
||||
rings.add(ring1);
|
||||
rings.add(ring2);
|
||||
|
||||
Double result = (Double)method.invoke(null, rings, 0.45d, 0.01, dm);
|
||||
Assert.assertEquals(0.01 - 0.004, result.doubleValue(), 0.0001);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test one ring ahead of the fin, one ring beyond the root chord.
|
||||
*/
|
||||
@Test
|
||||
public void testComputeOneLeadingOneTrailingRing() throws Exception {
|
||||
DoubleModel dm = new DoubleModel(1d);
|
||||
List<CenteringRing> rings = new ArrayList<CenteringRing>();
|
||||
|
||||
CenteringRing ring1 = new CenteringRing();
|
||||
ring1.setLength(0.004);
|
||||
ring1.setPositionValue(0.43);
|
||||
CenteringRing ring2 = new CenteringRing();
|
||||
ring2.setLength(0.004);
|
||||
ring2.setPositionValue(0.48);
|
||||
rings.add(ring1);
|
||||
rings.add(ring2);
|
||||
|
||||
Double result = (Double)method.invoke(null, rings, 0.47d, 0.01, dm);
|
||||
Assert.assertEquals(0.01, result.doubleValue(), 0.0001);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test one ring within the root chord, another ring beyond the root chord.
|
||||
*/
|
||||
@Test
|
||||
public void testComputeOneWithinRootOneTrailingRing() throws Exception {
|
||||
DoubleModel dm = new DoubleModel(1d);
|
||||
List<CenteringRing> rings = new ArrayList<CenteringRing>();
|
||||
|
||||
CenteringRing ring1 = new CenteringRing();
|
||||
ring1.setLength(0.004);
|
||||
ring1.setPositionValue(0.4701);
|
||||
CenteringRing ring2 = new CenteringRing();
|
||||
ring2.setLength(0.004);
|
||||
ring2.setPositionValue(0.48);
|
||||
rings.add(ring1);
|
||||
rings.add(ring2);
|
||||
|
||||
Double result = (Double)method.invoke(null, rings, 0.47d, 0.01, dm);
|
||||
Assert.assertEquals(0.0059, result.doubleValue(), 0.0001);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test both rings within the root chord.
|
||||
*/
|
||||
@Test
|
||||
public void testBothRingsWithinRootChord() throws Exception {
|
||||
DoubleModel dm = new DoubleModel(1d);
|
||||
List<CenteringRing> rings = new ArrayList<CenteringRing>();
|
||||
|
||||
CenteringRing ring1 = new CenteringRing();
|
||||
ring1.setLength(0.004);
|
||||
ring1.setPositionValue(0.4701);
|
||||
CenteringRing ring2 = new CenteringRing();
|
||||
ring2.setLength(0.004);
|
||||
ring2.setPositionValue(0.4750);
|
||||
rings.add(ring1);
|
||||
rings.add(ring2);
|
||||
|
||||
Double result = (Double)method.invoke(null, rings, 0.47d, 0.01, dm);
|
||||
Assert.assertEquals(0.0009, result.doubleValue(), 0.0001);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test both rings beyond the root chord.
|
||||
*/
|
||||
@Test
|
||||
public void testBothRingsBeyondRootChord() throws Exception {
|
||||
DoubleModel dm = new DoubleModel(1d);
|
||||
List<CenteringRing> rings = new ArrayList<CenteringRing>();
|
||||
|
||||
CenteringRing ring1 = new CenteringRing();
|
||||
ring1.setLength(0.004);
|
||||
ring1.setPositionValue(0.48);
|
||||
CenteringRing ring2 = new CenteringRing();
|
||||
ring2.setLength(0.004);
|
||||
ring2.setPositionValue(0.49);
|
||||
rings.add(ring1);
|
||||
rings.add(ring2);
|
||||
|
||||
Double result = (Double)method.invoke(null, rings, 0.47d, 0.01, dm);
|
||||
Assert.assertEquals(0.006, result.doubleValue(), 0.0001);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test both rings within the root chord - the top ring has an adjacent ring (so 3 rings total).
|
||||
*/
|
||||
@Test
|
||||
public void test3RingsWithinRootChord() throws Exception {
|
||||
DoubleModel dm = new DoubleModel(1d);
|
||||
List<CenteringRing> rings = new ArrayList<CenteringRing>();
|
||||
|
||||
CenteringRing ring1 = new CenteringRing();
|
||||
ring1.setLength(0.004);
|
||||
ring1.setPositionValue(0.47);
|
||||
CenteringRing ring2 = new CenteringRing();
|
||||
ring2.setLength(0.004);
|
||||
ring2.setPositionValue(0.4702);
|
||||
CenteringRing ring3 = new CenteringRing();
|
||||
ring3.setLength(0.004);
|
||||
ring3.setPositionValue(0.4770);
|
||||
rings.add(ring1);
|
||||
rings.add(ring2);
|
||||
rings.add(ring3);
|
||||
|
||||
Double result = (Double)method.invoke(null, rings, 0.47d, 0.01, dm);
|
||||
Assert.assertEquals(0.0028, result.doubleValue(), 0.0001);
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user