diff --git a/Changelog.md b/Changelog.md index f8bd3589abb..33888d53d97 100644 --- a/Changelog.md +++ b/Changelog.md @@ -442,6 +442,124 @@ ### Ignition Gazebo 3.X.X (202X-XX-XX) + +### Ignition Gazebo 3.9.0 (2021-07-XX) + +1. Add a convenience function for getting possibly non-existing components. + * [Pull request #629](https://github.com/ignitionrobotics/ign-gazebo/pull/629) + +1. Fix topLevelModel method + * [Pull request #600](https://github.com/ignitionrobotics/ign-gazebo/pull/600) + +1. World exporter + * [Pull request #474](https://github.com/ignitionrobotics/ign-gazebo/pull/474) + +1. Fix finding PBR materials + * [Pull request #575](https://github.com/ignitionrobotics/ign-gazebo/pull/575) + +1. Handle multiple logical cameras + * [Pull request #539](https://github.com/ignitionrobotics/ign-gazebo/pull/539) + +1. Make some tests more robust + * [Pull request #314](https://github.com/ignitionrobotics/ign-gazebo/pull/314) + +1. Fix codecheck + * [Pull request #887](https://github.com/ignitionrobotics/ign-gazebo/pull/887) + +1. Hello world plugin added + * [Pull request #699](https://github.com/ignitionrobotics/ign-gazebo/pull/699) + +1. Model info CLI `ign model` + * [Pull request #893](https://github.com/ignitionrobotics/ign-gazebo/pull/893) + +1. Don't create components for entities that don't exist + * [Pull request #927](https://github.com/ignitionrobotics/ign-gazebo/pull/927) + +1. Adds Mesh Tutorial + * [Pull request #915](https://github.com/ignitionrobotics/ign-gazebo/pull/915) + +1. Fix updating GUI plugin on load + * [Pull request #904](https://github.com/ignitionrobotics/ign-gazebo/pull/904) + +1. Fix documentation for the Sensor component + * [Pull request #898](https://github.com/ignitionrobotics/ign-gazebo/pull/898) + +1. Use UINT64_MAX for kComponentTpyeIDInvalid instead of relying on underflow + * [Pull request #889](https://github.com/ignitionrobotics/ign-gazebo/pull/889) + +1. Fix mouse view control target position + * [Pull request #879](https://github.com/ignitionrobotics/ign-gazebo/pull/879) + +1. Set GUI camera pose + * [Pull request #863](https://github.com/ignitionrobotics/ign-gazebo/pull/863) + +1. Enables confirmation dialog when closing Gazebo. + * [Pull request #850](https://github.com/ignitionrobotics/ign-gazebo/pull/850) + +1. Depend on ign-rendering 3.5 + * [Pull request #867](https://github.com/ignitionrobotics/ign-gazebo/pull/867) + +1. Using math::SpeedLimiter on the diff_drive controller. + * [Pull request #833](https://github.com/ignitionrobotics/ign-gazebo/pull/833) + +1. New example: get an ECM snapshot from an external program + * [Pull request #859](https://github.com/ignitionrobotics/ign-gazebo/pull/859) + +1. Fix WindEffects Plugin bug, not configuring new links + * [Pull request #844](https://github.com/ignitionrobotics/ign-gazebo/pull/844) + +1. Fix potentially flaky integration component test case + * [Pull request #848](https://github.com/ignitionrobotics/ign-gazebo/pull/848) + +1. Cleanup and alphabetize plugin headers + * [Pull request #838](https://github.com/ignitionrobotics/ign-gazebo/pull/838) + +1. Removed duplicated code with rendering::sceneFromFirstRenderEngine + * [Pull request #819](https://github.com/ignitionrobotics/ign-gazebo/pull/819) + +1. Remove unused headers in video_recoder plugin + * [Pull request #834](https://github.com/ignitionrobotics/ign-gazebo/pull/834) + +1. Use moveToHelper from ign-rendering + * [Pull request #825](https://github.com/ignitionrobotics/ign-gazebo/pull/825) + +1. Remove tools/code_check and update codecov + * [Pull request #814](https://github.com/ignitionrobotics/ign-gazebo/pull/814) + +1. Add service and GUI to configure physics parameters + * [Pull request #536](https://github.com/ignitionrobotics/ign-gazebo/pull/536) + * [Pull request #812](https://github.com/ignitionrobotics/ign-gazebo/pull/812) + +1. Fix documentation for EntityComponentManager::EachNew + * [Pull request #795](https://github.com/ignitionrobotics/ign-gazebo/pull/795) + +1. Fix macOS build: components::Name in benchmark + * [Pull request #784](https://github.com/ignitionrobotics/ign-gazebo/pull/784) + +1. Don't store duplicate ComponentTypeId in ECM + * [Pull request #751](https://github.com/ignitionrobotics/ign-gazebo/pull/751) + +1. [TPE] Support setting individual link velocity + * [Pull request #427](https://github.com/ignitionrobotics/ign-gazebo/pull/427) + +1. 👩‍🌾 Enable Focal CI + * [Pull request #646](https://github.com/ignitionrobotics/ign-gazebo/pull/646) + +1. Update benchmark comparison instructions + * [Pull request #766](https://github.com/ignitionrobotics/ign-gazebo/pull/766) + +1. Use Protobuf_IMPORT_DIRS instead of PROTOBUF_IMPORT_DIRS for compatibility with Protobuf CMake config + * [Pull request #715](https://github.com/ignitionrobotics/ign-gazebo/pull/715) + +1. Do not pass -Wno-unused-parameter to MSVC compiler + * [Pull request #716](https://github.com/ignitionrobotics/ign-gazebo/pull/716) + +1. Scenebroadcaster sensors + * [Pull request #698](https://github.com/ignitionrobotics/ign-gazebo/pull/698) + +1. Make it so joint state publisher is quieter + * [Pull request #696](https://github.com/ignitionrobotics/ign-gazebo/pull/696) + ### Ignition Gazebo 3.8.0 (2021-03-17) 1. Add joint position controller GUI, also enable tests for GUI plugins diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a3521cf28be..339988e4c7e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -31,7 +31,11 @@ set(gui_sources PARENT_SCOPE ) -ign_add_component(ign SOURCES ign.cc GET_TARGET_NAME ign_lib_target) +ign_add_component(ign + SOURCES + ign.cc + cmd/ModelCommandAPI.cc + GET_TARGET_NAME ign_lib_target) target_link_libraries(${ign_lib_target} PRIVATE ${PROJECT_LIBRARY_TARGET_NAME} @@ -59,6 +63,7 @@ set (sources Util.cc View.cc World.cc + cmd/ModelCommandAPI.cc ${PROTO_PRIVATE_SRC} ${network_sources} ) @@ -74,6 +79,7 @@ set (gtest_sources ign_TEST.cc Link_TEST.cc Model_TEST.cc + ModelCommandAPI_TEST.cc SdfEntityCreator_TEST.cc SdfGenerator_TEST.cc Server_TEST.cc @@ -134,7 +140,14 @@ ign_build_tests(TYPE UNIT ignition-gazebo${PROJECT_VERSION_MAJOR} ) -if(TARGET UNIT_ign_TEST) +# Command line tests need extra settings +foreach(CMD_TEST + UNIT_ign_TEST + UNIT_ModelCommandAPI_TEST) + + if(NOT TARGET ${CMD_TEST}) + continue() + endif() # Running `ign gazebo` on macOS has problems when run with /usr/bin/ruby # due to System Integrity Protection (SIP). Try to find ruby from @@ -143,26 +156,27 @@ if(TARGET UNIT_ign_TEST) find_program(BREW_RUBY ruby HINTS /usr/local/opt/ruby/bin) endif() - add_dependencies(UNIT_ign_TEST + add_dependencies(${CMD_TEST} ${ign_lib_target} TestModelSystem TestSensorSystem TestWorldSystem ) - target_compile_definitions(UNIT_ign_TEST PRIVATE + target_compile_definitions(${CMD_TEST} PRIVATE "BREW_RUBY=\"${BREW_RUBY} \"") - target_compile_definitions(UNIT_ign_TEST PRIVATE + target_compile_definitions(${CMD_TEST} PRIVATE "IGN_PATH=\"${IGNITION-TOOLS_BINARY_DIRS}\"") set(_env_vars) list(APPEND _env_vars "IGN_CONFIG_PATH=${CMAKE_BINARY_DIR}/test/conf") list(APPEND _env_vars "IGN_GAZEBO_SYSTEM_PLUGIN_PATH=$") - set_tests_properties(UNIT_ign_TEST PROPERTIES + set_tests_properties(${CMD_TEST} PROPERTIES ENVIRONMENT "${_env_vars}") -endif() + +endforeach() if(NOT WIN32) add_subdirectory(cmd) diff --git a/src/EntityComponentManager.cc b/src/EntityComponentManager.cc index 825f6227bd8..5c469916b9d 100644 --- a/src/EntityComponentManager.cc +++ b/src/EntityComponentManager.cc @@ -532,6 +532,15 @@ ComponentKey EntityComponentManager::CreateComponentImplementation( const Entity _entity, const ComponentTypeId _componentTypeId, const components::BaseComponent *_data) { + // make sure the entity exists + if (!this->HasEntity(_entity)) + { + ignerr << "Trying to create a component of type [" << _componentTypeId + << "] attached to entity [" << _entity << "], but this entity does not " + << "exist. This create component request will be ignored." << std::endl; + return ComponentKey(); + } + // If type hasn't been instantiated yet, create a storage for it if (!this->HasComponentType(_componentTypeId)) { diff --git a/src/EntityComponentManager_TEST.cc b/src/EntityComponentManager_TEST.cc index e22a4ce5c25..2c91051386e 100644 --- a/src/EntityComponentManager_TEST.cc +++ b/src/EntityComponentManager_TEST.cc @@ -437,6 +437,16 @@ TEST_P(EntityComponentManagerFixture, EntitiesAndComponents) EXPECT_FALSE(manager.EntityHasComponentType(entity, DoubleComponent::typeId)); EXPECT_FALSE(manager.EntityHasComponentType(entity2, IntComponent::typeId)); + // Try to add a component to an entity that does not exist + EXPECT_FALSE(manager.HasEntity(kNullEntity)); + EXPECT_FALSE(manager.EntityHasComponentType(kNullEntity, + IntComponent::typeId)); + EXPECT_EQ(ComponentKey(), manager.CreateComponent(kNullEntity, + IntComponent(123))); + EXPECT_FALSE(manager.HasEntity(kNullEntity)); + EXPECT_FALSE(manager.EntityHasComponentType(kNullEntity, + IntComponent::typeId)); + // Query non-existing component, the default value is default-constructed BoolComponent *boolComp = manager.ComponentDefault(entity); ASSERT_NE(nullptr, boolComp); diff --git a/src/ModelCommandAPI_TEST.cc b/src/ModelCommandAPI_TEST.cc new file mode 100644 index 00000000000..efb62254114 --- /dev/null +++ b/src/ModelCommandAPI_TEST.cc @@ -0,0 +1,390 @@ +/* + * 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 +#include +#include + +#include + +#include "ignition/gazebo/Server.hh" +#include "ignition/gazebo/test_config.hh" // NOLINT(build/include) + +static const std::string kIgnModelCommand( + std::string(BREW_RUBY) + std::string(IGN_PATH) + "/ign model "); + + +///////////////////////////////////////////////// +/// \brief Used to avoid the cases where the zero is +/// represented as a negative number. +/// \param _text Output string that may have negative zero values. +void ReplaceNegativeZeroValues(std::string &_text) +{ + std::string neg_zero{"-0.000000"}; + std::string zero{"0.000000"}; + size_t pos = 0; + while ((pos = _text.find(neg_zero, pos)) != std::string::npos) + { + _text.replace(pos, neg_zero.length(), zero); + pos += zero.length(); + } +} + +///////////////////////////////////////////////// +std::string customExecStr(std::string _cmd) +{ + std::cout << "Running command [" << _cmd << "]" << std::endl; + + _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" + "Command failed when trying to get the world name " + "of the running simulation.\n"; + EXPECT_EQ(expectedOutput, output); +} + +///////////////////////////////////////////////// +// Tests `ign model` command. +TEST(ModelCommandAPI, Commands) +{ + ignition::gazebo::ServerConfig serverConfig; + // Using an static model to avoid any movements in the simulation. + serverConfig.SetSdfFile(std::string(PROJECT_SOURCE_PATH) + + "/test/worlds/static_diff_drive_vehicle.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 = + "\nRequesting state for world [diff_drive]..." + "\n\nAvailable models:\n" + " - ground_plane\n" + " - vehicle_blue\n"; + EXPECT_EQ(expectedOutput, output); + } + + // Tested command: ign model -m vehicle_blue + { + const std::string cmd = kIgnModelCommand + "-m vehicle_blue"; + std::string output = customExecStr(cmd); + ReplaceNegativeZeroValues(output); + const std::string expectedOutput = + "\nRequesting state for world [diff_drive]...\n\n" + "Model: [8]\n" + " - Name: vehicle_blue\n" + " - Pose [ XYZ (m) ] [ RPY (rad) ]:\n" + " [0.000000 | 2.000000 | 0.325000]\n" + " [0.000000 | 0.000000 | 0.000000]\n" + " - Link [9]\n" + " - Name: chassis\n" + " - Parent: vehicle_blue [8]\n" + " - Mass (kg): [1.143950]\n" + " - Inertial Pose [ XYZ (m) ] [ RPY (rad) ]:\n" + " [0.000000 | 0.000000 | 0.000000]\n" + " [0.000000 | 0.000000 | 0.000000]\n" + " - Inertial Matrix (kg.m^2):\n" + " [0.126164 | 0.000000 | 0.000000]\n" + " [0.000000 | 0.416519 | 0.000000]\n" + " [0.000000 | 0.000000 | 0.481014]\n" + " - Pose [ XYZ (m) ] [ RPY (rad) ]:\n" + " [-0.151427 | 0.000000 | 0.175000]\n" + " [0.000000 | 0.000000 | 0.000000]\n" + " - Link [12]\n" + " - Name: left_wheel\n" + " - Parent: vehicle_blue [8]\n" + " - Mass (kg): [2.000000]\n" + " - Inertial Pose [ XYZ (m) ] [ RPY (rad) ]:\n" + " [0.000000 | 0.000000 | 0.000000]\n" + " [0.000000 | 0.000000 | 0.000000]\n" + " - Inertial Matrix (kg.m^2):\n" + " [0.145833 | 0.000000 | 0.000000]\n" + " [0.000000 | 0.145833 | 0.000000]\n" + " [0.000000 | 0.000000 | 0.125000]\n" + " - Pose [ XYZ (m) ] [ RPY (rad) ]:\n" + " [0.554283 | 0.625029 | -0.025000]\n" + " [-1.570700 | 0.000000 | 0.000000]\n" + " - Link [15]\n" + " - Name: right_wheel\n" + " - Parent: vehicle_blue [8]\n" + " - Mass (kg): [2.000000]\n" + " - Inertial Pose [ XYZ (m) ] [ RPY (rad) ]:\n" + " [0.000000 | 0.000000 | 0.000000]\n" + " [0.000000 | 0.000000 | 0.000000]\n" + " - Inertial Matrix (kg.m^2):\n" + " [0.145833 | 0.000000 | 0.000000]\n" + " [0.000000 | 0.145833 | 0.000000]\n" + " [0.000000 | 0.000000 | 0.125000]\n" + " - Pose [ XYZ (m) ] [ RPY (rad) ]:\n" + " [0.554282 | -0.625029 | -0.025000]\n" + " [-1.570700 | 0.000000 | 0.000000]\n" + " - Link [18]\n" + " - Name: caster\n" + " - Parent: vehicle_blue [8]\n" + " - Mass (kg): [1.000000]\n" + " - Inertial Pose [ XYZ (m) ] [ RPY (rad) ]:\n" + " [0.000000 | 0.000000 | 0.000000]\n" + " [0.000000 | 0.000000 | 0.000000]\n" + " - Inertial Matrix (kg.m^2):\n" + " [0.100000 | 0.000000 | 0.000000]\n" + " [0.000000 | 0.100000 | 0.000000]\n" + " [0.000000 | 0.000000 | 0.100000]\n" + " - Pose [ XYZ (m) ] [ RPY (rad) ]:\n" + " [-0.957138 | 0.000000 | -0.125000]\n" + " [0.000000 | 0.000000 | 0.000000]\n" + " - Joint [21]\n" + " - Name: left_wheel_joint\n" + " - Parent: vehicle_blue [8]\n" + " - Type: revolute\n" + " - Parent Link: chassis [9]\n" + " - Child Link: left_wheel [12]\n" + " - Pose [ XYZ (m) ] [ RPY (rad) ]:\n" + " [0.000000 | 0.000000 | 0.000000]\n" + " [0.000000 | 0.000000 | 0.000000]\n" + " - Axis unit vector [ XYZ ]:\n" + " [0 | 0 | 1]\n" + " - Joint [22]\n" + " - Name: right_wheel_joint\n" + " - Parent: vehicle_blue [8]\n" + " - Type: revolute\n" + " - Parent Link: chassis [9]\n" + " - Child Link: right_wheel [15]\n" + " - Pose [ XYZ (m) ] [ RPY (rad) ]:\n" + " [0.000000 | 0.000000 | 0.000000]\n" + " [0.000000 | 0.000000 | 0.000000]\n" + " - Axis unit vector [ XYZ ]:\n" + " [0 | 0 | 1]\n" + " - Joint [23]\n" + " - Name: caster_wheel\n" + " - Parent: vehicle_blue [8]\n" + " - Type: ball\n" + " - Parent Link: chassis [9]\n" + " - Child Link: caster [18]\n" + " - Pose [ XYZ (m) ] [ RPY (rad) ]:\n" + " [0.000000 | 0.000000 | 0.000000]\n" + " [0.000000 | 0.000000 | 0.000000]\n"; + EXPECT_EQ(expectedOutput, output); + } + + // Tested command: ign model -m vehicle_blue --pose + { + const std::string cmd = kIgnModelCommand + "-m vehicle_blue --pose "; + std::string output = customExecStr(cmd); + ReplaceNegativeZeroValues(output); + const std::string expectedOutput = + "\nRequesting state for world [diff_drive]...\n\n" + "Model: [8]\n" + " - Name: vehicle_blue\n" + " - Pose [ XYZ (m) ] [ RPY (rad) ]:\n" + " [0.000000 | 2.000000 | 0.325000]\n" + " [0.000000 | 0.000000 | 0.000000]\n"; + EXPECT_EQ(expectedOutput, output); + } + + // Tested command: ign model -m vehicle_blue --link + { + const std::string cmd = kIgnModelCommand + + "-m vehicle_blue --link"; + std::string output = customExecStr(cmd); + ReplaceNegativeZeroValues(output); + const std::string expectedOutput = + "\nRequesting state for world [diff_drive]...\n\n" + " - Link [9]\n" + " - Name: chassis\n" + " - Parent: vehicle_blue [8]\n" + " - Mass (kg): [1.143950]\n" + " - Inertial Pose [ XYZ (m) ] [ RPY (rad) ]:\n" + " [0.000000 | 0.000000 | 0.000000]\n" + " [0.000000 | 0.000000 | 0.000000]\n" + " - Inertial Matrix (kg.m^2):\n" + " [0.126164 | 0.000000 | 0.000000]\n" + " [0.000000 | 0.416519 | 0.000000]\n" + " [0.000000 | 0.000000 | 0.481014]\n" + " - Pose [ XYZ (m) ] [ RPY (rad) ]:\n" + " [-0.151427 | 0.000000 | 0.175000]\n" + " [0.000000 | 0.000000 | 0.000000]\n" + " - Link [12]\n" + " - Name: left_wheel\n" + " - Parent: vehicle_blue [8]\n" + " - Mass (kg): [2.000000]\n" + " - Inertial Pose [ XYZ (m) ] [ RPY (rad) ]:\n" + " [0.000000 | 0.000000 | 0.000000]\n" + " [0.000000 | 0.000000 | 0.000000]\n" + " - Inertial Matrix (kg.m^2):\n" + " [0.145833 | 0.000000 | 0.000000]\n" + " [0.000000 | 0.145833 | 0.000000]\n" + " [0.000000 | 0.000000 | 0.125000]\n" + " - Pose [ XYZ (m) ] [ RPY (rad) ]:\n" + " [0.554283 | 0.625029 | -0.025000]\n" + " [-1.570700 | 0.000000 | 0.000000]\n" + " - Link [15]\n" + " - Name: right_wheel\n" + " - Parent: vehicle_blue [8]\n" + " - Mass (kg): [2.000000]\n" + " - Inertial Pose [ XYZ (m) ] [ RPY (rad) ]:\n" + " [0.000000 | 0.000000 | 0.000000]\n" + " [0.000000 | 0.000000 | 0.000000]\n" + " - Inertial Matrix (kg.m^2):\n" + " [0.145833 | 0.000000 | 0.000000]\n" + " [0.000000 | 0.145833 | 0.000000]\n" + " [0.000000 | 0.000000 | 0.125000]\n" + " - Pose [ XYZ (m) ] [ RPY (rad) ]:\n" + " [0.554282 | -0.625029 | -0.025000]\n" + " [-1.570700 | 0.000000 | 0.000000]\n" + " - Link [18]\n" + " - Name: caster\n" + " - Parent: vehicle_blue [8]\n" + " - Mass (kg): [1.000000]\n" + " - Inertial Pose [ XYZ (m) ] [ RPY (rad) ]:\n" + " [0.000000 | 0.000000 | 0.000000]\n" + " [0.000000 | 0.000000 | 0.000000]\n" + " - Inertial Matrix (kg.m^2):\n" + " [0.100000 | 0.000000 | 0.000000]\n" + " [0.000000 | 0.100000 | 0.000000]\n" + " [0.000000 | 0.000000 | 0.100000]\n" + " - Pose [ XYZ (m) ] [ RPY (rad) ]:\n" + " [-0.957138 | 0.000000 | -0.125000]\n" + " [0.000000 | 0.000000 | 0.000000]\n"; + EXPECT_EQ(expectedOutput, output); + } + + // Tested command: ign model -m vehicle_blue --link caster + { + const std::string cmd = kIgnModelCommand + + "-m vehicle_blue --link caster"; + std::string output = customExecStr(cmd); + ReplaceNegativeZeroValues(output); + const std::string expectedOutput = + "\nRequesting state for world [diff_drive]...\n\n" + " - Link [18]\n" + " - Name: caster\n" + " - Parent: vehicle_blue [8]\n" + " - Mass (kg): [1.000000]\n" + " - Inertial Pose [ XYZ (m) ] [ RPY (rad) ]:\n" + " [0.000000 | 0.000000 | 0.000000]\n" + " [0.000000 | 0.000000 | 0.000000]\n" + " - Inertial Matrix (kg.m^2):\n" + " [0.100000 | 0.000000 | 0.000000]\n" + " [0.000000 | 0.100000 | 0.000000]\n" + " [0.000000 | 0.000000 | 0.100000]\n" + " - Pose [ XYZ (m) ] [ RPY (rad) ]:\n" + " [-0.957138 | 0.000000 | -0.125000]\n" + " [0.000000 | 0.000000 | 0.000000]\n"; + EXPECT_EQ(expectedOutput, output); + } + + // Tested command: ign model -m vehicle_blue --joint + { + const std::string cmd = kIgnModelCommand + + "-m vehicle_blue --joint"; + std::string output = customExecStr(cmd); + ReplaceNegativeZeroValues(output); + const std::string expectedOutput = + "\nRequesting state for world [diff_drive]...\n\n" + " - Joint [21]\n" + " - Name: left_wheel_joint\n" + " - Parent: vehicle_blue [8]\n" + " - Type: revolute\n" + " - Parent Link: chassis [9]\n" + " - Child Link: left_wheel [12]\n" + " - Pose [ XYZ (m) ] [ RPY (rad) ]:\n" + " [0.000000 | 0.000000 | 0.000000]\n" + " [0.000000 | 0.000000 | 0.000000]\n" + " - Axis unit vector [ XYZ ]:\n" + " [0 | 0 | 1]\n" + " - Joint [22]\n" + " - Name: right_wheel_joint\n" + " - Parent: vehicle_blue [8]\n" + " - Type: revolute\n" + " - Parent Link: chassis [9]\n" + " - Child Link: right_wheel [15]\n" + " - Pose [ XYZ (m) ] [ RPY (rad) ]:\n" + " [0.000000 | 0.000000 | 0.000000]\n" + " [0.000000 | 0.000000 | 0.000000]\n" + " - Axis unit vector [ XYZ ]:\n" + " [0 | 0 | 1]\n" + " - Joint [23]\n" + " - Name: caster_wheel\n" + " - Parent: vehicle_blue [8]\n" + " - Type: ball\n" + " - Parent Link: chassis [9]\n" + " - Child Link: caster [18]\n" + " - Pose [ XYZ (m) ] [ RPY (rad) ]:\n" + " [0.000000 | 0.000000 | 0.000000]\n" + " [0.000000 | 0.000000 | 0.000000]\n"; + EXPECT_EQ(expectedOutput, output); + } + + // Tested command: ign model -m vehicle_blue --joint caster_wheel + { + const std::string cmd = kIgnModelCommand + + "-m vehicle_blue --joint caster_wheel"; + std::string output = customExecStr(cmd); + ReplaceNegativeZeroValues(output); + const std::string expectedOutput = + "\nRequesting state for world [diff_drive]...\n\n" + " - Joint [23]\n" + " - Name: caster_wheel\n" + " - Parent: vehicle_blue [8]\n" + " - Type: ball\n" + " - Parent Link: chassis [9]\n" + " - Child Link: caster [18]\n" + " - Pose [ XYZ (m) ] [ RPY (rad) ]:\n" + " [0.000000 | 0.000000 | 0.000000]\n" + " [0.000000 | 0.000000 | 0.000000]\n"; + EXPECT_EQ(expectedOutput, output); + } +} + +////////////////////////////////////////////////// +int main(int argc, char **argv) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/src/cmd/CMakeLists.txt b/src/cmd/CMakeLists.txt index c82f1d776a5..a1ff8091c46 100644 --- a/src/cmd/CMakeLists.txt +++ b/src/cmd/CMakeLists.txt @@ -36,6 +36,34 @@ install( FILES DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/ignition/) +#=============================================================================== +# Used for the installed model command 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") + +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(model_configured "${CMAKE_CURRENT_BINARY_DIR}/model${PROJECT_VERSION_MAJOR}.yaml") +configure_file( + "model.yaml.in" + ${model_configured}) + +install(FILES ${model_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. @@ -66,3 +94,26 @@ set(ign_library_path configure_file( "${IGN_DESIGNATION}.yaml.in" "${CMAKE_BINARY_DIR}/test/conf/${IGN_DESIGNATION}${PROJECT_VERSION_MAJOR}.yaml" @ONLY) + +#=============================================================================== +# 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}/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") + +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( + "model.yaml.in" + "${CMAKE_BINARY_DIR}/test/conf/model${PROJECT_VERSION_MAJOR}.yaml" @ONLY) diff --git a/src/cmd/ModelCommandAPI.cc b/src/cmd/ModelCommandAPI.cc new file mode 100644 index 00000000000..7cebd405932 --- /dev/null +++ b/src/cmd/ModelCommandAPI.cc @@ -0,0 +1,409 @@ +/* + * 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 "ModelCommandAPI.hh" + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace ignition; +using namespace gazebo; + +////////////////////////////////////////////////// +/// \brief Get the name of the world being used by calling +/// `/gazebo/worlds` service. +/// \return The name of the world if service is available, +/// an empty string otherwise. +std::string getWorldName() +{ + // Create a transport node. + transport::Node node; + + bool result{false}; + const unsigned int timeout{5000}; + const std::string service{"/gazebo/worlds"}; + + // Request and block + msgs::StringMsg_V res; + + if (!node.Request(service, timeout, res, result)) + { + 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 ""; + } + + return res.data().Get(0); +} + +////////////////////////////////////////////////// +/// \brief Get entity info: name and entity ID +/// \param[in] _entity Entity to get info +/// \param[in] _ecm Entity component manager +/// \return " []" +std::string entityInfo(Entity _entity, const EntityComponentManager &_ecm) +{ + std::string info; + + const auto nameComp = _ecm.Component( _entity); + if (nameComp) + { + info += nameComp->Data() + " "; + } + info += "[" + std::to_string(_entity) + "]"; + return info; +} + +////////////////////////////////////////////////// +/// \brief Get entity info: name and entity ID +/// \param[in] _entity Name of entity to get info +/// \param[in] _ecm Entity component manager +/// \return " []" +std::string entityInfo(const std::string &_name, + const EntityComponentManager &_ecm) +{ + std::string info{_name}; + + auto entity = _ecm.EntityByComponents(components::Name(_name)); + if (kNullEntity != entity) + { + info += " [" + std::to_string(entity) + "]"; + } + return info; +} + +////////////////////////////////////////////////// +/// \brief Get pose info in a standard way +/// \param[in] _pose Pose to print +/// \param[in] _prefix Indentation prefix for every line +/// \return Pose formatted in a standard way +std::string poseInfo(math::Pose3d _pose, const std::string &_prefix) +{ + return + _prefix + "[" + std::to_string(_pose.X()) + " | " + + std::to_string(_pose.Y()) + " | " + + std::to_string(_pose.Z()) + "]\n" + + _prefix + "[" + std::to_string(_pose.Roll()) + " | " + + std::to_string(_pose.Pitch()) + " | " + + std::to_string(_pose.Yaw()) + "]"; +} + +////////////////////////////////////////////////// +// \brief Set the state of a ECM instance with a world snapshot. +// \param _ecm ECM instance to be populated. +// \return boolean indicating if it was able to populate the ECM. +bool populateECM(EntityComponentManager &_ecm) +{ + const std::string world = getWorldName(); + if (world.empty()) + { + std::cerr << "Command failed when trying to get the world name of " + << "the running simulation." << std::endl; + return false; + } + // Create a transport node. + transport::Node node; + bool result{false}; + const unsigned int timeout{5000}; + const std::string service{"/world/" + world + "/state"}; + + std::cout << std::endl << "Requesting state for world [" << world + << "]..." << std::endl << std::endl; + + // Request and block + msgs::SerializedStepMap res; + + if (!node.Request(service, timeout, res, result)) + { + std::cerr << std::endl << "Service call to [" << service << "] timed out" + << std::endl; + return false; + } + + if (!result) + { + std::cerr << std::endl << "Service call to [" << service << "] failed" + << std::endl; + return false; + } + + // Instantiate an ECM and populate with data from message + _ecm.SetState(res.state()); + return true; +} + + +////////////////////////////////////////////////// +// \brief Print the model information. +// \param[in] _entity Entity of the model requested. +// \param[in] _ecm ECM ready for requests. +void printModelInfo(const uint64_t _entity, + const EntityComponentManager &_ecm) +{ + const auto poseComp = + _ecm.Component(_entity); + const auto nameComp = + _ecm.Component(_entity); + if (poseComp && nameComp) + { + std::cout << "Model: [" << _entity << "]" << std::endl + << " - Name: " << nameComp->Data() << std::endl + << " - Pose [ XYZ (m) ] [ RPY (rad) ]:" << std::endl + << poseInfo(poseComp->Data(), " ") << std::endl; + } +} + +////////////////////////////////////////////////// +// \brief Print the model links information. +// \param[in] _modelEntity Entity of the model requested. +// \param[in] _ecm ECM ready for requests. +// \param[in] _linkName Link to be printed, if empty, print all links. +void printLinks(const uint64_t _modelEntity, + const EntityComponentManager &_ecm, + const std::string &_linkName) +{ + const auto links = _ecm.EntitiesByComponents( + components::ParentEntity(_modelEntity), components::Link()); + for (const auto &entity : links) + { + const auto nameComp = _ecm.Component(entity); + + if (_linkName.length() && _linkName != nameComp->Data()) + continue; + + std::cout << " - Link [" << entity << "]" << std::endl + << " - Name: " << nameComp->Data() << std::endl + << " - Parent: " << entityInfo(_modelEntity, _ecm) + << std::endl; + + const auto inertialComp = _ecm.Component(entity); + + if (inertialComp) + { + const auto inertialMatrix = inertialComp->Data().MassMatrix(); + const auto mass = inertialComp->Data().MassMatrix().Mass(); + + const std::string massInfo = "[" + std::to_string(mass) + "]"; + const std::string 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 (kg): " << massInfo << std::endl + << " - Inertial Pose [ XYZ (m) ] [ RPY (rad) ]:" + << std::endl + << poseInfo(inertialComp->Data().Pose(), " ") + << std::endl + << " - Inertial Matrix (kg.m^2):" + << inertialInfo << std::endl; + } + + const auto poseComp = _ecm.Component(entity); + if (poseComp) + { + std::cout << " - Pose [ XYZ (m) ] [ RPY (rad) ]:" << std::endl + << poseInfo(poseComp->Data(), " ") << std::endl; + } + } +} + +////////////////////////////////////////////////// +// \brief Print the model joints information. +// \param[in] _modelEntity Entity of the model requested. +// \param[in] _ecm ECM ready for requests. +// \param[in] _jointName Joint to be printed, if nullptr, print all joints. +void printJoints(const uint64_t _modelEntity, + const EntityComponentManager &_ecm, + const std::string &_jointName) +{ + static const std::map jointTypes = + { + {sdf::JointType::REVOLUTE, "revolute"}, + {sdf::JointType::BALL, "ball"}, + {sdf::JointType::CONTINUOUS, "continuous"}, + {sdf::JointType::FIXED, "fixed"}, + {sdf::JointType::GEARBOX, "gearbox"}, + {sdf::JointType::PRISMATIC, "prismatic"}, + {sdf::JointType::REVOLUTE2, "revolute2"}, + {sdf::JointType::SCREW, "screw"}, + {sdf::JointType::UNIVERSAL, "universal"} + }; + + const auto joints = _ecm.EntitiesByComponents( + components::ParentEntity(_modelEntity), components::Joint()); + + for (const auto &entity : joints) + { + const auto nameComp = _ecm.Component(entity); + + if (_jointName.length() && _jointName != nameComp->Data()) + continue; + + std::cout << " - Joint [" << entity << "]" << std::endl + << " - Name: " << nameComp->Data() << std::endl + << " - Parent: " << entityInfo(_modelEntity, _ecm) + << std::endl; + + const auto jointTypeComp = _ecm.Component(entity); + if (jointTypeComp) + { + std::cout << " - Type: " << jointTypes.at(jointTypeComp->Data()) + << std::endl; + } + + const auto childLinkComp = + _ecm.Component(entity); + const auto parentLinkComp = + _ecm.Component(entity); + + if (childLinkComp && parentLinkComp) + { + std::cout << " - Parent Link: " + << entityInfo(parentLinkComp->Data(), _ecm) << "\n" + << " - Child Link: " + << entityInfo(childLinkComp->Data(), _ecm) << "\n"; + } + + const auto poseComp = _ecm.Component(entity); + if (poseComp) + { + std::cout << " - Pose [ XYZ (m) ] [ RPY (rad) ]:" << std::endl + << poseInfo(poseComp->Data(), " ") << std::endl; + } + + const auto axisComp = _ecm.Component(entity); + if (axisComp) + { + std::cout << " - Axis unit vector [ XYZ ]:\n" + " [" << axisComp->Data().Xyz().X() << " | " + << axisComp->Data().Xyz().Y() << " | " + << axisComp->Data().Xyz().Z() << "]\n"; + } + } +} + +////////////////////////////////////////////////// +extern "C" IGNITION_GAZEBO_VISIBLE void cmdModelList() +{ + EntityComponentManager ecm{}; + if (!populateECM(ecm)) + { + return; + } + + auto world = ecm.EntityByComponents(components::World()); + if (kNullEntity == world) + { + std::cout << "No world found." << std::endl; + return; + } + + const auto models = ecm.EntitiesByComponents( + components::ParentEntity(world), components::Model()); + + if (models.size() == 0) + { + std::cout << "No models in world [" << world << "]" << std::endl; + return; + } + + std::cout << "Available models:" << std::endl; + + for (const auto &m : models) + { + const auto nameComp = ecm.Component(m); + std::cout << " - " << nameComp->Data() << std::endl; + } +} + +////////////////////////////////////////////////// +extern "C" IGNITION_GAZEBO_VISIBLE void cmdModelInfo( + const char *_modelName, int _pose, const char *_linkName, + const char *_jointName) +{ + std::string linkName{""}; + if (_linkName) + linkName = _linkName; + std::string jointName{""}; + if (_jointName) + jointName = _jointName; + bool printAll{false}; + if (!_pose && !_linkName && !_jointName) + printAll = true; + + if (!_modelName) + { + std::cerr << std::endl << "Model name not found" << std::endl; + return; + } + + EntityComponentManager ecm{}; + if (!populateECM(ecm)) + return; + + // Get the desired model entity. + auto entity = ecm.EntityByComponents(components::Name(_modelName), + components::Model()); + + if (entity == kNullEntity) + std::cout << "No model named <" << _modelName << "> was found" << std::endl; + + // Get the pose of the model + if (printAll | _pose) + printModelInfo(entity, ecm); + + // Get the links information + if (printAll | (_linkName != nullptr)) + printLinks(entity, ecm, linkName); + + // Get the joints information + if (printAll | (_jointName != nullptr)) + printJoints(entity, ecm, jointName); +} diff --git a/src/cmd/ModelCommandAPI.hh b/src/cmd/ModelCommandAPI.hh new file mode 100644 index 00000000000..fc776b1c181 --- /dev/null +++ b/src/cmd/ModelCommandAPI.hh @@ -0,0 +1,31 @@ +/* + * 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 "ignition/gazebo/Export.hh" + +/// \brief External hook to get a list of available models. +extern "C" IGNITION_GAZEBO_VISIBLE void cmdModelList(); + +/// \brief External hook to dump model information. +/// \param[in] _modelName Model name. +/// \param[in] _pose --pose option. +/// \param[in] _link_name Link name. +/// \param[in] _joint_name Joint name. +extern "C" IGNITION_GAZEBO_VISIBLE void cmdModelInfo( + const char *_modelName, int _pose, const char *_linkName, + const char *_jointName); + diff --git a/src/cmd/cmdmodel.rb.in b/src/cmd/cmdmodel.rb.in new file mode 100644 index 00000000000..8070bfec638 --- /dev/null +++ b/src/cmd/cmdmodel.rb.in @@ -0,0 +1,179 @@ +#!/usr/bin/ruby + +# 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. + +# 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 'optparse' + +# Constants. +LIBRARY_NAME = '@library_location@' +LIBRARY_VERSION = '@PROJECT_VERSION_FULL@' + +COMMON_OPTIONS = + " -h [--help] Print this help message.\n"\ + " \n" + + " --force-version Use a specific library version.\n"\ + " \n" + + ' --versions Show the available versions.' + +COMMANDS = { 'model' => + "Print information about models.\n\n"+ + " \n"\ + " ign model [options] \n"\ + " \n"\ + "Available Options: \n"\ + " --list Get a list of the available models. \n"\ + " -m [--model] arg Select the model to be shown. \n"\ + " -p [--pose] Print the pose of the model. \n"\ + " -l [--link] arg Select a link to show its properties. \n"\ + " If no arg is passed all links are printed \n"\ + " Requires the -m option \n"\ + " \n"\ + " E.g. to get information about the \n"\ + " caster link in the diff_drive world, run: \n"\ + " ign model -m vehicle_blue -l caster \n"\ + " \n"\ + " -j [--joint] arg Select a joint to show its properties. \n"\ + " If no arg is passed all joints are printed \n"\ + " Requires the -m option \n"\ + " \n"\ + " E.g. to get information about the \n"\ + " caster_wheel joint in the diff_drive \n"\ + " world, run: \n"\ + " ign model -m vehicle_blue -j caster_wheel \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_name' => 0, + 'joint_name' => 0 + } + 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_name'] = 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_name'] = '' + if l + options['link_name'] = l + end + end + opts.on('-j', '--joint [arg]', String, 'Request joint information') do |j| + # options['joint'] = 1 + options['joint_name'] = '' + if j + options['joint_name'] = j + end + end + end # opt_parser do + begin + opt_parser.parse!(args) + rescue + puts usage + exit(-1) + end + + # Check that there is at least one command and there is a plugin that knows + # how to handle it. + if ARGV.empty? || !COMMANDS.key?(ARGV[0]) || + options.empty? + puts usage + exit(-1) + end + + # Check that an option was selected. + if !(options.key?('list') || options.key?('model_name')) + 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_name') + Importer.extern 'void cmdModelInfo(const char *, int, const char *, + const char *)' + Importer.cmdModelInfo(options['model_name'], options['pose'], + options['link_name'], 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/src/cmd/model.yaml.in b/src/cmd/model.yaml.in new file mode 100644 index 00000000000..b9d2ec91841 --- /dev/null +++ b/src/cmd/model.yaml.in @@ -0,0 +1,8 @@ +--- # Model subcommand available inside ignition gazebo. +format: 1.0.0 +library_name: ignition-gazebo-ign +library_version: @PROJECT_VERSION_FULL@ +library_path: @ign_model_ruby_path@ +commands: + - model : Print information about models. +--- diff --git a/src/gui/GuiRunner.cc b/src/gui/GuiRunner.cc index dd3fd6664a4..4e3ef1b4c78 100644 --- a/src/gui/GuiRunner.cc +++ b/src/gui/GuiRunner.cc @@ -130,7 +130,7 @@ void GuiRunner::RequestState() ///////////////////////////////////////////////// void GuiRunner::OnPluginAdded(const QString &_objectName) { - auto plugin = gui::App()->findChild(_objectName); + auto plugin = gui::App()->PluginByName(_objectName.toStdString()); if (!plugin) { ignerr << "Failed to get plugin [" << _objectName.toStdString() @@ -139,6 +139,14 @@ void GuiRunner::OnPluginAdded(const QString &_objectName) } this->RequestState(); + + auto guiSystem = dynamic_cast(plugin.get()); + + // Do nothing for pure ign-gui plugins + if (!guiSystem) + return; + + guiSystem->Update(this->updateInfo, this->ecm); } ///////////////////////////////////////////////// diff --git a/src/systems/diff_drive/DiffDrive.cc b/src/systems/diff_drive/DiffDrive.cc index 27226d01af7..3e003b5d4b2 100644 --- a/src/systems/diff_drive/DiffDrive.cc +++ b/src/systems/diff_drive/DiffDrive.cc @@ -240,7 +240,6 @@ void DiffDrive::Configure(const Entity &_entity, this->dataPtr->limiterAng->SetMaxJerk(maxJerk); } - double odomFreq = _sdf->Get("odom_publish_frequency", 50).first; if (odomFreq > 0) { @@ -362,6 +361,10 @@ void DiffDrive::PreUpdate(const ignition::gazebo::UpdateInfo &_info, for (Entity joint : this->dataPtr->leftJoints) { + // skip this entity if it has been removed + if (!_ecm.HasEntity(joint)) + continue; + // Update wheel velocity auto vel = _ecm.Component(joint); @@ -378,6 +381,10 @@ void DiffDrive::PreUpdate(const ignition::gazebo::UpdateInfo &_info, for (Entity joint : this->dataPtr->rightJoints) { + // skip this entity if it has been removed + if (!_ecm.HasEntity(joint)) + continue; + // Update wheel velocity auto vel = _ecm.Component(joint); @@ -396,7 +403,7 @@ void DiffDrive::PreUpdate(const ignition::gazebo::UpdateInfo &_info, // don't exist. auto leftPos = _ecm.Component( this->dataPtr->leftJoints[0]); - if (!leftPos) + if (!leftPos && _ecm.HasEntity(this->dataPtr->leftJoints[0])) { _ecm.CreateComponent(this->dataPtr->leftJoints[0], components::JointPosition()); @@ -404,7 +411,7 @@ void DiffDrive::PreUpdate(const ignition::gazebo::UpdateInfo &_info, auto rightPos = _ecm.Component( this->dataPtr->rightJoints[0]); - if (!rightPos) + if (!rightPos && _ecm.HasEntity(this->dataPtr->rightJoints[0])) { _ecm.CreateComponent(this->dataPtr->rightJoints[0], components::JointPosition()); diff --git a/test/integration/diff_drive_system.cc b/test/integration/diff_drive_system.cc index 2a91cba74a1..027648c0e8d 100644 --- a/test/integration/diff_drive_system.cc +++ b/test/integration/diff_drive_system.cc @@ -315,7 +315,7 @@ TEST_P(DiffDriveTest, SkidPublishCmd) int maxSleep = 30; for (; odomPoses.size() < 3 && sleep < maxSleep; ++sleep) { - std::this_thread::sleep_for(std::chrono::milliseconds(100)); + std::this_thread::sleep_for(std::chrono::milliseconds(200)); } EXPECT_NE(maxSleep, sleep); diff --git a/test/worlds/static_diff_drive_vehicle.sdf b/test/worlds/static_diff_drive_vehicle.sdf new file mode 100644 index 00000000000..179a11ef20b --- /dev/null +++ b/test/worlds/static_diff_drive_vehicle.sdf @@ -0,0 +1,226 @@ + + + + + + + 0.001 + 1.0 + + + + + + + true + + + + + 0 0 1 + 100 100 + + + + + + + 0 0 1 + 100 100 + + + + 0.8 0.8 0.8 1 + 0.8 0.8 0.8 1 + 0.8 0.8 0.8 1 + + + + + + + 0 2 0.325 0 -0 0 + + + -0.151427 -0 0.175 0 -0 0 + + 1.14395 + + 0.126164 + 0 + 0 + 0.416519 + 0 + 0.481014 + + + + + + 2.01142 1 0.568726 + + + + 0.5 0.5 1.0 1 + 0.5 0.5 1.0 1 + 0.0 0.0 1.0 1 + + + + + + 2.01142 1 0.568726 + + + + + + + 0.554283 0.625029 -0.025 -1.5707 0 0 + + 2 + + 0.145833 + 0 + 0 + 0.145833 + 0 + 0.125 + + + + + + 0.3 + + + + 0.2 0.2 0.2 1 + 0.2 0.2 0.2 1 + 0.2 0.2 0.2 1 + + + + + + 0.3 + + + + + + + 0.554282 -0.625029 -0.025 -1.5707 0 0 + + 2 + + 0.145833 + 0 + 0 + 0.145833 + 0 + 0.125 + + + + + + 0.3 + + + + 0.2 0.2 0.2 1 + 0.2 0.2 0.2 1 + 0.2 0.2 0.2 1 + + + + + + 0.3 + + + + + + + -0.957138 -0 -0.125 0 -0 0 + + 1 + + 0.1 + 0 + 0 + 0.1 + 0 + 0.1 + + + + + + 0.2 + + + + 0.2 0.2 0.2 1 + 0.2 0.2 0.2 1 + 0.2 0.2 0.2 1 + + + + + + 0.2 + + + + + + + chassis + left_wheel + + 0 0 1 + + -1.79769e+308 + 1.79769e+308 + + + + + + chassis + right_wheel + + 0 0 1 + + -1.79769e+308 + 1.79769e+308 + + + + + + chassis + caster + + + + left_wheel_joint + right_wheel_joint + 1.25 + 0.3 + 1 + + true + + + + diff --git a/tutorials.md.in b/tutorials.md.in index 0250547e843..9355f837447 100644 --- a/tutorials.md.in +++ b/tutorials.md.in @@ -27,7 +27,9 @@ Ignition @IGN_DESIGNATION_CAP@ library and how to use the library effectively. * \subpage logicalaudiosensor "Logical Audio Sensor": Using the LogicalAudioSensor system to mimic logical audio emission and detection in simulation. * \subpage videorecorder "Video Recorder": Record videos from the 3D render window. * \subpage collada_world_exporter "Collada World Exporter": Export an entire world to a single Collada mesh. -* \subpage particle_mitter "Particle emitter": Using particle emitters in simulation +* \subpage particle_emitter "Particle emitter": Using particle emitters in simulation +* \subpage model_and_optimize_meshes "Model and optimize meshes": Some recomendations when creating meshes in blender for simulations. +* \subpage model_command "Model Command": Use the CLI to get information about the models in a simulation. **Migration from Gazebo classic** diff --git a/tutorials/files/model_and_optimize_meshes/10_loop_cut.jpg b/tutorials/files/model_and_optimize_meshes/10_loop_cut.jpg new file mode 100644 index 00000000000..242f79f1180 Binary files /dev/null and b/tutorials/files/model_and_optimize_meshes/10_loop_cut.jpg differ diff --git a/tutorials/files/model_and_optimize_meshes/11_duplicate.jpg b/tutorials/files/model_and_optimize_meshes/11_duplicate.jpg new file mode 100644 index 00000000000..3482b65757b Binary files /dev/null and b/tutorials/files/model_and_optimize_meshes/11_duplicate.jpg differ diff --git a/tutorials/files/model_and_optimize_meshes/12_other_modifiers.png b/tutorials/files/model_and_optimize_meshes/12_other_modifiers.png new file mode 100644 index 00000000000..9d0b06ceab8 Binary files /dev/null and b/tutorials/files/model_and_optimize_meshes/12_other_modifiers.png differ diff --git a/tutorials/files/model_and_optimize_meshes/13_polygons.jpg b/tutorials/files/model_and_optimize_meshes/13_polygons.jpg new file mode 100644 index 00000000000..b006af0ea79 Binary files /dev/null and b/tutorials/files/model_and_optimize_meshes/13_polygons.jpg differ diff --git a/tutorials/files/model_and_optimize_meshes/14_export_model.png b/tutorials/files/model_and_optimize_meshes/14_export_model.png new file mode 100644 index 00000000000..e93b4f947b2 Binary files /dev/null and b/tutorials/files/model_and_optimize_meshes/14_export_model.png differ diff --git a/tutorials/files/model_and_optimize_meshes/15_axis.png b/tutorials/files/model_and_optimize_meshes/15_axis.png new file mode 100644 index 00000000000..309f538ea95 Binary files /dev/null and b/tutorials/files/model_and_optimize_meshes/15_axis.png differ diff --git a/tutorials/files/model_and_optimize_meshes/16_check_scale.png b/tutorials/files/model_and_optimize_meshes/16_check_scale.png new file mode 100644 index 00000000000..ef14b636cd0 Binary files /dev/null and b/tutorials/files/model_and_optimize_meshes/16_check_scale.png differ diff --git a/tutorials/files/model_and_optimize_meshes/17_check_pivot_point_a.jpg b/tutorials/files/model_and_optimize_meshes/17_check_pivot_point_a.jpg new file mode 100644 index 00000000000..50fa52d0a1b Binary files /dev/null and b/tutorials/files/model_and_optimize_meshes/17_check_pivot_point_a.jpg differ diff --git a/tutorials/files/model_and_optimize_meshes/18_check_pivot_point_b.jpg b/tutorials/files/model_and_optimize_meshes/18_check_pivot_point_b.jpg new file mode 100644 index 00000000000..e463095bacb Binary files /dev/null and b/tutorials/files/model_and_optimize_meshes/18_check_pivot_point_b.jpg differ diff --git a/tutorials/files/model_and_optimize_meshes/1_wheels.jpg b/tutorials/files/model_and_optimize_meshes/1_wheels.jpg new file mode 100644 index 00000000000..5a17d207752 Binary files /dev/null and b/tutorials/files/model_and_optimize_meshes/1_wheels.jpg differ diff --git a/tutorials/files/model_and_optimize_meshes/2_cylinder.jpg b/tutorials/files/model_and_optimize_meshes/2_cylinder.jpg new file mode 100644 index 00000000000..5d577c1d979 Binary files /dev/null and b/tutorials/files/model_and_optimize_meshes/2_cylinder.jpg differ diff --git a/tutorials/files/model_and_optimize_meshes/3_transform_gizmos.png b/tutorials/files/model_and_optimize_meshes/3_transform_gizmos.png new file mode 100644 index 00000000000..a746211f12e Binary files /dev/null and b/tutorials/files/model_and_optimize_meshes/3_transform_gizmos.png differ diff --git a/tutorials/files/model_and_optimize_meshes/4_move.png b/tutorials/files/model_and_optimize_meshes/4_move.png new file mode 100644 index 00000000000..3ac88eea61c Binary files /dev/null and b/tutorials/files/model_and_optimize_meshes/4_move.png differ diff --git a/tutorials/files/model_and_optimize_meshes/5_rotate.jpg b/tutorials/files/model_and_optimize_meshes/5_rotate.jpg new file mode 100644 index 00000000000..dcf6b633aef Binary files /dev/null and b/tutorials/files/model_and_optimize_meshes/5_rotate.jpg differ diff --git a/tutorials/files/model_and_optimize_meshes/7_scale.jpg b/tutorials/files/model_and_optimize_meshes/7_scale.jpg new file mode 100644 index 00000000000..486dd969904 Binary files /dev/null and b/tutorials/files/model_and_optimize_meshes/7_scale.jpg differ diff --git a/tutorials/files/model_and_optimize_meshes/8_bevel.jpg b/tutorials/files/model_and_optimize_meshes/8_bevel.jpg new file mode 100644 index 00000000000..3f2e4c9ff76 Binary files /dev/null and b/tutorials/files/model_and_optimize_meshes/8_bevel.jpg differ diff --git a/tutorials/files/model_and_optimize_meshes/9_extrude.png b/tutorials/files/model_and_optimize_meshes/9_extrude.png new file mode 100644 index 00000000000..b028fa47e8a Binary files /dev/null and b/tutorials/files/model_and_optimize_meshes/9_extrude.png differ diff --git a/tutorials/model_and_optimize_meshes.md b/tutorials/model_and_optimize_meshes.md new file mode 100644 index 00000000000..cac18723584 --- /dev/null +++ b/tutorials/model_and_optimize_meshes.md @@ -0,0 +1,129 @@ +\page model_and_optimize_meshes Model and optimize meshes in blender for simulations + +3d shapes created for simulation serve the purpose of bringing realism to a scene. It also helps identify everything in the scene easier. If for example all of the objects in a scene were simple shapes like cubes and circles it can be difficult to distinguish objects in the simulation. + +## Collision shapes vs Visual shapes +Simple shapes like cubes and spheres, better known as “primitives”, are often used as collision pieces. Collision shapes use simplified geometry compared to the visual mesh because they serve different purposes in simulations. + +Collision models are important because they interact with the physics environment. They require less computation if they use primitive shapes. + +Visual shapes are detailed meshes. These shapes are made with the goal of bringing realism to a simulated scene. These shapes are more dense in polygons and usually have textures attached to them. These shapes are what camera sensors pick up and are also what the viewer sees when viewing simulations. + +## Creating Models +Blender has many tools to create simple/complex models. A great way to create complex objects is to start with something simple. In the example below we see a complex model created from a simple primitive. Although this is a great strategy for modeling, going from a simple mesh to a complex one usually requires a lot of tools to be used. + +@image html files/model_and_optimize_meshes/1_wheels.jpg width=600px + +Because we used the example of the wheel above, I will be using that shape to go over my modeling workflow. I will also explain some of the tools and modifiers that I use. A basic understanding of the movement keys should be known prior to starting this tutorial: [Recommended beginner guide](https://www.youtube.com/watch?v=K6Sm7DAPTGE&ab_channel=TutsByKaiTutsByKai) + +### Create/add simple mesh +For this model cylinder mesh is used as a starting point. +--> `(Add>Mesh>Cylinder)` + +@image html files/model_and_optimize_meshes/2_cylinder.jpg width=600px + +Having reference images of what you are planning to model can be a great help too, and is alway recommended. + +The move, rotate, and scale are used throughout most 3d modeling processes. These three actions are commonly known as the transform gizmos. + +@image html files/model_and_optimize_meshes/3_transform_gizmos.png "(Transform gizmos hotkeys: (G, R, S))" width=600px + + + +Oftentimes, problems after importing models into a program occur because of simple mistakes related to these gizmos. +These problems are fairly common, but aren't hard to fix. Changing one of those attributes accordingly and exporting it out with the updates fixes most issues. + + - **Move Gizmo**: allows you to change the location of an object along the x,y,z axis. `(Hotkey G)` + +@image html files/model_and_optimize_meshes/4_move.png width=600px + + - Rotate Gizmo: allows you to rotate object orientation along the x,y,z axis `(Hotkey R)` +This was used to rotate the cylinder upright for the wheel. + +@image html files/model_and_optimize_meshes/5_rotate.jpg width=600px + + - Scale Gizmo: allows you to scale an obj. Objects can also be scaled along a single axis if desired. `(Hotkey S)` + +@image html files/model_and_optimize_meshes/7_scale.jpg width=600px + + - Bevel: allow you to create curved/chamfered edges +This was used to create the curved sides of the wheel. + +@image html files/model_and_optimize_meshes/8_bevel.jpg width=600px + +- Extrude: adds additional geometry along a selected face/edges. The extrude tool can be used to push out a face or to push in one. Both examples are given here. + +@image html files/model_and_optimize_meshes/9_extrude.png width=600px + +This was used to create the indents of the wheel. + +`(Mesh > Extrude > Extrude Region)` + + - Loop cut: allows you to split a face with a new looped edge. This then allows for more detailed manipulation of faces to take place. + +This with a combination of the extrude tool you can make small ridges to your shape. + +@image html files/model_and_optimize_meshes/10_loop_cut.jpg width=600px + + - Duplicate: allows you to make identical copies of a selected object. + +@image html files/model_and_optimize_meshes/11_duplicate.jpg width=600px + + - Other Modifiers: +Some other commands that were used during a modeling process are: lattice functions,booleans, smooth, decimate. + +@image html files/model_and_optimize_meshes/12_other_modifiers.png width=600px + +#### More polygons less performance + +Shapes are made out of polygons. The higher the polygons on a shape the more detailed it is. A shape with too many polygons can overcomplicate mesh editing or can make simulations run very slowly. This is why it is important to optimize a shape so that it keeps most of its details but without making the poly count too high. + +@image html files/model_and_optimize_meshes/13_polygons.jpg "Left: Low Poly | Right: High Poly" width=600px + + +If you find that your model is too dense to work with. The “decimate” command can be used to lower the polycount of said mesh, while still keeping its general shape. + +Creating a complex model requires a good understanding of many tools in a 3d modeling program. They all serve their specific purposes and are extremely powerful. They have to be used on top of each other in different ways to get to an end result. The more you add/edit your shape the better it will look in the long run. + +## Exporting models + +When you export a model for Gazebo, the .dae (COLLADA) file type is recommended. +STL and OBJ files are also supported, but are less common for imports into Gazebo. + +To export: select your shape and then `File>Export>Collada(Default)(.dae)` + +@image html files/model_and_optimize_meshes/14_export_model.png width=600px + +After you hit export in whatever file format you decide to go with, the "Blender File View" will appear. This is where you choose the location of your file. You can also choose how you want to export your model. + +Double checking the “global Orientation” can save time and here you can quickly change the “Up Axis” to Z up. This is the standard orientation in Gazebo + +Equally make sure that the If the shape is meant to stand on the ground, place the origin on the base, and make the model face X (Forward Axis) + +@image html files/model_and_optimize_meshes/15_axis.png width=200px + +## Troubleshooting + +There are a few things to take account of when creating a 3d shape for use in simulations. Making sure your scale and orientation are set to Gazebo standards can save you a lot of time. + + - Check Scale/Location: If an imported shape is brought into Gazebo with the wrong units, it might be too big/small for the scene. Make sure that your modeling units are 1:1 with what is in Gazebo (Gazebo uses meters). + +@image html files/model_and_optimize_meshes/16_check_scale.png width=600px + +To change the units in Blender to match Gazebo go to “Scene Properties”. Change the “unit Systems” to Metric and change the “Length” to Meters. + +To change the grid size go to “Show Overlays” and change the “Scale” amount. + + - **Check Pivot Point/Shape Origins:**: When exporting a shape it is usually best practice to make sure that your model is in the middle of your grid `(0,0,0)`, unless said model is part of a bigger scene. + +@image html files/model_and_optimize_meshes/17_check_pivot_point_a.jpg width=600px + +The Pivot Point of a shape is the point from which the shape moves, rotates, and scales from. + +Not only do you need to make sure that your shape is in the right location prior to export, but you also need to make sure that the pivot points are in the right positions as well. + +@image html files/model_and_optimize_meshes/18_check_pivot_point_b.jpg width=600px + +Complex shapes often have a child/parent relationship. This is when a smaller part of a shape is attached to a larger piece, but they have different pivot points. + +For example: The wheel used for this tutorial would be a child shape. It would “parented” to the body of a car. The pivot point of the wheel is in its center axis as shown, but the general pivots of the whole shape would be attached based on the parent shape, around the joint origin. When you export to `Gazebo` make sure that the whole shape is selected and that the main body is on the origin `(0,0,0)`, and X Axis facing forward. diff --git a/tutorials/model_command.md b/tutorials/model_command.md new file mode 100644 index 00000000000..1fbc4ae755e --- /dev/null +++ b/tutorials/model_command.md @@ -0,0 +1,196 @@ +\page model_command Model Command + +## Overview +`ign model` command allows you to get information about the models for a given running Ignition Gazebo simulation. + +For each model, it is possible to get information about its + - Pose: Pose of the model + - Links: Pose, mass, and inertial matrix of the link + - Joints: Parent link, child link and joint type. + +## Example running the diff_drive world + +To try out this command we need first a running simulation. Let's load the `diff_drive` example world. In a terminal, run: + + ign gazebo diff_drive.sdf + +Once Ignition Gazebo is up, we can use the ign model command to get information of the simulation. +Open a new terminal and enter: + + ign model --list + +And available models should be printed: + + + Available models: + - ground_plane + - vehicle_blue + - vehicle_green + +Once you get the name of the model you want to see, you may run the following commands to get its properties. + +`ign model -m ` to get the **complete information of the model**. e.g. + + ign model -m vehicle_blue + +``` + Requesting state for world [diff_drive]... + + Model: [8] + - Name: vehicle_blue + - Pose [ XYZ (m) ] [ RPY (rad) ]: + [0.000000 | 2.000000 | 0.325000] + [0.000000 | 0.000000 | 0.000000] + + - Link [9] + - Name: chassis + - Parent: vehicle_blue [8] + - Mass (kg): [1.143950] + - Inertial Pose: + [0.000000 | 0.000000 | 0.000000] + - Inertial Matrix (kg⋅m^2): + [0.126164 | 0.000000 | 0.000000] + [0.000000 | 0.416519 | 0.000000] + [0.000000 | 0.000000 | 0.481014] + - Pose [ XYZ (m) ] [ RPY (rad) ]: + [-0.151427 | 0.000000 | 0.175000] + [0.000000 | 0.000000 | 0.000000] + - Link [12] + - Name: left_wheel + - Parent: vehicle_blue [8] + - Mass (kg): [2.000000] + - Inertial Pose: + [0.000000 | 0.000000 | 0.000000] + - Inertial Matrix (kg⋅m^2): + [0.145833 | 0.000000 | 0.000000] + [0.000000 | 0.145833 | 0.000000] + [0.000000 | 0.000000 | 0.125000] + - Pose [ XYZ (m) ] [ RPY (rad) ]: + [0.554283 | 0.625029 | -0.025000] + [-1.570700 | 0.000000 | 0.000000] + - Link [15] + - Name: right_wheel + - Parent: vehicle_blue [8] + - Mass (kg): [2.000000] + - Inertial Pose: + [0.000000 | 0.000000 | 0.000000] + - Inertial Matrix (kg⋅m^2): + [0.145833 | 0.000000 | 0.000000] + [0.000000 | 0.145833 | 0.000000] + [0.000000 | 0.000000 | 0.125000] + - Pose [ XYZ (m) ] [ RPY (rad) ]: + [0.554282 | -0.625029 | -0.025000] + [-1.570700 | 0.000000 | 0.000000] + - Link [18] + - Name: caster + - Parent: vehicle_blue [8] + - Mass (kg): [1.000000] + - Inertial Pose: + [0.000000 | 0.000000 | 0.000000] + - Inertial Matrix (kg⋅m^2): + [0.100000 | 0.000000 | 0.000000] + [0.000000 | 0.100000 | 0.000000] + [0.000000 | 0.000000 | 0.100000] + - Pose [ XYZ (m) ] [ RPY (rad) ]: + [-0.957138 | 0.000000 | -0.125000] + [0.000000 | 0.000000 | 0.000000] + - Joint [21] + - Name: left_wheel_joint + - Parent: vehicle_blue [8] + - Type: revolute + - Parent Link: left_wheel + - Child Link: chassis + - Pose [ XYZ (m) ] [ RPY (rad) ]: + [0.000000 | 0.000000 | 0.000000] + [0.000000 | 0.000000 | 0.000000] + - Axis position [ XYZ ]: + [0 | 0 | 1] + - Joint [22] + - Name: right_wheel_joint + - Parent: vehicle_blue [8] + - Type: revolute + - Parent Link: right_wheel + - Child Link: chassis + - Pose [ XYZ (m) ] [ RPY (rad) ]: + [0.000000 | 0.000000 | 0.000000] + [0.000000 | 0.000000 | 0.000000] + - Axis position [ XYZ ]: + [0 | 0 | 1] + - Joint [23] + - Name: caster_wheel + - Parent: vehicle_blue [8] + - Type: ball + - Parent Link: caster + - Child Link: chassis + - Pose [ XYZ (m) ] [ RPY (rad) ]: + [0.000000 | 0.000000 | 0.000000] + [0.000000 | 0.000000 | 0.000000] + +``` + + +`ign model -m --pose` to get the **pose** information. e.g. + + ign model -m vehicle_blue --pose + + +``` + Requesting state for world [diff_drive]... + + Model: [8] + - Name: vehicle_blue + - Pose [ XYZ (m) ] [ RPY (rad) ]: + [0.000000 | 2.000000 | 0.325000] + [0.000000 | 0.000000 | 0.000000] +``` + + +To get the information of **all the model links** enter + + ign model -m --link + + +Or you can get the information of a **single link** by adding the name as argument. e.g. + + ign model -m vehicle_blue --link caster + +``` + Requesting state for world [diff_drive]... + + - Link [18] + - Name: caster + - Parent: vehicle_blue [8] + - Mass (kg): [1.000000] + - Inertial Pose: + [0.000000 | 0.000000 | 0.000000] + - Inertial Matrix (kg⋅m^2): + [0.100000 | 0.000000 | 0.000000] + [0.000000 | 0.100000 | 0.000000] + [0.000000 | 0.000000 | 0.100000] + - Pose [ XYZ (m) ] [ RPY (rad) ]: + [-0.957138 | 0.000000 | -0.125000] + [0.000000 | -0.000000 | 0.000000] +``` + + +To get the information of **all the model joints** enter + + ign model -m --joint + +Or you can get the information of a **single joint** by adding the name as argument. e.g. + + ign model -m vehicle_blue --joint caster_wheel + +``` + Requesting state for world [diff_drive]... + + - Joint [23] + - Name: caster_wheel + - Parent: vehicle_blue [8] + - Type: ball + - Parent Link: caster + - Child Link: chassis + - Pose [ XYZ (m) ] [ RPY (rad) ]: + [0.000000 | 0.000000 | 0.000000] + [0.000000 | -0.000000 | 0.000000] +``` diff --git a/tutorials/particle_tutorial.md b/tutorials/particle_tutorial.md index e996b470397..d15049b9fff 100644 --- a/tutorials/particle_tutorial.md +++ b/tutorials/particle_tutorial.md @@ -1,4 +1,4 @@ -\page particle_mitter Particle Emitter +\page particle_emitter Particle Emitter This tutorial shows how to use the particle emitter system to add and configure particle effects like smoke and fog in simulation. It also shows the effects that particles have on different types of sensors in Ignition Gazebo.