diff --git a/test/sdf/basic_shapes.sdf b/test/sdf/basic_shapes.sdf
index 0191bda2e..32e0b416f 100644
--- a/test/sdf/basic_shapes.sdf
+++ b/test/sdf/basic_shapes.sdf
@@ -113,6 +113,10 @@
0.1
+
+ 0 0.1 0.2
+ 0.12 0.23 0.34 0.56
+
@@ -136,6 +140,18 @@
1.2 2.3 3.4
+
+ 0.2 0.5 0.1 1.0
+ 0.7 0.3 0.5 0.9
+
+
+ albedo_map.png
+ normal_map.png
+ roughness_map.png
+ metalness_map.png
+
+
+
diff --git a/usd/include/sdf/usd/Conversions.hh b/usd/include/sdf/usd/Conversions.hh
new file mode 100644
index 000000000..b1409a7af
--- /dev/null
+++ b/usd/include/sdf/usd/Conversions.hh
@@ -0,0 +1,54 @@
+/*
+ * 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.
+ *
+ */
+
+
+#ifndef SDF_USD_CONVERSIONS_HH_
+#define SDF_USD_CONVERSIONS_HH_
+
+#include
+
+#include
+
+#include "sdf/Material.hh"
+#include "sdf/sdf_config.h"
+#include "sdf/usd/Export.hh"
+
+namespace sdf
+{
+ // Inline bracket to help doxygen filtering.
+ inline namespace SDF_VERSION_NAMESPACE {
+ //
+ namespace usd
+ {
+ /// \brief Specialized conversion from an Ignition Common Material
+ /// to a SDF material
+ /// \param[in] _in Ignition Common Material.
+ /// \return SDF material.
+ IGNITION_SDFORMAT_USD_VISIBLE
+ sdf::Material convert(const ignition::common::Material *_in);
+
+ /// \brief Specialized conversion from an SDF material to a Ignition Common
+ /// material.
+ /// \param[in] _in SDF material.
+ /// \param[out] _out The Ignition Common Material.
+ IGNITION_SDFORMAT_USD_VISIBLE
+ void convert(const sdf::Material &_in, ignition::common::Material &_out);
+ }
+ }
+}
+
+#endif
diff --git a/usd/include/sdf/usd/UsdError.hh b/usd/include/sdf/usd/UsdError.hh
index 7078d4ef2..28742e2d6 100644
--- a/usd/include/sdf/usd/UsdError.hh
+++ b/usd/include/sdf/usd/UsdError.hh
@@ -66,6 +66,9 @@ namespace sdf
/// \brief Invalid submesh primitive type
INVALID_SUBMESH_PRIMITIVE_TYPE,
+
+ /// \brief Invalid material
+ INVALID_MATERIAL,
};
class IGNITION_SDFORMAT_USD_VISIBLE UsdError
diff --git a/usd/include/sdf/usd/sdf_parser/Material.hh b/usd/include/sdf/usd/sdf_parser/Material.hh
new file mode 100644
index 000000000..429dd7b8e
--- /dev/null
+++ b/usd/include/sdf/usd/sdf_parser/Material.hh
@@ -0,0 +1,60 @@
+/*
+ * 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_MATERIALS_HH_
+#define SDF_USD_SDF_PARSER_MATERIALS_HH_
+
+// 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
+#pragma pop_macro ("__DEPRECATED")
+
+#include "sdf/Material.hh"
+#include "sdf/usd/Export.hh"
+#include "sdf/usd/UsdError.hh"
+#include "sdf/sdf_config.h"
+
+namespace sdf
+{
+ // Inline bracket to help doxygen filtering.
+ inline namespace SDF_VERSION_NAMESPACE {
+ //
+ namespace usd
+ {
+ /// \brief Parse an SDF material into a USD stage.
+ /// \param[in] _materialSdf The SDF material to parse.
+ /// \param[in] _stage The stage that should contain the USD representation
+ /// of _material.
+ /// \param[out] _materialPath USD Material path
+ /// \return UsdErrors, which is a list of UsdError objects. This list is
+ /// empty if no errors occurred when parsing _materialSdf its USD
+ /// representation
+ UsdErrors IGNITION_SDFORMAT_USD_VISIBLE ParseSdfMaterial(
+ const sdf::Material *_materialSdf,
+ pxr::UsdStageRefPtr &_stage,
+ pxr::SdfPath &_materialPath);
+ }
+ }
+}
+
+#endif
diff --git a/usd/src/CMakeLists.txt b/usd/src/CMakeLists.txt
index 72db29aad..29b4ef4b5 100644
--- a/usd/src/CMakeLists.txt
+++ b/usd/src/CMakeLists.txt
@@ -1,9 +1,11 @@
set(sources
+ Conversions.cc
UsdError.cc
sdf_parser/Geometry.cc
sdf_parser/Joint.cc
sdf_parser/Light.cc
sdf_parser/Link.cc
+ sdf_parser/Material.cc
sdf_parser/Model.cc
sdf_parser/Sensor.cc
sdf_parser/Visual.cc
@@ -30,11 +32,13 @@ set(gtest_sources
sdf_parser/Joint_Sdf2Usd_TEST.cc
sdf_parser/Light_Sdf2Usd_TEST.cc
sdf_parser/Link_Sdf2Usd_TEST.cc
+ sdf_parser/Material_Sdf2Usd_TEST.cc
# TODO(adlarkin) add a test for SDF -> USD models once model parsing
# functionality is complete
sdf_parser/Sensors_Sdf2Usd_TEST.cc
sdf_parser/Visual_Sdf2Usd_TEST.cc
sdf_parser/World_Sdf2Usd_TEST.cc
+ Conversions_TEST.cc
UsdError_TEST.cc
UsdUtils_TEST.cc
)
diff --git a/usd/src/Conversions.cc b/usd/src/Conversions.cc
new file mode 100644
index 000000000..5386893b3
--- /dev/null
+++ b/usd/src/Conversions.cc
@@ -0,0 +1,152 @@
+/*
+ * 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 "sdf/usd/Conversions.hh"
+
+#include
+
+#include "sdf/Pbr.hh"
+
+namespace sdf
+{
+ inline namespace SDF_VERSION_NAMESPACE {
+ //
+ namespace usd
+ {
+ sdf::Material convert(const ignition::common::Material *_in)
+ {
+ sdf::Material out;
+ out.SetEmissive(_in->Emissive());
+ out.SetDiffuse(_in->Diffuse());
+ out.SetSpecular(_in->Specular());
+ out.SetAmbient(_in->Ambient());
+ out.SetRenderOrder(_in->RenderOrder());
+ out.SetLighting(_in->Lighting());
+ out.SetDoubleSided(_in->TwoSidedEnabled());
+ const ignition::common::Pbr * pbr = _in->PbrMaterial();
+ if (pbr != nullptr)
+ {
+ out.SetNormalMap(pbr->NormalMap());
+ sdf::Pbr pbrOut;
+ sdf::PbrWorkflow pbrWorkflow;
+ pbrWorkflow.SetAlbedoMap(pbr->AlbedoMap());
+ pbrWorkflow.SetMetalnessMap(pbr->MetalnessMap());
+ pbrWorkflow.SetEmissiveMap(pbr->EmissiveMap());
+ pbrWorkflow.SetRoughnessMap(pbr->RoughnessMap());
+ pbrWorkflow.SetSpecularMap(pbr->SpecularMap());
+ pbrWorkflow.SetEnvironmentMap(pbr->EnvironmentMap());
+ pbrWorkflow.SetAmbientOcclusionMap(pbr->AmbientOcclusionMap());
+ pbrWorkflow.SetLightMap(pbr->LightMap());
+ pbrWorkflow.SetRoughness(pbr->Roughness());
+ pbrWorkflow.SetGlossiness(pbr->Glossiness());
+ pbrWorkflow.SetMetalness(pbr->Metalness());
+
+ if (pbr->NormalMapType() == ignition::common::NormalMapSpace::TANGENT)
+ {
+ pbrWorkflow.SetNormalMap(
+ pbr->NormalMap(), sdf::NormalMapSpace::TANGENT);
+ }
+ else
+ {
+ pbrWorkflow.SetNormalMap(
+ pbr->NormalMap(), sdf::NormalMapSpace::OBJECT);
+ }
+
+ if (pbr->Type() == ignition::common::PbrType::METAL)
+ {
+ pbrOut.SetWorkflow(sdf::PbrWorkflowType::METAL, pbrWorkflow);
+ }
+ else if (pbr->Type() == ignition::common::PbrType::SPECULAR)
+ {
+ pbrOut.SetWorkflow(sdf::PbrWorkflowType::SPECULAR, pbrWorkflow);
+ }
+ out.SetPbrMaterial(pbrOut);
+ }
+ else if (!_in->TextureImage().empty())
+ {
+ sdf::Pbr pbrOut;
+ sdf::PbrWorkflow pbrWorkflow;
+ pbrWorkflow.SetAlbedoMap(_in->TextureImage());
+ pbrOut.SetWorkflow(sdf::PbrWorkflowType::SPECULAR, pbrWorkflow);
+ out.SetPbrMaterial(pbrOut);
+ }
+
+ return out;
+ }
+
+ void convert(const sdf::Material &_in, ignition::common::Material &_out)
+ {
+ _out.SetEmissive(_in.Emissive());
+ _out.SetDiffuse(_in.Diffuse());
+ _out.SetSpecular(_in.Specular());
+ _out.SetAmbient(_in.Ambient());
+ _out.SetRenderOrder(_in.RenderOrder());
+ _out.SetLighting(_in.Lighting());
+ _out.SetAlphaFromTexture(false, 0.5, _in.DoubleSided());
+
+ const sdf::Pbr * pbr = _in.PbrMaterial();
+ if (pbr != nullptr)
+ {
+ ignition::common::Pbr pbrOut;
+
+ const sdf::PbrWorkflow * pbrWorkflow =
+ pbr->Workflow(sdf::PbrWorkflowType::METAL);
+ if (pbrWorkflow)
+ {
+ pbrOut.SetType(ignition::common::PbrType::METAL);
+ }
+ else
+ {
+ pbrWorkflow = pbr->Workflow(sdf::PbrWorkflowType::SPECULAR);
+ if (pbrWorkflow)
+ {
+ pbrOut.SetType(ignition::common::PbrType::SPECULAR);
+ }
+ }
+ if (pbrWorkflow != nullptr)
+ {
+ pbrOut.SetAlbedoMap(pbrWorkflow->AlbedoMap());
+ pbrOut.SetMetalnessMap(pbrWorkflow->MetalnessMap());
+ pbrOut.SetEmissiveMap(pbrWorkflow->EmissiveMap());
+ pbrOut.SetRoughnessMap(pbrWorkflow->RoughnessMap());
+ pbrOut.SetSpecularMap(pbrWorkflow->SpecularMap());
+ pbrOut.SetEnvironmentMap(pbrWorkflow->EnvironmentMap());
+ pbrOut.SetAmbientOcclusionMap(pbrWorkflow->AmbientOcclusionMap());
+ pbrOut.SetLightMap(pbrWorkflow->LightMap());
+ pbrOut.SetRoughness(pbrWorkflow->Roughness());
+ pbrOut.SetGlossiness(pbrWorkflow->Glossiness());
+ pbrOut.SetMetalness(pbrWorkflow->Metalness());
+
+ if (pbrWorkflow->NormalMapType() == sdf::NormalMapSpace::TANGENT)
+ {
+ pbrOut.SetNormalMap(
+ pbrWorkflow->NormalMap(),
+ ignition::common::NormalMapSpace::TANGENT);
+ }
+ else if (pbrWorkflow->NormalMapType() == sdf::NormalMapSpace::OBJECT)
+ {
+ pbrOut.SetNormalMap(
+ pbrWorkflow->NormalMap(),
+ ignition::common::NormalMapSpace::OBJECT);
+ }
+ }
+ _out.SetPbrMaterial(pbrOut);
+ }
+ }
+ }
+ }
+}
diff --git a/usd/src/Conversions_TEST.cc b/usd/src/Conversions_TEST.cc
new file mode 100644
index 000000000..e5c092146
--- /dev/null
+++ b/usd/src/Conversions_TEST.cc
@@ -0,0 +1,161 @@
+/*
+ * 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
+
+#include
+#include
+#include
+
+#include "sdf/Material.hh"
+#include "sdf/Pbr.hh"
+#include "sdf/usd/Conversions.hh"
+
+/////////////////////////////////////////////////
+TEST(Conversions, SdfToCommonMaterial)
+{
+ sdf::Material material;
+ material.SetEmissive(ignition::math::Color(1, 0.2, 0.2, 0.7));
+ material.SetDiffuse(ignition::math::Color(0.1, 0.3, 0.4, 0.5));
+ material.SetSpecular(ignition::math::Color(0.11, 0.22, 0.23, 0.77));
+ material.SetAmbient(ignition::math::Color(0.25, 0.21, 0.28, 0.5));
+ material.SetRenderOrder(5);
+ material.SetLighting(true);
+ material.SetDoubleSided(false);
+
+ sdf::Pbr pbrSDF;
+ sdf::PbrWorkflow pbrWorkflow;
+
+ pbrWorkflow.SetAlbedoMap("AlbedoMap");
+ pbrWorkflow.SetMetalnessMap("MetalnessMap");
+ pbrWorkflow.SetEmissiveMap("EmissiveMap");
+ pbrWorkflow.SetRoughnessMap("RoughnessMap");
+ pbrWorkflow.SetSpecularMap("SpecularMap");
+ pbrWorkflow.SetEnvironmentMap("EnvironmentMap");
+ pbrWorkflow.SetAmbientOcclusionMap("AmbientOcclusionMap");
+ pbrWorkflow.SetLightMap("LightMap");
+ pbrWorkflow.SetNormalMap(
+ "NormalMap", sdf::NormalMapSpace::TANGENT);
+ pbrWorkflow.SetRoughness(0.2);
+ pbrWorkflow.SetGlossiness(0.3);
+ pbrWorkflow.SetMetalness(0.55);
+
+ pbrSDF.SetWorkflow(sdf::PbrWorkflowType::METAL, pbrWorkflow);
+ material.SetPbrMaterial(pbrSDF);
+
+ ignition::common::Material materialCommon;
+ sdf::usd::convert(material, materialCommon);
+ const ignition::common::Pbr * pbrCommon = materialCommon.PbrMaterial();
+ ASSERT_NE(nullptr, pbrCommon);
+
+ EXPECT_EQ(material.Emissive(), materialCommon.Emissive());
+ EXPECT_EQ(material.Diffuse(), materialCommon.Diffuse());
+ EXPECT_EQ(material.Specular(), materialCommon.Specular());
+ EXPECT_EQ(material.Ambient(), materialCommon.Ambient());
+ EXPECT_FLOAT_EQ(material.RenderOrder(),
+ static_cast(materialCommon.RenderOrder()));
+ EXPECT_EQ(material.Lighting(), materialCommon.Lighting());
+ EXPECT_EQ(material.DoubleSided(), materialCommon.TwoSidedEnabled());
+
+ EXPECT_EQ(pbrWorkflow.AlbedoMap(), pbrCommon->AlbedoMap());
+ EXPECT_EQ(pbrWorkflow.MetalnessMap(), pbrCommon->MetalnessMap());
+ EXPECT_EQ(pbrWorkflow.EmissiveMap(), pbrCommon->EmissiveMap());
+ EXPECT_EQ(pbrWorkflow.RoughnessMap(), pbrCommon->RoughnessMap());
+ EXPECT_EQ(pbrWorkflow.SpecularMap(), pbrCommon->SpecularMap());
+ EXPECT_EQ(pbrWorkflow.EnvironmentMap(), pbrCommon->EnvironmentMap());
+ EXPECT_EQ(pbrWorkflow.AmbientOcclusionMap(),
+ pbrCommon->AmbientOcclusionMap());
+ EXPECT_EQ(pbrWorkflow.LightMap(), pbrCommon->LightMap());
+ EXPECT_EQ(pbrWorkflow.NormalMap(), pbrCommon->NormalMap());
+
+ EXPECT_EQ(ignition::common::NormalMapSpace::TANGENT,
+ pbrCommon->NormalMapType());
+ EXPECT_EQ(ignition::common::PbrType::METAL, pbrCommon->Type());
+
+ EXPECT_DOUBLE_EQ(pbrWorkflow.Roughness(), pbrCommon->Roughness());
+ EXPECT_DOUBLE_EQ(pbrWorkflow.Glossiness(), pbrCommon->Glossiness());
+ EXPECT_DOUBLE_EQ(pbrWorkflow.Metalness(), pbrCommon->Metalness());
+}
+
+TEST(Conversions, CommonToSdfMaterial)
+{
+ ignition::common::Material materialCommon;
+ materialCommon.SetEmissive(ignition::math::Color(1, 0.2, 0.2, 0.7));
+ materialCommon.SetDiffuse(ignition::math::Color(0.1, 0.3, 0.4, 0.5));
+ materialCommon.SetSpecular(ignition::math::Color(0.11, 0.22, 0.23, 0.77));
+ materialCommon.SetAmbient(ignition::math::Color(0.25, 0.21, 0.28, 0.5));
+ materialCommon.SetRenderOrder(5);
+ materialCommon.SetLighting(true);
+ materialCommon.SetAlphaFromTexture(false, 0.5, true);
+
+ ignition::common::Pbr pbrCommon;
+ pbrCommon.SetType(ignition::common::PbrType::METAL);
+
+ pbrCommon.SetAlbedoMap("AlbedoMap");
+ pbrCommon.SetMetalnessMap("MetalnessMap");
+ pbrCommon.SetEmissiveMap("EmissiveMap");
+ pbrCommon.SetRoughnessMap("RoughnessMap");
+ pbrCommon.SetSpecularMap("SpecularMap");
+ pbrCommon.SetEnvironmentMap("EnvironmentMap");
+ pbrCommon.SetAmbientOcclusionMap("AmbientOcclusionMap");
+ pbrCommon.SetLightMap("LightMap");
+ pbrCommon.SetNormalMap(
+ "NormalMap", ignition::common::NormalMapSpace::TANGENT);
+ pbrCommon.SetRoughness(0.2);
+ pbrCommon.SetGlossiness(0.3);
+ pbrCommon.SetMetalness(0.55);
+
+ materialCommon.SetPbrMaterial(pbrCommon);
+
+ const sdf::Material material = sdf::usd::convert(&materialCommon);
+
+ const sdf::Pbr * pbr = material.PbrMaterial();
+ ASSERT_NE(nullptr, pbr);
+ const sdf::PbrWorkflow * pbrWorkflow =
+ pbr->Workflow(sdf::PbrWorkflowType::METAL);
+ ASSERT_NE(nullptr, pbrWorkflow);
+
+ EXPECT_EQ(material.Emissive(), materialCommon.Emissive());
+ EXPECT_EQ(material.Diffuse(), materialCommon.Diffuse());
+ EXPECT_EQ(material.Specular(), materialCommon.Specular());
+ EXPECT_EQ(material.Ambient(), materialCommon.Ambient());
+ EXPECT_FLOAT_EQ(material.RenderOrder(),
+ static_cast(materialCommon.RenderOrder()));
+ EXPECT_EQ(material.Lighting(), materialCommon.Lighting());
+ EXPECT_EQ(material.DoubleSided(), materialCommon.TwoSidedEnabled());
+
+ EXPECT_EQ(pbrWorkflow->AlbedoMap(), pbrCommon.AlbedoMap());
+ EXPECT_EQ(pbrWorkflow->MetalnessMap(), pbrCommon.MetalnessMap());
+ EXPECT_EQ(pbrWorkflow->EmissiveMap(), pbrCommon.EmissiveMap());
+ EXPECT_EQ(pbrWorkflow->RoughnessMap(), pbrCommon.RoughnessMap());
+ EXPECT_EQ(pbrWorkflow->SpecularMap(), pbrCommon.SpecularMap());
+ EXPECT_EQ(pbrWorkflow->EnvironmentMap(), pbrCommon.EnvironmentMap());
+ EXPECT_EQ(pbrWorkflow->AmbientOcclusionMap(),
+ pbrCommon.AmbientOcclusionMap());
+ EXPECT_EQ(pbrWorkflow->LightMap(), pbrCommon.LightMap());
+ EXPECT_EQ(pbrWorkflow->NormalMap(), pbrCommon.NormalMap());
+
+ EXPECT_EQ(ignition::common::NormalMapSpace::TANGENT,
+ pbrCommon.NormalMapType());
+ EXPECT_EQ(ignition::common::PbrType::METAL, pbrCommon.Type());
+
+ EXPECT_DOUBLE_EQ(pbrWorkflow->Roughness(), pbrCommon.Roughness());
+ EXPECT_DOUBLE_EQ(pbrWorkflow->Glossiness(), pbrCommon.Glossiness());
+ EXPECT_DOUBLE_EQ(pbrWorkflow->Metalness(), pbrCommon.Metalness());
+}
diff --git a/usd/src/cmd/sdf2usd.cc b/usd/src/cmd/sdf2usd.cc
index 06bc77980..5ca0d7131 100644
--- a/usd/src/cmd/sdf2usd.cc
+++ b/usd/src/cmd/sdf2usd.cc
@@ -17,11 +17,15 @@
#include
-// TODO(ahcorde):this is to remove deprecated "warnings" in usd, these warnings
+#include
+#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.
-#include
#pragma push_macro ("__DEPRECATED")
#undef __DEPRECATED
#include
@@ -51,8 +55,164 @@ struct Options
std::string outputFilename{"output.usd"};
};
+//////////////////////////////////////////////////
+/// \brief Get the full path of a file
+/// \param[in] _path Where to begin searching for the file
+/// \param[in] _name The name of the file to find
+/// \return The full path to the file named _name. Empty string is returned if
+/// the file could not be found.
+std::string findFileByName(const std::string &_path, const std::string &_name)
+{
+ for (ignition::common::DirIter file(_path);
+ file != ignition::common::DirIter(); ++file)
+ {
+ std::string current(*file);
+
+ if (ignition::common::isDirectory(current))
+ {
+ std::string result = findFileByName(current, _name);
+ if (result.empty())
+ {
+ continue;
+ }
+ return result;
+ }
+
+ if (!ignition::common::isFile(current))
+ continue;
+
+ auto fileName = ignition::common::basename(current);
+
+ if (fileName == _name)
+ {
+ return current;
+ }
+ }
+ return "";
+}
+
+//////////////////////////////////////////////////
+/// \brief Get the full path of a file based on the extension
+/// \param[in] _path Where to begin searching for the file
+/// \param[in] _extension The extension of the file
+/// \param[in] _insertDirectories Whether subdirectories should be inserted as
+/// needed when looking for the file (true) or not (false)
+/// \return The full path to the file with an extension _extension. Empty
+/// string is returned if the file could not be found.
+std::string findFileByExtension(
+ const std::string &_path, const std::string &_extension,
+ bool _insertDirectories = false)
+{
+ if (_insertDirectories)
+ {
+ for (ignition::common::DirIter file(_path);
+ file != ignition::common::DirIter(); ++file)
+ {
+ std::string current(*file);
+ if (ignition::common::isDirectory(current))
+ {
+ auto systemPaths = ignition::common::systemPaths();
+ systemPaths->AddFilePaths(current);
+ findFileByExtension(current, "", true);
+ }
+ }
+ }
+
+ for (ignition::common::DirIter file(_path);
+ file != ignition::common::DirIter(); ++file)
+ {
+ std::string current(*file);
+
+ if (ignition::common::isDirectory(current))
+ {
+ std::string result = findFileByExtension(current, _extension);
+ if (result.empty())
+ {
+ continue;
+ }
+ return result;
+ }
+
+ if (!ignition::common::isFile(current))
+ continue;
+
+ auto fileName = ignition::common::basename(current);
+ auto fileExtensionIndex = fileName.rfind(".");
+ auto fileExtension = fileName.substr(fileExtensionIndex + 1);
+
+ if (fileExtension == _extension)
+ {
+ return current;
+ }
+ }
+ return "";
+}
+
+//////////////////////////////////////////////////
+/// \brief This functions is used by sdf::setFindCallback to find
+/// the resources defined in the URI
+/// \param[in] _uri URI of the file to find
+/// \return The full path to the uri. Empty
+/// string is returned if the file could not be found.
+std::string FindResources(const std::string &_uri)
+{
+ ignition::common::URI uri(_uri);
+ std::string path;
+ std::string home;
+ if (!ignition::common::env("HOME", home, false))
+ {
+ std::cerr << "The HOME environment variable was not defined, "
+ << "so the resource [" << _uri << "] could not be found\n";
+ return "";
+ }
+ if (uri.Scheme() == "http" || uri.Scheme() == "https")
+ {
+ std::vector tokens =
+ ignition::common::split(uri.Path().Str(), "/");
+ const std::string server = tokens[0];
+ const std::string versionServer = tokens[1];
+ const std::string owner = ignition::common::lowercase(tokens[2]);
+ const std::string type = ignition::common::lowercase(tokens[3]);
+ const std::string modelName = ignition::common::lowercase(tokens[4]);
+ path = ignition::common::joinPaths(
+ home, ".ignition", "fuel", server, owner, type, modelName);
+ }
+ else
+ {
+ path = ignition::common::joinPaths(home, ".ignition", "fuel");
+ }
+
+ auto fileName = ignition::common::basename(uri.Path().Str());
+ auto fileExtensionIndex = fileName.rfind(".");
+ if (fileExtensionIndex == std::string::npos)
+ {
+ return findFileByExtension(path, "sdf", true);
+ }
+ else
+ {
+ return findFileByName(path, fileName);
+ }
+ return "";
+}
+
+//////////////////////////////////////////////////
+/// \brief This function is used by ignition::common::addFindFileURICallback to
+/// find the resources defined in the URI
+/// \param[in] _uri URI of the file to find
+/// \return The full path to the uri. Empty
+/// string is returned if the file could not be found.
+std::string FindResourceUri(const ignition::common::URI &_uri)
+{
+ return FindResources(_uri.Str());
+}
+
void runCommand(const Options &_opt)
{
+ // Configure SDF to fetch assets from ignition fuel.
+ sdf::setFindCallback(std::bind(&FindResources, std::placeholders::_1));
+ ignition::common::addFindFileURICallback(
+ std::bind(&FindResourceUri, std::placeholders::_1));
+
sdf::Root root;
auto errors = root.Load(_opt.inputFilename);
if (!errors.empty())
diff --git a/usd/src/sdf_parser/Geometry.cc b/usd/src/sdf_parser/Geometry.cc
index dde5e43b5..10912ca4f 100644
--- a/usd/src/sdf_parser/Geometry.cc
+++ b/usd/src/sdf_parser/Geometry.cc
@@ -58,6 +58,9 @@
#include "sdf/Mesh.hh"
#include "sdf/Plane.hh"
#include "sdf/Sphere.hh"
+#include "sdf/usd/Conversions.hh"
+#include "sdf/usd/sdf_parser/Material.hh"
+
#include "../UsdUtils.hh"
namespace sdf
@@ -380,6 +383,56 @@ namespace usd
pxr::GfVec3f(meshMax.X(), meshMax.Y(), meshMax.Z()));
usdMesh.CreateExtentAttr().Set(extentBounds);
+ // TODO(adlarkin) update this call in sdf13 to avoid casting the index to
+ // an int:
+ // https://github.com/ignitionrobotics/ign-common/pull/319
+ int materialIndex = subMesh->MaterialIndex();
+ if (materialIndex != -1)
+ {
+ const auto material = ignMesh->MaterialByIndex(materialIndex).get();
+ const sdf::Material materialSdf = sdf::usd::convert(material);
+ pxr::SdfPath materialPath;
+ UsdErrors materialErrors = ParseSdfMaterial(
+ &materialSdf, _stage, materialPath);
+ if (!materialErrors.empty())
+ {
+ errors.push_back(UsdError(
+ sdf::usd::UsdErrorCode::SDF_TO_USD_PARSING_ERROR,
+ "Unable to convert material [" + std::to_string(materialIndex)
+ + "] of submesh named [" + subMesh->Name()
+ + "] to a USD material."));
+ return errors;
+ }
+
+ auto materialPrim = _stage->GetPrimAtPath(materialPath);
+ if (!materialPrim)
+ {
+ errors.push_back(UsdError(
+ sdf::usd::UsdErrorCode::INVALID_PRIM_PATH,
+ "Unable to get material prim at path ["
+ + materialPath.GetString()
+ + "], but a prim should exist at this path."));
+ return errors;
+ }
+
+ auto materialUSD = pxr::UsdShadeMaterial(materialPrim);
+ if (materialUSD &&
+ (materialSdf.Emissive() != ignition::math::Color(0, 0, 0, 1)
+ || materialSdf.Specular() != ignition::math::Color(0, 0, 0, 1)
+ || materialSdf.PbrMaterial()))
+ {
+ pxr::UsdShadeMaterialBindingAPI(usdMesh).Bind(materialUSD);
+ }
+ else if (!materialUSD)
+ {
+ errors.push_back(UsdError(
+ sdf::usd::UsdErrorCode::SDF_TO_USD_PARSING_ERROR,
+ "The prim at path [" + materialPath.GetString()
+ + "] is not a pxr::UsdShadeMaterial object."));
+ return errors;
+ }
+ }
+
pxr::UsdGeomXformCommonAPI xform(usdMesh);
ignition::math::Vector3d scale = _geometry.MeshShape()->Scale();
xform.SetScale(pxr::GfVec3f{
diff --git a/usd/src/sdf_parser/Material.cc b/usd/src/sdf_parser/Material.cc
new file mode 100644
index 000000000..0a1c789a8
--- /dev/null
+++ b/usd/src/sdf_parser/Material.cc
@@ -0,0 +1,586 @@
+/*
+ * 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/Material.hh"
+
+#include