[Feature] Freeform Fins may not be attched to variable-shaped body components

- Fins may be attached to Transitions (and subclass NoseCones )

  [Fix] FinSet now implements the Ring-Instanceable interface
  [Refactor] Rocket inherits from ComponentAssembly instead of RocketComponent
  [Fix][Refactor] Fin tabs are now correctly validated upon change
  [Fix] Fin tabs are now corrected to be no-bigger-than their fins
  [Refactor] FinSet.getBodyRadius(..) now requires an argument
  [Fix] restricted fin tab positioning to be strictly top/middle/bottom
  [Refactor] Reimplement FreeformFinSet.setPoint(...)
  [Fix] Prevent Freeform Fins movement past parent's top/front
  [bugfix] Fins are now addable to transitions from the GUI
  [Fix] Fins, Transitions are now drawn correctly in fin-design window
  [Minor] Added makeV2 rocket to TestRockets

  [fix] getRootPoints() impl & test
This commit is contained in:
Daniel_M_Williams 2016-10-10 09:22:13 -04:00
parent b268d3aa59
commit 166d358c14
28 changed files with 2667 additions and 919 deletions

View File

@ -473,7 +473,7 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator {
FinSet f = (FinSet) c;
double mac = ((FinSetCalc) calcMap.get(c)).getMACLength();
double cd = componentCf * (1 + 2 * f.getThickness() / mac) *
2 * f.getFinCount() * f.getFinArea();
2 * f.getFinCount() * f.getPlanformArea();
finFriction += cd;
if (map != null) {
@ -757,7 +757,7 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator {
for (RocketComponent c : configuration.getActiveComponents()) {
if (c instanceof FinSet) {
FinSet f = (FinSet) c;
mul += 0.6 * Math.min(f.getFinCount(), 4) * f.getFinArea() *
mul += 0.6 * Math.min(f.getFinCount(), 4) * f.getPlanformArea() *
MathUtil.pow3(Math.abs(f.toAbsolute(new Coordinate(
((FinSetCalc) calcMap.get(f)).getMidchordPos()))[0].x
- cgx)) /

View File

@ -62,16 +62,19 @@ public class FinSetCalc extends RocketComponentCalc {
public FinSetCalc(FinSet component) {
super(component);
thickness = component.getThickness();
bodyRadius = component.getBodyRadius();
finCount = component.getFinCount();
baseRotation = component.getBaseRotation();
cantAngle = component.getCantAngle();
span = component.getSpan();
finArea = component.getFinArea();
crossSection = component.getCrossSection();
FinSet fin = (FinSet) component;
calculateFinGeometry(component);
thickness = fin.getThickness();
bodyRadius = fin.getFinFront().y;
finCount = fin.getFinCount();
baseRotation = fin.getBaseRotation();
cantAngle = fin.getCantAngle();
span = fin.getSpan();
finArea = fin.getPlanformArea();
crossSection = fin.getCrossSection();
calculateFinGeometry(fin);
calculatePoly();
calculateInterferenceFinCount(component);
}
@ -246,7 +249,7 @@ public class FinSetCalc extends RocketComponentCalc {
protected void calculateFinGeometry(FinSet component) {
span = component.getSpan();
finArea = component.getFinArea();
finArea = component.getPlanformArea();
ar = 2 * pow2(span) / finArea;
Coordinate[] points = component.getFinPoints();
@ -339,7 +342,7 @@ public class FinSetCalc extends RocketComponentCalc {
cosGammaLead = 0;
rollSum = 0;
double area = 0;
double radius = component.getBodyRadius();
double radius = component.getFinFront().y;
final double dy = span / (DIVISIONS - 1);
for (int i = 0; i < DIVISIONS; i++) {

View File

@ -3,6 +3,8 @@ package net.sf.openrocket.file.openrocket.importt;
import java.util.ArrayList;
import java.util.HashMap;
import org.xml.sax.SAXException;
import net.sf.openrocket.aerodynamics.Warning;
import net.sf.openrocket.aerodynamics.WarningSet;
import net.sf.openrocket.file.DocumentLoadingContext;
@ -10,11 +12,8 @@ import net.sf.openrocket.file.simplesax.AbstractElementHandler;
import net.sf.openrocket.file.simplesax.ElementHandler;
import net.sf.openrocket.file.simplesax.PlainTextHandler;
import net.sf.openrocket.rocketcomponent.FreeformFinSet;
import net.sf.openrocket.rocketcomponent.IllegalFinPointException;
import net.sf.openrocket.util.Coordinate;
import org.xml.sax.SAXException;
/**
* A handler that reads the <point> specifications within the freeformfinset's
* <finpoints> elements.
@ -62,10 +61,7 @@ class FinSetPointHandler extends AbstractElementHandler {
@Override
public void endHandler(String element, HashMap<String, String> attributes,
String content, WarningSet warnings) {
try {
finset.setPoints(coordinates.toArray(new Coordinate[0]));
} catch (IllegalFinPointException e) {
warnings.add(Warning.fromString("Freeform fin set point definitions illegal, ignoring."));
}
finset.setPoints(coordinates.toArray(new Coordinate[0]));
}
}

View File

@ -5,13 +5,13 @@ import java.util.HashMap;
import net.sf.openrocket.aerodynamics.WarningSet;
import net.sf.openrocket.rocketcomponent.FinSet;
import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.rocketcomponent.FinSet.TabRelativePosition;
import net.sf.openrocket.rocketcomponent.position.*;
import net.sf.openrocket.util.Reflection;
class FinTabPositionSetter extends DoubleSetter {
public FinTabPositionSetter() {
super(Reflection.findMethod(FinSet.class, "setTabShift", double.class));
super(Reflection.findMethod(FinSet.class, "setTabOffset", double.class));
}
@Override
@ -23,23 +23,30 @@ class FinTabPositionSetter extends DoubleSetter {
}
String relative = attributes.get("relativeto");
FinSet.TabRelativePosition position =
(TabRelativePosition) DocumentConfig.findEnum(relative,
FinSet.TabRelativePosition.class);
if (position != null) {
((FinSet) c).setTabRelativePosition(position);
if (relative == null) {
warnings.add("Required attribute 'relativeto' not found for fin tab position.");
} else {
if (relative == null) {
warnings.add("Required attribute 'relativeto' not found for fin tab position.");
} else {
warnings.add("Illegal attribute value '" + relative + "' encountered.");
// translate from old enum names to current enum names
if( relative.contains("front")){
relative = "top";
}else if( relative.contains("center")){
relative = "middle";
}else if( relative.contains("end")){
relative = "bottom";
}
AxialMethod position = (AxialMethod) DocumentConfig.findEnum(relative, AxialMethod.class);
if( null == position ){
warnings.add("Illegal attribute value '" + relative + "' encountered.");
}else{
((FinSet) c).setTabOffsetMethod(position);
super.set(c, s, attributes, warnings);
}
}
super.set(c, s, attributes, warnings);
}

View File

@ -29,8 +29,8 @@ public class FinSetSaver extends ExternalComponentSaver {
elements.add("<tabheight>" + fins.getTabHeight() + "</tabheight>");
elements.add("<tablength>" + fins.getTabLength() + "</tablength>");
elements.add("<tabposition relativeto=\"" +
fins.getTabRelativePosition().name().toLowerCase(Locale.ENGLISH) + "\">" +
fins.getTabShift() + "</tabposition>");
fins.getTabOffsetMethod().name().toLowerCase(Locale.ENGLISH) + "\">" +
fins.getTabFrontEdge() + "</tabposition>");
}

View File

@ -64,7 +64,7 @@ public class FinSetDTO extends BasePartDTO {
setCantAngle(theORFinSet.getCantAngle());
setTabDepth(theORFinSet.getTabHeight() * RocksimCommonConstants.ROCKSIM_TO_OPENROCKET_LENGTH);
setTabLength(theORFinSet.getTabLength() * RocksimCommonConstants.ROCKSIM_TO_OPENROCKET_LENGTH);
setTabOffset(theORFinSet.getTabShift() * RocksimCommonConstants.ROCKSIM_TO_OPENROCKET_LENGTH);
setTabOffset(theORFinSet.getTabOffset() * RocksimCommonConstants.ROCKSIM_TO_OPENROCKET_LENGTH);
setThickness(theORFinSet.getThickness() * RocksimCommonConstants.ROCKSIM_TO_OPENROCKET_LENGTH);
setRadialAngle(theORFinSet.getBaseRotation());

View File

@ -8,6 +8,8 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import org.xml.sax.SAXException;
import net.sf.openrocket.aerodynamics.WarningSet;
import net.sf.openrocket.file.DocumentLoadingContext;
import net.sf.openrocket.file.rocksim.RocksimCommonConstants;
@ -21,14 +23,11 @@ import net.sf.openrocket.rocketcomponent.EllipticalFinSet;
import net.sf.openrocket.rocketcomponent.ExternalComponent;
import net.sf.openrocket.rocketcomponent.FinSet;
import net.sf.openrocket.rocketcomponent.FreeformFinSet;
import net.sf.openrocket.rocketcomponent.IllegalFinPointException;
import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.rocketcomponent.TrapezoidFinSet;
import net.sf.openrocket.rocketcomponent.position.AxialMethod;
import net.sf.openrocket.util.Coordinate;
import org.xml.sax.SAXException;
/**
* A SAX handler for Rocksim fin sets. Because the type of fin may not be known first (in Rocksim file format, the fin
* shape type is in the middle of the XML structure), and because we're using SAX not DOM, all of the fin
@ -73,7 +72,7 @@ class FinSetHandler extends AbstractElementHandler {
/**
* The length of the mid-chord (aka height).
*/
@SuppressWarnings("unused") // spoiler: field IS actually used; eclipse complains anyway.
@SuppressWarnings("unused") // stored from file, but not used.
private double midChordLen = 0.0d;
/**
@ -304,11 +303,8 @@ class FinSetHandler extends AbstractElementHandler {
else if (shapeCode == 2) {
result = new FreeformFinSet();
try {
((FreeformFinSet) result).setPoints(toCoordinates(pointList, warnings));
} catch (IllegalFinPointException e) {
warnings.add("Illegal fin point set. " + e.getMessage() + " Ignoring.");
}
((FreeformFinSet) result).setPoints(toCoordinates(pointList, warnings));
}
else {
return null;
@ -318,10 +314,10 @@ class FinSetHandler extends AbstractElementHandler {
result.setFinCount(finCount);
result.setFinish(finish);
//All TTW tabs in Rocksim are relative to the front of the fin.
result.setTabRelativePosition(FinSet.TabRelativePosition.FRONT);
result.setTabOffsetMethod( AxialMethod.TOP);
result.setTabHeight(tabDepth);
result.setTabLength(tabLength);
result.setTabShift(taboffset);
result.setTabOffset(taboffset);
result.setBaseRotation(radialAngle);
result.setCrossSection(convertTipShapeCode(tipShapeCode));
result.setAxialMethod(axialMethod);

View File

@ -244,9 +244,6 @@ public class MassCalculation {
* The inertia is returned relative to the CG, and the CG is in the coordinates
* of the specified component, not global coordinates.
*
* @param calculation - i/o parameter to specifies the calculation parameters, and
* the instance returned with the calculation's tree data.
*
*/
/* package-scope */ MassCalculation calculateAssembly(){
final RocketComponent component = this.root;
@ -347,9 +344,6 @@ public class MassCalculation {
* (1) calculate overall Center-of-Mass (CM) first (down inline with data-gathering)
* (2) Move MOIs to CM via parallel axis theorem (this method)
*
* @param Center-of-Mass where the MOI should be calculated around.
* @param inertias a list of component MassData instances to condense into a single MOI
*
* @return freshly calculated Moment-of-Inertia matrix
*/
/* package-scope */ RigidBody calculateMomentOfInertia() {

View File

@ -3,7 +3,6 @@ package net.sf.openrocket.masscalc;
import java.util.HashMap;
import java.util.Map;
import net.sf.openrocket.masscalc.MassCalculation.Type;
import net.sf.openrocket.motor.Motor;
import net.sf.openrocket.rocketcomponent.FlightConfiguration;
import net.sf.openrocket.rocketcomponent.RocketComponent;
@ -25,7 +24,7 @@ public class MassCalculator implements Monitorable {
// private MassData rocketSpentMassCache;
// private MassData propellantMassCache;
private int modId=0;
private int modId = 0;
////////////////// Constructors ///////////////////
public MassCalculator() {
@ -40,7 +39,7 @@ public class MassCalculator implements Monitorable {
* - includes motors
* - for Black Powder & Composite motors, this generally *excludes* propellant
*
* @param configuration the rocket configuration to calculate for
* @param config the rocket configuration to calculate for
* @return the MassData struct of the motors at burnout
*/
public static RigidBody calculateStructure( final FlightConfiguration config) {
@ -53,7 +52,7 @@ public class MassCalculator implements Monitorable {
* - includes motors
* - for Black Powder & Composite motors, this generally *excludes* propellant
*
* @param configuration the rocket configuration to calculate for
* @param config the rocket configuration to calculate for
* @return the MassData struct of the motors at burnout
*/
public static RigidBody calculateBurnout( final FlightConfiguration config) {
@ -124,7 +123,6 @@ public class MassCalculator implements Monitorable {
* ( or mount-data collides with motor-data )
*
* @param configuration the rocket configuration
* @param type the state of the motors (none, launch mass, burnout mass)
* @return a map from each rocket component to its corresponding CG.
*/
@Deprecated
@ -152,10 +150,6 @@ public class MassCalculator implements Monitorable {
////////////////// Mass property calculations ///////////////////
@Override
public int getModID() {
return this.modId;

View File

@ -71,6 +71,7 @@ public class EllipticalFinSet extends FinSet {
if (MathUtil.equals(this.length, length))
return;
this.length = length;
validateFinTab();
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,8 @@
package net.sf.openrocket.rocketcomponent;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@ -9,9 +11,8 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.rocketcomponent.position.AxialMethod;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.util.ArrayList;
import net.sf.openrocket.util.BugException;
import net.sf.openrocket.util.Coordinate;
@ -19,10 +20,12 @@ public class FreeformFinSet extends FinSet {
private static final Logger log = LoggerFactory.getLogger(FreeformFinSet.class);
private static final Translator trans = Application.getTranslator();
private ArrayList<Coordinate> points = new ArrayList<Coordinate>();
public static final double MIN_ROOT_CHORD=0.01; // enforce this to prevent erroneous 'intersection' exceptions.
private List<Coordinate> points = new ArrayList<>();
public FreeformFinSet() {
points.add(Coordinate.NUL);
points.add(Coordinate.ZERO);
points.add(new Coordinate(0.025, 0.05));
points.add(new Coordinate(0.075, 0.05));
points.add(new Coordinate(0.05, 0));
@ -30,7 +33,7 @@ public class FreeformFinSet extends FinSet {
this.length = 0.05;
}
public FreeformFinSet(Coordinate[] finpoints) throws IllegalFinPointException {
public FreeformFinSet(Coordinate[] finpoints) {
setPoints(finpoints);
}
@ -45,7 +48,6 @@ public class FreeformFinSet extends FinSet {
* @return the new freeform fin set.
*/
public static FreeformFinSet convertFinSet(FinSet finset) {
log.info("Converting " + finset.getComponentName() + " into freeform fin set");
final RocketComponent root = finset.getRoot();
FreeformFinSet freeform;
List<RocketComponent> toInvalidate = Collections.emptyList();
@ -65,16 +67,10 @@ public class FreeformFinSet extends FinSet {
position = -1;
}
// Create the freeform fin set
Coordinate[] finpoints = finset.getFinPoints();
try {
freeform = new FreeformFinSet(finpoints);
} catch (IllegalFinPointException e) {
throw new BugException("Illegal fin points when converting existing fin to " +
"freeform fin, fin=" + finset + " points=" + Arrays.toString(finpoints),
e);
}
freeform = new FreeformFinSet(finpoints);
freeform.setAxialOffset(finset.getAxialMethod(), finset.getAxialOffset());
// Copy component attributes
toInvalidate = freeform.copyFrom(finset);
@ -107,8 +103,6 @@ public class FreeformFinSet extends FinSet {
return freeform;
}
/**
* Add a fin point between indices <code>index-1</code> and <code>index</code>.
* The point is placed at the midpoint of the current segment.
@ -124,7 +118,6 @@ public class FreeformFinSet extends FinSet {
fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
}
/**
* Remove the fin point with the given index. The first and last fin points
* cannot be removed, and will cause an <code>IllegalFinPointException</code>
@ -138,12 +131,16 @@ public class FreeformFinSet extends FinSet {
throw new IllegalFinPointException("cannot remove first or last point");
}
ArrayList<Coordinate> copy = this.points.clone();
copy.remove(index);
validate(copy);
this.points = copy;
// copy the old list in case the operation fails
List<Coordinate> copy = new ArrayList<>(this.points);
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
this.points.remove(index);
if( ! validate()){
// if error, rollback.
this.points = copy;
}
fireComponentChangeEvent(ComponentChangeEvent.AEROMASS_CHANGE);
}
@ -151,131 +148,159 @@ public class FreeformFinSet extends FinSet {
return points.size();
}
public void setPoints(Coordinate[] points) throws IllegalFinPointException {
setPoints(Arrays.asList(points));
}
/**
* The first point is assumed to be at the origin. If it isn't, it will be moved there.
*
* @param newPoints new fin points ; replaces previous fin points
*/
public void setPoints(Coordinate[] newPoints) {
if( ! Coordinate.ZERO.equals(newPoints[0])) {
final Coordinate p0 = newPoints[0];
newPoints = translatePoints(newPoints, p0.x, p0.y);
}
public void setPoints(List<Coordinate> points) throws IllegalFinPointException {
ArrayList<Coordinate> list = new ArrayList<Coordinate>(points);
validate(list);
this.points = list;
this.length = points.get(points.size() - 1).x;
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
ArrayList<Coordinate> newList = new ArrayList<>(Arrays.asList( newPoints));
setPoints( newList );
}
/**
* The first point is assumed to be at the origin. If it isn't, it will be moved there.
*
* @param newPoints New points to set as the exposed edges of the fin
*/
public void setPoints( List<Coordinate> newPoints) {
// copy the old points, in case validation fails
List<Coordinate> copy = new ArrayList<>(this.points);
this.points = newPoints;
update();
if( ! validate()){
// on error, reset to the old points
this.points = copy;
}
this.length = points.get(points.size() - 1).x;
fireComponentChangeEvent(ComponentChangeEvent.AEROMASS_CHANGE);
}
private double y_body( final double x){
return y_body( x, 0.0 );
}
private double y_body( final double x_target, final double x_ref){
final SymmetricComponent sym = (SymmetricComponent)getParent();
return ( sym.getRadius(x_target) - sym.getRadius( x_ref));
}
public void setPointRelToFin( final int index, final double x_request_fin, final double y_request_fin) throws IllegalFinPointException {
final double x_finStart_body = getAxialFront(); // x @ fin start, body frame
final double y_finStart_body = y_body( x_finStart_body);
setPoint( index, x_request_fin + x_finStart_body , y_request_fin + y_finStart_body);
}
/**
* Set the point at position <code>i</code> to coordinates (x,y).
* <p>
* Note that this method enforces basic fin shape restrictions (non-negative y,
* first and last point locations) silently, but throws an
* <code>IllegalFinPointException</code> if the point causes fin segments to
* intersect.
* <p>
* Note that this method silently enforces basic fin shape restrictions
* - points may not be within the parent body.
* - first point occurs before last (and vice versa)
* - first and last points must be on the parent body
* - non-self-intersecting fin shape (aborts set on invalid fin point)
* </p><p>
* NOTE: the fin-point axes differ from rocket axes:
* +x within the fin points foreward; +x for the rocket points aft
* </p><p>
* Moving of the first point in the X-axis is allowed, but this actually moves
* all of the other points the corresponding distance back.
* all of the other points the corresponding distance back, relative to the first.
* That is, moving the first point should not change how the rest of the
* points are positioned *relative to the fin-mount*.
*
* @param index the point index to modify.
* @param x the x-coordinate.
* @param y the y-coordinate.
* @throws IllegalFinPointException if the specified fin point would cause intersecting
* segments
* @param xRequest the x-coordinate.
* @param yRequest the y-coordinate.
*/
public void setPoint(int index, double x, double y) throws IllegalFinPointException {
if (y < 0)
y = 0;
public void setPoint( final int index, final double xRequest, final double yRequest) {
final SymmetricComponent body = (SymmetricComponent)getParent();
double x0, y0, x1, y1;
final int lastPointIndex = this.points.size() - 1;
final double xFinEnd = points.get(lastPointIndex).x;
final double xFinStart = getAxialFront(); // x of fin start, body-frame
final double yFinStart = body.getRadius( xFinStart); // y of fin start, body-frame
final double xBodyStart = -xFinStart; // x-offset from fin to body; fin-frame
if (index == 0) {
// Restrict point
x = Math.min(x, points.get(points.size() - 1).x);
y = 0;
x0 = Double.NaN;
y0 = Double.NaN;
x1 = points.get(1).x;
y1 = points.get(1).y;
// } else if ( (0 > index) || (points.size() <= index) ){
// throw new IllegalFinPointException("Point Index not available!");
} else if (index == points.size() - 1) {
// Restrict point
x = Math.max(x, 0);
y = 0;
x0 = points.get(index - 1).x;
y0 = points.get(index - 1).y;
x1 = Double.NaN;
y1 = Double.NaN;
} else {
x0 = points.get(index - 1).x;
y0 = points.get(index - 1).y;
x1 = points.get(index + 1).x;
y1 = points.get(index + 1).y;
// initial guess at these values; further checks follow.
double xAgreed = xRequest;
double yAgreed = yRequest;
// clamp x coordinates:
// within bounds, and consistent with the rest of the fin (at this time).
if( 0 == index ) {
// restrict the first point to be between the parent's start, and the last fin point
xAgreed = Math.max( xBodyStart, Math.min( xAgreed, xFinEnd - MIN_ROOT_CHORD ));
}else if( lastPointIndex == index ){
// restrict the last point to be between the first fin point, and the parent's end length.
xAgreed = Math.max( MIN_ROOT_CHORD, Math.min( xAgreed, xBodyStart + body.getLength()));
}
// adjust y-value to be consistent with body
final double yBody_atPoint= body.getRadius( xFinStart + xAgreed) - yFinStart;
if (index == 0 || index == lastPointIndex) {
// for the first and last points: set y-value to *exactly* match parent body:
yAgreed = yBody_atPoint;
}else{
// for all other points, merely insist that the point is outside the body...
yAgreed = Math.max( yAgreed, yBody_atPoint);
}
// if moving either begin or end points, we'll probably have to update the position, as well.
final AxialMethod locationMethod = getAxialMethod();
final double priorXOffset = getAxialOffset();
// Check for intersecting
double px0, py0, px1, py1;
px0 = 0;
py0 = 0;
for (int i = 1; i < points.size(); i++) {
px1 = points.get(i).x;
py1 = points.get(i).y;
if( 0 == index){
movePoints( xAgreed);
this.length = points.get( lastPointIndex ).x;
if (i != index - 1 && i != index && i != index + 1) {
if (intersects(x0, y0, x, y, px0, py0, px1, py1)) {
throw new IllegalFinPointException("segments intersect");
}
if( AxialMethod.TOP == locationMethod){
setAxialOffset( AxialMethod.TOP, priorXOffset + xAgreed );
}else if(AxialMethod.MIDDLE == locationMethod){
setAxialOffset( AxialMethod.MIDDLE, priorXOffset + xAgreed/2 );
}
if (i != index && i != index + 1 && i != index + 2) {
if (intersects(x, y, x1, y1, px0, py0, px1, py1)) {
throw new IllegalFinPointException("segments intersect");
}
}else if( lastPointIndex == index ){
points.set(index, new Coordinate( xAgreed, yAgreed ));
this.length = xAgreed;
if( AxialMethod.MIDDLE == locationMethod){
setAxialOffset( AxialMethod.MIDDLE, priorXOffset + (xAgreed - xFinEnd)/2 );
}else if(AxialMethod.BOTTOM== locationMethod){
setAxialOffset( AxialMethod.BOTTOM, priorXOffset + (xAgreed - xFinEnd) );
}
px0 = px1;
py0 = py1;
}else{
points.set(index, new Coordinate( xAgreed, yAgreed ));
}
if (index == 0) {
//System.out.println("Set point zero to x:" + x);
for (int i = 1; i < points.size(); i++) {
Coordinate c = points.get(i);
points.set(i, c.setX(c.x - x));
}
} else {
points.set(index, new Coordinate(x, y));
// this maps the last index and the next-to-last-index to the same 'testIndex'
int testIndex = Math.min( index, (points.size() - 2));
if( intersects( testIndex)){
// intersection found! log error and abort!
log.error(String.format("ERROR: found an intersection while setting fin point #%d to [%6.4g, %6.4g] <body frame> : ABORTING setPoint(..) !! ", index, xRequest, yRequest));
return;
}
if (index == 0 || index == points.size() - 1) {
this.length = points.get(points.size() - 1).x;
}
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
fireComponentChangeEvent(ComponentChangeEvent.AEROMASS_CHANGE);
}
private boolean intersects(double ax0, double ay0, double ax1, double ay1,
double bx0, double by0, double bx1, double by1) {
double d = ((by1 - by0) * (ax1 - ax0) - (bx1 - bx0) * (ay1 - ay0));
double ua = ((bx1 - bx0) * (ay0 - by0) - (by1 - by0) * (ax0 - bx0)) / d;
double ub = ((ax1 - ax0) * (ay0 - by0) - (ay1 - ay0) * (ax0 - bx0)) / d;
return (ua >= 0) && (ua <= 1) && (ub >= 0) && (ub <= 1);
private void movePoints( final double delta_x){
// skip 0th index -- it's the local origin and is always (0,0)
for( int index=1; index < points.size(); ++index){
final Coordinate oldPoint = this.points.get( index);
final Coordinate newPoint = oldPoint.sub( delta_x, 0.0f, 0.0f);
points.set( index, newPoint);
}
}
@Override
public Coordinate[] getFinPoints() {
return points.toArray(new Coordinate[0]);
@ -297,31 +322,206 @@ public class FreeformFinSet extends FinSet {
return trans.get("FreeformFinSet.FreeformFinSet");
}
@SuppressWarnings("unchecked")
@Override
protected RocketComponent copyWithOriginalID() {
RocketComponent c = super.copyWithOriginalID();
((FreeformFinSet) c).points = this.points.clone();
((FreeformFinSet) c).points = new ArrayList<>(this.points);
return c;
}
private void validate(ArrayList<Coordinate> pts) throws IllegalFinPointException {
final int n = pts.size();
if (pts.get(0).x != 0 || pts.get(0).y != 0 ||
pts.get(n - 1).x < 0 || pts.get(n - 1).y != 0) {
throw new IllegalFinPointException("Start or end point illegal.");
}
for (int i = 0; i < n - 1; i++) {
for (int j = i + 2; j < n - 1; j++) {
if (intersects(pts.get(i).x, pts.get(i).y, pts.get(i + 1).x, pts.get(i + 1).y,
pts.get(j).x, pts.get(j).y, pts.get(j + 1).x, pts.get(j + 1).y)) {
throw new IllegalFinPointException("segments intersect");
}
@Override
public void setAxialOffset( final AxialMethod newAxialMethod, final double newOffsetRequest){
super.setAxialOffset( newAxialMethod, newOffsetRequest);
if( null != parent ) {
// if the new position would cause fin overhang, only allow movement up to the end of the parent component.
// N.B. if you want a fin to overhang, add & adjust interior points.
final double backOverhang = getAxialOffset(AxialMethod.BOTTOM);
if (0 < backOverhang) {
final double newOffset = newOffsetRequest - backOverhang;
super.setAxialOffset(newAxialMethod, newOffset);
}
if (pts.get(i).z != 0) {
throw new IllegalFinPointException("z-coordinate not zero");
final double frontOverhang = getAxialFront();
if (0 > frontOverhang) {
final double newOffset = newOffsetRequest - frontOverhang;
super.setAxialOffset(newAxialMethod, newOffset);
}
}
}
@Override
public void update(){
final int lastPointIndex = this.points.size() - 1;
this.length = points.get(lastPointIndex).x;
this.setAxialOffset( this.axialMethod, this.axialOffset);
clampFirstPoint();
clampInteriorPoints();
clampLastPoint();
validateFinTab();
}
// if we translate the points, correct the first point, because it may be inconsistent
private void clampFirstPoint(){
double xFinStart = getAxialFront(); // x @ fin start, body frame
final double xFinOffset = getAxialOffset();
if( 0 > xFinStart ){
setAxialOffset( xFinOffset - xFinStart);
}
}
private void clampInteriorPoints(){
if( null == this.parent ){
// this is bad, but seems to only occur during unit tests.
return;
}
final SymmetricComponent symmetricParent = (SymmetricComponent)this.getParent();
final Coordinate finFront = getFinFront();
// omit end points index
for( int index=1; index < (points.size()-1); ++index){
final Coordinate oldPoint = this.points.get( index);
final double yBody = symmetricParent.getRadius( oldPoint.x + finFront.x);
final double yFinPoint = finFront.y+ oldPoint.y;
if( yBody > yFinPoint ){
final Coordinate newPoint = oldPoint.setY( yBody - finFront.y );
points.set( index, newPoint);
}
}
}
// if we translate the points, the final point may become inconsistent
private void clampLastPoint(){
if( null == this.parent ){
// this is bad, but seems to only occur during unit tests.
return;
}
final SymmetricComponent body = (SymmetricComponent)getParent();
// clamp the final x coord to the end of the parent body.
final int lastPointIndex = points.size() - 1;
final Coordinate oldPoint = points.get( lastPointIndex);
final double xFinStart_body = getAxialFront(); // x @ fin start, body frame
final double xBodyEnd_fin = body.getLength() - xFinStart_body;
double x_clamped = Math.min( oldPoint.x, xBodyEnd_fin);
double y_clamped = body.getRadius( x_clamped+xFinStart_body) - body.getRadius( xFinStart_body);
points.set( lastPointIndex, new Coordinate( x_clamped, y_clamped, 0));
}
private boolean validate() {
final Coordinate firstPoint = this.points.get(0);
if (firstPoint.x != 0 || firstPoint.y != 0 ){
log.error("Start point illegal -- not located at (0,0): "+firstPoint+ " ("+ getName()+")");
return false;
}
final Coordinate lastPoint = this.points.get( points.size() -1);
if( lastPoint.x < 0){
log.error("End point illegal: end point starts in front of start point: "+lastPoint.x);
return false;
}
// the last point *is* restricted to be on the surface of its owning component:
SymmetricComponent symBody = (SymmetricComponent)this.getParent();
if( null != symBody ){
final double startOffset = this.getAxialFront();
final Coordinate finStart = new Coordinate( startOffset, symBody.getRadius(startOffset) );
// campare x-values
final Coordinate finAtLast = lastPoint.add(finStart);
if( symBody.getLength() < finAtLast.x ){
log.error("End point falls after parent body ends: ["+symBody.getName()+"]. Exception: ", new IllegalFinPointException("Fin ends after its parent body \""+symBody.getName()+"\". Ignoring."));
log.error(String.format(" ..fin position: (x: %12.10f via: %s)", this.axialOffset, this.axialMethod.name()));
log.error(String.format(" ..Body Length: %12.10f finLength: %12.10f", symBody.getLength(), this.getLength()));
log.error(String.format(" ..fin endpoint: (x: %12.10f, y: %12.10f)", finAtLast.x, finAtLast.y));
return false;
}
// compare the y-values
final Coordinate bodyAtLast = finAtLast.setY( symBody.getRadius( finAtLast.x ) );
if( 0.0001 < Math.abs( finAtLast.y - bodyAtLast.y) ){
String numbers = String.format( "finStart=(%6.2g,%6.2g) // fin_end=(%6.2g,%6.2g) // body=(%6.2g,%6.2g)", finStart.x, finStart.y, finAtLast.x, finAtLast.y, bodyAtLast.x, bodyAtLast.y );
log.error("End point does not touch its parent body ["+symBody.getName()+"]. exception: ", new IllegalFinPointException("End point does not touch its parent body! Expected: "+numbers));
log.error(" .."+numbers);
return false;
}
}
if( intersects()){
log.error("found intersection in finset points!");
return false;
}
final int lastIndex = points.size() - 1;
final List<Coordinate> pts = this.points;
for (int i = 0; i < lastIndex; i++) {
if (pts.get(i).z != 0) {
log.error("z-coordinate not zero");
return false;
}
}
return true;
}
/**
* Check if *any* of the fin-point line segments intersects with another.
*
* @return true if an intersection is found
*/
public boolean intersects( ){
for( int index=0; index < (this.points.size()-1); ++index ){
if( intersects( index )){
return true;
}
}
return false;
}
/**
* Check if the line segment from targetIndex to targetIndex+1 intersects with any other part of the fin.
*
*
*
* @return true if an intersection was found
*/
private boolean intersects( final int targetIndex){
if( (points.size()-2) < targetIndex ){
throw new IndexOutOfBoundsException("request validate of non-existent fin edge segment: "+ targetIndex + "/"+points.size());
}
// (pre-check the indices above.)
Point2D.Double p1 = new Point2D.Double( points.get(targetIndex).x, points.get(targetIndex).y);
Point2D.Double p2 = new Point2D.Double( points.get(targetIndex+1).x, points.get(targetIndex+1).y);
Line2D.Double targetLine = new Line2D.Double( p1, p2);
for (int comparisonIndex = 0; comparisonIndex < (points.size()-1); ++comparisonIndex ) {
if( 2 > Math.abs( targetIndex - comparisonIndex) ){
// a line segment will trivially not intersect with itself
// nor can adjacent line segments intersect with each other, because they share a common endpoint.
continue;
}
Line2D.Double comparisonLine = new Line2D.Double( points.get(comparisonIndex).x, points.get(comparisonIndex).y, // p1
points.get(comparisonIndex+1).x, points.get(comparisonIndex+1).y); // p2
if ( targetLine.intersectsLine( comparisonLine ) ) {
return true;
}
}
return false;
}
}

View File

@ -910,8 +910,8 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab
/**
* Get the positioning of the component relative to its parent component.
* This is one of the enums of {@link AxialMethod}. A setter method is not provided,
* but can be provided by a subclass.
*
* @return This will return one of the enums of {@link AxialMethod}
*/
public final AxialMethod getAxialMethod() {
return axialMethod;
@ -952,7 +952,7 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab
public double getAxialOffset(AxialMethod asMethod) {
double parentLength = 0;
if (null != this.parent) {
parentLength = this.parent.length;
parentLength = this.parent.length;
}
if(AxialMethod.ABSOLUTE == asMethod){
@ -966,6 +966,10 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab
return this.axialOffset;
}
public double getAxialFront(){
return this.position.x;
}
public double getRadiusOffset() {
mutex.verify();
return 0;
@ -1017,14 +1021,14 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab
* Set the position value of the component. The exact meaning of the value
* depends on the current relative positioning.
*
* @param newOffset the position value of the component.
*/
public void setAxialOffset(double newOffset) {
* @param newOffset the desired offset of this component, using the components current axial-method
*/
public void setAxialOffset(double newOffset) {
this.setAxialOffset(this.axialMethod, newOffset);
this.fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}
final protected void setAxialOffset( final AxialMethod requestedMethod, final double requestedOffset) {
protected void setAxialOffset( final AxialMethod requestedMethod, final double requestedOffset) {
checkState();
double newX = Double.NaN;
@ -1060,7 +1064,7 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab
this.setAxialOffset(this.axialMethod, this.axialOffset);
}
public final void updateChildren(){
private final void updateChildren(){
this.update();
for( RocketComponent rc : children ){
rc.updateChildren();
@ -1079,7 +1083,7 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab
* <p>
* NOTE: the length of this array returned always equals this.getInstanceCount()
*
* @return an generated (i.e. new) array of instance locations
* @return a generated (i.e. new) array of instance locations
*/
// @Override Me !
public Coordinate[] getInstanceLocations(){
@ -1529,8 +1533,9 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab
public final RocketComponent getRoot() {
checkState();
RocketComponent gp = this;
while (gp.parent != null)
while (gp.parent != null){
gp = gp.parent;
}
return gp;
}
@ -2119,12 +2124,21 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab
// multi-line output
protected StringBuilder toDebugDetail() {
StringBuilder buf = new StringBuilder();
// infer the calling method name
StackTraceElement[] stackTrace = (new Exception()).getStackTrace();
buf.append(" >> Dumping Detailed Information from: " + stackTrace[1].getMethodName() + "\n");
buf.append(" current Component: " + this.getName() + " ofClass: " + this.getClass().getSimpleName() + "\n");
buf.append(" offset: " + this.axialOffset + " via: " + this.axialMethod.name() + " => " + this.getAxialOffset() + "\n");
buf.append(" thisCenterX: " + this.position.x + "\n");
buf.append(" this length: " + this.length + "\n");
String callingMethod = stackTrace[1].getMethodName();
for( StackTraceElement el : stackTrace ){
if( ! "toDebugDetail".equals(el.getMethodName())){
callingMethod = el.getMethodName();
break;
}
}
buf.append(String.format(" >> Dumping Detailed Information from: %s\n", callingMethod));
buf.append(String.format(" At Component: %s, of class: %s \n", this.getName(), this.getClass().getSimpleName()));
buf.append(String.format(" position: %.6f at offset: %.4f via: %s\n", this.position.x, this.axialOffset, this.axialMethod.name()));
buf.append(String.format(" length: %.4f\n", this.length ));
return buf;
}
@ -2152,7 +2166,24 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab
public void toDebugTreeNode(final StringBuilder buffer, final String indent) {
String prefix = String.format("%s%s (x%d)", indent, this.getName(), this.getInstanceCount() );
buffer.append(String.format("%-40s| %6.4f; %24s; %24s; \n", prefix, getLength(), getPosition().toPreciseString(), getComponentLocations()[0].toPreciseString() ));
// 1) instanced vs non-instanced
if( 1 == getInstanceCount() ){
// un-instanced RocketComponents (usual case)
buffer.append(String.format("%-40s| %5.3f; %24s; %24s; ", prefix, this.getLength(), this.axialOffset, this.getComponentLocations()[0]));
buffer.append(String.format("(offset: %4.1f via: %s )\n", this.getAxialOffset(), this.axialMethod.name()));
}else if( this instanceof Instanceable ){
// instanced components -- think motor clusters or booster stage clusters
final String patternName = ((Instanceable)this).getPatternName();
buffer.append(String.format("%-40s (cluster: %s )", prefix, patternName));
buffer.append(String.format("(offset: %4.1f via: %s )\n", this.getAxialOffset(), this.axialMethod.name()));
for (int instanceNumber = 0; instanceNumber < this.getInstanceCount(); instanceNumber++) {
final String instancePrefix = String.format("%s [%2d/%2d]", indent, instanceNumber+1, getInstanceCount());
buffer.append(String.format("%-40s| %5.3f; %24s; %24s;\n", instancePrefix, getLength(), this.axialOffset, getLocations()[0]));
}
}else{
throw new IllegalStateException("This is a developer error! If you implement an instanced class, please subclass the Instanceable interface.");
}
// 2) if this is an ACTING motor mount:
if(( this instanceof MotorMount ) &&( ((MotorMount)this).isMotorMount())){

View File

@ -1,5 +1,11 @@
package net.sf.openrocket.rocketcomponent;
import static java.lang.Math.sin;
import static net.sf.openrocket.util.MathUtil.pow2;
import static net.sf.openrocket.util.MathUtil.pow3;
import java.util.Collection;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.preset.ComponentPreset;
import net.sf.openrocket.preset.ComponentPreset.Type;
@ -7,12 +13,6 @@ import net.sf.openrocket.startup.Application;
import net.sf.openrocket.util.Coordinate;
import net.sf.openrocket.util.MathUtil;
import java.util.Collection;
import static java.lang.Math.sin;
import static net.sf.openrocket.util.MathUtil.pow2;
import static net.sf.openrocket.util.MathUtil.pow3;
public class Transition extends SymmetricComponent {
private static final Translator trans = Application.getTranslator();
@ -526,13 +526,16 @@ public class Transition extends SymmetricComponent {
* Check whether the given type can be added to this component. Transitions allow any
* InternalComponents to be added.
*
* @param ctype The RocketComponent class type to add.
* @param comptype The RocketComponent class type to add.
* @return Whether such a component can be added.
*/
@Override
public boolean isCompatible(Class<? extends RocketComponent> ctype) {
if (InternalComponent.class.isAssignableFrom(ctype))
public boolean isCompatible(Class<? extends RocketComponent> comptype) {
if (InternalComponent.class.isAssignableFrom(comptype)){
return true;
}else if ( FreeformFinSet.class.isAssignableFrom(comptype)){
return true;
}
return false;
}
@ -932,4 +935,5 @@ public class Transition extends SymmetricComponent {
return null;
}
}
}

View File

@ -1,13 +1,13 @@
package net.sf.openrocket.rocketcomponent;
import java.util.ArrayList;
import java.util.List;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.util.Coordinate;
import net.sf.openrocket.util.MathUtil;
import java.util.ArrayList;
import java.util.List;
/**
* A set of trapezoidal fins. The root and tip chords are perpendicular to the rocket
* base line, while the leading and aft edges may be slanted.
@ -65,6 +65,7 @@ public class TrapezoidFinSet extends FinSet {
this.sweep = sweep;
this.height = height;
this.thickness = thickness;
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}
@ -76,6 +77,7 @@ public class TrapezoidFinSet extends FinSet {
if (length == r)
return;
length = Math.max(r, 0);
validateFinTab();
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
}

View File

@ -268,14 +268,9 @@ public class TubeFinSet extends ExternalComponent {
}
// 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;
totalInertia += inertia + MathUtil.pow2( this.axialOffset);
}
return totalInertia;
}

View File

@ -32,8 +32,9 @@ public enum AxialMethod implements DistanceMethod {
@Override
public double getAsOffset(double position, double innerLength, double outerLength){
return position - outerLength;
//return outerLength - position;
}
},
},
// measure from the top of the target component to the top of the subject component
TOP (Application.getTranslator().get("RocketComponent.Position.Method.Axial.TOP")){
@ -61,6 +62,7 @@ public enum AxialMethod implements DistanceMethod {
@Override
public double getAsPosition(double offset, double innerLength, double outerLength){
return offset + (outerLength - innerLength) / 2;
// return (outerLength - innerLength) / 2 - offset;
}
@Override
@ -77,6 +79,7 @@ public enum AxialMethod implements DistanceMethod {
@Override
public double getAsPosition(double offset, double innerLength, double outerLength){
return offset + (outerLength - innerLength);
//return outerLength - innerLength - offset;
}
@Override

View File

@ -54,15 +54,16 @@ public class BasicTumbleStatus extends SimulationStatus {
continue;
}
if (component instanceof FinSet) {
final FinSet finComponent = ((FinSet) component);
final double finArea = finComponent.getPlanformArea();
int finCount = finComponent.getFinCount();
double finComponent = ((FinSet) component).getFinArea();
int finCount = ((FinSet) component).getFinCount();
// check bounds on finCount.
if (finCount >= finEff.length) {
finCount = finEff.length - 1;
}
aFins += finComponent * finEff[finCount];
aFins += finArea * finEff[finCount];
} else if (component instanceof SymmetricComponent) {
aBt += ((SymmetricComponent) component).getComponentPlanformArea();

View File

@ -613,17 +613,14 @@ public class TestRockets {
bodytube = new BodyTube(0.69, 0.033, 0.001);
finset = new FreeformFinSet();
try {
finset.setPoints(new Coordinate[] {
new Coordinate(0, 0),
new Coordinate(0.115, 0.072),
new Coordinate(0.255, 0.072),
new Coordinate(0.255, 0.037),
new Coordinate(0.150, 0)
});
} catch (IllegalFinPointException e) {
e.printStackTrace();
}
final Coordinate[] finPoints = {
new Coordinate(0, 0),
new Coordinate(0.115, 0.072),
new Coordinate(0.255, 0.072),
new Coordinate(0.255, 0.037),
new Coordinate(0.150, 0)};
finset.setPoints(finPoints);
finset.setThickness(0.003);
finset.setFinCount(4);

View File

@ -3,29 +3,77 @@
*/
package net.sf.openrocket.file.rocksim.importt;
import junit.framework.TestCase;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.lang.reflect.Method;
import java.util.HashMap;
import org.junit.BeforeClass;
import org.junit.Test;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Module;
import com.google.inject.util.Modules;
import net.sf.openrocket.ServicesForTesting;
import net.sf.openrocket.aerodynamics.WarningSet;
import net.sf.openrocket.l10n.DebugTranslator;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.plugin.PluginModule;
import net.sf.openrocket.rocketcomponent.BodyTube;
import net.sf.openrocket.rocketcomponent.EllipticalFinSet;
import net.sf.openrocket.rocketcomponent.FinSet;
import net.sf.openrocket.rocketcomponent.TrapezoidFinSet;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.util.Coordinate;
import net.sf.openrocket.util.MathUtil;
import java.lang.reflect.Method;
import java.util.HashMap;
/**
* FinSetHandler Tester.
*
*/
public class FinSetHandlerTest extends TestCase {
public class FinSetHandlerTest {
final static double EPSILON = MathUtil.EPSILON;
@BeforeClass
public static void setup() {
Module applicationModule = new ServicesForTesting();
Module pluginModule = new PluginModule();
Module debugTranslator = new AbstractModule() {
@Override
protected void configure() {
bind(Translator.class).toInstance(new DebugTranslator(null));
}
};
Injector injector = Guice.createInjector(Modules.override(applicationModule).with(debugTranslator), pluginModule);
Application.setInjector(injector);
File tmpDir = new File("./tmp");
if (!tmpDir.exists()) {
boolean success = tmpDir.mkdirs();
assertTrue("Unable to create core/tmp dir needed for tests.", success);
}
}
/**
* Method: asOpenRocket(WarningSet warnings)
*
* @throws Exception thrown if something goes awry
*/
@org.junit.Test
@Test
public void testAsOpenRocket() throws Exception {
FinSetHandler dto = new FinSetHandler(null, new BodyTube());
@ -37,15 +85,15 @@ public class FinSetHandlerTest extends TestCase {
dto.closeElement("ShapeCode", attributes, "0", warnings);
dto.closeElement("Xb", attributes, "2", warnings);
dto.closeElement("FinCount", attributes, "4", warnings);
dto.closeElement("RootChord", attributes, "10", warnings);
dto.closeElement("TipChord", attributes, "11", warnings);
dto.closeElement("RootChord", attributes, "100", warnings);
dto.closeElement("TipChord", attributes, "50", warnings);
dto.closeElement("SemiSpan", attributes, "12", warnings);
dto.closeElement("MidChordLen", attributes, "13", warnings);
dto.closeElement("SweepDistance", attributes, "14", warnings);
dto.closeElement("Thickness", attributes, "200", warnings);
dto.closeElement("TipShapeCode", attributes, "1", warnings);
dto.closeElement("TabLength", attributes, "400", warnings);
dto.closeElement("TabDepth", attributes, "500", warnings);
dto.closeElement("TabLength", attributes, "40", warnings);
dto.closeElement("TabDepth", attributes, "50", warnings);
dto.closeElement("TabOffset", attributes, "30", warnings);
dto.closeElement("RadialAngle", attributes, ".123", warnings);
dto.closeElement("PointList", attributes, "20,0|2,2|0,0", warnings);
@ -56,33 +104,36 @@ public class FinSetHandlerTest extends TestCase {
assertNotNull(fins);
assertEquals(0, set.size());
String debugInfo = fins.toDebugDetail().toString();
assertEquals("The name", fins.getName());
assertTrue(fins instanceof TrapezoidFinSet);
assertEquals(4, fins.getFinCount());
assertEquals("imported fin count does not match.", 4, fins.getFinCount());
assertEquals(0.012d, ((TrapezoidFinSet) fins).getHeight());
assertEquals(0.012d, fins.getSpan());
assertEquals("imported fin height does not match.", 0.012d, ((TrapezoidFinSet) fins).getHeight(), EPSILON);
assertEquals("imported fin span does not match.", 0.012d, fins.getSpan(), EPSILON);
assertEquals(0.2d, fins.getThickness());
assertEquals(0.4d, fins.getTabLength());
assertEquals(0.5d, fins.getTabHeight());
assertEquals(0.03d, fins.getTabShift());
assertEquals(.123d, fins.getBaseRotation());
assertEquals("imported fin thickness does not match.", 0.2d, fins.getThickness(), EPSILON);
assertEquals("imported fin tab length does not match: "+debugInfo, 0.04d, fins.getTabLength(), EPSILON);
assertEquals("imported fin tab height does not match: "+debugInfo, 0.05d, fins.getTabHeight(), EPSILON);
assertEquals("imported fin shift does not match.", 0.03d, fins.getTabOffset(), EPSILON);
assertEquals("imported fin rotation does not match.", .123d, fins.getBaseRotation(), EPSILON);
dto.closeElement("ShapeCode", attributes, "1", warnings);
fins = dto.asOpenRocket(set);
assertNotNull(fins);
assertEquals(0, set.size());
assertEquals("The name", fins.getName());
assertTrue(fins instanceof EllipticalFinSet);
assertEquals(4, fins.getFinCount());
assertEquals("imported fin count does not match.", 4, fins.getFinCount());
assertEquals(0.2d, fins.getThickness());
assertEquals(0.4d, fins.getTabLength());
assertEquals(0.5d, fins.getTabHeight());
assertEquals(0.03d, fins.getTabShift());
assertEquals(.123d, fins.getBaseRotation());
assertEquals("imported fin thickness does not match.", 0.2d, fins.getThickness(), EPSILON);
assertEquals("imported fin tab length does not match.", 0.04d, fins.getTabLength(), EPSILON);
assertEquals("imported fin tab height does not match.", 0.05d, fins.getTabHeight(), EPSILON);
assertEquals("imported fin tab shift does not match.", 0.03d, fins.getTabOffset(), EPSILON);
assertEquals("imported fin rotation does not match.", .123d, fins.getBaseRotation(), EPSILON);
}

View File

@ -2,22 +2,209 @@ package net.sf.openrocket.rocketcomponent;
import static org.junit.Assert.assertEquals;
import net.sf.openrocket.material.Material;
import org.junit.Test;
import net.sf.openrocket.rocketcomponent.position.*;
import net.sf.openrocket.util.Coordinate;
import net.sf.openrocket.util.BaseTestCase.BaseTestCase;
public class FinSetTest extends BaseTestCase {
private static final double EPSILON = 1E-8;
@Test
public void testMultiplicity() {
final TrapezoidFinSet trapFins = new TrapezoidFinSet();
assertEquals(1, trapFins.getFinCount());
final FreeformFinSet fffins = new FreeformFinSet();
assertEquals(1, fffins.getFinCount());
final EllipticalFinSet efins = new EllipticalFinSet();
assertEquals(1, efins.getFinCount());
final EllipticalFinSet fins = new EllipticalFinSet();
assertEquals(1, fins.getFinCount());
}
/**
* sweep= 0.02 | tipChord = 0.02
* | | |
* | +------+ ----------
* | / \
* | / \ height = 0.05
* | / \
* / \
* __________/________________\_____ length == rootChord == 0.06
* | |
* | | tab height = 0.02
* | |
* +--------+ tab length = 0.02
* position = 0.0 via middle
*
* Fin Area = 0.05 * ( (0.2 + 0.06)/2) = 0.0
*/
private static FinSet createSimpleFin() {
TrapezoidFinSet fins = new TrapezoidFinSet(1, 0.06, 0.02, 0.02, 0.05);
fins.setName("test fins");
fins.setAxialOffset(AxialMethod.MIDDLE, 0.0);
fins.setMaterial(Material.newMaterial(Material.Type.BULK, "Fin-Test-Material", 1.0, true));
fins.setThickness(0.005); // == 5 mm
fins.setTabLength(0.02);
fins.setTabOffsetMethod(AxialMethod.TOP);
fins.setTabOffset(0.02);
fins.setFilletRadius(0.0);
return fins;
}
@Test
public void testTabLocation() {
final FinSet fins = FinSetTest.createSimpleFin();
assertEquals("incorrect fin length:", 0.06, fins.getLength(), EPSILON);
assertEquals("incorrect fin tab length:", 0.02, fins.getTabLength(), EPSILON);
final double expFront = 0.02;
final AxialMethod[] methods = AxialMethod.axialOffsetMethods;
final double[] expShift = {0.02, 0.0, -0.02};
for( int caseIndex=0; caseIndex < methods.length; ++caseIndex ){
double actFront = fins.getTabFrontEdge();
assertEquals(" Front edge doesn't match!", expFront, actFront, EPSILON);
// update
fins.setTabOffsetMethod( methods[caseIndex]);
//query
double actShift = fins.getTabOffset();
assertEquals(String.format("Offset doesn't match for: %s \n", methods[caseIndex].name()), expShift[caseIndex], actShift, EPSILON);
}
}
@Test
public void testTabGetAs(){
final FinSet fins = FinSetTest.createSimpleFin();
assertEquals("incorrect fin length:", 0.06, fins.getLength(), EPSILON);
assertEquals("incorrect fin tab length:", 0.02, fins.getTabLength(), EPSILON);
// TOP -> native(TOP)
fins.setTabOffsetMethod(AxialMethod.TOP);
fins.setTabOffset(0.0);
assertEquals("Setting by TOP method failed!", 0.0, fins.getTabFrontEdge(), EPSILON);
assertEquals("Setting by TOP method failed!", 0.0, fins.getTabOffset(), EPSILON);
// MIDDLE -> native
fins.setTabOffsetMethod(AxialMethod.MIDDLE);
fins.setTabOffset(0.0);
assertEquals("Setting by TOP method failed!", 0.02, fins.getTabFrontEdge(), EPSILON);
assertEquals("Setting by TOP method failed!", 0.0, fins.getTabOffset(), EPSILON);
// BOTTOM -> native
fins.setTabOffsetMethod(AxialMethod.BOTTOM);
fins.setTabOffset(0.0);
assertEquals("Setting by TOP method failed!", 0.04, fins.getTabFrontEdge(), EPSILON);
assertEquals("Setting by TOP method failed!", 0.0, fins.getTabOffset(), EPSILON);
}
@Test
public void testTabLocationUpdate() {
final FinSet fins = FinSetTest.createSimpleFin();
assertEquals("incorrect fin length:", 0.06, fins.getLength(), EPSILON);
assertEquals("incorrect fin tab length:", 0.02, fins.getTabLength(), EPSILON);
// TOP -> native(TOP)
fins.setTabOffsetMethod(AxialMethod.MIDDLE);
fins.setTabOffset(0.0);
assertEquals("Setting by TOP method failed!", 0.0, fins.getTabOffset(), EPSILON);
assertEquals("Setting by TOP method failed!", 0.02, fins.getTabFrontEdge(), EPSILON);
((TrapezoidFinSet)fins).setRootChord(0.08);
assertEquals("Front edge doesn't match after adjusting root chord...", 0.03, fins.getTabFrontEdge(), EPSILON);
assertEquals("Offset doesn't match after adjusting root chord....", 0.0, fins.getTabOffset(), EPSILON);
}
@Test
public void testAreaCalculationsSingleIncrement() {
Coordinate[] basicPoints = {
new Coordinate(0.00, 0.0),
new Coordinate(0.06, 0.06),
new Coordinate(0.06, 0.0),
new Coordinate(0.00, 0.0) };
//
// [1] +
// /|
// / |
// [0] +--+ [2]
// [3]
//
final double expArea = 0.06 * 0.06 * 0.5;
final Coordinate actCentroid = FinSet.calculateCurveIntegral(basicPoints);
assertEquals(" basic area doesn't match...", expArea, actCentroid.weight, EPSILON);
assertEquals(" basic centroid x doesn't match: ", 0.04, actCentroid.x, 1e-8);
assertEquals(" basic centroid y doesn't match: ", 0.02, actCentroid.y, 1e-8);
}
@Test
public void testAreaCalculationsDoubleIncrement() {
Coordinate[] basicPoints = {
new Coordinate(0.00, 0.0),
new Coordinate(0.06, 0.06),
new Coordinate(0.12, 0.0),
new Coordinate(0.00, 0.0) };
//
// [1] +
// / \
// / \
// [0] +-----+ [2]
// [3]
//
final double expArea = 0.06 * 0.12 * 0.5;
final Coordinate actCentroid = FinSet.calculateCurveIntegral(basicPoints);
assertEquals(" basic area doesn't match...", expArea, actCentroid.weight, EPSILON);
assertEquals(" basic centroid x doesn't match: ", 0.06, actCentroid.x, 1e-8);
assertEquals(" basic centroid y doesn't match: ", 0.02, actCentroid.y, 1e-8);
}
@Test
public void testAreaCalculations() {
Coordinate[] basicPoints = {
new Coordinate(0.00, 0.0),
new Coordinate(0.02, 0.05),
new Coordinate(0.04, 0.05),
new Coordinate(0.06, 0.0),
new Coordinate(0.00, 0.0) };
/*
* [1] +--+ [2]
* / \
* / \
* [0] +--------+ [3]
* [4]
*/
final double expArea = 0.04 * 0.05;
final Coordinate actCentroid = FinSet.calculateCurveIntegral(basicPoints);
assertEquals(" basic area doesn't match...", expArea, actCentroid.weight, EPSILON);
assertEquals(" basic centroid x doesn't match: ", 0.03000, actCentroid.x, 1e-8);
assertEquals(" basic centroid y doesn't match: ", 0.020833333, actCentroid.y, 1e-8);
}
@Test
public void testFinInstanceAngles() {
FinSet fins = createSimpleFin();
fins.setBaseRotation(Math.PI/6); // == 30d
fins.setInstanceCount(3); // == 120d between each fin
// => [ 30, 150, 270 ]
// => PI*[ 1/6, 5/6, 9/6 ]
// => [ .523, 2.61, 4.71 ]
final double[] instanceAngles = fins.getInstanceAngles();
assertEquals( (1./6.)* Math.PI, fins.getBaseRotation(), EPSILON);
assertEquals( (1./6.)* Math.PI, fins.getAngleOffset(), EPSILON);
assertEquals((1./6.)* Math.PI, instanceAngles[0], EPSILON);
assertEquals((5./6.)* Math.PI, instanceAngles[1], EPSILON);
assertEquals((9./6.)* Math.PI, instanceAngles[2], EPSILON);
}
}

View File

@ -4,11 +4,189 @@ import static org.junit.Assert.assertEquals;
import org.junit.Test;
import net.sf.openrocket.material.Material;
import net.sf.openrocket.rocketcomponent.position.*;
import net.sf.openrocket.util.Coordinate;
import net.sf.openrocket.util.BaseTestCase.BaseTestCase;
public class TrapezoidFinSetTest extends BaseTestCase {
private static final double EPSILON = 1E-8;
private Rocket createSimpleTrapezoidalFin() {
final Rocket rkt = new Rocket();
final AxialStage stg = new AxialStage();
rkt.addChild(stg);
BodyTube body = new BodyTube(0.2, 0.1);
stg.addChild(body);
TrapezoidFinSet fins = new TrapezoidFinSet(1, 0.06, 0.02, 0.02, 0.05);
//
// sweep= 0.02 | tipChord = 0.02
// | | |
// | +------+ ----------
// | / \
// | / \ height = 0.05
// | / \
// / \
// __________/________________\_____ length == rootChord == 0.06
// | |
// | | tab height = 0.02
// | |
// +--------+ tab length = 0.02
// position = 0.0 via middle
//
// Fin Area = 0.05 * ( (0.2 + 0.06)/2) = 0.0
//
fins.setAxialOffset(AxialMethod.MIDDLE, 0.0);
fins.setMaterial(Material.newMaterial(Material.Type.BULK, "Fin-Test-Material", 1.0, true));
fins.setThickness(0.005); // == 5 mm
body.addChild(fins);
fins.setTabLength(0.00);
fins.setFilletRadius(0.0);
rkt.enableEvents();
return rkt;
}
@Test
public void testMultiplicity() {
final TrapezoidFinSet trapFins = new TrapezoidFinSet();
assertEquals(1, trapFins.getFinCount());
}
@Test
public void testGenerateTrapezoidalPoints() {
final Rocket rkt = createSimpleTrapezoidalFin();
FinSet fins = (FinSet) rkt.getChild(0).getChild(0).getChild(0);
// Fin length = 0.05
// Tab Length = 0.01
// +--+
// / \
// / \
// +---+--------+---+
//
Coordinate[] actPoints = fins.getFinPoints();
Coordinate[] expPoints = { new Coordinate(0.00, 0.0),
new Coordinate(0.02, 0.05),
new Coordinate(0.04, 0.05),
new Coordinate(0.06, 0.0),
new Coordinate(0.00, 0.0) };
for (int index = 0; index < actPoints.length; ++index) {
assertEquals(" generated fin point [" + index + "] doesn't match! ", expPoints[index].x, actPoints[index].x, EPSILON);
assertEquals(" generated fin point [" + index + "] doesn't match!", expPoints[index].x, actPoints[index].x, EPSILON);
assertEquals(" generated fin point [" + index + "] doesn't match!", expPoints[index].x, actPoints[index].x, EPSILON);
}
}
@Test
public void testCGCalculation_simpleSquareFin() {
final Rocket rkt = createSimpleTrapezoidalFin();
final TrapezoidFinSet fins = (TrapezoidFinSet)rkt.getChild(0).getChild(0).getChild(0);
// This is a simple square fin with sides of 1.0.
fins.setFinShape(0.1, 0.1, 0.0, 0.1, .005);
// should return a single-fin-planform area
assertEquals("area calculation doesn't match: ", 0.01, fins.getPlanformArea(), 0.00001);
final double expSingleMass = 0.00005;
final Coordinate singleCG = fins.getComponentCG();
assertEquals("Fin mass is wrong! ", expSingleMass, singleCG.weight, EPSILON);
assertEquals("Centroid x coordinate is wrong! ", 0.05, singleCG.x, EPSILON);
assertEquals("Centroid y coordinate is wrong! ", 0.15, singleCG.y, EPSILON);
// should still return a single-fin-wetted area
assertEquals(0.00005, fins.getComponentVolume(), 0.0000001);
{ // test instancing code
// this should also trigger a recalculation
fins.setFinCount(2);
// should still return a single-fin-planform area
assertEquals(0.01, fins.getPlanformArea(), 0.00001);
Coordinate doubleCG = fins.getComponentCG();
final double expDoubleMass = expSingleMass*2;
assertEquals("Fin x2 mass does not change from single fin instance! ", expDoubleMass, doubleCG.weight, EPSILON);
assertEquals(0.05, doubleCG.x, EPSILON);
assertEquals(0.0, doubleCG.y, EPSILON);
}
}
@Test
public void testCGCalculations_finWithTab() throws IllegalFinPointException {
final Rocket rkt = createSimpleTrapezoidalFin();
FinSet fins = (FinSet) rkt.getChild(0).getChild(0).getChild(0);
fins.setTabLength(0.02);
fins.setTabHeight(0.02);
fins.setTabOffsetMethod(AxialMethod.MIDDLE);
fins.setTabOffset(0.0);
assertEquals("Wetted Area does not match!", 0.0020, fins.getPlanformArea(), EPSILON);
final double expVol1 = 0.00001200;
final double actVol1 = fins.getComponentVolume();
assertEquals(" fin volume is incorrect", expVol1, actVol1, EPSILON);
Coordinate actCentroid1 = fins.getCG();
assertEquals(" basic centroid x doesn't match: ", 0.03000, actCentroid1.x, EPSILON);
assertEquals(" basic centroid y doesn't match: ", 0.11569444, actCentroid1.y, EPSILON);
{
fins.setFinCount(2);
final double expVol2 = expVol1 * 2;
final double actVol2 = fins.getComponentVolume();
assertEquals(" fin volume is incorrect", expVol2, actVol2, EPSILON);
Coordinate actCentroid2 = fins.getCG();
// x coordinate will be the same....
assertEquals(" basic centroid y doesn't match: ", 0.0, actCentroid2.y, EPSILON);
}
}
@Test
public void testFilletCalculations() {
final Rocket rkt = createSimpleTrapezoidalFin();
BodyTube body = (BodyTube) rkt.getChild(0).getChild(0);
FinSet fins = (FinSet) rkt.getChild(0).getChild(0).getChild(0);
fins.setFilletRadius(0.005);
fins.setFilletMaterial(Material.newMaterial(Material.Type.BULK, "Fillet-Test-Material", 1.0, true));
// used for fillet and edge calculations:
//
// [1] +--+ [2]
// / \
// / \
// [0] +--------+ [3]
//
assertEquals("Body radius doesn't match: ", 0.1, body.getOuterRadius(), EPSILON);
final Coordinate actVolume = fins.calculateFilletVolumeCentroid();
assertEquals("Line volume doesn't match: ", 5.973e-07, actVolume.weight, EPSILON);
assertEquals("Line mass center.x doesn't match: ", 0.03, actVolume.x, EPSILON);
assertEquals("Line mass center.y doesn't match: ", 0.101, actVolume.y, EPSILON);
{ // and then, check that the fillet volume feeds into a correct overall CG:
Coordinate actCentroid = fins.getCG();
assertEquals("Complete centroid x doesn't match: ", 0.03000, actCentroid.x, EPSILON);
assertEquals("Complete centroid y doesn't match: ", 0.11971548, actCentroid.y, EPSILON);
}
}
@Test
public void testTrapezoidCGComputation() {
@ -19,7 +197,7 @@ public class TrapezoidFinSetTest extends BaseTestCase {
fins.setFinShape(1.0, 1.0, 0.0, 1.0, .005);
Coordinate coords = fins.getCG();
assertEquals(1.0, fins.getFinArea(), 0.001);
assertEquals(1.0, fins.getPlanformArea(), 0.001);
assertEquals(0.5, coords.x, 0.001);
assertEquals(0.5, coords.y, 0.001);
}
@ -36,7 +214,7 @@ public class TrapezoidFinSetTest extends BaseTestCase {
fins.setFinShape(1.0, 0.5, 0.0, 1.0, .005);
Coordinate coords = fins.getCG();
assertEquals(0.75, fins.getFinArea(), 0.001);
assertEquals(0.75, fins.getPlanformArea(), 0.001);
assertEquals(0.3889, coords.x, 0.001);
assertEquals(0.4444, coords.y, 0.001);
}

View File

@ -201,7 +201,7 @@ public abstract class FinSetConfig extends RocketComponentConfig {
label.setToolTipText(trans.get("FinSetConfig.ttip.Tabposition"));
panel.add(label, "gapleft para");
final DoubleModel mts = new DoubleModel(component, "TabShift", UnitGroup.UNITS_LENGTH);
final DoubleModel mts = new DoubleModel(component, "TabOffset", UnitGroup.UNITS_LENGTH);
component.addChangeListener( mts);
spin = new JSpinner(mts.getSpinnerModel());
spin.setEditor(new SpinnerEditor(spin));
@ -215,7 +215,7 @@ public abstract class FinSetConfig extends RocketComponentConfig {
panel.add(label, "right, gapright unrel");
final EnumModel<AxialMethod> em = new EnumModel<>(component, "TabRelativePosition");
final EnumModel<AxialMethod> em = new EnumModel<>(component, "TabOffsetMethod");
JComboBox<AxialMethod> enumCombo = new JComboBox<>(em);

View File

@ -340,10 +340,6 @@ public class FreeformFinSetConfig extends FinSetConfig {
List<Coordinate> points = importer.getPoints(chooser.getSelectedFile());
document.startUndo(trans.get("CustomFinImport.undo"));
finset.setPoints( points);
} catch (IllegalFinPointException e) {
log.warn("Error storing fin points", e);
JOptionPane.showMessageDialog(this, trans.get("CustomFinImport.error.badimage"),
trans.get("CustomFinImport.error.title"), JOptionPane.ERROR_MESSAGE);
} catch (IOException e) {
log.warn("Error loading file", e);
JOptionPane.showMessageDialog(this, e.getLocalizedMessage(),
@ -426,11 +422,7 @@ public class FreeformFinSetConfig extends FinSetConfig {
}
Point2D.Double point = getCoordinates(event);
try {
finset.setPoint(dragIndex, point.x, point.y);
} catch (IllegalFinPointException ignore) {
log.debug("Ignoring IllegalFinPointException while dragging, dragIndex=" + dragIndex + " x=" + point.x + " y=" + point.y);
}
finset.setPoint(dragIndex, point.x, point.y);
updateFields();
}
@ -581,8 +573,6 @@ public class FreeformFinSetConfig extends FinSetConfig {
updateFields();
} catch (NumberFormatException ignore) {
log.warn("ignoring NumberFormatException while editing a Freeform Fin");
} catch (IllegalFinPointException ignore) {
log.warn("ignoring IllegalFinPointException while editing a Freeform Fin");
}
}
}

View File

@ -115,7 +115,7 @@ public class ScaleDialog extends JDialog {
addScaler(FinSet.class, "Thickness");
addScaler(FinSet.class, "TabHeight");
addScaler(FinSet.class, "TabLength");
addScaler(FinSet.class, "TabShift");
addScaler(FinSet.class, "TabOffset");
// TrapezoidFinSet
addScaler(TrapezoidFinSet.class, "Sweep");
@ -623,11 +623,9 @@ public class ScaleDialog extends JDialog {
for (int i = 0; i < points.length; i++) {
points[i] = points[i].multiply(multiplier);
}
try {
finset.setPoints(points);
} catch (IllegalFinPointException e) {
throw new BugException("Failed to set points after scaling, original=" + Arrays.toString(finset.getFinPoints()) + " scaled=" + Arrays.toString(points), e);
}
finset.setPoints(points);
}
}

View File

@ -128,17 +128,17 @@ public class ComponentAddButtons extends JPanel implements Scrollable {
//// Transition
new BodyComponentButton(Transition.class, trans.get("compaddbuttons.Transition")),
//// Trapezoidal
new FinButton(TrapezoidFinSet.class, trans.get("compaddbuttons.Trapezoidal")), // TODO: MEDIUM: freer fin placing
new ComponentButton(TrapezoidFinSet.class, trans.get("compaddbuttons.Trapezoidal")), // TODO: MEDIUM: freer fin placing
//// Elliptical
new FinButton(EllipticalFinSet.class, trans.get("compaddbuttons.Elliptical")),
new ComponentButton(EllipticalFinSet.class, trans.get("compaddbuttons.Elliptical")),
//// Freeform
new FinButton(FreeformFinSet.class, trans.get("compaddbuttons.Freeform")),
new ComponentButton(FreeformFinSet.class, trans.get("compaddbuttons.Freeform")),
//// Freeform
new FinButton(TubeFinSet.class, trans.get("compaddbuttons.Tubefin")),
//// Rail Button // TODO: implement drawing graphics for the component
new FinButton( RailButton.class, trans.get("compaddbuttons.RailButton")),
new ComponentButton(TubeFinSet.class, trans.get("compaddbuttons.Tubefin")),
//// Rail Button
new ComponentButton( RailButton.class, trans.get("compaddbuttons.RailButton")),
//// Launch lug
new FinButton(LaunchLug.class, trans.get("compaddbuttons.Launchlug")));
new ComponentButton(LaunchLug.class, trans.get("compaddbuttons.Launchlug")));
row++;
/////////////////////////////////////////////
@ -630,37 +630,6 @@ public class ComponentAddButtons extends JPanel implements Scrollable {
/**
* Class for fin sets, that attach only to BodyTubes.
*/
private class FinButton extends ComponentButton {
/**
*
*/
private static final long serialVersionUID = -219204844803871258L;
public FinButton(Class<? extends RocketComponent> c, String text) {
super(c, text);
}
public FinButton(String text, Icon enabled, Icon disabled) {
super(text, enabled, disabled);
}
public FinButton(String text) {
super(text);
}
@Override
public boolean isAddable(RocketComponent c) {
if (c == null)
return false;
return (c.getClass().equals(BodyTube.class));
}
}
///////// Scrolling functionality
@Override

View File

@ -2,55 +2,124 @@ package net.sf.openrocket.gui.rocketfigure;
import java.awt.Shape;
import java.awt.geom.Path2D;
import java.util.ArrayList;
import net.sf.openrocket.rocketcomponent.FinSet;
import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.util.Coordinate;
import net.sf.openrocket.util.MathUtil;
import net.sf.openrocket.util.Transformation;
public class FinSetShapes extends RocketComponentShape {
// TODO: LOW: Clustering is ignored (FinSet cannot currently be clustered)
public static RocketComponentShape[] getShapesSide(
net.sf.openrocket.rocketcomponent.RocketComponent component,
RocketComponent component,
Transformation transformation,
Coordinate instanceAbsoluteLocation) {
net.sf.openrocket.rocketcomponent.FinSet finset = (net.sf.openrocket.rocketcomponent.FinSet)component;
Coordinate finSetFront = instanceAbsoluteLocation;
Coordinate finPoints[] = finset.getFinPointsWithTab();
Coordinate componentAbsoluteLocation) {
FinSet finset = (FinSet)component;
Transformation cantRotation = finset.getCantRotation();
finPoints = cantRotation.transform(finPoints);
finPoints = transformation.transform(finPoints);
int finCount = finset.getFinCount();
// TODO: MEDIUM: sloping radius
final double rFront = finset.getFinFront().y;
Transformation cantRotation = finset.getCantRotation();
Transformation baseRotation = new Transformation(finset.getBaseRotation(), 0, 0); // rotation about x-axis
Transformation radialTranslation = new Transformation( 0, rFront, 0);
Transformation finRotation = finset.getFinRotationTransformation();
Transformation compositeTransform = baseRotation
.applyTransformation( radialTranslation)
.applyTransformation( cantRotation)
.applyTransformation( transformation);
Coordinate finSetFront = componentAbsoluteLocation;
Coordinate finPoints[] = finset.getFinPoints();
Coordinate tabPoints[] = finset.getTabPoints();
Coordinate basePoints[] = finset.getRootPoints();
// Translate & rotate points into place
finPoints = compositeTransform.transform( finPoints );
tabPoints = compositeTransform.transform( tabPoints);
basePoints = compositeTransform.transform( basePoints );
// Generate shapes
Path2D.Float p;
{
// Make polygon
p = new Path2D.Float();
for (int i=0; i<finPoints.length; i++) {
Coordinate c = finSetFront.add(finPoints[i]);
ArrayList<RocketComponentShape> shapeList = new ArrayList<>();
for (int finNum=0; finNum<finCount; finNum++) {
Coordinate curPoint;
if (i==0)
p.moveTo(c.x, c.y);
else
p.lineTo(c.x, c.y);
}
// Make fin polygon
Path2D.Float finShape = new Path2D.Float();
for (int i=0; i<finPoints.length; i++) {
curPoint = finSetFront.add(finPoints[i]);
p.closePath();
}
if (i==0)
finShape.moveTo(curPoint.x, curPoint.y);
else
finShape.lineTo(curPoint.x, curPoint.y);
}
shapeList.add( new RocketComponentShape( finShape, finset));
return new RocketComponentShape[] {new RocketComponentShape(p, finset)};
// draw fin-body intersection line
double angle_rad = finset.getBaseRotation() + ((double)finNum) / ((double)finCount) *2*Math.PI;
// only draw body-root intersection line if it's not hidden-- i.e. is not at {0,PI/2,PI,3/2*PI} angles
final boolean drawRoot= (0.05 < Math.abs( angle_rad % (Math.PI/2.0)));
boolean simpleRoot = finset.isRootStraight( );
if( drawRoot){
if( simpleRoot){
// draws a straight-line connection from the end back to the start
finShape.closePath();
}else{
// this implies a curved fin-body intersection
// ... which is more complicated.
Path2D.Float rootShape = new Path2D.Float();
for (int i=0; i< basePoints.length; i++) {
curPoint = finSetFront.add( basePoints[i]);
if (i==0)
rootShape.moveTo(curPoint.x, curPoint.y);
else
rootShape.lineTo(curPoint.x, curPoint.y);
}
shapeList.add( new RocketComponentShape( rootShape, finset));
}
}
// Make tab polygon
Path2D.Float tabShape = new Path2D.Float();
if( 0 < tabPoints.length ){
for (int i=0; i<tabPoints.length; i++) {
curPoint = finSetFront.add(tabPoints[i]);
if (i==0)
tabShape.moveTo(curPoint.x, curPoint.y);
else
tabShape.lineTo(curPoint.x, curPoint.y);
}
// the fin tab / body surface line should lay on the fin-root line above
shapeList.add( new RocketComponentShape( tabShape, finset));
}
// Rotate fin, tab coordinates
finPoints = finRotation.transform(finPoints);
tabPoints = finRotation.transform(tabPoints);
basePoints = finRotation.transform( basePoints);
}
return shapeList.toArray(new RocketComponentShape[0]);
}
public static RocketComponentShape[] getShapesBack(
net.sf.openrocket.rocketcomponent.RocketComponent component,
RocketComponent component,
Transformation transformation,
Coordinate location) {
net.sf.openrocket.rocketcomponent.FinSet finset = (net.sf.openrocket.rocketcomponent.FinSet)component;
FinSet finset = (FinSet)component;
Shape[] toReturn;
@ -65,7 +134,7 @@ public class FinSetShapes extends RocketComponentShape {
}
private static Shape[] uncantedShapesBack(net.sf.openrocket.rocketcomponent.FinSet finset,
private static Shape[] uncantedShapesBack(FinSet finset,
Transformation transformation,
Coordinate finFront) {
@ -101,12 +170,11 @@ public class FinSetShapes extends RocketComponentShape {
// TODO: LOW: Jagged shapes from back draw incorrectly.
private static Shape[] cantedShapesBack(net.sf.openrocket.rocketcomponent.FinSet finset,
private static Shape[] cantedShapesBack(FinSet finset,
Transformation transformation,
Coordinate location) {
int i;
int fins = finset.getFinCount();
// double radius = finset.getBodyRadius();
double thickness = finset.getThickness();
Transformation cantRotation = finset.getCantRotation();
@ -172,15 +240,7 @@ public class FinSetShapes extends RocketComponentShape {
return s;
}
private static void transformPoints(Coordinate[] array, Transformation t) {
for (int i=0; i < array.length; i++) {
array[i] = t.transform(array[i]);
}
}
private static Shape makePolygonBack(Coordinate[] array, net.sf.openrocket.rocketcomponent.FinSet finset,
private static Shape makePolygonBack(Coordinate[] array, FinSet finset,
Transformation t, Coordinate location) {
Path2D.Float p;