Skip to content

Commit 0f83de6

Browse files
authored
Merge pull request o3de#578 from aws-lumberyard-dev/sdf-parser-nested-model-test
Adding UnitTest for verifying import of nested SDF import via the `<include>` tag
2 parents 15f86f3 + 9b2d78a commit 0f83de6

File tree

4 files changed

+468
-253
lines changed

4 files changed

+468
-253
lines changed

Gems/ROS2/Code/Source/RobotImporter/URDF/URDFPrefabMaker.cpp

Lines changed: 99 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,8 @@ namespace ROS2
7373
struct JointToAttachedModel
7474
{
7575
AZStd::string m_fullyQualifiedName;
76-
const sdf::Joint* m_joint;
77-
const sdf::Model* m_attachedModel;
76+
const sdf::Joint* m_joint{};
77+
const sdf::Model* m_attachedModel{};
7878
};
7979
// this is a unique ordered vector
8080
AZStd::vector<JointToAttachedModel> m_joints;
@@ -121,41 +121,120 @@ namespace ROS2
121121
struct LinkToAttachedModel
122122
{
123123
AZStd::string m_fullyQualifiedName;
124-
const sdf::Link* m_link;
125-
const sdf::Model* m_attachedModel;
124+
const sdf::Link* m_link{};
125+
const sdf::Model* m_attachedModel{};
126126
};
127127
// this is a unique ordered vector
128128
AZStd::vector<LinkToAttachedModel> m_links;
129129
};
130130
LinksMapper linksMapper;
131131

132-
auto GetAllLinksFromModel = [&linksMapper](const sdf::Model& model, const Utils::ModelStack&) -> Utils::VisitModelResponse
132+
struct ModelMapper
133+
{
134+
struct NestedModelToAttachedModel
135+
{
136+
AZStd::string m_fullyQualifiedName;
137+
const sdf::Model* m_nestedModel{};
138+
const sdf::Model* m_attachedModel{};
139+
};
140+
// this is a unique ordered vector
141+
AZStd::vector<NestedModelToAttachedModel> m_models;
142+
};
143+
ModelMapper modelMapper;
144+
145+
auto GetAllLinksAndSetModelHierarchy = [&linksMapper, &modelMapper](const sdf::Model& model, const Utils::ModelStack& modelStack) -> Utils::VisitModelResponse
133146
{
134147
// As the VisitModels function visits nested models by default, gatherNestedModelLinks is set to false
135148
constexpr bool gatherNestedModelLinks = false;
136149
auto linksForModel = Utils::GetAllLinks(model, gatherNestedModelLinks);
137150
for (const auto& [fullyQualifiedName, link] : linksForModel)
138151
{
139152
// Push back the mapping of link to attached model into the ordered vector
140-
LinksMapper::LinkToAttachedModel linkToAttachedModel{ AZStd::string(fullyQualifiedName.c_str(), fullyQualifiedName.size()),
141-
link,
142-
&model };
153+
AZStd::string fullLinkName(fullyQualifiedName.c_str(), fullyQualifiedName.size());
154+
LinksMapper::LinkToAttachedModel linkToAttachedModel{ AZStd::move(fullLinkName), link, &model };
143155
linksMapper.m_links.push_back(AZStd::move(linkToAttachedModel));
144156
}
157+
158+
// Use the model stack to create a mapping from the current model
159+
// to the parent model it is attached to.
160+
// If the current model has no parent model the attached model is set to nullptr
161+
std::string stdFullModelName;
162+
for (const sdf::Model& ancestorModel : modelStack)
163+
{
164+
stdFullModelName = sdf::JoinName(stdFullModelName, ancestorModel.Name());
165+
}
166+
stdFullModelName = sdf::JoinName(stdFullModelName, model.Name());
167+
AZStd::string fullModelName(stdFullModelName.c_str(), stdFullModelName.size());
168+
ModelMapper::NestedModelToAttachedModel nestedModelToAttachedModel;
169+
nestedModelToAttachedModel.m_fullyQualifiedName = AZStd::move(fullModelName);
170+
nestedModelToAttachedModel.m_nestedModel = &model;
171+
nestedModelToAttachedModel.m_attachedModel = !modelStack.empty() ? &modelStack.back().get() : nullptr;
172+
modelMapper.m_models.push_back(AZStd::move(nestedModelToAttachedModel));
173+
145174
return Utils::VisitModelResponse::VisitNestedAndSiblings;
146175
};
147176

148-
// Gather all links from all the models in the SDF
149-
Utils::VisitModels(*m_root, GetAllLinksFromModel, visitNestedModels);
177+
// Gather all links and add a mapping of nested model -> parent model for each model in the SDF
178+
Utils::VisitModels(*m_root, GetAllLinksAndSetModelHierarchy, visitNestedModels);
150179

151180
// Build up a list of all entities created as a part of processing the file.
152181
AZStd::vector<AZ::EntityId> createdEntities;
153-
AZStd::unordered_map<const sdf::Link*, AzToolsFramework::Prefab::PrefabEntityResult> createdLinks;
154182
AZStd::unordered_map<const sdf::Model*, AzToolsFramework::Prefab::PrefabEntityResult> createdModels;
183+
184+
AZStd::unordered_map<const sdf::Link*, AzToolsFramework::Prefab::PrefabEntityResult> createdLinks;
155185
AZStd::unordered_map<AZStd::string, const sdf::Link*> links;
156-
for ([[maybe_unused]] const auto& [fullLinkName, linkPtr, attachedModel] : linksMapper.m_links)
186+
187+
// Create an entity for each model
188+
for ([[maybe_unused]] const auto& [fullModelName, modelPtr, _] : modelMapper.m_models)
189+
{
190+
// Create entities for each model in the SDF
191+
if (AzToolsFramework::Prefab::PrefabEntityResult createModelEntityResult = CreateEntityForModel(*modelPtr);
192+
createModelEntityResult)
193+
{
194+
AZ::EntityId createdModelEntityId = createModelEntityResult.GetValue();
195+
// Add the model entity to the created entity list so that it gets added to the prefab
196+
createdEntities.emplace_back(createdModelEntityId);
197+
198+
std::string modelName = modelPtr->Name();
199+
AZStd::string azModelName(modelName.c_str(), modelName.size());
200+
AZStd::lock_guard<AZStd::mutex> lck(m_statusLock);
201+
m_status.emplace(azModelName, AZStd::string::format("[model] created as: %s", createdModelEntityId.ToString().c_str()));
202+
createdModels.emplace(modelPtr, createModelEntityResult);
203+
}
204+
}
205+
206+
//! Setup the parent hierarchy for the nested models
207+
for ([[maybe_unused]] const auto& [_, modelPtr, parentModelPtr] : modelMapper.m_models)
208+
{
209+
// If there is no parent model, then the model would be at the top level of the hiearachy
210+
if (parentModelPtr == nullptr || modelPtr == nullptr)
211+
{
212+
continue;
213+
}
214+
// Create entities for each model in the SDF
215+
AZ::EntityId modelEntityId;
216+
if (auto modelIt = createdModels.find(modelPtr); modelIt != createdModels.end() && modelIt->second)
217+
{
218+
modelEntityId = modelIt->second.GetValue();
219+
}
220+
221+
AZ::EntityId parentModelEntityId;
222+
if (auto parentModelIt = createdModels.find(parentModelPtr); parentModelIt != createdModels.end() && parentModelIt->second)
223+
{
224+
parentModelEntityId = parentModelIt->second.GetValue();
225+
}
226+
227+
// If the both the parent model and current model entity exist
228+
// set the current model transform component parent to the parent model
229+
if (parentModelEntityId.IsValid() && modelEntityId.IsValid())
230+
{
231+
PrefabMakerUtils::SetEntityParent(modelEntityId, parentModelEntityId);
232+
}
233+
}
234+
235+
// Create an entity for each link and set the parent to be the model entity where the link is attached
236+
for ([[maybe_unused]] const auto& [_, linkPtr, attachedModel] : linksMapper.m_links)
157237
{
158-
// Create entities for the model containing the link
159238
AZ::EntityId modelEntityId;
160239
if (attachedModel != nullptr)
161240
{
@@ -166,26 +245,7 @@ namespace ROS2
166245
modelEntityId = modelIt->second.GetValue();
167246
}
168247
}
169-
else
170-
{
171-
if (AzToolsFramework::Prefab::PrefabEntityResult createModelEntityResult = CreateEntityForModel(*attachedModel);
172-
createModelEntityResult)
173-
{
174-
modelEntityId = createModelEntityResult.GetValue();
175-
// Add the model entity to the created entity list
176-
// so that it gets added to the prefab
177-
createdEntities.emplace_back(modelEntityId);
178-
179-
std::string modelName = attachedModel->Name();
180-
AZStd::string azModelName(modelName.c_str(), modelName.size());
181-
AZStd::lock_guard<AZStd::mutex> lck(m_statusLock);
182-
m_status.emplace(azModelName, AZStd::string::format("created as: %s", modelEntityId.ToString().c_str()));
183-
createdModels.emplace(attachedModel, createModelEntityResult);
184-
}
185-
}
186-
187248
}
188-
189249
// Add all link as children of their attached model entity by default
190250
createdLinks[linkPtr] = AddEntitiesForLink(*linkPtr, attachedModel, modelEntityId, createdEntities);
191251
}
@@ -202,11 +262,11 @@ namespace ROS2
202262
AZStd::lock_guard<AZStd::mutex> lck(m_statusLock);
203263
if (result.IsSuccess())
204264
{
205-
m_status.emplace(azLinkName, AZStd::string::format("created as: %s", result.GetValue().ToString().c_str()));
265+
m_status.emplace(azLinkName, AZStd::string::format("[link] created as: %s", result.GetValue().ToString().c_str()));
206266
}
207267
else
208268
{
209-
m_status.emplace(azLinkName, AZStd::string::format("failed : %s", result.GetError().c_str()));
269+
m_status.emplace(azLinkName, AZStd::string::format("[link] failed : %s", result.GetError().c_str()));
210270
}
211271
}
212272

@@ -390,7 +450,7 @@ namespace ROS2
390450
AZStd::lock_guard<AZStd::mutex> lck(m_statusLock);
391451
auto result = m_jointsMaker.AddJointComponent(jointPtr, childEntity.GetValue(), leadEntity.GetValue());
392452
m_status.emplace(
393-
azJointName, AZStd::string::format(" %s: %llu", result.IsSuccess() ? "created as" : "failed", result.GetValue()));
453+
azJointName, AZStd::string::format("[joint] %s: %llu", result.IsSuccess() ? "created as" : "failed", result.GetValue()));
394454
}
395455
else
396456
{
@@ -508,64 +568,11 @@ namespace ROS2
508568
if (auto* transformComponent = entity->FindComponent<AzToolsFramework::Components::TransformComponent>();
509569
transformComponent != nullptr)
510570
{
511-
gz::math::Pose3d modelPose;
512-
if (sdf::Errors poseResolveErrors = model.SemanticPose().Resolve(modelPose); !poseResolveErrors.empty())
513-
{
514-
AZStd::string poseErrorMessages = Utils::JoinSdfErrorsToString(poseResolveErrors);
515-
516-
auto poseErrorsForModel = AZStd::string::format(
517-
R"(Unable to resolve semantic pose for model %s. Creation of Model entity has failed. Errors: "%s")",
518-
model.Name().c_str(),
519-
poseErrorMessages.c_str());
520-
521-
return AZ::Failure(poseErrorsForModel);
522-
}
523-
571+
gz::math::Pose3d modelPose = model.RawPose();
524572
AZ::Transform modelTransform = URDF::TypeConversions::ConvertPose(modelPose);
525-
526-
// If the model is nested below another model, check if it has a placement frame
527-
if (const sdf::Model* parentModel = Utils::GetModelContainingModel(*m_root, model); parentModel != nullptr)
528-
{
529-
if (const sdf::Frame* modelPlacementFrame = parentModel->FrameByName(model.PlacementFrameName()); modelPlacementFrame != nullptr)
530-
{
531-
gz::math::Pose3d placementFramePose;
532-
if (sdf::Errors poseResolveErrors = modelPlacementFrame->SemanticPose().Resolve(placementFramePose);
533-
!poseResolveErrors.empty())
534-
{
535-
AZStd::string poseErrorMessages = Utils::JoinSdfErrorsToString(poseResolveErrors);
536-
537-
auto poseErrorsForModel = AZStd::string::format(
538-
R"(Unable to resolve semantic pose for model %s parent model %s. Creation of Model entity has failed. Errors: "%s")",
539-
model.Name().c_str(),
540-
parentModel->Name().c_str(),
541-
poseErrorMessages.c_str());
542-
543-
return AZ::Failure(poseErrorsForModel);
544-
}
545-
546-
modelTransform = modelTransform * URDF::TypeConversions::ConvertPose(placementFramePose);
547-
}
548-
}
549-
550-
if (const sdf::Frame* implicitFrame = model.FrameByName("__model__"); implicitFrame != nullptr)
551-
{
552-
gz::math::Pose3d implicitFramePose;
553-
if (sdf::Errors poseResolveErrors = implicitFrame->SemanticPose().Resolve(implicitFramePose); !poseResolveErrors.empty())
554-
{
555-
AZStd::string poseErrorMessages = Utils::JoinSdfErrorsToString(poseResolveErrors);
556-
557-
auto poseErrorsForModel = AZStd::string::format(
558-
R"(Unable to resolve semantic pose for model's implicit frame %s. Creation of Model entity has failed. Errors: "%s")",
559-
implicitFrame->Name().c_str(),
560-
poseErrorMessages.c_str());
561-
562-
return AZ::Failure(poseErrorsForModel);
563-
}
564-
565-
modelTransform = modelTransform * URDF::TypeConversions::ConvertPose(implicitFramePose);
566-
}
567-
568-
transformComponent->SetWorldTM(AZStd::move(modelTransform));
573+
// Set the local transform for each model to have it be translated in relation
574+
// to its parent
575+
transformComponent->SetLocalTM(AZStd::move(modelTransform));
569576
}
570577

571578
// Allow the created model entity to persist if there are no errors at ths point

0 commit comments

Comments
 (0)