<NME> ModelVisualizer.cpp
<BEF> /* -------------------------------------------------------------------------- *
 *                       OpenSim:  ModelVisualizer.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-2017 Stanford University and the Authors                *
 * Author(s): Michael A. Sherman                                              *
 *                                                                            *
 * 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 "ModelVisualizer.h"
#include "Model.h"
#include <OpenSim/version.h>
#include <OpenSim/Common/ModelDisplayHints.h>
#include <simbody/internal/Visualizer_InputListener.h>
#include <simbody/internal/Visualizer_Reporter.h>

#include <string>
using std::string;
#include <iostream>
using std::cerr; using std::clog; using std::endl;

using namespace OpenSim;
using namespace SimTK;

//==============================================================================
//                       OPENSIM INPUT LISTENER
//==============================================================================

// These constants are used to identify the OpenSim display menu in the 
// Visualizer window and particular selections from it.
static const int ShowMenuId = 1;
static const int ToggleWrapGeometry = 0;
static const int ToggleContactGeometry = 1;
static const int ToggleMusclePaths = 2;
static const int TogglePathPoints = 3;
static const int ToggleMarkers = 4;
static const int ToggleFrames = 5;
static const int ToggleDefaultGeometry = 6;

/* This class gets first crack at user input coming in through the Visualizer
window. We use it to intercept anything we want to handle as part of the 
standard OpenSim-provided interface, such as turning on and off display of wrap
objects. Anything we don't handle here will just get passed on to the
Visualizer's InputSilo where it will remain until the user's program goes
looking for it. */
class OpenSimInputListener : public Visualizer::InputListener {
public:
    OpenSimInputListener(Model& model) : _model(model) {}

    /* This is the implementation of the InputListener interface. We're going
    to override only the menu-pick method and ignore anything we don't
    recognize. Caution: this is being called from the Visualizer's input thread,
    *not* the main execution thread. Synchronization is required to do anything
    complicated; here we're just setting/clearing visualization flags so no 
    synchronization is required. Note that the Visualizer's InputSilo class 
    handles synchronization automatically but this one does not. */
    bool menuSelected(int menu, int item) override {
        if (menu != ShowMenuId) return false; // some other menu
        ModelDisplayHints& hints = _model.updDisplayHints();
        switch(item) {
        case ToggleWrapGeometry:
            hints.set_show_wrap_geometry(!hints.get_show_wrap_geometry());
            return true; // absorb this input
        case ToggleContactGeometry:
            hints.set_show_contact_geometry(!hints.get_show_contact_geometry());
            return true;
        case ToggleMusclePaths:
            hints.set_show_path_geometry(!hints.get_show_path_geometry());
            return true;
        case TogglePathPoints:
            hints.set_show_path_points(!hints.get_show_path_points());
            return true;
        case ToggleMarkers:
            hints.set_show_markers(!hints.get_show_markers());
            return true;
        case ToggleDefaultGeometry: {
            SimbodyMatterSubsystem& matter = 
                _model.updMatterSubsystem();
            matter.setShowDefaultGeometry(!matter.getShowDefaultGeometry());
            return true;
            }
        };
        return false; // let someone else deal with this input
    }
private:
    Model&  _model;
};





// Draw a path point with a small body-axis-aligned cross centered on
// the point.
void DefaultGeometry::drawPathPoint(const MobilizedBodyIndex&             body,
                          const Vec3&                           pt_B,
                          const Vec3&                           color,
                          Array_<SimTK::DecorativeGeometry>&    geometry)
{
    geometry.push_back(DecorativeSphere(0.005)
                .setTransform(pt_B)
                .setBodyId(body)
                .setColor(color)
                .setOpacity(0.8));
}

void DefaultGeometry::generateDecorations
   (const State&                         state, 
    Array_<SimTK::DecorativeGeometry>&   geometry) 
{
    // Ask all the ModelComponents to generate dynamic geometry.
    _model.generateDecorations(false, _model.getDisplayHints(),
                               state, geometry);
}

//==============================================================================
//                            MODEL VISUALIZER
//==============================================================================

void ModelVisualizer::show(const SimTK::State& state) const {
    // Make sure we're realized at least through Velocity stage.
    _model.getMultibodySystem().realize(state, SimTK::Stage::Velocity);
    getSimbodyVisualizer().report(state);
}

// See if we can find the given file. The rules are
//  - if it is an absolute pathname, we only get one shot, else:
//  - define "modelDir" to be the absolute pathname of the 
//      directory from which we read in the .osim model, if we did,
//      otherwise modelDir="." (current directory).
//  - look for the geometry file in modelDir
//  - look for the geometry file in modelDir/Geometry
//  - search the user added paths in dirToSearch in reverse chronological order
//    i.e. latest path added is searched first.
//  - look for the geometry file in installDir/Geometry
bool ModelVisualizer::
findGeometryFile(const Model& aModel, 
                 const std::string&          geoFile,
                 bool&                       geoFileIsAbsolute,
                 SimTK::Array_<std::string>& attempts)
{
    attempts.clear();
    std::string geoDirectory, geoFileName, geoExtension; 
    SimTK::Pathname::deconstructPathname(geoFile, 
        geoFileIsAbsolute, geoDirectory, geoFileName, geoExtension);

    bool foundIt = false;
    if (geoFileIsAbsolute) {
        attempts.push_back(geoFile);
        foundIt = Pathname::fileExists(attempts.back());
    } else {
        const string geoDir = "Geometry" + Pathname::getPathSeparator();
        string modelDir;
        if (aModel.getInputFileName() == "Unassigned") 
            modelDir = Pathname::getCurrentWorkingDirectory();
        else {
            bool isAbsolutePath; string directory, fileName, extension; 
            SimTK::Pathname::deconstructPathname(
                aModel.getInputFileName(),
                isAbsolutePath, directory, fileName, extension);
            modelDir = isAbsolutePath 
                ? directory
                : Pathname::getCurrentWorkingDirectory() + directory;
        }

        attempts.push_back(modelDir + geoFile);
        foundIt = Pathname::fileExists(attempts.back());

        if (!foundIt) {
            attempts.push_back(modelDir + geoDir + geoFile); 
            foundIt = Pathname::fileExists(attempts.back());
        }

        if (!foundIt) {
            for(auto dir = dirsToSearch.crbegin();
                dir != dirsToSearch.crend();
                ++dir) {
                attempts.push_back(*dir + geoFile);
                if(Pathname::fileExists(attempts.back())) {
                    foundIt = true;
                    break;
                }
            }
        }
    }

    return foundIt;
}

// Initialize the static variable.
SimTK::Array_<std::string> ModelVisualizer::dirsToSearch{};

void ModelVisualizer::addDirToGeometrySearchPaths(const std::string& dir) {
    // Make sure to add trailing path-separator if one is not present.
    if(dir.back() == Pathname::getPathSeparator().back())
        dirsToSearch.push_back(dir);
    else
        dirsToSearch.push_back(dir + Pathname::getPathSeparator());
}

// Call this on a newly-constructed ModelVisualizer (typically from the Model's
// initSystem() method) to set up the various auxiliary classes used for
// visualization and user interaction. This involves modifications to the
// System that must be done prior to realizeTopology(), and may modify the
// Model also.
void ModelVisualizer::createVisualizer() {
    _model.updMatterSubsystem().setShowDefaultGeometry(false);

    // Allocate a Simbody Visualizer. The search will go as 
    // follows: first look in the same directory as the currently-
    // executing executable; then look at all the paths in the environment
    // variable PATH, then look in various default Simbody places.
    Array_<String> searchPath;
    if (SimTK::Pathname::environmentVariableExists("PATH")) {
        const auto& path = SimTK::Pathname::getEnvironmentVariable("PATH");
        std::string buffer{};
        for(const auto ch : path) {
            if(ch == ':' || ch == ';') {
                searchPath.push_back(buffer);
                buffer.clear();
            } else
                buffer.push_back(ch);
        }
    }
    _viz = new SimTK::Visualizer(_model.getMultibodySystem(),
                                 searchPath);

    // Make the Simbody Visualizer (that is, the display window) kill itself 
    // when the API-side connection is lost (because the Visualizer object gets
    // destructed). Otherwise it will hang around afterwards.
    _viz->setShutdownWhenDestructed(true);

    _viz->setCameraClippingPlanes(.01,100.);

    // Give it an OpenSim-friendly window heading.
    bool isAbsolutePath; string directory, fileName, extension; 
    SimTK::Pathname::deconstructPathname(
        SimTK::Pathname::getThisExecutablePath(), 
        isAbsolutePath, directory, fileName, extension);
    _viz->setWindowTitle("OpenSim " + OpenSim::GetVersion() 
                            + ": " + fileName + " (" + _model.getName() + ")");

    // Create a menu for choosing what to display.
    SimTK::Array_< std::pair<SimTK::String, int> > selections;
    selections.push_back(std::make_pair("Wrap geometry",
                                        ToggleWrapGeometry));
    selections.push_back(std::make_pair("Contact geometry",
                                        ToggleContactGeometry));
    selections.push_back(std::make_pair("Muscle paths",ToggleMusclePaths));
    selections.push_back(std::make_pair("Path points",TogglePathPoints));
    selections.push_back(std::make_pair("Markers",ToggleMarkers));
    selections.push_back(std::make_pair("Frames",ToggleFrames));
    selections.push_back(std::make_pair("Default geometry",
                                        ToggleDefaultGeometry));
    _viz->addMenu("Show", ShowMenuId, selections);

    // Add a DecorationGenerator to dispatch runtime generateDecorations()
    // calls.
    _decoGen = new DefaultGeometry(_model);
    _viz->addDecorationGenerator(_decoGen);

    // Add an input listener to handle display menu picks.
    _viz->addInputListener(new OpenSimInputListener(_model));

    // Allocate an InputSilo to pick up anything the above listener doesn't.
    _silo = new SimTK::Visualizer::InputSilo();
    _viz->addInputListener(_silo);

    // This is used for regular output of frames during forward dynamics.
    // TODO: allow user control of timing.
    _model.updMultibodySystem().addEventReporter
        (new SimTK::Visualizer::Reporter(*_viz, 1./30));
}

// We also rummage through the model to find fixed geometry that should be part
// of every frame. The supplied State must be realized through Instance stage.
void ModelVisualizer::collectFixedGeometry(const State& state) const {
    // Collect any fixed geometry from the ModelComponents.
    Array_<DecorativeGeometry> fixedGeometry;
    _model.generateDecorations
       (true, _model.getDisplayHints(), state, fixedGeometry);

    for (unsigned i=0; i < fixedGeometry.size(); ++i) {
        const DecorativeGeometry& dgeo = fixedGeometry[i];
        _viz->addDecoration(MobilizedBodyIndex(dgeo.getBodyId()), 
                            Transform(), dgeo);
    }
}
void ModelVisualizer::createVisualizer() {
    _model.updMatterSubsystem().setShowDefaultGeometry(false);

    // Allocate a Simbody Visualizer.
    _viz = new SimTK::Visualizer(_model.getMultibodySystem());
    _viz->setCameraClippingPlanes(.01,100.);
    _viz->setBackgroundColor(SimTK::Black);
    _viz->setBackgroundType(SimTK::Visualizer::SolidColor);

<MSG> Now looks for the Simbody visualizer executable in $OPENSIM_HOME/bin if that environment variable is set. Requires upgrading Simbody to >= rev 2732 to pick up the Visualizer constructor that accepts a search path.

git-svn-id: 9e047e1e624cce0a2485e750063112e834f85522@6983 fc29be9c-2cb0-420d-b520-73dbd18add6e

<DFF> @@ -353,8 +353,20 @@ findGeometryFile(const std::string&          geoFile,
 void ModelVisualizer::createVisualizer() {
     _model.updMatterSubsystem().setShowDefaultGeometry(false);
 
-    // Allocate a Simbody Visualizer.
-    _viz = new SimTK::Visualizer(_model.getMultibodySystem());
+    // Allocate a Simbody Visualizer. If environment variable
+    // OPENSIM_HOME is set, add its bin subdirectory to the search path
+    // for the SimbodyVisualizer executable. The search will go as 
+    // follows: first look in the same directory as the currently-
+    // executing executable; then look in the $OPENSIM_HOME/bin 
+    // directory, then look in various default Simbody places.
+    Array_<String> searchPath;
+    if (SimTK::Pathname::environmentVariableExists("OPENSIM_HOME")) {
+        searchPath.push_back( 
+            SimTK::Pathname::getEnvironmentVariable("OPENSIM_HOME")
+            + "/bin");
+    }
+    _viz = new SimTK::Visualizer(_model.getMultibodySystem(),
+                                 searchPath);
     _viz->setCameraClippingPlanes(.01,100.);
     _viz->setBackgroundColor(SimTK::Black);
     _viz->setBackgroundType(SimTK::Visualizer::SolidColor);

{'commit': 'diff --git a/OpenSim/Simulation/Model/ModelVisualizer.cpp b/OpenSim/Simulation/Model/ModelVisualizer.cpp\nindex cfff08bfb..fa2855474 100644\n--- a/OpenSim/Simulation/Model/ModelVisualizer.cpp\n+++ b/OpenSim/Simulation/Model/ModelVisualizer.cpp\n@@ -353,8 +353,20 @@ findGeometryFile(const std::string&          geoFile,\n void ModelVisualizer::createVisualizer() {\r\n     _model.updMatterSubsystem().setShowDefaultGeometry(false);\r\n \r\n-    // Allocate a Simbody Visualizer.\r\n-    _viz = new SimTK::Visualizer(_model.getMultibodySystem());\r\n+    // Allocate a Simbody Visualizer. If environment variable\r\n+    // OPENSIM_HOME is set, add its bin subdirectory to the search path\r\n+    // for the SimbodyVisualizer executable. The search will go as \r\n+    // follows: first look in the same directory as the currently-\r\n+    // executing executable; then look in the $OPENSIM_HOME/bin \r\n+    // directory, then look in various default Simbody places.\r\n+    Array_<String> searchPath;\r\n+    if (SimTK::Pathname::environmentVariableExists("OPENSIM_HOME")) {\r\n+        searchPath.push_back( \r\n+            SimTK::Pathname::getEnvironmentVariable("OPENSIM_HOME")\r\n+            + "/bin");\r\n+    }\r\n+    _viz = new SimTK::Visualizer(_model.getMultibodySystem(),\r\n+                                 searchPath);\r\n     _viz->setCameraClippingPlanes(.01,100.);\r\n     _viz->setBackgroundColor(SimTK::Black);\r\n     _viz->setBackgroundType(SimTK::Visualizer::SolidColor);\r\n', 'commit_subject': 'Now looks for the Simbody visualizer executable in $OPENSIM_HOME/bin if that environment variable is set. Requires upgrading Simbody to >= rev 2732 to pick up the Visualizer constructor that accepts a search path.', 'repo_name': 'opensim-org/opensim-core', 'committer': {'name': 'Michael Sherman', 'email': '1ef7dc0b228fbaf52bf319955a60ba299f4f212a@gmail.com', 'time_sec': '1331769427', 'tz_offset': '0', 'date': {'seconds': '1331769427'}}, 'commit_tree': 'f17e1cc6c274f6085be4da3d07f82610d59e9cc0', 'commit_message': 'Now looks for the Simbody visualizer executable in $OPENSIM_HOME/bin if that environment variable is set. Requires upgrading Simbody to >= rev 2732 to pick up the Visualizer constructor that accepts a search path.\n\ngit-svn-id: 9e047e1e624cce0a2485e750063112e834f85522@6983 fc29be9c-2cb0-420d-b520-73dbd18add6e\n', 'language_name': 'Java', 'license': 'apache-2.0', 'hunks': '@@ -353,8 +353,20 @@ findGeometryFile(const std::string&          geoFile,\n void ModelVisualizer::createVisualizer() {\r\n     _model.updMatterSubsystem().setShowDefaultGeometry(false);\r\n \r\n-    // Allocate a Simbody Visualizer.\r\n-    _viz = new SimTK::Visualizer(_model.getMultibodySystem());\r\n+    // Allocate a Simbody Visualizer. If environment variable\r\n+    // OPENSIM_HOME is set, add its bin subdirectory to the search path\r\n+    // for the SimbodyVisualizer executable. The search will go as \r\n+    // follows: first look in the same directory as the currently-\r\n+    // executing executable; then look in the $OPENSIM_HOME/bin \r\n+    // directory, then look in various default Simbody places.\r\n+    Array_<String> searchPath;\r\n+    if (SimTK::Pathname::environmentVariableExists("OPENSIM_HOME")) {\r\n+        searchPath.push_back( \r\n+            SimTK::Pathname::getEnvironmentVariable("OPENSIM_HOME")\r\n+            + "/bin");\r\n+    }\r\n+    _viz = new SimTK::Visualizer(_model.getMultibodySystem(),\r\n+                                 searchPath);\r\n     _viz->setCameraClippingPlanes(.01,100.);\r\n     _viz->setBackgroundColor(SimTK::Black);\r\n     _viz->setBackgroundType(SimTK::Visualizer::SolidColor);\r\n', 'addition_count': 14, 'deletion_count': 2, 'src_file': 'a/OpenSim/Simulation/Model/ModelVisualizer.cpp', 'tgt_file': 'b/OpenSim/Simulation/Model/ModelVisualizer.cpp', 'file_extension': '.cpp', 'before_file': '/* -------------------------------------------------------------------------- *\n *                       OpenSim:  ModelVisualizer.cpp                        *\n * -------------------------------------------------------------------------- *\n * The OpenSim API is a toolkit for musculoskeletal modeling and simulation.  *\n * See http://opensim.stanford.edu and the NOTICE file for more information.  *\n * OpenSim is developed at Stanford University and supported by the US        *\n * National Institutes of Health (U54 GM072970, R24 HD065690) and by DARPA    *\n * through the Warrior Web program.                                           *\n *                                                                            *\n * Copyright (c) 2005-2017 Stanford University and the Authors                *\n * Author(s): Michael A. Sherman                                              *\n *                                                                            *\n * Licensed under the Apache License, Version 2.0 (the "License"); you may    *\n * not use this file except in compliance with the License. You may obtain a  *\n * copy of the License at http://www.apache.org/licenses/LICENSE-2.0.         *\n *                                                                            *\n * Unless required by applicable law or agreed to in writing, software        *\n * distributed under the License is distributed on an "AS IS" BASIS,          *\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   *\n * See the License for the specific language governing permissions and        *\n * limitations under the License.                                             *\n * -------------------------------------------------------------------------- */\n\n#include "ModelVisualizer.h"\n#include "Model.h"\n#include <OpenSim/version.h>\n#include <OpenSim/Common/ModelDisplayHints.h>\n#include <simbody/internal/Visualizer_InputListener.h>\n#include <simbody/internal/Visualizer_Reporter.h>\n\n#include <string>\nusing std::string;\n#include <iostream>\nusing std::cerr; using std::clog; using std::endl;\n\nusing namespace OpenSim;\nusing namespace SimTK;\n\n//==============================================================================\n//                       OPENSIM INPUT LISTENER\n//==============================================================================\n\n// These constants are used to identify the OpenSim display menu in the \n// Visualizer window and particular selections from it.\nstatic const int ShowMenuId = 1;\nstatic const int ToggleWrapGeometry = 0;\nstatic const int ToggleContactGeometry = 1;\nstatic const int ToggleMusclePaths = 2;\nstatic const int TogglePathPoints = 3;\nstatic const int ToggleMarkers = 4;\nstatic const int ToggleFrames = 5;\nstatic const int ToggleDefaultGeometry = 6;\n\n/* This class gets first crack at user input coming in through the Visualizer\nwindow. We use it to intercept anything we want to handle as part of the \nstandard OpenSim-provided interface, such as turning on and off display of wrap\nobjects. Anything we don\'t handle here will just get passed on to the\nVisualizer\'s InputSilo where it will remain until the user\'s program goes\nlooking for it. */\nclass OpenSimInputListener : public Visualizer::InputListener {\npublic:\n    OpenSimInputListener(Model& model) : _model(model) {}\n\n    /* This is the implementation of the InputListener interface. We\'re going\n    to override only the menu-pick method and ignore anything we don\'t\n    recognize. Caution: this is being called from the Visualizer\'s input thread,\n    *not* the main execution thread. Synchronization is required to do anything\n    complicated; here we\'re just setting/clearing visualization flags so no \n    synchronization is required. Note that the Visualizer\'s InputSilo class \n    handles synchronization automatically but this one does not. */\n    bool menuSelected(int menu, int item) override {\n        if (menu != ShowMenuId) return false; // some other menu\n        ModelDisplayHints& hints = _model.updDisplayHints();\n        switch(item) {\n        case ToggleWrapGeometry:\n            hints.set_show_wrap_geometry(!hints.get_show_wrap_geometry());\n            return true; // absorb this input\n        case ToggleContactGeometry:\n            hints.set_show_contact_geometry(!hints.get_show_contact_geometry());\n            return true;\n        case ToggleMusclePaths:\n            hints.set_show_path_geometry(!hints.get_show_path_geometry());\n            return true;\n        case TogglePathPoints:\n            hints.set_show_path_points(!hints.get_show_path_points());\n            return true;\n        case ToggleMarkers:\n            hints.set_show_markers(!hints.get_show_markers());\n            return true;\n        case ToggleDefaultGeometry: {\n            SimbodyMatterSubsystem& matter = \n                _model.updMatterSubsystem();\n            matter.setShowDefaultGeometry(!matter.getShowDefaultGeometry());\n            return true;\n            }\n        };\n        return false; // let someone else deal with this input\n    }\nprivate:\n    Model&  _model;\n};\n\n\n\n\n\n// Draw a path point with a small body-axis-aligned cross centered on\n// the point.\nvoid DefaultGeometry::drawPathPoint(const MobilizedBodyIndex&             body,\n                          const Vec3&                           pt_B,\n                          const Vec3&                           color,\n                          Array_<SimTK::DecorativeGeometry>&    geometry)\n{\n    geometry.push_back(DecorativeSphere(0.005)\n                .setTransform(pt_B)\n                .setBodyId(body)\n                .setColor(color)\n                .setOpacity(0.8));\n}\n\nvoid DefaultGeometry::generateDecorations\n   (const State&                         state, \n    Array_<SimTK::DecorativeGeometry>&   geometry) \n{\n    // Ask all the ModelComponents to generate dynamic geometry.\n    _model.generateDecorations(false, _model.getDisplayHints(),\n                               state, geometry);\n}\n\n//==============================================================================\n//                            MODEL VISUALIZER\n//==============================================================================\n\nvoid ModelVisualizer::show(const SimTK::State& state) const {\n    // Make sure we\'re realized at least through Velocity stage.\n    _model.getMultibodySystem().realize(state, SimTK::Stage::Velocity);\n    getSimbodyVisualizer().report(state);\n}\n\n// See if we can find the given file. The rules are\n//  - if it is an absolute pathname, we only get one shot, else:\n//  - define "modelDir" to be the absolute pathname of the \n//      directory from which we read in the .osim model, if we did,\n//      otherwise modelDir="." (current directory).\n//  - look for the geometry file in modelDir\n//  - look for the geometry file in modelDir/Geometry\n//  - search the user added paths in dirToSearch in reverse chronological order\n//    i.e. latest path added is searched first.\n//  - look for the geometry file in installDir/Geometry\nbool ModelVisualizer::\nfindGeometryFile(const Model& aModel, \n                 const std::string&          geoFile,\n                 bool&                       geoFileIsAbsolute,\n                 SimTK::Array_<std::string>& attempts)\n{\n    attempts.clear();\n    std::string geoDirectory, geoFileName, geoExtension; \n    SimTK::Pathname::deconstructPathname(geoFile, \n        geoFileIsAbsolute, geoDirectory, geoFileName, geoExtension);\n\n    bool foundIt = false;\n    if (geoFileIsAbsolute) {\n        attempts.push_back(geoFile);\n        foundIt = Pathname::fileExists(attempts.back());\n    } else {\n        const string geoDir = "Geometry" + Pathname::getPathSeparator();\n        string modelDir;\n        if (aModel.getInputFileName() == "Unassigned") \n            modelDir = Pathname::getCurrentWorkingDirectory();\n        else {\n            bool isAbsolutePath; string directory, fileName, extension; \n            SimTK::Pathname::deconstructPathname(\n                aModel.getInputFileName(),\n                isAbsolutePath, directory, fileName, extension);\n            modelDir = isAbsolutePath \n                ? directory\n                : Pathname::getCurrentWorkingDirectory() + directory;\n        }\n\n        attempts.push_back(modelDir + geoFile);\n        foundIt = Pathname::fileExists(attempts.back());\n\n        if (!foundIt) {\n            attempts.push_back(modelDir + geoDir + geoFile); \n            foundIt = Pathname::fileExists(attempts.back());\n        }\n\n        if (!foundIt) {\n            for(auto dir = dirsToSearch.crbegin();\n                dir != dirsToSearch.crend();\n                ++dir) {\n                attempts.push_back(*dir + geoFile);\n                if(Pathname::fileExists(attempts.back())) {\n                    foundIt = true;\n                    break;\n                }\n            }\n        }\n    }\n\n    return foundIt;\n}\n\n// Initialize the static variable.\nSimTK::Array_<std::string> ModelVisualizer::dirsToSearch{};\n\nvoid ModelVisualizer::addDirToGeometrySearchPaths(const std::string& dir) {\n    // Make sure to add trailing path-separator if one is not present.\n    if(dir.back() == Pathname::getPathSeparator().back())\n        dirsToSearch.push_back(dir);\n    else\n        dirsToSearch.push_back(dir + Pathname::getPathSeparator());\n}\n\n// Call this on a newly-constructed ModelVisualizer (typically from the Model\'s\n// initSystem() method) to set up the various auxiliary classes used for\n// visualization and user interaction. This involves modifications to the\n// System that must be done prior to realizeTopology(), and may modify the\n// Model also.\nvoid ModelVisualizer::createVisualizer() {\n    _model.updMatterSubsystem().setShowDefaultGeometry(false);\n\n    // Allocate a Simbody Visualizer. The search will go as \n    // follows: first look in the same directory as the currently-\n    // executing executable; then look at all the paths in the environment\n    // variable PATH, then look in various default Simbody places.\n    Array_<String> searchPath;\n    if (SimTK::Pathname::environmentVariableExists("PATH")) {\n        const auto& path = SimTK::Pathname::getEnvironmentVariable("PATH");\n        std::string buffer{};\n        for(const auto ch : path) {\n            if(ch == \':\' || ch == \';\') {\n                searchPath.push_back(buffer);\n                buffer.clear();\n            } else\n                buffer.push_back(ch);\n        }\n    }\n    _viz = new SimTK::Visualizer(_model.getMultibodySystem(),\n                                 searchPath);\n\n    // Make the Simbody Visualizer (that is, the display window) kill itself \n    // when the API-side connection is lost (because the Visualizer object gets\n    // destructed). Otherwise it will hang around afterwards.\n    _viz->setShutdownWhenDestructed(true);\n\n    _viz->setCameraClippingPlanes(.01,100.);\n\n    // Give it an OpenSim-friendly window heading.\n    bool isAbsolutePath; string directory, fileName, extension; \n    SimTK::Pathname::deconstructPathname(\n        SimTK::Pathname::getThisExecutablePath(), \n        isAbsolutePath, directory, fileName, extension);\n    _viz->setWindowTitle("OpenSim " + OpenSim::GetVersion() \n                            + ": " + fileName + " (" + _model.getName() + ")");\n\n    // Create a menu for choosing what to display.\n    SimTK::Array_< std::pair<SimTK::String, int> > selections;\n    selections.push_back(std::make_pair("Wrap geometry",\n                                        ToggleWrapGeometry));\n    selections.push_back(std::make_pair("Contact geometry",\n                                        ToggleContactGeometry));\n    selections.push_back(std::make_pair("Muscle paths",ToggleMusclePaths));\n    selections.push_back(std::make_pair("Path points",TogglePathPoints));\n    selections.push_back(std::make_pair("Markers",ToggleMarkers));\n    selections.push_back(std::make_pair("Frames",ToggleFrames));\n    selections.push_back(std::make_pair("Default geometry",\n                                        ToggleDefaultGeometry));\n    _viz->addMenu("Show", ShowMenuId, selections);\n\n    // Add a DecorationGenerator to dispatch runtime generateDecorations()\n    // calls.\n    _decoGen = new DefaultGeometry(_model);\n    _viz->addDecorationGenerator(_decoGen);\n\n    // Add an input listener to handle display menu picks.\n    _viz->addInputListener(new OpenSimInputListener(_model));\n\n    // Allocate an InputSilo to pick up anything the above listener doesn\'t.\n    _silo = new SimTK::Visualizer::InputSilo();\n    _viz->addInputListener(_silo);\n\n    // This is used for regular output of frames during forward dynamics.\n    // TODO: allow user control of timing.\n    _model.updMultibodySystem().addEventReporter\n        (new SimTK::Visualizer::Reporter(*_viz, 1./30));\n}\n\n// We also rummage through the model to find fixed geometry that should be part\n// of every frame. The supplied State must be realized through Instance stage.\nvoid ModelVisualizer::collectFixedGeometry(const State& state) const {\n    // Collect any fixed geometry from the ModelComponents.\n    Array_<DecorativeGeometry> fixedGeometry;\n    _model.generateDecorations\n       (true, _model.getDisplayHints(), state, fixedGeometry);\n\n    for (unsigned i=0; i < fixedGeometry.size(); ++i) {\n        const DecorativeGeometry& dgeo = fixedGeometry[i];\n        _viz->addDecoration(MobilizedBodyIndex(dgeo.getBodyId()), \n                            Transform(), dgeo);\n    }\n}\nvoid ModelVisualizer::createVisualizer() {\r\n    _model.updMatterSubsystem().setShowDefaultGeometry(false);\r\n\r\n    // Allocate a Simbody Visualizer.\r\n    _viz = new SimTK::Visualizer(_model.getMultibodySystem());\r\n    _viz->setCameraClippingPlanes(.01,100.);\r\n    _viz->setBackgroundColor(SimTK::Black);\r\n    _viz->setBackgroundType(SimTK::Visualizer::SolidColor);\r\n', 'message': 'Now looks for the Simbody visualizer executable in $OPENSIM_HOME/bin if that environment variable is set. Requires upgrading Simbody to >= rev 2732 to pick up the Visualizer constructor that accepts a search path.\n\ngit-svn-id: 9e047e1e624cce0a2485e750063112e834f85522@6983 fc29be9c-2cb0-420d-b520-73dbd18add6e\n', 'author_name': 'Michael Sherman', 'lang': 'cpp'}