diff --git a/core/src/net/sf/openrocket/masscalc/MassCalculator.java b/core/src/net/sf/openrocket/masscalc/MassCalculator.java index 914460d35..49e5891b3 100644 --- a/core/src/net/sf/openrocket/masscalc/MassCalculator.java +++ b/core/src/net/sf/openrocket/masscalc/MassCalculator.java @@ -8,9 +8,11 @@ import org.slf4j.LoggerFactory; import net.sf.openrocket.motor.Motor; import net.sf.openrocket.motor.MotorConfiguration; +import net.sf.openrocket.motor.ThrustCurveMotor; import net.sf.openrocket.rocketcomponent.AxialStage; import net.sf.openrocket.rocketcomponent.FlightConfiguration; import net.sf.openrocket.rocketcomponent.Instanceable; +import net.sf.openrocket.rocketcomponent.MotorMount; import net.sf.openrocket.rocketcomponent.ParallelStage; import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.util.BugException; @@ -61,7 +63,7 @@ public class MassCalculator implements Monitorable { // private Vector< MassData> motorData = new Vector(); // this turns on copious amounts of debug. Recommend leaving this false - // until reaching code that causes interesting conditions. + // until reaching code that causes troublesome conditions. public boolean debug = false; ////////////////// Constructors /////////////////// @@ -96,10 +98,9 @@ public class MassCalculator implements Monitorable { throw new BugException("method: calculateStageCache(...) is faulty-- returned null data for an active stage: "+stage.getName()+"("+stage.getStageNumber()+")"); } dryCM = stageData.cm.average(dryCM); -// if( debug){ -// System.err.println(" stageData <<@"+stageNumber+"mass: "+dryCM.weight+" @"+dryCM.toString()); -// } + } + Coordinate totalCM=null; if( MassCalcType.NO_MOTORS == type ){ @@ -110,62 +111,83 @@ public class MassCalculator implements Monitorable { totalCM = dryCM.average(motorCM); } -// if(debug){ -// Coordinate cm = totalCM; -// System.err.println(String.format("==>> Combined Mass: %5.3gg @( %g, %g, %g)", -// cm.weight, cm.x, cm.y, cm.z )); -// } - return totalCM; } /** - * Compute the CG of the rocket with the provided motor configuration. + * Compute the CM of all motors, given a configuration and type * * @param configuration the rocket configuration * @param motors the motor configuration * @return the CG of the configuration */ - private MassData getMotorMassData(FlightConfiguration config, MassCalcType type) { + public MassData getMotorMassData(FlightConfiguration config, MassCalcType type) { if( MassCalcType.NO_MOTORS == type ){ return MassData.ZERO_DATA; } - // Add motor CGs - - MassData motorData = MassData.ZERO_DATA; - - // vvvv DEVEL vvvv +// // vvvv DEVEL vvvv +// //String massFormat = " [%2s]: %-16s %6s x %6s = %6s += %6s @ (%s, %s, %s )"; +// String inertiaFormat = " [%2s](%2s): %-16s %6s %6s"; // if( debug){ -// System.err.println("====== ====== getMotorCM: (type: "+type.name()+") ====== ====== ====== ====== ====== ======"); -// System.err.println(" [Number] [Name] [mass]"); +// System.err.println("====== ====== getMotorMassData( config:"+config.toDebug()+", type: "+type.name()+") ====== ====== ====== ====== ====== ======"); +// //System.err.println(String.format(massFormat, " #", "","Mass","Count","Config","Sum", "x","y","z")); +// System.err.println(String.format(inertiaFormat, " #","ct", "","I_ax","I_tr")); // } - // ^^^^ DEVEL ^^^^ +// // ^^^^ DEVEL ^^^^ -// int motorCount = 0; - for (MotorConfiguration inst : config.getActiveMotors() ) { - //ThrustCurveMotor motor = (ThrustCurveMotor) inst.getMotor(); + MassData allMotorData = MassData.ZERO_DATA; + //int motorIndex = 0; + for (MotorConfiguration mtrConfig : config.getActiveMotors() ) { + ThrustCurveMotor mtr = (ThrustCurveMotor) mtrConfig.getMotor(); - Coordinate position = inst.getPosition(); - Coordinate curMotorCM = type.getCG(inst.getMotor()).add(position); - double Ir = inst.getRotationalInertia(); - double It = inst.getLongitudinalInertia(); + MotorMount mount = mtrConfig.getMount(); + RocketComponent mountComp = (RocketComponent)mount; + Coordinate[] locations = mountComp.getLocations(); // location of mount, w/in entire rocket + int instanceCount = locations.length; + double motorXPosition = mtrConfig.getX(); // location of motor from mount - MassData instData = new MassData( curMotorCM, Ir, It); - motorData = motorData.add( instData ); + Coordinate localCM = type.getCG( mtr ); // CoM from beginning of motor + localCM = localCM.setWeight( localCM.weight * instanceCount); + // a *bit* hacky :P + Coordinate curMotorCM = localCM.setX( localCM.x + locations[0].x + motorXPosition ); + + double motorMass = curMotorCM.weight; + double Ir_single = mtrConfig.getUnitRotationalInertia()*motorMass; + double It_single = mtrConfig.getUnitLongitudinalInertia()*motorMass; + double Ir=0; + double It=0; + if( 1 == instanceCount ){ + Ir=Ir_single; + It=It_single; + }else{ + It = It_single * instanceCount; + + Ir = Ir_single*instanceCount; + // these need more complex instancing code... + for( Coordinate coord : locations ){ + double distance = Math.hypot( coord.y, coord.z); + Ir += motorMass*Math.pow( distance, 2); + } + } + + MassData configData = new MassData( curMotorCM, Ir, It); + allMotorData = allMotorData.add( configData ); // BEGIN DEVEL -// if( debug){ -// System.err.println(String.format(" motor %2d: %s %s", //%5.3gg @( %g, %g, %g)", -// motorCount, inst.getMotor().getDesignation(), instData.toDebug())); -// System.err.println(String.format(" >> %s", -// motorData.toDebug())); -// } -// motorCount++; + //if( debug){ + // // Inertia + // System.err.println(String.format( inertiaFormat, motorIndex, instanceCount, mtr.getDesignation(), Ir, It)); + // // mass only + //double singleMass = type.getCG( mtr ).weight; + //System.err.println(String.format( massFormat, motorIndex, mtr.getDesignation(), + // singleMass, instanceCount, curMotorCM.weight, allMotorData.getMass(),curMotorCM.x, curMotorCM.y, curMotorCM.z )); + //} + //motorIndex++; // END DEVEL } - return motorData; + return allMotorData; } /** @@ -197,10 +219,11 @@ public class MassCalculator implements Monitorable { MassData totalData = structureData.add( motorData); -// if(debug){ -// System.err.println(String.format("==>> Combined MassData: %s", totalData.toDebug())); -// -// } + if(debug){ + System.err.println(String.format(" >> Structural MassData: %s", structureData.toDebug())); + System.err.println(String.format(" >> Motor MassData: %s", motorData.toDebug())); + System.err.println(String.format("==>> Combined MassData: %s", totalData.toDebug())); + } return totalData.getLongitudinalInertia(); } @@ -233,10 +256,11 @@ public class MassCalculator implements Monitorable { } MassData totalData = structureData.add( motorData); -// if(debug){ -// System.err.println(String.format("==>> Combined MassData: %s", totalData.toDebug())); -// -// } + if(debug){ + System.err.println(String.format(" >> Structural MassData: %s", structureData.toDebug())); + System.err.println(String.format(" >> Motor MassData: %s", motorData.toDebug())); + System.err.println(String.format("==>> Combined MassData: %s", totalData.toDebug())); + } return totalData.getRotationalInertia(); } @@ -254,9 +278,9 @@ public class MassCalculator implements Monitorable { //throw new BugException("getPropellantMass is not yet implemented.... "); // add up the masses of all motors in the rocket if ( MassCalcType.NO_MOTORS != calcType ){ - for (MotorConfiguration curInstance : configuration.getActiveMotors()) { - mass = mass + curInstance.getPropellantMass(); - mass = curInstance.getMotor().getLaunchCG().weight - curInstance.getMotor().getEmptyCG().weight; + for (MotorConfiguration curConfig : configuration.getActiveMotors()) { + int instanceCount = curConfig.getMount().getInstanceCount(); + mass = mass + curConfig.getPropellantMass()*instanceCount; } } return mass; diff --git a/core/src/net/sf/openrocket/motor/MotorConfiguration.java b/core/src/net/sf/openrocket/motor/MotorConfiguration.java index 96cb07a10..eae6e390c 100644 --- a/core/src/net/sf/openrocket/motor/MotorConfiguration.java +++ b/core/src/net/sf/openrocket/motor/MotorConfiguration.java @@ -16,7 +16,6 @@ public class MotorConfiguration implements FlightConfigurableParameter> Why am I being cloned!?", new IllegalStateException(this.toDebug()+" >to> "+clone.toDebug())); + // log.error(">> Why am I being cloned!?", new IllegalStateException(this.toDebug()+" >to> "+clone.toDebug())); + + + // DO NOT UPDATE this.stages or this.motors; + // these are are updated correctly on their own. - // DO NOT UPDATE: - // this.stages and this.motors are updated correctly on their own. clone.cachedBounds = this.cachedBounds.clone(); clone.modID = this.modID; clone.boundsModID = -1; @@ -516,7 +516,7 @@ public class FlightConfiguration implements FlightConfigurableParameter nozzleDistance) { nozzleDistance = x; } diff --git a/core/src/net/sf/openrocket/util/TestRockets.java b/core/src/net/sf/openrocket/util/TestRockets.java index a948ef134..534521792 100644 --- a/core/src/net/sf/openrocket/util/TestRockets.java +++ b/core/src/net/sf/openrocket/util/TestRockets.java @@ -87,6 +87,22 @@ public class TestRockets { this.rnd = new Random(key.hashCode()); } + + // This function is used for unit, integration tests, DO NOT CHANGE (without updating tests). + private static MotorConfiguration generateMotorInstance_C6_18mm(){ + // public ThrustCurveMotor(Manufacturer manufacturer, String designation, String description, + // Motor.Type type, double[] delays, double diameter, double length, + // double[] time, double[] thrust, + // Coordinate[] cg, String digest); + ThrustCurveMotor mtr = new ThrustCurveMotor( + Manufacturer.getManufacturer("Estes"),"C6", " SU Black Powder", + Motor.Type.SINGLE, new double[] {0,3,5,7}, 0.018, 0.070, + new double[] { 0, 1, 2 }, new double[] { 0, 6, 0 }, + new Coordinate[] { + new Coordinate(0.035, 0, 0, 0.0227),new Coordinate(.035, 0, 0, 0.0165),new Coordinate(.035, 0, 0, 0.0102)}, + "digest C6 test"); + return new MotorConfiguration(mtr); + } // This function is used for unit, integration tests, DO NOT CHANGE (without updating tests). private static MotorConfiguration generateMotorInstance_M1350_75mm(){ @@ -315,7 +331,6 @@ public class TestRockets { // It is picked as a standard, simple, validation rocket. // This function is used for unit, integration tests, DO NOT CHANGE (without updating tests). public static final Rocket makeEstesAlphaIII(){ - Rocket rocket = new Rocket(); rocket.setName("Estes Alpha III / Code Verification Rocket"); AxialStage stage = new AxialStage(); @@ -379,6 +394,12 @@ public class TestRockets { thrustBlock.setThickness(0.00075); thrustBlock.setName("Engine Block"); inner.addChild(thrustBlock); + + MotorConfiguration motorConfig = TestRockets.generateMotorInstance_C6_18mm(); + motorConfig.setID( new MotorInstanceId( inner.getName(), 1) ); + inner.setMotorMount( true); + FlightConfigurationId motorConfigId = rocket.getSelectedConfiguration().getFlightConfigurationID(); + inner.setMotorInstance( motorConfigId, motorConfig); } // parachute @@ -406,6 +427,7 @@ public class TestRockets { bodytube.setMaterial(material); finset.setMaterial(material); + rocket.getSelectedConfiguration().setAllStages(); rocket.enableEvents(); return rocket; } diff --git a/core/test/net/sf/openrocket/masscalc/MassCalculatorTest.java b/core/test/net/sf/openrocket/masscalc/MassCalculatorTest.java index 852521f82..1c7c34326 100644 --- a/core/test/net/sf/openrocket/masscalc/MassCalculatorTest.java +++ b/core/test/net/sf/openrocket/masscalc/MassCalculatorTest.java @@ -2,16 +2,14 @@ package net.sf.openrocket.masscalc; //import junit.framework.TestCase; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; import org.junit.Test; import net.sf.openrocket.masscalc.MassCalculator.MassCalcType; -import net.sf.openrocket.motor.MotorConfiguration; -import net.sf.openrocket.rocketcomponent.ParallelStage; +import net.sf.openrocket.motor.Motor; import net.sf.openrocket.rocketcomponent.FlightConfiguration; -import net.sf.openrocket.rocketcomponent.FlightConfigurationId; import net.sf.openrocket.rocketcomponent.InnerTube; +import net.sf.openrocket.rocketcomponent.ParallelStage; import net.sf.openrocket.rocketcomponent.Rocket; import net.sf.openrocket.rocketcomponent.RocketComponent; import net.sf.openrocket.util.Coordinate; @@ -288,21 +286,88 @@ public class MassCalculatorTest extends BaseTestCase { assertEquals(" Delta Heavy Booster CM is incorrect: ", expCM, boosterSetCM); } + + @Test + public void testSingleMotorMass() { + Rocket rocket = TestRockets.makeEstesAlphaIII(); + + InnerTube mmt = (InnerTube) rocket.getChild(0).getChild(1).getChild(2); + Motor activeMotor = mmt.getMotorInstance( rocket.getSelectedConfiguration().getId()).getMotor(); + String desig = activeMotor.getDesignation(); + + double expLaunchMass = 0.0227; // kg + double expSpentMass = 0.0102; // kg + assertEquals(" Motor Mass "+desig+" is incorrect: ", expLaunchMass, activeMotor.getLaunchCG().weight, EPSILON); + assertEquals(" Motor Mass "+desig+" is incorrect: ", expSpentMass, activeMotor.getEmptyCG().weight, EPSILON); + + // Validate Booster Launch Mass + MassCalculator mc = new MassCalculator(); + double actPropMass = mc.getPropellantMass( rocket.getSelectedConfiguration(), MassCalcType.LAUNCH_MASS); + + double expPropMass = expLaunchMass - expSpentMass; + assertEquals(" Motor Mass "+desig+" is incorrect: ", expPropMass, actPropMass, EPSILON); + } + + + @Test + public void testBoosterMotorMass() { + Rocket rocket = TestRockets.makeFalcon9Heavy(); + ParallelStage boosters = (ParallelStage) rocket.getChild(1).getChild(1); + int boostNum = boosters.getStageNumber(); + rocket.getSelectedConfiguration().setOnlyStage( boostNum); + +// String treeDump = rocket.toDebugTree(); +// System.err.println( treeDump); + + { + InnerTube mmt = (InnerTube) boosters.getChild(1).getChild(0); + double expX = (.564 + 0.8 - 0.150 ); + double actX = mmt.getLocations()[0].x; + assertEquals(" Booster motor mount tubes located incorrectly: ", expX, actX, EPSILON); + } + { + // Validate Booster Launch Mass + MassCalculator mc = new MassCalculator(); + MassData launchMotorData = mc.getMotorMassData( rocket.getSelectedConfiguration(), MassCalcType.LAUNCH_MASS); + Coordinate launchCM = launchMotorData.getCM(); + // 1.214 = beginning of engine mmt + // 1.364-.062 = middle of engine: 1.302 + Coordinate expLaunchCM = new Coordinate(1.31434, 0, 0, 0.123*2*4); + assertEquals(" Booster Launch Mass is incorrect: ", expLaunchCM.weight, launchCM.weight, EPSILON); + assertEquals(" Booster Launch CM.x is incorrect: ", expLaunchCM.x, launchCM.x, EPSILON); + assertEquals(" Booster Launch CM.y is incorrect: ", expLaunchCM.y, launchCM.y, EPSILON); + assertEquals(" Booster Launch CM.z is incorrect: ", expLaunchCM.z, launchCM.z, EPSILON); + assertEquals(" Booster Launch CM is incorrect: ", expLaunchCM, launchCM); + + + MassData spentMotorData = mc.getMotorMassData( rocket.getSelectedConfiguration(), MassCalcType.BURNOUT_MASS); + Coordinate spentCM = spentMotorData.getCM(); + Coordinate expSpentCM = new Coordinate(1.31434, 0, 0, 0.064*2*4); + assertEquals(" Booster Spent Mass is incorrect: ", expSpentCM.weight, spentCM.weight, EPSILON); + assertEquals(" Booster Launch CM.x is incorrect: ", expSpentCM.x, spentCM.x, EPSILON); + assertEquals(" Booster Launch CM.y is incorrect: ", expSpentCM.y, spentCM.y, EPSILON); + assertEquals(" Booster Launch CM.z is incorrect: ", expSpentCM.z, spentCM.z, EPSILON); + assertEquals(" Booster Launch CM is incorrect: ", expSpentCM, spentCM); + } + + } + + @Test public void testBoosterTotalCM() { Rocket rocket = TestRockets.makeFalcon9Heavy(); - rocket.setName("TestRocket."+Thread.currentThread().getStackTrace()[1].getMethodName()); ParallelStage boosters = (ParallelStage) rocket.getChild(1).getChild(1); int boostNum = boosters.getStageNumber(); //rocket.getDefaultConfiguration().setAllStages(false); rocket.getSelectedConfiguration().setOnlyStage( boostNum); - //String treeDump = rocket.toDebugTree(); - //System.err.println( treeDump); +// String treeDump = rocket.toDebugTree(); +// System.err.println( treeDump); { // Validate Booster Launch Mass MassCalculator mc = new MassCalculator(); + //mc.debug = true; Coordinate boosterSetCM = mc.getCM( rocket.getSelectedConfiguration(), MassCalcType.LAUNCH_MASS); double calcTotalMass = boosterSetCM.weight; @@ -318,6 +383,7 @@ public class MassCalculatorTest extends BaseTestCase { { // Validate Booster Burnout Mass MassCalculator mc = new MassCalculator(); + //mc.debug = true; Coordinate boosterSetCM = mc.getCM( rocket.getSelectedConfiguration(), MassCalcType.BURNOUT_MASS); double calcTotalMass = boosterSetCM.weight; @@ -374,12 +440,11 @@ public class MassCalculatorTest extends BaseTestCase { // Validate Boosters MassCalculator mc = new MassCalculator(); - //mc.debug = true; - double expMOI_axial = 0.00752743; - double boosterMOI_xx= mc.getRotationalInertia( defaultConfig, MassCalcType.LAUNCH_MASS); + final double expMOI_axial = 0.05009613217;//0.00752743; + final double boosterMOI_xx= mc.getRotationalInertia( defaultConfig, MassCalcType.LAUNCH_MASS); - double expMOI_tr = 0.0436639379937; - double boosterMOI_tr= mc.getLongitudinalInertia( defaultConfig, MassCalcType.LAUNCH_MASS); + final double expMOI_tr = 0.05263041249; // 0.0436639379937; + final double boosterMOI_tr= mc.getLongitudinalInertia( defaultConfig, MassCalcType.LAUNCH_MASS); assertEquals(" Booster x-axis MOI is incorrect: ", expMOI_axial, boosterMOI_xx, EPSILON); assertEquals(" Booster transverse MOI is incorrect: ", expMOI_tr, boosterMOI_tr, EPSILON); diff --git a/core/test/net/sf/openrocket/masscalc/MassDataTest.java b/core/test/net/sf/openrocket/masscalc/MassDataTest.java index 4fe3caca8..0f96eda02 100644 --- a/core/test/net/sf/openrocket/masscalc/MassDataTest.java +++ b/core/test/net/sf/openrocket/masscalc/MassDataTest.java @@ -2,11 +2,9 @@ package net.sf.openrocket.masscalc; //import junit.framework.TestCase; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; import org.junit.Test; - import net.sf.openrocket.util.Coordinate; import net.sf.openrocket.util.MathUtil; import net.sf.openrocket.util.BaseTestCase.BaseTestCase; @@ -86,7 +84,7 @@ public class MassDataTest extends BaseTestCase { // System.err.println(" @(1): "+ body1.toDebug()); // System.err.println(" @(2): "+ body2.toDebug()); // System.err.println(" @(3): "+ asbly3.toDebug()); - System.err.println(" Center of Mass: (3) expected: "+ cm3_expected); +// System.err.println(" Center of Mass: (3) expected: "+ cm3_expected); assertEquals(" Center of Mass calculated incorrectly: ", cm3_expected, asbly3.getCM() );