Skip to content

Commit

Permalink
Merge branch 'main' into feature_abspath-in-component-exceptions
Browse files Browse the repository at this point in the history
  • Loading branch information
adamkewley authored Jan 21, 2025
2 parents 068f242 + b4ba7ca commit f30869c
Show file tree
Hide file tree
Showing 7 changed files with 190 additions and 9 deletions.
4 changes: 3 additions & 1 deletion Applications/Analyze/test/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
find_package(Catch2 REQUIRED
HINTS "${OPENSIM_DEPENDENCIES_DIR}/catch2")

file(GLOB TEST_PROGS "test*.cpp")
file(GLOB TEST_FILES *.osim *.xml *.sto *.mot)

OpenSimAddTests(
TESTPROGRAMS ${TEST_PROGS}
DATAFILES ${TEST_FILES}
LINKLIBS osimTools
LINKLIBS osimTools Catch2::Catch2WithMain
)

if(BUILD_TESTING)
Expand Down
165 changes: 165 additions & 0 deletions Applications/Analyze/test/testPointKinematics.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
/* -------------------------------------------------------------------------- *
* OpenSim: testPointKinematics.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-2024 Stanford University and the Authors *
* Author(s): Alexander Beattie *
* *
* 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 <limits>
#include <iterator> // std::next

#include <OpenSim/Analyses/PointKinematics.h>
#include <OpenSim/Simulation/Model/Model.h>
#include <OpenSim/Tools/AnalyzeTool.h>

#include <catch2/catch_all.hpp>

using namespace OpenSim;

constexpr double tol = std::numeric_limits<double>::epsilon() * 10;

TEST_CASE("PointKinematics Construction with Blank Model", "[PointKinematics]") {
Model model;
PointKinematics pk(&model);

REQUIRE(pk.getBody() == nullptr);
REQUIRE(pk.getRelativeToBody() == nullptr);
REQUIRE(pk.getPointName() == "NONAME");
}

TEST_CASE("PointKinematics Construction with No Model", "[PointKinematics]") {
PointKinematics pk;

REQUIRE(pk.getBody() == nullptr);
REQUIRE(pk.getRelativeToBody() == nullptr);
REQUIRE(pk.getPointName() == "NONAME");
}

TEST_CASE("PointKinematics Set and Get Body", "[PointKinematics]") {
Model model("SinglePin.osim");
const auto &bodies = model.getComponentList<Body>();
const auto bodyIt = bodies.begin();
const auto body = *bodyIt;

PointKinematics pk(&model);
pk.setBody(&body);

REQUIRE(pk.getBody() == &body);
REQUIRE(pk.getBody()->getName() == body.getName());
}

TEST_CASE("PointKinematics Set and Get RelativeToBody", "[PointKinematics]") {
Model model("subject01.osim");
const auto &bodies = model.getComponentList<Body>();
// Gets the 2nd body (which is at index 1)
const auto bodyIt = std::next(bodies.begin(),1);
const auto body = *bodyIt;

PointKinematics pk(&model);
pk.setRelativeToBody(&body);

REQUIRE(pk.getRelativeToBody() == &body);
REQUIRE(pk.getRelativeToBody()->getName() == body.getName());
}

TEST_CASE("PointKinematics Set and Get Point", "[PointKinematics]") {
Model model;
PointKinematics pk(&model);
SimTK::Vec3 point(1.0, 2.0, 3.0);

pk.setPoint(point);
SimTK::Vec3 retrievedPoint;
pk.getPoint(retrievedPoint);

REQUIRE_THAT(retrievedPoint[0], Catch::Matchers::WithinAbs(1.0,tol));
REQUIRE_THAT(retrievedPoint[1], Catch::Matchers::WithinAbs(2.0,tol));
REQUIRE_THAT(retrievedPoint[2], Catch::Matchers::WithinAbs(3.0,tol));
}

TEST_CASE("PointKinematics Record Kinematics Single Body", "[PointKinematics]") {
std::string model_name = "subject01.osim";
std::string coordinates_file_name = "subject01_walk1_ik.mot";

Model model(model_name);
model.initSystem();

AnalyzeTool analyzeTool;
analyzeTool.setName("test_analysis");
analyzeTool.setModel(model);
analyzeTool.setModelFilename(model_name);
analyzeTool.setCoordinatesFileName(coordinates_file_name);
analyzeTool.setLowpassCutoffFrequency(-1);
analyzeTool.setResultsDir("results");

const auto &bodies = model.getComponentList<Body>();
const auto bodyIt = bodies.begin();
const auto body1 = *bodyIt;
// Gets the 2nd body (which is at index 1)
const auto bodyIt2 = std::next(bodies.begin(),1);
const auto body2 = *bodyIt2;

PointKinematics pk(&model);
pk.setBody(&body1);
pk.setRelativeToBody(&body2);
pk.setPoint(SimTK::Vec3(1.0, 2.0, 3.0));
analyzeTool.updAnalysisSet().cloneAndAppend(pk);

std::string output_file_name = "test_analysis_point_kinematics_1.xml";
analyzeTool.print(output_file_name);
AnalyzeTool roundTrip(output_file_name);
roundTrip.run();
}

TEST_CASE("PointKinematics Record Kinematics Whole Body", "[PointKinematics]") {
std::string model_name = "subject01.osim";
std::string coordinates_file_name = "subject01_walk1_ik.mot";

Model model(model_name);
model.initSystem();

AnalyzeTool analyzeTool;
analyzeTool.setName("test_analysis");
analyzeTool.setModel(model);
analyzeTool.setModelFilename(model_name);
analyzeTool.setCoordinatesFileName(coordinates_file_name);
analyzeTool.setLowpassCutoffFrequency(-1);
analyzeTool.setResultsDir("results");

const auto &bodies = model.getComponentList<Body>();
for (auto& root : bodies) {
const std::string& root_name = root.getName();
for (auto& sub_component: bodies) {
const std::string& sub_component_name = sub_component.getName();
// Create point kinematics reporter
PointKinematics pointKin;
pointKin.setPointName(root_name + "-" + sub_component_name);
pointKin.setPoint(SimTK::Vec3(1.0, 2.0, 3.0));
pointKin.setBody(&root);
pointKin.setRelativeToBody(&sub_component);
analyzeTool.updAnalysisSet().cloneAndAppend(pointKin);
}
}

std::string output_file_name = "test_analysis_point_kinematics_2.xml";
analyzeTool.print(output_file_name);
AnalyzeTool roundTrip(output_file_name);
roundTrip.run();
}

2 changes: 1 addition & 1 deletion Bindings/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
if(BUILD_PYTHON_WRAPPING OR BUILD_JAVA_WRAPPING)
find_package(SWIG 4.1.1 EXACT REQUIRED)
find_package(SWIG 4.1.1 REQUIRED)
endif()

# Flags are both Python and Java bindings will use.
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ v4.6
in the intersection of names in the model and names in the provided referece/data. Remove methods that are index based from public interface.(#3951)
- Replace usages of `OpenSim::make_unique` with `std::make_unique` and remove wrapper function now that C++14 is used in OpenSim (#3979).
- Add utility method `createVectorLinspaceInterval` for the `std::vector` type and add unit tests. Utilize the new utility method to fix a bug (#3976) in creating the uniformly sampled time interval from the experimental data sampling frequency in `APDMDataReader` and `XsensDataReader` (#3977).
- Fix Point Kinematics Reporter variable and initialization error and add unit tests (#3966)
- The message associated with `OpenSim::PropertyException` now includes the full absolute path to the component that threw the exception (#3987).


Expand Down
4 changes: 2 additions & 2 deletions OpenSim/Analyses/PointKinematics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -368,15 +368,15 @@ void PointKinematics::setRelativeToBody(const PhysicalFrame* aBody)
// CHECK
if (aBody==NULL) {
log_warn("PointKinematics.setRelativeToBody: null body pointer.");
_body = NULL;
_relativeToBody = NULL;
return;
}

// SET
_relativeToBody = aBody;
_relativeToBodyName = aBody->getName();
log_info("PointKinematics.setRelativeToBody: set relative-to body to {}.",
_bodyName);
_relativeToBodyName);
}

//_____________________________________________________________________________
Expand Down
2 changes: 2 additions & 0 deletions OpenSim/Common/DelimFileAdapter.h
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,7 @@ DelimFileAdapter<T>::clone() const {
template<typename T>
typename DelimFileAdapter<T>::OutputTables
DelimFileAdapter<T>::extendRead(const std::string& fileName) const {
#ifndef SWIG
OPENSIM_THROW_IF(fileName.empty(),
EmptyFileName);

Expand Down Expand Up @@ -468,6 +469,7 @@ DelimFileAdapter<T>::extendRead(const std::string& fileName) const {
output_tables.emplace(tableString(), table);

return output_tables;
#endif
}

template<typename T>
Expand Down
21 changes: 16 additions & 5 deletions OpenSim/Moco/MocoUtilities.h
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ OSIMMOCO_API void prescribeControlsToModel(const MocoTrajectory& trajectory,
OSIMMOCO_API MocoTrajectory simulateTrajectoryWithTimeStepping(
const MocoTrajectory& trajectory, Model model,
double integratorAccuracy = SimTK::NaN);

#ifndef SWIG
/// Convert a trajectory covering half the period of a symmetric motion into a
/// trajectory over the full period. This is useful for simulations of half a
/// gait cycle.
Expand Down Expand Up @@ -171,12 +171,23 @@ OSIMMOCO_API MocoTrajectory createPeriodicTrajectory(
".*lumbar_bending(?!/value).*",
".*lumbar_rotation.*"},
std::vector<std::string> negateAndShiftPatterns = {
".*pelvis_list/value",
".*pelvis_tz/value",
".*lumbar_bending/value"},
".*pelvis_list/value",
".*pelvis_tz/value",
".*lumbar_bending/value"},
std::vector<std::pair<std::string, std::string>> symmetryPatterns =
{{R"(_r(\/|_|$))", "_l$1"}, {R"(_l(\/|_|$))", "_r$1"}});

#else
// Variant that doesn't take symmetryPatterns which are unusable from scripting
OSIMMOCO_API MocoTrajectory createPeriodicTrajectory(
const MocoTrajectory& halfPeriodTrajectory,
std::vector<std::string> addPatterns = {".*pelvis_tx/value"},
std::vector<std::string> negatePatterns = {".*pelvis_list(?!/value).*",
".*pelvis_rotation.*", ".*pelvis_tz(?!/value).*",
".*lumbar_bending(?!/value).*", ".*lumbar_rotation.*"},
std::vector<std::string> negateAndShiftPatterns = {
".*pelvis_list/value", ".*pelvis_tz/value",
".*lumbar_bending/value"});
#endif
/// This obtains the value of the OPENSIM_MOCO_PARALLEL environment variable.
/// The value has the following meanings:
/// - 0: run in series (not parallel).
Expand Down

0 comments on commit f30869c

Please sign in to comment.