diff --git a/CMakeLists.txt b/CMakeLists.txt index 028af75f641..e7fd85ddc69 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -149,7 +149,9 @@ set(IGNITION_GAZEBO_GUI_PLUGIN_INSTALL_DIR #============================================================================ # Configure the build #============================================================================ -ign_configure_build(QUIT_IF_BUILD_ERRORS) +ign_configure_build(QUIT_IF_BUILD_ERRORS + COMPONENTS model +) add_subdirectory(examples) diff --git a/model/src/CMakeLists.txt b/model/src/CMakeLists.txt new file mode 100644 index 00000000000..8ae8ebf0c78 --- /dev/null +++ b/model/src/CMakeLists.txt @@ -0,0 +1,36 @@ +ign_get_libsources_and_unittests(sources gtest_sources) +list(APPEND sources cmd/ModelCommandAPI.cc) + +ign_add_component(model SOURCES ${sources} GET_TARGET_NAME model_lib_target) + + +if (MSVC) + # Warning #4251 is the "dll-interface" warning that tells you when types used + # by a class are not being exported. These generated source files have private + # members that don't get exported, so they trigger this warning. However, the + # warning is not important since those members do not need to be interfaced + # with. + set_source_files_properties(${sources} ${gtest_sources} COMPILE_FLAGS "/wd4251 /wd4146") +endif() + +# Unit tests +ign_build_tests( + TYPE "UNIT" + SOURCES ${gtest_sources} + LIB_DEPS ${EXTRA_TEST_LIB_DEPS} + TEST_LIST model_tests +) + +if(TARGET UNIT_ModelCommandAPI_TEST) + + target_compile_definitions(UNIT_ModelCommandAPI_TEST PRIVATE + "IGN_PATH=\"${IGNITION-TOOLS_BINARY_DIRS}\"") + + target_compile_definitions(UNIT_ModelCommandAPI_TEST PRIVATE + "PROJECT_SOURCE_PATH=\"${PROJECT_SOURCE_DIR}\"") + +endif() + +if(NOT WIN32) + add_subdirectory(cmd) +endif() diff --git a/model/src/ModelCommandAPI_TEST.cc b/model/src/ModelCommandAPI_TEST.cc new file mode 100644 index 00000000000..a9f8471852d --- /dev/null +++ b/model/src/ModelCommandAPI_TEST.cc @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2021 Open Source Robotics Foundation + * + * 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 "cmd/ModelCommandAPI.hh" + +#include +#include +#include + +#include + +#include "ignition/gazebo/Server.hh" + +static const std::string kIgnModelCommand(std::string(IGN_PATH) + + "/ign model "); + +///////////////////////////////////////////////// +std::string customExecStr(std::string _cmd) +{ + _cmd += " 2>&1"; + FILE *pipe = popen(_cmd.c_str(), "r"); + + if (!pipe) + return "ERROR"; + + char buffer[128]; + std::string result = ""; + + while (!feof(pipe)) + { + if (fgets(buffer, 128, pipe) != nullptr) + { + result += buffer; + } + } + + pclose(pipe); + return result; +} + +// Test `ign model` command when no Gazebo server is running. +TEST(ModelCommandAPI, NoServerRunning) +{ + const std::string cmd = kIgnModelCommand + "--list "; + const std::string output = customExecStr(cmd); + const std::string expectedOutput{ + "\nService call to [/gazebo/worlds] timed out\n"}; + EXPECT_EQ(expectedOutput, output); +} + +// Tests `ign model` command. +TEST(ModelCommandAPI, Commands) +{ + ignition::gazebo::ServerConfig serverConfig; + serverConfig.SetSdfFile(std::string(PROJECT_SOURCE_PATH) + + "/examples/worlds/shapes.sdf"); + + ignition::gazebo::Server server(serverConfig); + // Run at least one iteration before continuing to guarantee correctly set up. + ASSERT_TRUE(server.Run(true, 5, false)); + // Run without blocking. + server.Run(false, 0, false); + + // Tested command: ign model --list + { + const std::string cmd = kIgnModelCommand + "--list"; + const std::string output = customExecStr(cmd); + const std::string expectedOutput = + "Available models:\n" + " - ground_plane\n" + " - box\n" + " - cylinder\n" + " - sphere\n"; + EXPECT_EQ(expectedOutput, output); + } + + // Tested command: ign model -m cylinder + { + const std::string cmd = kIgnModelCommand + "-m box"; + const std::string output = customExecStr(cmd); + const std::string expectedOutput = + +"\nRequesting state for world [shapes] on service [/world/shapes/state]...\n\n" +"Name: box\n" +" - Pose: \n" +" [0.000000 | -0.000000 | 0.500000]\n" +" [0.000000 | 0.000000 | 0.000000]\n\n" +" - Link [9]\n" +" - Name: box_link\n" +" - Parent: box [8]\n" +" - Mass: [1.000000]\n" +" - Inertial Matrix: \n" +" [1.000000 | 0.000000 | 0.000000]\n" +" [0.000000 | 1.000000 | 0.000000]\n" +" [0.000000 | 0.000000 | 1.000000]\n" +" - Pose: \n" +" [0.000000 | 0.000000 | 0.000000]\n" +" [0.000000 | -0.000000 | 0.000000]\n"; + + // TODO(@wagnermarcos): Implement test. + EXPECT_EQ(expectedOutput, output); + } + + // Tested command: ign model -m vehicle_blue --pose + { + const std::string cmd = kIgnModelCommand + "-m box --pose "; + const std::string output = customExecStr(cmd); + const std::string expectedOutput = + "\nRequesting state for world [shapes] on service [/world/shapes/state]...\n\n" + "Name: box\n" + " - Pose: \n" + " [0.000000 | -0.000000 | 0.500000]\n" + " [0.000000 | 0.000000 | 0.000000]\n\n"; + EXPECT_EQ(expectedOutput, output); + // TODO(@wagnermarcos): Implement test. + } + + // Tested command: ign model -m box --link box_link + { + const std::string cmd = kIgnModelCommand + + "-m box --link box_link"; + const std::string output = customExecStr(cmd); + const std::string expectedOutput = + "\nRequesting state for world [shapes] on service [/world/shapes/state]...\n\n" + " - Link [9]\n" + " - Name: box_link\n" + " - Parent: box [8]\n" + " - Mass: [1.000000]\n" + " - Inertial Matrix: \n" + " [1.000000 | 0.000000 | 0.000000]\n" + " [0.000000 | 1.000000 | 0.000000]\n" + " [0.000000 | 0.000000 | 1.000000]\n" + " - Pose: \n" + " [0.000000 | 0.000000 | 0.000000]\n" + " [0.000000 | -0.000000 | 0.000000]\n"; + EXPECT_EQ(expectedOutput, output); + // TODO(@wagnermarcos): Implement test. + } +} + +////////////////////////////////////////////////// +int main(int argc, char **argv) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/model/src/cmd/CMakeLists.txt b/model/src/cmd/CMakeLists.txt new file mode 100644 index 00000000000..e0c2838aef0 --- /dev/null +++ b/model/src/cmd/CMakeLists.txt @@ -0,0 +1,58 @@ +#=============================================================================== +# Used for the installed version. +# Generate the ruby script that gets installed. +# Note that the major version of the library is included in the name. +set(cmd_model_script_generated "${CMAKE_CURRENT_BINARY_DIR}/cmdmodel${PROJECT_VERSION_MAJOR}.rb") +set(cmd_model_script_configured "${cmd_model_script_generated}.configured") + +# Set the model_library_location variable to the relative path to the library file +# within the install directory structure. +set(model_library_location "../../../${CMAKE_INSTALL_LIBDIR}/$") + +configure_file( + "cmdmodel.rb.in" + "${cmd_model_script_configured}" + @ONLY) + +file(GENERATE + OUTPUT "${cmd_model_script_generated}" + INPUT "${cmd_model_script_configured}") + +install(FILES ${cmd_model_script_generated} DESTINATION lib/ruby/ignition) + +# Used for the installed version. +set(ign_model_ruby_path "${CMAKE_INSTALL_PREFIX}/lib/ruby/ignition/cmdmodel${PROJECT_VERSION_MAJOR}") + +set(ignmodel_configured "${CMAKE_CURRENT_BINARY_DIR}/ignmodel${PROJECT_VERSION_MAJOR}.yaml") +configure_file( + "ignmodel.yaml.in" + ${ignmodel_configured}) + +install(FILES ${ignmodel_configured} DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/ignition/) + +#=============================================================================== +# Generate the ruby script for internal testing. +# Note that the major version of the library is included in the name. +set(cmd_model_ruby_test_dir "${CMAKE_BINARY_DIR}/model/test/lib/ruby/ignition") +set(cmd_model_script_generated_test "${cmd_model_ruby_test_dir}/cmdmodel${PROJECT_VERSION_MAJOR}.rb") +set(cmd_model_script_configured_test "${cmd_model_script_generated_test}.configured") + +# Set the model_library_location variable to the full path of the library file +# within the build directory +set(model_library_location "$") + +configure_file( + "cmdmodel.rb.in" + "${cmd_model_script_configured_test}" + @ONLY) + +file(GENERATE + OUTPUT "${cmd_model_script_generated_test}" + INPUT "${cmd_model_script_configured_test}") + +# Used for internal testing. +set(ign_model_ruby_path "${cmd_model_script_generated_test}") + +configure_file( + "ignmodel.yaml.in" + "${cmd_model_ruby_test_dir}/ignmodel${PROJECT_VERSION_MAJOR}.yaml") diff --git a/model/src/cmd/ModelCommandAPI.cc b/model/src/cmd/ModelCommandAPI.cc new file mode 100644 index 00000000000..32042d0e8ab --- /dev/null +++ b/model/src/cmd/ModelCommandAPI.cc @@ -0,0 +1,421 @@ +/* + * Copyright (C) 2019 Open Source Robotics Foundation + * + * 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 "ModelCommandAPI.hh" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +////////////////////////////////////////////////// +std::string getWorldName() +{ + std::string world_name; + + // Create a transport node. + ignition::transport::Node node; + + bool executed{false}; + bool result{false}; + unsigned int timeout{5000}; + std::string service{"/gazebo/worlds"}; + + // Request and block + ignition::msgs::StringMsg_V res; + executed = node.Request(service, timeout, res, result); + + if (!executed) + { + std::cerr << std::endl << "Service call to [" << service << "] timed out" + << std::endl; + return ""; + } + + if (!result) + { + std::cerr << std::endl << "Service call to [" << service << "] failed" + << std::endl; + return ""; + } + + world_name += res.data().Get(0); + + return world_name; +} + +////////////////////////////////////////////////// +extern "C" IGNITION_GAZEBO_VISIBLE void cmdModelList() +{ + std::string world = getWorldName(); + if (world.empty()) + return; + + // Create a transport node. + ignition::transport::Node node; + + bool executed{false}; + bool result{false}; + unsigned int timeout{5000}; + std::string service{"/world/" + world + "/state"}; + + // Request and block + ignition::msgs::SerializedStepMap res; + executed = node.Request(service, timeout, res, result); + + if (!executed) + { + std::cerr << std::endl << "Service call to [" << service << "] timed out" + << std::endl; + return; + } + + if (!result) + { + std::cerr << std::endl << "Service call to [" << service << "] failed" + << std::endl; + return; + } + + // Instantiate an ECM and populate with data from message + ignition::gazebo::EntityComponentManager ecm; + ecm.SetState(res.state()); + + auto models = ecm.EntitiesByComponents( + ignition::gazebo::components::ParentEntity(1), + ignition::gazebo::components::Model()); + + std::cout << "Available models:" << std::endl; + + for(auto &m : models) + { + auto nameComp = + ecm.Component(m); + std::cout << " - " << nameComp->Data() << std::endl; + } +} + +////////////////////////////////////////////////// +void printPose(uint64_t entity, + ignition::gazebo::EntityComponentManager &ecm){ + auto modelPose = ecm.EntitiesByComponents( + ignition::gazebo::components::ParentEntity(entity), + ignition::gazebo::components::Pose()); + { + std::string poseInfo; + auto poseComp = + ecm.Component(entity); + auto nameComp = + ecm.Component(entity); + if (poseComp) + { + poseInfo += "\n [" + std::to_string(poseComp->Data().X()) + " | " + + std::to_string(poseComp->Data().Y()) + " | " + + std::to_string(poseComp->Data().Z()) + "]\n" + " [" + std::to_string(poseComp->Data().Roll()) + " | " + + std::to_string(poseComp->Data().Pitch()) + " | " + + std::to_string(poseComp->Data().Yaw()) + "]"; + + std::cout << "Name: " << nameComp->Data() << std::endl + << " - Pose: " << poseInfo << std::endl << std::endl; + } + } + +} + +////////////////////////////////////////////////// +void printLinks(uint64_t entity, + ignition::gazebo::EntityComponentManager &ecm, + std::string link_name) +{ + auto links = ecm.EntitiesByComponents( + ignition::gazebo::components::ParentEntity(entity), + ignition::gazebo::components::Link()); + for(auto &_entity : links) + { + { + auto parentComp = + ecm.Component(_entity); + + auto nameComp = + ecm.Component(_entity); + + if(link_name.length() && link_name != nameComp->Data()) + continue; + + std::string parentInfo; + if (parentComp) + { + auto parentNameComp = + ecm.Component( + parentComp->Data()); + + if (parentNameComp) + { + parentInfo += parentNameComp->Data() + " "; + } + parentInfo += "[" + std::to_string(parentComp->Data()) + "]"; + } + + std::cout << " - Link [" << _entity << "]" << std::endl + << " - Name: " << nameComp->Data() << std::endl + << " - Parent: " << parentInfo << std::endl; + } + { + auto inertialComp = + ecm.Component(_entity); + + std::string massInfo; + std::string inertialInfo; + if (inertialComp) + { + auto inertialMatrix = inertialComp->Data().MassMatrix(); + auto massComp = inertialComp->Data().MassMatrix().Mass(); + + massInfo += "[" + std::to_string(massComp) + "]"; + inertialInfo += + "\n [" + std::to_string(inertialMatrix.Ixx()) + " | " + + std::to_string(inertialMatrix.Ixy()) + " | " + + std::to_string(inertialMatrix.Ixz()) + "]\n" + " [" + std::to_string(inertialMatrix.Ixy()) + " | " + + std::to_string(inertialMatrix.Iyy()) + " | " + + std::to_string(inertialMatrix.Iyz()) + "]\n" + " [" + std::to_string(inertialMatrix.Ixz()) + " | " + + std::to_string(inertialMatrix.Iyz()) + " | " + + std::to_string(inertialMatrix.Izz()) + "]"; + std::cout << " - Mass: " << massInfo << std::endl + << " - Inertial Matrix: " << inertialInfo << std::endl; + } + } + { + auto poseComp = + ecm.Component(_entity); + + std::string poseInfo; + if (poseComp) + { + poseInfo += + "\n [" + std::to_string(poseComp->Data().X()) + " | " + + std::to_string(poseComp->Data().Y()) + " | " + + std::to_string(poseComp->Data().Z()) + "]\n" + " [" + std::to_string(poseComp->Data().Roll()) + " | " + + std::to_string(poseComp->Data().Pitch()) + " | " + + std::to_string(poseComp->Data().Yaw()) + "]"; + + std::cout << " - Pose: " << poseInfo << std::endl; + } + } + } +} + +////////////////////////////////////////////////// +void printJoints(uint64_t entity, + ignition::gazebo::EntityComponentManager &ecm, + std::string joint_name) +{ + auto joints = ecm.EntitiesByComponents( + ignition::gazebo::components::ParentEntity(entity), + ignition::gazebo::components::Joint()); + + for(auto &_entity : joints) + { + { + auto parentComp = + ecm.Component(_entity); + + auto nameComp = + ecm.Component(_entity); + + if(joint_name.length() && joint_name != nameComp->Data()) + continue; + + std::string parentInfo; + if (parentComp) + { + auto parentNameComp = + ecm.Component( + parentComp->Data()); + + if (parentNameComp) + { + parentInfo += parentNameComp->Data() + " "; + } + parentInfo += "[" + std::to_string(parentComp->Data()) + "]"; + } + + std::cout << " - Joint [" << _entity << "]" << std::endl + << " - Name: " << nameComp->Data() << std::endl + << " - Parent: " << parentInfo << std::endl; + } + { + + auto childLinkComp = + ecm.Component(_entity); + auto parentLinkComp = + ecm.Component(_entity); + + if (childLinkComp && parentLinkComp) + { + std::cout << " - Parent Link: [" << childLinkComp->Data() << "]\n" + << " - Child Link: [" << parentLinkComp->Data() << "]\n"; + } + } + } +} + +////////////////////////////////////////////////// +void printBoundingBox(uint64_t entity, + ignition::gazebo::EntityComponentManager &ecm) +{ + auto bounding_boxes = ecm.EntitiesByComponents( + ignition::gazebo::components::ParentEntity(entity), + ignition::gazebo::components::AxisAlignedBox()); + + for(auto &_entity : bounding_boxes) + { + { + auto parentComp = + ecm.Component(_entity); + + auto nameComp = + ecm.Component(_entity); + + std::string parentInfo; + if (parentComp) + { + auto parentNameComp = + ecm.Component( + parentComp->Data()); + + if (parentNameComp) + { + parentInfo += parentNameComp->Data() + " "; + } + parentInfo += "[" + std::to_string(parentComp->Data()) + "]"; + } + + std::cout << " - Bounding Box [" << _entity << "]" << std::endl + << " - Name: " << nameComp->Data() << std::endl + << " - Parent: " << parentInfo << std::endl; + } + { + auto boxComp = + ecm.Component(_entity); + + if (boxComp) + { + + std::cout << " Size: [" << boxComp->Data().Size() << "]\n" + << " Center: [" << boxComp->Data().Center() << "]\n" + << " Min: [" << boxComp->Data().Min() << "]\n" + << " Max: [" << boxComp->Data().Max() << "]\n"; + } + } + } +} + +////////////////////////////////////////////////// +extern "C" IGNITION_GAZEBO_VISIBLE void cmdModelInfo( + const char *_model, int _pose, int _link, const char *_link_name, + int _joint, const char *_joint_name) +{ + std::string link_name{""}; + if(_link_name) + link_name = _link_name; + std::string joint_name{""}; + if(_joint_name) + joint_name = _joint_name; + + bool printAll{false}; + if(!_pose && !_link && !_joint) + printAll = true; + + std::string model{_model}; + // Get arguments + std::string world = getWorldName(); + if (world.empty()) + return; + // Create a transport node. + ignition::transport::Node node; + + bool executed{false}; + bool result{false}; + unsigned int timeout{5000}; + std::string service{"/world/" + world + "/state"}; + + std::cout << std::endl << "Requesting state for world [" << world + << "] on service [" << service << "]..." << std::endl << std::endl; + + // Request and block + ignition::msgs::SerializedStepMap res; + executed = node.Request(service, timeout, res, result); + + if (!executed) + { + std::cerr << std::endl << "Service call to [" << service << "] timed out" + << std::endl; + return; + } + + if (!result) + { + std::cerr << std::endl << "Service call to [" << service << "] failed" + << std::endl; + return; + } + + // Instantiate an ECM and populate with data from message + ignition::gazebo::EntityComponentManager ecm; + ecm.SetState(res.state()); + + // Get the desired model entity. + auto entity = ecm.EntityByComponents( + ignition::gazebo::components::Name(model), + ignition::gazebo::components::Model()); + + // Get the pose of the model + if(printAll | _pose) + printPose(entity, ecm); + + // Get the links information + if(printAll | _link) + printLinks(entity, ecm, link_name); + + // Get the links information + if(printAll | _joint) + printJoints(entity, ecm, joint_name); + + // Get the bounding_boxes information + if(printAll) + printBoundingBox(entity, ecm); +} diff --git a/model/src/cmd/ModelCommandAPI.hh b/model/src/cmd/ModelCommandAPI.hh new file mode 100644 index 00000000000..d79f85f7366 --- /dev/null +++ b/model/src/cmd/ModelCommandAPI.hh @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2018 Open Source Robotics Foundation + * + * 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 "ignition/gazebo/Export.hh" +#include "ignition/gazebo/Entity.hh" +#include +#include + +extern "C" +{ + /// \brief Get the name of the world being used. + std::string getWorldName(); + + /// \brief Print the model pose information. + void printPose(uint64_t entity, + ignition::gazebo::EntityComponentManager &ecm); + + /// \brief Print the model links information. + void printLinks(uint64_t entity, + ignition::gazebo::EntityComponentManager &ecm, std::string link_name); + + /// \brief Print the model joints information. + void printJoints(uint64_t entity, + ignition::gazebo::EntityComponentManager &ecm, std::string joint_name); + + /// \brief Print the model bounding box information. + void printBoundingBox(uint64_t entity, + ignition::gazebo::EntityComponentManager &ecm); + + /// \brief External hook to get a list of available models. + extern "C" IGNITION_GAZEBO_VISIBLE void cmdModelList(); + + /// \brief External hook to dump model information. + extern "C" IGNITION_GAZEBO_VISIBLE void cmdModelInfo( + const char *_model, int _pose, int _link, const char *_link_name, + int _joint, const char *joint_name); +} diff --git a/model/src/cmd/cmdmodel.rb.in b/model/src/cmd/cmdmodel.rb.in new file mode 100644 index 00000000000..ae9d8530c0e --- /dev/null +++ b/model/src/cmd/cmdmodel.rb.in @@ -0,0 +1,148 @@ +#!/usr/bin/ruby + +# Copyright (C) 2017 Open Source Robotics Foundation +# +# 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. + +# We use 'dl' for Ruby <= 1.9.x and 'fiddle' for Ruby >= 2.0.x +if RUBY_VERSION.split('.')[0] < '2' + require 'dl' + require 'dl/import' + include DL +else + require 'fiddle' + require 'fiddle/import' + include Fiddle +end + +require 'date' +require 'optparse' + +# Constants. +LIBRARY_NAME = '@model_library_location@' +LIBRARY_VERSION = '@PROJECT_VERSION_FULL@' +COMMON_OPTIONS = + " -h [ --help ] Print this help message. \n"\ + " \n"\ + +COMMANDS = { 'model' => +"Print information about models.\n\n"+ + " --list Get a list of the available models. \n"\ + " -m [--model] [model_name] Select the model to be shown. \n"\ + " -l [--link] [link_name] Select a link to show it's properties. \n"\ + " -j [--joint] [joint_name] Select a joint to show it's properties. \n\n"\ + " E.g. to get information about the \n"\ + " box_link in the shapes world, run: \n"\ + " ign model -m box -l box_link \n\n"+ + COMMON_OPTIONS +} + +# +# Class for the Ignition Gazebo Model command line tools. +# +class Cmd + + # + # Return a structure describing the options. + # + def parse(args) + options = { + 'pose' => 0, + 'link' => 0, + 'link_name' => '', + 'joint' => 0, + 'joint_name' => '' + } + usage = COMMANDS[args[0]] + + opt_parser = OptionParser.new do |opts| + opts.banner = usage + + opts.on('-h', '--help') do + puts usage + exit + end + opts.on('-m', '--model [arg]', String, 'Model name') do |m| + options['model'] = m + end + opts.on('--list', String, 'List models available') do + options['list'] = 1 + end + opts.on('-p', '--pose', Integer, 'Request pose') do + options['pose'] = 1 + end + opts.on('-l', '--link [arg]', String, 'Request link information') do |l| + options['link'] = 1 + options['link_name'] = l + end + opts.on('-j', '--joint [arg]', String, 'Request joint information') do |j| + options['joint'] = 1 + options['joint_name'] = j + end + end # opt_parser do + + + begin + opt_parser.parse!(args) + rescue + puts usage + exit(-1) + end + + options['command'] = args[0] + + options + end # parse() + + + def execute(args) + options = parse(args) + + # Read the plugin that handles the command. + if LIBRARY_NAME[0] == '/' + # If the first character is a slash, we'll assume that we've been given an + # absolute path to the library. This is only used during test mode. + plugin = LIBRARY_NAME + else + # We're assuming that the library path is relative to the current + # location of this script. + plugin = File.expand_path(File.join(File.dirname(__FILE__), LIBRARY_NAME)) + end + conf_version = LIBRARY_VERSION + + begin + Importer.dlload plugin + rescue DLError => e + puts "Library error for [#{plugin}]: #{e.to_s}" + exit(-1) + end + + + if options.key?('list') + Importer.extern 'void cmdModelList()' + Importer.cmdModelList() + exit(0) + elsif options.key?('model') + Importer.extern 'void cmdModelInfo(const char *, int, int, const char *, + int, const char *)' + + Importer.cmdModelInfo(options['model'], options['pose'], options['link'], + options['link_name'], options['joint'], options['joint_name']) + else + puts 'Command error: I do not have an implementation for '\ + "command [ign #{options['command']}]." + end + # # execute + end +# class +end diff --git a/model/src/cmd/ignmodel.yaml.in b/model/src/cmd/ignmodel.yaml.in new file mode 100644 index 00000000000..1e3c7eecc98 --- /dev/null +++ b/model/src/cmd/ignmodel.yaml.in @@ -0,0 +1,8 @@ +--- # Subcommands available inside ignition-model. +format: 1.0.0 +library_name: ignition-model +library_version: @PROJECT_VERSION_FULL@ +library_path: @ign_model_ruby_path@ +commands: + - model : Get information about a model. +---