From 9f5d4a2d27d554709353319fa0447b85ee90ab60 Mon Sep 17 00:00:00 2001 From: Daniel_M_Williams Date: Mon, 25 May 2020 17:49:10 -0400 Subject: [PATCH] [enable] Re-enables, fixes ComponentAnalysisDialog --- core/resources/l10n/messages.properties | 7 +- .../aerodynamics/BarrowmanCalculator.java | 265 ++++++++++-------- .../openrocket/masscalc/CMAnalysisEntry.java | 40 +++ .../openrocket/masscalc/MassCalculation.java | 91 ++++-- .../openrocket/masscalc/MassCalculator.java | 67 ++--- .../rocketcomponent/FlightConfiguration.java | 33 +-- .../aerodynamics/BarrowmanCalculatorTest.java | 3 - .../gui/dialogs/ComponentAnalysisDialog.java | 219 ++++++++------- .../sf/openrocket/gui/main/BasicFrame.java | 25 +- 9 files changed, 431 insertions(+), 319 deletions(-) create mode 100644 core/src/net/sf/openrocket/masscalc/CMAnalysisEntry.java diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties index 063a25506..4dd306657 100644 --- a/core/resources/l10n/messages.properties +++ b/core/resources/l10n/messages.properties @@ -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 diff --git a/core/src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java b/core/src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java index 2ce4b1045..d88b2fe2e 100644 --- a/core/src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java +++ b/core/src/net/sf/openrocket/aerodynamics/BarrowmanCalculator.java @@ -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 getForceAnalysis(FlightConfiguration configuration, - FlightConditions conditions, WarningSet warnings) { - checkCache(configuration); - - AerodynamicForces f; - Map map = - new LinkedHashMap(); - - // 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 eachMap = new LinkedHashMap<>(); + Map 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 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 eachForces, + Map 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 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 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 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> entry: imap.entrySet() ) { - final RocketComponent comp = entry.getKey(); + for(Map.Entry> mapEntry: imap.entrySet() ) { + final RocketComponent comp = mapEntry.getKey(); + final List contextList = mapEntry.getValue(); + RocketComponentCalc calcObj = calcMap.get(comp); - - // System.err.println("comp=" + comp); if (null != calcObj) { - // iterate across component instances - final ArrayList 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 queue = new LinkedList(); + Queue 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 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 map, WarningSet warnings) { + Map 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 iterator; - - calcMap = new HashMap(); + 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 ); } } diff --git a/core/src/net/sf/openrocket/masscalc/CMAnalysisEntry.java b/core/src/net/sf/openrocket/masscalc/CMAnalysisEntry.java new file mode 100644 index 000000000..8bd77cc2e --- /dev/null +++ b/core/src/net/sf/openrocket/masscalc/CMAnalysisEntry.java @@ -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); + } + } +} diff --git a/core/src/net/sf/openrocket/masscalc/MassCalculation.java b/core/src/net/sf/openrocket/masscalc/MassCalculation.java index 7a4ce4f78..fed4848d8 100644 --- a/core/src/net/sf/openrocket/masscalc/MassCalculation.java +++ b/core/src/net/sf/openrocket/masscalc/MassCalculation.java @@ -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 _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 bodies = new ArrayList(); - - String prefix = ""; + + String prefix = ""; + + Map 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 ); } - } diff --git a/core/src/net/sf/openrocket/masscalc/MassCalculator.java b/core/src/net/sf/openrocket/masscalc/MassCalculator.java index 08215cd4d..6036f853a 100644 --- a/core/src/net/sf/openrocket/masscalc/MassCalculator.java +++ b/core/src/net/sf/openrocket/masscalc/MassCalculator.java @@ -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 getCGAnalysis(FlightConfiguration configuration) { - // revalidateCache(configuration); - - Map map = new HashMap(); - - 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 getCMAnalysis(FlightConfiguration config) { + Map 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 diff --git a/core/src/net/sf/openrocket/rocketcomponent/FlightConfiguration.java b/core/src/net/sf/openrocket/rocketcomponent/FlightConfiguration.java index 239e03d04..bd9298e0d 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/FlightConfiguration.java +++ b/core/src/net/sf/openrocket/rocketcomponent/FlightConfiguration.java @@ -202,23 +202,16 @@ public class FlightConfiguration implements FlightConfigurableParameter getAllComponents() { - Queue toProcess = new ArrayDeque(); - toProcess.offer(this.rocket); - - ArrayList 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 traversalOrder = new ArrayList(); + recurseAllComponentsDepthFirst(this.rocket,traversalOrder); + return traversalOrder; + } + + private void recurseAllComponentsDepthFirst(RocketComponent comp, List 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 warningList; - private final List cgData = new ArrayList(); + private final List stabData = new ArrayList<>(); private final List dragData = new ArrayList(); - private double totalCD = 0; private final List rollData = new ArrayList(); @@ -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("CN" + ALPHA + "") { @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 aeroData = - aerodynamicCalculator.getForceAnalysis(configuration, conditions, set); - Map massData = - massCalculator.getCGAnalysis(configuration); + // key is the comp.hashCode() or motor.getDesignation().hashCode() + Map cmMap= MassCalculator.getCMAnalysis(configuration); + Map 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(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 diff --git a/swing/src/net/sf/openrocket/gui/main/BasicFrame.java b/swing/src/net/sf/openrocket/gui/main/BasicFrame.java index b3b2505d9..04a1c43dc 100644 --- a/swing/src/net/sf/openrocket/gui/main/BasicFrame.java +++ b/swing/src/net/sf/openrocket/gui/main/BasicFrame.java @@ -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