1 /* -------------------------------------------------------------------------- *
2  *                            OpenSim:  Joint.cpp                             *
3  * -------------------------------------------------------------------------- *
4  * The OpenSim API is a toolkit for musculoskeletal modeling and simulation.  *
5  * See http://opensim.stanford.edu and the NOTICE file for more information.  *
6  * OpenSim is developed at Stanford University and supported by the US        *
7  * National Institutes of Health (U54 GM072970, R24 HD065690) and by DARPA    *
8  * through the Warrior Web program.                                           *
9  *                                                                            *
10  * Copyright (c) 2005-2017 Stanford University and the Authors                *
11  * Author(s): Ajay Seth, Frank C. Anderson                                    *
12  *                                                                            *
13  * Licensed under the Apache License, Version 2.0 (the "License"); you may    *
14  * not use this file except in compliance with the License. You may obtain a  *
15  * copy of the License at http://www.apache.org/licenses/LICENSE-2.0.         *
16  *                                                                            *
17  * Unless required by applicable law or agreed to in writing, software        *
18  * distributed under the License is distributed on an "AS IS" BASIS,          *
19  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   *
20  * See the License for the specific language governing permissions and        *
21  * limitations under the License.                                             *
22  * -------------------------------------------------------------------------- */
23 
24 //==============================================================================
25 // INCLUDES
26 //==============================================================================
27 #include "Joint.h"
28 #include <OpenSim/Simulation/Model/Model.h>
29 #include <OpenSim/Simulation/Model/PhysicalFrame.h>
30 #include <OpenSim/Simulation/Model/PhysicalOffsetFrame.h>
31 #include "simbody/internal/Constraint.h"
32 #include "simbody/internal/MobilizedBody_Ground.h"
33 
34 //==============================================================================
35 // STATICS
36 //==============================================================================
37 using namespace std;
38 using namespace SimTK;
39 using namespace OpenSim;
40 
41 //==============================================================================
42 // CONSTRUCTORS AND DESTRUCTOR
43 //==============================================================================
~Joint()44 Joint::~Joint()
45 {
46 }
47 
Joint()48 Joint::Joint() : Super()
49 {
50     setNull();
51     constructProperties();
52 }
53 
Joint(const std::string & name,const PhysicalFrame & parent,const PhysicalFrame & child)54 Joint::Joint(const std::string&    name,
55              const PhysicalFrame&  parent,
56              const PhysicalFrame&  child) : Joint()
57 {
58     OPENSIM_THROW_IF(name.empty(), ComponentHasNoName, getClassName());
59 
60     setName(name);
61     connectSocket_parent_frame(parent);
62     connectSocket_child_frame(child);
63 }
64 
Joint(const std::string & name,const PhysicalFrame & parent,const SimTK::Vec3 & locationInParent,const SimTK::Vec3 & orientationInParent,const PhysicalFrame & child,const SimTK::Vec3 & locationInChild,const SimTK::Vec3 & orientationInChild)65 Joint::Joint(const std::string&    name,
66              const PhysicalFrame&  parent,
67              const SimTK::Vec3&    locationInParent,
68              const SimTK::Vec3&    orientationInParent,
69              const PhysicalFrame&  child,
70              const SimTK::Vec3&    locationInChild,
71              const SimTK::Vec3&    orientationInChild) : Joint()
72 {
73     OPENSIM_THROW_IF(name.empty(), ComponentHasNoName, getClassName());
74 
75     setName(name);
76 
77     // PARENT TRANSFORM
78     Rotation parentRotation(BodyRotationSequence,
79         orientationInParent[0], XAxis,
80         orientationInParent[1], YAxis,
81         orientationInParent[2], ZAxis);
82     SimTK::Transform parentTransform(parentRotation, locationInParent);
83 
84     // Define the offset frame that the joint connects to in the parent
85     PhysicalOffsetFrame pInPo( parent.getName() + "_offset",
86                                parent,
87                                parentTransform);
88 
89     // Append the offset frame to the Joint's internal list of frames
90     int pix = append_frames(pInPo);
91 
92     // CHILD TRANSFORM
93     Rotation childRotation(BodyRotationSequence,
94         orientationInChild[0], XAxis,
95         orientationInChild[1], YAxis,
96         orientationInChild[2], ZAxis);
97     SimTK::Transform childTransform(childRotation, locationInChild);
98 
99     PhysicalOffsetFrame cInCo( child.getName() + "_offset",
100                                child,
101                                childTransform);
102 
103     // Append the child offset frame to the Joint's internal list of frames
104     int cix = append_frames(cInCo);
105 
106     // finalize recognizes the offset frames as the Joint's subcomponents
107     finalizeFromProperties();
108 
109     // When the PhysicalOffsetFrames are constructed they are unaware that this
110     // Joint contains them as subcomponents and the path name associated with
111     // them will not be valid. This a temporary fix to set the name once the
112     // added frames have been included as subcomponents which occurs during
113     // finalizeFromProperties() above.
114     static_cast<PhysicalOffsetFrame&>(upd_frames(pix)).setParentFrame(parent);
115     static_cast<PhysicalOffsetFrame&>(upd_frames(cix)).setParentFrame(child);
116 
117     connectSocket_parent_frame(upd_frames(pix));
118     connectSocket_child_frame(upd_frames(cix));
119 }
120 
121 //=============================================================================
122 // CONSTRUCTION Utility
123 //=============================================================================
constructCoordinate(Coordinate::MotionType mt,unsigned idx)124 Joint::CoordinateIndex Joint::constructCoordinate(Coordinate::MotionType mt,
125                                                   unsigned idx)
126 {
127     Coordinate* coord = new Coordinate();
128     coord->setName(getName() + "_coord_" + std::to_string( numCoordinates() ));
129     // Joint takes ownership
130     coord->setJoint(*this);
131     updProperty_coordinates().adoptAndAppendValue(coord);
132     auto cix = CoordinateIndex(getProperty_coordinates().
133                                findIndexForName( coord->getName() ));
134     _motionTypes.push_back(mt);
135     SimTK_ASSERT_ALWAYS(static_cast<unsigned>(numCoordinates()) ==
136                         _motionTypes.size(),
137                         "Joint::constructCoordinate() MotionTypes do not "
138                         "correspond to coordinates");
139     SimTK_ASSERT_ALWAYS(static_cast<unsigned>(cix) == idx,
140                         "Joint::constructCoordinate() must be passed "
141                         "enumerations in the same order as the enumerations "
142                         "have been defined");
143     return cix;
144 }
145 
146 //_____________________________________________________________________________
147 /**
148  * Set the data members of this Joint to their null values.
149  */
setNull()150 void Joint::setNull()
151 {
152     setAuthors("Ajay Seth");
153     isReversed = false;
154 }
155 
156 //_____________________________________________________________________________
157 /**
158  * Construct properties and initialize their default values.
159  */
constructProperties()160 void Joint::constructProperties()
161 {
162     // Generalized coordinates
163     constructProperty_coordinates();
164 
165     //Default frames list is empty
166     constructProperty_frames();
167 }
168 
extendFinalizeFromProperties()169 void Joint::extendFinalizeFromProperties()
170 {
171     Super::extendFinalizeFromProperties();
172 
173     // add all coordinates listed under this joint
174     for (int i = 0; i < numCoordinates(); ++i)
175         upd_coordinates(i).setJoint(*this);
176 }
177 
178 //=============================================================================
179 // GET AND SET
180 //=============================================================================
181 //-----------------------------------------------------------------------------
182 // CHILD Frame
183 //-----------------------------------------------------------------------------
getChildFrame() const184 const PhysicalFrame& Joint::getChildFrame() const
185 {
186     return getSocket<PhysicalFrame>("child_frame").getConnectee();
187 }
188 
189 //-----------------------------------------------------------------------------
190 // PARENT BODY
191 //-----------------------------------------------------------------------------
getParentFrame() const192 const OpenSim::PhysicalFrame& Joint::getParentFrame() const
193 {
194     return getSocket<PhysicalFrame>("parent_frame").getConnectee();
195 }
196 
getCoordinate() const197 const Coordinate& Joint::getCoordinate() const {
198     OPENSIM_THROW_IF(numCoordinates() == 0,
199                      JointHasNoCoordinates);
200     OPENSIM_THROW_IF(numCoordinates() > 1,
201                      InvalidCall,
202                      "Joint has more than one coordinate. Use get_coordinates() "
203                      "or the getCoordinate() method defined in the concrete "
204                      "class instead.");
205 
206     return get_coordinates(0);
207 }
208 
updCoordinate()209 Coordinate& Joint::updCoordinate() {
210     OPENSIM_THROW_IF(numCoordinates() == 0,
211                      JointHasNoCoordinates);
212     OPENSIM_THROW_IF(numCoordinates() > 1,
213                      InvalidCall,
214                      "Joint has more than one coordinate. Use upd_coordinates() "
215                      "or the updCoordinate() method defined in the concrete "
216                      "class instead.");
217 
218     return upd_coordinates(0);
219 }
220 
getMotionType(CoordinateIndex cix) const221 Coordinate::MotionType Joint::getMotionType(CoordinateIndex cix) const
222 {
223     OPENSIM_THROW_IF(cix >= _motionTypes.size(), Exception,
224         "Joint::getMotionType() given an invalid CoordinateIndex");
225     return _motionTypes[cix];
226 }
227 
setMotionType(CoordinateIndex cix,Coordinate::MotionType mt)228 void Joint::setMotionType(CoordinateIndex cix, Coordinate::MotionType mt)
229 {
230     int nc = numCoordinates();
231 
232     // Ensure that coordinate index is less than the number of coordinates
233     // this Joint has in its CoordinateSet.
234     OPENSIM_THROW_IF(cix >= nc, Exception,
235         "Joint::setMotionType() for an invalid CoordinateIndex");
236     // Grow the size of _motionTypes (array) if it is less than the number of
237     // coordinates. Joint's _motionTypes must correspond to its CoordinateSet.
238     if (_motionTypes.size() < static_cast<unsigned>(nc))
239         _motionTypes.resize(nc);
240 
241     _motionTypes[cix] = mt;
242 }
243 
244 
245 //_____________________________________________________________________________
246 /**
247  * Check if a coordinate is used by the Joint.
248  *
249  * @param aCoordinate Coordinate to look for in joint.
250  * @return True if the coordinate is used.
251  */
isCoordinateUsed(const Coordinate & aCoordinate) const252 bool Joint::isCoordinateUsed(const Coordinate& aCoordinate) const
253 {
254     for(int i = 0; i < numCoordinates(); ++i) {
255         if(&get_coordinates(i) == &aCoordinate) return true;
256     }
257 
258     return false;
259 }
260 
261 
addFrame(PhysicalOffsetFrame * frame)262 void Joint::addFrame(PhysicalOffsetFrame* frame)
263 {
264     OPENSIM_THROW_IF(isComponentInOwnershipTree(frame),
265                      ComponentAlreadyPartOfOwnershipTree,
266                      frame->getName(), getName());
267     updProperty_frames().adoptAndAppendValue(frame);
268     finalizeFromProperties();
269     prependComponentPathToConnecteePath(*frame);
270 }
271 
272 const SimTK::MobilizedBodyIndex Joint::
getMobilizedBodyIndex(const OpenSim::Body & body) const273     getMobilizedBodyIndex(const OpenSim::Body& body) const
274 {
275         return body.getMobilizedBodyIndex();
276 }
277 
setChildMobilizedBodyIndex(const SimTK::MobilizedBodyIndex index) const278 void Joint::setChildMobilizedBodyIndex(const SimTK::MobilizedBodyIndex index) const
279 {
280     getChildFrame().setMobilizedBodyIndex(index);
281 }
282 
283 
extendConnectToModel(Model & model)284 void Joint::extendConnectToModel(Model& model)
285 {
286     Super::extendConnectToModel(model);
287 
288     auto& parent = updSocket<PhysicalFrame>("parent_frame").getConnectee();
289     auto& child = updSocket<PhysicalFrame>("child_frame").getConnectee();
290 
291     OPENSIM_THROW_IF(&parent == &child, JointFramesAreTheSame,
292         getName(), parent.getName());
293 }
294 
extendAddToSystem(SimTK::MultibodySystem & system) const295 void Joint::extendAddToSystem(SimTK::MultibodySystem& system) const
296 {
297     Super::extendAddToSystem(system);
298 
299     // The parent node in the multibody tree must part of the system
300     if(isReversed)
301         // this will be the child if the joint definition is reversed
302         getSocket<PhysicalFrame>("child_frame").getConnectee().addToSystem(system);
303     else // otherwise it is the parent frame
304         getSocket<PhysicalFrame>("parent_frame").getConnectee().addToSystem(system);
305 }
306 
extendInitStateFromProperties(SimTK::State & s) const307 void Joint::extendInitStateFromProperties(SimTK::State& s) const
308 {
309     Super::extendInitStateFromProperties(s);
310 
311     for (int i = 0; i < numCoordinates(); ++i)
312         get_coordinates(i).extendInitStateFromProperties(s);
313 }
314 
extendSetPropertiesFromState(const SimTK::State & state)315 void Joint::extendSetPropertiesFromState(const SimTK::State& state)
316 {
317     Super::extendSetPropertiesFromState(state);
318 
319     for (int i = 0; i < numCoordinates(); ++i)
320         upd_coordinates(i).extendSetPropertiesFromState(state);
321 }
322 
323 
324 //=============================================================================
325 // Computation
326 //=============================================================================
327 /* Calculate the equivalent spatial force, FB_G, acting on the body connected by
328    this joint at its location B, expressed in ground.  */
calcEquivalentSpatialForce(const SimTK::State & s,const SimTK::Vector & mobilityForces) const329 SimTK::SpatialVec Joint::calcEquivalentSpatialForce(const SimTK::State &s,
330     const SimTK::Vector &mobilityForces) const
331 {
332     // The number of mobilities for the entire system.
333     int nm = _model->getMatterSubsystem().getNumMobilities();
334 
335     if(nm != mobilityForces.size()){
336         throw Exception("Joint::calcEquivalentSpatialForce(): input mobilityForces does not match model's mobilities");
337     }
338 
339     const SimTK::MobilizedBodyIndex &mbx = getChildFrame().getMobilizedBodyIndex();
340     // build a unique list of underlying MobilizedBodies that are involved
341     // with this Joint in addition to and not including that of the child body
342 
343     std::set<SimTK::MobilizedBodyIndex> mbds;
344 
345     for(int i = 0; i < numCoordinates(); ++i) {
346         const MobilizedBodyIndex& coordsMbx = get_coordinates(i).getBodyIndex();
347         if (coordsMbx != mbx){
348             mbds.insert(coordsMbx);
349         }
350     }
351 
352     SimTK::SpatialVec FB_G = calcEquivalentSpatialForceForMobilizedBody(s, mbx, mobilityForces);
353     SimTK::SpatialVec FBx_G;
354 
355     std::set<SimTK::MobilizedBodyIndex>::const_iterator it = mbds.begin();
356 
357     //const SimTK::MobilizedBody &G = getModel().getMatterSubsystem().getGround();
358     //const SimTK::MobilizedBody &B = getModel().getMatterSubsystem().getMobilizedBody(mbx);
359     //SimTK::Vec3 r_BG =
360     //    B.expressVectorInAnotherBodyFrame(s, B.getOutboardFrame(s).p(), G);
361 
362     while(it != mbds.end()){
363         FBx_G = calcEquivalentSpatialForceForMobilizedBody(s, *it, mobilityForces);
364 
365         //const SimTK::MobilizedBody &b =
366         //   getModel().getMatterSubsystem().getMobilizedBody(*it);
367 
368 
369         //SimTK::Vec3 r_bG =
370         //    b.expressVectorInAnotherBodyFrame(s, b.getOutboardFrame(s).p(), G);
371 
372         // Torques add and include term due to offset in forces
373         FB_G += FBx_G; // shiftForceFromTo(FBx_G, r_bG, r_BG);
374         ++it;
375     }
376 
377     return FB_G;
378 }
379 
380 /** Joints only produce power when internal constraint forces have components along
381     the mobilities of the joint (for example to satisfy prescribed motion). In
382     which case the joint power is the constraint forces projected onto the mobilities
383     multiplied by the mobilities (internal coordinate velocities). Only constraints
384     internal to the joint are accounted for, not external constraints that affect
385     joint motion. */
calcPower(const SimTK::State & s) const386 double Joint::calcPower(const SimTK::State &s) const
387 {
388     double power = 0;
389     for(int i = 0; i < numCoordinates(); ++i) {
390         if (get_coordinates(i).isPrescribed(s)) {
391             // get the reaction force for this coordinate prescribed motion constraint
392             const SimTK::Constraint &pc =
393                 _model->updMultibodySystem().updMatterSubsystem()
394                     .getConstraint(get_coordinates(i)._prescribedConstraintIndex);
395             power += pc.calcPower(s);
396         }
397     }
398 
399     return power;
400 }
401 
402 //=============================================================================
403 // Helper
404 //=============================================================================
405 /* Calculate the equivalent spatial force, FB_G, acting on a mobilized body specified
406    by index acting at its mobilizer frame B, expressed in ground.  */
calcEquivalentSpatialForceForMobilizedBody(const SimTK::State & s,const SimTK::MobilizedBodyIndex mbx,const SimTK::Vector & mobilityForces) const407 SimTK::SpatialVec Joint::calcEquivalentSpatialForceForMobilizedBody(const SimTK::State &s,
408     const SimTK::MobilizedBodyIndex mbx, const SimTK::Vector &mobilityForces) const
409 {
410     // Get the mobilized body
411     const SimTK::MobilizedBody mbd    = getModel().getMatterSubsystem().getMobilizedBody(mbx);
412     const SimTK::UIndex        ustart = mbd.getFirstUIndex(s);
413     const int                  nu     = mbd.getNumU(s);
414 
415     const SimTK::MobilizedBody ground = getModel().getMatterSubsystem().getGround();
416 
417     if (nu == 0) // No mobility forces (weld joint?).
418         return SimTK::SpatialVec(SimTK::Vec3(0), SimTK::Vec3(0));
419 
420     // Construct the H (joint Jacobian, joint transition) matrix for this mobilizer
421     SimTK::Matrix transposeH_PB_w(nu, 3);
422     SimTK::Matrix transposeH_PB_v(nu, 3);
423     // from individual columns
424     SimTK::SpatialVec Hcol;
425 
426     // To obtain the joint Jacobian, H_PB (H_FM in Simbody) need to be realized to at least position
427     _model->getMultibodySystem().realize(s, SimTK::Stage::Position);
428 
429     SimTK::Vector f(nu, 0.0);
430     for(int i =0; i<nu; ++i){
431         f[i] = mobilityForces[ustart + i];
432         // Get the H matrix for this Joint by constructing it from the operator H*u
433         Hcol = mbd.getH_FMCol(s, SimTK::MobilizerUIndex(i));
434         const SimTK::Vector hcolw(Hcol[0]);
435         const SimTK::Vector hcolv(Hcol[1]);
436 
437         transposeH_PB_w[i] = ~hcolw;
438         transposeH_PB_v[i] = ~hcolv;
439     }
440 
441     // Spatial force and torque vectors
442     SimTK::Vector Fv(3, 0.0), Fw(3, 0.0);
443 
444     // Solve the pseudoinverse problem of Fv = pinv(~H_PB_G_v)*f;
445     SimTK::FactorQTZ pinvForce(transposeH_PB_v);
446 
447     //if rank = 0, body force cannot contribute to the mobility force
448     if(pinvForce.getRank() > 0)
449         pinvForce.solve(f, Fv);
450 
451     // Now solve the pseudoinverse for torque for any unaccounted f: Fw = pinv(~H_PB_G_w)*(f - ~H_PB_G_v*Fv);
452     SimTK::FactorQTZ pinvTorq(transposeH_PB_w);
453 
454     //if rank = 0, body torque cannot contribute to the mobility force
455     if(pinvTorq.getRank() > 0)
456         pinvTorq.solve(f, Fw);
457 
458     // Now we have two solution with either the body force Fv or body torque accounting for some or all of f
459     SimTK::Vector fv =  transposeH_PB_v*Fv;
460     SimTK::Vector fw =  transposeH_PB_w*Fw;
461 
462     // which to choose? Choose the more effective as fx.norm/Fx.norm
463     if(fv.norm() > SimTK::SignificantReal){ // if body force can contributes at all
464         // if body torque can contribute too and it is more effective
465         if(fw.norm() > SimTK::SignificantReal){
466             if (fw.norm()/Fw.norm() > fv.norm()/Fv.norm() ){
467                 // account for f using torque, Fw, so compute Fv with remainder
468                 pinvForce.solve(f-fw, Fv);
469             }else{
470                 // account for f using force, Fv, first and Fw from remainder
471                 pinvTorq.solve(f-fv, Fw);
472             }
473         }
474         // else no torque contribution and Fw should be zero
475     }
476     // no force contribution but have a torque
477     else if(fw.norm() > SimTK::SignificantReal){
478         // just Fw
479     }
480     else{
481         // should be the case where gen force is zero.
482         assert(f.norm() < SimTK::SignificantReal);
483     }
484 
485     // The spatial forces above are expressed in the joint frame of the parent
486     // Transform from parent joint frame, P into the parent body frame, Po
487     const SimTK::Rotation R_PPo = (mbd.getInboardFrame(s).R());
488 
489     // Re-express forces in ground, first by describing force in the parent, Po,
490     // frame instead of joint frame
491     SimTK::Vec3 vecFw = R_PPo*SimTK::Vec3::getAs(&Fw[0]);
492     SimTK::Vec3 vecFv = R_PPo*SimTK::Vec3::getAs(&Fv[0]);
493 
494     //Force Acting on joint frame, B,  in child body expressed in Parent body, Po
495     SimTK::SpatialVec FB_Po(vecFw, vecFv);
496 
497     const MobilizedBody parent = mbd.getParentMobilizedBody();
498     // to apply spatial forces on bodies they must be expressed in ground
499     vecFw = parent.expressVectorInAnotherBodyFrame(s, FB_Po[0], ground);
500     vecFv = parent.expressVectorInAnotherBodyFrame(s, FB_Po[1], ground);
501 
502     // Package resulting torque and force as a spatial vec
503     SimTK::SpatialVec FB_G(vecFw, vecFv);
504 
505     return FB_G;
506 }
507 
updateFromXMLNode(SimTK::Xml::Element & aNode,int versionNumber)508 void Joint::updateFromXMLNode(SimTK::Xml::Element& aNode, int versionNumber)
509 {
510     int documentVersion = versionNumber;
511     //bool converting = false;
512     if (documentVersion < XMLDocument::getLatestVersion()){
513         if (documentVersion<30500){
514             XMLDocument::renameChildNode(aNode, "location", "location_in_child");
515             XMLDocument::renameChildNode(aNode, "orientation", "orientation_in_child");
516         }
517         // Version 30501 converted Connector_Body_ to Connector_PhysicalFrame_
518         if (documentVersion < 30501) {
519             // Handle any models that have the Joint connecting to Bodies instead
520             // of PhyscialFrames
521             XMLDocument::renameChildNode(aNode, "Connector_Body_",
522                                                 "Connector_PhysicalFrame_");
523         }
524         // Version 30505 changed "parent_body" connector name to "parent_frame"
525         // Convert location and orientation into PhysicalOffsetFrames owned by the Joint
526         if (documentVersion < 30505) {
527             // Elements for the parent and child names the joint connects
528             SimTK::Xml::element_iterator parentNameElt;
529             SimTK::Xml::element_iterator childNameElt;
530             // The names of the two PhysicalFrames this joint connects
531             std::string parentFrameName("");
532             std::string childFrameName("");
533 
534             SimTK::Xml::element_iterator connectors_node = aNode.element_begin("connectors");
535 
536             SimTK::Xml::element_iterator connectorElement =
537                 connectors_node->element_begin("Connector_PhysicalFrame_");
538             while (connectorElement != aNode.element_end()) {
539                 // If connector name is "parent_body" rename it to "parent_frame"
540                 if (connectorElement->getRequiredAttributeValue("name") == "parent_body") {
541                     connectorElement->setAttributeValue("name", "parent_frame");
542                 }
543                 // If connector name is "parent_frame" get the name of the connectee
544                 if (connectorElement->getRequiredAttributeValue("name") == "parent_frame"){
545                     parentNameElt = connectorElement->element_begin("connectee_name");
546                     parentNameElt->getValueAs<std::string>(parentFrameName);
547 
548                     const auto slashLoc = parentFrameName.rfind('/');
549                     if (slashLoc != std::string::npos)
550                         parentFrameName = parentFrameName.substr(slashLoc + 1);
551                 }
552                 if (connectorElement->getRequiredAttributeValue("name") == "child_body") {
553                     connectorElement->setAttributeValue("name", "child_frame");
554                 }
555                 if (connectorElement->getRequiredAttributeValue("name") == "child_frame") {
556                     childNameElt =  connectorElement->element_begin("connectee_name");
557                     childNameElt->getValueAs<std::string>(childFrameName);
558                     const auto slashLoc = childFrameName.rfind('/');
559                     if (slashLoc != std::string::npos)
560                         childFrameName = childFrameName.substr(slashLoc + 1);
561                 }
562                 ++connectorElement;
563             }
564 
565             SimTK::Xml::element_iterator locParentElt =
566                 aNode.element_begin("location_in_parent");
567             SimTK::Xml::element_iterator orientParentElt =
568                 aNode.element_begin("orientation_in_parent");
569             SimTK::Xml::element_iterator locChildElt =
570                 aNode.element_begin("location_in_child");
571             SimTK::Xml::element_iterator orientChildElt =
572                 aNode.element_begin("orientation_in_child");
573 
574             Vec3 location_in_parent(0);
575             Vec3 orientation_in_parent(0);
576             Vec3 location_in_child(0);
577             Vec3 orientation_in_child(0);
578 
579             if (locParentElt != aNode.element_end()){
580                 locParentElt->getValueAs<Vec3>(location_in_parent);
581             }
582             if (orientParentElt != aNode.element_end()){
583                 orientParentElt->getValueAs<Vec3>(orientation_in_parent);
584             }
585             if (locChildElt != aNode.element_end()){
586                 locChildElt->getValueAs<Vec3>(location_in_child);
587             }
588             if (orientChildElt != aNode.element_end()){
589                 orientChildElt->getValueAs<Vec3>(orientation_in_child);
590             }
591 
592             // now append updated frames to the property list for
593             // both parent and child
594             XMLDocument::addPhysicalOffsetFrame30505_30517(aNode, parentFrameName+"_offset",
595                 parentFrameName, location_in_parent, orientation_in_parent);
596             parentNameElt->setValue(parentFrameName + "_offset");
597 
598             XMLDocument::addPhysicalOffsetFrame30505_30517(aNode, childFrameName + "_offset",
599                 childFrameName, location_in_child, orientation_in_child);
600             childNameElt->setValue(childFrameName + "_offset");
601         }
602 
603         // Version 30507 replaced Joint's CoordinateSet with a "coordinates"
604         // list property.
605         if (documentVersion < 30507) {
606             if (aNode.hasElement("CoordinateSet")) {
607                 auto coordSetIter = aNode.element_begin("CoordinateSet");
608                 if (coordSetIter->hasElement("objects")) {
609                     auto coordIter = coordSetIter->getRequiredElement("objects")
610                                                    .element_begin("Coordinate");
611                     if (coordIter != aNode.element_end()) {
612                         // A "CoordinateSet" element exists, it contains an
613                         // "objects" element, and the "objects" element contains
614                         // at least one "Coordinate" element.
615 
616                         // Create an element for the new layout.
617                         Xml::Element coordinatesElement("coordinates");
618                         // Copy all "Coordinate" elements from the old layout.
619                         while (coordIter != aNode.element_end()) {
620                             coordinatesElement.appendNode(coordIter->clone());
621                             ++coordIter;
622                         }
623                         // Insert new "coordinates" element.
624                         aNode.insertNodeAfter(coordSetIter, coordinatesElement);
625                     }
626                 }
627 
628                 // Remove old "CoordinateSet" element.
629                 aNode.eraseNode(coordSetIter);
630             }
631         }
632 
633         // Version 30514 removed the user-facing "reverse" property from Joint.
634         // The parent and child frames are swapped if a "reverse" element is
635         // found and its value is "true".
636         if (documentVersion < 30514) {
637             auto reverseElt = aNode.element_begin("reverse");
638 
639             if (reverseElt != aNode.element_end()) {
640                 bool swapFrames = false;
641                 reverseElt->getValue().tryConvertToBool(swapFrames);
642 
643                 if (swapFrames) {
644                     std::string oldParentFrameName = "";
645                     std::string oldChildFrameName  = "";
646 
647                     // Find names of parent and child frames. If more than one
648                     // "parent_frame" or "child_frame" element exists, keep the
649                     // first one. The "parent_frame" and "child_frame" elements
650                     // may be listed in either order.
651                     SimTK::Xml::element_iterator connectorsNode =
652                         aNode.element_begin("connectors");
653                     SimTK::Xml::element_iterator connectorElt = connectorsNode->
654                         element_begin("Connector_PhysicalFrame_");
655                     SimTK::Xml::element_iterator connecteeNameElt;
656 
657                     while (connectorElt != connectorsNode->element_end())
658                     {
659                         if (connectorElt->getRequiredAttributeValue("name") ==
660                             "parent_frame" && oldParentFrameName.empty())
661                         {
662                             connecteeNameElt = connectorElt->
663                                                element_begin("connectee_name");
664                             connecteeNameElt->getValueAs<std::string>(
665                                 oldParentFrameName);
666                         }
667                         else if (connectorElt->getRequiredAttributeValue("name")
668                                  == "child_frame" && oldChildFrameName.empty())
669                         {
670                             connecteeNameElt = connectorElt->
671                                                element_begin("connectee_name");
672                             connecteeNameElt->getValueAs<std::string>(
673                                 oldChildFrameName);
674                         }
675                         ++connectorElt;
676                     }
677 
678                     // Swap parent and child frame names. If more than one
679                     // "parent_frame" or "child_frame" element exists, assign
680                     // the same value to all such elements.
681                     connectorsNode = aNode.element_begin("connectors");
682                     connectorElt = connectorsNode->element_begin(
683                                    "Connector_PhysicalFrame_");
684 
685                     while (connectorElt != connectorsNode->element_end())
686                     {
687                         if (connectorElt->getRequiredAttributeValue("name") ==
688                             "parent_frame")
689                         {
690                             connecteeNameElt = connectorElt->
691                                                element_begin("connectee_name");
692                             connecteeNameElt->setValue(oldChildFrameName);
693                         }
694                         else if (connectorElt->getRequiredAttributeValue("name")
695                                  == "child_frame")
696                         {
697                             connecteeNameElt = connectorElt->
698                                                element_begin("connectee_name");
699                             connecteeNameElt->setValue(oldParentFrameName);
700                         }
701                         ++connectorElt;
702                     }
703                 }
704 
705                 // Remove "reverse" element regardless of its value (it is no
706                 // longer a property of Joint).
707                 aNode.eraseNode(reverseElt);
708             }
709         }
710 
711     }
712 
713     Super::updateFromXMLNode(aNode, versionNumber);
714 }
715 
assignSystemIndicesToBodyAndCoordinates(const SimTK::MobilizedBody & mobod,const OpenSim::PhysicalFrame * mobilized,const int & numMobilities,const int & startingCoordinateIndex) const716 int Joint::assignSystemIndicesToBodyAndCoordinates(
717     const SimTK::MobilizedBody& mobod,
718     const OpenSim::PhysicalFrame* mobilized,
719     const int& numMobilities,
720     const int& startingCoordinateIndex) const
721 {
722     // If not OpenSim body provided as the one being mobilized assume it is
723     // an intermediate body and ignore.
724     if (mobilized){
725         // Index can only be assigned to a parent or child body connected by this
726         // Joint
727 
728         SimTK_ASSERT3( ( (mobilized == &getParentFrame()) ||
729                          (mobilized == &getChildFrame()) ||
730                          (mobilized == _slaveBodyForParent.get()) ||
731                          (mobilized == _slaveBodyForChild.get()) ),
732             "%s::'%s' - Cannot assign underlying system index to a PhysicalFrame '%s', "
733             "which is not a parent or child Frame of this Joint.",
734                       getConcreteClassName().c_str(),
735                       getName().c_str(), mobilized->getName().c_str());
736 
737         // ONLY the base Joint can do this assignment
738         mobilized->setMobilizedBodyIndex(mobod.getMobilizedBodyIndex());
739 
740         // Note that setting the mobilized body index of a PhysicalOffsetFrame
741         // does not set it on the parent PhysicalFrame.
742         // Do the check and set it here as well since only the Joint can set the index.
743         const PhysicalOffsetFrame* physOff =
744             dynamic_cast<const PhysicalOffsetFrame*>(mobilized);
745         if (physOff) {
746             physOff->getParentFrame().setMobilizedBodyIndex(mobod.getMobilizedBodyIndex());
747         }
748     }
749     const int nc = numCoordinates();
750     SimTK_ASSERT3(numMobilities <= (nc - startingCoordinateIndex),
751         "%s attempted to create an underlying SimTK::MobilizedBody that "
752         "supplies %d mobilities but only %d required.",
753                   getConcreteClassName().c_str(),
754                   numMobilities, nc - startingCoordinateIndex);
755 
756     // Need a writable reference to this Joint so indices can be set on its
757     // Coordinates.
758     Self& mutableSelf = const_cast<Self&>(*this);
759 
760     int j = startingCoordinateIndex;
761     for (int iq = 0; iq < numMobilities; ++iq){
762         if (j < nc){ // assign
763             mutableSelf.upd_coordinates(j)._mobilizerQIndex =
764                 SimTK::MobilizerQIndex(iq);
765             mutableSelf.upd_coordinates(j)._bodyIndex =
766                 mobod.getMobilizedBodyIndex();
767             j++;
768         }
769         else{
770             std::string msg = getConcreteClassName() +
771                 " creating MobilizedBody with more mobilities than declared Coordinates.";
772             throw Exception(msg);
773         }
774     }
775     return j;
776 }
777 
778 /* Return the equivalent (internal) SimTK::Rigid::Body for a given parent OR
779 child OpenSim::Body. Not guaranteed to be valid until after addToSystem on
780 Body has be called  */
getParentInternalRigidBody() const781 const SimTK::Body& Joint::getParentInternalRigidBody() const
782 {
783     if (_slaveBodyForParent){
784         return _slaveBodyForParent->extractInternalRigidBody();
785     }
786 
787     return static_cast<const PhysicalFrame&>(getParentFrame()
788         .findBaseFrame()).extractInternalRigidBody();
789 }
getChildInternalRigidBody() const790 const SimTK::Body& Joint::getChildInternalRigidBody() const
791 {
792     if (_slaveBodyForChild){
793         return _slaveBodyForChild->extractInternalRigidBody();
794     }
795 
796     return static_cast<const PhysicalFrame&>(getChildFrame()
797         .findBaseFrame()).extractInternalRigidBody();;
798 }
799