Merge pull request #591 from wolsen/fix-print

[Fixes #531] Fix printing/export as pdf
This commit is contained in:
Daniel Williams 2020-03-28 20:47:16 -04:00 committed by GitHub
commit 7dbbb6c74d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 184 additions and 99 deletions

View File

@ -371,6 +371,11 @@ public class BodyTube extends SymmetricComponent implements MotorMount, Coaxial
return this.motors.getDefault();
}
@Override
public MotorConfigurationSet getMotorConfigurationSet() {
return this.motors;
}
@Override
public MotorConfiguration getMotorConfig( final FlightConfigurationId fcid){
return this.motors.get(fcid);
@ -432,7 +437,7 @@ public class BodyTube extends SymmetricComponent implements MotorMount, Coaxial
@Override
public int getMotorCount() {
return this.motors.size();
return this.getClusterConfiguration().getClusterCount();
}
@Override

View File

@ -126,6 +126,32 @@ public class FlightConfiguration implements FlightConfigurableParameter<FlightCo
_setStageActive( stageNumber, false );
}
/**
* Activates all stages as active starting from the specified component
* to the top-most stage in the rocket. Active stages are those stages
* which contribute to the mass of the rocket. Given a rocket with the
* following stages:
*
* <ul>
* <li>StageA - top most stage, containing nose cone etc.</li>
* <li>StageB - middle stage</li>
* <li>StageC - bottom stage</li>
* </ul>
*
* invoking <code>FlightConfiguration.activateStagesThrough(StageB)</code>
* will cause both StageA and StageB to be marked as active, and StageC
* will be marked as inactive.
*
* @param stage the AxialStage to activate all stages up to (inclusive)
*/
public void activateStagesThrough(final AxialStage stage) {
clearAllStages();
for (int i=0; i <= stage.getStageNumber(); i++) {
_setStageActive(i, true);
}
updateMotors();
}
/**
* This method flags the specified stage as active, and all other stages as inactive.
*

View File

@ -258,6 +258,11 @@ public class InnerTube extends ThicknessRingComponent implements AxialPositionab
return this.motors.getDefault();
}
@Override
public MotorConfigurationSet getMotorConfigurationSet() {
return this.motors;
}
@Override
public MotorConfiguration getMotorConfig( final FlightConfigurationId fcid){
return this.motors.get(fcid);
@ -321,7 +326,7 @@ public class InnerTube extends ThicknessRingComponent implements AxialPositionab
@Override
public int getMotorCount() {
return this.motors.size();
return this.getClusterConfiguration().getClusterCount();
}

View File

@ -3,6 +3,7 @@ package net.sf.openrocket.rocketcomponent;
import java.util.Iterator;
import net.sf.openrocket.motor.MotorConfiguration;
import net.sf.openrocket.motor.MotorConfigurationSet;
import net.sf.openrocket.util.ChangeSource;
import net.sf.openrocket.util.Coordinate;
@ -80,6 +81,13 @@ public interface MotorMount extends ChangeSource, FlightConfigurableComponent {
// duplicate of RocketComponent
public Coordinate[] getLocations();
/**
* Returns the set of motors configured for flight/simulation in this motor mount.
* @return the MotorConfigurationSet containing the set of motors configured in
* this motor mount.
*/
public MotorConfigurationSet getMotorConfigurationSet();
/**
*
* @param fcid id for which to return the motor (null retrieves the default)

View File

@ -201,7 +201,7 @@ public class FlightConfigurationTest extends BaseTestCase {
InnerTube smmt = (InnerTube)rkt.getChild(0).getChild(1).getChild(2);
int expectedMotorCount = 5;
int actualMotorCount = smmt.getMotorCount();
int actualMotorCount = smmt.getMotorConfigurationSet().size();
assertThat("number of motor configurations doesn't match.", actualMotorCount, equalTo(expectedMotorCount));
}
@ -295,6 +295,21 @@ public class FlightConfigurationTest extends BaseTestCase {
config.toggleStage(0);
assertThat(" toggle stage #0: ", config.isStageActive(0), equalTo(false));
AxialStage sustainer = rkt.getTopmostStage();
AxialStage booster = rkt.getBottomCoreStage();
assertThat(" sustainer stage is stage #0: ", sustainer.getStageNumber(), equalTo(0));
assertThat(" booster stage is stage #1: ", booster.getStageNumber(), equalTo(1));
config.clearAllStages();
config.activateStagesThrough(sustainer);
assertThat(" sustainer stage is active: ", config.isStageActive(sustainer.getStageNumber()), equalTo(true));
assertThat(" booster stage is inactive: ", config.isStageActive(booster.getStageNumber()), equalTo(false));
config.clearAllStages();
config.activateStagesThrough(booster);
assertThat(" sustainer stage is active: ", config.isStageActive(sustainer.getStageNumber()), equalTo(true));
assertThat(" booster stage is active: ", config.isStageActive(booster.getStageNumber()), equalTo(true));
}
/**

View File

@ -28,7 +28,9 @@ import net.sf.openrocket.gui.figureelements.FigureElement;
import net.sf.openrocket.gui.figureelements.RocketInfo;
import net.sf.openrocket.gui.scalefigure.RocketPanel;
import net.sf.openrocket.masscalc.MassCalculator;
import net.sf.openrocket.masscalc.RigidBody;
import net.sf.openrocket.motor.Motor;
import net.sf.openrocket.motor.MotorConfiguration;
import net.sf.openrocket.rocketcomponent.AxialStage;
import net.sf.openrocket.rocketcomponent.FlightConfiguration;
import net.sf.openrocket.rocketcomponent.FlightConfigurationId;
@ -162,7 +164,7 @@ public class DesignReport {
PrintUtilities.addText(document, PrintUtilities.BIG_BOLD, ROCKET_DESIGN);
Rocket rocket = rocketDocument.getRocket();
final FlightConfiguration configuration = rocket.getSelectedConfiguration();//.clone();
final FlightConfiguration configuration = rocket.getSelectedConfiguration();
configuration.setAllStages();
PdfContentByte canvas = writer.getDirectContent();
@ -177,8 +179,8 @@ public class DesignReport {
canvas.beginText();
canvas.setFontAndSize(ITextHelper.getBaseFont(), PrintUtilities.NORMAL_FONT_SIZE);
int figHeightPts = (int) (PrintUnit.METERS.toPoints(figure.getHeight()) * 0.4 * (scale / PrintUnit.METERS
.toPoints(1)));
double figureHeightInPoints = PrintUnit.METERS.toPoints(figure.getFigureHeight());
int figHeightPts = (int) (figureHeightInPoints * SCALE_FUDGE_FACTOR * (scale / PrintUnit.METERS.toPoints(1)));
final int diagramHeight = pageImageableHeight * 2 - 70 - (figHeightPts);
canvas.moveText(document.leftMargin() + pageSize.getBorderWidthLeft(), diagramHeight);
canvas.moveTextWithLeading(0, -16);
@ -190,8 +192,7 @@ public class DesignReport {
canvas.newlineShowText(STAGES);
canvas.showText("" + rocket.getStageCount());
if ( configuration.hasMotors()){
if (configuration.hasMotors()) {
if (configuration.getStageCount() > 1) {
canvas.newlineShowText(MASS_WITH_MOTORS);
} else {
@ -213,7 +214,11 @@ public class DesignReport {
canvas.endText();
try {
//Move the internal pointer of the document below that of what was just written using the direct byte buffer.
/*
* Move the internal pointer of the document below the rocket diagram and
* the key attributes. The height of the rocket figure is already calculated
* as diagramHeigt and the height of the attributes text is finalY - initialY.
*/
Paragraph paragraph = new Paragraph();
float finalY = canvas.getYTLM();
int heightOfDiagramAndText = (int) (pageSize.getHeight() - (finalY - initialY + diagramHeight));
@ -223,28 +228,26 @@ public class DesignReport {
List<Simulation> simulations = rocketDocument.getSimulations();
int motorNumber = 0;
for( FlightConfigurationId fcid : rocket.getIds()){
boolean firstMotor = true;
for (FlightConfigurationId fcid : rocket.getIds()) {
PdfPTable parent = new PdfPTable(2);
parent.setWidthPercentage(100);
parent.setHorizontalAlignment(Element.ALIGN_LEFT);
parent.setSpacingBefore(0);
parent.setWidths(new int[] { 1, 3 });
int leading = 0;
//The first motor config is always null. Skip it and the top-most motor, then set the leading.
if ( motorNumber > 1) {
leading = 25;
}
/* The first motor information will get no spacing
* before it, while each subsequent table will need
* a spacing of 25.
*/
int leading = (firstMotor) ? 0 : 25;
FlightData flight = findSimulation( fcid, simulations);
addFlightData(flight, rocket, fcid, parent, leading);
addMotorData(rocket, fcid, parent);
document.add(parent);
motorNumber++;
firstMotor = false;
}
} catch (DocumentException e) {
log.error("Could not modify document.", e);
@ -274,21 +277,22 @@ public class DesignReport {
theFigure.updateFigure();
double scale =
(thePageImageableWidth * 2.2) / theFigure.getWidth();
(thePageImageableWidth * 2.2) / theFigure.getFigureWidth();
theFigure.setScale(scale);
/*
* page dimensions are in points-per-inch, which, in Java2D, are the same as pixels-per-inch; thus we don't need any conversion
/* Conveniently, page dimensions are in points-per-inch, which, in
* Java2D, are the same as pixels-per-inch; thus we don't need any
* conversion for the figure size.
*/
theFigure.setSize(thePageImageableWidth, thePageImageableHeight);
theFigure.updateFigure();
final DefaultFontMapper mapper = new DefaultFontMapper();
Graphics2D g2d = theCanvas.createGraphics(thePageImageableWidth, thePageImageableHeight * 2, mapper);
final double halfFigureHeight = SCALE_FUDGE_FACTOR * theFigure.getFigureHeightPx() / 2;
final double halfFigureHeight = SCALE_FUDGE_FACTOR * theFigure.getFigureHeight() / 2;
int y = PrintUnit.POINTS_PER_INCH;
//If the y dimension is negative, then it will potentially be drawn off the top of the page. Move the origin
//to allow for this.
if (theFigure.getHeight() < 0.0d) {
if (theFigure.getDimensions().getY() < 0.0d) {
y += (int) halfFigureHeight;
}
g2d.translate(20, y);
@ -326,30 +330,26 @@ public class DesignReport {
DecimalFormat ttwFormat = new DecimalFormat("0.00");
MassCalculator massCalc = new MassCalculator();
if( !motorId.hasError() ){
if( motorId.hasError() ){
throw new IllegalStateException("Attempted to add motor data with an invalid fcid");
}
rocket.createFlightConfiguration(motorId);
FlightConfiguration config = rocket.getFlightConfiguration( motorId);
FlightConfiguration config = rocket.getFlightConfiguration(motorId);
int totalMotorCount = 0;
double totalPropMass = 0;
double totalImpulse = 0;
double totalTTW = 0;
int stage = 0;
double stageMass = 0;
boolean topBorder = false;
for (RocketComponent c : rocket) {
if (c instanceof AxialStage) {
config.clearAllStages();
config.setOnlyStage(stage);
stage++;
stageMass = massCalc.getCGAnalysis( config).get(stage).weight;
config.activateStagesThrough((AxialStage) c);
RigidBody launchInfo = MassCalculator.calculateLaunch(config);
stageMass = launchInfo.getMass();
// Calculate total thrust-to-weight from only lowest stage motors
totalTTW = 0;
topBorder = true;
@ -358,11 +358,19 @@ public class DesignReport {
if (c instanceof MotorMount && ((MotorMount) c).isMotorMount()) {
MotorMount mount = (MotorMount) c;
// TODO: refactor this... it's redundant with containing if, and could probably be simplified
if (mount.isMotorMount() && (mount.getMotorConfig(motorId) != null) &&(null != mount.getMotorConfig(motorId).getMotor())) {
Motor motor = mount.getMotorConfig(motorId).getMotor();
int motorCount = mount.getMotorCount();
MotorConfiguration motorConfig = mount.getMotorConfig(motorId);
if (null == motorConfig) {
log.warn("Unable to find motorConfig for motorId {}", motorId);
continue;
}
Motor motor = motorConfig.getMotor();
if (null == motor) {
log.warn("Motor instance is null for motorId {}", motorId);
continue;
}
int motorCount = mount.getMotorCount();
int border = Rectangle.NO_BORDER;
if (topBorder) {
@ -408,7 +416,6 @@ public class DesignReport {
totalTTW += ttw * motorCount;
}
}
}
if (totalMotorCount > 1) {
int border = Rectangle.TOP;

View File

@ -3,6 +3,8 @@
*/
package net.sf.openrocket.gui.print;
import java.awt.geom.Rectangle2D;
import net.sf.openrocket.gui.scalefigure.RocketFigure;
import net.sf.openrocket.rocketcomponent.Rocket;
@ -27,7 +29,15 @@ public class PrintFigure extends RocketFigure {
updateFigure();
}
public double getFigureHeightPx() {
return this.getSize().height;
public double getFigureHeight() {
return this.subjectBounds_m.getHeight();
}
public double getFigureWidth() {
return this.subjectBounds_m.getWidth();
}
public Rectangle2D getDimensions() {
return this.subjectBounds_m.getBounds2D();
}
}

View File

@ -19,6 +19,10 @@ public class SymmetricComponentShapes extends RocketComponentShape {
// TODO: LOW: Uses only first component of cluster (not currently clusterable)
public static RocketComponentShape[] getShapesSide( final RocketComponent component, final Transformation transformation) {
return getShapesSide(component, transformation, 1.0d);
}
public static RocketComponentShape[] getShapesSide( final RocketComponent component, final Transformation transformation, final double scaleFactor ) {
SymmetricComponent c = (SymmetricComponent) component;
@ -82,14 +86,14 @@ public class SymmetricComponentShapes extends RocketComponentShape {
// TODO: LOW: curved path instead of linear
Path2D.Double path = new Path2D.Double();
path.moveTo((nose.x + points.get(len - 1).x) , (nose.y+points.get(len - 1).y) );
path.moveTo((nose.x + points.get(len - 1).x) * scaleFactor, (nose.y+points.get(len - 1).y) * scaleFactor);
for (i = len - 2; i >= 0; i--) {
path.lineTo((nose.x+points.get(i).x), (nose.y+points.get(i).y) );
path.lineTo((nose.x+points.get(i).x) * scaleFactor, (nose.y+points.get(i).y) * scaleFactor);
}
for (i = 0; i < len; i++) {
path.lineTo((nose.x+points.get(i).x) , (nose.y-points.get(i).y) );
path.lineTo((nose.x+points.get(i).x) * scaleFactor, (nose.y-points.get(i).y) * scaleFactor);
}
path.lineTo((nose.x+points.get(len - 1).x) , (nose.y+points.get(len - 1).y) );
path.lineTo((nose.x+points.get(len - 1).x) * scaleFactor , (nose.y+points.get(len - 1).y) * scaleFactor);
path.closePath();
//s[len] = path;

View File

@ -37,15 +37,15 @@ public class TransitionShapes extends RocketComponentShape {
double r2 = transition.getAftRadius();
Path2D.Float path = new Path2D.Float();
path.moveTo( (frontCenter.x), (frontCenter.y+ r1));
path.lineTo( (frontCenter.x+length), (frontCenter.y+r2));
path.lineTo( (frontCenter.x+length), (frontCenter.y-r2));
path.lineTo( (frontCenter.x), (frontCenter.y-r1));
path.moveTo( (frontCenter.x) * scaleFactor, (frontCenter.y+ r1) * scaleFactor);
path.lineTo( (frontCenter.x+length) * scaleFactor, (frontCenter.y+r2) * scaleFactor);
path.lineTo( (frontCenter.x+length) * scaleFactor, (frontCenter.y-r2) * scaleFactor);
path.lineTo( (frontCenter.x) * scaleFactor, (frontCenter.y-r1) * scaleFactor);
path.closePath();
mainShapes = new RocketComponentShape[] { new RocketComponentShape( path, component) };
} else {
mainShapes = SymmetricComponentShapes.getShapesSide(component, transformation);
mainShapes = SymmetricComponentShapes.getShapesSide(component, transformation, scaleFactor);
}
Shape foreShoulder=null, aftShoulder=null;
@ -57,7 +57,7 @@ public class TransitionShapes extends RocketComponentShape {
final Transformation offsetTransform = Transformation.getTranslationTransform(-transition.getForeShoulderLength(), 0, 0);
final Transformation foreShoulderTransform = transformation.applyTransformation(offsetTransform);
foreShoulder = TubeShapes.getShapesSide( foreShoulderTransform, shoulderLength, shoulderRadius);
foreShoulder = TubeShapes.getShapesSide( foreShoulderTransform, shoulderLength, shoulderRadius, scaleFactor);
arrayLength++;
}
if (transition.getAftShoulderLength() > 0.0005) {
@ -66,7 +66,7 @@ public class TransitionShapes extends RocketComponentShape {
final Transformation offsetTransform = Transformation.getTranslationTransform(transition.getLength(), 0, 0);
final Transformation aftShoulderTransform = transformation.applyTransformation(offsetTransform);
aftShoulder = TubeShapes.getShapesSide(aftShoulderTransform, shoulderLength, shoulderRadius);
aftShoulder = TubeShapes.getShapesSide(aftShoulderTransform, shoulderLength, shoulderRadius, scaleFactor);
arrayLength++;
}
if (foreShoulder==null && aftShoulder==null)

View File

@ -10,14 +10,19 @@ import net.sf.openrocket.util.Transformation;
public class TubeShapes extends RocketComponentShape {
public static Shape getShapesSide( final Transformation transformation, final double length, final double radius ){
return getShapesSide(transformation, length, radius, 1.0d);
}
public static Shape getShapesSide( final Transformation transformation, final double length, final double radius, final double scaleFactor ){
final Coordinate instanceAbsoluteLocation = transformation.transform(Coordinate.ZERO);
return new Rectangle2D.Double((instanceAbsoluteLocation.x), //x - the X coordinate of the upper-left corner of the newly constructed Rectangle2D
(instanceAbsoluteLocation.y-radius), // y - the Y coordinate of the upper-left corner of the newly constructed Rectangle2D
length, // w - the width of the newly constructed Rectangle2D
2*radius); // h - the height of the newly constructed Rectangle2D
return new Rectangle2D.Double((instanceAbsoluteLocation.x) * scaleFactor, //x - the X coordinate of the upper-left corner of the newly constructed Rectangle2D
(instanceAbsoluteLocation.y-radius) * scaleFactor, // y - the Y coordinate of the upper-left corner of the newly constructed Rectangle2D
length * scaleFactor, // w - the width of the newly constructed Rectangle2D
2*radius * scaleFactor); // h - the height of the newly constructed Rectangle2D
}
public static Shape getShapesBack( final Transformation transformation, final double radius ) {