Skip to content

Commit

Permalink
[Simulation] Trigger warning when Node already contains component (so…
Browse files Browse the repository at this point in the history
…fa-framework#5212)

* [Simulation] Trigger error when Node already contains component

* also mention the class name

* convert error to warning

* fix STLExporter_test
  • Loading branch information
alxbilger authored Jan 31, 2025
1 parent 9ceb6dc commit 6af25d5
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 36 deletions.
58 changes: 25 additions & 33 deletions Sofa/Component/IO/Mesh/tests/STLExporter_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -90,25 +90,22 @@ class STLExporter_test : public BaseSimulationTest {
}
}

void checkBasicBehavior(const std::string& filename, std::vector<std::string> pathes){
void checkBasicBehavior(const std::string& filename, std::vector<std::string> pathes)
{
dataPath = pathes ;

EXPECT_MSG_NOEMIT(Error, Warning) ;
std::stringstream scene1;
scene1 <<
"<?xml version='1.0'?> \n"
"<Node name='Root' gravity='0 0 0' time='0' animate='0' > \n"
" <DefaultAnimationLoop/> \n"
" <MechanicalObject position='0 1 2 3 4 5 6 7 8 9'/> \n"
" <MeshOBJLoader name='loader' filename='mesh/liver-smooth.obj'/> \n"
" <VisualModel src='@loader'/> \n"
" <STLExporter name='exporter1' printLog='false' filename='"<< filename << "' exportAtBegin='true' /> \n"
"</Node> \n" ;

const Node::SPtr root = SceneLoaderXML::loadFromMemory("testscene", scene1.str().c_str());

ASSERT_NE(root.get(), nullptr) << scene1.str() ;
root->init(sofa::core::execparams::defaultInstance()) ;

const Node::SPtr root = sofa::simpleapi::createRootNode(sofa::simulation::getSimulation(), "root", {{"gravity", "0 0 0"}});
sofa::simpleapi::createObject(root, "DefaultAnimationLoop");
sofa::simpleapi::createObject(root, "MechanicalObject", {{"position", "0 1 2 3 4 5 6 7 8 9"}});
sofa::simpleapi::createObject(root, "MeshOBJLoader", {{"name", "loader"}, {"filename", "mesh/liver-smooth.obj"}});
const Node::SPtr visualNode = sofa::simpleapi::createChild(root, "Visual");
sofa::simpleapi::createObject(visualNode, "VisualModel", {{"src", "@../loader"}});
sofa::simpleapi::createObject(visualNode, "STLExporter", {{"name", "exporter1"}, {"filename", filename}, {"exportAtBegin", "true"}});

ASSERT_NE(root.get(), nullptr);
sofa::simulation::node::initRoot(root.get());

// SimulationInitDoneEvent is used to trigger exportAtBegin
SimulationInitDoneEvent endInit;
Expand All @@ -119,32 +116,27 @@ class STLExporter_test : public BaseSimulationTest {

for(auto& pathToCheck : pathes)
{
EXPECT_TRUE( FileSystem::exists(pathToCheck) ) << "Problem with '" << pathToCheck << "'"<< std::endl
<< "================= scene dump ==========================="
<< scene1.str() ;
EXPECT_TRUE( FileSystem::exists(pathToCheck) );
}
}


void checkSimulationWriteEachNbStep(const std::string& filename, std::vector<std::string> pathes, unsigned int numstep){
void checkSimulationWriteEachNbStep(const std::string& filename, std::vector<std::string> pathes, unsigned int numstep)
{
dataPath = pathes ;

EXPECT_MSG_NOEMIT(Error, Warning) ;
std::stringstream scene1;
scene1 <<
"<?xml version='1.0'?> \n"
"<Node name='Root' gravity='0 0 0' time='0' animate='0' > \n"
" <DefaultAnimationLoop/> \n"
" <MechanicalObject position='0 1 2 3 4 5 6 7 8 9'/> \n"
" <MeshOBJLoader name='loader' filename='mesh/liver-smooth.obj'/> \n"
" <VisualModel src='@loader'/> \n"
" <STLExporter name='exporterA' printLog='false' filename='"<< filename << "' exportEveryNumberOfSteps='5' /> \n"
"</Node> \n" ;

const Node::SPtr root = SceneLoaderXML::loadFromMemory("testscene", scene1.str().c_str());

const Node::SPtr root = sofa::simpleapi::createRootNode(sofa::simulation::getSimulation(), "root", {{"gravity", "0 0 0"}});
sofa::simpleapi::createObject(root, "DefaultAnimationLoop");
sofa::simpleapi::createObject(root, "MechanicalObject", {{"position", "0 1 2 3 4 5 6 7 8 9"}});
sofa::simpleapi::createObject(root, "MeshOBJLoader", {{"name", "loader"}, {"filename", "mesh/liver-smooth.obj"}});
const Node::SPtr visualNode = sofa::simpleapi::createChild(root, "Visual");
sofa::simpleapi::createObject(visualNode, "VisualModel", {{"src", "@../loader"}});
sofa::simpleapi::createObject(visualNode, "STLExporter", {{"name", "exporterA"}, {"filename", filename}, {"exportEveryNumberOfSteps", "5"}});

ASSERT_NE(root.get(), nullptr) ;
root->init(sofa::core::execparams::defaultInstance()) ;
sofa::simulation::node::initRoot(root.get());

for(unsigned int i=0;i<numstep;i++)
{
Expand Down
20 changes: 19 additions & 1 deletion Sofa/framework/Simulation/Core/src/sofa/simulation/Node.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1024,8 +1024,26 @@ void Node::setSleeping(bool val)
}
}

template<class LinkType, class Component>
void checkAlreadyContains(Node& self, LinkType& link, Component* obj)
{
if constexpr (!LinkType::IsMultiLink)
{
if (link != obj && link != nullptr)
{
static const auto componentClassName = Component::GetClass()->className;
msg_warning(&self) << "Trying to add a " << componentClassName << " ('"
<< obj->getName() << "' [" << obj->getClassName() << "] " << obj << ")"
<< " into the Node '" << self.getPathName()
<< "', whereas it already contains one ('" << link->getName() << "' [" << link->getClassName() << "] " << link.get() << ")."
<< " Only one " << componentClassName << " is permitted in a Node. The previous "
<< componentClassName << " is replaced and the behavior is undefined.";
}
}
}

#define NODE_DEFINE_SEQUENCE_ACCESSOR( CLASSNAME, FUNCTIONNAME, SEQUENCENAME ) \
void Node::add##FUNCTIONNAME( CLASSNAME* obj ) { SEQUENCENAME.add(obj); } \
void Node::add##FUNCTIONNAME( CLASSNAME* obj ) { checkAlreadyContains(*this, SEQUENCENAME, obj); SEQUENCENAME.add(obj); } \
void Node::remove##FUNCTIONNAME( CLASSNAME* obj ) { SEQUENCENAME.remove(obj); }

NODE_DEFINE_SEQUENCE_ACCESSOR( sofa::core::behavior::BaseAnimationLoop, AnimationLoop, animationManager )
Expand Down
55 changes: 55 additions & 0 deletions Sofa/framework/Simulation/Graph/test/Node_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
* *
* Contact information: [email protected] *
******************************************************************************/
#include <sofa/core/behavior/BaseMechanicalState.h>
#include <sofa/simulation/MechanicalVisitor.h>
#include <sofa/testing/BaseSimulationTest.h>
using sofa::testing::BaseSimulationTest ;

Expand All @@ -33,6 +35,9 @@ namespace sofa

TEST( Node_test, getPathName)
{
// required to be able to use EXPECT_MSG_NOEMIT and EXPECT_MSG_EMIT
sofa::helper::logging::MessageDispatcher::addHandler(sofa::testing::MainGtestMessageHandler::getInstance() ) ;

/* create trivial DAG :
*
* A
Expand Down Expand Up @@ -90,6 +95,9 @@ TEST(Node_test, addObjectAtFront)

TEST(Node_test, addObjectPreventingSharedContext)
{
// required to be able to use EXPECT_MSG_NOEMIT and EXPECT_MSG_EMIT
sofa::helper::logging::MessageDispatcher::addHandler(sofa::testing::MainGtestMessageHandler::getInstance() ) ;

const sofa::core::sptr<Node> root = sofa::simpleapi::createNode("root");

const BaseObject::SPtr A = core::objectmodel::New<Dummy>("A");
Expand Down Expand Up @@ -177,6 +185,53 @@ TEST(Node_test, getObjectsStdUnorderedSet)
EXPECT_NE(objects.find(B.get()), objects.end());
}

class CounterVisitor : public simulation::MechanicalVisitor
{
public:
using MechanicalVisitor::MechanicalVisitor;

Result fwdMechanicalState(simulation::Node* node, sofa::core::behavior::BaseMechanicalState* state) override
{
SOFA_UNUSED(node);
SOFA_UNUSED(state);
m_counter++;
return Result::RESULT_CONTINUE;
}

Result fwdMappedMechanicalState(simulation::Node* node, sofa::core::behavior::BaseMechanicalState* state) override
{
SOFA_UNUSED(node);
SOFA_UNUSED(state);
++m_counter;
return Result::RESULT_CONTINUE;
}

std::size_t m_counter = 0;
};

TEST(Node_test, twoMechanicalStatesInTheSameNode)
{
// required to be able to use EXPECT_MSG_NOEMIT and EXPECT_MSG_EMIT
sofa::helper::logging::MessageDispatcher::addHandler(sofa::testing::MainGtestMessageHandler::getInstance() ) ;

const sofa::core::sptr<Node> root = sofa::simpleapi::createNode("root");

const auto plugins = testing::makeScopedPlugin({Sofa.Component.StateContainer});
sofa::simpleapi::createObject(root, "MechanicalObject", {{"template", "Vec3"}, {"name", "A"}});

EXPECT_MSG_EMIT(Warning);
sofa::simpleapi::createObject(root, "MechanicalObject", {{"template", "Vec3"}, {"name", "B"}});

//the last added state is the one in Node
EXPECT_EQ(root->mechanicalState->getName(), "B");

CounterVisitor visitor(core::MechanicalParams::defaultInstance());
root->executeVisitor(&visitor);

//only one of the two added states is visited
EXPECT_EQ(visitor.m_counter, 1);
}

}// namespace sofa


Expand Down
3 changes: 1 addition & 2 deletions Sofa/framework/Simulation/Graph/test/Simulation_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -223,10 +223,9 @@ struct Scene_test: public NumericTest<SReal>
root = simulation::getSimulation()->createNewGraph("root");
root->addObject(core::objectmodel::New<InstrumentedObject<MechanicalObject3> >());

typename UniformMass3::SPtr uniformMass = core::objectmodel::New<UniformMass3>();
typename UniformMass3::SPtr uniformMass = core::objectmodel::New<InstrumentedObject<UniformMass3>>();
uniformMass->d_totalMass.setValue(1.0);
root->addObject(uniformMass);
root->addObject(core::objectmodel::New<InstrumentedObject<UniformMass3> >());

const simulation::Node::SPtr child = simulation::getSimulation()->createNewNode("child");
root->addChild(child);
Expand Down

0 comments on commit 6af25d5

Please sign in to comment.