diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dacfdaaf67..d2f8482925 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -53,3 +53,21 @@ jobs: # per bug https://github.com/gazebosim/gz-sim/issues/1409 cmake-args: '-DCMAKE_INSTALL_PREFIX=/usr -DBUILD_DOCS=OFF -DCMAKE_BUILD_TYPE=RelWithDebInfo' apt-dependencies: libdwarf-dev libdw-dev binutils-dev + + noble-ci-disable-gui: + runs-on: ubuntu-latest + name: Ubuntu Noble CI (GUI disabled) + steps: + - name: Checkout + uses: actions/checkout@v4 + - uses: actions/setup-python@v3 + - uses: pre-commit/action@v3.0.0 + with: + extra_args: --all-files + - name: Compile and test + id: ci + uses: gazebo-tooling/action-gz-ci@noble + with: + # per bug https://github.com/gazebosim/gz-sim/issues/1409 + cmake-args: '-DCMAKE_INSTALL_PREFIX=/usr -DBUILD_DOCS=OFF -DCMAKE_BUILD_TYPE=RelWithDebInfo -DENABLE_GUI=OFF' + apt-dependencies: libdwarf-dev libdw-dev binutils-dev diff --git a/CMakeLists.txt b/CMakeLists.txt index 0f56866fbf..27fe1af53d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -113,15 +113,21 @@ set(GZ_FUEL_TOOLS_VER ${gz-fuel_tools10_VERSION_MAJOR}) #-------------------------------------- # Find gz-gui -gz_find_package(gz-gui9 REQUIRED) -set(GZ_GUI_VER ${gz-gui9_VERSION_MAJOR}) -gz_find_package (Qt5 - COMPONENTS - Core - Quick - QuickControls2 - REQUIRED - PKGCONFIG "Qt5Core Qt5Quick Qt5QuickControls2") + +# Option to build gz-sim with GUI support +option(ENABLE_GUI "Build gz-sim with GUI enabled" ON) + +if(ENABLE_GUI) + gz_find_package(gz-gui9 REQUIRED) + set(GZ_GUI_VER ${gz-gui9_VERSION_MAJOR}) + gz_find_package (Qt5 + COMPONENTS + Core + Quick + QuickControls2 + REQUIRED + PKGCONFIG "Qt5Core Qt5Quick Qt5QuickControls2") +endif() #-------------------------------------- # Find gz-physics diff --git a/include/gz/sim/CMakeLists.txt b/include/gz/sim/CMakeLists.txt index 70660cf14f..76d1fc4d04 100644 --- a/include/gz/sim/CMakeLists.txt +++ b/include/gz/sim/CMakeLists.txt @@ -1,4 +1,8 @@ -gz_install_all_headers() +if(ENABLE_GUI) + gz_install_all_headers() +else() + gz_install_all_headers(EXCLUDE_DIRS gui) +endif() add_subdirectory(components) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 91301375a7..2335d0548c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,5 +1,10 @@ add_subdirectory(rendering) -add_subdirectory(gui) + +if(ENABLE_GUI) + add_subdirectory(gui) + add_compile_definitions(WITH_GUI) +endif() + add_subdirectory(systems) file(GLOB gz_msgs_proto_files @@ -50,14 +55,12 @@ set(comms_sources comms/MsgManager.cc ) -set(gui_sources - ${gui_sources} - PARENT_SCOPE -) - -set(cli_sources - gz.cc -) +if(ENABLE_GUI) + set(gui_sources + ${gui_sources} + PARENT_SCOPE + ) +endif() set(material_sources rendering/MaterialParser/MaterialParser.cc @@ -158,26 +161,9 @@ if (MSVC) # 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} ${cli_sources} COMPILE_FLAGS "/wd4251 /wd4146") + set_source_files_properties(${sources} ${gtest_sources} COMPILE_FLAGS "/wd4251 /wd4146") endif() -# CLI -gz_add_component(gz - SOURCES - ${cli_sources} - GET_TARGET_NAME gz_lib_target) -target_link_libraries(${gz_lib_target} - PUBLIC - ${PROJECT_LIBRARY_TARGET_NAME} - gz-common${GZ_COMMON_VER}::gz-common${GZ_COMMON_VER} - gz-sim${PROJECT_VERSION_MAJOR} - gz-sim${PROJECT_VERSION_MAJOR}-gui -) - -# Executable target that runs the GUI without ruby for debugging purposes. -add_executable(runGui cmd/runGui_main.cc) -target_link_libraries(runGui PRIVATE ${gz_lib_target}) - # Create the library target gz_create_core_library(SOURCES ${sources} CXX_STANDARD 17) gz_add_get_install_prefix_impl(GET_INSTALL_PREFIX_FUNCTION gz::sim::getInstallPrefix @@ -205,7 +191,6 @@ target_link_libraries(${PROJECT_LIBRARY_TARGET_NAME} gz-common${GZ_COMMON_VER}::graphics gz-common${GZ_COMMON_VER}::profiler gz-fuel_tools${GZ_FUEL_TOOLS_VER}::gz-fuel_tools${GZ_FUEL_TOOLS_VER} - gz-gui${GZ_GUI_VER}::gz-gui${GZ_GUI_VER} gz-physics${GZ_PHYSICS_VER}::core gz-rendering${GZ_RENDERING_VER}::core gz-transport${GZ_TRANSPORT_VER}::gz-transport${GZ_TRANSPORT_VER} @@ -216,6 +201,13 @@ target_link_libraries(${PROJECT_LIBRARY_TARGET_NAME} gz-plugin${GZ_PLUGIN_VER}::loader ) +if(ENABLE_GUI) + target_link_libraries(${PROJECT_LIBRARY_TARGET_NAME} + PUBLIC + gz-gui${GZ_GUI_VER}::gz-gui${GZ_GUI_VER} + ) +endif() + if (pybind11_FOUND) target_link_libraries(${PROJECT_LIBRARY_TARGET_NAME} PRIVATE pybind11::embed) target_compile_definitions(${PROJECT_LIBRARY_TARGET_NAME} PUBLIC -DHAVE_PYBIND11) diff --git a/src/cmd/CMakeLists.txt b/src/cmd/CMakeLists.txt index d98a39b014..d216f03245 100644 --- a/src/cmd/CMakeLists.txt +++ b/src/cmd/CMakeLists.txt @@ -8,10 +8,50 @@ target_link_libraries(${model_executable} install( TARGETS ${model_executable} - DESTINATION ${CMAKE_INSTALL_LIBEXECDIR}/gz/${GZ_DESIGNATION}${PROJECT_VERSION_MAJOR}/) + DESTINATION ${CMAKE_INSTALL_LIBEXECDIR}/gz/${GZ_DESIGNATION}${PROJECT_VERSION_MAJOR}/ +) + +# Create sim utilies library +add_library(gz STATIC gz.cc) +target_link_libraries(gz + ${PROJECT_LIBRARY_TARGET_NAME} + gz-common${GZ_COMMON_VER}::gz-common${GZ_COMMON_VER} + gz-utils${GZ_UTILS_VER}::cli + gz-sim${PROJECT_VERSION_MAJOR} +) + +# Create GUI executable if enabled +if(ENABLE_GUI) + target_link_libraries(gz gz-sim${PROJECT_VERSION_MAJOR}-gui) + + add_executable(gz-sim-gui gui_main.cc) + target_link_libraries(gz-sim-gui PRIVATE gz) + + install( + TARGETS gz-sim-gui + DESTINATION ${CMAKE_INSTALL_LIBEXECDIR}/gz/${GZ_DESIGNATION}${PROJECT_VERSION_MAJOR}/ + ) +endif() + +# Create sim main executable +set(sim_executable gz-sim-main) +add_executable(${sim_executable} sim_main.cc) +target_link_libraries(${sim_executable} PRIVATE gz) + +install( + TARGETS ${sim_executable} + DESTINATION ${CMAKE_INSTALL_LIBEXECDIR}/gz/${GZ_DESIGNATION}${PROJECT_VERSION_MAJOR}/ +) + +if(ENABLE_GUI) + target_compile_definitions(gz-sim-main + PRIVATE + "GZ_SIM_GUI_EXE=\"${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBEXECDIR}/gz/${GZ_DESIGNATION}${PROJECT_VERSION_MAJOR}/$\"" + ) +endif() # Build the unit tests. -set(test_sources) +set(test_sources gz_TEST.cc) # Add systems that need a valid display here. # \todo(anyone) Find a way to run these tests with a virtual display such Xvfb @@ -23,13 +63,6 @@ else() "Skipping ModelCommandAPI tests because a valid display was not found") endif() -# gz_TEST is not supported with multi config -# CMake generators, see also cmd/CMakeLists.txt -get_property(GENERATOR_IS_MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) -if(NOT GENERATOR_IS_MULTI_CONFIG) - list(APPEND test_sources gz_TEST.cc) -endif() - # Build unit tests if Gazebo tools is installed if(BUILD_TESTING AND GZ_TOOLS_PROGRAM) gz_build_tests(TYPE UNIT @@ -51,7 +84,6 @@ foreach(CMD_TEST endif() add_dependencies(${CMD_TEST} - ${gz_lib_target} TestModelSystem TestSensorSystem TestWorldSystem @@ -62,31 +94,8 @@ foreach(CMD_TEST "GZ_PATH=\"${GZ_TOOLS_PROGRAM}\"" ) - if(${CMD_TEST} STREQUAL UNIT_ModelCommandAPI_TEST) - set_tests_properties(${CMD_TEST} PROPERTIES - ENVIRONMENT "GZ_CONFIG_PATH=${CMAKE_BINARY_DIR}/test/conf/$") - endif() - - if(${CMD_TEST} STREQUAL UNIT_gz_TEST) - # Running `gz sim` on macOS has problems when run with /usr/bin/ruby - # due to System Integrity Protection (SIP). Try to find ruby from - # homebrew as a workaround. - if(APPLE) - find_program(BREW_RUBY ruby HINTS /usr/local/opt/ruby/bin) - endif() - - target_compile_definitions(${CMD_TEST} - PRIVATE - "BREW_RUBY=\"${BREW_RUBY} \"" - ) - - set(_env_vars) - list(APPEND _env_vars "GZ_CONFIG_PATH=${CMAKE_BINARY_DIR}/test/conf") - list(APPEND _env_vars "GZ_SIM_SYSTEM_PLUGIN_PATH=$") - - set_tests_properties(${CMD_TEST} PROPERTIES - ENVIRONMENT "${_env_vars}") - endif() + set_tests_properties(${CMD_TEST} PROPERTIES + ENVIRONMENT "GZ_CONFIG_PATH=${CMAKE_BINARY_DIR}/test/conf/$") # On Windows there is no RPATH, so an alternative way for tests for finding .dll libraries # in build directory in necessary. For regular tests, the trick is to place all libraries @@ -100,87 +109,6 @@ foreach(CMD_TEST endif() endforeach() -#=============================================================================== -# Generate the ruby script. -# Note that the major version of the library is included in the name. -# Ex: cmdsim0.rb -set(cmd_script_name "cmd${GZ_DESIGNATION}${PROJECT_VERSION_MAJOR}.rb") -set(cmd_script_generated "${CMAKE_CURRENT_BINARY_DIR}/$_${cmd_script_name}") -set(cmd_script_configured "${CMAKE_CURRENT_BINARY_DIR}/${cmd_script_name}.configured") - -# Set the library_location variable to the relative path to the library file -# within the install directory structure. -if(WIN32) - set(plugin_location ${CMAKE_INSTALL_BINDIR}) -else() - set(plugin_location ${CMAKE_INSTALL_LIBDIR}) -endif() - -set(library_location "../../../${plugin_location}/$") - -configure_file( - "cmd${GZ_DESIGNATION}.rb.in" - "${cmd_script_configured}" - @ONLY) - -file(GENERATE - OUTPUT "${cmd_script_generated}" - INPUT "${cmd_script_configured}") - -# Install the ruby command line library in an unversioned location. -install(FILES ${cmd_script_generated} DESTINATION lib/ruby/gz RENAME ${cmd_script_name}) - -set(gz_library_path "${CMAKE_INSTALL_PREFIX}/lib/ruby/gz/cmd${GZ_DESIGNATION}${PROJECT_VERSION_MAJOR}") - -# Generate a configuration file. -# Note that the major version of the library is included in the name. -# Ex: sim0.yaml -configure_file( - "${GZ_DESIGNATION}.yaml.in" - "${CMAKE_CURRENT_BINARY_DIR}/${GZ_DESIGNATION}${PROJECT_VERSION_MAJOR}.yaml" @ONLY) - -# Install the yaml configuration files in an unversioned location. -install( FILES - ${CMAKE_CURRENT_BINARY_DIR}/${GZ_DESIGNATION}${PROJECT_VERSION_MAJOR}.yaml - DESTINATION - ${CMAKE_INSTALL_DATAROOTDIR}/gz/) - -#=============================================================================== -# Generate the ruby script for internal testing. -# Note that the major version of the library is included in the name. -# Ex: cmdsim0.rb -# The logic is valid only for single-config CMake generators, so no script is -# generated if a multiple-config CMake geneator is used -get_property(GENERATOR_IS_MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) -if(NOT GENERATOR_IS_MULTI_CONFIG) - set(cmd_script_generated_test "${CMAKE_BINARY_DIR}/test/lib/ruby/gz/cmd${GZ_DESIGNATION}${PROJECT_VERSION_MAJOR}.rb") - set(cmd_script_configured_test "${cmd_script_generated_test}.configured") - - # Set the library_location variable to the relative path to the library file - # within the install directory structure. - set(library_location "$") - - configure_file( - "cmd${GZ_DESIGNATION}.rb.in" - "${cmd_script_configured_test}" - @ONLY) - - file(GENERATE - OUTPUT "${cmd_script_generated_test}" - INPUT "${cmd_script_configured_test}") - - # Used only for internal testing. - set(gz_library_path - "${CMAKE_BINARY_DIR}/test/lib/ruby/gz/cmd${GZ_DESIGNATION}${PROJECT_VERSION_MAJOR}") - - # Generate a configuration file for internal testing. - # Note that the major version of the library is included in the name. - # Ex: sim0.yaml - configure_file( - "${GZ_DESIGNATION}.yaml.in" - "${CMAKE_BINARY_DIR}/test/conf/${GZ_DESIGNATION}${PROJECT_VERSION_MAJOR}.yaml" @ONLY) -endif() - #=============================================================================== # Generate the ruby script for internal testing of model command. # Note that the major version of the library is included in the name. @@ -220,6 +148,46 @@ file(GENERATE INPUT "${CMAKE_CURRENT_BINARY_DIR}/model${PROJECT_VERSION_MAJOR}.yaml.configured" ) +#=============================================================================== +# Generate the ruby script for internal testing of sim command. +# Note that the major version of the library is included in the name. +# Ex: cmdsim0.rb +set(gz_sim_ruby_path + "${CMAKE_BINARY_DIR}/test/lib/$/ruby/gz/cmdsim${PROJECT_VERSION_MAJOR}") +set(cmd_sim_script_generated_test "${gz_sim_ruby_path}.rb") +set(cmd_sim_script_configured_test + "${CMAKE_CURRENT_BINARY_DIR}/test_cmdsim${PROJECT_VERSION_MAJOR}.rb.configured") + +# Set the library_location variable to the full path of the library file within +# the build directory. +set(sim_exe_location "$") + +configure_file( + "cmdsim.rb.in" + "${cmd_sim_script_configured_test}" + @ONLY +) + +file(GENERATE + OUTPUT "${cmd_sim_script_generated_test}" + INPUT "${cmd_sim_script_configured_test}" +) + +# Generate a configuration file for internal testing. +# Note that the major version of the library is included in the name. +# Ex: sim0.yaml +configure_file( + "sim.yaml.in" + "${CMAKE_CURRENT_BINARY_DIR}/sim${PROJECT_VERSION_MAJOR}.yaml.configured" + @ONLY +) + +file(GENERATE + OUTPUT "${CMAKE_BINARY_DIR}/test/conf/$/sim${PROJECT_VERSION_MAJOR}.yaml" + INPUT "${CMAKE_CURRENT_BINARY_DIR}/sim${PROJECT_VERSION_MAJOR}.yaml.configured" +) + + #=============================================================================== # Used for the installed model command version. # Generate the ruby script that gets installed. @@ -256,6 +224,42 @@ configure_file( install(FILES ${model_configured} DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/gz/) +#=============================================================================== +# Used for the installed sim command version. +# Generate the ruby script that gets installed. +# Note that the major version of the library is included in the name. +# Ex: cmdsim0.rb +set(cmd_sim_script_generated "${CMAKE_CURRENT_BINARY_DIR}/cmdsim${PROJECT_VERSION_MAJOR}.rb") +set(cmd_sim_script_configured "${cmd_sim_script_generated}.configured") + +# Set the library_location variable to the relative path to the library file +# within the install directory structure. +set(sim_exe_location "../../../${CMAKE_INSTALL_LIBEXECDIR}/gz/${GZ_DESIGNATION}${PROJECT_VERSION_MAJOR}/$") + +configure_file( + "cmdsim.rb.in" + "${cmd_sim_script_configured}" + @ONLY +) + +file(GENERATE + OUTPUT "${cmd_sim_script_generated}" + INPUT "${cmd_sim_script_configured}" +) +# Install the ruby command line library in an unversioned location. +install(FILES ${cmd_sim_script_generated} DESTINATION lib/ruby/gz) + +# Used for the installed version. +set(gz_sim_ruby_path "${CMAKE_INSTALL_PREFIX}/lib/ruby/gz/cmdsim${PROJECT_VERSION_MAJOR}") + +set(sim_configured "${CMAKE_CURRENT_BINARY_DIR}/sim${PROJECT_VERSION_MAJOR}.yaml") +configure_file( + "sim.yaml.in" + ${sim_configured} + @ONLY) + +install(FILES ${sim_configured} DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/gz/) + #=============================================================================== # Bash completion diff --git a/src/cmd/ModelCommandAPI.hh b/src/cmd/ModelCommandAPI.hh index 3285b9cd22..1004a61027 100644 --- a/src/cmd/ModelCommandAPI.hh +++ b/src/cmd/ModelCommandAPI.hh @@ -15,8 +15,6 @@ * */ -#include "gz/sim/gz/Export.hh" - /// \brief External hook to get a list of available models. void cmdModelList(); diff --git a/src/cmd/cmdsim.rb.in b/src/cmd/cmdsim.rb.in index 767738ad16..1d61f1d393 100755 --- a/src/cmd/cmdsim.rb.in +++ b/src/cmd/cmdsim.rb.in @@ -14,597 +14,39 @@ # 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' -require 'erb' require 'pathname' # 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 = { 'sim' => - "Run and manage Gazebo simulations. \n"\ - " \n"\ - " gz sim [options] [file] \n"\ - " \n"\ - " \n"\ - "Available Options: \n"\ - " -g Run only the GUI. \n"\ - "\n"\ - " --initial-sim-time [arg] Initial simulation time, in seconds. \n"\ - "\n"\ - " --iterations [arg] Number of iterations to execute. \n"\ - "\n"\ - " --levels Use the level system. The default is false, \n"\ - " which loads all models. It's always true \n"\ - " with --network-role. \n"\ - "\n"\ - " --network-role [arg] Participant role used in a distributed \n"\ - " simulation environment. Role is one of \n"\ - " [primary, secondary]. It implies --levels. \n"\ - "\n"\ - " --network-secondaries [arg] Number of secondary participants expected \n"\ - " to join a distributed simulation \n"\ - " environment. (Primary only). \n"\ - "\n"\ - " --record Use logging system to record states and \n"\ - " console messages to the default location, \n"\ - " in ~/.gz/sim/log. \n"\ - "\n"\ - " --record-path [arg] Implicitly invokes --record, and specifies \n"\ - " custom path to put recorded files. Argument \n"\ - " is path to record states and console \n"\ - " messages. Specifying this argument will \n"\ - " enable console logging to a console.log \n"\ - " file in the specified path. \n"\ - "\n"\ - " --record-resources Implicitly invokes --record, and records \n"\ - " meshes and material files, in addition to \n"\ - " states and console messages. \n"\ - "\n"\ - " --record-topic [arg] Specify the name of an additional topic to \n"\ - " record. Implicitly invokes --record. \n"\ - " Zero or more topics can be specified by \n"\ - " using multiple --record-topic options. \n"\ - " Regular expressions can be used, which \n"\ - " likely requires quotes. A default set of \n"\ - " topics are also recorded, which support \n"\ - " simulation state playback. Enable debug \n"\ - " console output with the -v 4 option \n"\ - " and look for 'Recording default topic' in \n"\ - " order to determine the default set of \n"\ - " topics. \n"\ - " Examples: \n"\ - " 1. Record all topics. \n"\ - " --record-topic \".*\" \n"\ - " 2. Record only the /stats topic. \n"\ - " --record-topic /stats \n"\ - " 3. Record the /stats and /clock topics. \n"\ - " --record-topic /stats \ \n"\ - " --record-topic /clock \n"\ - "\n"\ - " --record-period [arg] Specify the time period (seconds) between \n"\ - " state recording. \n"\ - "\n"\ - " --log-overwrite When recording, overwrite existing files. \n"\ - " Only valid if recording is enabled. \n"\ - "\n"\ - " --log-compress When recording, compress final log files. \n"\ - " Only valid if recording is enabled. \n"\ - "\n"\ - " --seed [arg] Pass a custom seed value to the random \n"\ - " number generator. \n"\ - "\n"\ - " --playback [arg] Use logging system to play back states. \n"\ - " Argument is path to recorded states. \n"\ - "\n"\ - " --headless-rendering Run rendering in headless mode \n"\ - "\n"\ - " -r Run simulation on start. \n"\ - "\n"\ - " -s Run only the server (headless mode). This \n"\ - " overrides -g, if it is also present. \n"\ - "\n"\ - " -v [ --verbose ] [arg] Adjust the level of console output (0~4). \n"\ - " The default verbosity is 1, use -v without \n"\ - " arguments for level 3. \n"\ - "\n"\ - " --gui-config [arg] Gazebo GUI configuration file to load. \n"\ - " If no config is given, the configuration in \n"\ - " the SDF file is used. And if that's not \n"\ - " provided, the default installed config is \n"\ - " used. \n"\ - "\n"\ - " --physics-engine [arg] Gazebo Physics engine plugin to load. \n"\ - " Gazebo will use DART by default. \n"\ - " (gz-physics-dartsim-plugin) \n"\ - " Make sure custom plugins are in \n"\ - " GZ_SIM_PHYSICS_ENGINE_PATH. \n"\ - "\n"\ - " --render-engine [arg] Gazebo Rendering engine plugin to load for \n"\ - " both the server and the GUI. Gazebo will use \n"\ - " OGRE2 by default. (ogre2) \n"\ - " Make sure custom plugins are in \n"\ - " GZ_SIM_RENDER_ENGINE_PATH. \n"\ - "\n"\ - " --render-engine-api-backend [arg] \n"\ - " API to use for both the Server & GUI. \n"\ - " Possible values for ogre2: \n"\ - " - opengl (default) \n"\ - " - vulkan (beta) \n"\ - " - metal (Apple only, default for Apple) \n"\ - " Note: If using Vulkan in the GUI and gz-gui \n"\ - " was built against Qt < 5.15.2, it may be very \n"\ - " slow. \n"\ - "\n"\ - " --render-engine-gui [arg] Gazebo Rendering engine plugin to load for \n"\ - " the GUI. Gazebo will use OGRE2 by default. \n"\ - " (ogre2) \n"\ - " Make sure custom plugins are in \n"\ - " GZ_SIM_RENDER_ENGINE_PATH. \n"\ - "\n"\ - " --render-engine-gui-api-backend [arg] \n"\ - " Same as --render-engine-api-backend but only \n"\ - " for the GUI. \n"\ - "\n"\ - " --render-engine-server [arg] Gazebo Rendering engine plugin to load for \n"\ - " the server. Gazebo will use OGRE2 by default. \n"\ - " (ogre2) \n"\ - " Make sure custom plugins are in \n"\ - " GZ_SIM_RENDER_ENGINE_PATH. \n"\ - "\n"\ - " --render-engine-server-api-backend [arg] \n"\ - " Same as --render-engine-api-backend but only \n"\ - " for the server. \n"\ - "\n"\ - " --version Print Gazebo version information. \n"\ - "\n"\ - " -z [arg] Update rate in Hertz. \n"\ - "\n"+ - COMMON_OPTIONS + "\n\n" + - "Environment variables: \n"\ - " GZ_SIM_RESOURCE_PATH Colon separated paths used to locate \n"\ - " resources such as worlds and models. \n\n"\ - " GZ_SIM_SYSTEM_PLUGIN_PATH Colon separated paths used to \n"\ - " locate system plugins. \n\n"\ - " GZ_SIM_SERVER_CONFIG_PATH Path to server configuration file. \n\n"\ - " GZ_GUI_PLUGIN_PATH Colon separated paths used to locate GUI \n"\ - " plugins. \n"\ - " GZ_GUI_RESOURCE_PATH Colon separated paths used to locate GUI \n"\ - " resources such as configuration files. \n\n" +COMMANDS = { + 'sim' => '@sim_exe_location@', } # # Class for the Gazebo command line tools. # class Cmd - - def killProcess(pid, name, timeout) - Process.kill("-INT", pid) - - sleepSecs = 0.001 - iterations = (timeout / sleepSecs).to_i - i = 0 - killedPid = 0 - while killedPid != pid && i < iterations - begin - killedPid = Process.waitpid(pid, Process::WNOHANG) - rescue - # The process has exited, so return. - return - end - - break if killedPid == pid - - sleep sleepSecs - i = i + 1 - end - - if killedPid != pid - puts "Escalating to SIGKILL on [#{name}]" - Process.kill("-KILL", pid) - end - end - - # - # Return a structure describing the options. - # - def parse(args) - options = { - 'file' => '', - 'gui' => 0, - 'hz' => -1, - 'initial_sim_time' => 0, - 'iterations' => 0, - 'levels' => 0, - 'network_role' => '', - 'network_secondaries' => 0, - 'record' => 0, - 'record-path' => '', - 'record-resources' => 0, - 'record-topics' => [], - 'record-period' => -1, - 'log-overwrite' => 0, - 'log-compress' => 0, - 'playback' => '', - 'run' => 0, - 'server' => 0, - 'verbose' => '1', - 'gui_config' => '', - 'physics_engine' => '', - 'render_engine_gui' => '', - 'render_engine_gui_api_backend' => '', - 'render_engine_server' => '', - 'render_engine_server_api_backend' => '', - 'headless-rendering' => 0, - 'wait_gui' => 1, - 'seed' => 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('--iterations [arg]', Integer, - 'Number of iterations to execute') do |i| - options['iterations'] = i - end - opts.on('--network-role [arg]', String) do |role| - options['network_role'] = role - end - opts.on('--network-secondaries [arg]', Integer) do |i| - options['network_secondaries'] = i - end - opts.on('-z [arg]', Float, 'Update rate in Hertz') do |h| - options['hz'] = h - end - opts.on('--initial-sim-time [arg]', Float, - 'Initial simulation time, in seconds.') do |t| - options['initial_sim_time'] = t - end - opts.on('-r') do - options['run'] = 1 - end - opts.on('-g') do - options['gui'] = 1 - # Runing the Gui only, don't show world loading menu - options['wait_gui'] = 0 - end - opts.on('-s') do - options['server'] = 1 - # Runing the server only, don't wait for starting world from Gui - options['wait_gui'] = 0 - end - opts.on('--levels') do - options['levels'] = 1 - end - opts.on('--record') do - options['record'] = 1 - end - opts.on('--record-path [arg]', String) do |r| - options['record-path'] = r - end - opts.on('--record-resources') do - options['record-resources'] = 1 - end - opts.on('--record-topic [arg]', String) do |t| - options['record-topics'].append(t) - end - opts.on('--record-period [arg]', Float) do |d| - options['record-period'] = d - end - opts.on('--log-overwrite') do - options['log-overwrite'] = 1 - end - opts.on('--log-compress') do - options['log-compress'] = 1 - end - opts.on('--playback [arg]', String) do |p| - options['playback'] = p - end - opts.on('-v [verbose]', '--verbose [verbose]', String) do |v| - options['verbose'] = v || '3' - end - opts.on('--gui-config [arg]', String) do |c| - options['gui_config'] = c - end - opts.on('--physics-engine [arg]', String) do |e| - options['physics_engine'] = e - end - opts.on('--headless-rendering') do - options['headless-rendering'] = 1 - end - opts.on('--render-engine-gui [arg]', String) do |g| - options['render_engine_gui'] = g - end - opts.on('--render-engine-gui-api-backend [arg]', String) do |a| - options['render_engine_gui_api_backend'] = a - end - opts.on('--render-engine-server [arg]', String) do |k| - options['render_engine_server'] = k - end - opts.on('--render-engine-server-api-backend [arg]', String) do |a| - options['render_engine_server_api_backend'] = a - end - opts.on('--render-engine [arg]', String) do |f| - options['render_engine_gui'] = f - options['render_engine_server'] = f - end - opts.on('--render-engine-api-backend [arg]', String) do |a| - options['render_engine_gui_api_backend'] = a - options['render_engine_server_api_backend'] = a - end - opts.on('--version') do - options['version'] = '1' - end - opts.on('--seed [arg]', Integer) do |i| - options['seed'] = i - end - - end # opt_parser do - - opt_parser.parse!(args) - - # SDF file as positional argument - filename = args.pop - if filename and filename != 'sim' and filename != 'gazebo' - options['file'] = filename - end - - options['command'] = args[0] - - options - end # parse() - def execute(args) - options = parse(args) + command = args[0] + exe_name = COMMANDS[command] - library_name_path = Pathname.new(LIBRARY_NAME) - if library_name_path.absolute? - # 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 + unless Pathname.new(exe_name).absolute? # 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)) + exe_name = File.expand_path(File.join(File.dirname(__FILE__), exe_name)) end conf_version = LIBRARY_VERSION - - begin - Importer.dlload plugin - rescue DLError => e - puts "Library error for [#{plugin}]: #{e.to_s}" - if plugin.end_with? ".dylib" - puts " -If this script was executed with /usr/bin/ruby, this error may be caused by -macOS System Integrity Protection. One workaround is to use a different -version of ruby, for example: - brew install ruby -and add the following line to your shell profile: - export PATH=/usr/local/opt/ruby/bin:$PATH -If you are using a colcon workspace, please ensure that the setup script -has properly set the DYLD_LIBRARY_PATH environment variables." - end - exit(-1) - end - - # Read the library version. - Importer.extern 'char *gzSimVersion()' - begin - plugin_version = Importer.gzSimVersion.to_s - rescue DLError - puts "Library error: Problem running 'gzSimVersion()' from #{plugin}." - exit(-1) - end + exe_version = `#{exe_name} --version`.strip # Sanity check: Verify that the version of the yaml file matches the version # of the library that we are using. - unless plugin_version.eql? conf_version + unless exe_version.eql? conf_version puts "Error: Version mismatch. Your configuration file version is - [#{conf_version}] but #{plugin} version is [#{plugin_version}]." + [#{conf_version}] but #{exe_name} version is [#{exe_version}]." exit(-1) end - usage = COMMANDS[args[0]] - - begin - - # Import the findFuelResource function - Importer.extern 'const char *findFuelResource(const char *)' - - if options.key?('version') - Importer.extern 'char *simVersionHeader()' - puts Importer.simVersionHeader.to_s - exit - end - - # Global configurations - if options.key?('verbose') - Importer.extern 'void cmdVerbosity(const char *)' - Importer.cmdVerbosity(options['verbose']) - end - - parsed = '' - if options['file'] != '' - # Check if the passed in file exists. - if File.exist?(options['file']) - path = options['file'] - # If not, then first check the GZ_SIM_RESOURCE_PATH environment - # variable, then the configuration path from the launch library. - else - resourcePathEnv = ENV['GZ_SIM_RESOURCE_PATH'] - - if !resourcePathEnv.nil? - resourcePaths = resourcePathEnv.split(':') - for resourcePath in resourcePaths - filePath = File.join(resourcePath, options['file']) - if File.exist?(filePath) - path = filePath - break - end - end - end - - if path.nil? - Importer.extern 'char *worldInstallDir()' - path = File.join(Importer.worldInstallDir().to_s, options['file']) - if !File.exist?(path) - path = Importer.findFuelResource(options['file']).to_s - options['file'] = path - if path == "" - puts "Unable to find or download file " + options['file'] - exit(-1) - end - end - end - end - - # ERB parse the file, and then run the result - parsed = ERB.new(File.read(path)).result() - end - - # Import the runServer function - Importer.extern 'int runServer(const char *, int, int, float, double, int, - const char *, int, int, const char *, - int, int, int, const char *, const char *, - const char *, const char *, const char *, - const char *, const char *, - const char *, int, int, float, int)' - - # Import the runGui function - Importer.extern 'int runGui(const char *, const char *, int, - const char *, const char *)' - - # If playback is specified, and the user has not specified a - # custom gui config, set the gui config to load the playback - # gui config - if (options['playback'] != '' and options['gui_config'] == '') - options['gui_config'] = "_playback_" - end - - # Neither the -s nor -g options were used, so run both the server - # and gui. - if options['server'] == 0 && options['gui'] == 0 - - if plugin.end_with? ".dylib" - puts "On macOS `gz sim` currently only works with either the -s argument -or the -g argument, you cannot run both server and gui in one terminal. -See https://github.com/gazebosim/gz-sim/issues/44 for more info." - exit(-1) - end - - if plugin.end_with? ".dll" - puts "`ign gazebo` currently only works with the -s argument on Windows. -See https://github.com/gazebosim/gz-sim/issues/168 for more info." - exit(-1) - end - - serverPid = Process.fork do - ENV['RMT_PORT'] = '1500' - Process.setpgid(0, 0) - Process.setproctitle('gz sim server') - Importer.runServer(parsed, - options['iterations'], options['run'], options['hz'], - options['initial_sim_time'], options['levels'], - options['network_role'], options['network_secondaries'], - options['record'], options['record-path'], - options['record-resources'], options['log-overwrite'], - options['log-compress'], options['playback'], - options['physics_engine'], - options['render_engine_server'], - options['render_engine_server_api_backend'], - options['render_engine_gui'], - options['render_engine_gui_api_backend'], - options['file'], options['record-topics'].join(':'), - options['wait_gui'], - options['headless-rendering'], options['record-period'], - options['seed']) - end - - guiPid = Process.fork do - ENV['RMT_PORT'] = '1501' - Process.setpgid(0, 0) - Process.setproctitle('gz sim gui') - Importer.runGui(options['gui_config'], options['file'], - options['wait_gui'], options['render_engine_gui'], - options['render_engine_gui_api_backend']) - end - - # Handle SIGINT and SIGTERM signals - def handle_signal(guiPid, serverPid) - self.killProcess(guiPid, "Gazebo Sim GUI", 5.0) - self.killProcess(serverPid, "Gazebo Sim Server", 5.0) - end - - Signal.trap("INT") { - handle_signal(guiPid, serverPid) - return 1 - } - - Signal.trap("TERM") { - handle_signal(guiPid, serverPid) - return 1 - } - - # Wait for a child process to end - pid, status = Process.wait2 - - if pid == serverPid - self.killProcess(guiPid, "Gazebo Sim GUI", 5.0) - else - self.killProcess(serverPid, "Gazebo Sim Server", 5.0) - end - - # If the -s option was specified, then run only the server - elsif options['server'] == 1 - ENV['RMT_PORT'] = '1500' - Importer.runServer(parsed, options['iterations'], options['run'], - options['hz'], options['initial_sim_time'], options['levels'], - options['network_role'], options['network_secondaries'], - options['record'], options['record-path'], - options['record-resources'], options['log-overwrite'], - options['log-compress'], options['playback'], - options['physics_engine'], - options['render_engine_server'], - options['render_engine_server_api_backend'], - options['render_engine_gui'], - options['render_engine_gui_api_backend'], - options['file'], options['record-topics'].join(':'), - options['wait_gui'], options['headless-rendering'], - options['record-period'], options['seed']) - # Otherwise run the gui - else options['gui'] - ENV['RMT_PORT'] = '1501' - Importer.runGui(options['gui_config'], options['file'], - options['wait_gui'], options['render_engine_gui'], - options['render_engine_gui_api_backend']) - end - end - # execute + # Drop command from list of arguments + exec(exe_name, *args[1..-1]) end -# class end diff --git a/src/cmd/gui_main.cc b/src/cmd/gui_main.cc new file mode 100644 index 0000000000..43588131ca --- /dev/null +++ b/src/cmd/gui_main.cc @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2025 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 "gz/sim/config.hh" +#include "gz.hh" + +using namespace gz; + +////////////////////////////////////////////////// +/// \brief Structure to hold all available GUI launch options +struct GuiOptions +{ + /// \brief Name of the SDFormat file + std::string file{""}; + + /// \brief Path to GUI configuration file + std::string guiConfig{""}; + + /// \brief Render engine GUI plugin + std::string renderEngineGui{""}; + + /// \brief Render engine GUI API Backend + std::string renderEngineGuiApiBackend{""}; + + /// \brief Show the world loading menu + int waitGui{1}; +}; + +////////////////////////////////////////////////// +void addGuiFlags(CLI::App &_app) +{ + auto opt = std::make_shared(); + + _app.add_option_function("file", + [opt](const std::string &_file){ + opt->file = _file; + }, + "Name of the SDFormat file."); + + _app.add_option("--render-engine-gui", opt->renderEngineGui, + "Gazebo Rendering engine plugin to load for the GUI.\n" + "Gazebo will use OGRE2 by default. Make sure custom\n" + "plugins are in GZ_SIM_RENDER_ENGINE_PATH.") + ->default_str("ogre2"); + + _app.add_option("--render-engine-gui-api-backend", + opt->renderEngineGuiApiBackend, + "Same as --render-engine-api-backend but only\n" + "for the GUI.") + ->check(CLI::IsMember({"opengl", "vulkan", "metal"})); + + _app.add_option("--render-engine", opt->renderEngineGui, + "Gazebo Rendering engine plugin to load for both the Server\n" + "and the GUI. Gazebo will use OGRE2 by default.\n" + "Make sure custom plugins are inside\n" + "GZ_SIM_RENDER_ENGINE_PATH.") + ->default_str("ogre2"); + + _app.add_option("--render-engine-api-backend", + opt->renderEngineGuiApiBackend, + "API to use for both Server and GUI.\n" + "Possible values for ogre2:\n" + " - opengl (default)\n" + " - vulkan (beta)\n" + " - metal (Apple only. Default for Apple)\n" + "Note: If Vulkan is being in the GUI and gz-gui was\n" + "built against Qt < 5.15.2, it may be very slow") + ->check(CLI::IsMember({"opengl", "vulkan", "metal"})); + + _app.add_option("--gui-config", opt->guiConfig, + "Gazebo GUI configuration file to load.\n" + "If no file is provided then the configuration in\n" + "SDF file is used. If that is also missing then\n" + "the default installed configuration is used."); + + _app.callback([opt](){ + + // Get verbosity level from environment + std::string verbosity; + if(utils::env("GZ_SIM_VERBOSITY", verbosity)) + { + cmdVerbosity(std::stoi(verbosity)); + } + + // Check SDF file and parse into string + if(checkFile(opt->file) < 0) + return; + + // Get flag to display Quickstart menu + std::string waitGui; + if(utils::env("GZ_SIM_WAIT_GUI", waitGui)) + { + opt->waitGui = std::stoi(waitGui); + } + + runGui(opt->guiConfig.c_str(), opt->file.c_str(), opt->waitGui, + opt->renderEngineGui.c_str(), + opt->renderEngineGuiApiBackend.c_str()); + }); +} + +////////////////////////////////////////////////// +int main(int argc, char** argv) +{ + CLI::App app{"Run and manage Gazebo GUI."}; + + addGuiFlags(app); + app.formatter(std::make_shared(&app)); + CLI11_PARSE(app, argc, argv); +} diff --git a/src/gz.cc b/src/cmd/gz.cc similarity index 77% rename from src/gz.cc rename to src/cmd/gz.cc index 3c7772e627..c2a8aa94f0 100644 --- a/src/gz.cc +++ b/src/cmd/gz.cc @@ -18,6 +18,9 @@ #include "gz.hh" #include +#include +#include +#include #include #include @@ -37,47 +40,119 @@ #include "gz/sim/Server.hh" #include "gz/sim/ServerConfig.hh" +#include "gz/sim/Util.hh" + +#ifdef WITH_GUI #include "gz/sim/gui/Gui.hh" +#endif using namespace gz; ////////////////////////////////////////////////// -extern "C" char *gzSimVersion() +void cmdVerbosity(const int _verbosity) { - return strdup(GZ_SIM_VERSION_FULL); + common::Console::SetVerbosity(_verbosity); + + // SDFormat only has 2 levels: quiet / loud. Let sim users suppress all SDF + // console output with zero verbosity. + if (_verbosity == 0) + { + sdf::Console::Instance()->SetQuiet(true); + } } ////////////////////////////////////////////////// -extern "C" char *simVersionHeader() +const std::string worldInstallDir() { - return strdup(GZ_SIM_VERSION_HEADER); + static std::string worldInstallDir = gz::sim::getWorldInstallDir(); + return worldInstallDir; } ////////////////////////////////////////////////// -extern "C" void cmdVerbosity( - const char *_verbosity) +int checkFile(std::string &_file) { - int verbosity = std::atoi(_verbosity); - common::Console::SetVerbosity(verbosity); - - // SDFormat only has 2 levels: quiet / loud. Let sim users suppress all SDF - // console output with zero verbosity. - if (verbosity == 0) + // Check if passed string is valid + if(!_file.empty()) { - sdf::Console::Instance()->SetQuiet(true); + std::string filepath; + + // Check if passed file exists + if(std::filesystem::exists(_file)) + { + filepath = _file; + } + else + { + // If passed file does not exist, check GZ_SIM_RESOURCE_PATH + // environment variable + auto resourcePaths = sim::resourcePaths(); + for(auto path : resourcePaths) + { + std::string resourceFilepath = + common::joinPaths(path, _file); + if(std::filesystem::exists(resourceFilepath)) + { + filepath = resourceFilepath; + break; + } + } + + // If file does not exist in resource path, check in the + // installation location of available worlds + if(filepath.empty()) + { + std::string worldPath = + common::joinPaths(worldInstallDir(), _file); + if(std::filesystem::exists(worldPath)) + { + filepath = worldPath; + } + else + { + // Check Fuel for file + filepath = findFuelResource(_file); + } + } + } + + if(filepath.empty()) + { + std::cout << "Unable to find or download file " + << _file << std::endl; + return -1; + } + + _file = filepath; } + + return 0; } ////////////////////////////////////////////////// -extern "C" const char *worldInstallDir() +int parseSdfFile(const std::string &_file, + std::string &_parsedSdfFile) { - static std::string worldInstallDir = gz::sim::getWorldInstallDir(); - return worldInstallDir.c_str(); + if(!_parsedSdfFile.empty()) + { + std::ifstream fs(_file); + if(!fs) + { + std::cout << "Error reading the SDFormat file " + << _file << std::endl; + return -1; + } + + std::stringstream buffer; + buffer << fs.rdbuf(); + _parsedSdfFile = buffer.str(); + } + + return 0; } ////////////////////////////////////////////////// -extern "C" const char *findFuelResource( - char *_pathToResource) +std::string findFuelResource( + const std::string &_pathToResource) { std::string path; std::string worldPath; @@ -123,7 +198,7 @@ extern "C" const char *findFuelResource( if (fileExtension == "sdf") { - return strdup(current.c_str()); + return current; } } } @@ -131,19 +206,17 @@ extern "C" const char *findFuelResource( } ////////////////////////////////////////////////// -extern "C" int runServer(const char *_sdfString, - int _iterations, int _run, float _hz, double _initialSimTime, - int _levels, const char *_networkRole, +void createServerConfig(sim::ServerConfig &_config, const char *_sdfString, + float _hz, double _initialSimTime, int _levels, const char *_networkRole, int _networkSecondaries, int _record, const char *_recordPath, int _recordResources, int _logOverwrite, int _logCompress, const char *_playback, const char *_physicsEngine, const char *_renderEngineServer, const char *_renderEngineServerApiBackend, const char *_renderEngineGui, const char *_renderEngineGuiApiBackend, - const char *_file, const char *_recordTopics, int _waitGui, + const char *_file, std::vector _recordTopics, int _waitGui, int _headless, float _recordPeriod, int _seed) { std::string startingWorldPath{""}; - sim::ServerConfig serverConfig; // Lock until the starting world is received from Gui if (_waitGui == 1) @@ -174,7 +247,7 @@ extern "C" int runServer(const char *_sdfString, } // Path for logs - std::string recordPathMod = serverConfig.LogRecordPath(); + std::string recordPathMod = _config.LogRecordPath(); // Path for compressed log, used to check for duplicates std::string cmpPath = std::string(recordPathMod); @@ -188,19 +261,19 @@ extern "C" int runServer(const char *_sdfString, // Initialize console log if ((_recordPath != nullptr && std::strlen(_recordPath) > 0) || _record > 0 || _recordResources > 0 || _recordPeriod >= 0 || - (_recordTopics != nullptr && std::strlen(_recordTopics) > 0)) + (_recordTopics.size() > 0)) { if (_playback != nullptr && std::strlen(_playback) > 0) { gzerr << "Both record and playback are specified. Only specify one.\n"; - return -1; + return; } - serverConfig.SetUseLogRecord(true); - serverConfig.SetLogRecordResources(_recordResources); + _config.SetUseLogRecord(true); + _config.SetLogRecordResources(_recordResources); if (_recordPeriod >= 0) { - serverConfig.SetLogRecordPeriod( + _config.SetLogRecordPeriod( std::chrono::duration_cast( std::chrono::duration(_recordPeriod))); } @@ -315,23 +388,21 @@ extern "C" int runServer(const char *_sdfString, gzmsg << "Recording states to default path [" << recordPathMod << "]" << std::endl; } - serverConfig.SetLogRecordPath(recordPathMod); + _config.SetLogRecordPath(recordPathMod); - std::vector topics = common::split( - _recordTopics, ":"); - for (const std::string &topic : topics) + for (const std::string &topic : _recordTopics) { - serverConfig.AddLogRecordTopic(topic); + _config.AddLogRecordTopic(topic); } } else { - gzLogInit(serverConfig.LogRecordPath(), "server_console.log"); + gzLogInit(_config.LogRecordPath(), "server_console.log"); } if (_logCompress > 0) { - serverConfig.SetLogRecordCompressPath(cmpPath); + _config.SetLogRecordCompressPath(cmpPath); } gzmsg << "Gazebo Sim Server v" << GZ_SIM_VERSION_FULL @@ -340,40 +411,40 @@ extern "C" int runServer(const char *_sdfString, // Set the SDF string to user if (_sdfString != nullptr && std::strlen(_sdfString) > 0) { - if (!serverConfig.SetSdfString(_sdfString)) + if (!_config.SetSdfString(_sdfString)) { gzerr << "Failed to set SDF string [" << _sdfString << "]" << std::endl; - return -1; + return; } } // This ensures if the server was run stand alone with a world from // command line, the correct world would be loaded. if(_waitGui == 1) - serverConfig.SetSdfFile(startingWorldPath); + _config.SetSdfFile(startingWorldPath); else - serverConfig.SetSdfFile(_file); + _config.SetSdfFile(_file); // Initial simulation time. - serverConfig.SetInitialSimTime(_initialSimTime); + _config.SetInitialSimTime(_initialSimTime); // Set the update rate. if (_hz > 0.0) - serverConfig.SetUpdateRate(_hz); + _config.SetUpdateRate(_hz); // Set whether levels should be used. if (_levels > 0) { gzmsg << "Using the level system\n"; - serverConfig.SetUseLevels(true); + _config.SetUseLevels(true); } if (_networkRole && std::strlen(_networkRole) > 0) { gzmsg << "Using the distributed simulation and levels systems\n"; - serverConfig.SetNetworkRole(_networkRole); - serverConfig.SetNetworkSecondaries(_networkSecondaries); - serverConfig.SetUseLevels(true); + _config.SetNetworkRole(_networkRole); + _config.SetNetworkSecondaries(_networkSecondaries); + _config.SetUseLevels(true); } if (_playback != nullptr && std::strlen(_playback) > 0) @@ -382,61 +453,53 @@ extern "C" int runServer(const char *_sdfString, { gzerr << "Both an SDF file and playback flag are specified. " << "Only specify one.\n"; - return -1; + return; } else { gzmsg << "Playing back states" << _playback << std::endl; - serverConfig.SetLogPlaybackPath(common::absPath( + _config.SetLogPlaybackPath(common::absPath( std::string(_playback))); } } if (_physicsEngine != nullptr && std::strlen(_physicsEngine) > 0) { - serverConfig.SetPhysicsEngine(_physicsEngine); + _config.SetPhysicsEngine(_physicsEngine); } - serverConfig.SetHeadlessRendering(_headless); + _config.SetHeadlessRendering(_headless); if (_renderEngineServer != nullptr && std::strlen(_renderEngineServer) > 0) { - serverConfig.SetRenderEngineServer(_renderEngineServer); + _config.SetRenderEngineServer(_renderEngineServer); } if (_renderEngineServerApiBackend != nullptr) { - serverConfig.SetRenderEngineServerApiBackend(_renderEngineServerApiBackend); + _config.SetRenderEngineServerApiBackend(_renderEngineServerApiBackend); } if (_renderEngineGuiApiBackend != nullptr) { - serverConfig.SetRenderEngineGuiApiBackend(_renderEngineGuiApiBackend); + _config.SetRenderEngineGuiApiBackend(_renderEngineGuiApiBackend); } if (_renderEngineGui != nullptr && std::strlen(_renderEngineGui) > 0) { - serverConfig.SetRenderEngineGui(_renderEngineGui); + _config.SetRenderEngineGui(_renderEngineGui); } if (_seed != 0) { - serverConfig.SetSeed(_seed); + _config.SetSeed(_seed); gzmsg << "Setting seed value: " << _seed << "\n"; } - - // Create the Gazebo server - sim::Server server(serverConfig); - - // Run the server - server.Run(true, _iterations, _run == 0); - - gzdbg << "Shutting down gz-sim-server" << std::endl; - return 0; } +#ifdef WITH_GUI ////////////////////////////////////////////////// -extern "C" int runGui(const char *_guiConfig, const char *_file, int _waitGui, +int runGui(const char *_guiConfig, const char *_file, int _waitGui, const char *_renderEngine, const char *_renderEngineGuiApiBackend) { @@ -470,3 +533,4 @@ extern "C" int runGui(const char *_guiConfig, const char *_file, int _waitGui, return gz::sim::gui::runGui(argc, argv, _guiConfig, _file, _waitGui, _renderEngine, _renderEngineGuiApiBackend); } +#endif diff --git a/src/gz.hh b/src/cmd/gz.hh similarity index 55% rename from src/gz.hh rename to src/cmd/gz.hh index d03b37c6e5..8710737d00 100644 --- a/src/gz.hh +++ b/src/cmd/gz.hh @@ -14,31 +14,42 @@ * limitations under the License. * */ -#ifndef GZ_SIM_GZ_HH_ -#define GZ_SIM_GZ_HH_ -#include "gz/sim/gz/Export.hh" +#include +#include -/// \brief External hook to read the library version. -/// \return C-string representing the version. Ex.: 0.1.2 -extern "C" GZ_SIM_GZ_VISIBLE char *gzSimVersion(); - -/// \brief Get the Gazebo version header. -/// \return C-string containing the Gazebo version information. -extern "C" GZ_SIM_GZ_VISIBLE char *simVersionHeader(); +#include "gz/sim/ServerConfig.hh" /// \brief Set verbosity level /// \param[in] _verbosity 0 to 4 -extern "C" GZ_SIM_GZ_VISIBLE void cmdVerbosity( - const char *_verbosity); +void cmdVerbosity(const int _verbosity); + +/// \brief Get the install directory of world SDFormat files +/// \return String containing the relative path +const std::string worldInstallDir(); + +/// \brief Check if the file exists +/// \param[in] _file Name of the SDFormat file +/// \return 0 if exists, -1 in case of error +int checkFile(std::string &_file); -extern "C" GZ_SIM_GZ_VISIBLE const char *worldInstallDir(); +/// \brief Parse the SDFormat file into a string +/// \param[in] _file Name of the SDFormat file +/// \param[in] _parsedSdfFile Assign the parsed string +/// \return 0 if successful, -1 in case of error +int parseSdfFile(const std::string &_file, std::string &_parsedSdfFile); + +/// \brief Find or download a fuel world provided a URL. +/// \param[in] _pathToResource Path to the fuel world resource, ie, +/// https://staging-fuel.gazebosim.org/1.0/gmas/worlds/ShapesClone +/// \return String containing the path to the local world sdf file +std::string findFuelResource(const std::string &_pathToResource); -/// \brief External hook to run simulation server. +/// \brief Create Gazebo server configuration +/// \param[in] _config Gazebo server config /// \param[in] _sdfString SDF file to run, as a string. -/// \param[in] _iterations --iterations option -/// \param[in] _run -r option /// \param[in] _hz -z option +/// \param[in] _initialSimTime --initial_sim_time option /// \param[in] _levels --levels option /// \param[in] _networkRole --network-role option /// \param[in] _networkSecondaries --network-secondaries option @@ -61,19 +72,24 @@ extern "C" GZ_SIM_GZ_VISIBLE const char *worldInstallDir(); /// \param[in] _headless True if server rendering should run headless /// \param[in] _recordPeriod --record-period option /// \param[in] _seed --seed value to be used for random number generator. -/// \return 0 if successful, 1 if not. -extern "C" GZ_SIM_GZ_VISIBLE int runServer(const char *_sdfString, - int _iterations, int _run, float _hz, double _initialSimTime, int _levels, - const char *_networkRole, int _networkSecondaries, int _record, - const char *_recordPath, int _recordResources, int _logOverwrite, - int _logCompress, const char *_playback, - const char *_physicsEngine, - const char *_renderEngineServer, const char *_renderEngineServerApiBackend, - const char *_renderEngineGui, const char *_renderEngineGuiApiBackend, - const char *_file, const char *_recordTopics, int _waitGui, int _headless, - float _recordPeriod, int _seed); +/// \return 0 if successful, -1 in case of failure +void createServerConfig(gz::sim::ServerConfig &_config, const char *_sdfString, + float _hz, double _initialSimTime, int _levels, + const char *_networkRole, int _networkSecondaries, + int _record, const char *_recordPath, + int _recordResources, int _logOverwrite, + int _logCompress, const char *_playback, + const char *_physicsEngine, + const char *_renderEngineServer, + const char *_renderEngineServerApiBackend, + const char *_renderEngineGui, + const char *_renderEngineGuiApiBackend, + const char *_file, + std::vector _recordTopics, int _waitGui, + int _headless, float _recordPeriod, int _seed); -/// \brief External hook to run simulation GUI. +#ifdef WITH_GUI +/// \brief Run simulation GUI. /// \param[in] _guiConfig Path to Gazebo GUI configuration file. /// \param[in] _file The world file path passed as a command line argument. /// If set, QuickStart Dialog will not be shown. @@ -81,18 +97,8 @@ extern "C" GZ_SIM_GZ_VISIBLE int runServer(const char *_sdfString, /// it receives a world path from GUI. /// \param[in] _renderEngine --render-engine-gui option /// \param[in] _renderEngineGuiApiBackend --render-engine-gui-api-backend option -/// \return 0 if successful, 1 if not. -extern "C" GZ_SIM_GZ_VISIBLE +/// \return 0 if successful, -1 in case of failure int runGui(const char *_guiConfig, const char *_file, - int _waitGui, - const char *_renderEngine, + int _waitGui, const char *_renderEngine, const char *_renderEngineGuiApiBackend); - -/// \brief External hook to find or download a fuel world provided a URL. -/// \param[in] _pathToResource Path to the fuel world resource, ie, -/// https://staging-fuel.gazebosim.org/1.0/gmas/worlds/ShapesClone -/// \return C-string containing the path to the local world sdf file -extern "C" GZ_SIM_GZ_VISIBLE const char *findFuelResource( - char *_pathToResource); - #endif diff --git a/src/cmd/gz_TEST.cc b/src/cmd/gz_TEST.cc index bea1235787..83145afb65 100644 --- a/src/cmd/gz_TEST.cc +++ b/src/cmd/gz_TEST.cc @@ -31,8 +31,7 @@ static const std::string kBinPath(PROJECT_BINARY_PATH); -static const std::string kGzCommand( - std::string(BREW_RUBY) + std::string(GZ_PATH) + " sim -s "); +static const std::string kGzCommand(std::string(GZ_PATH) + " sim -s"); ///////////////////////////////////////////////// std::string customExecStr(std::string _cmd) diff --git a/src/cmd/sim.yaml.in b/src/cmd/sim.yaml.in index 0e0b54d170..468e455590 100644 --- a/src/cmd/sim.yaml.in +++ b/src/cmd/sim.yaml.in @@ -2,7 +2,7 @@ format: 1.0.0 library_name: gz-sim-gz library_version: @PROJECT_VERSION_FULL@ -library_path: @gz_library_path@ +library_path: @gz_sim_ruby_path@ commands: - sim : Run and manage the Gazebo Simulator. --- diff --git a/src/cmd/sim_main.cc b/src/cmd/sim_main.cc new file mode 100644 index 0000000000..182b91e4a6 --- /dev/null +++ b/src/cmd/sim_main.cc @@ -0,0 +1,539 @@ +/* + * Copyright (C) 2025 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 + +#ifdef _WIN32 +#include +#endif + +#include +#include +#include +#include +#include + +#include "gz/sim/config.hh" +#include "gz/sim/Server.hh" +#include "gz/sim/ServerConfig.hh" +#include "gz.hh" + +using namespace gz; + +////////////////////////////////////////////////// +/// \brief Structure to hold all available launch options +struct SimOptions +{ + /// \brief Name of the SDFormat file + std::string file{""}; + + /// \brief Update rate in hertz + double rate{-1}; + + /// \brief Initial simulation time (in seconds) + double initialSimTime{0.0}; + + /// \brief Number of iterations to execute + int iterations{0}; + + /// \brief Levels system to display models + int levels{0}; + + /// \brief Participant role in distributed simulation environment + std::string networkRole{""}; + + /// \brief Number of expected secondary participants + int networkSecondaries{0}; + + /// \brief Record states and console + int record{0}; + + /// \brief Custom path to store recorded files + std::string recordPath{""}; + + /// \brief Records meshes and material files + int recordResources{0}; + + /// \brief List of topics to record + std::vector recordTopics{}; + + /// \brief Time period between state recording (in seconds) + float recordPeriod{-1}; + + /// \brief Overwrite existing files when recording + int logOverwrite{0}; + + /// \brief Compress final log when recording + int logCompress{0}; + + /// \brief Path to recorded states for playback + std::string playback{""}; + + /// \brief Run simulation on start + int runOnStart{0}; + + /// \brief Physics engine plugin + std::string physicsEngine{""}; + + /// \brief Render engine GUI plugin + std::string renderEngineGui{""}; + + /// \brief Render engine GUI API Backend + std::string renderEngineGuiApiBackend{""}; + + /// \brief Render engine Server plugin + std::string renderEngineServer{""}; + + /// \brief Render engine Server API Backend + std::string renderEngineServerApiBackend{""}; + + /// \brief Enable headless rendering + int headlessRendering{0}; + + /// \brief Show the world loading menu + int waitGui{1}; + + /// \brief Custom seed to random value generator + int seed{0}; + + /// \brief Path to GUI configuration file + std::string guiConfig{""}; + + /// \brief Flag to launch the Server + bool launchServer{false}; + + /// \brief Flag to launch the GUI + bool launchGui{false}; +}; + +#ifdef WITH_GUI +////////////////////////////////////////////////// +/// \brief Launch an executable as a separate process +int launchProcess( + const std::string &_executable, + std::vector _args) +{ + std::ostringstream command; + command << _executable; + for(const auto &val : _args) + { + command << " " << val; + } + + #ifdef _WIN32 + STARTUPINFO si; + PROCESS_INFORMATION pi; + + ZeroMemory(&si, sizeof(si)); + si.cb = sizeof(si); + + ZeroMemory(&pi, sizeof(pi)); + + if(!CreateProcess(NULL, const_cast(command.str().c_str()), + NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) + { + return -1; + } + + WaitForSingleObject(pi.hProcess, INFINITE); + + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + + return 0; + #else + return std::system(command.str().c_str()); + #endif +} + +////////////////////////////////////////////////// +/// \brief Generate list of arguments for GUI command +std::vector createGuiCommand( + std::shared_ptr _opt) +{ + std::vector args; + + if(!_opt->renderEngineGui.empty()) + { + args.push_back("--render-engine-gui"); + args.push_back(_opt->renderEngineGui); + } + + if(!_opt->renderEngineGuiApiBackend.empty()) + { + args.push_back("--render-engine-gui-api-backend"); + args.push_back(_opt->renderEngineGuiApiBackend); + } + + if(!_opt->guiConfig.empty()) + { + args.push_back("--gui-config"); + args.push_back(_opt->guiConfig); + } + + if(!_opt->file.empty()) + { + args.push_back(_opt->file); + } + + return args; +} +#endif + +////////////////////////////////////////////////// +void addSimFlags(CLI::App &_app, std::shared_ptr _opt) +{ + _app.add_option("file", _opt->file, + "Name of the SDFormat file."); + + _app.add_option("--initial-sim-time", _opt->initialSimTime, + "Initial simulation time, in seconds."); + + _app.add_option("--iterations", _opt->iterations, + "Number of iterations to execute."); + + _app.add_flag("--levels", _opt->levels, + "Use the level system. " + "The default is false which loads all models. \n" + "It is always true with --network-role."); + + auto networkRoleOpt = _app.add_option_function("--network-role", + [_opt](const std::string &_networkRole){ + _opt->levels = 1; + _opt->networkRole = _networkRole; + }, + "Participant role used in distributed simulation environment.\n") + ->check(CLI::IsMember({"primary", "secondary"})); + + _app.add_option_function("--network-secondaries", + [_opt](const int _networkSecondaries){ + if(_opt->networkRole != "primary") { + throw CLI::ValidationError( + "--network-secondaries", + "Can only be used when --network-role primary."); + } + _opt->networkSecondaries = _networkSecondaries; + }, + "Number of secondary participants expected\n" + "to join a distributed simulation environment.") + ->needs(networkRoleOpt); + + _app.add_flag("--record", _opt->record, + "Use logging system to record states and console\n" + "messages to the default location, ~/.gz/sim/log."); + + _app.add_option_function("--record-path", + [_opt](const std::string &_recordPath){ + _opt->record = 1; + _opt->recordPath = _recordPath; + }, + "Specifies a custom path to store recorded files.\n" + "This will enable console logging to a console.log\n" + "file in the specified path.\n" + "Note: Implicitly invokes --record"); + + _app.add_flag_callback("--record-resources", + [_opt](){ + _opt->record = 1; + _opt->recordResources = 1; + }, + "Records meshes and material files in addition to\n" + "states and console messages.\n" + "Note: Implicitly invokes --record"); + + _app.add_option_function>("--record-topic", + [_opt](const std::vector &_recordTopics){ + _opt->record = 1; + _opt->recordTopics = _recordTopics; + }, + "Specify the name of an additional topic to record. Zero or\n" + "more topics can be specified by using multiple --record-topic\n" + "options. Regular expressions can be used, likely requiring quotes.\n" + "A default set of topics are also recorded, which support simulation\n" + "state feedback. Enable debug console output with the -v 4 option and\n" + "look for 'Recording default topic' in order to determine the default\n" + "set of topics.\n" + "Examples:\n" + " 1. Record all topics.\n" + " --record-topic \".*\"\n" + " 2. Record only the /stats topic.\n" + " --record-topic /stats\n" + " 3. Record the /stats and /clock topics.\n" + " --record-topic /stats --record-topic /clock\n" + "Note: Implicitly invokes --record."); + + _app.add_option("--record-period", _opt->recordPeriod, + "Specify the time period (seconds) between\n" + "state recording."); + + _app.add_flag("--log-overwrite", _opt->logOverwrite, + "Overwrite existing files when recording.\n" + "Only valid if recording is enabled."); + + _app.add_flag("--log-compress", _opt->logCompress, + "Compress final log when recording.\n" + "Only valid if recording is enabled."); + + _app.add_option("--seed", _opt->seed, + "Pass a custom seed value to the random\n" + "number generator."); + + _app.add_option("--physics-engine", _opt->physicsEngine, + "Gazebo Physics engine plugin to load. Gazebo\n" + "will use DART by default (gz-physics-dartsim-plugin)\n" + "Make sure custom plugins are inside\n" + "GZ_SIM_PHYSICS_ENGINE_PATH."); + + _app.add_option("--render-engine-gui", _opt->renderEngineGui, + "Gazebo Rendering engine plugin to load for the GUI.\n" + "Gazebo will use OGRE2 by default. Make sure custom\n" + "plugins are in GZ_SIM_RENDER_ENGINE_PATH.") + ->default_str("ogre2"); + + _app.add_option("--render-engine-gui-api-backend", + _opt->renderEngineGuiApiBackend, + "Same as --render-engine-api-backend but only\n" + "for the GUI.") + ->check(CLI::IsMember({"opengl", "vulkan", "metal"})); + + _app.add_option("--render-engine-server", _opt->renderEngineServer, + "Gazebo Rendering engine plugin to load for the Server.\n" + "Gazebo will use OGRE2 by default. Make sure custom\n" + "plugins are in GZ_SIM_RENDER_ENGINE_PATH.") + ->default_str("ogre2"); + + _app.add_option("--render-engine-server-api-backend", + _opt->renderEngineServerApiBackend, + "Same as --render-engine-api-backend but only\n" + "for the Server.") + ->check(CLI::IsMember({"opengl", "vulkan", "metal"})); + + _app.add_option_function("--render-engine", + [_opt](const std::string &_renderEngine){ + _opt->renderEngineGui = _renderEngine; + _opt->renderEngineServer = _renderEngine; + }, + "Gazebo Rendering engine plugin to load for both the Server\n" + "and the GUI. Gazebo will use OGRE2 by default.\n" + "Make sure custom plugins are inside\n" + "GZ_SIM_RENDER_ENGINE_PATH.") + ->default_str("ogre2"); + + _app.add_option_function("--render-engine-api-backend", + [_opt](const std::string &_renderEngineApiBackend){ + _opt->renderEngineGuiApiBackend = _renderEngineApiBackend; + _opt->renderEngineServerApiBackend = _renderEngineApiBackend; + }, + "API to use for both Server and GUI.\n" + "Possible values for ogre2:\n" + " - opengl (default)\n" + " - vulkan (beta)\n" + " - metal (Apple only. Default for Apple)\n" + "Note: If Vulkan is being in the GUI and gz-gui was\n" + "built against Qt < 5.15.2, it may be very slow") + ->check(CLI::IsMember({"opengl", "vulkan", "metal"})); + + _app.add_flag("--headless-rendering", _opt->headlessRendering, + "Run rendering in headless mode."); + + _app.add_flag("-r", _opt->runOnStart, + "Run simulation on start."); + + _app.add_option("-z", _opt->rate, + "Update rate in hertz."); + + _app.add_option("--gui-config", _opt->guiConfig, + "Gazebo GUI configuration file to load.\n" + "If no file is provided then the configuration in\n" + "SDF file is used. If that is also missing then\n" + "the default installed configuration is used."); + + _app.add_option_function("--playback", + [_opt](const std::string &_playback){ + _opt->playback = _playback; + + if(_opt->guiConfig.empty()) + { + _opt->guiConfig = "_playback_"; + } + }, + "Use the logging system to play back states.\n" + "Argument is the path to recorded states."); +} + +////////////////////////////////////////////////// +int main(int argc, char** argv) +{ + CLI::App app{"Run and manage Gazebo simulations."}; + + auto opt = std::make_shared(); + + app.add_flag_callback("--version", + [](){ + std::cout << GZ_SIM_VERSION_FULL << std::endl; + throw CLI::Success(); + }, + "Print the current library version."); + + app.add_option_function("-v,--verbose", + [](const int _verbosity){ + utils::setenv( + std::string("GZ_SIM_VERBOSITY"), + std::to_string(_verbosity)); + + cmdVerbosity(_verbosity); + }, + "Adjust the level of console output (0~4).\n" + "The default verbosity level is 1. Use -v\n" + "without arguments for level 3.\n") + ->expected(0, 1) + ->default_val(3); + + // Dummy flags handled by gz-tools + app.add_flag("--force-version", "Use a particular library version."); + app.add_flag("--versions", "Show the available versions."); + + app.add_flag("-g", opt->launchGui, "Run and manage only the Gazebo GUI"); + + app.add_flag_callback("-s", + [opt]{ + opt->launchServer = true; + opt->launchGui = false; + opt->waitGui = 0; + }, + "Run and manage only the Gazebo Server (headless mode).\n" + "This overrides -g, if it is also present."); + + app.footer("Environment variables:\n" + " GZ_SIM_RESOURCE_PATH Colon separated paths used to locate\n" + " resources such as worlds and models.\n" + " GZ_SIM_SYSTEM_PLUGIN_PATH Colon separated paths used to locate\n" + " system plugins.\n" + " GZ_SIM_SERVER_CONFIG_PATH Path to server configuration file.\n" + " GZ_GUI_PLUGIN_PATH Colon separated paths used to locate\n" + " GUI plugins.\n" + " GZ_GUI_RESOURCE_PATH Colon separated paths used to locate\n" + " resources such as configuration files."); + + addSimFlags(app, opt); + + app.formatter(std::make_shared(&app)); + CLI11_PARSE(app, argc, argv); + + if(!opt->launchServer && !opt->launchGui) + { + // Check SDF file and parse into string + if(checkFile(opt->file) < 0) + return -1; + + std::string parsedSdfFile; + if(parseSdfFile(opt->file, parsedSdfFile) < 0) + return -1; + + bool blocking = true; + opt->waitGui = 0; + + #ifdef WITH_GUI + // Launch the GUI in a separate thread + std::thread guiThread( + [opt]{ + launchProcess(std::string(GZ_SIM_GUI_EXE), createGuiCommand(opt)); + }); + + blocking = false; + opt->waitGui = 1; + #endif + + // Create a Gazebo server configuration + sim::ServerConfig serverConfig; + createServerConfig(serverConfig, parsedSdfFile.c_str(), + opt->rate, opt->initialSimTime, opt->levels, + opt->networkRole.c_str(), opt->networkSecondaries, + opt->record, opt->recordPath.c_str(), opt->recordResources, + opt->logOverwrite, opt->logCompress, opt->playback.c_str(), + opt->physicsEngine.c_str(), opt->renderEngineServer.c_str(), + opt->renderEngineServerApiBackend.c_str(), + opt->renderEngineGui.c_str(), + opt->renderEngineGuiApiBackend.c_str(), opt->file.c_str(), + opt->recordTopics, opt->waitGui, opt->headlessRendering, + opt->recordPeriod, opt->seed); + + // Run the server in a separate thread + sim::Server server(serverConfig); + server.Run(blocking, opt->iterations, opt->runOnStart == 0); + + #ifdef WITH_GUI + // Join the GUI thread to wait for a possible window close + guiThread.join(); + + // Shutdown server if the GUI has been closed from the screen + if(server.Running()) + { + server.Stop(); + } + #endif + + gzdbg << "Shutting down gz-sim-server" << std::endl; + } + else + { + if(opt->launchServer) + { + // Check SDF file and parse into string + if(checkFile(opt->file) < 0) + return -1; + + std::string parsedSdfFile; + if(parseSdfFile(opt->file, parsedSdfFile) < 0) + return -1; + + // Create a Gazebo server configuration + sim::ServerConfig serverConfig; + createServerConfig(serverConfig, parsedSdfFile.c_str(), + opt->rate, opt->initialSimTime, opt->levels, + opt->networkRole.c_str(), opt->networkSecondaries, + opt->record, opt->recordPath.c_str(), opt->recordResources, + opt->logOverwrite, opt->logCompress, opt->playback.c_str(), + opt->physicsEngine.c_str(), opt->renderEngineServer.c_str(), + opt->renderEngineServerApiBackend.c_str(), + opt->renderEngineGui.c_str(), + opt->renderEngineGuiApiBackend.c_str(), opt->file.c_str(), + opt->recordTopics, opt->waitGui, opt->headlessRendering, + opt->recordPeriod, opt->seed); + + // Run the server in the main thread + sim::Server server(serverConfig); + server.Run(true, opt->iterations, opt->runOnStart == 0); + + gzdbg << "Shutting down gz-sim-server" << std::endl; + } + else if(opt->launchGui) + { + #ifdef WITH_GUI + utils::setenv(std::string("GZ_SIM_WAIT_GUI"), std::to_string(0)); + launchProcess(std::string(GZ_SIM_GUI_EXE), createGuiCommand(opt)); + #else + std::cerr << "This version of Gazebo does not support GUI" << std::endl; + #endif + } + } + + return 0; +} diff --git a/src/systems/physics/CMakeLists.txt b/src/systems/physics/CMakeLists.txt index 006fe39fa4..78b16f2dad 100644 --- a/src/systems/physics/CMakeLists.txt +++ b/src/systems/physics/CMakeLists.txt @@ -27,6 +27,7 @@ gz_build_tests(TYPE UNIT ${gtest_sources} LIB_DEPS gz-physics${GZ_PHYSICS_VER}::core + gz-plugin${GZ_PLUGIN_VER}::loader ENVIRONMENT GZ_SIM_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX} ) diff --git a/test/performance/CMakeLists.txt b/test/performance/CMakeLists.txt index b43c3cba4f..5ed05aef0c 100644 --- a/test/performance/CMakeLists.txt +++ b/test/performance/CMakeLists.txt @@ -54,7 +54,6 @@ target_link_libraries( PERFORMANCE_${exec} gz-common${GZ_COMMON_VER}::gz-common${GZ_COMMON_VER} gz-sim${PROJECT_VERSION_MAJOR} - gz-sim${PROJECT_VERSION_MAJOR}-gui ) if(VALID_DISPLAY AND VALID_DRI_DISPLAY AND TARGET PERFORMANCE_sensors_system)