Skip to content

Commit

Permalink
Support merge-include of nested models (#659)
Browse files Browse the repository at this point in the history
This adds a new attribute, `merge`, to //include that when set to true copies all the children elements of the nested model into the parent model without introducing a new scope.

Signed-off-by: Addisu Z. Taddese <[email protected]>

Co-authored-by: Steve Peters <[email protected]>
  • Loading branch information
azeey and scpeters authored Sep 20, 2021
1 parent b6fa291 commit 3b4d136
Show file tree
Hide file tree
Showing 21 changed files with 1,359 additions and 71 deletions.
2 changes: 1 addition & 1 deletion doc/sdf.in
Original file line number Diff line number Diff line change
Expand Up @@ -1510,7 +1510,7 @@ SEARCH_INCLUDES = YES
# contain include files that are not input files but should be processed by
# the preprocessor.

INCLUDE_PATH = .
INCLUDE_PATH = @PROJECT_BINARY_DIR@ @PROJECT_SOURCE_DIR@/include

# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
# patterns (like *.h and *.hpp) to filter out the header-files in the
Expand Down
13 changes: 13 additions & 0 deletions include/sdf/Error.hh
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,10 @@ namespace sdf

/// \brief The provided version has been deprecated or it is pre-versioning
VERSION_DEPRECATED,

/// \brief Merge include is unspported for the type of entity being
/// included.
MERGE_INCLUDE_UNSUPPORTED,
};

class SDFORMAT_VISIBLE Error
Expand Down Expand Up @@ -188,10 +192,19 @@ namespace sdf
/// nullopt otherwise.
public: std::optional<std::string> FilePath() const;

/// \brief Sets the file path that is associated with this error.
/// \param[in] _filePath The file path that is related to this error. (e.g.
/// /tmp/test_file.sdf)
public: void SetFilePath(const std::string &_filePath);

/// \brief Get the line number associated with this error.
/// \return Returns the line number. nullopt otherwise.
public: std::optional<int> LineNumber() const;

/// \brief Sets the line number that is associated with this error.
/// \param[in] _lineNumber The line number that is related to this error.
public: void SetLineNumber(int _lineNumber);

/// \brief Get the XPath-like trace that is associated with this error.
/// \return Returns the XPath-like trace that this error is related to,
/// nullopt otherwise.
Expand Down
145 changes: 135 additions & 10 deletions include/sdf/InterfaceElements.hh
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include <memory>

#include <ignition/math/Pose3.hh>
#include <ignition/utils/ImplPtr.hh>

#include "sdf/Element.hh"
#include "sdf/InterfaceModel.hh"
Expand All @@ -40,53 +41,177 @@ inline namespace SDF_VERSION_NAMESPACE
#endif
/// \brief Contains the necessary information about an included model file
/// for custom model parsers to be able to find the file and parse it.
struct SDFORMAT_VISIBLE NestedInclude
class SDFORMAT_VISIBLE NestedInclude
{
/// \brief Constructor
public: NestedInclude();
// Defaulted copy, move constructors and destructors are needed to avoid
// deprecation warnings on memeber variables when simply instantiating this
// class.
// TODO(anyone) Remove the constructor and destructor once the deprecated
// members are removed.
SDF_SUPPRESS_DEPRECATED_BEGIN
public: NestedInclude(const NestedInclude&) = default;
public: NestedInclude(NestedInclude&&) = default;
public: NestedInclude& operator=(const NestedInclude&) = default;
public: NestedInclude& operator=(NestedInclude&&) = default;
public: ~NestedInclude() = default;
SDF_SUPPRESS_DEPRECATED_END

/// \brief Provides the URI as specified in `//include/uri`. This may or may
/// not end with a file extension (it will not end with an extension if it
/// refers to a model package).
/// \return URI of the included model
public: const std::string &Uri() const;

/// \brief Set the URI of the included model
/// \param[in] _uri URI of the included model
public: void SetUri(const std::string &_uri);

/// \brief Provides the *resolved* absolute file path from the URI.
/// It is recommended to use this in `CustomModelParser` when checking
/// predicates on filenames -- however, the predicates should generally only
/// check the file extension.
/// \return The resolved absolute file path from the URI.
public: const std::string &ResolvedFileName() const;

/// \brief Set the resolved absolute file path.
/// \param[in] _resolvedFileName The resolved absolute file path
public: void SetResolvedFileName(const std::string &_resolvedFileName);

/// \brief Name of the parent entity in absolute hierarchy.
/// Example: if the interface model's name is
/// `top_model::middle_model::my_new_model`, the absoluteParentName would be
/// `top_model::middle_model`. If the parent entity is the world, this would
/// be an empty string.
/// \return Absolute name of parent entity
public: const std::string &AbsoluteParentName() const;

/// \brief Set the absolute name of parent entity
/// \param[in] _absoluteparentname Absolute name of parent entity
public: void SetAbsoluteParentName(const std::string &_absoluteparentname);

/// \brief Name relative to immediate parent as specified in
/// `//include/name`. This is nullopt if `//include/name` is not set. Then the
/// name of the model must be determined by the custom model parser from the
/// included model file.
/// Example: `my_new_model`
/// \return The local name. nullopt if `//include/name` is not set
public: const std::optional<std::string> &LocalModelName() const;

/// \brief Set the name relative to immediate parent as specified in
/// `//include/name`
/// \param[in] _localModelName The local name
public: void SetLocalModelName(const std::string &_localModelName);

/// \brief Whether the model is static as defined by `//include/static`. This
/// is nullopt if `//include/static` is not set.
/// \return Whether the model is static. nullopt if `//include/static` is not
/// set.
public: const std::optional<bool> &IsStatic() const;

/// \brief Set whether the model is static.
/// \param[in] _isStatic True if the model is static.
public: void SetIsStatic(bool _isStatic);

/// \brief The raw pose as specified in `//include/pose`. This is nullopt if
/// `//include/pose` is not set.
/// \return The raw pose. nullopt if `//include/pose` is not set.
public: const std::optional<ignition::math::Pose3d> &IncludeRawPose() const;

/// \brief Set the raw pose as specified in `//include/pose`.
/// \param[in] _includeRawPose The raw pose
public: void SetIncludeRawPose(const ignition::math::Pose3d &_includeRawPose);

/// \brief The relative-to frame of the pose as specified in
/// `//include/pose/@relative_to`. This is nullopt if
/// `//include/pose/@relative_to` is not set.
/// \return The relative-to frame of the pose. nullopt if
/// `//include/pose/@relative_to` is not set.
public: const std::optional<std::string> &IncludePoseRelativeTo() const;

/// \brief Set the relative-to frame of the pose.
/// \param[in] _includePoseRelativeTo The relative-to frame.
public: void SetIncludePoseRelativeTo(
const std::string &_includePoseRelativeTo);

/// \brief The placement frame as specified in `//include/placement_frame`.
/// This is nullopt if `//include/placement_frame` is is not set.
/// \return The placement frame. nullopt if `//include/placement_frame` is is
/// not set.
public: const std::optional<std::string> &PlacementFrame() const;

/// \brief Set the placement frame.
/// \param[in] _placementFrame The placement frame.
public: void SetPlacementFrame(const std::string &_placementFrame);

/// This is the `//include` element. This can be used to pass custom elements
/// and attributes to the custom model parser.
/// \return The `//include` element
public: sdf::ElementPtr IncludeElement() const;

/// Set the `//include` element.
/// \param[in] _includeElement The include element
public: void SetIncludeElement(sdf::ElementPtr _includeElement);

/// \brief Provides the URI as specified in `//include/uri`. This may or may
/// not end with a file extension (it will not end with an extension if it
/// refers to a model package).
std::string uri;
/// \deprecated Use NestedInclude::Uri() instead
public: std::string uri SDF_DEPRECATED(12);

/// \brief Provides the *resolved* absolute file path from the URI.
/// It is recommended to use this in `CustomModelParser` when checking
/// predicates on filenames -- however, the predicates should generally only
/// check the file extension.
std::string resolvedFileName;
/// \deprecated Use NestedInclude::ResolvedFileName() instead
public: std::string resolvedFileName SDF_DEPRECATED(12);

/// \brief Name of the parent entity in absolute hierarchy.
/// Example: if the interface model's name is
/// `top_model::middle_model::my_new_model`, the absoluteParentName would be
/// `top_model::middle_model`. If the parent entity is the world, this would
/// be an empty string.
std::string absoluteParentName;
/// \deprecated Use NestedInclude::AbsoluteParentName() instead
public: std::string absoluteParentName SDF_DEPRECATED(12);

/// \brief Name relative to immediate parent as specified in
/// `//include/name`. This is nullopt if `//include/name` is not set. Then the
/// name of the model must be determined by the custom model parser from the
/// included model file.
/// Example: `my_new_model`
std::optional<std::string> localModelName;
/// \deprecated Use NestedInclude::LocalModelName() instead
public: std::optional<std::string> localModelName SDF_DEPRECATED(12);

/// \brief Whether the model is static as defined by `//include/static`. This
/// is nullopt if `//include/static` is not set.
std::optional<bool> isStatic;
/// \deprecated Use NestedInclude::IsStatic() instead
public: std::optional<bool> isStatic SDF_DEPRECATED(12);

/// \brief The raw pose as specified in //include/pose. This is nullopt if
/// `//include/pose` is not set.
std::optional<ignition::math::Pose3d> includeRawPose;
/// \deprecated Use NestedInclude::IncludeRawPose() instead
public: std::optional<ignition::math::Pose3d> includeRawPose
SDF_DEPRECATED(12);

/// \brief The relative-to frame of the pose as specified in
/// `//include/pose/@relative_to`. This is nullopt if
/// `//include/pose/@relative_to` is not set.
std::optional<std::string> includePoseRelativeTo;
/// \deprecated Use NestedInclude::IncludePoseRelativeTo() instead
public: std::optional<std::string> includePoseRelativeTo SDF_DEPRECATED(12);

/// \brief The placement frame as specified in `//include/placement_frame`.
/// This is nullopt if `//include/placement_frame` is is not set.
std::optional<std::string> placementFrame;
/// \deprecated Use NestedInclude::PlacementFrame() instead
public: std::optional<std::string> placementFrame SDF_DEPRECATED(12);

/// This is the `//include` element. This can be used to pass custom elements
/// and attributes to the custom model parser.
sdf::ElementPtr includeElement;
/// \deprecated Use NestedInclude::IncludeElement() instead
public: sdf::ElementPtr includeElement SDF_DEPRECATED(12);

/// \brief Private data pointer.
IGN_UTILS_IMPL_PTR(dataPtr)
};
#ifdef _MSC_VER
#pragma warning(pop)
Expand Down
2 changes: 1 addition & 1 deletion include/sdf/Model.hh
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ namespace sdf
class Joint;
class Link;
class ParserConfig;
struct NestedInclude;
class NestedInclude;
struct PoseRelativeToGraph;
struct FrameAttachedToGraph;
template <typename T> class ScopedGraph;
Expand Down
2 changes: 1 addition & 1 deletion include/sdf/World.hh
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ namespace sdf
class Model;
class ParserConfig;
class Physics;
struct NestedInclude;
class NestedInclude;
struct PoseRelativeToGraph;
struct FrameAttachedToGraph;
template <typename T> class ScopedGraph;
Expand Down
4 changes: 4 additions & 0 deletions sdf/1.9/model.sdf
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@
<description>
Include resources from a URI. This can be used to nest models. Included resources can only contain one 'model', 'light' or 'actor' element. The URI can point to a directory or a file. If the URI is a directory, it must conform to the model database structure (see /tutorials?tut=composition&amp;cat=specification&amp;#defining-models-in-separate-files).
</description>
<attribute name="merge" type="bool" default="false" required="0">
<description>Merge the included nested model into the top model</description>
</attribute>

<element name="uri" type="string" default="__default__" required="1">
<description>URI to a resource, such as a model</description>
</element>
Expand Down
2 changes: 2 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ set (sources
Gui.cc
Heightmap.cc
ign.cc
InterfaceElements.cc
InterfaceFrame.cc
InterfaceJoint.cc
InterfaceLink.cc
Expand Down Expand Up @@ -114,6 +115,7 @@ if (BUILD_SDF_TEST)
Gui_TEST.cc
Heightmap_TEST.cc
Imu_TEST.cc
InterfaceElements_TEST.cc
Joint_TEST.cc
JointAxis_TEST.cc
Lidar_TEST.cc
Expand Down
11 changes: 11 additions & 0 deletions src/Error.cc
Original file line number Diff line number Diff line change
Expand Up @@ -88,13 +88,24 @@ std::optional<std::string> Error::FilePath() const
{
return this->dataPtr->filePath;
}
/////////////////////////////////////////////////
void Error::SetFilePath(const std::string &_filePath)
{
this->dataPtr->filePath = _filePath;
}

/////////////////////////////////////////////////
std::optional<int> Error::LineNumber() const
{
return this->dataPtr->lineNumber;
}

/////////////////////////////////////////////////
void Error::SetLineNumber(int _lineNumber)
{
this->dataPtr->lineNumber = _lineNumber;
}

/////////////////////////////////////////////////
std::optional<std::string> Error::XmlPath() const
{
Expand Down
13 changes: 13 additions & 0 deletions src/Error_TEST.cc
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,20 @@ TEST(Error, DefaultConstruction)
EXPECT_FALSE(error.LineNumber().has_value());

if (error)
{
FAIL();
}
error.SetXmlPath("/sdf/world");
ASSERT_TRUE(error.XmlPath().has_value());
EXPECT_EQ("/sdf/world", error.XmlPath());

error.SetFilePath("/tmp/test_file.sdf");
ASSERT_TRUE(error.FilePath().has_value());
EXPECT_EQ("/tmp/test_file.sdf", error.FilePath());

error.SetLineNumber(5);
ASSERT_TRUE(error.LineNumber().has_value());
EXPECT_EQ(5, error.LineNumber());
}

/////////////////////////////////////////////////
Expand Down
16 changes: 8 additions & 8 deletions src/FrameSemantics.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1154,7 +1154,7 @@ Errors buildPoseRelativeToGraph(
auto relativeToId = modelFrameId;

const std::string &relativeTo =
nestedInclude->includePoseRelativeTo.value_or("");
nestedInclude->IncludePoseRelativeTo().value_or("");
if (!relativeTo.empty())
{
// look for vertex in graph that matches relative_to value
Expand All @@ -1180,11 +1180,11 @@ Errors buildPoseRelativeToGraph(
}

ignition::math::Pose3d resolvedModelPose =
nestedInclude->includeRawPose.value_or(ignition::math::Pose3d());
nestedInclude->IncludeRawPose().value_or(ignition::math::Pose3d());

sdf::Errors resolveErrors = resolveModelPoseWithPlacementFrame(
nestedInclude->includeRawPose.value_or(ignition::math::Pose3d()),
nestedInclude->placementFrame.value_or(""),
nestedInclude->IncludeRawPose().value_or(ignition::math::Pose3d()),
nestedInclude->PlacementFrame().value_or(""),
outModel.ChildModelScope(ifaceModel->Name()), resolvedModelPose);
errors.insert(errors.end(), resolveErrors.begin(), resolveErrors.end());

Expand Down Expand Up @@ -1517,7 +1517,7 @@ Errors buildPoseRelativeToGraph(
auto relativeToId = worldFrameId;

const std::string &relativeTo =
nestedInclude->includePoseRelativeTo.value_or("");
nestedInclude->IncludePoseRelativeTo().value_or("");
if (!relativeTo.empty())
{
// look for vertex in graph that matches relative_to value
Expand All @@ -1543,11 +1543,11 @@ Errors buildPoseRelativeToGraph(
}

ignition::math::Pose3d resolvedModelPose =
nestedInclude->includeRawPose.value_or(ignition::math::Pose3d());
nestedInclude->IncludeRawPose().value_or(ignition::math::Pose3d());

sdf::Errors resolveErrors = resolveModelPoseWithPlacementFrame(
nestedInclude->includeRawPose.value_or(ignition::math::Pose3d()),
nestedInclude->placementFrame.value_or(""),
nestedInclude->IncludeRawPose().value_or(ignition::math::Pose3d()),
nestedInclude->PlacementFrame().value_or(""),
_out.ChildModelScope(ifaceModel->Name()), resolvedModelPose);
errors.insert(errors.end(), resolveErrors.begin(), resolveErrors.end());

Expand Down
Loading

0 comments on commit 3b4d136

Please sign in to comment.