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