Follow this link to skip to the main content

me_joint.cc

Go to the documentation of this file.
00001 // -*-c++-*-
00002 //---------------------------< /-/ CLARAty /-/ >------------------------------
00003 /**
00004  * @file me_joint.cc
00005  *
00006  * The ME_Joint object corresponds to the joint in a mechanism.
00007  * ME_Joint attaches a ME_Body to its parent.
00008  * 
00009  * <br>@b Designer(s):   Antonio Diaz-Calderon,
00010  *                       Won S. Kim,
00011  *                       Hari Das Nayar,
00012  *                       Issa Nesnas
00013  * <br>@b Author(s):     Hari Das Nayar
00014  * <br>@b Date:          March 24, 2004 
00015  *
00016  * <b>Software License:</b><br>
00017  * <code> http://claraty.jpl.nasa.gov/license/open_src/  or 
00018  *        file: license/open_src.txt </code> 
00019  *
00020  * &copy; 2006, Jet Propulsion Laboratory, California Institute of Technology<br>
00021  *
00022  * $Revision: 1.8 $
00023  */
00024 //-----------------------------------------------------------------------------
00025 
00026 #include "claraty/me_joint.h"
00027 #include "claraty/mechanism_model.h"
00028 #include "claraty/me_body.h"
00029 #include "claraty/xml_out.h"
00030 #include "claraty/string_util.h"
00031 
00032 using namespace std;
00033 
00034 namespace claraty {
00035 
00036 //----------------------------------------------------------------------------
00037 /**
00038  * Evaluates the constraint expression and assigns the parts of the
00039  * constraint equation to their respective elements. The constraint
00040  * expression should have the form:
00041  *   [-]slope * dependent_body_name[(joint_value_index)] [+/- offset]
00042  * where
00043  *     slope is the required multiplication factor for the dependence.
00044  *         slope may have an optional negative sign before it.
00045  *     dependent_body_name is the required name of the body that this
00046  *         joint's constraint depends on
00047  *     [(joint_value_index)] is optional and designates the index of
00048  *         the joint value to depend on for multi-dof joints. The default
00049  *         if joint_value_index is not specified is 0.
00050  *     [+/- offset] is the optional positive or negative offset value
00051  *         in the constraint equation.
00052  *
00053  * @return true if successfully parsed the string expression, false if
00054  *         failed.
00055  *
00056  * @ingroup mech_model_pkg
00057  */
00058 bool Joint_Constraint::evaluate_constraint_expression()
00059 {
00060   double slope_sign = 1.0;
00061   _dependent_body_joint_value_index = 0;
00062 
00063   // Make a copy of the string expression
00064   std::string string_expression(s_expr_attribute);
00065 
00066   // Remove white space from the expression and make a copy
00067   for (unsigned int i=0; i<string_expression.length(); ++i)
00068     {
00069       if ((string_expression[i] == ' ')||(string_expression[i] == '\t')
00070           ||(string_expression[i] == '\n')||(string_expression[i] == '\r'))
00071         {
00072           string_expression.erase(i, 1);
00073           --i;
00074         }
00075     }
00076 
00077   if (string_expression[0] == '-')
00078     {
00079       slope_sign = -1.0;
00080       string_expression.erase(0,1);
00081     }
00082 
00083   std::string string_expression_copy = string_expression;
00084 
00085   // if the expression is empty, return false
00086   if (string_expression.length() == 0)
00087     {
00088     return false;
00089     }
00090   // If there is no * char in the string, return false
00091   if (string_expression.find("*", 0) == std::string::npos)
00092     {
00093 
00094     return false;
00095     }
00096   string delims("*+-)( \t\n\r");
00097   string tokens("");
00098   vector<string> out_list = string_get_tokens(string_expression,
00099                                               delims,
00100                                               tokens);
00101 
00102   // Check that the correst number of elements were found in the expression
00103   if ((out_list.size() > 4) || (out_list.size() < 2))
00104     {
00105       return false;
00106     }
00107 
00108   // Check that the multiplication factor was in the right place
00109   if (string_expression_copy[string_expression_copy.find(out_list[1])-1] != '*')
00110     {
00111       return false;
00112     }
00113 
00114   if (out_list.size() == 3)
00115     {
00116       if (string_expression_copy[string_expression_copy.find(out_list[2])-1]
00117           == '(')
00118         {
00119           string_eat_long(out_list[2], _dependent_body_joint_value_index);
00120         }
00121       else if (string_expression_copy[
00122                      string_expression_copy.find(out_list[2])-1] == '+')
00123         {
00124           string_eat_double(out_list[2], _offset);
00125         }
00126       else if (string_expression_copy[
00127                     string_expression_copy.find(out_list[2])-1] == '-')
00128         {
00129           string_eat_double(out_list[2], _offset);
00130           _offset = - _offset;
00131         }
00132       else
00133         {
00134           return false;
00135         }
00136     }
00137 
00138   if (out_list.size() == 4)
00139     {
00140       if (string_expression_copy[string_expression_copy.find(out_list[2])-1]
00141           == '(')
00142         {
00143           string_eat_long(out_list[2], _dependent_body_joint_value_index);
00144         }
00145       else
00146         {
00147           return false;
00148         }
00149 
00150       if (string_expression_copy[string_expression_copy.find(out_list[3])-1]
00151           == '+')
00152         {
00153           string_eat_double(out_list[3], _offset);
00154         }
00155       else if (string_expression_copy[
00156                      string_expression_copy.find(out_list[3])-1] == '-')
00157         {
00158           string_eat_double(out_list[3], _offset);
00159           _offset = - _offset;
00160         }
00161       else
00162         {
00163           return false;
00164         }
00165     }
00166 
00167   string_eat_double(out_list[0], _slope);
00168   _slope *= slope_sign;
00169 
00170   _dependent_body_name = out_list[1];
00171 
00172   return true;
00173 
00174 }
00175 
00176 //----------------------------------------------------------------------------
00177 /**
00178  * Default Constructor for a ME_Joint object.
00179  *
00180  * @ingroup mech_model_pkg
00181  */
00182 ME_Joint::ME_Joint()
00183   : _limited(false),
00184     _number_of_dofs(1),
00185     _parent_frame(0),
00186     _constrained(false),
00187     _stiffness(0),
00188     _constraint(0)
00189 {
00190   _values.resize(_number_of_dofs);
00191   _joint_axis.resize(3);
00192   _joint_axis(0) = 0.0;
00193   _joint_axis(1) = 0.0;
00194   _joint_axis(2) = 1.0;
00195 }
00196 
00197 //----------------------------------------------------------------------------
00198 /**
00199  * Copy Constructor for a ME_Joint object.
00200  *
00201  * @param[in] me_joint The joint to be copied.
00202  *
00203  * @ingroup mech_model_pkg
00204  */
00205 ME_Joint::ME_Joint(ME_Joint & me_joint)
00206 : _index(me_joint._index),
00207   _limited(me_joint._limited),
00208   _offset(me_joint._offset),
00209   _home(me_joint._home),
00210   _number_of_dofs(me_joint._number_of_dofs),
00211   _parent_frame(me_joint._parent_frame),        
00212   _constrained(me_joint._constrained),
00213   _name(me_joint._name),
00214   _limits(me_joint._limits)
00215 
00216 {
00217   if (me_joint._parent_frame)
00218     _parent_frame = new Frame(*me_joint._parent_frame);
00219 
00220   _limits = me_joint._limits;
00221   if (me_joint._stiffness)
00222     {
00223       _stiffness = new Joint_Stiffness(*me_joint._stiffness);
00224     }
00225   if (me_joint._constraint)
00226     {
00227       _constraint = new Joint_Constraint(*me_joint._constraint);
00228     }
00229 
00230   set_type(me_joint._type);
00231   _joint_axis.resize(3);
00232   _joint_axis = me_joint._joint_axis;
00233   _values.resize(_number_of_dofs);
00234 }
00235 
00236 
00237 //----------------------------------------------------------------------------
00238 /**
00239  * Get the number of dofs
00240  *
00241  * @return number of dofs
00242  *
00243  * @ingroup mech_model_pkg
00244  */
00245 int ME_Joint::get_number_dofs()
00246 {
00247   return _number_of_dofs;
00248 }
00249 
00250 //----------------------------------------------------------------------------
00251 /**
00252  * Return the joint name
00253  *
00254  * @return joint name string
00255  *
00256  * @ingroup mech_model_pkg
00257  */
00258 std::string ME_Joint::get_name()
00259 {
00260   return _name;
00261 }
00262 //----------------------------------------------------------------------------
00263 /**
00264  * Set name.
00265  *
00266  * @param[in] name
00267  *
00268  * @ingroup mech_model_pkg
00269  */
00270 void ME_Joint::set_name(std::string name)
00271  {
00272   _name = name;
00273 }
00274 
00275 //----------------------------------------------------------------------------
00276 /**
00277  * 
00278  * Get type.
00279  * @return Type.
00280  *
00281  * @ingroup mech_model_pkg
00282  */
00283 std::string ME_Joint::get_type()
00284 {
00285   return _unresolve_type(_type);
00286 }
00287 
00288 //----------------------------------------------------------------------------
00289 /**
00290  * 
00291  * Get vector values.
00292  *
00293  * @return Vector.
00294  *
00295  * @ingroup mech_model_pkg
00296  */
00297 vector<double> & ME_Joint::get_values_vector()
00298 {
00299   if (_constrained)
00300     {
00301       _values[0] = get_value(0);
00302     }
00303   return _values;
00304 }
00305 
00306 
00307 //----------------------------------------------------------------------------
00308 /**
00309  * Set joint axes.
00310  *
00311  * @param[in] joint_axis  Vector of joint axis.
00312  *
00313  * @ingroup mech_model_pkg
00314  */
00315 void ME_Joint::set_joint_axes(Vector<double> joint_axis)
00316 {
00317   _joint_axis(0) = joint_axis(0);
00318   _joint_axis(1) = joint_axis(1);
00319   _joint_axis(2) = joint_axis(2);
00320 }
00321 
00322 //----------------------------------------------------------------------------
00323 /**
00324  * Set joint axes.
00325  *
00326  * @param[in] joint_axis  Vector of joint axis.
00327  *
00328  * @ingroup mech_model_pkg
00329  */
00330 void ME_Joint::get_joint_axes(Vector<double> & joint_axis)
00331 {
00332   joint_axis(0) = _joint_axis(0);
00333   joint_axis(1) = _joint_axis(1);
00334   joint_axis(2) = _joint_axis(2);
00335 }
00336 
00337 //----------------------------------------------------------------------------
00338 /**
00339  * Get joint axis.
00340  *
00341  * @return Vector.
00342  *
00343  * @ingroup mech_model_pkg
00344  */
00345 Vector<double>& ME_Joint::get_joint_axis()
00346 {
00347   return _joint_axis;
00348 }
00349 //----------------------------------------------------------------------------
00350 /**
00351  * Rotate the joint axis by the specified rotation.
00352  *
00353  * @param[in] rotation  Quaternion specifying rotation
00354  *
00355  * @ingroup mech_model_pkg
00356  */
00357 void ME_Joint::rotate_joint_axes(const Quaternion<double> & rotation)
00358 {
00359   Quaternion<double> rotation_conjugate(rotation);
00360   rotation_conjugate.conjugate();
00361 
00362   set_joint_axes(rotation_conjugate * Point<double>(_joint_axis));
00363 }
00364 //----------------------------------------------------------------------------
00365 /**
00366  * Rotate the joint axis by the specified rotation.
00367  *
00368  * @param[in] rotation Quaternion specifying rotation
00369  *
00370  * @ingroup mech_model_pkg
00371  */
00372 void ME_Joint::rotate_joint_axes(const RMatrix<double> & rotation)
00373 {
00374   Quaternion<double> q_rotation(rotation);
00375   rotate_joint_axes(q_rotation);
00376 }
00377 //----------------------------------------------------------------------------
00378 /**
00379  * Set joint type.
00380  *
00381  * @param[in] type
00382  *
00383  * @ingroup mech_model_pkg
00384  */
00385 void ME_Joint::set_type(short type)
00386 {
00387   _type = type;
00388   switch(_type)
00389     {
00390     case PIN:
00391       _number_of_dofs = 1;
00392       break;
00393     case SLIDER:
00394       _number_of_dofs = 1;
00395       break;
00396     case FULL6DOF:
00397       _number_of_dofs = 6;
00398       break;
00399     case STATIC:
00400       _number_of_dofs = 0;
00401       break;
00402     default:
00403       break;
00404     }
00405   _values.resize(_number_of_dofs);
00406 }
00407 //----------------------------------------------------------------------------
00408 /**
00409  * Set joint type.
00410  *
00411  * @param[in] type
00412  *
00413  * @ingroup mech_model_pkg
00414  */
00415 void ME_Joint::set_type(std::string type)
00416 {
00417   _type = _resolve_type(type);
00418   switch(_type)
00419     {
00420     case PIN:
00421       _number_of_dofs = 1;
00422       break;
00423     case SLIDER:
00424       _number_of_dofs = 1;
00425       break;
00426     case FULL6DOF:
00427       _number_of_dofs = 6;
00428       break;
00429     case STATIC:
00430       _number_of_dofs = 0;
00431       break;
00432     default:
00433       break;
00434     }
00435   _values.resize(_number_of_dofs);
00436 }
00437 
00438 //----------------------------------------------------------------------------
00439 /**
00440  * Interogate if joing is actuated.
00441  *
00442  * @return Ture if actuated, false otherwise.
00443  *
00444  * @ingroup mech_model_pkg
00445  */
00446 bool ME_Joint::is_actuated()
00447 {
00448   return _actuated;
00449 }
00450 
00451 //----------------------------------------------------------------------------
00452 /**
00453  * Set joint actuated value.
00454  *
00455  * @param[in] actuated
00456  *
00457  * @ingroup mech_model_pkg
00458  */
00459 void ME_Joint::set_actuated(bool actuated)
00460 {
00461   _actuated = actuated;
00462 }
00463 
00464 //----------------------------------------------------------------------------
00465 /**
00466  * Get joint offset
00467  *
00468  * @return Joint offset.
00469  *
00470  * @ingroup mech_model_pkg
00471  */
00472 double & ME_Joint::get_offset()
00473 {
00474   return _offset;
00475 }
00476 
00477 //----------------------------------------------------------------------------
00478 /**
00479  * Compute the joint value based on the constraint parameters.
00480  *
00481  * @param[in] dof_index
00482  *
00483  * @return Joint value.
00484  *
00485  * @ingroup mech_model_pkg
00486  */
00487 double ME_Joint::get_value(int dof_index)
00488 {
00489   if (_number_of_dofs == 0)
00490     return 0.0;
00491   if (!_constrained)
00492     return _values[dof_index];
00493   else  // compute the joint joint value based on the constraint parameters
00494     {
00495       return _constraint->get_slope() *
00496         _me_body->get_tree_body(
00497              _constraint->get_dependent_body_name()).get_joint().get_value(
00498                                       _constraint->get_dependent_joint_index())
00499         + _constraint->get_offset();
00500     }
00501 }
00502 //----------------------------------------------------------------------------
00503 /**
00504  * Set joint value.
00505  *
00506  * @param[in] value
00507  * @param[in] dof_index
00508  *
00509  * @ingroup mech_model_pkg
00510  */
00511 void ME_Joint::set_value(double value, int dof_index)
00512 {
00513   if (_number_of_dofs != 0)
00514   _values[dof_index] = value;
00515 }
00516 //----------------------------------------------------------------------------
00517 /**
00518  *  Get joint transform.
00519  *
00520  * @return Transform.
00521  *
00522  * @ingroup mech_model_pkg
00523  */
00524 Transform ME_Joint::get_transform()
00525 {
00526   Transform result;
00527   switch(_type)
00528     {
00529     case PIN:
00530       result = result.rotate_z(_values[0]);
00531       break;
00532     case SLIDER:
00533       result = result.translate_z(_values[0]);
00534       break;
00535     case FULL6DOF:
00536       result = Transform(Point<double>(_values[0], _values[1],_values[2]),
00537                       RMatrix<double>(RPY,_values[3], _values[4], _values[5]));
00538       break;
00539     case STATIC:
00540       result = Transform::identity;
00541     }
00542   return result;
00543 }
00544 
00545 //----------------------------------------------------------------------------
00546 /**
00547  * Set joint offset
00548  *
00549  * @param[in] offset
00550  *
00551  * @ingroup mech_model_pkg
00552  */
00553 void ME_Joint::set_offset(double offset)
00554 {
00555   _offset = offset;
00556 }
00557 
00558 //----------------------------------------------------------------------------
00559 /**
00560  * Get joint home.
00561  *
00562  * @return Home.
00563  *
00564  * @ingroup mech_model_pkg
00565  */
00566 double & ME_Joint::get_home()
00567 {
00568   return _home;
00569 }
00570 
00571 //----------------------------------------------------------------------------
00572 /**
00573  * Set joint home.
00574  *
00575  * @param[in] home
00576  *
00577  * @ingroup mech_model_pkg
00578  */
00579 void ME_Joint::set_home(double home)
00580 {
00581   _home = home;
00582 }
00583 
00584 //----------------------------------------------------------------------------
00585 /**
00586  * Get joint type as a string, revolute, prismatic, full6dof, or fixed_mount.
00587  *
00588  * @param[in] joint_type
00589  *
00590  * @return String representing the joint type.
00591  *
00592  * @ingroup mech_model_pkg
00593  */
00594 std::string ME_Joint::_unresolve_type(int joint_type)
00595 {
00596   if (joint_type == PIN)
00597     return std::string("revolute");
00598   
00599   if (joint_type == SLIDER)
00600     return std::string("prismatic");
00601   
00602   if (joint_type == FULL6DOF)
00603     return std::string("full6dof");
00604   
00605   if (joint_type == STATIC)
00606     return std::string("fixed_mount");
00607   
00608   return std::string("");
00609 }
00610 //----------------------------------------------------------------------------
00611 /**
00612  * Take the string for a joint type and returns the integer which represents
00613  * this joint type.
00614  *
00615  * @param[in] joint_type
00616  * @return The integer representing the joint type.
00617  *
00618  * @ingroup mech_model_pkg
00619  */
00620 int ME_Joint::_resolve_type(std::string joint_type)
00621 {
00622   if (strcmp(joint_type.c_str(), "revolute") == 0)
00623     return PIN;
00624   
00625   if (strcmp(joint_type.c_str(), "prismatic") == 0)
00626     return SLIDER;
00627   
00628   if (strcmp(joint_type.c_str(), "full6dof") == 0)
00629     return FULL6DOF;
00630   
00631   if (strcmp(joint_type.c_str(), "fixed_mount") == 0)
00632     return STATIC;
00633   
00634   return UNDEFINED;
00635 }
00636 
00637 //----------------------------------------------------------------------------
00638 /**
00639  * Get joint limits.
00640  *
00641  * @param[out] joint_min
00642  * @param[out] joint_max
00643  * @param[out] joint_vmax
00644  * @param[out] joint_tmin
00645  * @param[out] joint_tmax
00646  *
00647  * @ingroup mech_model_pkg
00648  */
00649 void ME_Joint::get_limits(double & joint_min, double & joint_max,
00650                                 double & joint_vmax,
00651                                 double & joint_tmin, double & joint_tmax)
00652 {
00653   _limits.get_parameters(joint_min, joint_max, joint_vmax,
00654                                 joint_tmin, joint_tmax);
00655 }
00656 
00657 //----------------------------------------------------------------------------
00658 /**
00659  * Set joint limits.
00660  *
00661  * @param[in] joint_min
00662  * @param[in] joint_max
00663  * @param[in] joint_vmax
00664  * @param[in] joint_tmin
00665  * @param[in] joint_tmax
00666  *
00667  * @ingroup mech_model_pkg
00668  */
00669 void ME_Joint::set_limits(double joint_min, double joint_max,
00670                                 double joint_vmax,
00671                                 double joint_tmin, double joint_tmax)
00672 {  
00673   _limits.set_parameters(joint_min, joint_max, joint_vmax,
00674                                 joint_tmin, joint_tmax);
00675   _limited = true;
00676 }
00677 
00678 //----------------------------------------------------------------------------
00679 /**
00680  * Get joint stiffness.
00681  *
00682  * @param[out] kx
00683  * @param[out] ky
00684  * @param[out] kz
00685  *
00686  * @ingroup mech_model_pkg
00687  */
00688 void ME_Joint::get_stiffness(double & kx, double & ky, double & kz)
00689 {
00690   if(_stiffness)
00691     _stiffness->get_parameters(kx, ky, kz);
00692   else
00693     kx = 0.0; ky = 0.0; kz = 0.0;
00694 }
00695 
00696 //----------------------------------------------------------------------------
00697 /**
00698  * Set joint stiffness.
00699  *
00700  * @param[in] kx
00701  * @param[in] ky
00702  * @param[in] kz
00703  *
00704  * @ingroup mech_model_pkg
00705  */
00706 void ME_Joint::set_stiffness(double kx, double ky, double kz)
00707 {
00708   if (!_stiffness)
00709     _stiffness = new Joint_Stiffness();
00710   _stiffness->set_parameters(kx, ky, kz);
00711 }
00712 
00713 //----------------------------------------------------------------------------
00714 /**
00715  * Get joint constraint expression.
00716  *
00717  * @return String of the expression for the constraints.
00718  *
00719  * @ingroup mech_model_pkg
00720  */
00721 std::string ME_Joint::get_constraint_expression()
00722 {
00723   if (_constraint)
00724     return _constraint->get_parameters();
00725   else
00726     return ("");
00727 }
00728 //----------------------------------------------------------------------------
00729 /**
00730  * Set joint contstraint expression.
00731  *
00732  * @param[in]  expr The joint constraint expression.
00733  *
00734  * @ingroup mech_model_pkg
00735  */
00736 void ME_Joint::set_constraint_expression(std::string expr)
00737 {
00738   if (!_constraint)
00739     _constraint = new Joint_Constraint;
00740   _constraint->set_parameters(expr);
00741   if ((expr.length() > 0) &&(_constraint->evaluate_constraint_expression()))
00742     _constrained = true;
00743   else
00744     {
00745       delete _constraint;
00746       _constraint = NULL;
00747       _constrained = false;
00748     }
00749 
00750 }
00751 //----------------------------------------------------------------------------
00752 /**
00753  * Output to ostream
00754  *
00755  * @param[out] os  ostream to put output on.
00756  * @param[in]  me_joint  ME_Joint object to be output.
00757  * @return ostream with output on it.
00758  *
00759  * @ingroup mechanism_model_io_pkg
00760  */
00761 std::ostream& operator<< (std::ostream& os, ME_Joint & me_joint)
00762 {
00763   std::string spacing = "";
00764   std::string child_spacing = "";
00765   spacing.append(XML_Out::get_indentation_space(), ' ');
00766   child_spacing.append(XML_Out::get_indentation_space()
00767                        + XML_Out::get_tab_space(), ' ');  
00768   os << spacing;
00769   os << "<ME_Joint ";
00770   os << "name = \""     << me_joint.get_name() << "\" ";
00771   
00772   os << "type = \""     << me_joint.get_type() << "\" ";
00773   
00774   os << "actuated = \"" << (me_joint.is_actuated() ? "true" : "false")
00775      << "\" " ;
00776   
00777   os << "offset = \""   << me_joint.get_offset() << "\" ";
00778 
00779   os << "home = \""     << me_joint.get_home() << "\" ";
00780   
00781   //  Vector<double> joint_axis(3);
00782   //  me_joint.get_joint_axes(joint_axis);
00783   //  os << "x_axis = \""     << joint_axis(0) << "\" ";
00784   //  os << "y_axis = \""     << joint_axis(1) << "\" ";
00785   //  os << "z_axis = \""     << joint_axis(2) << "\" ";
00786 
00787   os << ">" << endl;
00788   
00789 
00790   double jmin, jmax, vmax, tmin, tmax;
00791   me_joint.get_limits(jmin, jmax, vmax, tmin, tmax);
00792   os << child_spacing;
00793   os << "<Joint_Limits ";
00794   os << "min = \""         <<  jmin << "\" ";
00795   os << "max = \""         <<  jmax << "\" ";
00796   os << "vmax = \""        <<  vmax << "\" ";
00797   os << "torque_min = \""  <<  tmin << "\" ";
00798   os << "torque_max = \""  <<  tmax << "\" ";
00799   os << "/>" << endl;
00800   
00801   double kx, ky, kz;
00802   me_joint.get_stiffness(kx, ky, kz);
00803   os << child_spacing;
00804   os << "<Joint_Stiffness ";
00805   os << "kx = \""  << kx << "\" ";
00806   os << "ky = \""  << ky << "\" ";
00807   os << "kz = \""  << kz << "\" ";
00808   os << "/>" << endl;
00809   
00810   os << child_spacing;
00811   os << "<Joint_Constraint ";
00812   os << "expr = \"" << me_joint.get_constraint_expression() << "\" ";
00813   os << "/>" << endl;
00814   
00815   os << spacing;
00816   os << "</ME_Joint> \n";
00817 
00818   return os;
00819 }
00820 //----------------------------------------------------------------------------
00821 //
00822 
00823 } // namespace claraty