diff --git a/Applications/Analyze/test/CMakeLists.txt b/Applications/Analyze/test/CMakeLists.txt index ebbd36b51a..a65715d7bb 100644 --- a/Applications/Analyze/test/CMakeLists.txt +++ b/Applications/Analyze/test/CMakeLists.txt @@ -1,3 +1,5 @@ +find_package(Catch2 REQUIRED + HINTS "${OPENSIM_DEPENDENCIES_DIR}/catch2") file(GLOB TEST_PROGS "test*.cpp") file(GLOB TEST_FILES *.osim *.xml *.sto *.mot) @@ -5,7 +7,7 @@ file(GLOB TEST_FILES *.osim *.xml *.sto *.mot) OpenSimAddTests( TESTPROGRAMS ${TEST_PROGS} DATAFILES ${TEST_FILES} - LINKLIBS osimTools + LINKLIBS osimTools Catch2::Catch2WithMain ) if(BUILD_TESTING) diff --git a/Applications/Analyze/test/testPointKinematics.cpp b/Applications/Analyze/test/testPointKinematics.cpp new file mode 100644 index 0000000000..bc3f177a79 --- /dev/null +++ b/Applications/Analyze/test/testPointKinematics.cpp @@ -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 +#include // std::next + +#include +#include +#include + +#include + +using namespace OpenSim; + +constexpr double tol = std::numeric_limits::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(); + 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(); + // 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(); + 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(); + 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(); +} + diff --git a/Bindings/CMakeLists.txt b/Bindings/CMakeLists.txt index 4854f4dffd..afe43ffc3a 100644 --- a/Bindings/CMakeLists.txt +++ b/Bindings/CMakeLists.txt @@ -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. diff --git a/CHANGELOG.md b/CHANGELOG.md index 165950b671..7a0eb34cd9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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). diff --git a/OpenSim/Analyses/PointKinematics.cpp b/OpenSim/Analyses/PointKinematics.cpp index c70806b59f..a32d2d26cc 100644 --- a/OpenSim/Analyses/PointKinematics.cpp +++ b/OpenSim/Analyses/PointKinematics.cpp @@ -368,7 +368,7 @@ void PointKinematics::setRelativeToBody(const PhysicalFrame* aBody) // CHECK if (aBody==NULL) { log_warn("PointKinematics.setRelativeToBody: null body pointer."); - _body = NULL; + _relativeToBody = NULL; return; } @@ -376,7 +376,7 @@ void PointKinematics::setRelativeToBody(const PhysicalFrame* aBody) _relativeToBody = aBody; _relativeToBodyName = aBody->getName(); log_info("PointKinematics.setRelativeToBody: set relative-to body to {}.", - _bodyName); + _relativeToBodyName); } //_____________________________________________________________________________ diff --git a/OpenSim/Common/DelimFileAdapter.h b/OpenSim/Common/DelimFileAdapter.h index 9414060f9d..6ad9c3b65d 100644 --- a/OpenSim/Common/DelimFileAdapter.h +++ b/OpenSim/Common/DelimFileAdapter.h @@ -307,6 +307,7 @@ DelimFileAdapter::clone() const { template typename DelimFileAdapter::OutputTables DelimFileAdapter::extendRead(const std::string& fileName) const { +#ifndef SWIG OPENSIM_THROW_IF(fileName.empty(), EmptyFileName); @@ -468,6 +469,7 @@ DelimFileAdapter::extendRead(const std::string& fileName) const { output_tables.emplace(tableString(), table); return output_tables; +#endif } template diff --git a/OpenSim/Moco/MocoUtilities.h b/OpenSim/Moco/MocoUtilities.h index 54638246e5..6c967a8104 100644 --- a/OpenSim/Moco/MocoUtilities.h +++ b/OpenSim/Moco/MocoUtilities.h @@ -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. @@ -171,12 +171,23 @@ OSIMMOCO_API MocoTrajectory createPeriodicTrajectory( ".*lumbar_bending(?!/value).*", ".*lumbar_rotation.*"}, std::vector negateAndShiftPatterns = { - ".*pelvis_list/value", - ".*pelvis_tz/value", - ".*lumbar_bending/value"}, + ".*pelvis_list/value", + ".*pelvis_tz/value", + ".*lumbar_bending/value"}, std::vector> 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 addPatterns = {".*pelvis_tx/value"}, + std::vector negatePatterns = {".*pelvis_list(?!/value).*", + ".*pelvis_rotation.*", ".*pelvis_tz(?!/value).*", + ".*lumbar_bending(?!/value).*", ".*lumbar_rotation.*"}, + std::vector 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).