diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties index c5929d0bb..135ad0ed8 100644 --- a/core/resources/l10n/messages.properties +++ b/core/resources/l10n/messages.properties @@ -930,6 +930,12 @@ RocketCompCfg.checkbox.OverrideSubcomponents = Override for all subcomponents RocketCompCfg.checkbox.OverrideSubcomponents.Mass.ttip = Overrides the aggregate mass of this component
and its subcomponents with the mass of this component. RocketCompCfg.checkbox.OverrideSubcomponents.CG.ttip = Overrides the center of gravity (CG) of this component
and its subcomponents with the CG of this component. RocketCompCfg.checkbox.OverrideSubcomponents.CD.ttip = Overrides the coefficient of drag (CD) of this component
and its subcomponents with the CD of this component. +RocketCompCfg.lbl.MassOverriddenBy = Mass overridden by %s +RocketCompCfg.lbl.CGOverriddenBy = CG overridden by %s +RocketCompCfg.lbl.CDOverriddenBy = CD overridden by %s +RocketCompCfg.lbl.MassOverriddenBy.ttip = The mass of this component is determined by the mass override value of %s +RocketCompCfg.lbl.CGOverriddenBy.ttip = The CG of this component is determined by the CG override value of %s +RocketCompCfg.lbl.CDOverriddenBy.ttip = The CD of this component is determined by the CD override value of %s RocketCompCfg.lbl.longB1 = The overridden mass and center of gravity does not include motors.
RocketCompCfg.lbl.longB2 = The center of gravity is measured from the front end of the RocketCompCfg.lbl.Commentsonthe = Comments on the @@ -1354,10 +1360,13 @@ PlotDialog.CheckBox.Showdatapoints = Show data points PlotDialog.lbl.Chart = left click drag to zoom area. mouse wheel to zoom. ctrl-mouse wheel to zoom x axis only. ctrl-left click drag to pan. right click drag to zoom dynamically. PlotDialog.btn.exportImage = Export Image -ComponentTree.ttip.massoverride = mass override -ComponentTree.ttip.cgoverride = cg override +ComponentTree.ttip.massoverride = mass overriden +ComponentTree.ttip.cgoverride = CG overriden +ComponentTree.ttip.cdoverride = CD overriden ! "main" prefix is used for the main application dialog +ComponentTreeRenderer.total = total + # FIXME: Rename the description keys main.menu.file = File diff --git a/core/resources/l10n/messages_de.properties b/core/resources/l10n/messages_de.properties index e2cda7b33..11418eb82 100644 --- a/core/resources/l10n/messages_de.properties +++ b/core/resources/l10n/messages_de.properties @@ -1024,6 +1024,7 @@ PlotDialog.Chart.Simulatedflight = Simulierter Flug PlotDialog.CheckBox.Showdatapoints = Datenpunkte anzeigen PlotDialog.lbl.Chart = Klicken+ziehen: runter+rechts um hinein zu zoomen, hoch+links um heraus zu zoomen +ComponentTreeRenderer.total = gesamt ! "main" prefix is used for the main application dialog diff --git a/core/resources/l10n/messages_es.properties b/core/resources/l10n/messages_es.properties index aec98d9c5..48a1df482 100644 --- a/core/resources/l10n/messages_es.properties +++ b/core/resources/l10n/messages_es.properties @@ -732,6 +732,8 @@ PlotDialog.CheckBox.Showdatapoints = Mostrar los datos de los puntos PlotDialog.lbl.Chart = Arastrar con bot\u00f3n-izq rat\u00f3n para zoom del \u00e1rea. Rueda rat\u00f3n para zoom. Ctrl+rueda rat\u00f3n para zoom eje x. Ctrl+arrastrar con bot\u00f3n-izq rat\u00f3n para desplazar. Arrastrar con bot\u00f3n-der para zoom din\u00e1mico PlotDialog.title.Flightdataplot = Representaci\u00f3n de los datos de vuelo +ComponentTreeRenderer.total = total + PreferencesDialog.languages.default = Idioma por defecto PreferencesDialog.lbl.language = Idioma de la interfaz: PreferencesDialog.lbl.languageEffect = El idioma cambiar\u00e1 la pr\u00f3xima vez que abra OpenRocket. diff --git a/core/resources/l10n/messages_fr.properties b/core/resources/l10n/messages_fr.properties index 0e9ea5cdb..8eed74a8f 100644 --- a/core/resources/l10n/messages_fr.properties +++ b/core/resources/l10n/messages_fr.properties @@ -724,6 +724,8 @@ PlotDialog.lbl.Chart = Cliquer+d\u00E9placer en bas +droite pour a ! PlotDialog PlotDialog.title.Flightdataplot = Trac\u00E9 du vol +ComponentTreeRenderer.total = total + PreferencesDialog.languages.default = Valeur syst\u00E8me par d\u00E9faut PreferencesDialog.lbl.language = Langue du programme: PreferencesDialog.lbl.languageEffect = La langue sera chang\u00E9e apr\u00E8s avoir red\u00E9marr\u00E9 OpenRocket. diff --git a/core/resources/l10n/messages_it.properties b/core/resources/l10n/messages_it.properties index f8b4cb3f0..897a2f29b 100644 --- a/core/resources/l10n/messages_it.properties +++ b/core/resources/l10n/messages_it.properties @@ -1025,6 +1025,7 @@ PlotDialog.Chart.Simulatedflight = Volo simulato PlotDialog.CheckBox.Showdatapoints = Mostra i punti PlotDialog.lbl.Chart = Clicca e trascina giu'-dx per ingrandire, su-sx per rimpicciolire +ComponentTreeRenderer.total = totali ! "main" prefix is used for the main application dialog diff --git a/core/resources/l10n/messages_nl.properties b/core/resources/l10n/messages_nl.properties index 08dccf2ca..f30d5c91e 100644 --- a/core/resources/l10n/messages_nl.properties +++ b/core/resources/l10n/messages_nl.properties @@ -1224,10 +1224,12 @@ TCMotorSelPan.btn.close = Sluit PlotDialog.CheckBox.Showdatapoints = Toon datapunten PlotDialog.lbl.Chart = Linksklik+sleep om in te zoomen op omgeving. Muiswiel om te zoomen. Ctrl-muiswiel om enkel x-as te zoomen. Ctrl-linksklik om te bewegen. Rechtsklik+sleep om dynamisch te zoomen. -ComponentTree.ttip.massoverride = Massa overschrijven +ComponentTree.ttip.massoverride = Massa overschreven ComponentTree.ttip.cgoverride = ZP overschrijven ! "main" prefix is used for the main application dialog +ComponentTreeRenderer.total = totaal + # FIXME: Rename the description keys main.menu.file = Bestand diff --git a/core/resources/pix/icons/cd-override-subcomponent.png b/core/resources/pix/icons/cd-override-subcomponent.png new file mode 100644 index 000000000..228cb95f3 Binary files /dev/null and b/core/resources/pix/icons/cd-override-subcomponent.png differ diff --git a/core/resources/pix/icons/cg-override-subcomponent.png b/core/resources/pix/icons/cg-override-subcomponent.png new file mode 100644 index 000000000..1107ec954 Binary files /dev/null and b/core/resources/pix/icons/cg-override-subcomponent.png differ diff --git a/core/resources/pix/icons/mass-override-subcomponent.png b/core/resources/pix/icons/mass-override-subcomponent.png new file mode 100755 index 000000000..5b09443d7 Binary files /dev/null and b/core/resources/pix/icons/mass-override-subcomponent.png differ diff --git a/core/src/net/sf/openrocket/rocketcomponent/ComponentChangeEvent.java b/core/src/net/sf/openrocket/rocketcomponent/ComponentChangeEvent.java index f74798f6a..b1aaf1777 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/ComponentChangeEvent.java +++ b/core/src/net/sf/openrocket/rocketcomponent/ComponentChangeEvent.java @@ -14,8 +14,9 @@ public class ComponentChangeEvent extends EventObject { UNDO( 16, "UNDO"), MOTOR( 32, "Motor"), EVENT( 64, "Event"), - TEXTURE ( 128, "Texture") - , GRAPHIC( 256, "Configuration") + TEXTURE ( 128, "Texture"), + GRAPHIC( 256, "Configuration"), + TREE_CHILDREN( 512, "TREE_CHILDREN"), ; protected int value; @@ -45,6 +46,8 @@ public class ComponentChangeEvent extends EventObject { /** A change that affects the rocket tree structure */ public static final int TREE_CHANGE = TYPE.TREE.value; + /** A change that affects the children's tree structure */ + public static final int TREE_CHANGE_CHILDREN = TYPE.TREE_CHILDREN.value; /** A change caused by undo/redo. */ public static final int UNDO_CHANGE = TYPE.UNDO.value; /** A change in the motor configurations or names */ @@ -124,6 +127,9 @@ public class ComponentChangeEvent extends EventObject { public boolean isTreeChange() { return TYPE.TREE.matches(this.type); } + public boolean isTreeChildrenChange() { + return TYPE.TREE_CHILDREN.matches(this.type); + } public boolean isUndoChange() { return TYPE.UNDO.matches(this.type); @@ -150,6 +156,8 @@ public class ComponentChangeEvent extends EventObject { s += ",aero"; if (isTreeChange()) s += ",tree"; + if (isTreeChildrenChange()) + s += ",treechild"; if (isUndoChange()) s += ",undo"; if (isMotorChange()) diff --git a/core/src/net/sf/openrocket/rocketcomponent/RocketComponent.java b/core/src/net/sf/openrocket/rocketcomponent/RocketComponent.java index 11f2fbb1a..3309e354e 100644 --- a/core/src/net/sf/openrocket/rocketcomponent/RocketComponent.java +++ b/core/src/net/sf/openrocket/rocketcomponent/RocketComponent.java @@ -96,18 +96,23 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab private LineStyle lineStyle = null; - // Override mass/CG/CD + // Override mass protected double overrideMass = 0; protected boolean massOverridden = false; private boolean overrideSubcomponentsMass = false; - + private RocketComponent massOverriddenBy = null; // The (super-)parent component that overrides the mass of this component + + // Override CG private double overrideCGX = 0; private boolean cgOverridden = false; private boolean overrideSubcomponentsCG = false; - + private RocketComponent CGOverriddenBy = null; // The (super-)parent component that overrides the CG of this component + + // Override CD private double overrideCD = 0; private boolean cdOverridden = false; private boolean overrideSubcomponentsCD = false; + private RocketComponent CDOverriddenBy = null; // The (super-)parent component that overrides the CD of this component private boolean cdOverriddenByAncestor = false; @@ -606,6 +611,7 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab } checkState(); massOverridden = o; + updateChildrenMassOverriddenBy(); fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE); } @@ -680,6 +686,7 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab } checkState(); cgOverridden = o; + updateChildrenCGOverriddenBy(); fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE); } @@ -746,6 +753,7 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab } checkState(); cdOverridden = o; + updateChildrenCDOverriddenBy(); // if overrideSubcompoents is set, we need to descend the component // tree. If we are overriding our own CD, we need to override all @@ -784,7 +792,7 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab return overrideSubcomponentsMass; } - // TODO: delete no compatibility is needed anymore with OR 15.03 + // TODO: delete when compatibility with OR 15.03 is not needed anymore public void setSubcomponentsOverridden(boolean override) { setSubcomponentsOverriddenMass(override); setSubcomponentsOverriddenCG(override); @@ -809,7 +817,9 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab checkState(); overrideSubcomponentsMass = override; - fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE); + updateChildrenMassOverriddenBy(); + + fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE | ComponentChangeEvent.TREE_CHANGE_CHILDREN); } /** @@ -845,7 +855,9 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab checkState(); overrideSubcomponentsCG = override; - fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE); + updateChildrenCGOverriddenBy(); + + fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE | ComponentChangeEvent.TREE_CHANGE_CHILDREN); } /** @@ -881,9 +893,11 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab checkState(); overrideSubcomponentsCD = override; + updateChildrenCDOverriddenBy(); + overrideSubcomponentsCD(override); - fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE); + fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE | ComponentChangeEvent.TREE_CHANGE_CHILDREN); } /** @@ -931,8 +945,62 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab mutex.verify(); return isCGOverridden() || isMassOverridden() || isCDOverridden(); } - - /** + + /** + * Returns which (super-)parent overrides the mass of this component, or null if no parent does so. + */ + public RocketComponent getMassOverriddenBy() { + return massOverriddenBy; + } + + /** + * Returns which (super-)parent overrides the CG of this component, or null if no parent does so. + */ + public RocketComponent getCGOverriddenBy() { + return CGOverriddenBy; + } + + /** + * Returns which (super-)parent overrides the CD of this component, or null if no parent does so. + */ + public RocketComponent getCDOverriddenBy() { + return CDOverriddenBy; + } + + private void updateChildrenMassOverriddenBy() { + RocketComponent overriddenBy = massOverridden && overrideSubcomponentsMass ? this : null; + for (RocketComponent c : getAllChildren()) { + c.massOverriddenBy = overriddenBy; + // We need to update overriddenBy in case one of the children components has its subcomponents overridden + if (overriddenBy == null) { + overriddenBy = c.massOverridden && c.overrideSubcomponentsMass ? c : null; + } + } + } + + private void updateChildrenCGOverriddenBy() { + RocketComponent overriddenBy = cgOverridden && overrideSubcomponentsCG ? this : null; + for (RocketComponent c : getAllChildren()) { + c.CGOverriddenBy = overriddenBy; + // We need to update overriddenBy in case one of the children components has its subcomponents overridden + if (overriddenBy == null) { + overriddenBy = c.cgOverridden && c.overrideSubcomponentsCG ? c : null; + } + } + } + + private void updateChildrenCDOverriddenBy() { + RocketComponent overriddenBy = cdOverridden && overrideSubcomponentsCD ? this : null; + for (RocketComponent c : getAllChildren()) { + c.CDOverriddenBy = overriddenBy; + // We need to update overriddenBy in case one of the children components has its subcomponents overridden + if (overriddenBy == null) { + overriddenBy = c.cdOverridden && c.overrideSubcomponentsCD ? c : null; + } + } + } + + /** * placeholder. This allows code to generally test if this component represents multiple instances with just one function call. * * @return number of instances @@ -1534,6 +1602,9 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab */ public final double getSectionMass() { Double massSubtotal = getMass(); + if (massOverridden && overrideSubcomponentsMass) { + return massSubtotal; + } mutex.verify(); for (RocketComponent rc : children) { massSubtotal += rc.getSectionMass(); @@ -1639,6 +1710,36 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab children.add(index, component); component.parent = this; + if (this.massOverridden && this.overrideSubcomponentsMass) { + component.massOverriddenBy = this; + } else { + component.massOverriddenBy = this.massOverriddenBy; + } + if (this.cgOverridden && this.overrideSubcomponentsCG) { + component.CGOverriddenBy = this; + } else { + component.CGOverriddenBy = this.CGOverriddenBy; + } + if (this.cdOverridden && this.overrideSubcomponentsCD) { + component.CDOverriddenBy = this; + } else { + component.CDOverriddenBy = this.CDOverriddenBy; + } + for (Iterator it = component.iterator(false); it.hasNext(); ) { + RocketComponent child = it.next(); + // You only want to change the overriddenBy if the overriddenBy of component changed (i.e. is not null), + // otherwise you could lose overriddenBy information of the sub-children that have one of this component's + // children as its overrideBy component. + if (component.massOverriddenBy != null) { + child.massOverriddenBy = component.massOverriddenBy; + } + if (component.CGOverriddenBy != null) { + child.CGOverriddenBy = component.CGOverriddenBy; + } + if (component.CDOverriddenBy != null) { + child.CDOverriddenBy = component.CDOverriddenBy; + } + } if (component instanceof AxialStage) { AxialStage nStage = (AxialStage) component; @@ -1660,7 +1761,7 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab */ public final void removeChild(int n) { checkState(); - RocketComponent component = this.getChild(n); + RocketComponent component = this.getChild(n); this.removeChild(component); } @@ -1679,6 +1780,20 @@ public abstract class RocketComponent implements ChangeSource, Cloneable, Iterab if (children.remove(component)) { component.parent = null; + for (RocketComponent c : component) { + // You only want to set the override components to null if the child's override component is either + // this component, or a (super-)parent of this component. Otherwise, you could lose the overrideBy + // information of sub-children that have one of this component's children as its overrideBy component. + if (c.massOverriddenBy == this || c.massOverriddenBy == this.massOverriddenBy) { + c.massOverriddenBy = null; + } + if (c.CGOverriddenBy == this || c.CGOverriddenBy == this.CGOverriddenBy) { + c.CGOverriddenBy = null; + } + if (c.CDOverriddenBy == this || c.CDOverriddenBy == this.CDOverriddenBy) { + c.CDOverriddenBy = null; + } + } if (component instanceof AxialStage) { AxialStage stage = (AxialStage) component; diff --git a/core/src/net/sf/openrocket/util/Color.java b/core/src/net/sf/openrocket/util/Color.java index d74032a3f..59582160c 100644 --- a/core/src/net/sf/openrocket/util/Color.java +++ b/core/src/net/sf/openrocket/util/Color.java @@ -4,6 +4,7 @@ public class Color { public static Color BLACK = new Color(255,255,255); public static Color INVISIBLE = new Color(1, 1, 1, 0); + public static Color DARK_RED = new Color(200, 0, 0); private int red; private int green; @@ -60,5 +61,9 @@ public class Color { public String toString() { return "Color [r=" + red + ", g=" + green + ", b=" + blue + ", a=" + alpha + "]"; } + + public java.awt.Color toAWTColor() { + return new java.awt.Color(red, green, blue, alpha); + } } diff --git a/core/test/net/sf/openrocket/masscalc/MassCalculatorTest.java b/core/test/net/sf/openrocket/masscalc/MassCalculatorTest.java index 6c89ed6eb..977feb3c6 100644 --- a/core/test/net/sf/openrocket/masscalc/MassCalculatorTest.java +++ b/core/test/net/sf/openrocket/masscalc/MassCalculatorTest.java @@ -4,10 +4,12 @@ import static org.junit.Assert.assertEquals; import java.util.List; +import net.sf.openrocket.document.OpenRocketDocumentFactory; import net.sf.openrocket.rocketcomponent.*; import net.sf.openrocket.rocketcomponent.position.AngleMethod; import net.sf.openrocket.rocketcomponent.position.AxialMethod; import net.sf.openrocket.rocketcomponent.position.RadiusMethod; +import net.sf.openrocket.util.MathUtil; import org.junit.Test; import net.sf.openrocket.motor.Motor; @@ -377,10 +379,12 @@ public class MassCalculatorTest extends BaseTestCase { assertEquals(0.0, sustainerBody.getPosition().x, EPSILON); assertEquals(0.1, sustainerBody.getLength(), EPSILON); assertEquals(expSingleBodyMass, sustainerBody.getMass(), EPSILON); + assertEquals(expSingleBodyMass, sustainerBody.getSectionMass(), EPSILON); assertEquals(0.10, boosterBody.getComponentLocations()[0].x, EPSILON); assertEquals(0.10, boosterBody.getLength(), EPSILON); assertEquals(expSingleBodyMass, boosterBody.getMass(), EPSILON); + assertEquals(expSingleBodyMass, boosterBody.getSectionMass(), EPSILON); } { // [1] test Rocket CM, before: @@ -397,13 +401,16 @@ public class MassCalculatorTest extends BaseTestCase { boosterBody.setSubcomponentsOverriddenMass(false); boosterBody.setMassOverridden(true); - boosterBody.setOverrideMass(0.001); + double newMass = 0.001; + boosterBody.setOverrideMass(newMass); { // [1] test Rocket CM, after: final RigidBody actualStructure = MassCalculator.calculateStructure(config); final double actualRocketDryMass = actualStructure.cm.weight; - assertEquals(expSingleBodyMass+0.001, actualRocketDryMass, EPSILON); + assertEquals(expSingleBodyMass+newMass, actualRocketDryMass, EPSILON); + assertEquals(newMass, boosterBody.getMass(), EPSILON); + assertEquals(newMass, boosterBody.getSectionMass(), EPSILON); final Coordinate actualRocketDryCM = actualStructure.cm; assertEquals(0.06976699, actualRocketDryCM.x, EPSILON); @@ -411,13 +418,15 @@ public class MassCalculatorTest extends BaseTestCase { boosterBody.setSubcomponentsOverriddenMass(true); // change. Also, this body lacks subcomponents. boosterBody.setMassOverridden(true); // repeat - boosterBody.setOverrideMass(0.001); // repeat + boosterBody.setOverrideMass(newMass); // repeat { // [1] test Rocket CM, after: final RigidBody actualStructure = MassCalculator.calculateStructure(config); final double actualRocketDryMass = actualStructure.cm.weight; - assertEquals(expSingleBodyMass+0.001, actualRocketDryMass, EPSILON); + assertEquals(expSingleBodyMass+newMass, actualRocketDryMass, EPSILON); + assertEquals(newMass, boosterBody.getMass(), EPSILON); + assertEquals(newMass, boosterBody.getSectionMass(), EPSILON); final Coordinate actualRocketDryCM = actualStructure.cm; assertEquals(0.06976699, actualRocketDryCM.x, EPSILON); @@ -1232,5 +1241,31 @@ public class MassCalculatorTest extends BaseTestCase { assertEquals("Empty Stages Rocket Rotational MOI calculated incorrectly: ", overrideMOIrotRef, overrideMOIrot, EPSILON); assertEquals("Empty Stages Rocket Longitudinal MOI calculated incorrectly: ", overrideMOIlongRef, overrideMOIlong, EPSILON); } + + @Test + public void testStructureMass() { + Rocket rocket = OpenRocketDocumentFactory.createNewRocket().getRocket(); + AxialStage stage = rocket.getStage(0); + stage.addChild(new NoseCone()); + BodyTube bodyTube = new BodyTube(); + stage.addChild(bodyTube); + MassComponent massComponent = new MassComponent(); + massComponent.setComponentMass(0.01); + bodyTube.addChild(massComponent); + + assertEquals(0.041016634, bodyTube.getMass(), EPSILON); + assertEquals(0.051016634, bodyTube.getSectionMass(), EPSILON); + + bodyTube.setMassOverridden(true); + bodyTube.setOverrideMass(0.02); + + assertEquals(0.02, bodyTube.getMass(), EPSILON); + assertEquals(0.03, bodyTube.getSectionMass(), EPSILON); + + bodyTube.setSubcomponentsOverriddenMass(true); + + assertEquals(0.02, bodyTube.getMass(), EPSILON); + assertEquals(0.02, bodyTube.getSectionMass(), EPSILON); + } } diff --git a/core/test/net/sf/openrocket/rocketcomponent/OverrideTest.java b/core/test/net/sf/openrocket/rocketcomponent/OverrideTest.java index aaad470bb..71bfa9ab5 100644 --- a/core/test/net/sf/openrocket/rocketcomponent/OverrideTest.java +++ b/core/test/net/sf/openrocket/rocketcomponent/OverrideTest.java @@ -1,32 +1,21 @@ package net.sf.openrocket.rocketcomponent; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import org.junit.Test; - import net.sf.openrocket.aerodynamics.AerodynamicForces; import net.sf.openrocket.aerodynamics.BarrowmanCalculator; import net.sf.openrocket.aerodynamics.FlightConditions; import net.sf.openrocket.aerodynamics.WarningSet; - -import net.sf.openrocket.rocketcomponent.AxialStage; -import net.sf.openrocket.rocketcomponent.BodyTube; -import net.sf.openrocket.rocketcomponent.FinSet; -import net.sf.openrocket.rocketcomponent.FlightConfiguration; -import net.sf.openrocket.rocketcomponent.NoseCone; -import net.sf.openrocket.rocketcomponent.Rocket; -import net.sf.openrocket.rocketcomponent.RocketComponent; - -import net.sf.openrocket.util.Coordinate; +import net.sf.openrocket.util.BaseTestCase.BaseTestCase; import net.sf.openrocket.util.MathUtil; import net.sf.openrocket.util.TestRockets; -import net.sf.openrocket.util.BaseTestCase.BaseTestCase; +import org.junit.Test; + +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; public class OverrideTest extends BaseTestCase { @@ -109,4 +98,345 @@ public class OverrideTest extends BaseTestCase { Map forceMap = calc.getForceAnalysis(configuration, conditions, warnings); assertEquals(sustainer.getOverrideCD() + bodytube.getOverrideCD() + forceMap.get(nosecone).getCD(), forceMap.get(rocket).getCD(), MathUtil.EPSILON); } + + /** + * Test whether children components of a parent that has subcomponents overridden for mass, CG, or CD have the correct + * overriddenBy object. + */ + @Test + public void testOverriddenBy() { + // Create test rocket + Rocket rocket = TestRockets.makeEstesAlphaIII(); + + // Obtain the necessary components + AxialStage sustainer = rocket.getStage(0); + NoseCone noseCone = (NoseCone) sustainer.getChild(0); + BodyTube bodyTube = (BodyTube) sustainer.getChild(1); + FinSet finSet = (FinSet) bodyTube.getChild(0); + LaunchLug launchLug = (LaunchLug) bodyTube.getChild(1); + InnerTube innerTube = (InnerTube) bodyTube.getChild(2); + EngineBlock engineBlock = (EngineBlock) innerTube.getChild(0); + Parachute parachute = (Parachute) bodyTube.getChild(3); + CenteringRing bulkhead = (CenteringRing) bodyTube.getChild(4); + + // Check initial override by components + assertNull(rocket.getMassOverriddenBy()); + assertNull(rocket.getCGOverriddenBy()); + assertNull(rocket.getCDOverriddenBy()); + assertNull(sustainer.getMassOverriddenBy()); + assertNull(sustainer.getCGOverriddenBy()); + assertNull(sustainer.getCDOverriddenBy()); + assertNull(noseCone.getMassOverriddenBy()); + assertNull(noseCone.getCGOverriddenBy()); + assertNull(noseCone.getCDOverriddenBy()); + assertNull(bodyTube.getMassOverriddenBy()); + assertNull(bodyTube.getCGOverriddenBy()); + assertNull(bodyTube.getCDOverriddenBy()); + assertNull(finSet.getMassOverriddenBy()); + assertNull(finSet.getCGOverriddenBy()); + assertNull(finSet.getCDOverriddenBy()); + assertNull(launchLug.getMassOverriddenBy()); + assertNull(launchLug.getCGOverriddenBy()); + assertNull(launchLug.getCDOverriddenBy()); + assertNull(innerTube.getMassOverriddenBy()); + assertNull(innerTube.getCGOverriddenBy()); + assertNull(innerTube.getCDOverriddenBy()); + assertNull(engineBlock.getMassOverriddenBy()); + assertNull(engineBlock.getCGOverriddenBy()); + assertNull(engineBlock.getCDOverriddenBy()); + assertNull(parachute.getMassOverriddenBy()); + assertNull(parachute.getCGOverriddenBy()); + assertNull(parachute.getCDOverriddenBy()); + assertNull(bulkhead.getMassOverriddenBy()); + assertNull(bulkhead.getCGOverriddenBy()); + assertNull(bulkhead.getCDOverriddenBy()); + + // Override body tube mass, CG, and CD without for subcomponents + bodyTube.setMassOverridden(true); + bodyTube.setCGOverridden(true); + bodyTube.setCDOverridden(true); + + assertNull(sustainer.getMassOverriddenBy()); + assertNull(sustainer.getCGOverriddenBy()); + assertNull(sustainer.getCDOverriddenBy()); + assertNull(noseCone.getMassOverriddenBy()); + assertNull(noseCone.getCGOverriddenBy()); + assertNull(noseCone.getCDOverriddenBy()); + assertNull(bodyTube.getMassOverriddenBy()); + assertNull(bodyTube.getCGOverriddenBy()); + assertNull(bodyTube.getCDOverriddenBy()); + assertNull(finSet.getMassOverriddenBy()); + assertNull(finSet.getCGOverriddenBy()); + assertNull(finSet.getCDOverriddenBy()); + assertNull(launchLug.getMassOverriddenBy()); + assertNull(launchLug.getCGOverriddenBy()); + assertNull(launchLug.getCDOverriddenBy()); + assertNull(innerTube.getMassOverriddenBy()); + assertNull(innerTube.getCGOverriddenBy()); + assertNull(innerTube.getCDOverriddenBy()); + assertNull(engineBlock.getMassOverriddenBy()); + assertNull(engineBlock.getCGOverriddenBy()); + assertNull(engineBlock.getCDOverriddenBy()); + assertNull(parachute.getMassOverriddenBy()); + assertNull(parachute.getCGOverriddenBy()); + assertNull(parachute.getCDOverriddenBy()); + assertNull(bulkhead.getMassOverriddenBy()); + assertNull(bulkhead.getCGOverriddenBy()); + assertNull(bulkhead.getCDOverriddenBy()); + + // Override body tube mass for subcomponents + bodyTube.setSubcomponentsOverriddenMass(true); + + assertNull(sustainer.getMassOverriddenBy()); + assertNull(sustainer.getCGOverriddenBy()); + assertNull(sustainer.getCDOverriddenBy()); + assertNull(noseCone.getMassOverriddenBy()); + assertNull(noseCone.getCGOverriddenBy()); + assertNull(noseCone.getCDOverriddenBy()); + assertNull(bodyTube.getMassOverriddenBy()); + assertNull(bodyTube.getCGOverriddenBy()); + assertNull(bodyTube.getCDOverriddenBy()); + assertEquals(bodyTube, finSet.getMassOverriddenBy()); + assertNull(finSet.getCGOverriddenBy()); + assertNull(finSet.getCDOverriddenBy()); + assertEquals(bodyTube, launchLug.getMassOverriddenBy()); + assertNull(launchLug.getCGOverriddenBy()); + assertNull(launchLug.getCDOverriddenBy()); + assertEquals(bodyTube, innerTube.getMassOverriddenBy()); + assertNull(innerTube.getCGOverriddenBy()); + assertNull(innerTube.getCDOverriddenBy()); + assertEquals(bodyTube, engineBlock.getMassOverriddenBy()); + assertNull(engineBlock.getCGOverriddenBy()); + assertNull(engineBlock.getCDOverriddenBy()); + assertEquals(bodyTube, parachute.getMassOverriddenBy()); + assertNull(parachute.getCGOverriddenBy()); + assertNull(parachute.getCDOverriddenBy()); + assertEquals(bodyTube, bulkhead.getMassOverriddenBy()); + assertNull(bulkhead.getCGOverriddenBy()); + assertNull(bulkhead.getCDOverriddenBy()); + + // Undo override body tube mass for subcomponents, do override of CG and CD for subcomponents + bodyTube.setSubcomponentsOverriddenMass(false); + bodyTube.setSubcomponentsOverriddenCG(true); + bodyTube.setSubcomponentsOverriddenCD(true); + + assertNull(noseCone.getMassOverriddenBy()); + assertNull(noseCone.getCGOverriddenBy()); + assertNull(noseCone.getCDOverriddenBy()); + assertNull(bodyTube.getMassOverriddenBy()); + assertNull(bodyTube.getCGOverriddenBy()); + assertNull(bodyTube.getCDOverriddenBy()); + assertNull(finSet.getMassOverriddenBy()); + assertEquals(bodyTube, finSet.getCGOverriddenBy()); + assertEquals(bodyTube, finSet.getCDOverriddenBy()); + assertNull(launchLug.getMassOverriddenBy()); + assertEquals(bodyTube, launchLug.getCGOverriddenBy()); + assertEquals(bodyTube, launchLug.getCDOverriddenBy()); + assertNull(innerTube.getMassOverriddenBy()); + assertEquals(bodyTube, innerTube.getCGOverriddenBy()); + assertEquals(bodyTube, innerTube.getCDOverriddenBy()); + assertNull(engineBlock.getMassOverriddenBy()); + assertEquals(bodyTube, engineBlock.getCGOverriddenBy()); + assertEquals(bodyTube, engineBlock.getCDOverriddenBy()); + assertNull(parachute.getMassOverriddenBy()); + assertEquals(bodyTube, parachute.getCGOverriddenBy()); + assertEquals(bodyTube, parachute.getCDOverriddenBy()); + assertNull(bulkhead.getMassOverriddenBy()); + assertEquals(bodyTube, bulkhead.getCGOverriddenBy()); + assertEquals(bodyTube, bulkhead.getCDOverriddenBy()); + + // Move the inner tube from the body tube to the nose cone + bodyTube.removeChild(innerTube); + noseCone.addChild(innerTube); + + assertNull(noseCone.getMassOverriddenBy()); + assertNull(noseCone.getCGOverriddenBy()); + assertNull(noseCone.getCDOverriddenBy()); + assertNull(innerTube.getMassOverriddenBy()); + assertNull(innerTube.getCGOverriddenBy()); + assertNull(innerTube.getCDOverriddenBy()); + assertNull(engineBlock.getMassOverriddenBy()); + assertNull(engineBlock.getCGOverriddenBy()); + assertNull(engineBlock.getCDOverriddenBy()); + assertNull(bodyTube.getMassOverriddenBy()); + assertNull(bodyTube.getCGOverriddenBy()); + assertNull(bodyTube.getCDOverriddenBy()); + assertNull(finSet.getMassOverriddenBy()); + assertEquals(bodyTube, finSet.getCGOverriddenBy()); + assertEquals(bodyTube, finSet.getCDOverriddenBy()); + assertNull(launchLug.getMassOverriddenBy()); + assertEquals(bodyTube, launchLug.getCGOverriddenBy()); + assertEquals(bodyTube, launchLug.getCDOverriddenBy()); + assertNull(parachute.getMassOverriddenBy()); + assertEquals(bodyTube, parachute.getCGOverriddenBy()); + assertEquals(bodyTube, parachute.getCDOverriddenBy()); + assertNull(bulkhead.getMassOverriddenBy()); + assertEquals(bodyTube, bulkhead.getCGOverriddenBy()); + assertEquals(bodyTube, bulkhead.getCDOverriddenBy()); + + // Override mass of nose cone + noseCone.setMassOverridden(true); + + assertNull(noseCone.getMassOverriddenBy()); + assertNull(noseCone.getCGOverriddenBy()); + assertNull(noseCone.getCDOverriddenBy()); + assertNull(innerTube.getMassOverriddenBy()); + assertNull(innerTube.getCGOverriddenBy()); + assertNull(innerTube.getCDOverriddenBy()); + assertNull(engineBlock.getMassOverriddenBy()); + assertNull(engineBlock.getCGOverriddenBy()); + assertNull(engineBlock.getCDOverriddenBy()); + assertNull(bodyTube.getMassOverriddenBy()); + assertNull(bodyTube.getCGOverriddenBy()); + assertNull(bodyTube.getCDOverriddenBy()); + assertNull(finSet.getMassOverriddenBy()); + assertEquals(bodyTube, finSet.getCGOverriddenBy()); + assertEquals(bodyTube, finSet.getCDOverriddenBy()); + assertNull(launchLug.getMassOverriddenBy()); + assertEquals(bodyTube, launchLug.getCGOverriddenBy()); + assertEquals(bodyTube, launchLug.getCDOverriddenBy()); + assertNull(parachute.getMassOverriddenBy()); + assertEquals(bodyTube, parachute.getCGOverriddenBy()); + assertEquals(bodyTube, parachute.getCDOverriddenBy()); + assertNull(bulkhead.getMassOverriddenBy()); + assertEquals(bodyTube, bulkhead.getCGOverriddenBy()); + assertEquals(bodyTube, bulkhead.getCDOverriddenBy()); + + // Override nose cone mass for all subcomponents + noseCone.setSubcomponentsOverriddenMass(true); + + assertNull(sustainer.getMassOverriddenBy()); + assertNull(sustainer.getCGOverriddenBy()); + assertNull(sustainer.getCDOverriddenBy()); + assertNull(noseCone.getMassOverriddenBy()); + assertNull(noseCone.getCGOverriddenBy()); + assertNull(noseCone.getCDOverriddenBy()); + assertEquals(noseCone, innerTube.getMassOverriddenBy()); + assertNull(innerTube.getCGOverriddenBy()); + assertNull(innerTube.getCDOverriddenBy()); + assertEquals(noseCone, engineBlock.getMassOverriddenBy()); + assertNull(engineBlock.getCGOverriddenBy()); + assertNull(engineBlock.getCDOverriddenBy()); + assertNull(bodyTube.getMassOverriddenBy()); + assertNull(bodyTube.getCGOverriddenBy()); + assertNull(bodyTube.getCDOverriddenBy()); + assertNull(finSet.getMassOverriddenBy()); + assertEquals(bodyTube, finSet.getCGOverriddenBy()); + assertEquals(bodyTube, finSet.getCDOverriddenBy()); + assertNull(launchLug.getMassOverriddenBy()); + assertEquals(bodyTube, launchLug.getCGOverriddenBy()); + assertEquals(bodyTube, launchLug.getCDOverriddenBy()); + assertNull(parachute.getMassOverriddenBy()); + assertEquals(bodyTube, parachute.getCGOverriddenBy()); + assertEquals(bodyTube, parachute.getCDOverriddenBy()); + assertNull(bulkhead.getMassOverriddenBy()); + assertEquals(bodyTube, bulkhead.getCGOverriddenBy()); + assertEquals(bodyTube, bulkhead.getCDOverriddenBy()); + + // Override inner tube CG for all subcomponents + innerTube.setCGOverridden(true); + innerTube.setSubcomponentsOverriddenCG(true); + + assertNull(noseCone.getMassOverriddenBy()); + assertNull(noseCone.getCGOverriddenBy()); + assertNull(noseCone.getCDOverriddenBy()); + assertEquals(noseCone, innerTube.getMassOverriddenBy()); + assertNull(innerTube.getCGOverriddenBy()); + assertNull(innerTube.getCDOverriddenBy()); + assertEquals(noseCone, engineBlock.getMassOverriddenBy()); + assertEquals(innerTube, engineBlock.getCGOverriddenBy()); + assertNull(engineBlock.getCDOverriddenBy()); + assertNull(bodyTube.getMassOverriddenBy()); + assertNull(bodyTube.getCGOverriddenBy()); + assertNull(bodyTube.getCDOverriddenBy()); + assertNull(finSet.getMassOverriddenBy()); + assertEquals(bodyTube, finSet.getCGOverriddenBy()); + assertEquals(bodyTube, finSet.getCDOverriddenBy()); + assertNull(launchLug.getMassOverriddenBy()); + assertEquals(bodyTube, launchLug.getCGOverriddenBy()); + assertEquals(bodyTube, launchLug.getCDOverriddenBy()); + assertNull(parachute.getMassOverriddenBy()); + assertEquals(bodyTube, parachute.getCGOverriddenBy()); + assertEquals(bodyTube, parachute.getCDOverriddenBy()); + assertNull(bulkhead.getMassOverriddenBy()); + assertEquals(bodyTube, bulkhead.getCGOverriddenBy()); + assertEquals(bodyTube, bulkhead.getCDOverriddenBy()); + + // Set body tube mass override, reset CG & CD, and move inner tube back to body tube + bodyTube.setMassOverridden(true); + bodyTube.setSubcomponentsOverriddenMass(true); + bodyTube.setCGOverridden(false); + bodyTube.setCDOverridden(false); + noseCone.removeChild(innerTube); + bodyTube.addChild(innerTube); + + assertNull(noseCone.getMassOverriddenBy()); + assertNull(noseCone.getCGOverriddenBy()); + assertNull(noseCone.getCDOverriddenBy()); + assertNull(bodyTube.getMassOverriddenBy()); + assertNull(bodyTube.getCGOverriddenBy()); + assertNull(bodyTube.getCDOverriddenBy()); + assertEquals(bodyTube, finSet.getMassOverriddenBy()); + assertNull(finSet.getCGOverriddenBy()); + assertNull(finSet.getCDOverriddenBy()); + assertEquals(bodyTube, launchLug.getMassOverriddenBy()); + assertNull(launchLug.getCGOverriddenBy()); + assertNull(launchLug.getCDOverriddenBy()); + assertEquals(bodyTube, parachute.getMassOverriddenBy()); + assertNull(parachute.getCGOverriddenBy()); + assertNull(parachute.getCDOverriddenBy()); + assertEquals(bodyTube, bulkhead.getMassOverriddenBy()); + assertNull(bulkhead.getCGOverriddenBy()); + assertNull(bulkhead.getCDOverriddenBy()); + assertEquals(bodyTube, innerTube.getMassOverriddenBy()); + assertNull(innerTube.getCGOverriddenBy()); + assertNull(innerTube.getCDOverriddenBy()); + assertEquals(bodyTube, engineBlock.getMassOverriddenBy()); + assertEquals(innerTube, engineBlock.getCGOverriddenBy()); + assertNull(engineBlock.getCDOverriddenBy()); + + // Toggle the body tube CG override for all subcomponents + bodyTube.setCGOverridden(true); + bodyTube.setSubcomponentsOverriddenCG(true); + + assertEquals(bodyTube, finSet.getMassOverriddenBy()); + assertEquals(bodyTube, finSet.getCGOverriddenBy()); + assertNull(finSet.getCDOverriddenBy()); + assertEquals(bodyTube, launchLug.getMassOverriddenBy()); + assertEquals(bodyTube, launchLug.getCGOverriddenBy()); + assertNull(launchLug.getCDOverriddenBy()); + assertEquals(bodyTube, parachute.getMassOverriddenBy()); + assertEquals(bodyTube, parachute.getCGOverriddenBy()); + assertNull(parachute.getCDOverriddenBy()); + assertEquals(bodyTube, bulkhead.getMassOverriddenBy()); + assertEquals(bodyTube, bulkhead.getCGOverriddenBy()); + assertNull(bulkhead.getCDOverriddenBy()); + assertEquals(bodyTube, innerTube.getMassOverriddenBy()); + assertEquals(bodyTube, innerTube.getCGOverriddenBy()); + assertNull(innerTube.getCDOverriddenBy()); + assertEquals(bodyTube, engineBlock.getMassOverriddenBy()); + assertEquals(bodyTube, engineBlock.getCGOverriddenBy()); + assertNull(engineBlock.getCDOverriddenBy()); + + // Toggle back + bodyTube.setSubcomponentsOverriddenCG(false); + assertEquals(bodyTube, finSet.getMassOverriddenBy()); + assertNull(finSet.getCGOverriddenBy()); + assertNull(finSet.getCDOverriddenBy()); + assertEquals(bodyTube, launchLug.getMassOverriddenBy()); + assertNull(launchLug.getCGOverriddenBy()); + assertNull(launchLug.getCDOverriddenBy()); + assertEquals(bodyTube, parachute.getMassOverriddenBy()); + assertNull(parachute.getCGOverriddenBy()); + assertNull(parachute.getCDOverriddenBy()); + assertEquals(bodyTube, bulkhead.getMassOverriddenBy()); + assertNull(bulkhead.getCGOverriddenBy()); + assertNull(bulkhead.getCDOverriddenBy()); + assertEquals(bodyTube, innerTube.getMassOverriddenBy()); + assertNull(innerTube.getCGOverriddenBy()); + assertNull(innerTube.getCDOverriddenBy()); + assertEquals(bodyTube, engineBlock.getMassOverriddenBy()); + assertEquals(innerTube, engineBlock.getCGOverriddenBy()); + assertNull(engineBlock.getCDOverriddenBy()); + } } diff --git a/swing/src/net/sf/openrocket/gui/adaptors/BooleanModel.java b/swing/src/net/sf/openrocket/gui/adaptors/BooleanModel.java index 2f631058a..915313994 100644 --- a/swing/src/net/sf/openrocket/gui/adaptors/BooleanModel.java +++ b/swing/src/net/sf/openrocket/gui/adaptors/BooleanModel.java @@ -201,6 +201,15 @@ public class BooleanModel extends AbstractAction implements StateChangeListener, componentEnableState.add(enableState); updateEnableStatus(); } + + /** + * Remove a component from the list of enable components controlled by this model. + * @param component component to remove from the list + */ + public void removeEnableComponent(Component component) { + checkState(true); + components.remove(component); + } /** * Add a component which will be enabled when this boolean is true. diff --git a/swing/src/net/sf/openrocket/gui/configdialog/RocketComponentConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/RocketComponentConfig.java index de4777116..2d0729992 100644 --- a/swing/src/net/sf/openrocket/gui/configdialog/RocketComponentConfig.java +++ b/swing/src/net/sf/openrocket/gui/configdialog/RocketComponentConfig.java @@ -235,11 +235,9 @@ public class RocketComponentConfig extends JPanel { overridetext = trans.get("RocketCompCfg.lbl.overriddento") + " " + UnitGroup.UNITS_MASS.getDefaultUnit(). toStringUnit(component.getOverrideMass()) + ")"; } - - for (RocketComponent c = component.getParent(); c != null; c = c.getParent()) { - if (c.isMassOverridden() && c.isSubcomponentsOverriddenMass()) { - overridetext = trans.get("RocketCompCfg.lbl.overriddenby") + " " + c.getName() + ")"; - } + + if (component.getMassOverriddenBy() != null) { + overridetext = trans.get("RocketCompCfg.lbl.overriddenby") + " " + component.getMassOverriddenBy().getName() + ")"; } if (overridetext != null) { @@ -355,6 +353,7 @@ public class RocketComponentConfig extends JPanel { //// Override the mass, center of gravity, or drag coeficient of the component JCheckBox check; + JCheckBox checkSub; BooleanModel bm; UnitSelector us; BasicSlider bs; @@ -371,15 +370,26 @@ public class RocketComponentConfig extends JPanel { ////// Override subcomponents BooleanModel bmSubcomp = new BooleanModel(component, "SubcomponentsOverriddenMass"); - check = new JCheckBox(bmSubcomp); - check.setText(trans.get("RocketCompCfg.checkbox.OverrideSubcomponents")); - Font smallFont = check.getFont(); + checkSub = new JCheckBox(bmSubcomp); + checkSub.setText(trans.get("RocketCompCfg.checkbox.OverrideSubcomponents")); + Font smallFont = checkSub.getFont(); smallFont = smallFont.deriveFont(smallFont.getSize2D() - 1); - check.setFont(smallFont); - check.setToolTipText(trans.get("RocketCompCfg.checkbox.OverrideSubcomponents.Mass.ttip")); - bm.addEnableComponent(check, true); - checkboxes.add(check, "gapleft 25lp, wrap"); - order.add(check); + checkSub.setFont(smallFont); + checkSub.setToolTipText(trans.get("RocketCompCfg.checkbox.OverrideSubcomponents.Mass.ttip")); + bm.addEnableComponent(checkSub, true); + checkboxes.add(checkSub, "gapleft 25lp, wrap"); + order.add(checkSub); + + ////// Mass overridden by + if (component.getMassOverriddenBy() != null) { + StyledLabel labelMassOverriddenBy = new StyledLabel( + String.format(trans.get("RocketCompCfg.lbl.MassOverriddenBy"), component.getMassOverriddenBy().getName()), + 0, StyledLabel.Style.BOLD); + labelMassOverriddenBy.setFontColor(net.sf.openrocket.util.Color.DARK_RED.toAWTColor()); + labelMassOverriddenBy.setToolTipText( + String.format(trans.get("RocketCompCfg.lbl.MassOverriddenBy.ttip"), component.getMassOverriddenBy().getName())); + checkboxes.add(labelMassOverriddenBy, "gapleft 25lp, wrap"); + } panel.add(checkboxes, "growx 1, gapright 20lp"); @@ -399,6 +409,18 @@ public class RocketComponentConfig extends JPanel { bm.addEnableComponent(bs); panel.add(bs, "growx 5, w 100lp, wrap"); + if (component.getMassOverriddenBy() != null) { + check.setEnabled(false); + bm.removeEnableComponent(checkSub); + bm.removeEnableComponent(spin); + bm.removeEnableComponent(us); + bm.removeEnableComponent(bs); + checkSub.setEnabled(false); + spin.setEnabled(false); + us.setEnabled(false); + bs.setEnabled(false); + } + // END OVERRIDE MASS ---------------------------------- // OVERRIDE CG ---------------------------------- @@ -413,13 +435,24 @@ public class RocketComponentConfig extends JPanel { ////// Override subcomponents bmSubcomp = new BooleanModel(component, "SubcomponentsOverriddenCG"); - check = new JCheckBox(bmSubcomp); - check.setText(trans.get("RocketCompCfg.checkbox.OverrideSubcomponents")); - check.setFont(smallFont); - check.setToolTipText(trans.get("RocketCompCfg.checkbox.OverrideSubcomponents.CG.ttip")); - bm.addEnableComponent(check, true); - checkboxes.add(check, "gapleft 25lp, wrap"); - order.add(check); + checkSub = new JCheckBox(bmSubcomp); + checkSub.setText(trans.get("RocketCompCfg.checkbox.OverrideSubcomponents")); + checkSub.setFont(smallFont); + checkSub.setToolTipText(trans.get("RocketCompCfg.checkbox.OverrideSubcomponents.CG.ttip")); + bm.addEnableComponent(checkSub, true); + checkboxes.add(checkSub, "gapleft 25lp, wrap"); + order.add(checkSub); + + ////// CG overridden by + if (component.getCGOverriddenBy() != null) { + StyledLabel labelCGOverriddenBy = new StyledLabel( + String.format(trans.get("RocketCompCfg.lbl.CGOverriddenBy"), component.getCGOverriddenBy().getName()), + 0, StyledLabel.Style.BOLD); + labelCGOverriddenBy.setFontColor(net.sf.openrocket.util.Color.DARK_RED.toAWTColor()); + labelCGOverriddenBy.setToolTipText( + String.format(trans.get("RocketCompCfg.lbl.CGOverriddenBy.ttip"), component.getCGOverriddenBy().getName())); + checkboxes.add(labelCGOverriddenBy, "gapleft 25lp, wrap"); + } panel.add(checkboxes, "growx 1, gapright 20lp"); @@ -469,6 +502,18 @@ public class RocketComponentConfig extends JPanel { bm.addEnableComponent(bs); panel.add(bs, "growx 5, w 100lp, wrap"); + if (component.getCGOverriddenBy() != null) { + check.setEnabled(false); + bm.removeEnableComponent(checkSub); + bm.removeEnableComponent(spin); + bm.removeEnableComponent(us); + bm.removeEnableComponent(bs); + checkSub.setEnabled(false); + spin.setEnabled(false); + us.setEnabled(false); + bs.setEnabled(false); + } + // END OVERRIDE CG --------------------------------------------------- @@ -484,13 +529,24 @@ public class RocketComponentConfig extends JPanel { ////// Override subcomponents bmSubcomp = new BooleanModel(component, "SubcomponentsOverriddenCD"); - check = new JCheckBox(bmSubcomp); - check.setText(trans.get("RocketCompCfg.checkbox.OverrideSubcomponents")); - check.setFont(smallFont); - check.setToolTipText(trans.get("RocketCompCfg.checkbox.OverrideSubcomponents.CD.ttip")); - bm.addEnableComponent(check, true); - checkboxes.add(check, "gapleft 25lp, wrap"); - order.add(check); + checkSub = new JCheckBox(bmSubcomp); + checkSub.setText(trans.get("RocketCompCfg.checkbox.OverrideSubcomponents")); + checkSub.setFont(smallFont); + checkSub.setToolTipText(trans.get("RocketCompCfg.checkbox.OverrideSubcomponents.CD.ttip")); + bm.addEnableComponent(checkSub, true); + checkboxes.add(checkSub, "gapleft 25lp, wrap"); + order.add(checkSub); + + ////// CD overridden by + if (component.getCDOverriddenBy() != null) { + StyledLabel labelCDOverriddenBy = new StyledLabel( + String.format(trans.get("RocketCompCfg.lbl.CDOverriddenBy"), component.getCDOverriddenBy().getName()), + 0, StyledLabel.Style.BOLD); + labelCDOverriddenBy.setFontColor(net.sf.openrocket.util.Color.DARK_RED.toAWTColor()); + labelCDOverriddenBy.setToolTipText( + String.format(trans.get("RocketCompCfg.lbl.CDOverriddenBy"), component.getCDOverriddenBy().getName())); + checkboxes.add(labelCDOverriddenBy, "gapleft 25lp, wrap"); + } panel.add(checkboxes, "growx 1, gapright 20lp"); @@ -507,6 +563,16 @@ public class RocketComponentConfig extends JPanel { bm.addEnableComponent(bs); panel.add(bs, "top, skip, growx 5, w 100lp, wrap"); + if (component.getCDOverriddenBy() != null) { + check.setEnabled(false); + bm.removeEnableComponent(checkSub); + bm.removeEnableComponent(spin); + bm.removeEnableComponent(bs); + checkSub.setEnabled(false); + spin.setEnabled(false); + bs.setEnabled(false); + } + // END OVERRIDE CD -------------------------------------------------- // OVERRIDE MASS, CG DOESN'T INCLUDE MOTORS -------------------------------------------------- diff --git a/swing/src/net/sf/openrocket/gui/dialogs/preferences/LaunchPreferencesPanel.java b/swing/src/net/sf/openrocket/gui/dialogs/preferences/LaunchPreferencesPanel.java index 3179a2c3b..f2bc4c8b5 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/preferences/LaunchPreferencesPanel.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/preferences/LaunchPreferencesPanel.java @@ -38,7 +38,7 @@ public class LaunchPreferencesPanel extends PreferencesPanel { StyledLabel warning = new StyledLabel(String.format( "%s", trans.get("pref.dlg.lbl.launchWarning")), 0.5f, StyledLabel.Style.BOLD); - warning.setFontColor(Color.RED); + warning.setFontColor(net.sf.openrocket.util.Color.DARK_RED.toAWTColor()); warning.setToolTipText(trans.get("pref.dlg.lbl.launchWarning.ttip")); add(warning, "spanx, growx 0, gapbottom para, wrap"); diff --git a/swing/src/net/sf/openrocket/gui/dialogs/preferences/SimulationPreferencesPanel.java b/swing/src/net/sf/openrocket/gui/dialogs/preferences/SimulationPreferencesPanel.java index 10af0a981..8e6e9eb8a 100644 --- a/swing/src/net/sf/openrocket/gui/dialogs/preferences/SimulationPreferencesPanel.java +++ b/swing/src/net/sf/openrocket/gui/dialogs/preferences/SimulationPreferencesPanel.java @@ -15,6 +15,7 @@ import net.sf.openrocket.gui.SpinnerEditor; import net.sf.openrocket.gui.adaptors.DoubleModel; import net.sf.openrocket.gui.adaptors.EnumModel; import net.sf.openrocket.gui.components.BasicSlider; +import net.sf.openrocket.gui.components.StyledLabel; import net.sf.openrocket.gui.components.UnitSelector; import net.sf.openrocket.simulation.RK4SimulationStepper; import net.sf.openrocket.unit.UnitGroup; @@ -79,6 +80,14 @@ public class SimulationPreferencesPanel extends PreferencesPanel { // layout subsub = new JPanel(new MigLayout("insets 0, fill", "[grow][min!][min!][]")); + // // Warning + StyledLabel warning = new StyledLabel(String.format( + "%s", trans.get("pref.dlg.lbl.launchWarning")), + 0, StyledLabel.Style.BOLD); + warning.setFontColor(net.sf.openrocket.util.Color.DARK_RED.toAWTColor()); + warning.setToolTipText(trans.get("pref.dlg.lbl.launchWarning.ttip")); + subsub.add(warning, "spanx, wrap para"); + // // Calculation method: tip = trans.get("simedtdlg.lbl.ttip.Calcmethod"); label = new JLabel(trans.get("simedtdlg.lbl.Calcmethod")); diff --git a/swing/src/net/sf/openrocket/gui/main/componenttree/ComponentTreeModel.java b/swing/src/net/sf/openrocket/gui/main/componenttree/ComponentTreeModel.java index b33089118..5506cc3ce 100644 --- a/swing/src/net/sf/openrocket/gui/main/componenttree/ComponentTreeModel.java +++ b/swing/src/net/sf/openrocket/gui/main/componenttree/ComponentTreeModel.java @@ -90,9 +90,9 @@ public class ComponentTreeModel implements TreeModel, ComponentChangeListener { private void fireTreeNodeChanged(RocketComponent node) { TreeModelEvent e = new TreeModelEvent(this, makeTreePath(node), null, null); - Object[] l = listeners.toArray(); - for (int i = 0; i < l.length; i++) - ((TreeModelListener) l[i]).treeNodesChanged(e); + for (TreeModelListener listener : listeners) { + listener.treeNodesChanged(e); + } } @@ -147,7 +147,12 @@ public class ComponentTreeModel implements TreeModel, ComponentChangeListener { // TODO: LOW: Could this be performed better? expandAll(); } - } else { + } else if (e.isTreeChildrenChange()) { + for (RocketComponent c : e.getSource().getAllChildren()) { + fireTreeNodeChanged(c); + } + } + else { fireTreeNodeChanged(e.getSource()); } } diff --git a/swing/src/net/sf/openrocket/gui/main/componenttree/ComponentTreeRenderer.java b/swing/src/net/sf/openrocket/gui/main/componenttree/ComponentTreeRenderer.java index ccd0e6ee4..2dad7795d 100644 --- a/swing/src/net/sf/openrocket/gui/main/componenttree/ComponentTreeRenderer.java +++ b/swing/src/net/sf/openrocket/gui/main/componenttree/ComponentTreeRenderer.java @@ -49,23 +49,31 @@ public class ComponentTreeRenderer extends DefaultTreeCellRenderer { } else { setIcon(ComponentIcons.getSmallIcon(value.getClass())); } - if (c.isMassOverridden() || c.isCGOverridden() || c.isCDOverridden()) { + if (c.isMassOverridden() || c.getMassOverriddenBy() != null || + c.isCGOverridden() || c.getCGOverriddenBy() != null || + c.isCDOverridden() || c.getCDOverriddenBy() != null) { JPanel p = new JPanel(); p.setLayout(new FlowLayout(FlowLayout.LEFT, 1, 1)); p.setBackground(UIManager.getColor("Tree.textBackground")); p.setForeground(UIManager.getColor("Tree.textForeground")); p.add(comp/* , BorderLayout.WEST */); - if (c.isMassOverridden()) { + if (c.getMassOverriddenBy() != null) { + p.add(new JLabel(Icons.MASS_OVERRIDE_SUBCOMPONENT)); + } else if (c.isMassOverridden()) { p.add(new JLabel(Icons.MASS_OVERRIDE)); } - if (c.isCGOverridden()) { + if (c.getCGOverriddenBy() != null) { + p.add(new JLabel(Icons.CG_OVERRIDE_SUBCOMPONENT)); + } else if (c.isCGOverridden()) { p.add(new JLabel(Icons.CG_OVERRIDE)); } - if (c.isCDOverridden()) { + if (c.getCDOverriddenBy() != null) { + p.add(new JLabel(Icons.CD_OVERRIDE_SUBCOMPONENT)); + } else if (c.isCDOverridden()) { p.add(new JLabel(Icons.CD_OVERRIDE)); } - + // Make sure the tooltip also works on the override icons if (components != null && components.size() > 1 && components.contains(c)) { p.setToolTipText(getToolTipMultipleComponents(components)); } else { @@ -88,32 +96,60 @@ public class ComponentTreeRenderer extends DefaultTreeCellRenderer { StringBuilder sb = new StringBuilder(); sb.append(""); + // Component name title sb.append("").append(c.getName()).append(""); - if (c.isMassive() || c.isMassOverridden()) { + + // Only add mass information if mass is not overridden by a parent component + RocketComponent overriddenBy = c.getMassOverriddenBy(); + if (overriddenBy == null) { + if (c.isMassive() || c.isMassOverridden()) { + sb.append(" (").append( + UnitGroup.UNITS_MASS.toStringUnit(c.getMass())); + if (c.getChildCount() > 0) { + sb.append(" of ") + .append(UnitGroup.UNITS_MASS.toStringUnit(c + .getSectionMass())).append(" ").append(trans.get("ComponentTreeRenderer.total")); + } + sb.append(")"); + } else { + if ((c.getChildCount() > 0) && (c.getSectionMass() > 0)) { + sb.append(" (") + .append(UnitGroup.UNITS_MASS.toStringUnit(c + .getSectionMass())).append(" ").append(trans.get("ComponentTreeRenderer.total")).append(")"); + } + } + } + + // Add component override information in title + if (c.isMassOverridden() && c.getMassOverriddenBy() == null) { + sb.append(", ").append(trans.get("ComponentTree.ttip.massoverride")); + } + if (c.isCGOverridden() && c.getCGOverriddenBy() == null) { + sb.append(", ").append(trans.get("ComponentTree.ttip.cgoverride")); + } + if (c.isCDOverridden() && c.getCDOverriddenBy() == null) { + sb.append(", ").append(trans.get("ComponentTree.ttip.cdoverride")); + } + + // Add parent component override information on new lines + if (overriddenBy != null) { + sb.append("
").append(String.format(trans.get("RocketCompCfg.lbl.MassOverriddenBy"), overriddenBy.getName())); sb.append(" (").append( - UnitGroup.UNITS_MASS.toStringUnit(c.getMass())); - if (c.getChildCount() > 0) { - sb.append(" of ") - .append(UnitGroup.UNITS_MASS.toStringUnit(c - .getSectionMass())).append(" total"); - } - sb.append(")"); - } else { - if ((c.getChildCount() > 0) && (c.getSectionMass() > 0)) { - sb.append(" (") - .append(UnitGroup.UNITS_MASS.toStringUnit(c - .getSectionMass())).append(" total)"); - } - } - - if (c.isMassOverridden()) { - sb.append(" ").append(trans.get("ComponentTree.ttip.massoverride")); - } - - if (c.isCGOverridden()) { - sb.append(" ").append(trans.get("ComponentTree.ttip.cgoverride")); + UnitGroup.UNITS_MASS.toStringUnit(overriddenBy.getMass())).append(")"); + } + overriddenBy = c.getCGOverriddenBy(); + if (overriddenBy != null) { + sb.append("
").append(String.format(trans.get("RocketCompCfg.lbl.CGOverriddenBy"), overriddenBy.getName())); + sb.append(" (").append( + UnitGroup.UNITS_LENGTH.toStringUnit(overriddenBy.getOverrideCGX())).append(")"); + } + overriddenBy = c.getCDOverriddenBy(); + if (overriddenBy != null) { + sb.append("
").append(String.format(trans.get("RocketCompCfg.lbl.CDOverriddenBy"), overriddenBy.getName())); + sb.append(" (").append(overriddenBy.getOverrideCD()).append(")"); } + // Add component comment on new line String comment = c.getComment().trim(); if (comment.length() > 0) { comment = TextUtil.escapeXML(comment); @@ -132,11 +168,24 @@ public class ComponentTreeRenderer extends DefaultTreeCellRenderer { StringBuilder sb = new StringBuilder(); sb.append(""); + // Components title sb.append("Components"); + + // Calculate the total mass of the selected components double totalMass = 0; double totalSectionMass = 0; boolean containsSectionMass = false; + List overriddenByComponents = new java.util.ArrayList<>(); // Components that override the mass of the selected components for (RocketComponent c : components) { + RocketComponent overriddenBy = c.getMassOverriddenBy(); + if (overriddenBy != null) { + if (!components.contains(overriddenBy) && !overriddenByComponents.contains(overriddenBy)) { + totalMass += overriddenBy.getMass(); + overriddenByComponents.add(overriddenBy); + } + continue; + } + if (c.isMassive() || c.isMassOverridden()) { totalMass += c.getMass(); // Don't add this component's mass to the section mass if its parent is in the list, otherwise you add up duplicate mass @@ -155,9 +204,11 @@ public class ComponentTreeRenderer extends DefaultTreeCellRenderer { } } + // Add total mass of the selected components in the title sb.append(" (").append(UnitGroup.UNITS_MASS.toStringUnit(totalMass)); if (containsSectionMass) { - sb.append(" of ").append(UnitGroup.UNITS_MASS.toStringUnit(totalSectionMass)).append(" total)"); + sb.append(" of ").append(UnitGroup.UNITS_MASS.toStringUnit(totalSectionMass)) + .append(" ").append(trans.get("ComponentTreeRenderer.total")).append(")"); } else { sb.append(")"); } diff --git a/swing/src/net/sf/openrocket/gui/util/Icons.java b/swing/src/net/sf/openrocket/gui/util/Icons.java index f3bdcd413..76190fbc1 100644 --- a/swing/src/net/sf/openrocket/gui/util/Icons.java +++ b/swing/src/net/sf/openrocket/gui/util/Icons.java @@ -94,8 +94,11 @@ public class Icons { public static final Icon FAVORITE = loadImageIcon("pix/icons/star_gold.png", "Favorite"); public static final Icon CG_OVERRIDE = loadImageIcon("pix/icons/cg-override.png", "CG Override"); + public static final Icon CG_OVERRIDE_SUBCOMPONENT = loadImageIcon("pix/icons/cg-override-subcomponent.png", "CG Override Subcomponent"); public static final Icon CD_OVERRIDE = loadImageIcon("pix/icons/cd-override.png", "CD Override"); + public static final Icon CD_OVERRIDE_SUBCOMPONENT = loadImageIcon("pix/icons/cd-override-subcomponent.png", "CD Override Subcomponent"); public static final Icon MASS_OVERRIDE = loadImageIcon("pix/icons/mass-override.png", "Mass Override"); + public static final Icon MASS_OVERRIDE_SUBCOMPONENT = loadImageIcon("pix/icons/mass-override-subcomponent.png", "Mass Override Subcomponent"); // MANUFACTURERS ICONS public static final Icon RASAERO_ICON = loadImageIcon("pix/icons/RASAero_16.png", "RASAero Icon");