Versions Compared
Key
 This line was added.
 This line was removed.
 Formatting was changed.
Page Tree  


How to Add a new Attitude Parameter
by Dunn Idle
Thanks to Linda Jun for the extra guidance.
Table of Contents  


Introduction
The following procedure was developed for adding a new type of attitude parameter to GMAT. There may be steps still to be documented if a user needs to propagate a variable as part of an entity's internal state, but these instructions will cover the specifics of adding Modified Rodrigues Parameters as part of the Attitude Tab of the Spacecraft GUI Dialog Box. This involves not only converting between parameter types, but getting wxWidgets to show the new parameters as part of the GUI. We will also look at adding these parameters to the Parameter classes at a top level in GMAT so they can be plotted in the XYPlot. We will attempt to provide steps in an order that as you accomplish them, you can recompile and run the code without it crashing. It may not show the new parameters yet, or use them, but hopefully each step will be compatible with continuing operations of GMAT. That way you don't have to accomplish all these steps completely before continuing to use your current solution. Hopefully the entire set of steps below will give the GMAT programmer a feel for what is required to add a new parameter to GMAT. There are many types of parameters in addition to "Attitude" parameters, but this is a real world example of how MRPs were added and includes every step that was needed. Hopefully, the next person to add a parameter will find a large percentage of overlap between what they need to do and what is shown here. This is not as detailed as adding a new "resource" but should serve as a good introduction as to how GMAT is designed with user extensibility in mind.
Step 1:
In the directory src\base\attitude, open the file Attitude.hpp.
Step 2:
Make the following additions to Attitude.hpp –
 In the declaration of the namespace GmatAttitude, add MODIFIED_RODRIGUES_PARAMETERS_TYPE to the AttitudeStateType enumerations list. This will be the fourth type, right under EULER_ANGLES_AND_SEQUENCE_TYPE.
 In the public section of the declaration of the GMAT_API Attitude class, under the heading BEGIN static methods for conversion, right after the last ToQuaternion method is declared, add the following two method declarations.
static Rvector ToQuaternion(const Rvector3 &MRPs);static Rvector3 ToMRPs(const Rvector &quat1);
 In the protected section of the same GMAT_API Attitude class, in the enum declarations of all the attitude enumerations, after the final direction cosine matrix entry, called DCM_33, add the following three enumerations.
MRP_1,MRP_2,MRP_3,
 In the same protected section, in the enum OtherReps declaration of all the "other" possible enumerations, after the QUATERNION entry, add the following.
MRPS,
 Further down in the same protected section, right after the "last computed quaternion" which is declared as Rvector quaternion;, add the following.
Rvector3 mrps;
 In the declaration of the ComputeCosineMatrixAndAngularVelocity method, in the private section, under ValidateQuaternion, add the following method declaration.
bool ValidateMRPs(const Rvector &mrps);
Step 3:
Re build GMAT with the new version of Attitude.hpp.
Step 4:
Next let's modify Attitude.cpp found in the same src\base\attitude directory.
 In the definition of Attitude::PARAMETER_TEXT, add the following under the final direction cosine "DCM33".
"MRP1","MRP2","MRP3",
 Just below that, in the definition of Attitude::PARAMETER_TYPE, in the same location as the variables above (just count positions), add the following.
Gmat::REAL_TYPE, Gmat::REAL_TYPE, Gmat::REAL_TYPE,
 Just below that, in the definition of Attitude::OTHER_REP_TEXT, just under "Quaternion", add the following.
"MRPs",
 Just below that, in the definition of Attitude::OTHER_REP_TYPE, in the same location as "MRPs" above, which is third from the bottom, add the following.
Gmat::RVECTOR_TYPE,
 Next we write and place the methods for converting from quaternions to MRPs, and from MRPs to quaternions. Since this is a procedure for adding a new parameter, we won't worry about the actual algorithms. They are simple enough and we can get them from the Attitude section of the GMAT Math Specification. Just below the final ToQuaternion method, insert the following two new methods.
Rvector Attitude::ToQuaternion(const Rvector3 &MRPs)Rvector3 Attitude::ToMRPs(const Rvector &quat1)
 In the copy constructor Attitude::Attitude(const Attitude& att) : under the quaternion entry, add the following.
mrps (att.mrps),
 In the assignment operator Attitude& Attitude::operator=(const Attitude& att) under the quaternion entry, add the following.
mrps = att.mrps;
 In the bool Attitude::Initialize() method, we are computing the cosine matrix based on the input attitude type. There is a switch statement that is based on input type. This statement is switch (inputAttitudeType). We now have a new type, MRPs, that we need to account for. So we add the new MRP case, right under the DIRECTION_COSINE_MATRIX_TYPE case. We add the following code.
case GmatAttitude::MODIFIED_RODRIGUES_PARAMETERS_TYPE: ValidateMRPs(mrps); quaternion = Attitude::ToQuaternion(mrps); cosMat = Attitude::ToCosineMatrix(quaternion); break;
 Note that in the above step, we still have to write the ValidateMRPs(mrps) method and insert it somewhere below. The ToQuaternion method that takes mrps as an input is something we have already written and inserted above. The method named ToCosineMatrix already exists.
 In the bool Attitude::IsParameterReadOnly(const Integer id) const method, find the logic statement that checks if attitudeDisplayType is "Quaternion". Where it checks if the id is equal to EULER_ANGLE_1 through DCM_33, find an appropriate place to check if id is equal to MRPs. The result should look like this.
if ((id == EULER_ANGLE_1)  (id == EULER_ANGLE_2)  (id == EULER_ANGLE_3)  (id == MRP_1)  (id == MRP_2)  (id == MRP_3)  (id == DCM_11)  (id == DCM_12)  (id == DCM_13)  (id == DCM_21)  (id == DCM_22)  (id == DCM_23)  (id == DCM_31)  (id == DCM_32)  (id == DCM_33)) return true;
 In the same method, find the logic statement that checks if attitudeDisplayType is "DirectionCosineMatrix". Where it checks if id is equal to Q_1 through EULER_ANGLE_3, find an appropriate place to check if id is equal to MRPs. The result should look like this.
if ((id == Q_1)  (id == Q_2)  (id == Q_3)  (id == Q_4)  (id == MRP_1)  (id == MRP_2)  (id == MRP_3)  (id == EULER_ANGLE_1)  (id == EULER_ANGLE_2)  (id == EULER_ANGLE_3)) return true;
 Now it's time to write an entire section for what happens if attitudeDisplayType is "MRPs". Here is what we end up with. We place it right below the direction cosine section from the previous step.
else if (attitudeDisplayType == "MRPs") { #ifdef DEBUG_ATTITUDE_READ_ONLY MessageInterface::ShowMessage( " ....... Attitude::ReadOnly in MRPs section\n"); #endif if ((id == EULER_ANGLE_1)  (id == EULER_ANGLE_2)  (id == EULER_ANGLE_3)  (id == Q_1)  (id == Q_2)  (id == Q_3)  (id == Q_4)  (id == DCM_11)  (id == DCM_12)  (id == DCM_13)  (id == DCM_21)  (id == DCM_22)  (id == DCM_23)  (id == DCM_31)  (id == DCM_32)  (id == DCM_33)) return true; }
 There is only one attitude display type left. That is "else" Euler Angles. In that section we add a line for MRPs. Here is the result.
if ((id == Q_1)  (id == Q_2)  (id == Q_3)  (id == Q_4)  (id == MRP_1)  (id == MRP_2)  (id == MRP_3)  (id == DCM_11)  (id == DCM_12)  (id == DCM_13)  (id == DCM_21)  (id == DCM_22)  (id == DCM_23)  (id == DCM_31)  (id == DCM_32)  (id == DCM_33)) return true;
 Next we go to the real Attitude::GetRealParameter(const Integer id) const method, and add three MRP lines. Just below Euler Angles we add the following.
if (id == MRP_1) { (const_cast(this))>UpdateState("MRPs"); return mrps(0); } if (id == MRP_2) { (const_cast(this))>UpdateState("MRPs"); return mrps(1); } if (id == MRP_3) { (const_cast(this))>UpdateState("MRPs"); return mrps(2); }
 Next we got to the Set Real Parameter method. Syntax for this method is as follows.
Real Attitude::SetRealParameter(const Integer id, const Real value) Right after EULER_ANGLE_3, we add the following. if (id == MRP_1) { (const_cast(this))>UpdateState("MRPs"); mrps(0) = value; inputAttitudeType = GmatAttitude::MODIFIED_RODRIGUES_PARAMETERS_TYPE; if (isInitialized) needsReinit = true; return mrps(0); } if (id == MRP_2) { (const_cast(this))>UpdateState("MRPs"); mrps(1) = value; inputAttitudeType = GmatAttitude::MODIFIED_RODRIGUES_PARAMETERS_TYPE; if (isInitialized) needsReinit = true; return mrps(1); } if (id == MRP_3) { (const_cast(this))>UpdateState("MRPs"); mrps(2) = value; inputAttitudeType = GmatAttitude::MODIFIED_RODRIGUES_PARAMETERS_TYPE; if (isInitialized) needsReinit = true; return mrps(2); }
 Next we add to another Set Real Parameter method. This method is different from the one above because it checks the range of the input parameter index. The calling syntax is as follows.
Real Attitude::SetRealParameter(const Integer id, const Real value, const Integer index) Right after QUATERNION we add the following. if (id == MRPS) { if ((index < 0)  (index > 2)) { throw AttitudeException("Error: the MRPs index is outofrange\n"); } mrps(index) = value; inputAttitudeType = GmatAttitude::MODIFIED_RODRIGUES_PARAMETERS_TYPE; #ifdef DEBUG_ATTITUDE_SET_REAL MessageInterface::ShowMessage(" ... set MRP[%d] = %12.10f\n", index, value); MessageInterface::ShowMessage(" ... MRP = %s\n", quaternion.ToString().c_str()); #endif if (isInitialized) needsReinit = true; return mrps(index); }
 Next is the const Rvector& Attitude::GetRvectorParameter(const Integer id) const method. Just after QUATERNION we add the following.
if (id == MRPS) { (const_cast(this))>UpdateState("MRPs"); vec3 = mrps; return vec3; }
 Next is the Set Rvector Parameter method. The calling syntax is as follows.
const Rvector& Attitude::SetRvectorParameter(const Integer id, const Rvector &value) Just after QUATERNION we add the following. if (id == MRPS) { #ifdef DEBUG_ATTITUDE_SET MessageInterface::ShowMessage("In Attitude::SetRvector, setting MRPs\n"); #endif if (sz != 3) throw AttitudeException( "Incorrectly sized Rvector passed in for MRPs."); ValidateMRPs(value); for (i=0;i<3;i++) mrps = value; quaternion = Attitude::ToQuaternion(mrps); cosMat = Attitude::ToCosineMatrix(quaternion); inputAttitudeType = GmatAttitude::MODIFIED_RODRIGUES_PARAMETERS_TYPE; if (isInitialized) needsReinit = true; return mrps; }
 Next we add two lines to the Set String Parameter method. There are two lines where value is being compared to string values. We add MRPs to the second line to get the following.
if ((value != "Quaternion" ) && (value != "DirectionCosineMatrix") && (value != "EulerAngles") && (value != "MRPs"))
 In the same method, four lines below we add MRPs to get the following.
"\"Quaternion\" \"DirectionCosineMatrix\" \"EulerAngles\" \"MRPs\"");
 In the same method, near the bottom, just after the comment that says to "handle the array values", we add MRPs to get the following two lines.
if ((id == QUATERNION)  (id == EULER_ANGLES)  (id == DIRECTION_COSINE_MATRIX)  (id == MRPS)  (id == ANGULAR_VELOCITY)  (id == EULER_ANGLE_RATES))
 Next we write and insert our own method, bool ValidateMRPs. At this point we may be missing some charateristics such as maximum and minimum possible values that we could use to enhance this algorithm, but at least we have a start.
// // bool ValidateMRPs(const Rvector &mrps) // /** * This method validates the MRPs. * * @param quaternion to validate * * @return flag indicating whether or not the input is a valid quaternion * */ // bool Attitude::ValidateMRPs(const Rvector &mrps) { // There may be some more qualities of MRPs we can check if (mrps.GetSize() != 3) { throw AttitudeException( "The MRP vector must have 3 elements.\n"); } return true; }
 We are at long last down to augmenting our final method. This is the UpdateState method. This method updates the attitude in the representation specified. We start in the section where rep == "Quaternion". We add the following logic.
else if (inputAttitudeType == GmatAttitude::MODIFIED_RODRIGUES_PARAMETERS_TYPE) quaternion = Attitude::ToQuaternion(mrps); Next we go to rep == "EulerAngles". We add the following logic. else if (inputAttitudeType == GmatAttitude::MODIFIED_RODRIGUES_PARAMETERS_TYPE){ quaternion = Attitude::ToQuaternion(mrps); eulerAngles = Attitude::ToEulerAngles(quaternion, (Integer) eulerSequenceArray.at(0), (Integer) eulerSequenceArray.at(1), (Integer) eulerSequenceArray.at(2));}
 Where rep == "DirectionCosineMatrix" we add the following.
else if (inputAttitudeType == GmatAttitude::MODIFIED_RODRIGUES_PARAMETERS_TYPE){ quaternion = Attitude::ToQuaternion(mrps); cosMat = ToCosineMatrix(quaternion);}
 Finally we add an entire section for rep == "MRPs". We add the following.
else if (rep == "MRPs") { if (inputAttitudeType == GmatAttitude::QUATERNION_TYPE) mrps = ToMRPs(quaternion); else if (inputAttitudeType == GmatAttitude::DIRECTION_COSINE_MATRIX_TYPE){ quaternion = Attitude::ToQuaternion(cosMat); mrps = ToMRPs(quaternion);} else if (inputAttitudeType == GmatAttitude::EULER_ANGLES_AND_SEQUENCE_TYPE){ cosMat = ToCosineMatrix(eulerAngles, (Integer) eulerSequenceArray.at(0), (Integer) eulerSequenceArray.at(1), (Integer) eulerSequenceArray.at(2)); quaternion = ToQuaternion(cosMat); mrps = ToMRPs(quaternion); } }
 Finally we compile, rebuild GMAT, and test its functionality.
Step 5:
Next we will modify AttitudePanel.hpp. This is found in the src\GUI\spacecraft directory.
 Halfway down the file, in the declaration of the AttitudePanel: public wxPanel class, in the private: section, in the // string versions of values in text boxes section, just after wxString *eulerAngles[3];, add the following.
wxString *MRPs[3];
 Just below this, in the // local values of attitude data section, right after the declaration of Rvector3 ea;, add the following.
vector3 mrp;
 Just below this, in the /// modification flags section, right after the declaration of bool eaModified[3];, add the following.
bool mrpModified[3];
 Just below this, after the void DisplayDCM(); method declaration, add the following.
void DisplayMRPs();
 Just below this, after the void UpdateCosineMatrix(); method declaration, add the following.
void UpdateMRPs();
 Add MRPs to the StateType enumeration. It should now look like this.
enum StateType { EULER_ANGLES = 0, QUATERNION, DCM, MRPS, attStateTypeCount, };
 Rebuild GMAT with AttitudePanel.hpp updated. Test functionality. At this point it should be broken because we will have changed some of the parameters used in the methods in AttitudePanel.cpp. Everything will have compiled, but there will be linking errors. We can track down each of the linking errors, or go to the next step and modify AttitudePanel.cpp. We will take the second option.
Step 6:
Next we modify AttitudePanel.cpp. It is in the same directory as AttitudePanel.hpp.
 In the const std::string AttitudePanel::STATE_TEXT[attStateTypeCount] method, just below "DirectionCosineMatrix" add MRPs. The method should now look like this.
// These labels show up in the following location in the GUI: //  "Spacecraft" Dialog Box //  "Attitude" Tab //  "Attitude Initial Conditions" Static Box //  "Attitude State Type" Combo Box const std::string AttitudePanel::STATE_TEXT[attStateTypeCount] = { "EulerAngles", "Quaternion", "DirectionCosineMatrix", "MRPs", };
 In the AttitudePanel::Create() method, in the loop under the heading titled //arrays to hold temporary values, add MRPs. The loop should now look like this.
// arrays to hold temporary values unsigned int x; for (x = 0; x < 3; ++x) { eulerAngles[x] = new wxString(); eulerAngleRates[x] = new wxString(); quaternion[x] = new wxString(); MRPs[x] = new wxString(); angVel[x] = new wxString(); }
The observant reader will note that cosineMatrix[x] was replaced by MRPs[x]. This is because the full 9 element array was being populated in three separate statements. Dunn took the final loop and changed its indices to fully populate cosineMatrix in a single location. There is still some awkwardness remaining in this method with the 4 element quaternion string array, but we will leave fixing that to the student as an exercise.
 In the AttitudePanel::LoadData() method, just below the else if logic branch for attStateType == "Quaternion", just below the DisplayQuaternion() statement, add the following code.
else if (attStateType == "MRPs") { Rvector MRPVal = theAttitude>GetRvectorParameter("MRPs"); for (x = 0; x < 3; ++x) { *MRPs[x] = theGuiManager>ToWxString(MRPVal[x]); mrp[x] = MRPVal[x]; } DisplayMRPs(); }
 In the AttitudePanel::SaveData() method, in the logic branch for the condition if (seqModified  isNewAttitude) just below the statement which says useAttitude>SetRvectorParameter("Quaternion", q); add the following.
else if (attStateType == stateTypeArray[MRPS]) useAttitude>SetRvectorParameter("MRPs", mrp);
 In the same AttitudePanel::SaveData() method, in the logic branch if (stateModified  isNewAttitude) just below the statement which says useAttitude>SetRvectorParameter("Quaternion", q); add the following.
else if (attStateType == stateTypeArray[MRPS]) useAttitude>SetRvectorParameter("MRPs", mrp);
This may seem identical to the process in the previous step. The code is the same, but the "if" statement is different. Inspect them closely!
 In the AttitudePanel::IsStateModified method, in the logic branch if ((which=="State")(which=="Both")) just below the statement which says if (qModified[ii]) return true; add the following.
else if (attStateType == stateTypeArray[MRPS]) { for (Integer ii = 0; ii < 3; ii++) if (mrpModified[ii]) return true; }
 In the AttitudePanel::ResetStateFlags method, just below the statement which says qModified[ii] = false; add the following.
for (Integer ii = 0; ii < 3; ii++) mrpModified[ii] = false;
 In the AttitudePanel::ValidateState method, just below the statement which says else q[3] = tmpVal; we need to add an entire section on MRPs. It should look like this.
else if (attStateType == stateTypeArray[MRPS]) { if (mrpModified[0]) { strVal = st1TextCtrl>GetValue(); if (!theScPanel>CheckReal(tmpVal, strVal, "MRP 1", "Real Number")) retval = false; else mrp[0] = tmpVal; } if (mrpModified[1]) { strVal = st2TextCtrl>GetValue(); if (!theScPanel>CheckReal(tmpVal, strVal, "MRP 2", "Real Number")) retval = false; else mrp[1] = tmpVal; } if (mrpModified[2]) { strVal = st3TextCtrl>GetValue(); if (!theScPanel>CheckReal(tmpVal, strVal, "MRP 3", "Real Number")) retval = false; else mrp[2] = tmpVal; } }
 In the AttitudePanel::OnStateTextUpdate method, after the section on Quaternions, add the following.
else if (attStateType == STATE_TEXT[MRPS]) { if (st1TextCtrl>IsModified()) mrpModified[0] = true; if (st2TextCtrl>IsModified()) mrpModified[1] = true; if (st3TextCtrl>IsModified()) mrpModified[2] = true; }
 In the AttitudePanel::OnStateTypeSelection method, after the DisplayDCM(); statement, add the following.
else if (newStateType == stateTypeArray[MRPS]) DisplayMRPs();
 The next step is to write a method called void AttitudePanel::DisplayMRPs(). This method is based on the similar method for displaying Euler Angles. It is an entire standalone method rather than a modification of an existing method. Please see the actual code for this method because it is rather long for a checklist like this.
 In the AttitudePanel::UpdateCosineMatrix() method, just below the conversion to dcmat from EULER_ANGLES, add a conversion to the dcmat from MRPS. It will go through quaternions as an intermediate stage as follows.
else if (attStateType == stateTypeArray[MRPS]) { q = Attitude::ToQuaternion(mrp); dcmat = Attitude::ToCosineMatrix(q); }
 In the AttitudePanel::UpdateQuaternion() method, just below the conversion to q from EULER_ANGLES, add a conversion to q from MRPS. It will look like this.
else if (attStateType == stateTypeArray[MRPS]) { q = Attitude::ToQuaternion(mrp); }
 In the AttitudePanel::UpdateEulerAngles() method, just below converstion to ea from QUATERNION, add a conversion to ea from MRPS. It will look like this.
else if (attStateType == stateTypeArray[MRPS]) { q = Attitude::ToQuaternion(mrp); ea = Attitude::ToEulerAngles(q, (Integer) seq[0], (Integer) seq[1], (Integer) seq[2]) * GmatMathUtil::DEG_PER_RAD; }
 Next add the method AttitudePanel::UpdateMRPs(). Since this is an entire method, please go see the example in the code. It parallels all the other update methods, but this method updates MRPs.
 Finally, compile AttitudePanel.cpp. Then test functionality. This will require linking which hopefully will have no errors now that all the methods in AttitudePanel.cpp match their declarations in AttitudePanel.hpp.
Step 7:
At this point the Attitude Tab of the Spacecraft Dialog Box will let the user choose MRPs.
If the user inputs MRPs, they can be converted to Quaternions, Euler Angles, or the DC Matrix. If the user leaves the representation as MRPs and saves a script, that script will show MRPs. Here is an example.
GMAT ISS.AttitudeDisplayStateType = 'MRPs'; GMAT ISS.AttitudeRateDisplayStateType = 'AngularVelocity'; GMAT ISS.AttitudeCoordinateSystem = 'LVLH'; GMAT ISS.EulerAngleSequence = '321'; GMAT ISS.MRP1 = 0.4142135623; GMAT ISS.MRP2 = 0; GMAT ISS.MRP3 = 0; GMAT ISS.AngularVelocityX = 0; GMAT ISS.AngularVelocityY = 0; GMAT ISS.AngularVelocityZ = 0;
Figure 1  MRPs for ISS Showing 90 Deg Roll
Step 8:
The picture above shows the MRPs from a GMAT script with a model of the ISS which has been rolled. Since the ISS model started with no attitude relative to the ECI reference frame, shown with Red Green and Blue axes, it rolled around both its own XAxis and the red ECI XAxis.
Figure 2  ISS MRPs Converted to Other Three Attitude Representations
Step 9:
The picture above shows the original MRPs converted into the other three basic GMAT attitude representations. Note that in the first figure, the Euler Angle Sequence is 321, or YawPitchRoll. In the Euler Angles display, we see that the third angle, or roll, is 90°. This means the ISS is rolled on its side but it's XAxis is still aligned with ECIX.
Step 10:
Next we modify methods and add parameters so that GMAT can output MRPs in data files and plot them in its standard XYPlot routines. We will start by listing Linda Jun's suggested steps.
 In base/parameter/AttitudeParameters.hpp, add a new Parameter as a new class. This file declares each related Parameter class. If Parameter returns a scalar value, make it derive from AttitudeReal class. If you want to see it in the XY plot, it has to be a scalar Parameter. The following for MRPs was added directly below the declarations for Euler Angles. The excerpt below only shows the code for MRP1. There are two more similar blocks for MRP2 and MRP3.
class GMAT_API MRP1 : public AttitudeReal { public:
MRP1(const std::string &name = "", GmatBase *obj = NULL); MRP1(const MRP1 ©); MRP1& operator=(const MRP1 &right); virtual ~MRP1();
// methods inherited from Parameter virtual bool Evaluate();
// methods inherited from GmatBase virtual GmatBase* Clone(void) const;
protected: };
 Choose an all caps enumeration that is different from the parameter name from the step above. In base/parameter/AttitudeData.hpp, add ATTPARENUM to enum list. For this example, we will make the Modified Rodriguez Parameter enumerations MRP_1, MRP_2, and MRP_3.
 In base/parameter/AttitudeParameters.cpp, add Parameter methods. Parameter names must be unique and can be different from class name. In Evaluate(), change to call AttitudeData::GetAttitudeReal(ATTPARENUM), where ATTPARENUM is the enum named constant. For this example the MRP classes will be MRP1, MRP2, and MRP3. We won't show examples of each method here. Please see the code.
 In base/parameter/AttitudeData.cpp, add implementation for ATTPARENUM in GetAttitudeReal(). For this example we added implementation immediately below the conversion for Euler Angles. It looks like this.
if ((item >= MRP_1) && (item <= MRP_3)) { Rvector quat = Attitude::ToQuaternion(cosMat); Rvector3 mrp = Attitude::ToMRPs(quat); return mrp[item  MRP_1]; }
 In base/factory/ParameterFactory.cpp, in CreateParameter(), under //AttitudeParameters, add the following.
if(ofType == "NewParamName") return new NewParamClass(withName); For the current MRP example, we add the following under Euler Angles. if (ofType == "MRP1") return new MRP1(withName); if (ofType == "MRP2") return new MRP2(withName); if (ofType == "MRP3") return new MRP3(withName);
 In ParameterFactory::ParameterFactory():Factory(Gmat::PARAMETER) , add "NewParamName" into creatables. For the MRP example this is found just below the push_back calls for Euler Angles. It looks like this.
creatables.push_back("MRP1"); creatables.push_back("MRP2"); creatables.push_back("MRP3");
 In base/executive/Moderator.cpp, in CreateDefaultMission(), under the heading //Attitude Parameters, add CreateParameter("NewParamName", "DefaultSC.NewParamName"); so new Parameter can show up in the ParameterSelect DialogBox. For our example, these are immediately below Euler Angles and look like this.
CreateParameter("MRP1", "DefaultSC.MRP1"); CreateParameter("MRP2", "DefaultSC.MRP2"); CreateParameter("MRP3", "DefaultSC.MRP3");
Step 11:
Next we compile GMAT and test it. If it works, it can give MRPs plotted in the XYPlot output screen as shown below for the ISS. In the figure below the ISS model is no longer rolled around its own Xaxis. It is still shown at an interesting angle because now it is aligned with both the velocity vector and nadir for an orbit that is inclined at 50 degrees.
Figure 3  Plotting MRPs for Nadir Stabilized, And No Longer Rolled, International Space Station