Also add fin tabs to SVG export
This commit is contained in:
parent
4cf77bcdc4
commit
9ecabd585f
@ -104,6 +104,10 @@ public class SVGBuilder {
|
||||
svgRoot.appendChild(path);
|
||||
}
|
||||
|
||||
public void addPath(Coordinate[] coordinates, double xPos, double yPos, Color fill, Color stroke, double strokeWidth) {
|
||||
addPath(coordinates, xPos, yPos, fill, stroke, strokeWidth, LineCap.SQUARE);
|
||||
}
|
||||
|
||||
public void addPath(Coordinate[] coordinates, Color fill, Color stroke, double strokeWidth, LineCap lineCap) {
|
||||
addPath(coordinates, 0, 0, fill, stroke, strokeWidth, lineCap);
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ public class FinSetExporter extends RocketComponentExporter<FinSet> {
|
||||
obj.setActiveGroupNames(groupName);
|
||||
|
||||
final Coordinate[] points = component.getFinPointsWithRoot();
|
||||
final Coordinate[] tabPoints = component.getTabPoints();
|
||||
final Coordinate[] tabPoints = component.getTabPointsWithRoot();
|
||||
final Coordinate[] tabPointsReversed = new Coordinate[tabPoints.length]; // We need clockwise points for the PolygonExporter
|
||||
for (int i = 0; i < tabPoints.length; i++) {
|
||||
tabPointsReversed[i] = tabPoints[tabPoints.length - i - 1];
|
||||
|
@ -701,7 +701,7 @@ public abstract class FinSet extends ExternalComponent implements AxialPositiona
|
||||
|
||||
// get body points, relTo fin front / centerline);
|
||||
final Coordinate[] upperCurve = getMountPoints( xTabFront_body, xTabTrail_body, -xFinFront_body, 0);
|
||||
final Coordinate[] lowerCurve = translateToCenterline( getTabPoints());
|
||||
final Coordinate[] lowerCurve = translateToCenterline( getTabPointsWithRoot());
|
||||
final Coordinate[] tabPoints = combineCurves( upperCurve, lowerCurve);
|
||||
|
||||
return calculateCurveIntegral( tabPoints );
|
||||
@ -1083,7 +1083,7 @@ public abstract class FinSet extends ExternalComponent implements AxialPositiona
|
||||
final double intervalLength = xEnd - xStart;
|
||||
|
||||
// for anything more complicated, increase the count:
|
||||
if ((!MathUtil.equals(getCantAngle(), 0)) || (parent instanceof Transition) && (((Transition)parent).getShapeType() != Shape.CONICAL)) {
|
||||
if ((!MathUtil.equals(getCantAngle(), 0)) || (parent instanceof Transition && ((Transition)parent).getShapeType() != Shape.CONICAL)) {
|
||||
// the maximum precision to enforce when calculating the areas of fins (especially on curved parent bodies)
|
||||
final double xWidth = 0.0025; // width (in meters) of each individual iteration
|
||||
divisionCount = (int) Math.ceil(intervalLength / xWidth);
|
||||
@ -1209,6 +1209,33 @@ public abstract class FinSet extends ExternalComponent implements AxialPositiona
|
||||
return combineCurves(getFinPoints(), getRootPoints(MAX_ROOT_DIVISIONS_LOW_RES));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this fin set has a tab.
|
||||
*
|
||||
* @return true if the tab dimensions are greater than 0, otherwise false.
|
||||
*/
|
||||
public boolean hasTab() {
|
||||
return (getTabHeight() > 0) && (getTabLength() > 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the tab is fully beyond the fin.
|
||||
*
|
||||
* @return true if the tab is beyond the fin, otherwise false.
|
||||
*/
|
||||
public boolean isTabBeyondFin() {
|
||||
if (!hasTab()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final double xTabFront = getTabFrontEdge();
|
||||
final double xTabTrail = getTabTrailingEdge();
|
||||
|
||||
final double xFinEnd = getLength(); // Fin tab is referenced to the fin front, so the fin end is the fin front (0) + length
|
||||
|
||||
return (xTabFront > xFinEnd && xTabTrail > xFinEnd) || (xTabTrail < 0 && xTabFront < 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of X,Y coordinates defining the geometry of a single fin tab.
|
||||
* The origin is the leading root edge, and the tab height (or 'depth') is
|
||||
@ -1221,9 +1248,8 @@ public abstract class FinSet extends ExternalComponent implements AxialPositiona
|
||||
*
|
||||
* @return List of XY-coordinates.
|
||||
*/
|
||||
public Coordinate[] getTabPoints() {
|
||||
if (MathUtil.equals(getTabHeight(), 0) ||
|
||||
MathUtil.equals(getTabLength(), 0)){
|
||||
public Coordinate[] getTabPointsWithRoot() {
|
||||
if (!hasTab()) {
|
||||
return new Coordinate[]{};
|
||||
}
|
||||
|
||||
@ -1240,6 +1266,62 @@ public abstract class FinSet extends ExternalComponent implements AxialPositiona
|
||||
return generateTabPointsWithRoot(rootPoints);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a combined shape of the fin and tab that is uniform and uninterrupted, creating a continuous contour
|
||||
* around the entire structure.
|
||||
*
|
||||
* The function operates under the following conditions:
|
||||
* - If the tab is present and does not extend beyond the fin, it combines the fin, root,
|
||||
* and tab points in a way that maintains a continuous shape.
|
||||
* - If the tab extends beyond the fin or if the tab is not present, only the fin points,
|
||||
* including the root, are returned, maintaining the fin's original shape.
|
||||
*
|
||||
* @return Array of Coordinates representing the continuous shape combining fin and tab points.
|
||||
* If the tab is not present or extends beyond the fin, returns only the fin points.
|
||||
*/
|
||||
public Coordinate[] generateContinuousFinAndTabShape() {
|
||||
if (!hasTab() || isTabBeyondFin()) {
|
||||
return getFinPointsWithRoot();
|
||||
}
|
||||
|
||||
final Coordinate[] finPoints = getFinPoints();
|
||||
final Coordinate[] rootPoints = getRootPoints();
|
||||
final Coordinate[] tabPoints = getTabPoints();
|
||||
|
||||
final double finStart = finPoints[0].x;
|
||||
|
||||
final List<Coordinate> uniformPoints = new LinkedList<>(Arrays.asList(finPoints));
|
||||
|
||||
boolean tabAdded = false;
|
||||
for (Coordinate rootPoint : rootPoints) {
|
||||
// If the tab is not yet added, we need to check whether we need to include root tabs before the tab.
|
||||
if (!tabAdded) {
|
||||
// Check if the root point is beyond the tab. If so, add it to the list.
|
||||
if (rootPoint.x > tabPoints[tabPoints.length - 1].x) {
|
||||
uniformPoints.add(rootPoint);
|
||||
}
|
||||
// If the root point is before the tab, we need to first add the tab points.
|
||||
else {
|
||||
for (int j = tabPoints.length - 1; j >= 0; j--) {
|
||||
uniformPoints.add(tabPoints[j]);
|
||||
}
|
||||
tabAdded = true;
|
||||
}
|
||||
}
|
||||
// Once the tab is added, we need to add the remaining root points that lie before the tab.
|
||||
if (tabAdded && rootPoint.x < tabPoints[0].x) {
|
||||
uniformPoints.add(rootPoint);
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure we close the shape in case the tab is before the fin.
|
||||
if (tabPoints[0].x < finStart) {
|
||||
uniformPoints.add(finPoints[0]);
|
||||
}
|
||||
|
||||
return uniformPoints.toArray(new Coordinate[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of X,Y coordinates defining the geometry of a single fin tab.
|
||||
* The origin is the leading root edge, and the tab height (or 'depth') is
|
||||
@ -1255,7 +1337,7 @@ public abstract class FinSet extends ExternalComponent implements AxialPositiona
|
||||
*
|
||||
* @return List of XY-coordinates.
|
||||
*/
|
||||
public Coordinate[] getTabPointsLowRes() {
|
||||
public Coordinate[] getTabPointsWithRootLowRes() {
|
||||
if (MathUtil.equals(getTabHeight(), 0) ||
|
||||
MathUtil.equals(getTabLength(), 0)){
|
||||
return new Coordinate[]{};
|
||||
@ -1274,7 +1356,26 @@ public abstract class FinSet extends ExternalComponent implements AxialPositiona
|
||||
return generateTabPointsWithRoot(rootPoints);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an array of tab points with the root side.
|
||||
*
|
||||
* @param rootPoints A list of root points
|
||||
* @return An array of tab points with the root (relative to the fin front)
|
||||
*/
|
||||
private Coordinate[] generateTabPointsWithRoot(List<Coordinate> rootPoints) {
|
||||
Coordinate[] tabPoints = getTabPoints();
|
||||
|
||||
rootPoints.add(0, new Coordinate(tabPoints[0].x, tabPoints[0].y));
|
||||
|
||||
return combineCurves(tabPoints, rootPoints.toArray(new Coordinate[0]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an array of coordinates representing the points of a tab (without the root of the tab).
|
||||
*
|
||||
* @return an array of Coordinate objects representing the points of a tab (relative to the fin front)
|
||||
*/
|
||||
public Coordinate[] getTabPoints() {
|
||||
final double xTabFront = getTabFrontEdge();
|
||||
final double xTabTrail = getTabTrailingEdge();
|
||||
|
||||
@ -1283,13 +1384,12 @@ public abstract class FinSet extends ExternalComponent implements AxialPositiona
|
||||
|
||||
final SymmetricComponent body = (SymmetricComponent)this.getParent();
|
||||
|
||||
// // limit the new heights to be no greater than the current body radius.
|
||||
double yTabFront = Double.NaN;
|
||||
double yTabTrail = Double.NaN;
|
||||
double yTabBottom = Double.NaN;
|
||||
if( null != body ){
|
||||
yTabFront = body.getRadius( finFront.x + xTabFront) - finFront.y;
|
||||
yTabTrail = body.getRadius( finFront.x + xTabTrail) - finFront.y;
|
||||
if (body != null) {
|
||||
yTabFront = body.getRadius(finFront.x + xTabFront) - finFront.y;
|
||||
yTabTrail = body.getRadius(finFront.x + xTabTrail) - finFront.y;
|
||||
yTabBottom = MathUtil.min(yTabFront, yTabTrail) - tabHeight;
|
||||
}
|
||||
|
||||
@ -1297,9 +1397,8 @@ public abstract class FinSet extends ExternalComponent implements AxialPositiona
|
||||
tabPoints[1] = new Coordinate(xTabFront, yTabBottom );
|
||||
tabPoints[2] = new Coordinate(xTabTrail, yTabBottom );
|
||||
tabPoints[3] = new Coordinate(xTabTrail, yTabTrail);
|
||||
rootPoints.add(0, new Coordinate(xTabFront, yTabFront));
|
||||
|
||||
return combineCurves(tabPoints, rootPoints.toArray(new Coordinate[0]));
|
||||
return tabPoints;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -1568,7 +1667,7 @@ public abstract class FinSet extends ExternalComponent implements AxialPositiona
|
||||
|
||||
if( ! this.isTabTrivial() ) {
|
||||
buf.append(String.format(" TabLength: %6.4f TabHeight: %6.4f @ %6.4f via: %s\n", tabLength, tabHeight, tabPosition, this.tabOffsetMethod));
|
||||
buf.append(getPointDescr(this.getTabPoints(), "Tab Points", ""));
|
||||
buf.append(getPointDescr(this.getTabPointsWithRoot(), "Tab Points", ""));
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package net.sf.openrocket.rocketcomponent;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
|
||||
import net.sf.openrocket.material.Material;
|
||||
import net.sf.openrocket.util.TestRockets;
|
||||
@ -266,4 +267,42 @@ public class FinSetTest extends BaseTestCase {
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testGenerateContinuousFinAndTabShape() {
|
||||
BodyTube parent = new BodyTube();
|
||||
final FinSet fins = FinSetTest.createSimpleFin();
|
||||
fins.setCantAngle(0);
|
||||
parent.addChild(fins);
|
||||
Coordinate[] finShapeContinuous = fins.generateContinuousFinAndTabShape();
|
||||
final Coordinate[] finShape = fins.getFinPointsWithRoot();
|
||||
|
||||
assertEquals("incorrect fin shape length", finShape.length, finShapeContinuous.length);
|
||||
|
||||
assertArrayEquals("incorrect fin shape", finShape, finShapeContinuous);
|
||||
|
||||
// Set the tab
|
||||
fins.setTabHeight(0.02);
|
||||
|
||||
finShapeContinuous = fins.generateContinuousFinAndTabShape();
|
||||
|
||||
assertEquals("incorrect fin shape length", finShape.length + 3, finShapeContinuous.length);
|
||||
|
||||
for (int i = 0; i < finShape.length-2; i++) {
|
||||
assertEquals("incorrect fin shape point " + i, finShape[i], finShapeContinuous[i]);
|
||||
}
|
||||
|
||||
int idx = finShape.length-2;
|
||||
assertEquals("incorrect fin shape point " + idx, new Coordinate(0.04, 0.0), finShapeContinuous[idx]);
|
||||
idx++;
|
||||
assertEquals("incorrect fin shape point " + idx, new Coordinate(0.04, -0.02), finShapeContinuous[idx]);
|
||||
idx++;
|
||||
assertEquals("incorrect fin shape point " + idx, new Coordinate(0.02, -0.02), finShapeContinuous[idx]);
|
||||
idx++;
|
||||
assertEquals("incorrect fin shape point " + idx, new Coordinate(0.02, 0.0), finShapeContinuous[idx]);
|
||||
idx++;
|
||||
assertEquals("incorrect fin shape point " + idx, new Coordinate(0.0, 0.0), finShapeContinuous[idx]);
|
||||
|
||||
// TODO: test on transition parent...
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -48,6 +48,10 @@ public class SVGOptionPanel extends JPanel {
|
||||
return colorChooser.getSelectedColor();
|
||||
}
|
||||
|
||||
public void setStrokeColor(Color color) {
|
||||
colorChooser.setSelectedColor(color);
|
||||
}
|
||||
|
||||
public double getStrokeWidth() {
|
||||
return strokeWidth;
|
||||
}
|
||||
|
@ -637,11 +637,20 @@ public abstract class FinSetConfig extends RocketComponentConfig {
|
||||
* @param svgOptions the SVGOptionPanel object containing the options for writing the SVG file
|
||||
* @throws Exception if there is an error writing the SVG file
|
||||
*/
|
||||
private static void writeSVGFile(FinSet finSet, File file, SVGOptionPanel svgOptions) throws ParserConfigurationException, TransformerException {
|
||||
Coordinate[] points = finSet.getFinPointsWithRoot();
|
||||
public static void writeSVGFile(FinSet finSet, File file, SVGOptionPanel svgOptions) throws ParserConfigurationException, TransformerException {
|
||||
Coordinate[] points = finSet.generateContinuousFinAndTabShape();
|
||||
|
||||
SVGBuilder builder = new SVGBuilder();
|
||||
builder.addPath(points, null, svgOptions.getStrokeColor(), svgOptions.getStrokeWidth());
|
||||
|
||||
// Export fin tab separately if it's beyond the fin
|
||||
if (finSet.isTabBeyondFin()) {
|
||||
Coordinate[] tabPoints = finSet.getTabPointsWithRoot();
|
||||
Coordinate finFront = finSet.getFinFront();
|
||||
// Need to offset to the fin front because the tab points are relative to the fin front
|
||||
builder.addPath(tabPoints, finFront.x, finFront.y, null, svgOptions.getStrokeColor(), svgOptions.getStrokeWidth());
|
||||
}
|
||||
|
||||
builder.writeToFile(file);
|
||||
}
|
||||
}
|
||||
|
@ -16,8 +16,6 @@ import net.sf.openrocket.util.BoundingBox;
|
||||
import net.sf.openrocket.util.Coordinate;
|
||||
import net.sf.openrocket.gui.figure3d.geometry.Geometry.Surface;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
public class FinRenderer {
|
||||
private GLUtessellator tess = GLU.gluNewTess();
|
||||
|
||||
@ -37,7 +35,7 @@ public class FinRenderer {
|
||||
gl.glMatrixMode(GLMatrixFunc.GL_MODELVIEW);
|
||||
|
||||
Coordinate[] finPoints = finSet.getFinPointsWithLowResRoot();
|
||||
Coordinate[] tabPoints = finSet.getTabPointsLowRes();
|
||||
Coordinate[] tabPoints = finSet.getTabPointsWithRootLowRes();
|
||||
|
||||
{
|
||||
gl.glPushMatrix();
|
||||
|
@ -51,7 +51,7 @@ public class PrintableFinSet extends AbstractPrintable<FinSet> {
|
||||
protected void init (FinSet component) {
|
||||
|
||||
Coordinate[] points = component.getFinPointsWithRoot();
|
||||
Coordinate[] tabPoints = component.getTabPoints();
|
||||
Coordinate[] tabPoints = component.getTabPointsWithRoot();
|
||||
|
||||
finPolygon = new GeneralPath(GeneralPath.WIND_NON_ZERO, points.length);
|
||||
finTabPolygon = new GeneralPath(GeneralPath.WIND_NON_ZERO, tabPoints.length);
|
||||
|
@ -7,7 +7,6 @@ import java.util.Arrays;
|
||||
|
||||
import net.sf.openrocket.rocketcomponent.FinSet;
|
||||
import net.sf.openrocket.rocketcomponent.RocketComponent;
|
||||
import net.sf.openrocket.rocketcomponent.SymmetricComponent;
|
||||
import net.sf.openrocket.util.Coordinate;
|
||||
import net.sf.openrocket.util.MathUtil;
|
||||
import net.sf.openrocket.util.Transformation;
|
||||
@ -35,7 +34,7 @@ public class FinSetShapes extends RocketComponentShape {
|
||||
final Transformation compositeTransform = transformation.applyTransformation(cantRotation);
|
||||
|
||||
Coordinate[] finPoints = finset.getFinPoints();
|
||||
Coordinate[] tabPoints = finset.getTabPoints();
|
||||
Coordinate[] tabPoints = finset.getTabPointsWithRoot();
|
||||
Coordinate[] rootPoints = finset.getRootPoints();
|
||||
|
||||
// Translate & rotate points into place
|
||||
@ -203,7 +202,7 @@ public class FinSetShapes extends RocketComponentShape {
|
||||
Coordinate[] backPoints;
|
||||
int minIndex;
|
||||
|
||||
Coordinate[] points = finset.getTabPoints();
|
||||
Coordinate[] points = finset.getTabPointsWithRoot();
|
||||
|
||||
// this loop finds the index @ min-y, as visible from the back
|
||||
for (minIndex = points.length-1; minIndex > 0; minIndex--) {
|
||||
|
@ -1,9 +1,15 @@
|
||||
package net.sf.openrocket.gui.configdialog;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import net.sf.openrocket.gui.components.SVGOptionPanel;
|
||||
import net.sf.openrocket.rocketcomponent.FinSet;
|
||||
import net.sf.openrocket.rocketcomponent.TrapezoidFinSet;
|
||||
import org.junit.Assert;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
@ -15,6 +21,9 @@ import net.sf.openrocket.rocketcomponent.RocketComponent;
|
||||
import net.sf.openrocket.rocketcomponent.position.AxialMethod;
|
||||
import net.sf.openrocket.util.BaseTestCase.BaseTestCase;
|
||||
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.transform.TransformerException;
|
||||
|
||||
public class FinSetConfigTest extends BaseTestCase {
|
||||
|
||||
static Method method;
|
||||
@ -256,4 +265,36 @@ public class FinSetConfigTest extends BaseTestCase {
|
||||
Assert.assertEquals(0.0028, result.doubleValue(), 0.0001);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExportToSVG() throws IOException, ParserConfigurationException, TransformerException {
|
||||
BodyTube bodyTube = new BodyTube();
|
||||
bodyTube.setOuterRadius(0.06);
|
||||
bodyTube.setLength(0.2);
|
||||
|
||||
TrapezoidFinSet finSet = new TrapezoidFinSet();
|
||||
finSet.setCantAngle(0);
|
||||
finSet.setRootChord(0.06);
|
||||
finSet.setRootChord(0.05);
|
||||
finSet.setHeight(0.03);
|
||||
finSet.setSweep(0.02);
|
||||
finSet.setSweepAngle(Math.toRadians(20));
|
||||
finSet.setThickness(0.002);
|
||||
finSet.setTabLength(0.02);
|
||||
finSet.setTabHeight(0.012);
|
||||
finSet.setTabOffsetMethod(AxialMethod.MIDDLE);
|
||||
finSet.setTabOffset(0);
|
||||
|
||||
bodyTube.addChild(finSet);
|
||||
|
||||
SVGOptionPanel options = new SVGOptionPanel();
|
||||
options.setStrokeWidth(0.1);
|
||||
options.setStrokeColor(Color.BLACK);
|
||||
|
||||
File destFile = File.createTempFile("test", ".svg");
|
||||
|
||||
FinSetConfig.writeSVGFile(finSet, destFile, options);
|
||||
|
||||
// TODO: load the file and check the contents
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user