Merge pull request #230 from kruland2607/feature-tubefins
Feature tubefins
This commit is contained in:
commit
e1cce52e3d
BIN
core/resources-src/pix/componenticons/tubefin.xcf.gz
Normal file
BIN
core/resources-src/pix/componenticons/tubefin.xcf.gz
Normal file
Binary file not shown.
@ -662,6 +662,7 @@ compaddbuttons.Transition = Transition
|
|||||||
compaddbuttons.Trapezoidal = Trapezoidal
|
compaddbuttons.Trapezoidal = Trapezoidal
|
||||||
compaddbuttons.Elliptical = Elliptical
|
compaddbuttons.Elliptical = Elliptical
|
||||||
compaddbuttons.Freeform = Freeform
|
compaddbuttons.Freeform = Freeform
|
||||||
|
compaddbuttons.Tubefin = Tube fins
|
||||||
compaddbuttons.Launchlug = Launch lug
|
compaddbuttons.Launchlug = Launch lug
|
||||||
compaddbuttons.Innercomponent = Inner component
|
compaddbuttons.Innercomponent = Inner component
|
||||||
compaddbuttons.Innertube = Inner tube
|
compaddbuttons.Innertube = Inner tube
|
||||||
@ -921,6 +922,15 @@ FreeformFinSetConfig.lbl.clickDrag = Click+drag: Add and move points
|
|||||||
FreeformFinSetConfig.lbl.ctrlClick = Ctrl+click: Remove point
|
FreeformFinSetConfig.lbl.ctrlClick = Ctrl+click: Remove point
|
||||||
FreeformFinSetConfig.lbl.scaleFin = Scale Fin
|
FreeformFinSetConfig.lbl.scaleFin = Scale Fin
|
||||||
|
|
||||||
|
!TubeFinSetConfig
|
||||||
|
TubeFinSetCfg.lbl.Nbroffins = Number of fins:
|
||||||
|
TubeFinSetCfg.lbl.Length = Length:
|
||||||
|
TubeFinSetCfg.lbl.Outerdiam = Outer diameter:
|
||||||
|
TubeFinSetCfg.checkbox.Automatic = Automatic
|
||||||
|
TubeFinSetCfg.lbl.Innerdiam = Inner diameter:
|
||||||
|
TubeFinSetCfg.lbl.Thickness = Thickness:
|
||||||
|
TubeFinSetCfg.lbl.Finrotation = Fin rotation:
|
||||||
|
TubeFinSetCfg.lbl.ttip.Finrotation = The angle of the first fin in the fin set.
|
||||||
|
|
||||||
!InnerTubeConfig
|
!InnerTubeConfig
|
||||||
InnerTubeCfg.tab.Motor = Motor
|
InnerTubeCfg.tab.Motor = Motor
|
||||||
@ -1379,6 +1389,8 @@ InnerTube.InnerTube = Inner Tube
|
|||||||
TrapezoidFinSet.TrapezoidFinSet = Trapezoidal fin set
|
TrapezoidFinSet.TrapezoidFinSet = Trapezoidal fin set
|
||||||
! FreeformFinSet
|
! FreeformFinSet
|
||||||
FreeformFinSet.FreeformFinSet = Freeform fin set
|
FreeformFinSet.FreeformFinSet = Freeform fin set
|
||||||
|
! TubeFinSEt
|
||||||
|
TubeFinSet.TubeFinSet = Tube fin set
|
||||||
!MassComponent
|
!MassComponent
|
||||||
MassComponent.MassComponent = Unspecified
|
MassComponent.MassComponent = Unspecified
|
||||||
MassComponent.Altimeter = Altimeter
|
MassComponent.Altimeter = Altimeter
|
||||||
@ -1429,6 +1441,7 @@ ComponentIcons.Transition = Transition
|
|||||||
ComponentIcons.Trapezoidalfinset = Trapezoidal fin set
|
ComponentIcons.Trapezoidalfinset = Trapezoidal fin set
|
||||||
ComponentIcons.Ellipticalfinset = Elliptical fin set
|
ComponentIcons.Ellipticalfinset = Elliptical fin set
|
||||||
ComponentIcons.Freeformfinset = Freeform fin set
|
ComponentIcons.Freeformfinset = Freeform fin set
|
||||||
|
ComponentIcons.Tubefinset = Tube fin set
|
||||||
ComponentIcons.Launchlug = Launch lug
|
ComponentIcons.Launchlug = Launch lug
|
||||||
ComponentIcons.Innertube = Inner tube
|
ComponentIcons.Innertube = Inner tube
|
||||||
ComponentIcons.Tubecoupler = Tube coupler
|
ComponentIcons.Tubecoupler = Tube coupler
|
||||||
|
BIN
core/resources/pix/componenticons/tubefin-large.png
Normal file
BIN
core/resources/pix/componenticons/tubefin-large.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
BIN
core/resources/pix/componenticons/tubefin-small.png
Normal file
BIN
core/resources/pix/componenticons/tubefin-small.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 553 B |
@ -0,0 +1,675 @@
|
|||||||
|
package net.sf.openrocket.aerodynamics.barrowman;
|
||||||
|
|
||||||
|
import static java.lang.Math.pow;
|
||||||
|
import static net.sf.openrocket.util.MathUtil.pow2;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import net.sf.openrocket.aerodynamics.AerodynamicForces;
|
||||||
|
import net.sf.openrocket.aerodynamics.FlightConditions;
|
||||||
|
import net.sf.openrocket.aerodynamics.Warning;
|
||||||
|
import net.sf.openrocket.aerodynamics.Warning.Other;
|
||||||
|
import net.sf.openrocket.aerodynamics.WarningSet;
|
||||||
|
import net.sf.openrocket.rocketcomponent.FinSet;
|
||||||
|
import net.sf.openrocket.rocketcomponent.RocketComponent;
|
||||||
|
import net.sf.openrocket.rocketcomponent.TubeFinSet;
|
||||||
|
import net.sf.openrocket.util.Coordinate;
|
||||||
|
import net.sf.openrocket.util.LinearInterpolator;
|
||||||
|
import net.sf.openrocket.util.MathUtil;
|
||||||
|
import net.sf.openrocket.util.PolyInterpolator;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Preliminary computation of tube fin aerodynamics.
|
||||||
|
*
|
||||||
|
* Uses a complete clone of FinSetCalc modelling each tube fin as 3 individual fins. It does not correctly account for
|
||||||
|
* fin & tube fin interference.
|
||||||
|
*
|
||||||
|
* Changes to BarrowmanCalculator's calculateFrictionDrag are also probably required.
|
||||||
|
*
|
||||||
|
* @author kruland
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class TubeFinSetCalc extends RocketComponentCalc {
|
||||||
|
|
||||||
|
private final static Logger logger = LoggerFactory.getLogger(FinSetCalc.class);
|
||||||
|
|
||||||
|
private static final double STALL_ANGLE = (20 * Math.PI / 180);
|
||||||
|
|
||||||
|
/** Number of divisions in the fin chords. */
|
||||||
|
protected static final int DIVISIONS = 48;
|
||||||
|
|
||||||
|
protected double macLength = Double.NaN; // MAC length
|
||||||
|
protected double macLead = Double.NaN; // MAC leading edge position
|
||||||
|
protected double macSpan = Double.NaN; // MAC spanwise position
|
||||||
|
protected double finArea = Double.NaN; // Fin area
|
||||||
|
protected double ar = Double.NaN; // Fin aspect ratio
|
||||||
|
protected double span = Double.NaN; // Fin span
|
||||||
|
protected double cosGamma = Double.NaN; // Cosine of midchord sweep angle
|
||||||
|
protected double cosGammaLead = Double.NaN; // Cosine of leading edge sweep angle
|
||||||
|
protected double rollSum = Double.NaN; // Roll damping sum term
|
||||||
|
|
||||||
|
|
||||||
|
protected double[] chordLead = new double[DIVISIONS];
|
||||||
|
protected double[] chordTrail = new double[DIVISIONS];
|
||||||
|
protected double[] chordLength = new double[DIVISIONS];
|
||||||
|
|
||||||
|
private double[] poly = new double[6];
|
||||||
|
|
||||||
|
private final double thickness;
|
||||||
|
private final double bodyRadius;
|
||||||
|
private final int finCount;
|
||||||
|
private final double baseRotation;
|
||||||
|
private final double cantAngle;
|
||||||
|
protected final int interferenceFinCount;
|
||||||
|
private final FinSet.CrossSection crossSection;
|
||||||
|
|
||||||
|
public TubeFinSetCalc(RocketComponent component) {
|
||||||
|
super(component);
|
||||||
|
if (!(component instanceof TubeFinSet)) {
|
||||||
|
throw new IllegalArgumentException("Illegal component type " + component);
|
||||||
|
}
|
||||||
|
|
||||||
|
TubeFinSet fin = (TubeFinSet) component;
|
||||||
|
thickness = fin.getThickness();
|
||||||
|
bodyRadius = fin.getBodyRadius();
|
||||||
|
finCount = 3 * fin.getFinCount();
|
||||||
|
baseRotation = fin.getBaseRotation();
|
||||||
|
cantAngle = 0;
|
||||||
|
span = 2 * fin.getOuterRadius();
|
||||||
|
finArea = span * fin.getLength();
|
||||||
|
crossSection = FinSet.CrossSection.SQUARE;
|
||||||
|
|
||||||
|
calculateFinGeometry(fin);
|
||||||
|
calculatePoly();
|
||||||
|
interferenceFinCount = calculateInterferenceFinCount(fin);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Calculates the non-axial forces produced by the fins (normal and side forces,
|
||||||
|
* pitch, yaw and roll moments, CP position, CNa).
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void calculateNonaxialForces(FlightConditions conditions,
|
||||||
|
AerodynamicForces forces, WarningSet warnings) {
|
||||||
|
|
||||||
|
if (span < 0.001) {
|
||||||
|
forces.setCm(0);
|
||||||
|
forces.setCN(0);
|
||||||
|
forces.setCNa(0);
|
||||||
|
forces.setCP(Coordinate.NUL);
|
||||||
|
forces.setCroll(0);
|
||||||
|
forces.setCrollDamp(0);
|
||||||
|
forces.setCrollForce(0);
|
||||||
|
forces.setCside(0);
|
||||||
|
forces.setCyaw(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add warnings (radius/2 == diameter/4)
|
||||||
|
if (thickness > bodyRadius / 2) {
|
||||||
|
warnings.add(Warning.THICK_FIN);
|
||||||
|
}
|
||||||
|
warnings.add(new Other("Tube fin support is experimental"));
|
||||||
|
|
||||||
|
//////// Calculate CNa. /////////
|
||||||
|
|
||||||
|
// One fin without interference (both sub- and supersonic):
|
||||||
|
double cna1 = calculateFinCNa1(conditions);
|
||||||
|
|
||||||
|
// logger.debug("Component cna1 = {}", cna1);
|
||||||
|
|
||||||
|
// Multiple fins with fin-fin interference
|
||||||
|
double cna;
|
||||||
|
double theta = conditions.getTheta();
|
||||||
|
double angle = baseRotation;
|
||||||
|
|
||||||
|
// Compute basic CNa without interference effects
|
||||||
|
if (finCount == 1 || finCount == 2) {
|
||||||
|
// Basic CNa from geometry
|
||||||
|
double mul = 0;
|
||||||
|
for (int i = 0; i < finCount; i++) {
|
||||||
|
mul += MathUtil.pow2(Math.sin(theta - angle));
|
||||||
|
angle += 2 * Math.PI / finCount;
|
||||||
|
}
|
||||||
|
cna = cna1 * mul;
|
||||||
|
} else {
|
||||||
|
// Basic CNa assuming full efficiency
|
||||||
|
cna = cna1 * finCount / 2.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// logger.debug("Component cna = {}", cna);
|
||||||
|
|
||||||
|
// Take into account fin-fin interference effects
|
||||||
|
switch (interferenceFinCount) {
|
||||||
|
case 1:
|
||||||
|
case 2:
|
||||||
|
case 3:
|
||||||
|
case 4:
|
||||||
|
// No interference effect
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 5:
|
||||||
|
cna *= 0.948;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 6:
|
||||||
|
cna *= 0.913;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 7:
|
||||||
|
cna *= 0.854;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 8:
|
||||||
|
cna *= 0.81;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Assume 75% efficiency
|
||||||
|
cna *= 0.75;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Body-fin interference effect
|
||||||
|
double r = bodyRadius;
|
||||||
|
double tau = r / (span + r);
|
||||||
|
if (Double.isNaN(tau) || Double.isInfinite(tau))
|
||||||
|
tau = 0;
|
||||||
|
cna *= 1 + tau; // Classical Barrowman
|
||||||
|
// cna *= pow2(1 + tau); // Barrowman thesis (too optimistic??)
|
||||||
|
// logger.debug("Component cna = {}", cna);
|
||||||
|
|
||||||
|
// TODO: LOW: check for fin tip mach cone interference
|
||||||
|
// (Barrowman thesis pdf-page 40)
|
||||||
|
|
||||||
|
// TODO: LOW: fin-fin mach cone effect, MIL-HDBK page 5-25
|
||||||
|
|
||||||
|
// Calculate CP position
|
||||||
|
double x = macLead + calculateCPPos(conditions) * macLength;
|
||||||
|
// logger.debug("Component macLead = {}", macLead);
|
||||||
|
// logger.debug("Component macLength = {}", macLength);
|
||||||
|
// logger.debug("Component x = {}", x);
|
||||||
|
|
||||||
|
|
||||||
|
// Calculate roll forces, reduce forcing above stall angle
|
||||||
|
|
||||||
|
// Without body-fin interference effect:
|
||||||
|
// forces.CrollForce = fins * (macSpan+r) * cna1 * component.getCantAngle() /
|
||||||
|
// conditions.getRefLength();
|
||||||
|
// With body-fin interference effect:
|
||||||
|
forces.setCrollForce(finCount * (macSpan + r) * cna1 * (1 + tau) * cantAngle / conditions.getRefLength());
|
||||||
|
|
||||||
|
if (conditions.getAOA() > STALL_ANGLE) {
|
||||||
|
// System.out.println("Fin stalling in roll");
|
||||||
|
forces.setCrollForce(forces.getCrollForce() * MathUtil.clamp(
|
||||||
|
1 - (conditions.getAOA() - STALL_ANGLE) / (STALL_ANGLE / 2), 0, 1));
|
||||||
|
}
|
||||||
|
forces.setCrollDamp(calculateDampingMoment(conditions));
|
||||||
|
forces.setCroll(forces.getCrollForce() - forces.getCrollDamp());
|
||||||
|
|
||||||
|
// System.out.printf(component.getName() + ": roll rate:%.3f force:%.3f damp:%.3f " +
|
||||||
|
// "total:%.3f\n",
|
||||||
|
// conditions.getRollRate(), forces.CrollForce, forces.CrollDamp, forces.Croll);
|
||||||
|
|
||||||
|
forces.setCNa(cna);
|
||||||
|
forces.setCN(cna * MathUtil.min(conditions.getAOA(), STALL_ANGLE));
|
||||||
|
forces.setCP(new Coordinate(x, 0, 0, cna));
|
||||||
|
forces.setCm(forces.getCN() * x / conditions.getRefLength());
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TODO: HIGH: Compute actual side force and yaw moment.
|
||||||
|
* This is not currently performed because it produces strange results for
|
||||||
|
* stable rockets that have two fins in the front part of the fuselage,
|
||||||
|
* where the rocket flies at an ever-increasing angle of attack. This may
|
||||||
|
* be due to incorrect computation of pitch/yaw damping moments.
|
||||||
|
*/
|
||||||
|
// if (fins == 1 || fins == 2) {
|
||||||
|
// forces.Cside = fins * cna1 * Math.cos(theta-angle) * Math.sin(theta-angle);
|
||||||
|
// forces.Cyaw = fins * forces.Cside * x / conditions.getRefLength();
|
||||||
|
// } else {
|
||||||
|
// forces.Cside = 0;
|
||||||
|
// forces.Cyaw = 0;
|
||||||
|
// }
|
||||||
|
forces.setCside(0);
|
||||||
|
forces.setCyaw(0);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the MAC length of the fin. This is required in the friction drag
|
||||||
|
* computation.
|
||||||
|
*
|
||||||
|
* @return the MAC length of the fin.
|
||||||
|
*/
|
||||||
|
public double getMACLength() {
|
||||||
|
return macLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getMidchordPos() {
|
||||||
|
return macLead + 0.5 * macLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pre-calculates the fin geometry values.
|
||||||
|
*/
|
||||||
|
protected void calculateFinGeometry(TubeFinSet component) {
|
||||||
|
|
||||||
|
ar = 2 * pow2(span) / finArea;
|
||||||
|
|
||||||
|
Coordinate[] points = {
|
||||||
|
Coordinate.NUL,
|
||||||
|
new Coordinate(0, span),
|
||||||
|
new Coordinate(component.getLength(), span),
|
||||||
|
new Coordinate(component.getLength(), 0)
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// Calculate the chord lead and trail positions and length
|
||||||
|
|
||||||
|
Arrays.fill(chordLead, Double.POSITIVE_INFINITY);
|
||||||
|
Arrays.fill(chordTrail, Double.NEGATIVE_INFINITY);
|
||||||
|
Arrays.fill(chordLength, 0);
|
||||||
|
|
||||||
|
for (int point = 1; point < points.length; point++) {
|
||||||
|
double x1 = points[point - 1].x;
|
||||||
|
double y1 = points[point - 1].y;
|
||||||
|
double x2 = points[point].x;
|
||||||
|
double y2 = points[point].y;
|
||||||
|
|
||||||
|
// Don't use the default EPSILON since it is too small
|
||||||
|
// and causes too much numerical instability in the computation of x below
|
||||||
|
if (MathUtil.equals(y1, y2, 0.001))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
int i1 = (int) (y1 * 1.0001 / span * (DIVISIONS - 1));
|
||||||
|
int i2 = (int) (y2 * 1.0001 / span * (DIVISIONS - 1));
|
||||||
|
i1 = MathUtil.clamp(i1, 0, DIVISIONS - 1);
|
||||||
|
i2 = MathUtil.clamp(i2, 0, DIVISIONS - 1);
|
||||||
|
if (i1 > i2) {
|
||||||
|
int tmp = i2;
|
||||||
|
i2 = i1;
|
||||||
|
i1 = tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = i1; i <= i2; i++) {
|
||||||
|
// Intersection point (x,y)
|
||||||
|
double y = i * span / (DIVISIONS - 1);
|
||||||
|
double x = (y - y2) / (y1 - y2) * x1 + (y1 - y) / (y1 - y2) * x2;
|
||||||
|
if (x < chordLead[i])
|
||||||
|
chordLead[i] = x;
|
||||||
|
if (x > chordTrail[i])
|
||||||
|
chordTrail[i] = x;
|
||||||
|
|
||||||
|
// TODO: LOW: If fin point exactly on chord line, might be counted twice:
|
||||||
|
if (y1 < y2) {
|
||||||
|
chordLength[i] -= x;
|
||||||
|
} else {
|
||||||
|
chordLength[i] += x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check and correct any inconsistencies
|
||||||
|
for (int i = 0; i < DIVISIONS; i++) {
|
||||||
|
if (Double.isInfinite(chordLead[i]) || Double.isInfinite(chordTrail[i]) ||
|
||||||
|
Double.isNaN(chordLead[i]) || Double.isNaN(chordTrail[i])) {
|
||||||
|
chordLead[i] = 0;
|
||||||
|
chordTrail[i] = 0;
|
||||||
|
}
|
||||||
|
if (chordLength[i] < 0 || Double.isNaN(chordLength[i])) {
|
||||||
|
chordLength[i] = 0;
|
||||||
|
}
|
||||||
|
if (chordLength[i] > chordTrail[i] - chordLead[i]) {
|
||||||
|
chordLength[i] = chordTrail[i] - chordLead[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Calculate fin properties:
|
||||||
|
*
|
||||||
|
* macLength // MAC length
|
||||||
|
* macLead // MAC leading edge position
|
||||||
|
* macSpan // MAC spanwise position
|
||||||
|
* ar // Fin aspect ratio (already set)
|
||||||
|
* span // Fin span (already set)
|
||||||
|
*/
|
||||||
|
macLength = 0;
|
||||||
|
macLead = 0;
|
||||||
|
macSpan = 0;
|
||||||
|
cosGamma = 0;
|
||||||
|
cosGammaLead = 0;
|
||||||
|
rollSum = 0;
|
||||||
|
double area = 0;
|
||||||
|
double radius = component.getBodyRadius();
|
||||||
|
|
||||||
|
final double dy = span / (DIVISIONS - 1);
|
||||||
|
for (int i = 0; i < DIVISIONS; i++) {
|
||||||
|
double length = chordTrail[i] - chordLead[i];
|
||||||
|
double y = i * dy;
|
||||||
|
|
||||||
|
macLength += length * length;
|
||||||
|
logger.debug("macLength = {}, length = {}, i = {}", macLength, length, i);
|
||||||
|
macSpan += y * length;
|
||||||
|
macLead += chordLead[i] * length;
|
||||||
|
area += length;
|
||||||
|
rollSum += chordLength[i] * pow2(radius + y);
|
||||||
|
|
||||||
|
if (i > 0) {
|
||||||
|
double dx = (chordTrail[i] + chordLead[i]) / 2 - (chordTrail[i - 1] + chordLead[i - 1]) / 2;
|
||||||
|
cosGamma += dy / MathUtil.hypot(dx, dy);
|
||||||
|
|
||||||
|
dx = chordLead[i] - chordLead[i - 1];
|
||||||
|
cosGammaLead += dy / MathUtil.hypot(dx, dy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macLength *= dy;
|
||||||
|
logger.debug("macLength = {}", macLength);
|
||||||
|
macSpan *= dy;
|
||||||
|
macLead *= dy;
|
||||||
|
area *= dy;
|
||||||
|
rollSum *= dy;
|
||||||
|
|
||||||
|
macLength /= area;
|
||||||
|
macSpan /= area;
|
||||||
|
macLead /= area;
|
||||||
|
cosGamma /= (DIVISIONS - 1);
|
||||||
|
cosGammaLead /= (DIVISIONS - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////// CNa1 calculation ////////////////
|
||||||
|
|
||||||
|
private static final double CNA_SUBSONIC = 0.9;
|
||||||
|
private static final double CNA_SUPERSONIC = 1.5;
|
||||||
|
private static final double CNA_SUPERSONIC_B = pow(pow2(CNA_SUPERSONIC) - 1, 1.5);
|
||||||
|
private static final double GAMMA = 1.4;
|
||||||
|
private static final LinearInterpolator K1, K2, K3;
|
||||||
|
private static final PolyInterpolator cnaInterpolator = new PolyInterpolator(
|
||||||
|
new double[] { CNA_SUBSONIC, CNA_SUPERSONIC },
|
||||||
|
new double[] { CNA_SUBSONIC, CNA_SUPERSONIC },
|
||||||
|
new double[] { CNA_SUBSONIC }
|
||||||
|
);
|
||||||
|
/* Pre-calculate the values for K1, K2 and K3 */
|
||||||
|
static {
|
||||||
|
// Up to Mach 5
|
||||||
|
int n = (int) ((5.0 - CNA_SUPERSONIC) * 10);
|
||||||
|
double[] x = new double[n];
|
||||||
|
double[] k1 = new double[n];
|
||||||
|
double[] k2 = new double[n];
|
||||||
|
double[] k3 = new double[n];
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
double M = CNA_SUPERSONIC + i * 0.1;
|
||||||
|
double beta = MathUtil.safeSqrt(M * M - 1);
|
||||||
|
x[i] = M;
|
||||||
|
k1[i] = 2.0 / beta;
|
||||||
|
k2[i] = ((GAMMA + 1) * pow(M, 4) - 4 * pow2(beta)) / (4 * pow(beta, 4));
|
||||||
|
k3[i] = ((GAMMA + 1) * pow(M, 8) + (2 * pow2(GAMMA) - 7 * GAMMA - 5) * pow(M, 6) +
|
||||||
|
10 * (GAMMA + 1) * pow(M, 4) + 8) / (6 * pow(beta, 7));
|
||||||
|
}
|
||||||
|
K1 = new LinearInterpolator(x, k1);
|
||||||
|
K2 = new LinearInterpolator(x, k2);
|
||||||
|
K3 = new LinearInterpolator(x, k3);
|
||||||
|
|
||||||
|
// System.out.println("K1[m="+CNA_SUPERSONIC+"] = "+k1[0]);
|
||||||
|
// System.out.println("K2[m="+CNA_SUPERSONIC+"] = "+k2[0]);
|
||||||
|
// System.out.println("K3[m="+CNA_SUPERSONIC+"] = "+k3[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected double calculateFinCNa1(FlightConditions conditions) {
|
||||||
|
double mach = conditions.getMach();
|
||||||
|
double ref = conditions.getRefArea();
|
||||||
|
double alpha = MathUtil.min(conditions.getAOA(),
|
||||||
|
Math.PI - conditions.getAOA(), STALL_ANGLE);
|
||||||
|
|
||||||
|
// Subsonic case
|
||||||
|
if (mach <= CNA_SUBSONIC) {
|
||||||
|
return 2 * Math.PI * pow2(span) / (1 + MathUtil.safeSqrt(1 + (1 - pow2(mach)) *
|
||||||
|
pow2(pow2(span) / (finArea * cosGamma)))) / ref;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Supersonic case
|
||||||
|
if (mach >= CNA_SUPERSONIC) {
|
||||||
|
return finArea * (K1.getValue(mach) + K2.getValue(mach) * alpha +
|
||||||
|
K3.getValue(mach) * pow2(alpha)) / ref;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transonic case, interpolate
|
||||||
|
double subV, superV;
|
||||||
|
double subD, superD;
|
||||||
|
|
||||||
|
double sq = MathUtil.safeSqrt(1 + (1 - pow2(CNA_SUBSONIC)) * pow2(span * span / (finArea * cosGamma)));
|
||||||
|
subV = 2 * Math.PI * pow2(span) / ref / (1 + sq);
|
||||||
|
subD = 2 * mach * Math.PI * pow(span, 6) / (pow2(finArea * cosGamma) * ref *
|
||||||
|
sq * pow2(1 + sq));
|
||||||
|
|
||||||
|
superV = finArea * (K1.getValue(CNA_SUPERSONIC) + K2.getValue(CNA_SUPERSONIC) * alpha +
|
||||||
|
K3.getValue(CNA_SUPERSONIC) * pow2(alpha)) / ref;
|
||||||
|
superD = -finArea / ref * 2 * CNA_SUPERSONIC / CNA_SUPERSONIC_B;
|
||||||
|
|
||||||
|
// System.out.println("subV="+subV+" superV="+superV+" subD="+subD+" superD="+superD);
|
||||||
|
|
||||||
|
return cnaInterpolator.interpolate(mach, subV, superV, subD, superD, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private double calculateDampingMoment(FlightConditions conditions) {
|
||||||
|
double rollRate = conditions.getRollRate();
|
||||||
|
|
||||||
|
if (Math.abs(rollRate) < 0.1)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
double mach = conditions.getMach();
|
||||||
|
double absRate = Math.abs(rollRate);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* At low speeds and relatively large roll rates (i.e. near apogee) the
|
||||||
|
* fin tips rotate well above stall angle. In this case sum the chords
|
||||||
|
* separately.
|
||||||
|
*/
|
||||||
|
if (absRate * (bodyRadius + span) / conditions.getVelocity() > 15 * Math.PI / 180) {
|
||||||
|
double sum = 0;
|
||||||
|
for (int i = 0; i < DIVISIONS; i++) {
|
||||||
|
double dist = bodyRadius + span * i / DIVISIONS;
|
||||||
|
double aoa = Math.min(absRate * dist / conditions.getVelocity(), 15 * Math.PI / 180);
|
||||||
|
sum += chordLength[i] * dist * aoa;
|
||||||
|
}
|
||||||
|
sum = sum * (span / DIVISIONS) * 2 * Math.PI / conditions.getBeta() /
|
||||||
|
(conditions.getRefArea() * conditions.getRefLength());
|
||||||
|
|
||||||
|
// System.out.println("SPECIAL: " +
|
||||||
|
// (MathUtil.sign(rollRate) *component.getFinCount() * sum));
|
||||||
|
return MathUtil.sign(rollRate) * finCount * sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mach <= CNA_SUBSONIC) {
|
||||||
|
// System.out.println("BASIC: "+
|
||||||
|
// (component.getFinCount() * 2*Math.PI * rollRate * rollSum /
|
||||||
|
// (conditions.getRefArea() * conditions.getRefLength() *
|
||||||
|
// conditions.getVelocity() * conditions.getBeta())));
|
||||||
|
|
||||||
|
return finCount * 2 * Math.PI * rollRate * rollSum /
|
||||||
|
(conditions.getRefArea() * conditions.getRefLength() *
|
||||||
|
conditions.getVelocity() * conditions.getBeta());
|
||||||
|
}
|
||||||
|
if (mach >= CNA_SUPERSONIC) {
|
||||||
|
|
||||||
|
double vel = conditions.getVelocity();
|
||||||
|
double k1 = K1.getValue(mach);
|
||||||
|
double k2 = K2.getValue(mach);
|
||||||
|
double k3 = K3.getValue(mach);
|
||||||
|
|
||||||
|
double sum = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < DIVISIONS; i++) {
|
||||||
|
double y = i * span / (DIVISIONS - 1);
|
||||||
|
double angle = rollRate * (bodyRadius + y) / vel;
|
||||||
|
|
||||||
|
sum += (k1 * angle + k2 * angle * angle + k3 * angle * angle * angle)
|
||||||
|
* chordLength[i] * (bodyRadius + y);
|
||||||
|
}
|
||||||
|
|
||||||
|
return finCount * sum * span / (DIVISIONS - 1) /
|
||||||
|
(conditions.getRefArea() * conditions.getRefLength());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transonic, do linear interpolation
|
||||||
|
|
||||||
|
FlightConditions cond = conditions.clone();
|
||||||
|
cond.setMach(CNA_SUBSONIC - 0.01);
|
||||||
|
double subsonic = calculateDampingMoment(cond);
|
||||||
|
cond.setMach(CNA_SUPERSONIC + 0.01);
|
||||||
|
double supersonic = calculateDampingMoment(cond);
|
||||||
|
|
||||||
|
return subsonic * (CNA_SUPERSONIC - mach) / (CNA_SUPERSONIC - CNA_SUBSONIC) +
|
||||||
|
supersonic * (mach - CNA_SUBSONIC) / (CNA_SUPERSONIC - CNA_SUBSONIC);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the relative position of the CP along the mean aerodynamic chord.
|
||||||
|
* Below mach 0.5 it is at the quarter chord, above mach 2 calculated using an
|
||||||
|
* empirical formula, between these two using an interpolation polynomial.
|
||||||
|
*
|
||||||
|
* @param cond Mach speed used
|
||||||
|
* @return CP position along the MAC
|
||||||
|
*/
|
||||||
|
private double calculateCPPos(FlightConditions cond) {
|
||||||
|
double m = cond.getMach();
|
||||||
|
// logger.debug("m = {} ", m);
|
||||||
|
if (m <= 0.5) {
|
||||||
|
// At subsonic speeds CP at quarter chord
|
||||||
|
return 0.25;
|
||||||
|
}
|
||||||
|
if (m >= 2) {
|
||||||
|
// At supersonic speeds use empirical formula
|
||||||
|
double beta = cond.getBeta();
|
||||||
|
return (ar * beta - 0.67) / (2 * ar * beta - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// In between use interpolation polynomial
|
||||||
|
double x = 1.0;
|
||||||
|
double val = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < poly.length; i++) {
|
||||||
|
val += poly[i] * x;
|
||||||
|
x *= m;
|
||||||
|
}
|
||||||
|
// logger.debug("val = {}", val);
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate CP position interpolation polynomial coefficients from the
|
||||||
|
* fin geometry. This is a fifth order polynomial that satisfies
|
||||||
|
*
|
||||||
|
* p(0.5)=0.25
|
||||||
|
* p'(0.5)=0
|
||||||
|
* p(2) = f(2)
|
||||||
|
* p'(2) = f'(2)
|
||||||
|
* p''(2) = 0
|
||||||
|
* p'''(2) = 0
|
||||||
|
*
|
||||||
|
* where f(M) = (ar*sqrt(M^2-1) - 0.67) / (2*ar*sqrt(M^2-1) - 1).
|
||||||
|
*
|
||||||
|
* The values were calculated analytically in Mathematica. The coefficients
|
||||||
|
* are used as poly[0] + poly[1]*x + poly[2]*x^2 + ...
|
||||||
|
*/
|
||||||
|
private void calculatePoly() {
|
||||||
|
double denom = pow2(1 - 3.4641 * ar); // common denominator
|
||||||
|
|
||||||
|
poly[5] = (-1.58025 * (-0.728769 + ar) * (-0.192105 + ar)) / denom;
|
||||||
|
poly[4] = (12.8395 * (-0.725688 + ar) * (-0.19292 + ar)) / denom;
|
||||||
|
poly[3] = (-39.5062 * (-0.72074 + ar) * (-0.194245 + ar)) / denom;
|
||||||
|
poly[2] = (55.3086 * (-0.711482 + ar) * (-0.196772 + ar)) / denom;
|
||||||
|
poly[1] = (-31.6049 * (-0.705375 + ar) * (-0.198476 + ar)) / denom;
|
||||||
|
poly[0] = (9.16049 * (-0.588838 + ar) * (-0.20624 + ar)) / denom;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// @SuppressWarnings("null")
|
||||||
|
// public static void main(String arg[]) {
|
||||||
|
// Rocket rocket = TestRocket.makeRocket();
|
||||||
|
// FinSet finset = null;
|
||||||
|
//
|
||||||
|
// Iterator<RocketComponent> iter = rocket.deepIterator();
|
||||||
|
// while (iter.hasNext()) {
|
||||||
|
// RocketComponent c = iter.next();
|
||||||
|
// if (c instanceof FinSet) {
|
||||||
|
// finset = (FinSet)c;
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// ((TrapezoidFinSet)finset).setHeight(0.10);
|
||||||
|
// ((TrapezoidFinSet)finset).setRootChord(0.10);
|
||||||
|
// ((TrapezoidFinSet)finset).setTipChord(0.10);
|
||||||
|
// ((TrapezoidFinSet)finset).setSweep(0.0);
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// FinSetCalc calc = new FinSetCalc(finset);
|
||||||
|
//
|
||||||
|
// calc.calculateFinGeometry();
|
||||||
|
// FlightConditions cond = new FlightConditions(new Configuration(rocket));
|
||||||
|
// for (double m=0; m < 3; m+=0.05) {
|
||||||
|
// cond.setMach(m);
|
||||||
|
// cond.setAOA(0.0*Math.PI/180);
|
||||||
|
// double cna = calc.calculateFinCNa1(cond);
|
||||||
|
// System.out.printf("%5.2f "+cna+"\n", m);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double calculatePressureDragForce(FlightConditions conditions,
|
||||||
|
double stagnationCD, double baseCD, WarningSet warnings) {
|
||||||
|
|
||||||
|
double mach = conditions.getMach();
|
||||||
|
double drag = 0;
|
||||||
|
|
||||||
|
// Pressure fore-drag
|
||||||
|
if (crossSection == FinSet.CrossSection.AIRFOIL ||
|
||||||
|
crossSection == FinSet.CrossSection.ROUNDED) {
|
||||||
|
|
||||||
|
// Round leading edge
|
||||||
|
if (mach < 0.9) {
|
||||||
|
drag = Math.pow(1 - pow2(mach), -0.417) - 1;
|
||||||
|
} else if (mach < 1) {
|
||||||
|
drag = 1 - 1.785 * (mach - 0.9);
|
||||||
|
} else {
|
||||||
|
drag = 1.214 - 0.502 / pow2(mach) + 0.1095 / pow2(pow2(mach));
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (crossSection == FinSet.CrossSection.SQUARE) {
|
||||||
|
drag = stagnationCD;
|
||||||
|
} else {
|
||||||
|
throw new UnsupportedOperationException("Unsupported fin profile: " + crossSection);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Slanted leading edge
|
||||||
|
drag *= pow2(cosGammaLead);
|
||||||
|
|
||||||
|
// Trailing edge drag
|
||||||
|
if (crossSection == FinSet.CrossSection.SQUARE) {
|
||||||
|
drag += baseCD;
|
||||||
|
} else if (crossSection == FinSet.CrossSection.ROUNDED) {
|
||||||
|
drag += baseCD / 2;
|
||||||
|
}
|
||||||
|
// Airfoil assumed to have zero base drag
|
||||||
|
|
||||||
|
// Scale to correct reference area
|
||||||
|
drag *= finCount * span * thickness / conditions.getRefArea();
|
||||||
|
|
||||||
|
return drag;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int calculateInterferenceFinCount(TubeFinSet component) {
|
||||||
|
RocketComponent parent = component.getParent();
|
||||||
|
if (parent == null) {
|
||||||
|
throw new IllegalStateException("fin set without parent component");
|
||||||
|
}
|
||||||
|
|
||||||
|
return 3 * component.getFinCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -18,6 +18,7 @@ import net.sf.openrocket.rocketcomponent.RadiusRingComponent;
|
|||||||
import net.sf.openrocket.rocketcomponent.RocketComponent;
|
import net.sf.openrocket.rocketcomponent.RocketComponent;
|
||||||
import net.sf.openrocket.rocketcomponent.Transition;
|
import net.sf.openrocket.rocketcomponent.Transition;
|
||||||
import net.sf.openrocket.rocketcomponent.TubeCoupler;
|
import net.sf.openrocket.rocketcomponent.TubeCoupler;
|
||||||
|
import net.sf.openrocket.rocketcomponent.TubeFinSet;
|
||||||
import net.sf.openrocket.util.Color;
|
import net.sf.openrocket.util.Color;
|
||||||
import net.sf.openrocket.util.Coordinate;
|
import net.sf.openrocket.util.Coordinate;
|
||||||
|
|
||||||
@ -77,7 +78,7 @@ public class DefaultAppearance {
|
|||||||
public static Appearance getDefaultAppearance(RocketComponent c) {
|
public static Appearance getDefaultAppearance(RocketComponent c) {
|
||||||
if (c instanceof BodyTube)
|
if (c instanceof BodyTube)
|
||||||
return ESTES_BT;
|
return ESTES_BT;
|
||||||
if (c instanceof InnerTube || c instanceof TubeCoupler)
|
if (c instanceof InnerTube || c instanceof TubeCoupler || c instanceof TubeFinSet)
|
||||||
return ESTES_IT;
|
return ESTES_IT;
|
||||||
if (c instanceof FinSet)
|
if (c instanceof FinSet)
|
||||||
return BALSA;
|
return BALSA;
|
||||||
|
@ -24,6 +24,7 @@ import net.sf.openrocket.rocketcomponent.Rocket;
|
|||||||
import net.sf.openrocket.rocketcomponent.RocketComponent;
|
import net.sf.openrocket.rocketcomponent.RocketComponent;
|
||||||
import net.sf.openrocket.rocketcomponent.Stage;
|
import net.sf.openrocket.rocketcomponent.Stage;
|
||||||
import net.sf.openrocket.rocketcomponent.TubeCoupler;
|
import net.sf.openrocket.rocketcomponent.TubeCoupler;
|
||||||
|
import net.sf.openrocket.rocketcomponent.TubeFinSet;
|
||||||
import net.sf.openrocket.simulation.FlightData;
|
import net.sf.openrocket.simulation.FlightData;
|
||||||
import net.sf.openrocket.simulation.FlightDataBranch;
|
import net.sf.openrocket.simulation.FlightDataBranch;
|
||||||
import net.sf.openrocket.simulation.FlightDataType;
|
import net.sf.openrocket.simulation.FlightDataType;
|
||||||
@ -223,6 +224,7 @@ public class OpenRocketSaver extends RocketSaver {
|
|||||||
*
|
*
|
||||||
* File version 1.7 is required for:
|
* File version 1.7 is required for:
|
||||||
* - simulation extensions
|
* - simulation extensions
|
||||||
|
* - saving tube fins.
|
||||||
*
|
*
|
||||||
* File version 1.6 is required for:
|
* File version 1.6 is required for:
|
||||||
* - saving files using appearances and textures, flight configurations.
|
* - saving files using appearances and textures, flight configurations.
|
||||||
@ -252,6 +254,13 @@ public class OpenRocketSaver extends RocketSaver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Search the rocket for any TubeFinSet objects (version 1.7)
|
||||||
|
for (RocketComponent c : document.getRocket()) {
|
||||||
|
if (c instanceof TubeFinSet) {
|
||||||
|
return FILE_VERSION_DIVISOR + 7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/////////////////
|
/////////////////
|
||||||
// Version 1.6 //
|
// Version 1.6 //
|
||||||
|
@ -40,6 +40,7 @@ import net.sf.openrocket.rocketcomponent.ThicknessRingComponent;
|
|||||||
import net.sf.openrocket.rocketcomponent.Transition;
|
import net.sf.openrocket.rocketcomponent.Transition;
|
||||||
import net.sf.openrocket.rocketcomponent.TrapezoidFinSet;
|
import net.sf.openrocket.rocketcomponent.TrapezoidFinSet;
|
||||||
import net.sf.openrocket.rocketcomponent.TubeCoupler;
|
import net.sf.openrocket.rocketcomponent.TubeCoupler;
|
||||||
|
import net.sf.openrocket.rocketcomponent.TubeFinSet;
|
||||||
import net.sf.openrocket.util.BugException;
|
import net.sf.openrocket.util.BugException;
|
||||||
import net.sf.openrocket.util.Color;
|
import net.sf.openrocket.util.Color;
|
||||||
import net.sf.openrocket.util.LineStyle;
|
import net.sf.openrocket.util.LineStyle;
|
||||||
@ -69,6 +70,7 @@ class DocumentConfig {
|
|||||||
constructors.put("trapezoidfinset", TrapezoidFinSet.class.getConstructor(new Class<?>[0]));
|
constructors.put("trapezoidfinset", TrapezoidFinSet.class.getConstructor(new Class<?>[0]));
|
||||||
constructors.put("ellipticalfinset", EllipticalFinSet.class.getConstructor(new Class<?>[0]));
|
constructors.put("ellipticalfinset", EllipticalFinSet.class.getConstructor(new Class<?>[0]));
|
||||||
constructors.put("freeformfinset", FreeformFinSet.class.getConstructor(new Class<?>[0]));
|
constructors.put("freeformfinset", FreeformFinSet.class.getConstructor(new Class<?>[0]));
|
||||||
|
constructors.put("tubefinset", TubeFinSet.class.getConstructor(new Class<?>[0]));
|
||||||
constructors.put("launchlug", LaunchLug.class.getConstructor(new Class<?>[0]));
|
constructors.put("launchlug", LaunchLug.class.getConstructor(new Class<?>[0]));
|
||||||
|
|
||||||
// Internal components
|
// Internal components
|
||||||
@ -230,6 +232,20 @@ class DocumentConfig {
|
|||||||
|
|
||||||
// FreeformFinSet points handled as a special handler
|
// FreeformFinSet points handled as a special handler
|
||||||
|
|
||||||
|
// TubeFinSet
|
||||||
|
setters.put("TubeFinSet:fincount", new IntSetter(
|
||||||
|
Reflection.findMethod(TubeFinSet.class, "setFinCount", int.class)));
|
||||||
|
setters.put("TubeFinSet:rotation", new DoubleSetter(
|
||||||
|
Reflection.findMethod(TubeFinSet.class, "setBaseRotation", double.class), Math.PI / 180.0));
|
||||||
|
setters.put("TubeFinSet:thickness", new DoubleSetter(
|
||||||
|
Reflection.findMethod(TubeFinSet.class, "setThickness", double.class)));
|
||||||
|
setters.put("TubeFinSet:length", new DoubleSetter(
|
||||||
|
Reflection.findMethod(TubeFinSet.class, "setLength", double.class)));
|
||||||
|
setters.put("TubeFinSet:radius", new DoubleSetter(
|
||||||
|
Reflection.findMethod(TubeFinSet.class, "setOuterRadius", double.class),
|
||||||
|
"auto",
|
||||||
|
Reflection.findMethod(TubeFinSet.class, "setOuterRadiusAutomatic", boolean.class)));
|
||||||
|
|
||||||
// LaunchLug
|
// LaunchLug
|
||||||
setters.put("LaunchLug:radius", new DoubleSetter(
|
setters.put("LaunchLug:radius", new DoubleSetter(
|
||||||
Reflection.findMethod(LaunchLug.class, "setOuterRadius", double.class)));
|
Reflection.findMethod(LaunchLug.class, "setOuterRadius", double.class)));
|
||||||
|
@ -9,6 +9,7 @@ import net.sf.openrocket.rocketcomponent.InternalComponent;
|
|||||||
import net.sf.openrocket.rocketcomponent.LaunchLug;
|
import net.sf.openrocket.rocketcomponent.LaunchLug;
|
||||||
import net.sf.openrocket.rocketcomponent.RocketComponent;
|
import net.sf.openrocket.rocketcomponent.RocketComponent;
|
||||||
import net.sf.openrocket.rocketcomponent.RocketComponent.Position;
|
import net.sf.openrocket.rocketcomponent.RocketComponent.Position;
|
||||||
|
import net.sf.openrocket.rocketcomponent.TubeFinSet;
|
||||||
|
|
||||||
class PositionSetter implements Setter {
|
class PositionSetter implements Setter {
|
||||||
|
|
||||||
@ -40,6 +41,9 @@ class PositionSetter implements Setter {
|
|||||||
} else if (c instanceof InternalComponent) {
|
} else if (c instanceof InternalComponent) {
|
||||||
((InternalComponent) c).setRelativePosition(type);
|
((InternalComponent) c).setRelativePosition(type);
|
||||||
c.setPositionValue(pos);
|
c.setPositionValue(pos);
|
||||||
|
} else if (c instanceof TubeFinSet) {
|
||||||
|
((TubeFinSet) c).setRelativePosition(type);
|
||||||
|
c.setPositionValue(pos);
|
||||||
} else {
|
} else {
|
||||||
warnings.add(Warning.FILE_INVALID_PARAMETER);
|
warnings.add(Warning.FILE_INVALID_PARAMETER);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,38 @@
|
|||||||
|
package net.sf.openrocket.file.openrocket.savers;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import net.sf.openrocket.rocketcomponent.TubeFinSet;
|
||||||
|
|
||||||
|
|
||||||
|
public class TubeFinSetSaver extends ExternalComponentSaver {
|
||||||
|
|
||||||
|
private static final TubeFinSetSaver instance = new TubeFinSetSaver();
|
||||||
|
|
||||||
|
public static List<String> getElements(net.sf.openrocket.rocketcomponent.RocketComponent c) {
|
||||||
|
List<String> list = new ArrayList<String>();
|
||||||
|
|
||||||
|
list.add("<tubefinset>");
|
||||||
|
instance.addParams(c, list);
|
||||||
|
list.add("</tubefinset>");
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void addParams(net.sf.openrocket.rocketcomponent.RocketComponent c, List<String> elements) {
|
||||||
|
super.addParams(c, elements);
|
||||||
|
TubeFinSet fins = (TubeFinSet) c;
|
||||||
|
|
||||||
|
elements.add("<fincount>" + fins.getFinCount() + "</fincount>");
|
||||||
|
elements.add("<rotation>" + (fins.getBaseRotation() * 180.0 / Math.PI) + "</rotation>");
|
||||||
|
if (fins.isOuterRadiusAutomatic())
|
||||||
|
elements.add("<radius>auto</radius>");
|
||||||
|
else
|
||||||
|
elements.add("<radius>" + fins.getOuterRadius() + "</radius>");
|
||||||
|
elements.add("<length>" + fins.getLength() + "</length>");
|
||||||
|
elements.add("<thickness>" + fins.getThickness() + "</thickness>");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -281,8 +281,8 @@ public class BodyTube extends SymmetricComponent implements MotorMount, Coaxial
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public double getLongitudinalUnitInertia() {
|
public double getLongitudinalUnitInertia() {
|
||||||
// 1/12 * (3 * (r1^2 + r2^2) + h^2)
|
// 1/12 * (3 * (r2^2 + r1^2) + h^2)
|
||||||
return (3 * (MathUtil.pow2(getInnerRadius())) + MathUtil.pow2(getOuterRadius()) + MathUtil.pow2(getLength())) / 12;
|
return (3 * (MathUtil.pow2(getOuterRadius()) + MathUtil.pow2(getInnerRadius())) + MathUtil.pow2(getLength())) / 12;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -115,12 +115,12 @@ public class LaunchLug extends ExternalComponent implements Coaxial {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void loadFromPreset(ComponentPreset preset) {
|
protected void loadFromPreset(ComponentPreset preset) {
|
||||||
if ( preset.has(ComponentPreset.OUTER_DIAMETER) ) {
|
if (preset.has(ComponentPreset.OUTER_DIAMETER)) {
|
||||||
double outerDiameter = preset.get(ComponentPreset.OUTER_DIAMETER);
|
double outerDiameter = preset.get(ComponentPreset.OUTER_DIAMETER);
|
||||||
this.radius = outerDiameter/2.0;
|
this.radius = outerDiameter / 2.0;
|
||||||
if ( preset.has(ComponentPreset.INNER_DIAMETER) ) {
|
if (preset.has(ComponentPreset.INNER_DIAMETER)) {
|
||||||
double innerDiameter = preset.get(ComponentPreset.INNER_DIAMETER);
|
double innerDiameter = preset.get(ComponentPreset.INNER_DIAMETER);
|
||||||
this.thickness = (outerDiameter-innerDiameter) / 2.0;
|
this.thickness = (outerDiameter - innerDiameter) / 2.0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -211,8 +211,8 @@ public class LaunchLug extends ExternalComponent implements Coaxial {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public double getLongitudinalUnitInertia() {
|
public double getLongitudinalUnitInertia() {
|
||||||
// 1/12 * (3 * (r1^2 + r2^2) + h^2)
|
// 1/12 * (3 * (r2^2 + r1^2) + h^2)
|
||||||
return (3 * (MathUtil.pow2(getInnerRadius())) + MathUtil.pow2(getOuterRadius()) + MathUtil.pow2(getLength())) / 12;
|
return (3 * (MathUtil.pow2(getOuterRadius()) + MathUtil.pow2(getInnerRadius())) + MathUtil.pow2(getLength())) / 12;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
344
core/src/net/sf/openrocket/rocketcomponent/TubeFinSet.java
Normal file
344
core/src/net/sf/openrocket/rocketcomponent/TubeFinSet.java
Normal file
@ -0,0 +1,344 @@
|
|||||||
|
package net.sf.openrocket.rocketcomponent;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import net.sf.openrocket.l10n.Translator;
|
||||||
|
import net.sf.openrocket.preset.ComponentPreset;
|
||||||
|
import net.sf.openrocket.preset.ComponentPreset.Type;
|
||||||
|
import net.sf.openrocket.startup.Application;
|
||||||
|
import net.sf.openrocket.util.Coordinate;
|
||||||
|
import net.sf.openrocket.util.MathUtil;
|
||||||
|
import net.sf.openrocket.util.Transformation;
|
||||||
|
|
||||||
|
public class TubeFinSet extends ExternalComponent {
|
||||||
|
private static final Translator trans = Application.getTranslator();
|
||||||
|
|
||||||
|
private final static double DEFAULT_RADIUS = 0.025;
|
||||||
|
|
||||||
|
private boolean autoRadius = true; // Radius chosen automatically based on parent component
|
||||||
|
private double outerRadius = DEFAULT_RADIUS;
|
||||||
|
protected double thickness = 0.002;
|
||||||
|
|
||||||
|
protected int fins = 6;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rotation angle of the first fin. Zero corresponds to the positive y-axis.
|
||||||
|
*/
|
||||||
|
protected double rotation = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rotation about the x-axis by angle this.rotation.
|
||||||
|
*/
|
||||||
|
protected Transformation baseRotation = Transformation.rotate_x(rotation);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rotation about the x-axis by 2*PI/fins.
|
||||||
|
*/
|
||||||
|
protected Transformation finRotation = Transformation.rotate_x(2 * Math.PI / fins);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* New FinSet with given number of fins and given base rotation angle.
|
||||||
|
* Sets the component relative position to POSITION_RELATIVE_BOTTOM,
|
||||||
|
* i.e. fins are positioned at the bottom of the parent component.
|
||||||
|
*/
|
||||||
|
public TubeFinSet() {
|
||||||
|
super(RocketComponent.Position.BOTTOM);
|
||||||
|
length = 0.10;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLength(double length) {
|
||||||
|
if (MathUtil.equals(this.length, length))
|
||||||
|
return;
|
||||||
|
this.length = length;
|
||||||
|
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public boolean isOuterRadiusAutomatic() {
|
||||||
|
return autoRadius;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the outer radius of the body tube.
|
||||||
|
*
|
||||||
|
* @return the outside radius of the tube
|
||||||
|
*/
|
||||||
|
public double getOuterRadius() {
|
||||||
|
if (autoRadius) {
|
||||||
|
// Return auto radius from front or rear
|
||||||
|
double r = -1;
|
||||||
|
RocketComponent c = this.getParent();
|
||||||
|
if (c != null) {
|
||||||
|
if (c instanceof SymmetricComponent) {
|
||||||
|
r = ((SymmetricComponent) c).getAftRadius();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (r < 0) {
|
||||||
|
r = DEFAULT_RADIUS;
|
||||||
|
} else {
|
||||||
|
// for 5,6, and 8 fins, adjust the diameter to provide touching fins.
|
||||||
|
switch (fins) {
|
||||||
|
case 5:
|
||||||
|
r *= 1.43; // sin(36) / (1- sin(36), 36 = 360/5/2
|
||||||
|
break;
|
||||||
|
case 7:
|
||||||
|
r *= 0.77; // sin(25.7) / (1- sin(25.7)
|
||||||
|
break;
|
||||||
|
case 8:
|
||||||
|
r *= 0.62; // sin(22.5) / (1- sin(22.5)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
return outerRadius;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the outer radius of the body tube. If the radius is less than the wall thickness,
|
||||||
|
* the wall thickness is decreased accordingly of the value of the radius.
|
||||||
|
* This method sets the automatic radius off.
|
||||||
|
*
|
||||||
|
* @param radius the outside radius in standard units
|
||||||
|
*/
|
||||||
|
public void setOuterRadius(double radius) {
|
||||||
|
if ((this.outerRadius == radius) && (autoRadius == false))
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.autoRadius = false;
|
||||||
|
this.outerRadius = Math.max(radius, 0);
|
||||||
|
|
||||||
|
if (this.thickness > this.outerRadius)
|
||||||
|
this.thickness = this.outerRadius;
|
||||||
|
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
|
||||||
|
clearPreset();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets whether the radius is selected automatically or not.
|
||||||
|
*/
|
||||||
|
public void setOuterRadiusAutomatic(boolean auto) {
|
||||||
|
if (autoRadius == auto)
|
||||||
|
return;
|
||||||
|
|
||||||
|
autoRadius = auto;
|
||||||
|
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
|
||||||
|
clearPreset();
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getInnerRadius() {
|
||||||
|
return Math.max(getOuterRadius() - thickness, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInnerRadius(double r) {
|
||||||
|
setThickness(getOuterRadius() - r);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the component wall thickness.
|
||||||
|
*/
|
||||||
|
public double getThickness() {
|
||||||
|
return Math.min(thickness, getOuterRadius());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the component wall thickness. Values greater than the maximum radius are not
|
||||||
|
* allowed, and will result in setting the thickness to the maximum radius.
|
||||||
|
*/
|
||||||
|
public void setThickness(double thickness) {
|
||||||
|
if ((this.thickness == thickness))
|
||||||
|
return;
|
||||||
|
this.thickness = MathUtil.clamp(thickness, 0, getOuterRadius());
|
||||||
|
fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
|
||||||
|
clearPreset();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the number of fins in the set.
|
||||||
|
* @return The number of fins.
|
||||||
|
*/
|
||||||
|
public int getFinCount() {
|
||||||
|
return fins;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the number of fins in the set.
|
||||||
|
* @param n The number of fins, greater of equal to one.
|
||||||
|
*/
|
||||||
|
public void setFinCount(int n) {
|
||||||
|
if (fins == n)
|
||||||
|
return;
|
||||||
|
if (n < 1)
|
||||||
|
n = 1;
|
||||||
|
if (n > 8)
|
||||||
|
n = 8;
|
||||||
|
fins = n;
|
||||||
|
finRotation = Transformation.rotate_x(2 * Math.PI / fins);
|
||||||
|
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the base rotation amount of the first fin.
|
||||||
|
* @return The base rotation amount.
|
||||||
|
*/
|
||||||
|
public double getBaseRotation() {
|
||||||
|
return rotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getFinRotation() {
|
||||||
|
return 2 * Math.PI / fins;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the base rotation amount of the first fin.
|
||||||
|
* @param r The base rotation amount.
|
||||||
|
*/
|
||||||
|
public void setBaseRotation(double r) {
|
||||||
|
r = MathUtil.reduce180(r);
|
||||||
|
if (MathUtil.equals(r, rotation))
|
||||||
|
return;
|
||||||
|
rotation = r;
|
||||||
|
baseRotation = Transformation.rotate_x(rotation);
|
||||||
|
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Transformation getBaseRotationTransformation() {
|
||||||
|
return baseRotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Transformation getFinRotationTransformation() {
|
||||||
|
return finRotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRelativePosition(RocketComponent.Position position) {
|
||||||
|
super.setRelativePosition(position);
|
||||||
|
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setPositionValue(double value) {
|
||||||
|
super.setPositionValue(value);
|
||||||
|
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double getComponentVolume() {
|
||||||
|
double or = getOuterRadius();
|
||||||
|
double ir = getInnerRadius();
|
||||||
|
double volume = or * or - ir * ir;
|
||||||
|
volume *= Math.PI;
|
||||||
|
volume *= length;
|
||||||
|
volume *= fins;
|
||||||
|
return volume;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getComponentName() {
|
||||||
|
//// Tube Fin Set
|
||||||
|
return trans.get("TubeFinSet.TubeFinSet");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Coordinate getComponentCG() {
|
||||||
|
double mass = getComponentMass(); // safe
|
||||||
|
double halflength = length / 2;
|
||||||
|
|
||||||
|
if (fins == 1) {
|
||||||
|
return baseRotation.transform(new Coordinate(halflength, getOuterRadius() + getBodyRadius(), 0, mass));
|
||||||
|
} else {
|
||||||
|
return baseRotation.transform(new Coordinate(halflength, 0, 0, mass));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double getLongitudinalUnitInertia() {
|
||||||
|
// Logitudinal Unit Inertia for a single tube fin.
|
||||||
|
// 1/12 * (3 * (r1^2 + r2^2) + h^2)
|
||||||
|
final double inertia = (3 * (MathUtil.pow2(getOuterRadius()) + MathUtil.pow2(getInnerRadius())) + MathUtil.pow2(getLength())) / 12;
|
||||||
|
if (fins == 1) {
|
||||||
|
return inertia;
|
||||||
|
}
|
||||||
|
|
||||||
|
// translate each to the center of mass.
|
||||||
|
final double hypot = getOuterRadius() + getBodyRadius();
|
||||||
|
final double finrotation = 2 * Math.PI / fins;
|
||||||
|
double angularoffset = 0.0;
|
||||||
|
double totalInertia = 0.0;
|
||||||
|
for (int i = 0; i < fins; i++) {
|
||||||
|
double offset = hypot * Math.cos(angularoffset);
|
||||||
|
totalInertia += inertia + MathUtil.pow2(offset);
|
||||||
|
angularoffset += finrotation;
|
||||||
|
}
|
||||||
|
return totalInertia;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double getRotationalUnitInertia() {
|
||||||
|
// The rotational inertia of a single fin about its center.
|
||||||
|
// 1/2 * (r1^2 + r2^2)
|
||||||
|
double icentermass = (MathUtil.pow2(getInnerRadius()) + MathUtil.pow2(getOuterRadius())) / 2;
|
||||||
|
if (fins == 1) {
|
||||||
|
return icentermass;
|
||||||
|
} else {
|
||||||
|
// Use parallel axis rule and multiply by number of fins.
|
||||||
|
return fins * (icentermass + MathUtil.pow2(getOuterRadius()) + getBodyRadius());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean allowsChildren() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Type getPresetType() {
|
||||||
|
return ComponentPreset.Type.BODY_TUBE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCompatible(Class<? extends RocketComponent> type) {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<Coordinate> getComponentBounds() {
|
||||||
|
List<Coordinate> bounds = new ArrayList<Coordinate>();
|
||||||
|
double r = getBodyRadius();
|
||||||
|
|
||||||
|
addBound(bounds, 0, 2 * (r + outerRadius));
|
||||||
|
addBound(bounds, length, 2 * (r + outerRadius));
|
||||||
|
|
||||||
|
return bounds;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the radius of the BodyComponent the fin set is situated on. Currently
|
||||||
|
* only supports SymmetricComponents and returns the radius at the starting point of the
|
||||||
|
* root chord.
|
||||||
|
*
|
||||||
|
* @return radius of the underlying BodyComponent or 0 if none exists.
|
||||||
|
*/
|
||||||
|
public double getBodyRadius() {
|
||||||
|
RocketComponent s;
|
||||||
|
|
||||||
|
s = this.getParent();
|
||||||
|
while (s != null) {
|
||||||
|
if (s instanceof SymmetricComponent) {
|
||||||
|
double x = this.toRelative(new Coordinate(0, 0, 0), s)[0].x;
|
||||||
|
return ((SymmetricComponent) s).getRadius(x);
|
||||||
|
}
|
||||||
|
s = s.getParent();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -20,6 +20,7 @@ import net.sf.openrocket.rocketcomponent.LaunchLug;
|
|||||||
import net.sf.openrocket.rocketcomponent.MassObject;
|
import net.sf.openrocket.rocketcomponent.MassObject;
|
||||||
import net.sf.openrocket.rocketcomponent.RecoveryDevice;
|
import net.sf.openrocket.rocketcomponent.RecoveryDevice;
|
||||||
import net.sf.openrocket.rocketcomponent.RocketComponent;
|
import net.sf.openrocket.rocketcomponent.RocketComponent;
|
||||||
|
import net.sf.openrocket.rocketcomponent.TubeFinSet;
|
||||||
import net.sf.openrocket.simulation.RK4SimulationStepper;
|
import net.sf.openrocket.simulation.RK4SimulationStepper;
|
||||||
import net.sf.openrocket.util.BugException;
|
import net.sf.openrocket.util.BugException;
|
||||||
import net.sf.openrocket.util.BuildProperties;
|
import net.sf.openrocket.util.BuildProperties;
|
||||||
@ -743,6 +744,7 @@ public abstract class Preferences implements ChangeSource {
|
|||||||
private static final HashMap<Class<?>, String> DEFAULT_COLORS = new HashMap<Class<?>, String>();
|
private static final HashMap<Class<?>, String> DEFAULT_COLORS = new HashMap<Class<?>, String>();
|
||||||
static {
|
static {
|
||||||
DEFAULT_COLORS.put(BodyComponent.class, "0,0,240");
|
DEFAULT_COLORS.put(BodyComponent.class, "0,0,240");
|
||||||
|
DEFAULT_COLORS.put(TubeFinSet.class, "0,0,200");
|
||||||
DEFAULT_COLORS.put(FinSet.class, "0,0,200");
|
DEFAULT_COLORS.put(FinSet.class, "0,0,200");
|
||||||
DEFAULT_COLORS.put(LaunchLug.class, "0,0,180");
|
DEFAULT_COLORS.put(LaunchLug.class, "0,0,180");
|
||||||
DEFAULT_COLORS.put(InternalComponent.class, "170,0,100");
|
DEFAULT_COLORS.put(InternalComponent.class, "170,0,100");
|
||||||
|
BIN
swing/resources/datafiles/examples/Tube Fin.ork
Normal file
BIN
swing/resources/datafiles/examples/Tube Fin.ork
Normal file
Binary file not shown.
@ -0,0 +1,170 @@
|
|||||||
|
package net.sf.openrocket.gui.configdialog;
|
||||||
|
|
||||||
|
|
||||||
|
import javax.swing.JCheckBox;
|
||||||
|
import javax.swing.JComboBox;
|
||||||
|
import javax.swing.JLabel;
|
||||||
|
import javax.swing.JPanel;
|
||||||
|
import javax.swing.JSpinner;
|
||||||
|
|
||||||
|
import net.miginfocom.swing.MigLayout;
|
||||||
|
import net.sf.openrocket.document.OpenRocketDocument;
|
||||||
|
import net.sf.openrocket.gui.SpinnerEditor;
|
||||||
|
import net.sf.openrocket.gui.adaptors.DoubleModel;
|
||||||
|
import net.sf.openrocket.gui.adaptors.EnumModel;
|
||||||
|
import net.sf.openrocket.gui.adaptors.IntegerModel;
|
||||||
|
import net.sf.openrocket.gui.components.BasicSlider;
|
||||||
|
import net.sf.openrocket.gui.components.UnitSelector;
|
||||||
|
import net.sf.openrocket.l10n.Translator;
|
||||||
|
import net.sf.openrocket.material.Material;
|
||||||
|
import net.sf.openrocket.rocketcomponent.RocketComponent;
|
||||||
|
import net.sf.openrocket.startup.Application;
|
||||||
|
import net.sf.openrocket.unit.UnitGroup;
|
||||||
|
|
||||||
|
public class TubeFinSetConfig extends RocketComponentConfig {
|
||||||
|
|
||||||
|
private MotorConfig motorConfigPane = null;
|
||||||
|
private static final Translator trans = Application.getTranslator();
|
||||||
|
|
||||||
|
public TubeFinSetConfig(OpenRocketDocument d, RocketComponent c) {
|
||||||
|
super(d, c);
|
||||||
|
|
||||||
|
JPanel primary = new JPanel(new MigLayout("fill"));
|
||||||
|
|
||||||
|
|
||||||
|
JPanel panel = new JPanel(new MigLayout("gap rel unrel", "[][65lp::][30lp::][]", ""));
|
||||||
|
|
||||||
|
//// Number of fins
|
||||||
|
panel.add(new JLabel(trans.get("TubeFinSetCfg.lbl.Nbroffins")));
|
||||||
|
|
||||||
|
IntegerModel im = new IntegerModel(component, "FinCount", 1, 8);
|
||||||
|
|
||||||
|
JSpinner spin = new JSpinner(im.getSpinnerModel());
|
||||||
|
spin.setEditor(new SpinnerEditor(spin));
|
||||||
|
panel.add(spin, "growx, wrap");
|
||||||
|
|
||||||
|
//// Length:
|
||||||
|
panel.add(new JLabel(trans.get("TubeFinSetCfg.lbl.Length")));
|
||||||
|
|
||||||
|
DoubleModel m = new DoubleModel(component, "Length", 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(0, 0.02, 0.1)), "w 100lp, wrap para");
|
||||||
|
|
||||||
|
|
||||||
|
//// Outer diameter:
|
||||||
|
panel.add(new JLabel(trans.get("TubeFinSetCfg.lbl.Outerdiam")));
|
||||||
|
|
||||||
|
DoubleModel od = new DoubleModel(component, "OuterRadius", 2, UnitGroup.UNITS_LENGTH, 0);
|
||||||
|
// Diameter = 2*Radius
|
||||||
|
|
||||||
|
spin = new JSpinner(od.getSpinnerModel());
|
||||||
|
spin.setEditor(new SpinnerEditor(spin));
|
||||||
|
panel.add(spin, "growx");
|
||||||
|
|
||||||
|
panel.add(new UnitSelector(od), "growx");
|
||||||
|
panel.add(new BasicSlider(od.getSliderModel(0, 0.04, 0.2)), "w 100lp, wrap rel");
|
||||||
|
|
||||||
|
JCheckBox check = new JCheckBox(od.getAutomaticAction());
|
||||||
|
//// Automatic
|
||||||
|
check.setText(trans.get("TubeFinSetCfg.checkbox.Automatic"));
|
||||||
|
panel.add(check, "skip, span 2, wrap");
|
||||||
|
|
||||||
|
//// Inner diameter:
|
||||||
|
panel.add(new JLabel(trans.get("TubeFinSetCfg.lbl.Innerdiam")));
|
||||||
|
|
||||||
|
// Diameter = 2*Radius
|
||||||
|
m = new DoubleModel(component, "InnerRadius", 2, 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(new DoubleModel(0), od)), "w 100lp, wrap rel");
|
||||||
|
|
||||||
|
|
||||||
|
//// Wall thickness
|
||||||
|
//// Thickness:
|
||||||
|
panel.add(new JLabel(trans.get("TubeFinSetCfg.lbl.Thickness")));
|
||||||
|
|
||||||
|
m = new DoubleModel(component, "Thickness", 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(0, 0.01)), "w 100lp, wrap 20lp");
|
||||||
|
|
||||||
|
|
||||||
|
//// Base rotation
|
||||||
|
//// Fin rotation:
|
||||||
|
JLabel label = new JLabel(trans.get("TubeFinSetCfg.lbl.Finrotation"));
|
||||||
|
//// The angle of the first fin in the fin set.
|
||||||
|
label.setToolTipText(trans.get("TubeFinSetCfg.lbl.ttip.Finrotation"));
|
||||||
|
panel.add(label);
|
||||||
|
|
||||||
|
m = new DoubleModel(component, "BaseRotation", UnitGroup.UNITS_ANGLE);
|
||||||
|
|
||||||
|
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(-Math.PI, Math.PI)), "w 100lp, wrap");
|
||||||
|
|
||||||
|
primary.add(panel, "grow, gapright 20lp");
|
||||||
|
panel = new JPanel(new MigLayout("gap rel unrel", "[][65lp::][30lp::][]", ""));
|
||||||
|
|
||||||
|
//// Position relative to:
|
||||||
|
panel.add(new JLabel(trans.get("LaunchLugCfg.lbl.Posrelativeto")));
|
||||||
|
|
||||||
|
JComboBox combo = new JComboBox(
|
||||||
|
new EnumModel<RocketComponent.Position>(component, "RelativePosition",
|
||||||
|
new RocketComponent.Position[] {
|
||||||
|
RocketComponent.Position.TOP,
|
||||||
|
RocketComponent.Position.MIDDLE,
|
||||||
|
RocketComponent.Position.BOTTOM,
|
||||||
|
RocketComponent.Position.ABSOLUTE
|
||||||
|
}));
|
||||||
|
panel.add(combo, "spanx, growx, wrap");
|
||||||
|
|
||||||
|
//// plus
|
||||||
|
panel.add(new JLabel(trans.get("LaunchLugCfg.lbl.plus")), "right");
|
||||||
|
|
||||||
|
m = new DoubleModel(component, "PositionValue", 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(
|
||||||
|
new DoubleModel(component.getParent(), "Length", -1.0, UnitGroup.UNITS_NONE),
|
||||||
|
new DoubleModel(component.getParent(), "Length"))),
|
||||||
|
"w 100lp, wrap para");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//// Material
|
||||||
|
panel.add(materialPanel(Material.Type.BULK), "span, wrap");
|
||||||
|
|
||||||
|
primary.add(panel, "grow");
|
||||||
|
|
||||||
|
//// General and General properties
|
||||||
|
tabbedPane.insertTab(trans.get("LaunchLugCfg.tab.General"), null, primary,
|
||||||
|
trans.get("LaunchLugCfg.tab.Generalprop"), 0);
|
||||||
|
tabbedPane.setSelectedIndex(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateFields() {
|
||||||
|
super.updateFields();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -17,6 +17,7 @@ import net.sf.openrocket.rocketcomponent.RingComponent;
|
|||||||
import net.sf.openrocket.rocketcomponent.RocketComponent;
|
import net.sf.openrocket.rocketcomponent.RocketComponent;
|
||||||
import net.sf.openrocket.rocketcomponent.Transition;
|
import net.sf.openrocket.rocketcomponent.Transition;
|
||||||
import net.sf.openrocket.rocketcomponent.Transition.Shape;
|
import net.sf.openrocket.rocketcomponent.Transition.Shape;
|
||||||
|
import net.sf.openrocket.rocketcomponent.TubeFinSet;
|
||||||
import net.sf.openrocket.util.Coordinate;
|
import net.sf.openrocket.util.Coordinate;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@ -102,6 +103,8 @@ public class ComponentRenderer {
|
|||||||
} else if (c instanceof FinSet) {
|
} else if (c instanceof FinSet) {
|
||||||
if (which == Surface.OUTSIDE)
|
if (which == Surface.OUTSIDE)
|
||||||
fr.renderFinSet(gl, (FinSet) c);
|
fr.renderFinSet(gl, (FinSet) c);
|
||||||
|
} else if (c instanceof TubeFinSet) {
|
||||||
|
renderTubeFins( gl, (TubeFinSet) c, which);
|
||||||
} else {
|
} else {
|
||||||
renderOther(gl, c);
|
renderOther(gl, c);
|
||||||
}
|
}
|
||||||
@ -266,6 +269,21 @@ public class ComponentRenderer {
|
|||||||
renderTube(gl, which, t.getOuterRadius(), t.getInnerRadius(), t.getLength());
|
renderTube(gl, which, t.getOuterRadius(), t.getInnerRadius(), t.getLength());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void renderTubeFins(GL2 gl, TubeFinSet fs, Surface which) {
|
||||||
|
gl.glPushMatrix();
|
||||||
|
gl.glMatrixMode(GLMatrixFunc.GL_MODELVIEW);
|
||||||
|
System.out.println(fs.getBaseRotation());
|
||||||
|
gl.glRotated(fs.getBaseRotation() * (180.0 / Math.PI), 1, 0, 0);
|
||||||
|
for( int i = 0; i< fs.getFinCount(); i++ ) {
|
||||||
|
gl.glPushMatrix();
|
||||||
|
gl.glTranslated(0, fs.getOuterRadius() + fs.getBodyRadius(), 0);
|
||||||
|
renderTube(gl, which, fs.getOuterRadius(), fs.getInnerRadius(), fs.getLength());
|
||||||
|
gl.glPopMatrix();
|
||||||
|
gl.glRotated(360.0 / fs.getFinCount(), 1, 0, 0);
|
||||||
|
}
|
||||||
|
gl.glPopMatrix();
|
||||||
|
}
|
||||||
|
|
||||||
private void renderMassObject(GL2 gl, MassObject o) {
|
private void renderMassObject(GL2 gl, MassObject o) {
|
||||||
gl.glRotated(90, 0, 1.0, 0);
|
gl.glRotated(90, 0, 1.0, 0);
|
||||||
|
|
||||||
|
@ -25,9 +25,6 @@ import javax.swing.event.TreeSelectionListener;
|
|||||||
import javax.swing.tree.TreePath;
|
import javax.swing.tree.TreePath;
|
||||||
import javax.swing.tree.TreeSelectionModel;
|
import javax.swing.tree.TreeSelectionModel;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import net.miginfocom.swing.MigLayout;
|
import net.miginfocom.swing.MigLayout;
|
||||||
import net.sf.openrocket.document.OpenRocketDocument;
|
import net.sf.openrocket.document.OpenRocketDocument;
|
||||||
import net.sf.openrocket.gui.components.StyledLabel;
|
import net.sf.openrocket.gui.components.StyledLabel;
|
||||||
@ -54,12 +51,16 @@ import net.sf.openrocket.rocketcomponent.Streamer;
|
|||||||
import net.sf.openrocket.rocketcomponent.Transition;
|
import net.sf.openrocket.rocketcomponent.Transition;
|
||||||
import net.sf.openrocket.rocketcomponent.TrapezoidFinSet;
|
import net.sf.openrocket.rocketcomponent.TrapezoidFinSet;
|
||||||
import net.sf.openrocket.rocketcomponent.TubeCoupler;
|
import net.sf.openrocket.rocketcomponent.TubeCoupler;
|
||||||
|
import net.sf.openrocket.rocketcomponent.TubeFinSet;
|
||||||
import net.sf.openrocket.startup.Application;
|
import net.sf.openrocket.startup.Application;
|
||||||
import net.sf.openrocket.startup.Preferences;
|
import net.sf.openrocket.startup.Preferences;
|
||||||
import net.sf.openrocket.util.BugException;
|
import net.sf.openrocket.util.BugException;
|
||||||
import net.sf.openrocket.util.Pair;
|
import net.sf.openrocket.util.Pair;
|
||||||
import net.sf.openrocket.util.Reflection;
|
import net.sf.openrocket.util.Reflection;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A component that contains addition buttons to add different types of rocket components
|
* A component that contains addition buttons to add different types of rocket components
|
||||||
* to a rocket. It enables and disables buttons according to the current selection of a
|
* to a rocket. It enables and disables buttons according to the current selection of a
|
||||||
@ -122,6 +123,8 @@ public class ComponentAddButtons extends JPanel implements Scrollable {
|
|||||||
new FinButton(EllipticalFinSet.class, trans.get("compaddbuttons.Elliptical")),
|
new FinButton(EllipticalFinSet.class, trans.get("compaddbuttons.Elliptical")),
|
||||||
//// Freeform
|
//// Freeform
|
||||||
new FinButton(FreeformFinSet.class, trans.get("compaddbuttons.Freeform")),
|
new FinButton(FreeformFinSet.class, trans.get("compaddbuttons.Freeform")),
|
||||||
|
//// Freeform
|
||||||
|
new FinButton(TubeFinSet.class, trans.get("compaddbuttons.Tubefin")),
|
||||||
//// Launch lug
|
//// Launch lug
|
||||||
new FinButton(LaunchLug.class, trans.get("compaddbuttons.Launchlug")));
|
new FinButton(LaunchLug.class, trans.get("compaddbuttons.Launchlug")));
|
||||||
|
|
||||||
|
@ -27,6 +27,7 @@ import net.sf.openrocket.rocketcomponent.Streamer;
|
|||||||
import net.sf.openrocket.rocketcomponent.Transition;
|
import net.sf.openrocket.rocketcomponent.Transition;
|
||||||
import net.sf.openrocket.rocketcomponent.TrapezoidFinSet;
|
import net.sf.openrocket.rocketcomponent.TrapezoidFinSet;
|
||||||
import net.sf.openrocket.rocketcomponent.TubeCoupler;
|
import net.sf.openrocket.rocketcomponent.TubeCoupler;
|
||||||
|
import net.sf.openrocket.rocketcomponent.TubeFinSet;
|
||||||
import net.sf.openrocket.startup.Application;
|
import net.sf.openrocket.startup.Application;
|
||||||
|
|
||||||
public class ComponentIcons {
|
public class ComponentIcons {
|
||||||
@ -45,31 +46,25 @@ public class ComponentIcons {
|
|||||||
load("nosecone", trans.get("ComponentIcons.Nosecone"), NoseCone.class);
|
load("nosecone", trans.get("ComponentIcons.Nosecone"), NoseCone.class);
|
||||||
// // Body tube
|
// // Body tube
|
||||||
load("bodytube", trans.get("ComponentIcons.Bodytube"), BodyTube.class);
|
load("bodytube", trans.get("ComponentIcons.Bodytube"), BodyTube.class);
|
||||||
// // Transition
|
//// Transition
|
||||||
load("transition", trans.get("ComponentIcons.Transition"),
|
load("transition", trans.get("ComponentIcons.Transition"), Transition.class);
|
||||||
Transition.class);
|
//// Trapezoidal fin set
|
||||||
// // Trapezoidal fin set
|
load("trapezoidfin", trans.get("ComponentIcons.Trapezoidalfinset"), TrapezoidFinSet.class);
|
||||||
load("trapezoidfin", trans.get("ComponentIcons.Trapezoidalfinset"),
|
//// Elliptical fin set
|
||||||
TrapezoidFinSet.class);
|
load("ellipticalfin", trans.get("ComponentIcons.Ellipticalfinset"), EllipticalFinSet.class);
|
||||||
// // Elliptical fin set
|
//// Freeform fin set
|
||||||
load("ellipticalfin", trans.get("ComponentIcons.Ellipticalfinset"),
|
load("freeformfin", trans.get("ComponentIcons.Freeformfinset"), FreeformFinSet.class);
|
||||||
EllipticalFinSet.class);
|
//// Tube fin set
|
||||||
// // Freeform fin set
|
load("tubefin", trans.get("ComponentIcons.Tubefinset"), TubeFinSet.class);
|
||||||
load("freeformfin", trans.get("ComponentIcons.Freeformfinset"),
|
//// Launch lug
|
||||||
FreeformFinSet.class);
|
load("launchlug", trans.get("ComponentIcons.Launchlug"), LaunchLug.class);
|
||||||
// // Launch lug
|
//// Inner tube
|
||||||
load("launchlug", trans.get("ComponentIcons.Launchlug"),
|
load("innertube", trans.get("ComponentIcons.Innertube"), InnerTube.class);
|
||||||
LaunchLug.class);
|
//// Tube coupler
|
||||||
// // Inner tube
|
load("tubecoupler", trans.get("ComponentIcons.Tubecoupler"), TubeCoupler.class);
|
||||||
load("innertube", trans.get("ComponentIcons.Innertube"),
|
//// Centering ring
|
||||||
InnerTube.class);
|
load("centeringring", trans.get("ComponentIcons.Centeringring"), CenteringRing.class);
|
||||||
// // Tube coupler
|
//// Bulk head
|
||||||
load("tubecoupler", trans.get("ComponentIcons.Tubecoupler"),
|
|
||||||
TubeCoupler.class);
|
|
||||||
// // Centering ring
|
|
||||||
load("centeringring", trans.get("ComponentIcons.Centeringring"),
|
|
||||||
CenteringRing.class);
|
|
||||||
// // Bulk head
|
|
||||||
load("bulkhead", trans.get("ComponentIcons.Bulkhead"), Bulkhead.class);
|
load("bulkhead", trans.get("ComponentIcons.Bulkhead"), Bulkhead.class);
|
||||||
// // Engine block
|
// // Engine block
|
||||||
load("engineblock", trans.get("ComponentIcons.Engineblock"),
|
load("engineblock", trans.get("ComponentIcons.Engineblock"),
|
||||||
|
@ -0,0 +1,70 @@
|
|||||||
|
package net.sf.openrocket.gui.rocketfigure;
|
||||||
|
|
||||||
|
import net.sf.openrocket.util.Coordinate;
|
||||||
|
import net.sf.openrocket.util.Transformation;
|
||||||
|
|
||||||
|
import java.awt.Shape;
|
||||||
|
import java.awt.geom.Ellipse2D;
|
||||||
|
import java.awt.geom.Rectangle2D;
|
||||||
|
|
||||||
|
|
||||||
|
public class TubeFinSetShapes extends RocketComponentShapes {
|
||||||
|
|
||||||
|
public static Shape[] getShapesSide(net.sf.openrocket.rocketcomponent.RocketComponent component,
|
||||||
|
Transformation transformation) {
|
||||||
|
net.sf.openrocket.rocketcomponent.TubeFinSet finset = (net.sf.openrocket.rocketcomponent.TubeFinSet)component;
|
||||||
|
|
||||||
|
int fins = finset.getFinCount();
|
||||||
|
double length = finset.getLength();
|
||||||
|
double outerradius = finset.getOuterRadius();
|
||||||
|
double bodyradius = finset.getBodyRadius();
|
||||||
|
|
||||||
|
Coordinate[] start = finset.toAbsolute(new Coordinate(0,0,0));
|
||||||
|
|
||||||
|
Transformation baseRotation = finset.getBaseRotationTransformation();
|
||||||
|
Transformation finRotation = finset.getFinRotationTransformation();
|
||||||
|
|
||||||
|
// Translate & rotate the coordinates
|
||||||
|
for (int i=0; i<start.length; i++) {
|
||||||
|
start[i] = baseRotation.transform(transformation.transform(start[i].add(0,bodyradius+outerradius,0)));
|
||||||
|
}
|
||||||
|
|
||||||
|
//start = baseRotation.transform(start);
|
||||||
|
|
||||||
|
Shape[] s = new Shape[fins];
|
||||||
|
for (int i=0; i<fins; i++) {
|
||||||
|
s[i] = new Rectangle2D.Double(start[0].x*S,(start[0].y-outerradius)*S,length*S,2*outerradius*S);
|
||||||
|
start = finRotation.transform(start);
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static Shape[] getShapesBack(net.sf.openrocket.rocketcomponent.RocketComponent component,
|
||||||
|
Transformation transformation) {
|
||||||
|
net.sf.openrocket.rocketcomponent.TubeFinSet finset = (net.sf.openrocket.rocketcomponent.TubeFinSet)component;
|
||||||
|
|
||||||
|
int fins = finset.getFinCount();
|
||||||
|
double outerradius = finset.getOuterRadius();
|
||||||
|
double bodyradius = finset.getBodyRadius();
|
||||||
|
|
||||||
|
Coordinate[] start = finset.toAbsolute(new Coordinate(0,0,0));
|
||||||
|
|
||||||
|
Transformation baseRotation = finset.getBaseRotationTransformation();
|
||||||
|
Transformation finRotation = finset.getFinRotationTransformation();
|
||||||
|
|
||||||
|
// Translate & rotate the coordinates
|
||||||
|
for (int i=0; i<start.length; i++) {
|
||||||
|
start[i] = baseRotation.transform(transformation.transform(start[i].add(0,bodyradius+outerradius,0)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Shape[] s = new Shape[fins];
|
||||||
|
for (int i=0; i < fins; i++) {
|
||||||
|
s[i] = new Ellipse2D.Double((start[0].z-outerradius)*S,(start[0].y-outerradius)*S,2*outerradius*S,2*outerradius*S);
|
||||||
|
start = finRotation.transform(start);
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user