|
@@ -13,6 +13,7 @@
|
|
|
#include <AzCore/IO/Path/Path.h>
|
|
|
#include <AzCore/StringFunc/StringFunc.h>
|
|
|
#include <AzCore/std/string/regex.h>
|
|
|
+#include <AzCore/Utils/Utils.h>
|
|
|
#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
|
|
|
#include <RobotImporter/Utils/ErrorUtils.h>
|
|
|
#include <string.h>
|
|
@@ -507,161 +508,222 @@ namespace ROS2::Utils
|
|
|
return resultModel;
|
|
|
}
|
|
|
|
|
|
- /// Finds global path from URDF path
|
|
|
- AZ::IO::Path ResolveURDFPath(
|
|
|
+ AZ::IO::Path ResolveAmentPrefixPath(
|
|
|
AZ::IO::Path unresolvedPath,
|
|
|
- const AZ::IO::PathView& urdfFilePath,
|
|
|
- const AZ::IO::PathView& amentPrefixPath,
|
|
|
- const FileExistsCB& fileExists)
|
|
|
+ AZStd::string_view amentPrefixPath,
|
|
|
+ const FileExistsCB& fileExistsCB)
|
|
|
{
|
|
|
- AZ_Printf("ResolveURDFPath", "ResolveURDFPath with %s\n", unresolvedPath.c_str());
|
|
|
-
|
|
|
- // TODO: Query URDF prefix map from Settings Registry
|
|
|
AZStd::vector<AZ::IO::Path> amentPrefixPaths;
|
|
|
|
|
|
- // Split the AMENT_PREFIX_PATH into multiple paths
|
|
|
- auto AmentPrefixPathVisitor = [&amentPrefixPaths](AZStd::string_view prefixPath)
|
|
|
+ // Parse the AMENT_PREFIX_PATH environment variable into a set of distinct paths.
|
|
|
+ auto AmentPrefixPathVisitor = [&amentPrefixPaths](
|
|
|
+ AZStd::string_view prefixPath)
|
|
|
{
|
|
|
amentPrefixPaths.push_back(prefixPath);
|
|
|
};
|
|
|
// Note this code only works on Unix platforms
|
|
|
// For Windows this will not work as the drive letter has a colon in it (C:\)
|
|
|
- AZ::StringFunc::TokenizeVisitor(amentPrefixPath.Native(), AmentPrefixPathVisitor, ':');
|
|
|
+ AZ::StringFunc::TokenizeVisitor(amentPrefixPath, AmentPrefixPathVisitor, ':');
|
|
|
+
|
|
|
+ AZ::IO::PathView strippedPath;
|
|
|
|
|
|
- // Append the urdf file ancestor directories to the candidate replacement paths
|
|
|
- AZStd::vector<AZ::IO::Path> urdfAncestorPaths;
|
|
|
- if (!urdfFilePath.empty())
|
|
|
+ // The AMENT_PREFIX_PATH is only used for lookups if the URI starts with "model://" or "package://"
|
|
|
+ constexpr AZStd::string_view ValidAmentPrefixes[] = {"model://", "package://"};
|
|
|
+ for (const auto& prefix : ValidAmentPrefixes)
|
|
|
{
|
|
|
- AZ::IO::Path urdfFileAncestorPath = urdfFilePath;
|
|
|
- bool rootPathVisited = false;
|
|
|
- do
|
|
|
+ // Perform a case-sensitive check to look for the prefix.
|
|
|
+ constexpr bool prefixMatchCaseSensitive = true;
|
|
|
+ if (AZ::StringFunc::StartsWith(unresolvedPath.Native(), prefix, prefixMatchCaseSensitive))
|
|
|
{
|
|
|
- AZ::IO::PathView parentPath = urdfFileAncestorPath.ParentPath();
|
|
|
- rootPathVisited = (urdfFileAncestorPath == parentPath);
|
|
|
- urdfAncestorPaths.emplace_back(parentPath);
|
|
|
- urdfFileAncestorPath = parentPath;
|
|
|
- } while (!rootPathVisited);
|
|
|
+ strippedPath = AZ::IO::PathView(unresolvedPath).Native().substr(prefix.size());
|
|
|
+ break;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- // Structure which accepts a callback that can convert an unresolved URI path(package://, model://, file://, etc...)
|
|
|
- // to a filesystem path
|
|
|
- struct UriPrefix
|
|
|
+ // If no valid prefix was found, or if the URI *only* contains the prefix, return an empty result.
|
|
|
+ if (strippedPath.empty())
|
|
|
{
|
|
|
- using SchemeResolver = AZStd::function<AZStd::optional<AZ::IO::Path>(AZ::IO::PathView)>;
|
|
|
- SchemeResolver m_schemeResolver;
|
|
|
- };
|
|
|
+ return {};
|
|
|
+ }
|
|
|
+
|
|
|
+ // If the remaining path is an absolute path, it shouldn't get resolved with AMENT_PREFIX_PATH. return an empty result.
|
|
|
+ if (strippedPath.IsAbsolute())
|
|
|
+ {
|
|
|
+ return {};
|
|
|
+ }
|
|
|
+
|
|
|
+ // Check to see if the relative part of the URI path refers to a location
|
|
|
+ // within each <ament prefix path>/share directory
|
|
|
+ for (const AZ::IO::Path& amentPrefixPath : amentPrefixPaths)
|
|
|
+ {
|
|
|
+ auto pathIter = strippedPath.begin();
|
|
|
+ AZ::IO::PathView packageName = *pathIter;
|
|
|
+ const AZ::IO::Path amentSharePath = amentPrefixPath / "share";
|
|
|
+ const AZ::IO::Path packageManifestPath = amentSharePath / packageName / "package.xml";
|
|
|
+
|
|
|
+ // Given a path like 'ambulance/meshes/model.stl', it will be considered a match if
|
|
|
+ // <ament prefix path>/share/ambulance/package.xml exists and
|
|
|
+ // <ament prefix path>/share/ambulance/meshes/model.stl exists.
|
|
|
+ if (const AZ::IO::Path candidateResolvedPath = amentSharePath / strippedPath;
|
|
|
+ fileExistsCB(packageManifestPath) && fileExistsCB(candidateResolvedPath))
|
|
|
+ {
|
|
|
+ AZ_Trace("ResolveAssetPath", R"(Resolved using AMENT_PREFIX_PATH: "%.*s" -> "%.*s")" "\n",
|
|
|
+ AZ_PATH_ARG(unresolvedPath), AZ_PATH_ARG(candidateResolvedPath));
|
|
|
+ return candidateResolvedPath;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // No resolution was found, return an empty result.
|
|
|
+ return {};
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Finds global path from URDF/SDF path
|
|
|
+ AZ::IO::Path ResolveAssetPath(
|
|
|
+ AZ::IO::Path unresolvedPath,
|
|
|
+ const AZ::IO::PathView& baseFilePath,
|
|
|
+ AZStd::string_view amentPrefixPath,
|
|
|
+ const SdfAssetBuilderSettings& settings,
|
|
|
+ const FileExistsCB& fileExistsCB)
|
|
|
+ {
|
|
|
+ AZ_Printf("ResolveAssetPath", "ResolveAssetPath with %s\n", unresolvedPath.c_str());
|
|
|
+
|
|
|
+ const auto& pathResolverSettings = settings.m_resolverSettings;
|
|
|
|
|
|
- auto GetReplacementSchemeResolver = [](AZStd::string_view schemePrefix,
|
|
|
- AZStd::span<const AZ::IO::Path> amentPrefixPaths,
|
|
|
- AZStd::span<const AZ::IO::Path> urdfAncestorPaths,
|
|
|
- const FileExistsCB& fileExistsCB)
|
|
|
+ // If the settings tell us to try the AMENT_PREFIX_PATH, use that first to try and resolve path.
|
|
|
+ if (pathResolverSettings.m_useAmentPrefixPath)
|
|
|
{
|
|
|
- return [schemePrefix, amentPrefixPaths, urdfAncestorPaths, &fileExistsCB](
|
|
|
- AZ::IO::PathView uriPath) -> AZStd::optional<AZ::IO::Path>
|
|
|
+ if (AZ::IO::Path amentResolvedPath = ResolveAmentPrefixPath(unresolvedPath, amentPrefixPath, fileExistsCB); !amentResolvedPath.empty())
|
|
|
{
|
|
|
- // Note this is a case-sensitive check to match the exact URI scheme
|
|
|
- // If that is not desired, then this code should be updated to read
|
|
|
- // a value from the Setting Registry indicating whether the uriPrefix matching
|
|
|
- // should be case sensitive
|
|
|
- bool uriPrefixMatchCaseSensitive = true;
|
|
|
- // Check if the path starts with the URI scheme prefix
|
|
|
- if (AZ::StringFunc::StartsWith(uriPath.Native(), schemePrefix, uriPrefixMatchCaseSensitive))
|
|
|
+ return amentResolvedPath;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Append all ancestor directories from the root file to the candidate replacement paths if the settings enable using
|
|
|
+ // them for path resolution and the root file isn't empty.
|
|
|
+ AZStd::vector<AZ::IO::Path> ancestorPaths;
|
|
|
+ if (!baseFilePath.empty() && pathResolverSettings.m_useAncestorPaths)
|
|
|
+ {
|
|
|
+ // The first time through this loop, fileAncestorPath contains the full file name ('/a/b/c.sdf') so
|
|
|
+ // ParentPath() will return the path containing the file ('/a/b'). Each iteration will walk up the path
|
|
|
+ // to the root, including the root, before stopping ('/a', '/').
|
|
|
+ AZ::IO::Path fileAncestorPath = baseFilePath;
|
|
|
+ do
|
|
|
+ {
|
|
|
+ fileAncestorPath = fileAncestorPath.ParentPath();
|
|
|
+ ancestorPaths.emplace_back(fileAncestorPath);
|
|
|
+ } while (fileAncestorPath != fileAncestorPath.RootPath());
|
|
|
+ }
|
|
|
+
|
|
|
+ // Loop through each prefix in the builder settings and attempt to resolve it as either an absolute path
|
|
|
+ // or a relative path that's relative in some way to the base file.
|
|
|
+ for (const auto& [prefix, replacements] : pathResolverSettings.m_uriPrefixMap)
|
|
|
+ {
|
|
|
+ // Note this is a case-sensitive check to match the exact URI scheme
|
|
|
+ // If that is not desired, then this code should be updated to read
|
|
|
+ // a value from the Setting Registry indicating whether the uriPrefix matching
|
|
|
+ // should be case sensitive
|
|
|
+ constexpr bool uriPrefixMatchCaseSensitive = true;
|
|
|
+ // If the path doesn't start with the given prefix, move on to the next prefix
|
|
|
+ if (!AZ::StringFunc::StartsWith(unresolvedPath.Native(), prefix, uriPrefixMatchCaseSensitive))
|
|
|
+ {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Strip the number of characters from the Uri scheme from beginning of the path
|
|
|
+ AZ::IO::PathView strippedUriPath = AZ::IO::PathView(unresolvedPath).Native().substr(prefix.size());
|
|
|
+
|
|
|
+ // Loop through each replacement path for this prefix, attach it to the front, and look for matches.
|
|
|
+ for (const auto& replacement : replacements)
|
|
|
+ {
|
|
|
+ AZ::IO::Path replacedUriPath(replacement);
|
|
|
+ replacedUriPath /= strippedUriPath;
|
|
|
+
|
|
|
+ // If we successfully matched the prefix, and the replacement path is completely empty, we don't need to look any further.
|
|
|
+ // There's no match.
|
|
|
+ if (replacedUriPath.empty())
|
|
|
{
|
|
|
- // Strip the number of characters from the Uri scheme from beginning of the path
|
|
|
- AZ::IO::PathView strippedUriPath = uriPath.Native().substr(schemePrefix.size());
|
|
|
- if (strippedUriPath.empty())
|
|
|
+ AZ_Trace("ResolveAssetPath", R"(Resolved Path is empty: "%.*s" -> "")" "\n", AZ_PATH_ARG(unresolvedPath));
|
|
|
+ return {};
|
|
|
+ }
|
|
|
+
|
|
|
+ // If the replaced path is an absolute path, if it exists, return it.
|
|
|
+ // If it doesn't exist, keep trying other replacements.
|
|
|
+ if (replacedUriPath.IsAbsolute())
|
|
|
+ {
|
|
|
+ if (fileExistsCB(replacedUriPath))
|
|
|
{
|
|
|
- // The stripped URI path is empty, so there is nothing to resolve
|
|
|
- return AZStd::nullopt;
|
|
|
+ AZ_Trace("ResolveAssetPath", R"(Resolved Absolute Path: "%.*s" -> "%.*s")" "\n",
|
|
|
+ AZ_PATH_ARG(unresolvedPath), AZ_PATH_ARG(replacedUriPath));
|
|
|
+ return replacedUriPath;
|
|
|
}
|
|
|
-
|
|
|
- // Check to see if the relative part of the URI path refers to a location
|
|
|
- // within each <ament prefix path>/share directory
|
|
|
- for (const AZ::IO::Path& amentPrefixPath : amentPrefixPaths)
|
|
|
+ else
|
|
|
{
|
|
|
- auto pathIter = strippedUriPath.begin();
|
|
|
- AZ::IO::PathView packageName = *pathIter;
|
|
|
- const AZ::IO::Path amentSharePath = amentPrefixPath / "share";
|
|
|
- const AZ::IO::Path packageManifestPath = amentSharePath / packageName / "package.xml";
|
|
|
-
|
|
|
- if (const AZ::IO::Path candidateResolvedPath = amentSharePath / strippedUriPath;
|
|
|
- fileExistsCB(packageManifestPath) && fileExistsCB(candidateResolvedPath))
|
|
|
- {
|
|
|
- return candidateResolvedPath;
|
|
|
- }
|
|
|
+ // The file didn't exist, so continue.
|
|
|
+ continue;
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
- // The URI path cannot be resolved within the any ament prefix path,
|
|
|
- // so try the directory containing the URDF file as well as any of its parent directories
|
|
|
- for (const AZ::IO::Path& urdfAncestorPath : urdfAncestorPaths)
|
|
|
+ // The URI path is not absolute, so attempt to append it to the ancestor directories of the URDF/SDF file
|
|
|
+ for (const AZ::IO::Path& ancestorPath : ancestorPaths)
|
|
|
+ {
|
|
|
+ if (const AZ::IO::Path candidateResolvedPath = ancestorPath / replacedUriPath;
|
|
|
+ fileExistsCB(candidateResolvedPath))
|
|
|
{
|
|
|
- if (const AZ::IO::Path candidateResolvedPath = urdfAncestorPath / strippedUriPath;
|
|
|
- fileExistsCB(candidateResolvedPath))
|
|
|
- {
|
|
|
- return candidateResolvedPath;
|
|
|
- }
|
|
|
+ AZ_Trace("ResolveAssetPath", R"(Resolved using ancestor paths: "%.*s" -> "%.*s")" "\n",
|
|
|
+ AZ_PATH_ARG(unresolvedPath), AZ_PATH_ARG(candidateResolvedPath));
|
|
|
+ return candidateResolvedPath;
|
|
|
}
|
|
|
}
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- return AZStd::nullopt;
|
|
|
- };
|
|
|
- };
|
|
|
-
|
|
|
- constexpr AZStd::string_view PackageSchemePrefix = "package://";
|
|
|
- UriPrefix packageUriPrefix;
|
|
|
- packageUriPrefix.m_schemeResolver =
|
|
|
- GetReplacementSchemeResolver(PackageSchemePrefix, amentPrefixPaths, urdfAncestorPaths, fileExists);
|
|
|
-
|
|
|
- constexpr AZStd::string_view ModelSchemePrefix = "model://";
|
|
|
- UriPrefix modelUriPrefix;
|
|
|
- modelUriPrefix.m_schemeResolver = GetReplacementSchemeResolver(ModelSchemePrefix, amentPrefixPaths, urdfAncestorPaths, fileExists);
|
|
|
|
|
|
- // For a local file path convert the file URI to a local path
|
|
|
- UriPrefix fileUriPrefix;
|
|
|
- fileUriPrefix.m_schemeResolver = [](AZ::IO::PathView uriPath) -> AZStd::optional<AZ::IO::Path>
|
|
|
+ // At this point, the path has no identified URI prefix. If it's an absolute path, try to locate and return it.
|
|
|
+ // Otherwise, return an empty path as an error.
|
|
|
+ if (unresolvedPath.IsAbsolute())
|
|
|
{
|
|
|
- constexpr AZStd::string_view FileSchemePrefix = "file://";
|
|
|
- // Paths that start with 'file:///' are absolute paths, so only 'file://' needs to be stripped
|
|
|
- bool uriPrefixMatchCaseSensitive = true;
|
|
|
- if (AZ::StringFunc::StartsWith(uriPath.Native(), FileSchemePrefix, uriPrefixMatchCaseSensitive))
|
|
|
+ if (fileExistsCB(unresolvedPath))
|
|
|
{
|
|
|
- AZStd::string_view strippedUriPath = uriPath.Native().substr(FileSchemePrefix.size());
|
|
|
- return AZ::IO::Path(strippedUriPath);
|
|
|
+ AZ_Trace("ResolveAssetPath", R"(Resolved Absolute Path: "%.*s")" "\n",
|
|
|
+ AZ_PATH_ARG(unresolvedPath));
|
|
|
+ return unresolvedPath;
|
|
|
}
|
|
|
-
|
|
|
- return AZStd::nullopt;
|
|
|
- };
|
|
|
-
|
|
|
- // Step 1: Attempt to resolved URI scheme paths
|
|
|
- // libsdformat seems to convert package:// references to model:// references
|
|
|
- // So the model:// URI prefix resolver is run first
|
|
|
- const auto uriPrefixes =
|
|
|
- AZStd::to_array<UriPrefix>({ AZStd::move(modelUriPrefix), AZStd::move(fileUriPrefix), AZStd::move(packageUriPrefix) });
|
|
|
- for (const UriPrefix& uriPrefix : uriPrefixes)
|
|
|
- {
|
|
|
- if (auto resolvedPath = uriPrefix.m_schemeResolver(unresolvedPath); resolvedPath.has_value())
|
|
|
+ else
|
|
|
{
|
|
|
- AZ_Printf(
|
|
|
- "ResolveURDFPath",
|
|
|
- R"(Resolved Path using URI Prefix "%.*s" -> "%.*s")"
|
|
|
- "\n",
|
|
|
- AZ_PATH_ARG(unresolvedPath),
|
|
|
- AZ_PATH_ARG(resolvedPath.value()));
|
|
|
- return resolvedPath.value();
|
|
|
+ AZ_Trace("ResolveAssetPath", R"(Failed to resolve Absolute Path: "%.*s")" "\n",
|
|
|
+ AZ_PATH_ARG(unresolvedPath));
|
|
|
+ return {};
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- // At this point, the path has no URI scheme
|
|
|
- if (unresolvedPath.IsAbsolute())
|
|
|
+ // The path is a relative path, so use the directory containing the base URDF/SDF file as the root path,
|
|
|
+ // and if the file can be found successfully, return the path. Otherwise, return an empty path as an error.
|
|
|
+ const AZ::IO::Path relativePath = AZ::IO::Path(baseFilePath.ParentPath()) / unresolvedPath;
|
|
|
+
|
|
|
+ if (fileExistsCB(relativePath))
|
|
|
{
|
|
|
- AZ_Printf("ResolveURDFPath", "Input Path is an absolute local filesystem path to : %s\n", unresolvedPath.c_str());
|
|
|
- return unresolvedPath;
|
|
|
+ AZ_Trace("ResolveAssetPath", R"(Resolved Relative Path: "%.*s" -> "%.*s")" "\n",
|
|
|
+ AZ_PATH_ARG(unresolvedPath), AZ_PATH_ARG(relativePath));
|
|
|
+ return relativePath;
|
|
|
}
|
|
|
|
|
|
- // The path is relative path, so append to the directory containing the .urdf file
|
|
|
- const AZ::IO::Path resolvedPath = AZ::IO::Path(urdfFilePath.ParentPath()) / unresolvedPath;
|
|
|
- AZ_Printf("ResolveURDFPath", "Input Path %s is being returned as is\n", unresolvedPath.c_str());
|
|
|
- return resolvedPath;
|
|
|
+ AZ_Trace("ResolveAssetPath", R"(Failed to resolve Relative Path: "%.*s" -> "%.*s")" "\n",
|
|
|
+ AZ_PATH_ARG(unresolvedPath), AZ_PATH_ARG(relativePath));
|
|
|
+ return {};
|
|
|
+ }
|
|
|
+ AmentPrefixString GetAmentPrefixPath()
|
|
|
+ {
|
|
|
+ // Support reading the AMENT_PREFIX_PATH environment variable on Unix/Windows platforms
|
|
|
+ auto StoreAmentPrefixPath = [](char* buffer, size_t size) -> size_t
|
|
|
+ {
|
|
|
+ auto getEnvOutcome = AZ::Utils::GetEnv(AZStd::span(buffer, size), "AMENT_PREFIX_PATH");
|
|
|
+ return getEnvOutcome ? getEnvOutcome.GetValue().size() : 0;
|
|
|
+ };
|
|
|
+ AmentPrefixString amentPrefixPath;
|
|
|
+ amentPrefixPath.resize_and_overwrite(amentPrefixPath.capacity(), StoreAmentPrefixPath);
|
|
|
+ AZ_Error("UrdfAssetMap", !amentPrefixPath.empty(), "AMENT_PREFIX_PATH is not found.");
|
|
|
+
|
|
|
+ return amentPrefixPath;
|
|
|
}
|
|
|
|
|
|
} // namespace ROS2::Utils
|
|
@@ -715,4 +777,51 @@ namespace ROS2::Utils::SDFormat
|
|
|
{
|
|
|
return supportedPlugins.contains(GetPluginFilename(plugin));
|
|
|
}
|
|
|
+
|
|
|
+ sdf::ParserConfig CreateSdfParserConfigFromSettings(const SdfAssetBuilderSettings& settings, const AZ::IO::PathView& baseFilePath)
|
|
|
+ {
|
|
|
+ sdf::ParserConfig sdfConfig;
|
|
|
+
|
|
|
+ sdfConfig.URDFSetPreserveFixedJoint(settings.m_urdfPreserveFixedJoints);
|
|
|
+
|
|
|
+ // Fill in the URI resolution with the supplied prefix mappings.
|
|
|
+ for (auto& [prefix, pathList] : settings.m_resolverSettings.m_uriPrefixMap)
|
|
|
+ {
|
|
|
+ std::string uriPath;
|
|
|
+ for(auto& path : pathList)
|
|
|
+ {
|
|
|
+ if (!uriPath.empty())
|
|
|
+ {
|
|
|
+ uriPath.append(std::string(":"));
|
|
|
+ }
|
|
|
+
|
|
|
+ uriPath.append(std::string(path.c_str(), path.size()));
|
|
|
+ }
|
|
|
+ if (!prefix.empty() && !uriPath.empty())
|
|
|
+ {
|
|
|
+ std::string uriPrefix(prefix.c_str(), prefix.size());
|
|
|
+ sdfConfig.AddURIPath(uriPrefix, uriPath);
|
|
|
+ AZ_Trace("SdfParserConfig", "Added URI mapping '%s' -> '%s'", uriPrefix.c_str(), uriPath.c_str());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // If any files couldn't be found using our supplied prefix mappings, this callback will get called.
|
|
|
+ // Attempt to use our full path resolution, and print a warning if it still couldn't be resolved.
|
|
|
+ sdfConfig.SetFindCallback([settings, baseFilePath](const std::string &fileName) -> std::string
|
|
|
+ {
|
|
|
+ auto amentPrefixPath = Utils::GetAmentPrefixPath();
|
|
|
+
|
|
|
+ auto resolved = Utils::ResolveAssetPath(AZ::IO::Path(fileName.c_str()), baseFilePath, amentPrefixPath, settings);
|
|
|
+ if (!resolved.empty())
|
|
|
+ {
|
|
|
+ AZ_Trace("SdfParserConfig", "SDF SetFindCallback resolved '%s' -> '%s'", fileName.c_str(), resolved.c_str());
|
|
|
+ return resolved.c_str();
|
|
|
+ }
|
|
|
+
|
|
|
+ AZ_Warning("SdfParserConfig", false, "SDF SetFindCallback failed to resolve '%s'", fileName.c_str());
|
|
|
+ return fileName;
|
|
|
+ });
|
|
|
+
|
|
|
+ return sdfConfig;
|
|
|
+ }
|
|
|
} // namespace ROS2::Utils::SDFormat
|