Merge pull request #676 from teyrana/509-fix-component-analysis

[fixes #509] Fix Component Analysis Dialog
This commit is contained in:
Daniel Williams 2020-07-04 12:28:12 -04:00 committed by GitHub
commit 8c129dcc10
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 431 additions and 319 deletions

View File

@ -700,9 +700,10 @@ componentanalysisdlg.lbl.machnumber = Mach number:
componentanalysisdlg.lbl.rollrate = Roll rate:
componentanalysisdlg.lbl.activestages = Active stages:
componentanalysisdlg.lbl.motorconf = Motor configuration:
componentanalysisdlg.TabStability.Col = Component
componentanalysisdlg.TabStability.Col.CG = CG
componentanalysisdlg.TabStability.Col.Mass = Mass
componentanalysisdlg.TabStability.Col = Component Name
componentanalysisdlg.TabStability.Col.EachMass = Instance Mass
componentanalysisdlg.TabStability.Col.AllMass = Aggregate Mass
componentanalysisdlg.TabStability.Col.CG = Aggregate CG Location
componentanalysisdlg.TabStability.Col.CP = CP
componentanalysisdlg.TabStability = Stability
componentanalysisdlg.TabStability.ttip = Stability information

View File

@ -2,14 +2,7 @@ package net.sf.openrocket.aerodynamics;
import static net.sf.openrocket.util.MathUtil.pow2;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
import java.util.*;
import net.sf.openrocket.aerodynamics.barrowman.FinSetCalc;
import net.sf.openrocket.aerodynamics.barrowman.RocketComponentCalc;
@ -27,7 +20,7 @@ import net.sf.openrocket.util.Coordinate;
import net.sf.openrocket.util.MathUtil;
import net.sf.openrocket.util.PolyInterpolator;
import net.sf.openrocket.util.Reflection;
import net.sf.openrocket.util.Transformation;
/**
* An aerodynamic calculator that uses the extended Barrowman method to
@ -64,7 +57,7 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator {
public Coordinate getCP(FlightConfiguration configuration, FlightConditions conditions,
WarningSet warnings) {
checkCache(configuration);
AerodynamicForces forces = calculateNonAxialForces(configuration, conditions, null, warnings);
AerodynamicForces forces = calculateNonAxialForces(configuration, conditions, warnings);
return forces.getCP();
}
@ -72,60 +65,102 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator {
@Override
public Map<RocketComponent, AerodynamicForces> getForceAnalysis(FlightConfiguration configuration,
FlightConditions conditions, WarningSet warnings) {
checkCache(configuration);
AerodynamicForces f;
Map<RocketComponent, AerodynamicForces> map =
new LinkedHashMap<RocketComponent, AerodynamicForces>();
// Add all components to the map
for (RocketComponent component : configuration.getActiveComponents()) {
// Skip non-aerodynamic components
if (!component.isAerodynamic())
continue;
f = new AerodynamicForces();
f.setComponent(component);
map.put(component, f);
FlightConditions conditions,
WarningSet warnings) {
if (calcMap == null){
buildCalcMap(configuration);
}
InstanceMap instMap = configuration.getActiveInstances();
Map<RocketComponent, AerodynamicForces> eachMap = new LinkedHashMap<>();
Map<RocketComponent, AerodynamicForces> assemblyMap = new LinkedHashMap<>();
// Calculate non-axial force data
AerodynamicForces total = calculateNonAxialForces(configuration, conditions, map, warnings);
calculateForceAnalysis(conditions, configuration.getRocket(), instMap, eachMap, assemblyMap, warnings);
// Calculate friction data
total.setFrictionCD(calculateFrictionDrag(configuration, conditions, map, warnings));
total.setPressureCD(calculatePressureDrag(configuration, conditions, map, warnings));
total.setBaseCD(calculateBaseDrag(configuration, conditions, map, warnings));
total.setComponent(configuration.getRocket());
map.put(total.getComponent(), total);
for (RocketComponent c : map.keySet()) {
f = map.get(c);
if (Double.isNaN(f.getBaseCD()) && Double.isNaN(f.getPressureCD()) &&
Double.isNaN(f.getFrictionCD()))
AerodynamicForces rocketForces = assemblyMap.get(configuration.getRocket());
rocketForces.setFrictionCD(calculateFrictionDrag(configuration, conditions, eachMap, warnings));
rocketForces.setPressureCD(calculatePressureDrag(configuration, conditions, eachMap, warnings));
rocketForces.setBaseCD(calculateBaseDrag(configuration, conditions, eachMap, warnings));
Map<RocketComponent, AerodynamicForces> finalMap = new LinkedHashMap<>();
for(final RocketComponent comp : instMap.keySet()){
AerodynamicForces f;
if(comp instanceof ComponentAssembly) {
f = assemblyMap.get(comp);
} else if(comp.isAerodynamic()){
f = eachMap.get(comp);
}else{
continue;
}
if (Double.isNaN(f.getCNa())){
f.setCNa(0.0);
}
if (f.getCP().isNaN()) {
f.setCP(Coordinate.ZERO);
}
if (Double.isNaN(f.getBaseCD()))
f.setBaseCD(0);
if (Double.isNaN(f.getPressureCD()))
f.setPressureCD(0);
if (Double.isNaN(f.getFrictionCD()))
f.setFrictionCD(0);
f.setCD(f.getBaseCD() + f.getPressureCD() + f.getFrictionCD());
f.setCaxial(calculateAxialDrag(conditions, f.getCD()));
finalMap.put(comp, f);
}
return map;
return finalMap;
}
private AerodynamicForces calculateForceAnalysis( FlightConditions conds,
RocketComponent comp,
InstanceMap instances,
Map<RocketComponent, AerodynamicForces> eachForces,
Map<RocketComponent, AerodynamicForces> assemblyForces,
WarningSet warnings)
{
// forces for this component, and all forces below it, in the rocket-tree
// => regardless `if(comp isinstance ComponentAssembly)`, or not.
AerodynamicForces aggregateForces = new AerodynamicForces().zero();
aggregateForces.setComponent(comp);
// forces for this component, _only_
if(comp.isAerodynamic()) {
RocketComponentCalc calcObj = calcMap.get(comp);
if (null == calcObj) {
throw new NullPointerException("Could not find a CalculationObject for aerodynamic Component!: " + comp.getComponentName());
} else {
List<InstanceContext> contextList = instances.get(comp);
// across every instance of this component:
AerodynamicForces compForces = calculateComponentNonAxialForces(conds, comp, calcObj, contextList, warnings);
eachForces.put(comp, compForces);
aggregateForces.merge(compForces);
}
}
for( RocketComponent child : comp.getChildren()) {
// forces particular to each component
AerodynamicForces childForces = calculateForceAnalysis(conds, child, instances, eachForces, assemblyForces, warnings);
if(null != childForces) {
aggregateForces.merge(childForces);
}
}
assemblyForces.put(comp, aggregateForces);
return eachForces.get(comp);
}
@Override
public AerodynamicForces getAerodynamicForces(FlightConfiguration configuration,
FlightConditions conditions, WarningSet warnings) {
@ -135,7 +170,7 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator {
warnings = ignoreWarningSet;
// Calculate non-axial force data
AerodynamicForces total = calculateNonAxialForces(configuration, conditions, null, warnings);
AerodynamicForces total = calculateNonAxialForces(configuration, conditions, warnings);
// Calculate friction data
total.setFrictionCD(calculateFrictionDrag(configuration, conditions, null, warnings));
@ -150,77 +185,79 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator {
calculateDampingMoments(configuration, conditions, total);
total.setCm(total.getCm() - total.getPitchDampingMoment());
total.setCyaw(total.getCyaw() - total.getYawDampingMoment());
return total;
}
private AerodynamicForces calculateComponentNonAxialForces( FlightConditions conditions,
RocketComponent comp,
RocketComponentCalc calcObj,
List<InstanceContext> contextList,
WarningSet warnings)
{
// across every instance of this component:
final AerodynamicForces componentForces = new AerodynamicForces().zero();
// iterate across component instances
for(InstanceContext context: contextList ) {
// specific to this _instance_ of this component:
AerodynamicForces instanceForces = new AerodynamicForces().zero();
calcObj.calculateNonaxialForces(conditions, context.transform, instanceForces, warnings);
Coordinate cp_inst = instanceForces.getCP();
Coordinate cp_abs = context.transform.transform(cp_inst);
cp_abs = cp_abs.setY(0.0).setZ(0.0);
instanceForces.setCP(cp_abs);
double CN_instanced = instanceForces.getCN();
instanceForces.setCm(CN_instanced * instanceForces.getCP().x / conditions.getRefLength());
componentForces.merge(instanceForces);
}
componentForces.setComponent(comp);
return componentForces;
}
/**
* Perform the actual CP calculation.
*/
private AerodynamicForces calculateNonAxialForces(FlightConfiguration configuration, FlightConditions conditions,
Map<RocketComponent, AerodynamicForces> calculators, WarningSet warnings) {
private AerodynamicForces calculateNonAxialForces(FlightConfiguration configuration, FlightConditions conditions, WarningSet warnings) {
checkCache(configuration);
if (warnings == null)
warnings = ignoreWarningSet;
if (conditions.getAOA() > 17.5 * Math.PI / 180)
warnings.add(new Warning.LargeAOA(conditions.getAOA()));
if (calcMap == null)
buildCalcMap(configuration);
if( ! isContinuous( configuration.getRocket() ) ){
warnings.add( Warning.DIAMETER_DISCONTINUITY);
}
final InstanceMap imap = configuration.getActiveInstances();
// across the _entire_ assembly -- like a rocket, or a stage
final AerodynamicForces assemblyForces= new AerodynamicForces().zero();
// System.err.println("======================================");
// System.err.println("==== Iterating through components ====");
// iterate through all components
for(Map.Entry<RocketComponent, ArrayList<InstanceContext>> entry: imap.entrySet() ) {
final RocketComponent comp = entry.getKey();
for(Map.Entry<RocketComponent, ArrayList<InstanceContext>> mapEntry: imap.entrySet() ) {
final RocketComponent comp = mapEntry.getKey();
final List<InstanceContext> contextList = mapEntry.getValue();
RocketComponentCalc calcObj = calcMap.get(comp);
// System.err.println("comp=" + comp);
if (null != calcObj) {
// iterate across component instances
final ArrayList<InstanceContext> contextList = entry.getValue();
// calculated across all component instances
final AerodynamicForces componentForces = calculateComponentNonAxialForces(conditions, comp, calcObj, contextList, warnings);
for(InstanceContext context: contextList ) {
AerodynamicForces instanceForces = new AerodynamicForces().zero();
// System.err.println(String.format("@ [%s]:[%d/%d]", comp.getName(), context.instanceNumber + 1, comp.getInstanceCount()));
// System.err.println("_________ inst/ctxt: xrotation: " + context.transform.getXrotation());
calcObj.calculateNonaxialForces(conditions, context.transform, instanceForces, warnings);
Coordinate cp_inst = instanceForces.getCP();
Coordinate cp_abs = context.transform.transform(cp_inst);
cp_abs = cp_abs.setY(0.0).setZ(0.0);
// if( 1e-6 < cp_inst.weight) {
// System.err.println("_________ cp:inst: (rel): " + cp_inst.toString());
// System.err.println("_________ cp:inst: (abs): " + cp_abs.toString());
// }
instanceForces.setCP(cp_abs);
double CN_instanced = instanceForces.getCN();
instanceForces.setCm(CN_instanced * instanceForces.getCP().x / conditions.getRefLength());
assemblyForces.merge(instanceForces);
}
assemblyForces.merge(componentForces);
}
}
// System.err.println("____ cp:asbly: " + assemblyForces.getCP().toString());
return assemblyForces;
}
@ -230,7 +267,7 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator {
}
private boolean testIsContinuous( final RocketComponent treeRoot ){
Queue<RocketComponent> queue = new LinkedList<RocketComponent>();
Queue<RocketComponent> queue = new LinkedList<>();
queue.addAll(treeRoot.getChildren());
boolean isContinuous = true;
@ -279,7 +316,7 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator {
* @param conditions Flight conditions taken into account
* @param map ?
* @param set Set to handle
* @return
* @return friction drag for entire rocket
*/
private double calculateFrictionDrag(FlightConfiguration configuration, FlightConditions conditions,
Map<RocketComponent, AerodynamicForces> map, WarningSet set) {
@ -510,12 +547,12 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator {
*
* @param configuration Rocket configuration
* @param conditions Flight conditions taken into account
* @param map ?
* @param set Set to handle
* @param forceMap
* @param warningSet all current warnings
* @return
*/
private double calculatePressureDrag(FlightConfiguration configuration, FlightConditions conditions,
Map<RocketComponent, AerodynamicForces> map, WarningSet warnings) {
Map<RocketComponent, AerodynamicForces> forceMap, WarningSet warningSet) {
double stagnation, base, total;
@ -538,11 +575,11 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator {
// Pressure fore drag
double cd = calcMap.get(c).calculatePressureDragForce(conditions, stagnation, base,
warnings);
warningSet);
total += cd;
if (map != null) {
map.get(c).setPressureCD(cd);
if (forceMap != null) {
forceMap.get(c).setPressureCD(cd);
}
if(c.isCDOverridden())
@ -562,8 +599,8 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator {
cd = stagnation * area / conditions.getRefArea();
total += cd;
if (map != null) {
map.get(c).setPressureCD(map.get(c).getPressureCD() + cd);
if (forceMap != null) {
forceMap.get(c).setPressureCD(forceMap.get(c).getPressureCD() + cd);
}
}
}
@ -580,7 +617,7 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator {
* @param configuration Rocket configuration
* @param conditions Flight conditions taken into account
* @param map ?
* @param set Set to handle
* @param warnings all current warnings
* @return
*/
private double calculateBaseDrag(FlightConfiguration configuration, FlightConditions conditions,
@ -752,10 +789,7 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator {
total.setPitchDampingMoment(MathUtil.sign(pitchRate) * pitchDampingMomentMagnitude);
total.setYawDampingMoment(MathUtil.sign(yawRate) * yawDampingMomentMagnitude);
}
// TODO: MEDIUM: Are the rotation etc. being added correctly? sin/cos theta?
private double getDampingMultiplier(FlightConfiguration configuration, FlightConditions conditions,
double cgx) {
if (cacheDiameter < 0) {
@ -811,18 +845,15 @@ public class BarrowmanCalculator extends AbstractAerodynamicCalculator {
private void buildCalcMap(FlightConfiguration configuration) {
Iterator<RocketComponent> iterator;
calcMap = new HashMap<RocketComponent, RocketComponentCalc>();
calcMap = new HashMap<>();
// because this is not a per-instance iteration... this usage of 'getActiveComponents' is probably fine.
for (RocketComponent comp: configuration.getActiveComponents()) {
if (!comp.isAerodynamic())
continue;
RocketComponentCalc calcObj = (RocketComponentCalc) Reflection.construct(BARROWMAN_PACKAGE, comp, BARROWMAN_SUFFIX, comp);
//String isNull = (null==calcObj?"null":"valid");
//System.err.println(" >> At component: "+c.getName() +"=="+c.getID()+". CalcObj is "+isNull);
calcMap.put(comp, calcObj );
}
}

View File

@ -0,0 +1,40 @@
package net.sf.openrocket.masscalc;
import net.sf.openrocket.motor.Motor;
import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.util.Coordinate;
public class CMAnalysisEntry {
public CMAnalysisEntry(final Object _src) {
source = _src;
if (source instanceof RocketComponent){
name = ((RocketComponent)source).getName();
}else if( source instanceof Motor ){
name = ((Motor) source).getDesignation();
}else {
name = null;
}
eachMass = Double.NaN;
totalCM = Coordinate.NaN;
}
public String name;
public Object source;
public double eachMass;
public Coordinate totalCM;
public void updateEachMass(final double newMass) {
if (Double.isNaN(eachMass)) {
eachMass = newMass;
}
}
public void updateAverageCM(final Coordinate newCM) {
if (this.totalCM.isNaN()) {
this.totalCM = newCM;
} else {
this.totalCM = totalCM.average(newCM);
}
}
}

View File

@ -1,12 +1,11 @@
package net.sf.openrocket.masscalc;
import java.util.ArrayList;
import java.util.Map;
import net.sf.openrocket.motor.Motor;
import net.sf.openrocket.motor.MotorConfiguration;
import net.sf.openrocket.rocketcomponent.FlightConfiguration;
import net.sf.openrocket.rocketcomponent.MotorMount;
import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.rocketcomponent.*;
import net.sf.openrocket.util.Coordinate;
import net.sf.openrocket.util.MathUtil;
import net.sf.openrocket.util.Transformation;
@ -73,8 +72,8 @@ public class MassCalculation {
}
}
public MassCalculation copy( final RocketComponent _root, final Transformation _transform ){
return new MassCalculation( this.type, this.config, this.simulationTime, _root, _transform );
public MassCalculation copy( final RocketComponent _root, final Transformation _transform){
return new MassCalculation( this.type, this.config, this.simulationTime, _root, _transform, this.analysisMap);
}
public Coordinate getCM() {
@ -110,13 +109,17 @@ public class MassCalculation {
return (int) (this.centerOfMass.hashCode());
}
public MassCalculation( final Type _type, final FlightConfiguration _config, final double _time, final RocketComponent _root, final Transformation _transform) {
public MassCalculation( final Type _type, final FlightConfiguration _config, final double _time,
final RocketComponent _root, final Transformation _transform,
Map<Integer, CMAnalysisEntry> _map)
{
type = _type;
config = _config;
simulationTime = _time;
root = _root;
transform = _transform;
analysisMap = _map;
reset();
}
@ -167,8 +170,10 @@ public class MassCalculation {
// center-of-mass AND moment-of-inertia data.
final ArrayList<RigidBody> bodies = new ArrayList<RigidBody>();
String prefix = "";
String prefix = "";
Map<Integer, CMAnalysisEntry> analysisMap;
// =========== Private Instance Functions ========================
@ -183,8 +188,8 @@ public class MassCalculation {
if( motorConfig.isEmpty() ){
return this;
}
final double mountXPosition = root.getPosition().x;
final int instanceCount = root.getInstanceCount();
@ -210,10 +215,10 @@ public class MassCalculation {
eachMass = eachMotorMass - eachCasingMass;
eachCMx = (eachMotorCMx*eachMotorMass - eachCasingCMx*eachCasingMass)/eachMass;
}
// System.err.println(String.format("%-40s|Motor: %s.... Mass @%f = %.6f", prefix, motorConfig.toDescription(), simulationTime, eachMass ));
// coordinates in rocket frame; Ir, It about CoM.
final Coordinate clusterLocalCM = new Coordinate( mountXPosition + motorXPosition + eachCMx, 0, 0, eachMass*instanceCount);
@ -232,7 +237,17 @@ public class MassCalculation {
final Coordinate clusterCM = transform.transform( clusterLocalCM );
addMass( clusterCM );
if(null != this.analysisMap) {
CMAnalysisEntry entry = analysisMap.get(motor.getDesignation());
if (null == entry){
entry = new CMAnalysisEntry(motor);
analysisMap.put(motor.getDesignation().hashCode(), entry);
}
entry.updateEachMass(eachMass);
entry.updateAverageCM(clusterCM);
}
RigidBody clusterMOI = new RigidBody( clusterCM, clusterIr, clusterIt, clusterIt );
addInertia( clusterMOI );
@ -273,7 +288,14 @@ public class MassCalculation {
// if( this.config.isComponentActive(component) ){
// System.err.println(String.format( "%s>>[%s]....", prefix, component.getName()));
// }
if(null != analysisMap) {
if (this.config.isComponentActive(component) && (! analysisMap.containsKey(component.hashCode()))){
CMAnalysisEntry entry = new CMAnalysisEntry(component);
analysisMap.put(component.hashCode(), entry);
}
}
// iterate over the aggregated instances for the whole tree.
MassCalculation children = this.copy(component, parentTransform );
for( int currentInstanceNumber = 0; currentInstanceNumber < instanceCount; ++currentInstanceNumber) {
@ -288,14 +310,14 @@ public class MassCalculation {
for (RocketComponent child : component.getChildren()) {
// child data, relative to rocket reference frame
MassCalculation eachChild = copy( child, currentTransform);
MassCalculation eachChild = copy(child, currentTransform);
eachChild.prefix = prefix + "....";
eachChild.calculateStructure();
// accumulate children's data
children.merge( eachChild );
}
}
}
if( MIN_MASS < children.getMass() ) {
@ -320,6 +342,20 @@ public class MassCalculation {
// mass data for *this component only* in the rocket-frame
compCM = parentTransform.transform( compCM.add(component.getPosition()) );
this.addMass( compCM );
if(null != analysisMap){
if( component instanceof ComponentAssembly) {
// For ComponentAssemblies, record the _assembly_ information
CMAnalysisEntry entry = analysisMap.get(component.hashCode());
entry.updateEachMass(children.getMass() / component.getInstanceCount());
entry.updateAverageCM(this.centerOfMass);
}else{
// For actual components, record the mass of the component, and disregard children
CMAnalysisEntry entry = analysisMap.get(component.hashCode());
entry.updateEachMass(compCM.weight);
entry.updateAverageCM(compCM);
}
}
double compIx = component.getRotationalUnitInertia() * compCM.weight;
double compIt = component.getLongitudinalUnitInertia() * compCM.weight;
@ -331,7 +367,7 @@ public class MassCalculation {
// System.err.println(String.format( "%s....componentData: %s", prefix, compCM.toPreciseString() ));
// }
if (component.getOverrideSubcomponents()) {
if (component.getOverrideSubcomponents()) {
if (component.isMassOverridden()) {
double newMass = MathUtil.max(component.getOverrideMass(), MIN_MASS);
Coordinate newCM = this.getCM().setWeight( newMass );
@ -355,29 +391,29 @@ public class MassCalculation {
}
MassCalculation calculateMotors() {
final RocketComponent component = this.root;
final Transformation parentTransform = this.transform;
final int instanceCount = component.getInstanceCount();
Coordinate[] instanceLocations = component.getInstanceLocations();
// // vvv DEBUG
// if( this.config.isComponentActive(component) ){
// System.err.println(String.format( "%s[%s]....", prefix, component.getName()));
// }
if (component.isMotorMount()) {
MassCalculation propellant = this.copy(component, parentTransform);
propellant.calculateMountData();
this.merge( propellant );
// // vvv DEBUG
// // vvv DEBUG
// if( 0 < propellant.getMass() ) {
// System.err.println(String.format( "%s........++ propellantData: %s", prefix, propellant.toCMDebug()));
// }
}
// iterate over the aggregated instances for the whole tree.
@ -395,7 +431,7 @@ public class MassCalculation {
// accumulate children's data
children.merge( eachChild );
}
}
}
if( MIN_MASS < children.getMass() ) {
@ -407,7 +443,7 @@ public class MassCalculation {
// if( this.config.isComponentActive(component) && 0 < this.getMass() ) {
// System.err.println(String.format( "%s....<< return assemblyData: %s (tree @%s)", prefix, this.toCMDebug(), component.getName() ));
// }
// ^^^ DEBUG
// // ^^^ DEBUG
return this;
}
@ -427,9 +463,8 @@ public class MassCalculation {
It += eachGlobal.Iyy;
}
return new RigidBody( centerOfMass, Ir, It, It );
return new RigidBody( centerOfMass, Ir, It, It );
}
}

View File

@ -7,10 +7,7 @@ import net.sf.openrocket.motor.Motor;
import net.sf.openrocket.rocketcomponent.FlightConfiguration;
import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.simulation.SimulationStatus;
import net.sf.openrocket.util.Coordinate;
import net.sf.openrocket.util.MathUtil;
import net.sf.openrocket.util.Monitorable;
import net.sf.openrocket.util.Transformation;
import net.sf.openrocket.util.*;
public class MassCalculator implements Monitorable {
@ -104,7 +101,7 @@ public class MassCalculator implements Monitorable {
public static RigidBody calculate( final MassCalculation.Type _type, final SimulationStatus status ){
final FlightConfiguration config = status.getConfiguration();
final double time = status.getSimulationTime();
MassCalculation calculation= new MassCalculation( _type, config, time, config.getRocket(), Transformation.IDENTITY);
MassCalculation calculation= new MassCalculation( _type, config, time, config.getRocket(), Transformation.IDENTITY, null);
calculation.calculateAssembly();
RigidBody result = calculation.calculateMomentOfInertia();
@ -112,55 +109,51 @@ public class MassCalculator implements Monitorable {
}
// convenience wrapper -- use this to implicitly create a plain MassCalculation object with common parameters
public static RigidBody calculate( final MassCalculation.Type _type, final FlightConfiguration _config, double _time ){
MassCalculation calculation = new MassCalculation( _type, _config, _time, _config.getRocket(), Transformation.IDENTITY);
public static RigidBody calculate( final MassCalculation.Type _type, final FlightConfiguration _config, double _time){
MassCalculation calculation = new MassCalculation( _type, _config, _time, _config.getRocket(), Transformation.IDENTITY, null);
calculation.calculateAssembly();
return calculation.calculateMomentOfInertia();
}
/**
* Compute an analysis of the per-component CG's of the provided configuration.
* The returned map will contain an entry for each physical rocket component (not stages)
* with its corresponding (best-effort) CG. Overriding of subcomponents is ignored.
* The CG of the entire configuration with motors is stored in the entry with the corresponding
* Rocket as the key.
*
*
* Deprecated:
* This function is fundamentally broken, because it asks for a calculation which ignores instancing.
* This function is fundamentally broken, because it asks for a calculation which ignores instancing.
* This function will work with simple rockets, but will be misleading or downright wrong for others.
*
* This is a problem with using a single-typed map:
*
* This is a problem with using a single-typed map:
* [1] multiple instances of components are not allowed, and must be merged.
* [2] propellant / motor data does not have a corresponding RocketComponent.
* ( or mount-data collides with motor-data )
* ( or mount-data collides with motor-data )
*
* @param configuration the rocket configuration
* @return a map from each rocket component to its corresponding CG.
* @return a list of CG coordinates for every instance of this component
*/
@Deprecated
public Map<RocketComponent, Coordinate> getCGAnalysis(FlightConfiguration configuration) {
// revalidateCache(configuration);
Map<RocketComponent, Coordinate> map = new HashMap<RocketComponent, Coordinate>();
Coordinate rocketCG = Coordinate.ZERO;
for (RocketComponent comp : configuration.getActiveComponents()) {
Coordinate[] cgs = comp.toAbsolute(comp.getCG());
Coordinate stageCG = Coordinate.NUL;
for (Coordinate cg : cgs) {
stageCG = stageCG.average(cg);
}
map.put(comp, stageCG);
rocketCG.average( stageCG);
}
map.put(configuration.getRocket(), rocketCG );
return map;
}
public static Map<Integer,CMAnalysisEntry> getCMAnalysis(FlightConfiguration config) {
Map<Integer,CMAnalysisEntry> analysisMap = new HashMap<>();
MassCalculation calculation = new MassCalculation(
MassCalculation.Type.LAUNCH,
config,
Motor.PSEUDO_TIME_LAUNCH,
config.getRocket(),
Transformation.IDENTITY,
analysisMap);
calculation.calculateAssembly();
CMAnalysisEntry totals = new CMAnalysisEntry(config.getRocket());
totals.totalCM = calculation.centerOfMass;
totals.eachMass = calculation.centerOfMass.weight;
analysisMap.put(config.getRocket().hashCode(), totals);
return analysisMap;
}
////////////////// Mass property calculations ///////////////////
@Override

View File

@ -202,23 +202,16 @@ public class FlightConfiguration implements FlightConfigurableParameter<FlightCo
}
public Collection<RocketComponent> getAllComponents() {
Queue<RocketComponent> toProcess = new ArrayDeque<RocketComponent>();
toProcess.offer(this.rocket);
ArrayList<RocketComponent> toReturn = new ArrayList<>();
while (!toProcess.isEmpty()) {
RocketComponent comp = toProcess.poll();
toReturn.add(comp);
for (RocketComponent child : comp.getChildren()) {
if (!(child instanceof AxialStage)) {
toProcess.offer(child);
}
}
List<RocketComponent> traversalOrder = new ArrayList<RocketComponent>();
recurseAllComponentsDepthFirst(this.rocket,traversalOrder);
return traversalOrder;
}
private void recurseAllComponentsDepthFirst(RocketComponent comp, List<RocketComponent> traversalOrder){
traversalOrder.add(comp);
for( RocketComponent child : comp.getChildren()){
recurseAllComponentsDepthFirst(child, traversalOrder);
}
return toReturn;
}
/** Returns all the components on core stages (i.e. centerline)
@ -401,10 +394,10 @@ public class FlightConfiguration implements FlightConfigurableParameter<FlightCo
}
private void updateStages() {
if (this.rocket.getStageCount() == this.stages.size()) {
return;
}
if (this.rocket.getStageCount() == this.stages.size()) {
return;
}
this.stages.clear();
for (AxialStage curStage : this.rocket.getStageList()) {

View File

@ -341,12 +341,9 @@ public class BarrowmanCalculatorTest {
final Coordinate cpNoPods = calcNoPods.getCP(configNoPods, conditionsNoPods, warningsNoPods);
final Coordinate cpPods = calcPods.getCP(configPods, conditionsPods, warningsPods);
System.out.printf("with pods %s\n", cpPods.toString());
System.out.printf("without pods %s\n", cpNoPods.toString());
assertEquals(" Alpha III With Pods rocket cp x value is incorrect:", cpNoPods.x - 0.002788761352, cpPods.x, EPSILON);
assertEquals(" Alpha III With Pods rocket cp y value is incorrect:", cpNoPods.y, cpPods.y, EPSILON);
assertEquals(" Alpha III With Pods rocket cp z value is incorrect:", cpNoPods.z, cpPods.z, EPSILON);
assertEquals(" Alpha III With Pods rocket CNa value is incorrect:", cpPods.weight, cpNoPods.weight - 3.91572, EPSILON);
}
}

View File

@ -19,7 +19,6 @@ import java.util.Vector;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
@ -54,21 +53,22 @@ import net.sf.openrocket.gui.components.UnitSelector;
import net.sf.openrocket.gui.scalefigure.RocketPanel;
import net.sf.openrocket.gui.util.GUIUtil;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.masscalc.CMAnalysisEntry;
import net.sf.openrocket.masscalc.MassCalculator;
import net.sf.openrocket.motor.MotorConfiguration;
import net.sf.openrocket.rocketcomponent.AxialStage;
import net.sf.openrocket.rocketcomponent.FinSet;
import net.sf.openrocket.rocketcomponent.FlightConfiguration;
import net.sf.openrocket.rocketcomponent.Rocket;
import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.rocketcomponent.*;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.unit.Unit;
import net.sf.openrocket.unit.UnitGroup;
import net.sf.openrocket.util.Coordinate;
import net.sf.openrocket.util.MathUtil;
import net.sf.openrocket.util.StateChangeListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ComponentAnalysisDialog extends JDialog implements StateChangeListener {
private static final Logger log = LoggerFactory.getLogger(ComponentAnalysisDialog.class);
private static final long serialVersionUID = 9131240570600307935L;
private static ComponentAnalysisDialog singletonDialog = null;
private static final Translator trans = Application.getTranslator();
@ -80,18 +80,16 @@ public class ComponentAnalysisDialog extends JDialog implements StateChangeListe
private final JToggleButton worstToggle;
private boolean fakeChange = false;
private AerodynamicCalculator aerodynamicCalculator;
private final MassCalculator massCalculator = new MassCalculator();
private final ColumnTableModel cpTableModel;
private final ColumnTableModel longitudeStabilityTableModel;
private final ColumnTableModel dragTableModel;
private final ColumnTableModel rollTableModel;
private final JList<Object> warningList;
private final List<Object[]> cgData = new ArrayList<Object[]>();
private final List<LongitudinalStabilityRow> stabData = new ArrayList<>();
private final List<AerodynamicForces> dragData = new ArrayList<AerodynamicForces>();
private double totalCD = 0;
private final List<AerodynamicForces> rollData = new ArrayList<AerodynamicForces>();
@ -186,69 +184,59 @@ public class ComponentAnalysisDialog extends JDialog implements StateChangeListe
panel.add(tabbedPane, "spanx, growx, growy");
// Create the CP data table
cpTableModel = new ColumnTableModel(
// Create the Longitudinal Stability (CM vs CP) data table
longitudeStabilityTableModel = new ColumnTableModel(
//// Component
new Column(trans.get("componentanalysisdlg.TabStability.Col.Component")) {
@Override
public Object getValueAt(int row) {
Object c = cgData.get(row)[0];
if (c instanceof Rocket) {
return trans.get("componentanalysisdlg.TOTAL");
}
Object c = stabData.get(row).name;
return c.toString();
}
@Override
public int getDefaultWidth() {
return 200;
}
},
new Column(trans.get("componentanalysisdlg.TabStability.Col.CG") + " / " + UnitGroup.UNITS_LENGTH.getDefaultUnit().getUnit()) {
private Unit unit = UnitGroup.UNITS_LENGTH.getDefaultUnit();
// would be per-instance mass
new Column(trans.get("componentanalysisdlg.TabStability.Col.EachMass") + " (" + UnitGroup.UNITS_MASS.getDefaultUnit().getUnit() + ")") {
final private Unit unit = UnitGroup.UNITS_MASS.getDefaultUnit();
@Override
public Object getValueAt(int row) {
Coordinate cg = (Coordinate) cgData.get(row)[1];
if ( cg == null ) {
return null;
}
return unit.toString(cg.x);
return unit.toString(stabData.get(row).eachMass);
}
},
new Column(trans.get("componentanalysisdlg.TabStability.Col.Mass") + " / " + UnitGroup.UNITS_MASS.getDefaultUnit().getUnit()) {
private Unit unit = UnitGroup.UNITS_MASS.getDefaultUnit();
new Column(trans.get("componentanalysisdlg.TabStability.Col.AllMass") + " (" + UnitGroup.UNITS_MASS.getDefaultUnit().getUnit() + ")") {
final private Unit unit = UnitGroup.UNITS_MASS.getDefaultUnit();
@Override
public Object getValueAt(int row) {
Coordinate cg = (Coordinate) cgData.get(row)[1];
if ( cg == null ) {
return null;
}
return unit.toString(cg.weight);
return unit.toString(stabData.get(row).cm.weight);
}
},
new Column(trans.get("componentanalysisdlg.TabStability.Col.CP") + " / " + UnitGroup.UNITS_LENGTH.getDefaultUnit().getUnit()) {
private Unit unit = UnitGroup.UNITS_LENGTH.getDefaultUnit();
new Column(trans.get("componentanalysisdlg.TabStability.Col.CG") + " (" + UnitGroup.UNITS_LENGTH.getDefaultUnit().getUnit() + ")") {
final private Unit unit = UnitGroup.UNITS_LENGTH.getDefaultUnit();
@Override
public Object getValueAt(int row) {
AerodynamicForces forces = (AerodynamicForces) cgData.get(row)[2];
if ( forces == null ) {
return null;
}
return unit.toString(forces.getCP().x);
return unit.toString(stabData.get(row).cm.x);
}
},
new Column(trans.get("componentanalysisdlg.TabStability.Col.CP") + " (" + UnitGroup.UNITS_LENGTH.getDefaultUnit().getUnit() + ")") {
final private Unit unit = UnitGroup.UNITS_LENGTH.getDefaultUnit();
@Override
public Object getValueAt(int row) {
return NOUNIT.toString(stabData.get(row).cpx);
}
},
new Column("<html>C<sub>N<sub>" + ALPHA + "</sub></sub>") {
@Override
public Object getValueAt(int row) {
AerodynamicForces forces = (AerodynamicForces) cgData.get(row)[2];
if ( forces == null ) {
return null;
}
return NOUNIT.toString(forces.getCP().weight);
return NOUNIT.toString(stabData.get(row).cna);
}
}
@ -261,15 +249,15 @@ public class ComponentAnalysisDialog extends JDialog implements StateChangeListe
@Override
public int getRowCount() {
return cgData.size();
return stabData.size();
}
};
table = new ColumnTable(cpTableModel);
table = new ColumnTable(longitudeStabilityTableModel);
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
table.setSelectionBackground(Color.LIGHT_GRAY);
table.setSelectionForeground(Color.BLACK);
cpTableModel.setColumnWidths(table.getColumnModel());
longitudeStabilityTableModel.setColumnWidths(table.getColumnModel());
table.setDefaultRenderer(Object.class, new CustomCellRenderer());
// table.setShowHorizontalLines(false);
@ -425,8 +413,6 @@ public class ComponentAnalysisDialog extends JDialog implements StateChangeListe
// Add the data updater to listen to changes in aoa and theta
mach.addChangeListener(this);
theta.addChangeListener(this);
@ -534,80 +520,97 @@ public class ComponentAnalysisDialog extends JDialog implements StateChangeListe
}
}
Map<RocketComponent, AerodynamicForces> aeroData =
aerodynamicCalculator.getForceAnalysis(configuration, conditions, set);
Map<RocketComponent, Coordinate> massData =
massCalculator.getCGAnalysis(configuration);
// key is the comp.hashCode() or motor.getDesignation().hashCode()
Map<Integer, CMAnalysisEntry> cmMap= MassCalculator.getCMAnalysis(configuration);
Map<RocketComponent, AerodynamicForces> aeroData = aerodynamicCalculator.getForceAnalysis(configuration, conditions, set);
cgData.clear();
stabData.clear();
dragData.clear();
rollData.clear();
for (RocketComponent c : configuration.getActiveComponents()) {
if ( c instanceof AxialStage ) {
continue;
}
Object[] data = new Object[3];
cgData.add(data);
data[0] = c;
Coordinate cg = massData.get(c);
data[1] = cg;
forces = aeroData.get(c);
if (forces == null) {
for(final RocketComponent comp: configuration.getAllComponents()) {
// // this is actually redundant, because the analysis will not contain inactive stages.
// if (!configuration.isComponentActive(comp)) {
// continue;
// }
CMAnalysisEntry cmEntry = cmMap.get(comp.hashCode());
if (null == cmEntry) {
log.warn("Could not find massData entry for component: " + comp.getName());
continue;
}
if (forces.getCP() != null) {
data[2] = forces;
if ((comp instanceof ComponentAssembly) && !(comp instanceof Rocket)){
continue;
}
LongitudinalStabilityRow row = new LongitudinalStabilityRow(cmEntry.name, cmEntry.source);
stabData.add(row);
row.source = cmEntry.source;
row.eachMass = cmEntry.eachMass;
row.cm = cmEntry.totalCM;
forces = aeroData.get(comp);
if (forces == null) {
// DEBUG / trace level
System.err.println("Could not find aeroData entry for component: " + comp.getName());
row.cpx = 0.0;
row.cna = 0.0;
continue;
}
// System.err.println(String.format(" .CP = %s", forces.getCP()));
if (forces.getCP() != null) {
row.cpx = forces.getCP().x;
row.cna = forces.getCNa();
}
// System.err.println(String.format(" .CD = %s", forces.getCD()));
if (!Double.isNaN(forces.getCD())) {
dragData.add(forces);
}
if (c instanceof FinSet) {
if (comp instanceof FinSet) {
rollData.add(forces);
}
// // We _would_ check this, except TubeFinSet doesn't implement cant angles... so they can't impart any roll torque
// // If this is ever implemented, uncomment this block:
// else if(comp instanceof TubeFinSet){
// rollData.add(forces)
// }
}
for ( MotorConfiguration motorConfig : configuration.getActiveMotors()) {
Object [] data = new Object[3];
cgData.add(data);
data[0] = motorConfig.getMotor().getDesignation();
data[1] = motorConfig.getMotor().getLaunchMass();
}
forces = aeroData.get(rkt);
if (forces != null) {
Object[] data = new Object[3];
cgData.add(data);
data[0] = rkt;
data[1] = massData.get(rkt);
data[2] = forces;
dragData.add(forces);
rollData.add(forces);
totalCD = forces.getCD();
} else {
totalCD = 0;
for(final MotorConfiguration config: configuration.getActiveMotors()) {
CMAnalysisEntry cmEntry = cmMap.get(config.getMotor().getDesignation());
if (null == cmEntry) {
continue;
}
LongitudinalStabilityRow row = new LongitudinalStabilityRow(cmEntry.name, cmEntry.source);
stabData.add(row);
row.source = cmEntry.source;
row.eachMass = cmEntry.eachMass;
row.cm = cmEntry.totalCM;
row.cpx = 0.0;
row.cna = 0.0;
}
// Set warnings
if (set.isEmpty()) {
warningList.setListData(new String[] {
trans.get("componentanalysisdlg.noWarnings")
warningList.setListData(new String[] {trans.get("componentanalysisdlg.noWarnings")
});
} else {
warningList.setListData(new Vector<Warning>(set));
}
cpTableModel.fireTableDataChanged();
longitudeStabilityTableModel.fireTableDataChanged();
dragTableModel.fireTableDataChanged();
rollTableModel.fireTableDataChanged();
}
private class CustomCellRenderer extends JLabel implements TableCellRenderer {
/**
*
@ -628,10 +631,10 @@ public class ComponentAnalysisDialog extends JDialog implements StateChangeListe
this.setText(value == null ? null : value.toString());
if ((row < 0) || (row >= cgData.size()))
if ((row < 0) || (row >= stabData.size()))
return this;
if (cgData.get(row)[0] instanceof Rocket) {
if ( 0 == row ) {
this.setFont(boldFont);
} else {
this.setFont(normalFont);
@ -662,6 +665,7 @@ public class ComponentAnalysisDialog extends JDialog implements StateChangeListe
boolean isSelected, boolean hasFocus, int row, int column) {
if (value instanceof Double) {
final double totalCD = dragData.get(0).getCD();
// A drag coefficient
double cd = (Double) value;
@ -698,6 +702,25 @@ public class ComponentAnalysisDialog extends JDialog implements StateChangeListe
}
}
private class LongitudinalStabilityRow {
public String name;
public Object source;
public double eachMass;
public Coordinate cm;
public double cpx;
public double cna;
public LongitudinalStabilityRow(final String _name, final Object _source){
name = _name;
source = _source;
eachMass = Double.NaN;
cm = Coordinate.NaN;
cpx = Double.NaN;
cna = Double.NaN;
}
}
///////// Singleton implementation

View File

@ -682,19 +682,18 @@ public class BasicFrame extends JFrame {
menubar.add(menu);
// TODO: reimplement this
// //// Component analysis
// item = new JMenuItem(trans.get("main.menu.analyze.componentAnalysis"), KeyEvent.VK_C);
// //// Analyze the rocket components separately
// item.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.analyze.componentAnalysis.desc"));
// item.addActionListener(new ActionListener() {
// @Override
// public void actionPerformed(ActionEvent e) {
// log.info(Markers.USER_MARKER, "Component analysis selected");
// ComponentAnalysisDialog.showDialog(rocketpanel);
// }
// });
// menu.add(item);
//// Component analysis
item = new JMenuItem(trans.get("main.menu.analyze.componentAnalysis"), KeyEvent.VK_C);
//// Analyze the rocket components separately
item.getAccessibleContext().setAccessibleDescription(trans.get("main.menu.analyze.componentAnalysis.desc"));
item.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
log.info(Markers.USER_MARKER, "Component analysis selected");
ComponentAnalysisDialog.showDialog(rocketpanel);
}
});
menu.add(item);
// TODO: reimplement this dialog
// //// Optimize