diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e252392c7..d592d8c83b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,14 @@ request related to the change, then we may provide the commit. This is not a comprehensive list of changes but rather a hand-curated collection of the more notable ones. For a comprehensive history, see the [OpenSim Core GitHub repo](https://github.com/opensim-org/opensim-core). +v4.5 +==== +- Added `AbstractPath` which is a base class for `GeometryPath` and other path types (#3388). All path-based forces now +own the property `path` of type `AbstractPath` instead of the `GeometryPath` unnamed property. Getters and setters have +been added to these forces to provide access to concrete path types (e.g., `updPath`). In `Ligament` and +`Blankevoort1991Ligament`, usages of `get_GeometryPath`, `upd_GeometryPath`, etc., need to be been updated to +`getGeometryPath`, `updGeometryPath`, etc., or a suitable alternative. + v4.4.1 ====== - IMU::calcGyroscopeSignal() now reports angular velocities in the IMU frame. diff --git a/OpenSim/Actuators/DeGrooteFregly2016Muscle.cpp b/OpenSim/Actuators/DeGrooteFregly2016Muscle.cpp index 4cf3f431b3..6aef818887 100644 --- a/OpenSim/Actuators/DeGrooteFregly2016Muscle.cpp +++ b/OpenSim/Actuators/DeGrooteFregly2016Muscle.cpp @@ -1020,35 +1020,7 @@ void DeGrooteFregly2016Muscle::replaceMuscles( muscBase.get_ignore_tendon_compliance()); actu->set_ignore_activation_dynamics( muscBase.get_ignore_activation_dynamics()); - - const auto& pathPointSet = muscBase.getGeometryPath().getPathPointSet(); - auto& geomPath = actu->updGeometryPath(); - for (int ipp = 0; ipp < pathPointSet.getSize(); ++ipp) { - auto* pathPoint = pathPointSet.get(ipp).clone(); - const auto& socketNames = pathPoint->getSocketNames(); - for (const auto& socketName : socketNames) { - pathPoint->updSocket(socketName) - .connect(pathPointSet.get(ipp) - .getSocket(socketName) - .getConnecteeAsObject()); - } - geomPath.updPathPointSet().adoptAndAppend(pathPoint); - } - - const auto& pathWrapSet = muscBase.getGeometryPath().getWrapSet(); - for (int ipw = 0; ipw < pathWrapSet.getSize(); ++ipw) { - auto* pathWrap = pathWrapSet.get(ipw).clone(); - const auto& socketNames = pathWrap->getSocketNames(); - for (const auto& socketName : socketNames) { - pathWrap->updSocket(socketName) - .connect(pathWrapSet.get(ipw) - .getSocket(socketName) - .getConnecteeAsObject()); - } - geomPath.updWrapSet().adoptAndAppend(pathWrap); - } - - std::string actname = actu->getName(); + actu->updProperty_path().assign(muscBase.getProperty_path()); model.addForce(actu.release()); musclesToDelete.push_back(&muscBase); @@ -1074,14 +1046,14 @@ void DeGrooteFregly2016Muscle::extendPostScale( const SimTK::State& s, const ScaleSet& scaleSet) { Super::extendPostScale(s, scaleSet); - GeometryPath& path = upd_GeometryPath(); + AbstractPath& path = updPath(); if (path.getPreScaleLength(s) > 0.0) { double scaleFactor = path.getLength(s) / path.getPreScaleLength(s); upd_optimal_fiber_length() *= scaleFactor; upd_tendon_slack_length() *= scaleFactor; - // Clear the pre-scale length that was stored in the GeometryPath. + // Clear the pre-scale length that was stored in the path. path.setPreScaleLength(s, 0.0); } } diff --git a/OpenSim/Actuators/McKibbenActuator.cpp b/OpenSim/Actuators/McKibbenActuator.cpp index 65a80bcc37..06719f4c62 100644 --- a/OpenSim/Actuators/McKibbenActuator.cpp +++ b/OpenSim/Actuators/McKibbenActuator.cpp @@ -107,7 +107,7 @@ void McKibbenActuator::computeForce(const SimTK::State& s, double actuation = computeActuation(s); - getGeometryPath().addInEquivalentForces(s, actuation, bodyForces, generalizedForces); + getPath().addInEquivalentForces(s, actuation, bodyForces, generalizedForces); } //_____________________________________________________________________________ /** diff --git a/OpenSim/Actuators/Millard2012AccelerationMuscle.cpp b/OpenSim/Actuators/Millard2012AccelerationMuscle.cpp index 8dfe6bf55e..8da5b485f6 100644 --- a/OpenSim/Actuators/Millard2012AccelerationMuscle.cpp +++ b/OpenSim/Actuators/Millard2012AccelerationMuscle.cpp @@ -557,14 +557,14 @@ extendPostScale(const SimTK::State& s, const ScaleSet& scaleSet) Super::extendPostScale(s, scaleSet); - GeometryPath& path = upd_GeometryPath(); + AbstractPath& path = updPath(); if (path.getPreScaleLength(s) > 0.0) { double scaleFactor = path.getLength(s) / path.getPreScaleLength(s); upd_optimal_fiber_length() *= scaleFactor; upd_tendon_slack_length() *= scaleFactor; - // Clear the pre-scale length that was stored in the GeometryPath. + // Clear the pre-scale length that was stored in the path. path.setPreScaleLength(s, 0.0); } } diff --git a/OpenSim/Actuators/Millard2012EquilibriumMuscle.cpp b/OpenSim/Actuators/Millard2012EquilibriumMuscle.cpp index ea5faf233a..25ce5e5bc0 100644 --- a/OpenSim/Actuators/Millard2012EquilibriumMuscle.cpp +++ b/OpenSim/Actuators/Millard2012EquilibriumMuscle.cpp @@ -598,14 +598,14 @@ extendPostScale(const SimTK::State& s, const ScaleSet& scaleSet) { Super::extendPostScale(s, scaleSet); - GeometryPath& path = upd_GeometryPath(); + AbstractPath& path = updPath(); if (path.getPreScaleLength(s) > 0.0) { double scaleFactor = path.getLength(s) / path.getPreScaleLength(s); upd_optimal_fiber_length() *= scaleFactor; upd_tendon_slack_length() *= scaleFactor; - // Clear the pre-scale length that was stored in the GeometryPath. + // Clear the pre-scale length that was stored in the path. path.setPreScaleLength(s, 0.0); } } diff --git a/OpenSim/Actuators/ModelFactory.cpp b/OpenSim/Actuators/ModelFactory.cpp index ca5a294d6b..4cacb6980b 100644 --- a/OpenSim/Actuators/ModelFactory.cpp +++ b/OpenSim/Actuators/ModelFactory.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include using namespace OpenSim; @@ -158,49 +159,21 @@ void ModelFactory::replaceMusclesWithPathActuators(OpenSim::Model &model) { // Create path actuators from muscle properties and add to the model. Save // a list of pointers of the muscles to delete. + model.finalizeConnections(); std::vector musclesToDelete; auto& muscleSet = model.updMuscles(); for (int im = 0; im < muscleSet.getSize(); ++im) { auto& musc = muscleSet.get(im); - auto* actu = new PathActuator(); + auto actu = OpenSim::make_unique(); actu->setName(musc.getName()); musc.setName(musc.getName() + "_delete"); + actu->set_appliesForce(musc.get_appliesForce()); actu->setOptimalForce(musc.getMaxIsometricForce()); actu->setMinControl(musc.getMinControl()); actu->setMaxControl(musc.getMaxControl()); - - model.addForce(actu); - - // For the connectee names in the PathPoints to be correct, we must add - // the path points after adding the PathActuator to the model. - const auto& pathPointSet = musc.getGeometryPath().getPathPointSet(); - auto& geomPath = actu->updGeometryPath(); - for (int ip = 0; ip < pathPointSet.getSize(); ++ip) { - auto* pathPoint = pathPointSet.get(ip).clone(); - const auto& socketNames = pathPoint->getSocketNames(); - for (const auto& socketName : socketNames) { - pathPoint->updSocket(socketName) - .connect(pathPointSet.get(ip) - .getSocket(socketName) - .getConnecteeAsObject()); - } - geomPath.updPathPointSet().adoptAndAppend(pathPoint); - } - - // For the connectee names in the PathWraps to be correct, we must add - // the path wraps after adding the PathActuator to the model. - const auto& pathWrapSet = musc.getGeometryPath().getWrapSet(); - for (int ipw = 0; ipw < pathWrapSet.getSize(); ++ipw) { - auto* pathWrap = pathWrapSet.get(ipw).clone(); - const auto& socketNames = pathWrap->getSocketNames(); - for (const auto& socketName : socketNames) { - pathWrap->updSocket(socketName) - .connect(pathWrapSet.get(ipw) - .getSocket(socketName) - .getConnecteeAsObject()); - } - geomPath.updWrapSet().adoptAndAppend(pathWrap); - } + actu->updProperty_path().assign(musc.getProperty_path()); + actu->upd_path().setDefaultColor({0.5, 0.5, 0.5}); + model.addForce(actu.release()); musclesToDelete.push_back(&musc); } diff --git a/OpenSim/Actuators/ModelFactory.h b/OpenSim/Actuators/ModelFactory.h index c680841710..1d0b907791 100644 --- a/OpenSim/Actuators/ModelFactory.h +++ b/OpenSim/Actuators/ModelFactory.h @@ -60,7 +60,7 @@ class OSIMACTUATORS_API ModelFactory { /// @name Modify a Model /// @{ - /// Replace muscles in a model with a PathActuator of the same GeometryPath, + /// Replace muscles in a model with a PathActuator of the same path, /// optimal force, and min/max control defaults. /// @note This only replaces muscles within the model's ForceSet. static void replaceMusclesWithPathActuators(Model& model); diff --git a/OpenSim/Actuators/RigidTendonMuscle.cpp b/OpenSim/Actuators/RigidTendonMuscle.cpp index 2663dc13b4..508908df77 100644 --- a/OpenSim/Actuators/RigidTendonMuscle.cpp +++ b/OpenSim/Actuators/RigidTendonMuscle.cpp @@ -161,7 +161,7 @@ void RigidTendonMuscle::calcMusclePotentialEnergyInfo(const SimTK::State& s, void RigidTendonMuscle::calcFiberVelocityInfo(const State& s, FiberVelocityInfo& fvi) const { /*const MuscleLengthInfo &mli = */getMuscleLengthInfo(s); - fvi.fiberVelocity = getGeometryPath().getLengtheningSpeed(s); + fvi.fiberVelocity = getPath().getLengtheningSpeed(s); fvi.normFiberVelocity = fvi.fiberVelocity / (getOptimalFiberLength()*getMaxContractionVelocity()); fvi.fiberForceVelocityMultiplier = diff --git a/OpenSim/Common/XMLDocument.cpp b/OpenSim/Common/XMLDocument.cpp index da02e5c9f9..6c8136f7a9 100644 --- a/OpenSim/Common/XMLDocument.cpp +++ b/OpenSim/Common/XMLDocument.cpp @@ -66,8 +66,9 @@ using namespace std; // 30516 for GeometryPath default_color -> Appearance // 30517 for removal of _connectee_name suffix to shorten XML for socket, input // 40000 for OpenSim 4.0 release 40000 +// 40500 for updating 'GeometryPath' nodes to have property name 'path'. -const int XMLDocument::LatestVersion = 40000; +const int XMLDocument::LatestVersion = 40500; //============================================================================= // DESTRUCTOR AND CONSTRUCTOR(S) //============================================================================= diff --git a/OpenSim/Simulation/Model/AbstractPath.cpp b/OpenSim/Simulation/Model/AbstractPath.cpp new file mode 100644 index 0000000000..4c048a6b63 --- /dev/null +++ b/OpenSim/Simulation/Model/AbstractPath.cpp @@ -0,0 +1,68 @@ +/* -------------------------------------------------------------------------- * + * OpenSim: AbstractPath.cpp * + * -------------------------------------------------------------------------- * + * The OpenSim API is a toolkit for musculoskeletal modeling and simulation. * + * See http://opensim.stanford.edu and the NOTICE file for more information. * + * OpenSim is developed at Stanford University and supported by the US * + * National Institutes of Health (U54 GM072970, R24 HD065690) and by DARPA * + * through the Warrior Web program. * + * * + * Copyright (c) 2005-2023 Stanford University and the Authors * + * Author(s): Nicholas Bianco, Joris Verhagen, Adam Kewley * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); you may * + * not use this file except in compliance with the License. You may obtain a * + * copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * -------------------------------------------------------------------------- */ + +#include "AbstractPath.h" +#include "Appearance.h" + +using namespace OpenSim; + +AbstractPath::AbstractPath() : ModelComponent() { + setAuthors("Nicholas Bianco, Joris Verhagen, Adam Kewley"); + + Appearance appearance; + appearance.set_color(SimTK::Gray); + constructProperty_Appearance(appearance); +} + +AbstractPath::AbstractPath(AbstractPath const&) = default; + +AbstractPath::~AbstractPath() noexcept = default; + +AbstractPath& AbstractPath::operator=(const AbstractPath&) = default; + +AbstractPath::AbstractPath(AbstractPath&& other) = default; + +AbstractPath& AbstractPath::operator=(AbstractPath&& other) = default; + +// DEFAULTED METHODS +const SimTK::Vec3& AbstractPath::getDefaultColor() const +{ + return get_Appearance().get_color(); +} + +void AbstractPath::setDefaultColor(const SimTK::Vec3& color) +{ + updProperty_Appearance().setValueIsDefault(false); + upd_Appearance().set_color(color); +} + +double AbstractPath::getPreScaleLength(const SimTK::State&) const +{ + return _preScaleLength; +} + +void AbstractPath::setPreScaleLength(const SimTK::State&, + double preScaleLength) +{ + _preScaleLength = preScaleLength; +} diff --git a/OpenSim/Simulation/Model/AbstractPath.h b/OpenSim/Simulation/Model/AbstractPath.h new file mode 100644 index 0000000000..8f0d678bcb --- /dev/null +++ b/OpenSim/Simulation/Model/AbstractPath.h @@ -0,0 +1,223 @@ +#ifndef OPENSIM_ABSTRACTPATH_H +#define OPENSIM_ABSTRACTPATH_H +/* -------------------------------------------------------------------------- * + * OpenSim: AbstractPath.h * + * -------------------------------------------------------------------------- * + * The OpenSim API is a toolkit for musculoskeletal modeling and simulation. * + * See http://opensim.stanford.edu and the NOTICE file for more information. * + * OpenSim is developed at Stanford University and supported by the US * + * National Institutes of Health (U54 GM072970, R24 HD065690) and by DARPA * + * through the Warrior Web program. * + * * + * Copyright (c) 2005-2023 Stanford University and the Authors * + * Author(s): Nicholas Bianco, Joris Verhagen, Adam Kewley * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); you may * + * not use this file except in compliance with the License. You may obtain a * + * copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * -------------------------------------------------------------------------- */ + +#include +#include +#include + +#ifdef SWIG + #ifdef OSIMSIMULATION_API + #undef OSIMSIMULATION_API + #define OSIMSIMULATION_API + #endif +#endif + +namespace OpenSim { + +class Coordinate; + +//============================================================================= +// ABSTRACT PATH +//============================================================================= +/** + * A base class that represents a path that has a computable length and + * lengthening speed. + * + * This class is typically used in places where the model needs to simulate + * the changes in a path over time. For example, in `OpenSim::Muscle`s, + * `OpenSim::Ligament`s, etc. + * + * This class *only* defines a length and lengthening speed. We do not assume + * that an `OpenSim::AbstractPath` is a straight line between two points or + * assume that it is many straight lines between `n` points. The derived + * implementation may define a path using points, or it may define a path using + * a curve fit. It may also define a path based on analytical functions for the + * length and lengthening speed. + */ +class OSIMSIMULATION_API AbstractPath : public ModelComponent { +OpenSim_DECLARE_ABSTRACT_OBJECT(AbstractPath, ModelComponent); + +public: +//============================================================================= +// OUTPUTS +//============================================================================= + OpenSim_DECLARE_OUTPUT(length, double, getLength, SimTK::Stage::Position); + OpenSim_DECLARE_OUTPUT(lengthening_speed, double, getLengtheningSpeed, + SimTK::Stage::Velocity); + +//============================================================================= +// PROPERTIES +//============================================================================= + OpenSim_DECLARE_UNNAMED_PROPERTY(Appearance, + "Default appearance attributes for this AbstractPath."); + +//============================================================================= +// METHODS +//============================================================================= + AbstractPath(); + ~AbstractPath() noexcept; + + AbstractPath(const AbstractPath&); + AbstractPath& operator=(const AbstractPath&); + + AbstractPath(AbstractPath&& other); + AbstractPath& operator=(AbstractPath&& other); + + // INTERFACE METHODS + // + // Concrete implementations of `AbstractPath` *must* provide these. + + /** + * Get the current color of the path. + * + * This is the runtime, potentially state-dependent, color of the path. It + * is the color used to display the path in that state (e.g., for UI + * rendering). + * + * This color value is typically initialized with the default color (see: + * `getDefaultColor`), but the color can change between simulation states + * because downstream code (e.g. muscles) might call `setColor` to implement + * state-dependent path coloring. + */ + virtual SimTK::Vec3 getColor(const SimTK::State& s) const = 0; + + /** + * Set the current color of the path. + * + * Internally, sets the current color value of the path for the provided + * state (e.g. using cache variables). + * + * The value of this variable is used as the color when the path is drawn, + * which occurs with the state realized to Stage::Dynamics. Therefore, you + * must call this method during realizeDynamics() or earlier in order for it + * to have any effect. + */ + virtual void setColor( + const SimTK::State& s, const SimTK::Vec3& color) const = 0; + + /** + * Get the current length of the path. + * + * Internally, this may use a variety of methods to figure out how long the + * path is, such as using spline-fits, or computing the distance between + * points in space. It is up to concrete implementations (e.g., + * `GeometryPath`) to provide a relevant implementation. + */ + virtual double getLength(const SimTK::State& s) const = 0; + + /** + * Get the lengthening speed of the path. + * + * Internally, this may use a variety of methods to figure out the + * lengthening speed. It might use the finite difference between two + * lengths, or an analytic solution, or always return `0.0`. It is up to + * concrete implementations (e.g., `GeometryPath`) to provide a relevant + * implementation. + */ + virtual double getLengtheningSpeed(const SimTK::State& s) const = 0; + + /** + * Add in the equivalent body and generalized forces to be applied to the + * multibody system resulting from a tension along the AbstractPath. + * + * @param state state used to evaluate forces + * @param[in] tension scalar of the applied (+ve) tensile force + * @param[in,out] bodyForces Vector of forces (SpatialVec's) on bodies + * @param[in,out] mobilityForces Vector of generalized forces + */ + virtual void addInEquivalentForces(const SimTK::State& state, + const double& tension, + SimTK::Vector_& bodyForces, + SimTK::Vector& mobilityForces) const = 0; + + /** + * Returns the moment arm of the path in the given state with respect to + * the specified coordinate. + */ + virtual double computeMomentArm(const SimTK::State& s, + const Coordinate& aCoord) const = 0; + + /** + * Return whether or not a path can be visualized. + * + * Concrete implementations may be visualizable (e.g., `GeometryPath`) or + * they may not be and therefore must provide a relevant implementation. + */ + virtual bool isVisualPath() const = 0; + + // DEFAULTED METHODS + // + // These are methods that for which AbstractPath provides default + // implementation. + + /** + * Get the default color of the path. + * + * Returns the color that will be used to initialize the color cache + * at the next extendAddToSystem() call. Use `getColor` to retrieve the + * (potentially different) color that will be used to draw the path. + */ + const SimTK::Vec3& getDefaultColor() const; + + /** + * Set the default color of the path. + * + * Sets the internal, default, color value for the path. This is the color + * that's used when the simulation is initialized (specifically, during the + * `extendAddToSystem` call). + * + * This color is not necessarily the *current* color of the path. Other code + * in the system (e.g. muscle implementations) may change the runtime color + * with `setColor`. Use `getColor`, with a particular simulation state, to + * get the color of the path in that state. + */ + void setDefaultColor(const SimTK::Vec3& color); + + /** + * Get the current length of the path, *before* the last set of scaling + * operations were applied to it. + * + * Internally, the path stores the original length in a `double` during + * `extendPreScale`. Therefore, be *very* careful with this method, because + * the recorded length is dependent on the length as computed during + * `extendPreScale`, which may have been called with a different state. + */ + double getPreScaleLength(const SimTK::State& s) const; + void setPreScaleLength(const SimTK::State& s, double preScaleLength); + +private: + // Used by `(get|set)PreLengthScale`. Used during `extend(Pre|Post)Scale` by + // downstream users of AbstractPath to cache the length of the path before + // scaling. + // + // Ideally, downstream classes would perform the caching themselves, because + // the AbstractPath API isn't an ideal place to store this information. This + // field is mostly here for backwards-compatability with the API. + double _preScaleLength = 0.0; +}; + +} // namespace OpenSim + +#endif // OPENSIM_ABSTRACTPATH_H diff --git a/OpenSim/Simulation/Model/ActivationFiberLengthMuscle.cpp b/OpenSim/Simulation/Model/ActivationFiberLengthMuscle.cpp index aa686fc7e6..a37983c358 100644 --- a/OpenSim/Simulation/Model/ActivationFiberLengthMuscle.cpp +++ b/OpenSim/Simulation/Model/ActivationFiberLengthMuscle.cpp @@ -182,14 +182,14 @@ extendPostScale(const SimTK::State& s, const ScaleSet& scaleSet) { Super::extendPostScale(s, scaleSet); - GeometryPath& path = upd_GeometryPath(); + AbstractPath& path = updPath(); if (path.getPreScaleLength(s) > 0.0) { double scaleFactor = path.getLength(s) / path.getPreScaleLength(s); upd_optimal_fiber_length() *= scaleFactor; upd_tendon_slack_length() *= scaleFactor; - // Clear the pre-scale length that was stored in the GeometryPath. + // Clear the pre-scale length that was stored in the path. path.setPreScaleLength(s, 0.0); } } diff --git a/OpenSim/Simulation/Model/ActivationFiberLengthMuscle_Deprecated.cpp b/OpenSim/Simulation/Model/ActivationFiberLengthMuscle_Deprecated.cpp index 95aee886fd..27dd9c6908 100644 --- a/OpenSim/Simulation/Model/ActivationFiberLengthMuscle_Deprecated.cpp +++ b/OpenSim/Simulation/Model/ActivationFiberLengthMuscle_Deprecated.cpp @@ -346,14 +346,14 @@ extendPostScale(const SimTK::State& s, const ScaleSet& scaleSet) { Super::extendPostScale(s, scaleSet); - GeometryPath& path = upd_GeometryPath(); + AbstractPath& path = updPath(); if (path.getPreScaleLength(s) > 0.0) { double scaleFactor = path.getLength(s) / path.getPreScaleLength(s); upd_optimal_fiber_length() *= scaleFactor; upd_tendon_slack_length() *= scaleFactor; - // Clear the pre-scale length that was stored in the GeometryPath. + // Clear the pre-scale length that was stored in the path. path.setPreScaleLength(s, 0.0); } } diff --git a/OpenSim/Simulation/Model/Blankevoort1991Ligament.cpp b/OpenSim/Simulation/Model/Blankevoort1991Ligament.cpp index 5d68506aad..af71594206 100644 --- a/OpenSim/Simulation/Model/Blankevoort1991Ligament.cpp +++ b/OpenSim/Simulation/Model/Blankevoort1991Ligament.cpp @@ -22,7 +22,6 @@ * -------------------------------------------------------------------------- */ #include -#include #include "Blankevoort1991Ligament.h" using namespace OpenSim; @@ -41,8 +40,8 @@ Blankevoort1991Ligament::Blankevoort1991Ligament(std::string name, const PhysicalFrame& frame2, SimTK::Vec3 point2) : Blankevoort1991Ligament() { setName(name); - upd_GeometryPath().appendNewPathPoint("p1", frame1, point1); - upd_GeometryPath().appendNewPathPoint("p2", frame2, point2); + updGeometryPath().appendNewPathPoint("p1", frame1, point1); + updGeometryPath().appendNewPathPoint("p2", frame2, point2); } Blankevoort1991Ligament::Blankevoort1991Ligament(std::string name, @@ -81,7 +80,7 @@ void Blankevoort1991Ligament::setNull() } void Blankevoort1991Ligament::constructProperties() { - constructProperty_GeometryPath(GeometryPath()); + constructProperty_path(GeometryPath()); constructProperty_linear_stiffness(1.0); constructProperty_transition_strain(0.06); constructProperty_damping_coefficient(0.003); @@ -109,8 +108,7 @@ void Blankevoort1991Ligament::extendFinalizeFromProperties() { "Transistion Strain cannot be less than 0"); // Set Default Ligament Color - GeometryPath& path = upd_GeometryPath(); - path.setDefaultColor(SimTK::Vec3(0.1202, 0.7054, 0.1318)); + updPath().setDefaultColor(SimTK::Vec3(0.1202, 0.7054, 0.1318)); } void Blankevoort1991Ligament::extendAddToSystem( @@ -132,14 +130,14 @@ void Blankevoort1991Ligament::extendPostScale( const SimTK::State& s, const ScaleSet& scaleSet) { Super::extendPostScale(s, scaleSet); - GeometryPath& path = upd_GeometryPath(); + AbstractPath& path = updPath(); double slack_length = get_slack_length(); if (path.getPreScaleLength(s) > 0.0) { double scaleFactor = path.getLength(s) / path.getPreScaleLength(s); set_slack_length(scaleFactor * slack_length); - // Clear the pre-scale length that was stored in the GeometryPath. + // Clear the pre-scale length that was stored in the path. path.setPreScaleLength(s, 0.0); } } @@ -149,12 +147,12 @@ void Blankevoort1991Ligament::extendPostScale( //============================================================================= double Blankevoort1991Ligament::getLength(const SimTK::State& state) const { - return get_GeometryPath().getLength(state); + return getPath().getLength(state); } double Blankevoort1991Ligament::getLengtheningSpeed( const SimTK::State& state) const { - return get_GeometryPath().getLengtheningSpeed(state); + return getPath().getLengtheningSpeed(state); } double Blankevoort1991Ligament::getStrain(const SimTK::State& state) const { @@ -340,7 +338,7 @@ void Blankevoort1991Ligament::computeForce(const SimTK::State& s, // total force double force_total = getTotalForce(s); - const GeometryPath &path = get_GeometryPath(); + const AbstractPath &path = getPath(); path.addInEquivalentForces( s, force_total, bodyForces, generalizedForces); @@ -372,7 +370,7 @@ double Blankevoort1991Ligament::computePotentialEnergy( double Blankevoort1991Ligament::computeMomentArm( const SimTK::State& s, Coordinate& aCoord) const { - return get_GeometryPath().computeMomentArm(s, aCoord); + return getPath().computeMomentArm(s, aCoord); } //============================================================================= diff --git a/OpenSim/Simulation/Model/Blankevoort1991Ligament.h b/OpenSim/Simulation/Model/Blankevoort1991Ligament.h index 4177aa704c..eef8ceff5a 100644 --- a/OpenSim/Simulation/Model/Blankevoort1991Ligament.h +++ b/OpenSim/Simulation/Model/Blankevoort1991Ligament.h @@ -24,6 +24,7 @@ * -------------------------------------------------------------------------- */ #include +#include #include namespace OpenSim { @@ -107,7 +108,7 @@ setLinearStiffnessForcePerLength() and getLinearStiffnessForcePerLength(). When scaling a model (using the ScaleTool) that contains a Blankevoort1991Ligament, the slack_length property is scaled by the ratio of -the entire GeometryPath length in the default model pose before and after +the entire path length in the default model pose before and after scaling the bone geometries. This ensures that the strain in the ligament in the default pose is equivilent before and after scaling. Thus, it is important to consider the order of scaling the model and setting the slack_length @@ -176,9 +177,9 @@ OpenSim_DECLARE_CONCRETE_OBJECT(Blankevoort1991Ligament, Force) //============================================================================= // PROPERTIES //============================================================================= - - OpenSim_DECLARE_UNNAMED_PROPERTY(GeometryPath, - "The set of points defining the path of the ligament") + + OpenSim_DECLARE_PROPERTY(path, AbstractPath, + "The path defines the length and lengthening speed of the ligament.") OpenSim_DECLARE_PROPERTY(linear_stiffness, double, "The slope of the linear region of the force-strain curve. " "Units of force/strain (N).") @@ -221,7 +222,7 @@ OpenSim_DECLARE_CONCRETE_OBJECT(Blankevoort1991Ligament, Force) // METHODS //============================================================================= public: - //Constructors + // Constructors Blankevoort1991Ligament(); Blankevoort1991Ligament(std::string name, @@ -236,6 +237,37 @@ OpenSim_DECLARE_CONCRETE_OBJECT(Blankevoort1991Ligament, Force) Blankevoort1991Ligament(std::string name, double linear_stiffness, double slack_length); + // Path + AbstractPath& updPath() { return upd_path(); } + const AbstractPath& getPath() const { return get_path(); } + + template + PathType& updPath() { + return dynamic_cast(upd_path()); + } + template + const PathType& getPath() const { + return dynamic_cast(get_path()); + } + + template + PathType* tryUpdPath() { + return dynamic_cast(&upd_path()); + } + template + const PathType* tryGetPath() const { + return dynamic_cast(&get_path()); + } + + GeometryPath& updGeometryPath() { + return updPath(); + } + const GeometryPath& getGeometryPath() const { + return getPath(); + } + + bool hasVisualPath() const override { return getPath().isVisualPath(); }; + //------------------------------------------------------------------------- // SET //------------------------------------------------------------------------- diff --git a/OpenSim/Simulation/Model/Force.cpp b/OpenSim/Simulation/Model/Force.cpp index b78e8e23d7..121fde2094 100644 --- a/OpenSim/Simulation/Model/Force.cpp +++ b/OpenSim/Simulation/Model/Force.cpp @@ -78,6 +78,22 @@ Force::updateFromXMLNode(SimTK::Xml::Element& node, int versionNumber) { elem.setValue(SimTK::String(!isDisabled)); } } + if (versionNumber < 40500) { + // In version 40500, the XML syntax for components that own + // GeometryPath objects (PathActuator, PathSpring, Ligament, + // and Blankevoort1991Ligament) changed: the 'GeometryPath` unnamed + // property was replaced with the named property 'path', which is of + // type 'AbstractPath'. Since 'path' is still a one object property, + // the property will still be serialized using the concrete type + // (e.g., 'GeometryPath') and with name attribute set to 'path'. + // Therefore, we can simply update the name attribute of any + // existing 'GeometryPath' nodes to 'path'. + SimTK::Xml::element_iterator geometryPathNode = + node.element_begin("GeometryPath"); + if (geometryPathNode != node.element_end()) { + geometryPathNode->setAttributeValue("name", "path"); + } + } } Super::updateFromXMLNode(node, versionNumber); diff --git a/OpenSim/Simulation/Model/Force.h b/OpenSim/Simulation/Model/Force.h index 1a7466c57f..7a85da6af1 100644 --- a/OpenSim/Simulation/Model/Force.h +++ b/OpenSim/Simulation/Model/Force.h @@ -99,14 +99,12 @@ OpenSim_DECLARE_ABSTRACT_OBJECT(Force, ModelComponent); getRecordValues(const SimTK::State& state) const { return OpenSim::Array(); }; - - - /** Return a flag indicating whether the Force is applied along a Path. If - you override this method to return true for a specific subclass, it must - also implement the getGeometryPath() method. **/ - virtual bool hasGeometryPath() const { - return getPropertyIndex("GeometryPath").isValid(); - }; + + /** Return a flag indicating whether the Force is applied along a path that + * can be visualized. If you override this method to return true for a + * specific subclass, it must also implement the getPath() method. + */ + virtual bool hasVisualPath() const { return false; } protected: /** Default constructor sets up Force-level properties; can only be diff --git a/OpenSim/Simulation/Model/GeometryPath.cpp b/OpenSim/Simulation/Model/GeometryPath.cpp index c9834348e5..c7db2882d7 100644 --- a/OpenSim/Simulation/Model/GeometryPath.cpp +++ b/OpenSim/Simulation/Model/GeometryPath.cpp @@ -133,9 +133,7 @@ static void PopulatePathElementLookup( /* * Default constructor. */ -GeometryPath::GeometryPath() : - ModelComponent(), - _preScaleLength(0.0) +GeometryPath::GeometryPath() : AbstractPath() { setAuthors("Peter Loan"); constructProperties(); @@ -281,10 +279,6 @@ void GeometryPath::constructProperties() constructProperty_PathPointSet(PathPointSet()); constructProperty_PathWrapSet(PathWrapSet()); - - Appearance appearance; - appearance.set_color(SimTK::Gray); - constructProperty_Appearance(appearance); } //_____________________________________________________________________________ @@ -570,13 +564,6 @@ void GeometryPath::setLengtheningSpeed( const SimTK::State& s, double speed ) co setCacheVariableValue(s, _speedCV, speed); } -void GeometryPath::setPreScaleLength( const SimTK::State& s, double length ) { - _preScaleLength = length; -} -double GeometryPath::getPreScaleLength( const SimTK::State& s) const { - return _preScaleLength; -} - //============================================================================= // UTILITY //============================================================================= diff --git a/OpenSim/Simulation/Model/GeometryPath.h b/OpenSim/Simulation/Model/GeometryPath.h index a53b4bc50e..5dc552cdbf 100644 --- a/OpenSim/Simulation/Model/GeometryPath.h +++ b/OpenSim/Simulation/Model/GeometryPath.h @@ -25,8 +25,7 @@ // INCLUDE -#include -#include "OpenSim/Simulation/Model/ModelComponent.h" +#include "OpenSim/Simulation/Model/AbstractPath.h" #include "PathPointSet.h" #include #include @@ -55,23 +54,12 @@ class WrapObject; * @author Peter Loan * @version 1.0 */ -class OSIMSIMULATION_API GeometryPath : public ModelComponent { -OpenSim_DECLARE_CONCRETE_OBJECT(GeometryPath, ModelComponent); - //============================================================================= - // OUTPUTS - //============================================================================= - OpenSim_DECLARE_OUTPUT(length, double, getLength, SimTK::Stage::Position); - // - OpenSim_DECLARE_OUTPUT(lengthening_speed, double, getLengtheningSpeed, - SimTK::Stage::Velocity); +class OSIMSIMULATION_API GeometryPath : public AbstractPath { +OpenSim_DECLARE_CONCRETE_OBJECT(GeometryPath, AbstractPath); //============================================================================= // DATA //============================================================================= -public: - OpenSim_DECLARE_UNNAMED_PROPERTY(Appearance, - "Default appearance attributes for this GeometryPath"); - private: OpenSim_DECLARE_UNNAMED_PROPERTY(PathPointSet, "The set of points defining the path"); @@ -79,9 +67,6 @@ OpenSim_DECLARE_CONCRETE_OBJECT(GeometryPath, ModelComponent); OpenSim_DECLARE_UNNAMED_PROPERTY(PathWrapSet, "The wrap objects that are associated with this path"); - // used for scaling tendon and fiber lengths - double _preScaleLength; - // Solver used to compute moment-arms. The GeometryPath owns this object, // but we cannot simply use a unique_ptr because we want the pointer to be // cleared on copy. @@ -135,18 +120,6 @@ OpenSim_DECLARE_CONCRETE_OBJECT(GeometryPath, ModelComponent); // GET //-------------------------------------------------------------------------- - /** If you call this prior to extendAddToSystem() it will be used to initialize - the color cache variable. Otherwise %GeometryPath will choose its own - default which varies depending on owner. **/ - void setDefaultColor(const SimTK::Vec3& color) { - updProperty_Appearance().setValueIsDefault(false); - upd_Appearance().set_color(color); - }; - /** Returns the color that will be used to initialize the color cache - at the next extendAddToSystem() call. The actual color used to draw the path - will be taken from the cache variable, so may have changed. **/ - const SimTK::Vec3& getDefaultColor() const { return get_Appearance().get_color(); } - /** %Set the value of the color cache variable owned by this %GeometryPath object, in the cache of the given state. The value of this variable is used as the color when the path is drawn, which occurs with the state realized @@ -164,8 +137,6 @@ OpenSim_DECLARE_CONCRETE_OBJECT(GeometryPath, ModelComponent); double getLength( const SimTK::State& s) const; void setLength( const SimTK::State& s, double length) const; - double getPreScaleLength( const SimTK::State& s) const; - void setPreScaleLength( const SimTK::State& s, double preScaleLength); const Array& getCurrentPath( const SimTK::State& s) const; double getLengtheningSpeed(const SimTK::State& s) const; @@ -186,13 +157,15 @@ OpenSim_DECLARE_CONCRETE_OBJECT(GeometryPath, ModelComponent); void addInEquivalentForces(const SimTK::State& state, const double& tension, SimTK::Vector_& bodyForces, - SimTK::Vector& mobilityForces) const; - - + SimTK::Vector& mobilityForces) const override; + + bool isVisualPath() const override { return true; } + //-------------------------------------------------------------------------- // COMPUTATIONS //-------------------------------------------------------------------------- - virtual double computeMomentArm(const SimTK::State& s, const Coordinate& aCoord) const; + double computeMomentArm(const SimTK::State& s, + const Coordinate& aCoord) const override; //-------------------------------------------------------------------------- // SCALING diff --git a/OpenSim/Simulation/Model/Ligament.cpp b/OpenSim/Simulation/Model/Ligament.cpp index 942a90e426..d2f76678a3 100644 --- a/OpenSim/Simulation/Model/Ligament.cpp +++ b/OpenSim/Simulation/Model/Ligament.cpp @@ -25,7 +25,6 @@ // INCLUDES //============================================================================= #include "Ligament.h" -#include "GeometryPath.h" #include "PointForceDirection.h" #include @@ -63,7 +62,7 @@ Ligament::Ligament() void Ligament::constructProperties() { setAuthors("Peter Loan"); - constructProperty_GeometryPath(GeometryPath()); + constructProperty_path(GeometryPath()); constructProperty_resting_length(0.0); constructProperty_pcsa_force(0.0); @@ -84,8 +83,7 @@ void Ligament::extendFinalizeFromProperties() // Resting length must be greater than 0.0. assert(get_resting_length() > 0.0); - GeometryPath& path = upd_GeometryPath(); - path.setDefaultColor(DefaultLigamentColor); + updPath().setDefaultColor(DefaultLigamentColor); } @@ -99,7 +97,7 @@ void Ligament::extendRealizeDynamics(const SimTK::State& state) const { if(appliesForce(state)){ const SimTK::Vec3 color = computePathColor(state); if (!color.isNaN()) - getGeometryPath().setColor(state, color); + getPath().setColor(state, color); } } @@ -145,7 +143,7 @@ SimTK::Vec3 Ligament::computePathColor(const SimTK::State& state) const { */ double Ligament::getLength(const SimTK::State& s) const { - return getGeometryPath().getLength(s); + return getPath().getLength(s); } //_____________________________________________________________________________ @@ -193,13 +191,13 @@ void Ligament::extendPostScale(const SimTK::State& s, const ScaleSet& scaleSet) { Super::extendPostScale(s, scaleSet); - GeometryPath& path = upd_GeometryPath(); + AbstractPath& path = updPath(); if (path.getPreScaleLength(s) > 0.0) { double scaleFactor = path.getLength(s) / path.getPreScaleLength(s); upd_resting_length() *= scaleFactor; - // Clear the pre-scale length that was stored in the GeometryPath. + // Clear the pre-scale length that was stored in the path. path.setPreScaleLength(s, 0.0); } } @@ -218,7 +216,7 @@ const double& Ligament::getTension(const SimTK::State& s) const */ double Ligament::computeMomentArm(const SimTK::State& s, Coordinate& aCoord) const { - return getGeometryPath().computeMomentArm(s, aCoord); + return getPath().computeMomentArm(s, aCoord); } @@ -227,30 +225,22 @@ void Ligament::computeForce(const SimTK::State& s, SimTK::Vector_& bodyForces, SimTK::Vector& generalizedForces) const { - const GeometryPath& path = getGeometryPath(); + const auto& path = getPath(); const double& restingLength = get_resting_length(); const double& pcsaForce = get_pcsa_force(); - double force = 0; + double tension = 0; if (path.getLength(s) <= restingLength){ - setCacheVariableValue(s, _tensionCV, force); + setCacheVariableValue(s, _tensionCV, tension); return; } // evaluate normalized tendon force length curve - force = getForceLengthCurve().calcValue( + tension = getForceLengthCurve().calcValue( SimTK::Vector(1, path.getLength(s)/restingLength))* pcsaForce; - setCacheVariableValue(s, _tensionCV, force); + setCacheVariableValue(s, _tensionCV, tension); - OpenSim::Array PFDs; - path.getPointForceDirections(s, &PFDs); - - for (int i=0; i < PFDs.getSize(); i++) { - applyForceToPoint(s, PFDs[i]->frame(), PFDs[i]->point(), - force*PFDs[i]->direction(), bodyForces); - } - for(int i=0; i < PFDs.getSize(); i++) - delete PFDs[i]; + path.addInEquivalentForces(s, tension, bodyForces, generalizedForces); } diff --git a/OpenSim/Simulation/Model/Ligament.h b/OpenSim/Simulation/Model/Ligament.h index 9e5770332e..54c08f293b 100644 --- a/OpenSim/Simulation/Model/Ligament.h +++ b/OpenSim/Simulation/Model/Ligament.h @@ -28,6 +28,8 @@ // INCLUDES //============================================================================= #include "Force.h" +#include "AbstractPath.h" +#include "GeometryPath.h" #ifdef SWIG #ifdef OSIMACTUATORS_API @@ -39,14 +41,13 @@ namespace OpenSim { class Function; -class GeometryPath; class ScaleSet; //============================================================================= //============================================================================= /** * A class implementing a ligament. The path of the ligament is - * stored in a GeometryPath object. + * stored in an object derived from AbstractPath. */ class OSIMSIMULATION_API Ligament : public Force { OpenSim_DECLARE_CONCRETE_OBJECT(Ligament, Force); @@ -54,8 +55,8 @@ OpenSim_DECLARE_CONCRETE_OBJECT(Ligament, Force); //============================================================================= // PROPERTIES //============================================================================= - OpenSim_DECLARE_UNNAMED_PROPERTY(GeometryPath, - "the set of points defining the path of the ligament"); + OpenSim_DECLARE_PROPERTY(path, AbstractPath, + "The path defines the length and lengthening speed of the PathSpring"); OpenSim_DECLARE_PROPERTY(resting_length, double, "resting length of the ligament"); OpenSim_DECLARE_PROPERTY(pcsa_force, double, @@ -72,15 +73,42 @@ OpenSim_DECLARE_CONCRETE_OBJECT(Ligament, Force); // Uses default (compiler-generated) destructor, copy constructor, and copy // assignment operator. + //-------------------------------------------------------------------------- + // PATH + //-------------------------------------------------------------------------- + AbstractPath& updPath() { return upd_path(); } + const AbstractPath& getPath() const { return get_path(); } + + template + PathType& updPath() { + return dynamic_cast(upd_path()); + } + template + const PathType& getPath() const { + return dynamic_cast(get_path()); + } + + template + PathType* tryUpdPath() { + return dynamic_cast(&upd_path()); + } + template + const PathType* tryGetPath() const { + return dynamic_cast(&get_path()); + } + + GeometryPath& updGeometryPath() { + return updPath(); + } + const GeometryPath& getGeometryPath() const { + return getPath(); + } + + bool hasVisualPath() const override { return getPath().isVisualPath(); }; + //-------------------------------------------------------------------------- // GET //-------------------------------------------------------------------------- - // Properties - const GeometryPath& getGeometryPath() const - { return get_GeometryPath(); } - GeometryPath& updGeometryPath() - { return upd_GeometryPath(); } - bool hasGeometryPath() const override { return true;}; virtual double getLength(const SimTK::State& s) const; virtual double getRestingLength() const { return get_resting_length(); } diff --git a/OpenSim/Simulation/Model/Muscle.cpp b/OpenSim/Simulation/Model/Muscle.cpp index 3d1544109d..754cb8676e 100644 --- a/OpenSim/Simulation/Model/Muscle.cpp +++ b/OpenSim/Simulation/Model/Muscle.cpp @@ -26,7 +26,6 @@ //============================================================================= #include "Muscle.h" -#include "GeometryPath.h" #include "Model.h" #include @@ -156,7 +155,7 @@ void Muscle::constructProperties() // By default the min and max controls on muscle are 0.0 and 1.0 setMinControl(0.0); setMaxControl(1.0); - upd_GeometryPath().setDefaultColor(DefaultMuscleColor); + updPath().setDefaultColor(DefaultMuscleColor); } diff --git a/OpenSim/Simulation/Model/PathActuator.cpp b/OpenSim/Simulation/Model/PathActuator.cpp index 130965241c..218e433ea9 100644 --- a/OpenSim/Simulation/Model/PathActuator.cpp +++ b/OpenSim/Simulation/Model/PathActuator.cpp @@ -62,7 +62,7 @@ void PathActuator::setNull() */ void PathActuator::constructProperties() { - constructProperty_GeometryPath(GeometryPath()); + constructProperty_path(GeometryPath()); constructProperty_optimal_force(1.0); } @@ -107,7 +107,7 @@ double PathActuator::getOptimalForce() const */ double PathActuator::getLength(const SimTK::State& s) const { - return getGeometryPath().getLength(s); + return getPath().getLength(s); } //_____________________________________________________________________________ /** @@ -117,7 +117,7 @@ double PathActuator::getLength(const SimTK::State& s) const */ double PathActuator::getLengtheningSpeed(const SimTK::State& s) const { - return getGeometryPath().getLengtheningSpeed(s); + return getPath().getLengtheningSpeed(s); } //_____________________________________________________________________________ /** @@ -173,7 +173,7 @@ void PathActuator::computeForce( const SimTK::State& s, { if(!_model) return; - const GeometryPath &path = getGeometryPath(); + const auto &path = getPath(); // compute path's lengthening speed if necessary double speed = path.getLengtheningSpeed(s); @@ -200,7 +200,7 @@ void PathActuator::computeForce( const SimTK::State& s, */ double PathActuator::computeMomentArm(const SimTK::State& s, Coordinate& aCoord) const { - return getGeometryPath().computeMomentArm(s, aCoord); + return getPath().computeMomentArm(s, aCoord); } //------------------------------------------------------------------------------ @@ -216,7 +216,7 @@ void PathActuator::extendRealizeDynamics(const SimTK::State& state) const if (appliesForce(state) && !isActuationOverridden(state)){ const SimTK::Vec3 color = computePathColor(state); if (!color.isNaN()) - getGeometryPath().setColor(state, color); + getPath().setColor(state, color); } } diff --git a/OpenSim/Simulation/Model/PathActuator.h b/OpenSim/Simulation/Model/PathActuator.h index 49e1f5b2ec..18c2401964 100644 --- a/OpenSim/Simulation/Model/PathActuator.h +++ b/OpenSim/Simulation/Model/PathActuator.h @@ -24,6 +24,7 @@ * -------------------------------------------------------------------------- */ #include "Actuator.h" +#include "AbstractPath.h" #include "GeometryPath.h" //============================================================================= @@ -37,8 +38,8 @@ class Model; /** * This is the base class for actuators that apply controllable tension along - * a geometry path. %PathActuator has no states; the control is simply the - * tension to be applied along a geometry path (i.e. tensionable rope). + * a path. %PathActuator has no states; the control is simply the tension to be + * applied along a path (i.e. tensionable rope). * * @author Ajay Seth */ @@ -48,8 +49,8 @@ class OSIMSIMULATION_API PathActuator : public ScalarActuator { //============================================================================= // PROPERTIES //============================================================================= - OpenSim_DECLARE_UNNAMED_PROPERTY(GeometryPath, - "The set of points defining the path of the actuator."); + OpenSim_DECLARE_PROPERTY(path, AbstractPath, + "The path of the actuator which defines length and lengthening speed."); OpenSim_DECLARE_PROPERTY(optimal_force, double, "The maximum force this actuator can produce."); @@ -67,10 +68,35 @@ class OSIMSIMULATION_API PathActuator : public ScalarActuator { // GET AND SET //-------------------------------------------------------------------------- // Path - GeometryPath& updGeometryPath() { return upd_GeometryPath(); } - const GeometryPath& getGeometryPath() const - { return get_GeometryPath(); } - bool hasGeometryPath() const override { return true;}; + AbstractPath& updPath() { return upd_path(); } + const AbstractPath& getPath() const { return get_path(); } + + template + PathType& updPath() { + return dynamic_cast(upd_path()); + } + template + const PathType& getPath() const { + return dynamic_cast(get_path()); + } + + template + PathType* tryUpdPath() { + return dynamic_cast(&upd_path()); + } + template + const PathType* tryGetPath() const { + return dynamic_cast(&get_path()); + } + + GeometryPath& updGeometryPath() { + return updPath(); + } + const GeometryPath& getGeometryPath() const { + return getPath(); + } + + bool hasVisualPath() const override { return getPath().isVisualPath(); }; // OPTIMAL FORCE void setOptimalForce(double aOptimalForce); @@ -90,8 +116,10 @@ class OSIMSIMULATION_API PathActuator : public ScalarActuator { double getStress( const SimTK::State& s ) const override; // Convenience method to add PathPoints - /** Note that this function does not maintain the State and so should be used only - before a valid State is created */ + /** @note This function does not maintain the State and so should be used + * only before a valid State is created. + * @note Only valid if the `path` owned by this PathActuator supports + * PathPoint%s (e.g., GeometryPath). */ void addNewPathPoint(const std::string& proposedName, const PhysicalFrame& aBody, const SimTK::Vec3& aPositionOnBody); diff --git a/OpenSim/Simulation/Model/PathSpring.cpp b/OpenSim/Simulation/Model/PathSpring.cpp index 695380e4e3..bfbf6a5f0a 100644 --- a/OpenSim/Simulation/Model/PathSpring.cpp +++ b/OpenSim/Simulation/Model/PathSpring.cpp @@ -25,8 +25,6 @@ // INCLUDES //============================================================================= #include "PathSpring.h" -#include "GeometryPath.h" -#include "PointForceDirection.h" //============================================================================= // STATICS @@ -64,7 +62,7 @@ PathSpring::PathSpring(const string& name, double restLength, void PathSpring::constructProperties() { setAuthors("Ajay Seth"); - constructProperty_GeometryPath(GeometryPath()); + constructProperty_path(GeometryPath()); constructProperty_resting_length(SimTK::NaN); constructProperty_stiffness(SimTK::NaN); constructProperty_dissipation(SimTK::NaN); @@ -106,8 +104,7 @@ void PathSpring::extendFinalizeFromProperties() { Super::extendFinalizeFromProperties(); - GeometryPath& path = upd_GeometryPath(); - path.setDefaultColor(DefaultPathSpringColor); + updPath().setDefaultColor(DefaultPathSpringColor); OPENSIM_THROW_IF_FRMOBJ( (SimTK::isNaN(get_resting_length()) || get_resting_length() < 0), @@ -139,7 +136,7 @@ void PathSpring::extendFinalizeFromProperties() */ double PathSpring::getLength(const SimTK::State& s) const { - return getGeometryPath().getLength(s); + return getPath().getLength(s); } double PathSpring::getStretch(const SimTK::State& s) const @@ -152,7 +149,7 @@ double PathSpring::getStretch(const SimTK::State& s) const double PathSpring::getLengtheningSpeed(const SimTK::State& s) const { - return getGeometryPath().getLengtheningSpeed(s); + return getPath().getLengtheningSpeed(s); } double PathSpring::getTension(const SimTK::State& s) const @@ -174,13 +171,13 @@ extendPostScale(const SimTK::State& s, const ScaleSet& scaleSet) { Super::extendPostScale(s, scaleSet); - GeometryPath& path = upd_GeometryPath(); + AbstractPath& path = updPath(); if (path.getPreScaleLength(s) > 0.0) { double scaleFactor = path.getLength(s) / path.getPreScaleLength(s); upd_resting_length() *= scaleFactor; - // Clear the pre-scale length that was stored in the GeometryPath. + // Clear the pre-scale length that was stored in the AbstractPath. path.setPreScaleLength(s, 0.0); } } @@ -191,9 +188,10 @@ extendPostScale(const SimTK::State& s, const ScaleSet& scaleSet) /** * Compute the moment-arm of this muscle about a coordinate. */ -double PathSpring::computeMomentArm(const SimTK::State& s, const Coordinate& aCoord) const +double PathSpring::computeMomentArm(const SimTK::State& s, + const Coordinate& aCoord) const { - return getGeometryPath().computeMomentArm(s, aCoord); + return getPath().computeMomentArm(s, aCoord); } @@ -202,17 +200,7 @@ void PathSpring::computeForce(const SimTK::State& s, SimTK::Vector_& bodyForces, SimTK::Vector& generalizedForces) const { - const GeometryPath& path = getGeometryPath(); + const AbstractPath& path = getPath(); const double& tension = getTension(s); - - OpenSim::Array PFDs; - path.getPointForceDirections(s, &PFDs); - - for (int i=0; i < PFDs.getSize(); i++) { - applyForceToPoint(s, PFDs[i]->frame(), PFDs[i]->point(), - tension*PFDs[i]->direction(), bodyForces); - } - - for(int i=0; i < PFDs.getSize(); i++) - delete PFDs[i]; + path.addInEquivalentForces(s, tension, bodyForces, generalizedForces); } diff --git a/OpenSim/Simulation/Model/PathSpring.h b/OpenSim/Simulation/Model/PathSpring.h index 4d9f2fe332..bc6f5922c7 100644 --- a/OpenSim/Simulation/Model/PathSpring.h +++ b/OpenSim/Simulation/Model/PathSpring.h @@ -28,23 +28,24 @@ // INCLUDES //============================================================================= #include "Force.h" +#include "AbstractPath.h" +#include "GeometryPath.h" namespace OpenSim { -class GeometryPath; class ScaleSet; //============================================================================= //============================================================================= /** * A class implementing a PathSpring. The path of the PathSpring is - * determined by a GeometryPath object. A PathSpring is a massless Force - * element which applies tension along a path connected to bodies and can wrap - * over surfaces. The tension is proportional to its stretch beyond its - * resting length and the amount of dissipation scales with amount of stretch, - * such that tension = (K*s)*(1+D*ldot) where stretch, s = l-lo for l > lo, and - * 0 otherwise. l is the path length of the spring and lo is its rest length. - * K is the linear stiffness and D is the dissipation factor. + * determined by an object derived from AbstractPath. A PathSpring is a + * massless Force element which applies tension along a path connected to bodies + * and can wrap over surfaces. The tension is proportional to its stretch + * beyond its resting length and the amount of dissipation scales with amount of + * stretch, such that tension = (K*s)*(1+D*ldot) where stretch, s = l-lo for + * l > lo, and 0 otherwise. l is the path length of the spring and lo is its + * rest length. K is the linear stiffness and D is the dissipation factor. * When l < lo the spring applies no tension to the bodies and considered * to be slack. * @@ -62,9 +63,8 @@ OpenSim_DECLARE_CONCRETE_OBJECT(PathSpring, Force); "The linear stiffness (N/m) of the PathSpring"); OpenSim_DECLARE_PROPERTY(dissipation, double, "The dissipation factor (s/m) of the PathSpring"); - OpenSim_DECLARE_UNNAMED_PROPERTY(GeometryPath, - "The GeometryPath defines the set of points and wrapping surface" - "interactions that form the path of action of the PathSpring"); + OpenSim_DECLARE_PROPERTY(path, AbstractPath, + "The path defines the length and lengthening speed of the PathSpring"); //============================================================================= // OUTPUTS @@ -118,12 +118,36 @@ OpenSim_DECLARE_CONCRETE_OBJECT(PathSpring, Force); { return get_dissipation(); } void setDissipation(double dissipation); - /** Access the GeometryPath to update connection points and - specify wrap objects the path can interact with. */ - const GeometryPath& getGeometryPath() const - { return get_GeometryPath(); } - GeometryPath& updGeometryPath() - { return upd_GeometryPath(); } + /** get/set the path object */ + AbstractPath& updPath() { return upd_path(); } + const AbstractPath& getPath() const { return get_path(); } + + template + PathType& updPath() { + return dynamic_cast(upd_path()); + } + template + const PathType& getPath() const { + return dynamic_cast(get_path()); + } + + template + PathType* tryUpdPath() { + return dynamic_cast(&upd_path()); + } + template + const PathType* tryGetPath() const { + return dynamic_cast(&get_path()); + } + + GeometryPath& updGeometryPath() { + return updPath(); + } + const GeometryPath& getGeometryPath() const { + return getPath(); + } + + bool hasVisualPath() const override { return getPath().isVisualPath(); }; //-------------------------------------------------------------------------- // State dependent values diff --git a/OpenSim/Simulation/Test/testForces.cpp b/OpenSim/Simulation/Test/testForces.cpp index cdfb53dc0e..c897702bb1 100644 --- a/OpenSim/Simulation/Test/testForces.cpp +++ b/OpenSim/Simulation/Test/testForces.cpp @@ -2093,13 +2093,13 @@ void testBlankevoort1991Ligament() { new Blankevoort1991Ligament("ligament", stiffness, restlength); ligament->set_damping_coefficient(0.0); - ligament->upd_GeometryPath().appendNewPathPoint( + ligament->updGeometryPath().appendNewPathPoint( "origin", ground, Vec3(0.0, 0.0, 0.0)); - ligament->upd_GeometryPath().addPathWrap(*pulley1); - ligament->upd_GeometryPath().appendNewPathPoint( + ligament->updGeometryPath().addPathWrap(*pulley1); + ligament->updGeometryPath().appendNewPathPoint( "midpoint", ground, Vec3(0.1, 0.6, 0.0)); - ligament->upd_GeometryPath().addPathWrap(*pulley2); - ligament->upd_GeometryPath().appendNewPathPoint( + ligament->updGeometryPath().addPathWrap(*pulley2); + ligament->updGeometryPath().appendNewPathPoint( "insertion", *block, Vec3(0.0, 0.0, 0.0)); osimModel.addForce(ligament); diff --git a/OpenSim/Simulation/Test/testMomentArms.cpp b/OpenSim/Simulation/Test/testMomentArms.cpp index 30365fb399..ab9af57578 100644 --- a/OpenSim/Simulation/Test/testMomentArms.cpp +++ b/OpenSim/Simulation/Test/testMomentArms.cpp @@ -252,7 +252,7 @@ SimTK::Vector computeGenForceScaling(const Model &osimModel, const SimTK::State && (ac.getJoint().getName() != "tib_pat_r") ){ MobilizedBodyIndex modbodIndex = ac.getBodyIndex(); const MobilizedBody& mobod = osimModel.getMatterSubsystem().getMobilizedBody(modbodIndex); - SpatialVec Hcol = mobod.getHCol(s, SimTK::MobilizerUIndex(0)); //ac.getMobilizerQIndex())); // get n’th column of H + SpatialVec Hcol = mobod.getHCol(s, SimTK::MobilizerUIndex(0)); //ac.getMobilizerQIndex())); // get nth column of H /*double thetaScale = */Hcol[0].norm(); // magnitude of the rotational part of this column of H