diff --git a/.github/ci/before_cmake.sh b/.github/ci/before_cmake.sh new file mode 100644 index 000000000..123af216d --- /dev/null +++ b/.github/ci/before_cmake.sh @@ -0,0 +1,56 @@ +#!/bin/sh -l + +set -x + +BUILD_DIR=`pwd` + +cd /tmp + +# check that we can compile USD from sources (only Focal) +mkdir cmake_test +cd cmake_test + +echo "cmake_minimum_required(VERSION 3.12)" > CMakeLists.txt + +return_code=0 +cmake . || return_code=$(($return_code + $?)) +if [ $return_code -eq 0 ] +then + # compile USD from sources + cd /tmp + mkdir usd_binaries + cd usd_binaries + + apt-get install libboost-all-dev libtbb-dev p7zip-full -y + + wget https://github.com/PixarAnimationStudios/USD/archive/refs/tags/v21.11.zip + unzip v21.11.zip + cd USD-21.11 + mkdir build + cd build + + cmake -DCMAKE_INSTALL_PREFIX="/tmp/USD" -DCMAKE_PREFIX_PATH="/tmp/USD" \ + -DCMAKE_BUILD_TYPE=Release \ + -DPXR_PREFER_SAFETY_OVER_SPEED=ON \ + -DPXR_ENABLE_PYTHON_SUPPORT=OFF \ + -DBUILD_SHARED_LIBS=ON \ + -DTBB_USE_DEBUG_BUILD=OFF \ + -DPXR_BUILD_DOCUMENTATION=OFF \ + -DPXR_BUILD_TESTS=OFF \ + -DPXR_BUILD_EXAMPLES=OFF \ + -DPXR_BUILD_TUTORIALS=OFF \ + -DPXR_BUILD_USD_TOOLS=OFF \ + -DPXR_BUILD_IMAGING=OFF \ + -DPXR_BUILD_USD_IMAGING=OFF \ + -DPXR_BUILD_USDVIEW=OFF \ + -DPXR_BUILD_ALEMBIC_PLUGIN=OFF \ + -DPXR_BUILD_DRACO_PLUGIN=OFF \ + -DPXR_ENABLE_MATERIALX_SUPPORT=OFF \ + -DBoost_INCLUDE_DIR=/usr/include \ + -DBoost_NO_BOOST_CMAKE=FALSE \ + .. + + make -j$(nproc) install +fi + +cd $BUILD_DIR diff --git a/.github/ci/packages.apt b/.github/ci/packages.apt index 2ea67b09f..09308b7f2 100644 --- a/.github/ci/packages.apt +++ b/.github/ci/packages.apt @@ -1,7 +1,9 @@ libignition-cmake2-dev +libignition-common4-dev libignition-math6-dev libignition-tools-dev libignition-utils1-dev +libignition-utils1-cli-dev libtinyxml2-dev liburdfdom-dev libxml2-utils diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2914eb5d5..277bab16a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,6 +18,11 @@ jobs: runs-on: ubuntu-latest name: Ubuntu Focal CI steps: + - name: Set env + run: | + echo "PATH=$PATH:/tmp/USD/bin" >> $GITHUB_ENV + echo "LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/tmp/USD/lib" >> $GITHUB_ENV + echo "CMAKE_PREFIX_PATH=$CMAKE_PREFIX_PATH:/tmp/USD" >> $GITHUB_ENV - name: Checkout uses: actions/checkout@v2 - name: Compile and test diff --git a/CMakeLists.txt b/CMakeLists.txt index 5ff7e0038..1b69c0e65 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -109,8 +109,18 @@ if (BUILD_SDF) ign_find_package(ignition-utils1 VERSION REQUIRED) set(IGN_UTILS_VER ${ignition-utils1_VERSION_MAJOR}) + ######################################## + # Find ignition common + ign_find_package(ignition-common4 COMPONENTS graphics REQUIRED_BY usd) + set(IGN_COMMON_VER ${ignition-common4_VERSION_MAJOR}) + + ######################################## + # Find PXR + ign_find_package(pxr QUIET REQUIRED_BY usd PKGCONFIG pxr) + + ign_configure_build(HIDE_SYMBOLS_BY_DEFAULT QUIT_IF_BUILD_ERRORS + COMPONENTS usd) - ign_configure_build(HIDE_SYMBOLS_BY_DEFAULT QUIT_IF_BUILD_ERRORS) ign_create_packages() add_subdirectory(sdf) diff --git a/examples/usdConverter/README.md b/examples/usdConverter/README.md new file mode 100644 index 000000000..924944959 --- /dev/null +++ b/examples/usdConverter/README.md @@ -0,0 +1,63 @@ +# Converting between SDF and USD + +This example shows how a world in a SDF file can be converted to [USD](https://graphics.pixar.com/usd/release/index.html). + +## Requirements + +You will need all of the dependencies for sdformat, along with the following additional dependencies: +* USD: [installation instructions](https://github.com/PixarAnimationStudios/USD/blob/release/README.md#getting-and-building-the-code) +* [ignition-common4](https://github.com/ignitionrobotics/ign-common) +* [ignition-utils1 (including the CLI component)](https://github.com/ignitionrobotics/ign-utils) + +## Setup + +Build sdformat. The steps below follow a traditional cmake build, but sdformat +can also be built with [colcon](https://colcon.readthedocs.io/en/released/index.html): +```bash +git clone https://github.com/ignitionrobotics/sdformat.git +cd sdformat +mkdir build +cd build +cmake .. +make +``` + +You should now have an executable named `sdf2usd` in the `sdformat/build/bin` directory. +This executable can be used to convert a SDF world file to a USD file. +To see how the executable works, run the following command from the `sdformat/build/bin` directory: +```bash +./sdf2usd -h +``` + +To convert [shapes_world.sdf](https://github.com/ignitionrobotics/sdformat/blob/sdf12/test/sdf/shapes_world.sdf) to its USD representation as a file called `shapes.usd`, run the following commands: + +```bash +wget https://raw.githubusercontent.com/ignitionrobotics/sdformat/sdf12/test/sdf/shapes_world.sdf +./sdf2usd shapes_world.sdf shapes.usd +``` + +You can now view the contents of the generated USD file with `usdcat` (this should have been installed when setting up the USD dependency): +```bash +usdcat shapes.usd +``` + +To see the visual representation of the USD world, run `usdview` (this should have also been installed when setting up the USD dependency): +```bash +usdview shapes.usd +``` + +### Note about building with colcon +You may need to add the USD library path to your `LD_LIBRARY_PATH` environment variable after sourcing the colcon workspace. +If the USD library path is not a part of `LD_LIBRARY_PATH`, you will probably see the following error when running the `sdf2usd` executable: +```bash +sdf2usd: error while loading shared libraries: libusd_usd.so: cannot open shared object file: No such file or directory +``` +The typical USD library path is `/lib`. +So, if you installed USD at `/usr/local/USD`, the following command on Linux properly updates the `LD_LIBRARY_PATH` environment variable: +```bash +export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/USD/lib +``` + +Another thing to note if building with colcon is that after sourcing the workspace with sdformat, +the `sdf2usd` executable can be run without having to go to the `sdformat/build/bin` directory. +So, instead of going to that directory and running `./sdf2usd ...`, you should be able to run `sdf2usd ...` from anywhere. diff --git a/test/test_utils.hh b/test/test_utils.hh index 150f70b60..df8e9c43f 100644 --- a/test/test_utils.hh +++ b/test/test_utils.hh @@ -18,7 +18,10 @@ #define SDF_TEST_UTILS_HH_ #include +#include + #include "sdf/Console.hh" +#include "sdf/Root.hh" namespace sdf { @@ -104,6 +107,25 @@ class RedirectConsoleStream private: sdf::Console::ConsoleStream oldStream; }; +/// \brief Load an SDF file into a sdf::Root object +/// \param[in] _fileName The name of the file to load +/// \param[in] _root The sdf::Root object to load the file into +/// \return True if a file named _fileName was successfully loaded into +/// _root. False otherwise +bool LoadSdfFile(const std::string &_fileName, sdf::Root &_root) +{ + auto errors = _root.Load(_fileName); + if (!errors.empty()) + { + std::cerr << "Errors encountered:\n"; + for (const auto &e : errors) + std::cerr << e << "\n"; + return false; + } + + return true; +} + } // namespace testing } // namespace sdf diff --git a/usd/include/sdf/CMakeLists.txt b/usd/include/sdf/CMakeLists.txt new file mode 100644 index 000000000..f2909dd9d --- /dev/null +++ b/usd/include/sdf/CMakeLists.txt @@ -0,0 +1 @@ +ign_install_all_headers(COMPONENT usd) diff --git a/usd/include/sdf/usd/sdf_parser/World.hh b/usd/include/sdf/usd/sdf_parser/World.hh new file mode 100644 index 000000000..f5c7d8ccd --- /dev/null +++ b/usd/include/sdf/usd/sdf_parser/World.hh @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2022 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. + * +*/ + +#ifndef SDF_USD_SDF_PARSER_WORLD_HH_ +#define SDF_USD_SDF_PARSER_WORLD_HH_ + +#include + +// TODO(ahcorde):this is to remove deprecated "warnings" in usd, these warnings +// are reported using #pragma message so normal diagnostic flags cannot remove +// them. This workaround requires this block to be used whenever usd is +// included. +#pragma push_macro ("__DEPRECATED") +#undef __DEPRECATED +#include +#pragma pop_macro ("__DEPRECATED") + +#include "sdf/config.hh" +#include "sdf/system_util.hh" +#include "sdf/World.hh" + +namespace sdf +{ + // Inline bracke to help doxygen filtering. + inline namespace SDF_VERSION_NAMESPACE { + // + namespace usd + { + /// \brief Parse an SDF world into a USD stage. + /// \param[in] _world The SDF world to parse. + /// \param[in] _stage The stage that should contain the USD representation + /// of _world. It must be initialized first + /// \param[in] _path The USD path of the parsed world in _stage, which must be + /// a valid USD path. + /// \return Errors, which is a vector of Error objects. Each Error includes + /// an error code and message. An empty vector indicates no error. + sdf::Errors SDFORMAT_VISIBLE ParseSdfWorld(const sdf::World &_world, + pxr::UsdStageRefPtr &_stage, const std::string &_path); + } + } +} + +#endif diff --git a/usd/src/CMakeLists.txt b/usd/src/CMakeLists.txt new file mode 100644 index 000000000..cf9ccb0ff --- /dev/null +++ b/usd/src/CMakeLists.txt @@ -0,0 +1,31 @@ +set(sources + sdf_parser/World.cc +) + +ign_add_component(usd SOURCES ${sources} GET_TARGET_NAME usd_target) + +target_include_directories(${usd_target} + PUBLIC + ${PXR_INCLUDE_DIRS} +) + +target_link_libraries(${usd_target} + PUBLIC + ignition-common${IGN_COMMON_VER}::ignition-common${IGN_COMMON_VER} + ${PXR_LIBRARIES} +) + +set(gtest_sources + sdf_parser/sdf2usd_TEST.cc + sdf_parser/World_Sdf2Usd_TEST.cc +) + +# Build the unit tests +ign_build_tests( + TYPE UNIT + SOURCES ${gtest_sources} + LIB_DEPS ${usd_target} ignition-cmake${IGN_CMAKE_VER}::utilities + INCLUDE_DIRS ${PROJECT_SOURCE_DIR}/test +) + +add_subdirectory(cmd) diff --git a/usd/src/cmd/CMakeLists.txt b/usd/src/cmd/CMakeLists.txt new file mode 100644 index 000000000..46b6ca8a3 --- /dev/null +++ b/usd/src/cmd/CMakeLists.txt @@ -0,0 +1,18 @@ +if(TARGET ${usd_target}) + add_executable(sdf2usd + sdf2usd.cc + ) + + target_link_libraries(sdf2usd + PUBLIC + ignition-utils${IGN_UTILS_VER}::ignition-utils${IGN_UTILS_VER} + ${usd_target} + ) + + install( + TARGETS + sdf2usd + DESTINATION + ${BIN_INSTALL_DIR} + ) +endif() diff --git a/usd/src/cmd/sdf2usd.cc b/usd/src/cmd/sdf2usd.cc new file mode 100644 index 000000000..c6f03353a --- /dev/null +++ b/usd/src/cmd/sdf2usd.cc @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2022 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 + +// TODO(ahcorde):this is to remove deprecated "warnings" in usd, these warnings +// are reported using #pragma message so normal diagnostic flags cannot remove +// them. This workaround requires this block to be used whenever usd is +// included. +#include +#pragma push_macro ("__DEPRECATED") +#undef __DEPRECATED +#include +#pragma pop_macro ("__DEPRECATED") + +#include "sdf/sdf.hh" +#include "sdf/usd/sdf_parser/World.hh" + +////////////////////////////////////////////////// +/// \brief Enumeration of available commands +enum class Command +{ + kNone, +}; + +////////////////////////////////////////////////// +/// \brief Structure to hold all available topic options +struct Options +{ + /// \brief Command to execute + Command command{Command::kNone}; + + /// \brief input filename + std::string inputFilename{"input.sdf"}; + + /// \brief output filename + std::string outputFilename{"output.usd"}; +}; + +void runCommand(const Options &_opt) +{ + sdf::Root root; + auto errors = root.Load(_opt.inputFilename); + if (!errors.empty()) + { + std::cerr << "Errors encountered:\n"; + for (const auto &e : errors) + std::cout << e << "\n"; + exit(-2); + } + + // only support SDF files with exactly 1 world for now + if (root.WorldCount() != 1u) + { + std::cerr << _opt.inputFilename << " does not have exactly 1 world\n"; + exit(-3); + } + + auto world = root.WorldByIndex(0u); + if (!world) + { + std::cerr << "Error retrieving the world from " + << _opt.inputFilename << "\n"; + exit(-4); + } + + auto stage = pxr::UsdStage::CreateInMemory(); + + const auto worldPath = std::string("/" + world->Name()); + auto usdErrors = sdf::usd::ParseSdfWorld(*world, stage, worldPath); + if (!usdErrors.empty()) + { + std::cerr << "The following errors occurred when parsing world [" + << world->Name() << "]\n:"; + for (const auto &e : errors) + std::cout << e << "\n"; + exit(-5); + } + + if (!stage->GetRootLayer()->Export(_opt.outputFilename)) + { + std::cerr << "Issue saving USD to " << _opt.outputFilename << "\n"; + exit(-6); + } +} + +void addFlags(CLI::App &_app) +{ + auto opt = std::make_shared(); + + _app.add_option("input", + opt->inputFilename, + "Input filename. Defaults to input.sdf unless otherwise specified."); + + _app.add_option("output", + opt->outputFilename, + "Output filename. Defaults to output.usd unless otherwise specified."); + + _app.callback([&_app, opt](){ + runCommand(*opt); + }); +} + +////////////////////////////////////////////////// +int main(int argc, char** argv) +{ + CLI::App app{"SDF to USD converter"}; + + app.set_help_all_flag("--help-all", "Show all help"); + + app.add_flag_callback("--version", [](){ + std::cout << SDF_VERSION_FULL << std::endl; + throw CLI::Success(); + }); + + addFlags(app); + CLI11_PARSE(app, argc, argv); + + return 0; +} diff --git a/usd/src/sdf_parser/World.cc b/usd/src/sdf_parser/World.cc new file mode 100644 index 000000000..390e3cefd --- /dev/null +++ b/usd/src/sdf_parser/World.cc @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2022 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 "sdf/usd/sdf_parser/World.hh" + +#include +#include + +// TODO(ahcorde):this is to remove deprecated "warnings" in usd, these warnings +// are reported using #pragma message so normal diagnostic flags cannot remove +// them. This workaround requires this block to be used whenever usd is +// included. +#pragma push_macro ("__DEPRECATED") +#undef __DEPRECATED +#include +#include +#include +#include +#include +#include +#pragma pop_macro ("__DEPRECATED") + +#include "sdf/World.hh" + +namespace sdf +{ +// Inline bracke to help doxygen filtering. +inline namespace SDF_VERSION_NAMESPACE { +// +namespace usd +{ + sdf::Errors ParseSdfWorld(const sdf::World &_world, pxr::UsdStageRefPtr &_stage, + const std::string &_path) + { + sdf::Errors errors; + _stage->SetMetadata(pxr::UsdGeomTokens->upAxis, pxr::UsdGeomTokens->z); + _stage->SetEndTimeCode(100); + _stage->SetMetadata(pxr::TfToken("metersPerUnit"), 1.0); + _stage->SetStartTimeCode(0); + _stage->SetTimeCodesPerSecond(24); + + const pxr::SdfPath worldPrimPath(_path); + auto usdWorldPrim = _stage->DefinePrim(worldPrimPath); + + auto usdPhysics = pxr::UsdPhysicsScene::Define(_stage, + pxr::SdfPath(_path + "/physics")); + const auto &sdfWorldGravity = _world.Gravity(); + const auto normalizedGravity = sdfWorldGravity.Normalized(); + usdPhysics.CreateGravityDirectionAttr().Set(pxr::GfVec3f( + normalizedGravity.X(), normalizedGravity.Y(), normalizedGravity.Z())); + usdPhysics.CreateGravityMagnitudeAttr().Set( + static_cast(sdfWorldGravity.Length())); + + // TODO(ahcorde) Add parser + std::cerr << "Parser for a sdf world only parses physics information at " + << "the moment. Models and lights that are children of the world " + << "are currently being ignored.\n"; + + return errors; + } +} +} +} diff --git a/usd/src/sdf_parser/World_Sdf2Usd_TEST.cc b/usd/src/sdf_parser/World_Sdf2Usd_TEST.cc new file mode 100644 index 000000000..189276456 --- /dev/null +++ b/usd/src/sdf_parser/World_Sdf2Usd_TEST.cc @@ -0,0 +1,96 @@ +/* + * Copyright 2022 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 + +// TODO(ahcorde):this is to remove deprecated "warnings" in usd, these warnings +// are reported using #pragma message so normal diagnostic flags cannot remove +// them. This workaround requires this block to be used whenever usd is +// included. +#pragma push_macro ("__DEPRECATED") +#undef __DEPRECATED +#include +#include +#include +#include +#include +#include +#include +#pragma pop_macro ("__DEPRECATED") + +#include "sdf/usd/sdf_parser/World.hh" +#include "sdf/Root.hh" +#include "test_config.h" +#include "test_utils.hh" + +///////////////////////////////////////////////// +// Fixture that creates a USD stage for each test case. +class UsdStageFixture : public::testing::Test +{ + public: UsdStageFixture() = default; + + protected: void SetUp() override + { + this->stage = pxr::UsdStage::CreateInMemory(); + ASSERT_TRUE(this->stage); + } + + public: pxr::UsdStageRefPtr stage; +}; + +///////////////////////////////////////////////// +TEST_F(UsdStageFixture, World) +{ + const auto path = sdf::testing::TestFile("sdf", "empty.sdf"); + sdf::Root root; + + ASSERT_TRUE(sdf::testing::LoadSdfFile(path, root)); + ASSERT_EQ(1u, root.WorldCount()); + auto world = root.WorldByIndex(0u); + + const auto worldPath = std::string("/" + world->Name()); + auto usdErrors = sdf::usd::ParseSdfWorld(*world, stage, worldPath); + EXPECT_TRUE(usdErrors.empty()); + + // check top-level stage information + EXPECT_DOUBLE_EQ(100.0, this->stage->GetEndTimeCode()); + EXPECT_DOUBLE_EQ(0.0, this->stage->GetStartTimeCode()); + EXPECT_DOUBLE_EQ(24.0, this->stage->GetTimeCodesPerSecond()); + pxr::TfToken upAxisVal; + EXPECT_TRUE(this->stage->GetMetadata(pxr::UsdGeomTokens->upAxis, &upAxisVal)); + EXPECT_EQ(pxr::UsdGeomTokens->z, upAxisVal); + double metersPerUnitVal; + EXPECT_TRUE(this->stage->GetMetadata(pxr::TfToken("metersPerUnit"), + &metersPerUnitVal)); + EXPECT_DOUBLE_EQ(1.0, metersPerUnitVal); + + // Check that world prim exists, and that things like physics information + // were parsed correctly + auto worldPrim = this->stage->GetPrimAtPath(pxr::SdfPath(worldPath)); + ASSERT_TRUE(worldPrim); + auto physicsScene = pxr::UsdPhysicsScene::Get(this->stage, + pxr::SdfPath(worldPath + "/physics")); + ASSERT_TRUE(physicsScene); + pxr::GfVec3f gravityDirectionVal; + EXPECT_TRUE(physicsScene.GetGravityDirectionAttr().Get(&gravityDirectionVal)); + EXPECT_EQ(gravityDirectionVal, pxr::GfVec3f(0, 0, -1)); + float gravityMagnitudeVal; + EXPECT_TRUE(physicsScene.GetGravityMagnitudeAttr().Get(&gravityMagnitudeVal)); + EXPECT_FLOAT_EQ(gravityMagnitudeVal, 9.8f); +} diff --git a/usd/src/sdf_parser/sdf2usd_TEST.cc b/usd/src/sdf_parser/sdf2usd_TEST.cc new file mode 100644 index 000000000..d532172dc --- /dev/null +++ b/usd/src/sdf_parser/sdf2usd_TEST.cc @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2022 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 + +#include "test_config.h" +#include "test_utils.hh" + +#ifdef _WIN32 + #define popen _popen + #define pclose _pclose +#endif + +static std::string sdf2usdCommand() +{ + return ignition::common::joinPaths(std::string(PROJECT_BINARY_DIR), "bin", + "sdf2usd"); +} + +///////////////////////////////////////////////// +std::string custom_exec_str(std::string _cmd) +{ + _cmd += " 2>&1"; + FILE *pipe = popen(_cmd.c_str(), "r"); + + if (!pipe) + return "ERROR"; + + char buffer[128]; + std::string result = ""; + + while (!feof(pipe)) + { + if (fgets(buffer, 128, pipe) != NULL) + result += buffer; + } + + pclose(pipe); + return result; +} + +///////////////////////////////////////////////// +TEST(check_cmd, IGN_UTILS_TEST_DISABLED_ON_WIN32(SDF)) +{ + std::string pathBase = PROJECT_SOURCE_PATH; + pathBase = ignition::common::joinPaths(pathBase, "test", "sdf"); + + auto tmpDir = ignition::common::tempDirectoryPath(); + auto tmp = ignition::common::createTempDirectory("usd", tmpDir); + // Check a good SDF file + { + std::string path = ignition::common::joinPaths(pathBase, + "shapes_world.sdf"); + const auto outputUsdFilePath = + ignition::common::joinPaths(tmp, "shapes.usd"); + EXPECT_FALSE(ignition::common::isFile(outputUsdFilePath)); + std::string output = + custom_exec_str(sdf2usdCommand() + " " + path + " " + outputUsdFilePath); + // TODO(adlarkin) make sure 'output' (i.e., the result of running the + // sdf2usd executable) is an empty string once the usd2sdf parser is fully + // implemented (right now, running the parser outputs an error indicating + // that functionality isn't complete) + + // make sure that a shapes.usd file was generated + EXPECT_TRUE(ignition::common::isFile(outputUsdFilePath)); + + // TODO(ahcorde): Check the contents of outputUsdFilePath when the parser + // is implemented + } +} + +///////////////////////////////////////////////// +/// Main +int main(int argc, char **argv) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +}