diff --git a/src/InfinyToolkit/InteractionTools/ArticulatedToolManager.cpp b/src/InfinyToolkit/InteractionTools/ArticulatedToolManager.cpp index 3a903f0..cc3e78b 100644 --- a/src/InfinyToolkit/InteractionTools/ArticulatedToolManager.cpp +++ b/src/InfinyToolkit/InteractionTools/ArticulatedToolManager.cpp @@ -29,9 +29,11 @@ #include #include +#include #include #include +#include #include @@ -65,8 +67,14 @@ ArticulatedToolManager::ArticulatedToolManager() , l_targetModel(initLink("targetModel", "link to the second jaw model component, if not set will search through graph and take second one encountered.")) , d_handleFactor(initData(&d_handleFactor, SReal(1.0), "handleFactor", "jaw speed factor.")) , d_outputPositions(initData(&d_outputPositions, "outputPositions", "jaw positions.")) + , d_isCutter(initData(&d_isCutter, false, "isCutter", "if true, will draw slices BB, ray and intersected triangles")) + , d_cutMaxStep(initData(&d_cutMaxStep, int(10), "cutMaxStep", "number of step before really cutting")) + , d_cutMode(initData(&d_cutMode, int(0), "cutMode", "mode of cut (debug)")) + , d_isControlled(initData(&d_isControlled, false, "isControlled", "if true, will draw slices BB, ray and intersected triangles")) , d_drawContacts(initData(&d_drawContacts, false, "drawContacts", "if true, will draw slices BB, ray and intersected triangles")) -{ + , d_manageBurning(initData(&d_manageBurning, false, "manageBurning", "if true, will draw slices BB, ray and intersected triangles")) + , m_vtexcoords(initData(&m_vtexcoords, "texcoords", "coordinates of the texture")) +{ this->f_listening.setValue(true); m_idgrabed.clear(); } @@ -138,10 +146,28 @@ void ArticulatedToolManager::init() l_jawModel1->setTargetModel(l_targetModel.get()); l_jawModel2->setTargetModel(l_targetModel.get()); + + m_cutCount = 0; + sofa::core::objectmodel::BaseObject::d_componentState.setValue(sofa::core::objectmodel::ComponentState::Valid); } +void ArticulatedToolManager::bwdInit() +{ + if (d_manageBurning.getValue()) + { + TetrahedronSetTopologyContainer* tetraCon; + l_targetModel->getContext()->get(tetraCon); + + m_vtexcoords.createTopologyHandler(tetraCon); + + helper::WriteAccessor< Data > texcoords = m_vtexcoords; + texcoords.resize(tetraCon->getNbPoints()); + } +} + + int ArticulatedToolManager::testModels() { if (m_jawModel1 == nullptr) @@ -218,6 +244,131 @@ bool ArticulatedToolManager::stopAction() } +bool ArticulatedToolManager::performSecondaryAction() +{ + m_performCut = true; + return true; +} + + + void ArticulatedToolManager::performCut() +{ + //msg_warning() << "performSecondaryAction()"; + if (!d_isCutter.getValue()) + return; + + sofa::type::vector idVGrab1 = l_jawModel1.get()->getRawContactModelIds(); + sofa::type::vector idVGrab2 = l_jawModel2.get()->getRawContactModelIds(); + + // Detect all tetra on the cut path + TetrahedronSetTopologyContainer* tetraCon; + l_targetModel->getContext()->get(tetraCon); + if (tetraCon == nullptr) { + msg_info() << "Error: NO tetraCon"; + return; + } + + std::set idVGrab; + for (auto id : idVGrab1) + { + idVGrab.insert(id); + } + + for (auto id : idVGrab2) + { + idVGrab.insert(id); + } + + const int cutMax = d_cutMaxStep.getValue(); + float invC = 1.0 / float(cutMax); + if (m_cutCount < cutMax) + { + + helper::WriteAccessor< Data > texcoords = m_vtexcoords; + float coef = float(m_cutCount) * invC; + for (auto id : idVGrab) + { + texcoords[id][0] = coef; + texcoords[id][1] = coef; + } + } + + if (m_cutCount < cutMax * 2) { + m_cutCount++; + return; + } + + m_cutCount = 0; + + + + TetrahedronSetTopologyModifier* tetraModif; + tetraCon->getContext()->get(tetraModif); + + if (tetraModif == nullptr) { + msg_info() << "Error: NO tetraModif"; + return; + } + + // First get all tetra that are on the first side + sofa::type::vector tetraIds; + std::map< sofa::core::topology::Topology::TetrahedronID, int> tetraCounter; + for (auto id : idVGrab) + { + const BaseMeshTopology::TetrahedraAroundVertex& tetraAV = tetraCon->getTetrahedraAroundVertex(id); + for (sofa::Index j = 0; j < tetraAV.size(); ++j) + { + int tetraId = tetraAV[j]; + + auto elem = tetraCounter.find(tetraId); + if (elem == tetraCounter.end()) // first time + { + tetraCounter[tetraId] = 1; + } + else + { + tetraCounter[tetraId] = elem->second + 1; + } + } + } + + if (d_cutMode.getValue() == 0) + { + for (auto elem : tetraCounter) + { + if (elem.second > 0) + { + tetraIds.push_back(elem.first); + } + } + } + else + { + for (auto elem : tetraCounter) + { + if (elem.second > 1) + { + tetraIds.push_back(elem.first); + } + } + } + + std::cout << "tetra2Rmove: " << tetraIds << std::endl; + + // remove springs first + stopAction(); + tetraModif->removeTetrahedra(tetraIds); + + + return; +} + +bool ArticulatedToolManager::stopSecondaryAction() +{ + //msg_warning() << "stopSecondaryAction()"; + return true; +} + void ArticulatedToolManager::openTool() { @@ -275,12 +426,14 @@ void ArticulatedToolManager::filterCollision() const ContactVector* contacts = nullptr; for (core::collision::NarrowPhaseDetection::DetectionOutputMap::const_iterator it = detectionOutputs.begin(); it != detectionOutputs.end(); ++it) { - sofa::core::CollisionModel* collMod1 = it->first.first; - sofa::core::CollisionModel* collMod2 = it->first.second; - - dmsg_info() << "collMod1: " << collMod1->getTypeName() << " -> " << collMod1->getContext()->getName(); - dmsg_info() << "collMod1: " << collMod2->getTypeName() << " -> " << collMod2->getContext()->getName(); + if (this->f_printLog.getValue()) + { + sofa::core::CollisionModel* collMod1 = it->first.first; + sofa::core::CollisionModel* collMod2 = it->first.second; + msg_info() << "collMod1: " << collMod1->getTypeName() << " -> " << collMod1->getContext()->getName(); + msg_info() << "collMod2: " << collMod2->getTypeName() << " -> " << collMod2->getContext()->getName(); + } // Get the number of contacts contacts = dynamic_cast(it->second); @@ -295,35 +448,53 @@ void ArticulatedToolManager::filterCollision() { // update the triangle id if a mapping is present GrabContactInfo* info = new GrabContactInfo(); + const ContactVector::value_type& c = (*contacts)[j]; bool firstJaw = false; - const ContactVector::value_type& c = (*contacts)[j]; - if (c.elem.first.getCollisionModel()->getEnumType() == sofa::core::CollisionModel::TRIANGLE_TYPE) // first model is target model + if (c.elem.first.getCollisionModel() == l_jawModel1.get()->l_jawCollision.get() || c.elem.first.getCollisionModel() == l_jawModel2.get()->l_jawCollision.get()) // first model is a jaw + { + info->idTool = c.elem.first.getIndex(); // id of the tool collision model + if (c.elem.second.getCollisionModel()->getEnumType() == sofa::core::CollisionModel::TRIANGLE_TYPE) // first model is triangle model + { + sofa::Index idTri = c.elem.second.getIndex(); + info->idsModel = c.elem.second.getCollisionModel()->getCollisionTopology()->getTriangle(idTri); + } + else + { + info->idvModel = c.elem.second.getIndex(); + } + + if (c.elem.first.getCollisionModel() == l_jawModel1.get()->l_jawCollision.get()) + firstJaw = true; + } + else if (c.elem.second.getCollisionModel() == l_jawModel1.get()->l_jawCollision.get() || c.elem.second.getCollisionModel() == l_jawModel2.get()->l_jawCollision.get()) // second model is a jaw { info->idTool = c.elem.second.getIndex(); - sofa::Index idTri = c.elem.first.getIndex(); - info->idsModel = c.elem.first.getCollisionModel()->getCollisionTopology()->getTriangle(idTri); + if (c.elem.first.getCollisionModel()->getEnumType() == sofa::core::CollisionModel::TRIANGLE_TYPE) // first model is triangle model + { + sofa::Index idTri = c.elem.first.getIndex(); + info->idsModel = c.elem.first.getCollisionModel()->getCollisionTopology()->getTriangle(idTri); + } + else + { + info->idvModel = c.elem.first.getIndex(); + } if (c.elem.second.getCollisionModel() == l_jawModel1.get()->l_jawCollision.get()) firstJaw = true; } - else + else // not related to this tool { - info->idTool = c.elem.first.getIndex(); - sofa::Index idTri = c.elem.second.getIndex(); - info->idsModel = c.elem.second.getCollisionModel()->getCollisionTopology()->getTriangle(idTri); - - if (c.elem.first.getCollisionModel() == l_jawModel1.get()->l_jawCollision.get()) - firstJaw = true; + continue; } info->normal = c.normal; info->dist = c.value; - dmsg_info() << j << " contact: " << c.elem.first.getIndex() << " | " << c.elem.second.getIndex() - << " -> " << " pA: " << c.point[0] << " pB: " << c.point[1] - << " | normal: " << c.normal << " d: " << c.value - << " | cDir: " << (c.point[1] - c.point[0]).normalized() << " d: " << (c.point[1] - c.point[0]).norm(); + //dmsg_info() << j << " contact: " << c.elem.first.getIndex() << " | " << c.elem.second.getIndex() + // << " -> " << " pA: " << c.point[0] << " pB: " << c.point[1] + // << " | normal: " << c.normal << " d: " << c.value + // << " | cDir: " << (c.point[1] - c.point[0]).normalized() << " d: " << (c.point[1] - c.point[0]).norm(); if (firstJaw) l_jawModel1->addContact(info); @@ -336,8 +507,18 @@ void ArticulatedToolManager::filterCollision() void ArticulatedToolManager::handleEvent(sofa::core::objectmodel::Event* event) { + if (m_performCut && sofa::simulation::AnimateEndEvent::checkEventType(event)) + { + performCut(); + m_performCut = false; + } + + if (KeypressedEvent::checkEventType(event)) { + if (!d_isControlled.getValue()) + return; + KeypressedEvent *ev = static_cast(event); switch (ev->getKey()) @@ -350,16 +531,25 @@ void ArticulatedToolManager::handleEvent(sofa::core::objectmodel::Event* event) //closeTool(); - filterCollision(); + //filterCollision(); performAction(); + deActivateTool(); break; } case 'G': case 'g': { - openTool(); + stopAction(); + activateTool(); + + break; + } + case 'U': + case 'u': + { + performSecondaryAction(); break; } case '0': @@ -455,9 +645,9 @@ void ArticulatedToolManager::draw(const core::visual::VisualParams* vparams) return; - auto m_model = l_targetModel.get(); - auto m_jaw1 = l_jawModel1.get()->l_jawDofs; - auto m_jaw2 = l_jawModel2.get()->l_jawDofs; + //auto m_model = l_targetModel.get(); + //auto m_jaw1 = l_jawModel1.get()->l_jawDofs; + //auto m_jaw2 = l_jawModel2.get()->l_jawDofs; if (d_drawContacts.getValue()) { diff --git a/src/InfinyToolkit/InteractionTools/ArticulatedToolManager.h b/src/InfinyToolkit/InteractionTools/ArticulatedToolManager.h index 56586f5..0b8ee1c 100644 --- a/src/InfinyToolkit/InteractionTools/ArticulatedToolManager.h +++ b/src/InfinyToolkit/InteractionTools/ArticulatedToolManager.h @@ -27,13 +27,16 @@ #include #include #include +#include namespace sofa::infinytoolkit { /** -* +* This class is a SOFA component, it will handle 2 different @sa BaseJawModel, one for the up jaw and another fot the bottom jaw in order +* to simulate grasper or scisors or any other tool defined by the jaws. +* This component will handle the motion, closing opening and activation of the tool. As well as collision detection in some cases. */ class SOFA_INFINYTOOLKIT_API ArticulatedToolManager : public core::objectmodel::BaseObject { @@ -44,6 +47,9 @@ class SOFA_INFINYTOOLKIT_API ArticulatedToolManager : public core::objectmodel:: using RigidCoord = sofa::defaulttype::RigidTypes::Coord; using ContactVector = type::vector; + using TexCoord = sofa::type::Vec<2, float>; + using VecTexCoord = type::vector; + protected: ArticulatedToolManager(); @@ -51,6 +57,9 @@ class SOFA_INFINYTOOLKIT_API ArticulatedToolManager : public core::objectmodel:: public: virtual void init() override; + + /// Sofa API init method of the component + void bwdInit() override; int testModels(); const sofa::type::vector< int >& vertexIdsInBroadPhase() { return m_idBroadPhase; } @@ -67,6 +76,9 @@ class SOFA_INFINYTOOLKIT_API ArticulatedToolManager : public core::objectmodel:: int performAction(); bool stopAction(); + bool performSecondaryAction(); + bool stopSecondaryAction(); + void closeTool(); void openTool(); @@ -81,6 +93,8 @@ class SOFA_INFINYTOOLKIT_API ArticulatedToolManager : public core::objectmodel:: void filterCollision(); + void performCut(); + public: // Path to the different JawModel SingleLink l_jawModel1; @@ -99,8 +113,15 @@ class SOFA_INFINYTOOLKIT_API ArticulatedToolManager : public core::objectmodel:: Data d_inputPosition; Data > d_outputPositions; + Data d_isCutter; + Data d_cutMaxStep; + Data d_cutMode; + Data d_isControlled; Data d_drawContacts; ///< if true, draw the collision outputs + Data d_manageBurning; ///< if true, draw the collision outputs + core::topology::PointData< VecTexCoord > m_vtexcoords; ///< coordinates of the texture + protected: // Buffer of points ids sofa::type::vector m_idgrabed; @@ -110,6 +131,9 @@ class SOFA_INFINYTOOLKIT_API ArticulatedToolManager : public core::objectmodel:: BaseJawModel::SPtr m_jawModel1 = nullptr; BaseJawModel::SPtr m_jawModel2 = nullptr; + bool m_performCut = false; + + int m_cutCount = 0; }; diff --git a/src/InfinyToolkit/InteractionTools/BaseJawModel.h b/src/InfinyToolkit/InteractionTools/BaseJawModel.h index eb64de7..890c099 100644 --- a/src/InfinyToolkit/InteractionTools/BaseJawModel.h +++ b/src/InfinyToolkit/InteractionTools/BaseJawModel.h @@ -37,7 +37,8 @@ namespace sofa::infinytoolkit class GrabContactInfo { public: - sofa::Index idTool; // in global mesh + sofa::Index idTool = sofa::InvalidID; // in global mesh + sofa::Index idvModel = sofa::InvalidID; sofa::core::topology::BaseMeshTopology::Triangle idsModel; // in global mesh //Vec3 pointA; //Vec3 pointB; @@ -53,7 +54,7 @@ class SOFA_INFINYTOOLKIT_API BaseJawModel : public core::objectmodel::BaseObject using Vec3 = sofa::type::Vec3; BaseJawModel(); - virtual void init(); + virtual void init() override; virtual ~BaseJawModel() = default; int getModelId() { return m_modelId; } @@ -67,6 +68,11 @@ class SOFA_INFINYTOOLKIT_API BaseJawModel : public core::objectmodel::BaseObject /// Main API public method to stop the action of the Jaw virtual void stopAction() {} + /// Main API public method to launch the action of the Jaw + virtual void performSecondaryAction() {} + /// Main API public method to stop the action of the Jaw + virtual void stopSecondaryAction() {} + /// Method to compute tool axis. Will fill @sa m_matP, @sa m_origin, @sa m_xAxis, @sa m_yAxis, @sa m_zAxis void computeAxis(); @@ -78,7 +84,8 @@ class SOFA_INFINYTOOLKIT_API BaseJawModel : public core::objectmodel::BaseObject virtual void addContact(GrabContactInfo* grabInfo); virtual void clearContacts(); - const sofa::type::vector& getContacts() { return m_contactInfos; } + const sofa::type::vector& getContacts() const { return m_contactInfos; } + const sofa::type::vector& getRawContactModelIds() const { return m_rawIds; } void setTargetModel(sofa::core::behavior::BaseMechanicalState* model) { m_target = model; } virtual void drawImpl(const core::visual::VisualParams* vparams); @@ -121,6 +128,8 @@ class SOFA_INFINYTOOLKIT_API BaseJawModel : public core::objectmodel::BaseObject /// List of contacts filter during collision sofa::type::vector m_contactInfos; + + type::vector m_rawIds; }; } // namespace sofa::infinytoolkit diff --git a/src/InfinyToolkit/InteractionTools/GrasperJawModel.cpp b/src/InfinyToolkit/InteractionTools/GrasperJawModel.cpp index bd831c7..9554fa7 100644 --- a/src/InfinyToolkit/InteractionTools/GrasperJawModel.cpp +++ b/src/InfinyToolkit/InteractionTools/GrasperJawModel.cpp @@ -23,6 +23,7 @@ ****************************************************************************/ #include + #include namespace sofa::infinytoolkit @@ -87,14 +88,34 @@ void GrasperJawModel::stopAction() { // clean springs m_forcefield->clear(); + m_rawIds.clear(); //activateImpl(); } +void GrasperJawModel::performSecondaryAction() +{ + /*TetrahedronSetTopologyContainer* tetraCon; + m_target->getContext()->get(tetraCon); + + if (tetraCon == nullptr) { + msg_warning("GrasperJawModel") << "Error: NO tetraCon"; + return; + }*/ + + +} + + +void GrasperJawModel::stopSecondaryAction() +{ + +} + + int GrasperJawModel::createStiffSpringFF() { std::cout << this->getName() << " + createStiffSpringFF()" << std::endl; - SReal stiffness = d_stiffness.getValue(); m_forcefield = sofa::core::objectmodel::New(dynamic_cast(m_jaw), dynamic_cast(m_target)); SpringFF* stiffspringforcefield = static_cast(m_forcefield.get()); @@ -114,13 +135,41 @@ void GrasperJawModel::addJawSprings() { Vec3 posTool = Vec3(m_jaw->getPX(cInfo->idTool), m_jaw->getPY(cInfo->idTool), m_jaw->getPZ(cInfo->idTool)); - for (int i = 0; i < 3; ++i) + if (cInfo->idvModel != sofa::InvalidID) // pointModel { - Vec3 posModel = Vec3(m_target->getPX(cInfo->idsModel[i]), m_target->getPY(cInfo->idsModel[i]), m_target->getPZ(cInfo->idsModel[i])); + Vec3 posModel = Vec3(m_target->getPX(cInfo->idvModel), m_target->getPY(cInfo->idvModel), m_target->getPZ(cInfo->idvModel)); SReal dist = (posModel - posTool).norm(); - stiffspringforcefield->addSpring(cInfo->idTool, cInfo->idsModel[i], stiffness, 0.0, dist); + stiffspringforcefield->addSpring(cInfo->idTool, cInfo->idvModel, stiffness, 0.0, dist); + + bool found = false; + for (int id : m_rawIds) + { + if (id == cInfo->idvModel) + { + found = true; + break; + } + } + + if (!found) + m_rawIds.push_back(cInfo->idvModel); + + std::cout << "Add spring: " << cInfo->idTool << " model: " << cInfo->idvModel << std::endl; } + else + { + + } + + //for (int i = 0; i < 3; ++i) + //{ + // Vec3 posModel = Vec3(m_target->getPX(cInfo->idsModel[i]), m_target->getPY(cInfo->idsModel[i]), m_target->getPZ(cInfo->idsModel[i])); + // SReal dist = (posModel - posTool).norm(); + // stiffspringforcefield->addSpring(cInfo->idTool, cInfo->idsModel[i], stiffness, 0.0, dist); + //} } + + std::cout << "m_rawIds: " << m_rawIds << std::endl; } diff --git a/src/InfinyToolkit/InteractionTools/GrasperJawModel.h b/src/InfinyToolkit/InteractionTools/GrasperJawModel.h index 155416b..ee29162 100644 --- a/src/InfinyToolkit/InteractionTools/GrasperJawModel.h +++ b/src/InfinyToolkit/InteractionTools/GrasperJawModel.h @@ -50,6 +50,11 @@ class SOFA_INFINYTOOLKIT_API GrasperJawModel : public BaseJawModel void activateImpl() override; void deActivateImpl() override; + /// Main API public method to launch the action of the Jaw + void performSecondaryAction() override; + /// Main API public method to stop the action of the Jaw + void stopSecondaryAction() override; + Data d_stiffness; protected: diff --git a/src/InfinyToolkit/InteractionTools/ScissorJawModel.cpp b/src/InfinyToolkit/InteractionTools/ScissorJawModel.cpp index aedb5cd..9d1d3da 100644 --- a/src/InfinyToolkit/InteractionTools/ScissorJawModel.cpp +++ b/src/InfinyToolkit/InteractionTools/ScissorJawModel.cpp @@ -45,7 +45,7 @@ int ScissorJawModel::cutFromTetra(float minX, float maxX, bool cut) // Classify right/left points of the plier sofa::type::vector idsLeft; sofa::type::vector idsRight; - for (int i = 0; i < m_idBroadPhase.size(); i++) + for (sofa::Index i = 0; i < m_idBroadPhase.size(); i++) { Vec3 vert = Vec3(m_jaw->getPX(m_idBroadPhase[i]), m_jaw->getPY(m_idBroadPhase[i]), m_jaw->getPZ(m_idBroadPhase[i])); vert = m_matP * (vert - m_origin); @@ -92,8 +92,8 @@ int ScissorJawModel::cutFromTetra(float minX, float maxX, bool cut) } // First get all tetra that are on the first side - sofa::type::vector tetraIds; - for (int i = 0; i < idsLeft.size(); ++i) + sofa::type::vector tetraIds; + for (sofa::Index i = 0; i < idsLeft.size(); ++i) { const BaseMeshTopology::TetrahedraAroundVertex& tetraAV = tetraCon->getTetrahedraAroundVertex(idsLeft[i]); for (int j = 0; j < tetraAV.size(); ++j) @@ -118,7 +118,7 @@ int ScissorJawModel::cutFromTetra(float minX, float maxX, bool cut) // Then test for each tetra if one of the vertex is on the other side. If yes put on but path tetraIdsOnCut.clear(); std::set< unsigned int > items; - for (int i = 0; i < tetraIds.size(); ++i) + for (sofa::Index i = 0; i < tetraIds.size(); ++i) { const BaseMeshTopology::Tetra& tetra = tetraCon->getTetra(tetraIds[i]); for (unsigned int j = 0; j < 4; ++j) @@ -158,7 +158,7 @@ int ScissorJawModel::cutFromTetra(float minX, float maxX, bool cut) vitems.reserve(items.size()); vitems.insert(vitems.end(), items.rbegin(), items.rend()); - for (int i = 0; i < vitems.size(); i++) + for (sofa::Index i = 0; i < vitems.size(); i++) { sofa::type::vector its; its.push_back(vitems[i]); @@ -195,7 +195,7 @@ int ScissorJawModel::pathCutFromTetra(float minX, float maxX) return -40; } - for (int i = 0; i < tetraIds.size(); i++) + for (sofa::Index i = 0; i < tetraIds.size(); i++) { const BaseMeshTopology::Tetra& tetra = tetraCon->getTetra(tetraIds[i]); for (int j = 0; j < 4; j++) @@ -226,7 +226,7 @@ void ScissorJawModel::cutFromTriangles() // Classify right/left points of the plier sofa::type::vector idsLeft; sofa::type::vector idsRight; - for (int i = 0; i < m_idgrabed.size(); i++) + for (sofa::Index i = 0; i < m_idgrabed.size(); i++) { Vec3 vert = Vec3(m_model->getPX(m_idgrabed[i]), m_model->getPY(m_idgrabed[i]), m_model->getPZ(m_idgrabed[i])); vert = m_matP * (vert - m_origin); @@ -253,7 +253,7 @@ void ScissorJawModel::cutFromTriangles() } const sofa::type::vector& allTri = triCons[1]->getTriangleArray(); - for (int i = 0; i < allTri.size(); ++i) + for (sofa::Index i = 0; i < allTri.size(); ++i) { const BaseMeshTopology::Triangle& tri = allTri[i]; bool foundLeft = false; diff --git a/src/InfinyToolkit/PliersPositionsMapper.h b/src/InfinyToolkit/PliersPositionsMapper.h index 01da45c..9405c89 100644 --- a/src/InfinyToolkit/PliersPositionsMapper.h +++ b/src/InfinyToolkit/PliersPositionsMapper.h @@ -57,7 +57,7 @@ class SOFA_INFINYTOOLKIT_API PliersPositionsMapper: public sofa::core::DataEngin void draw(const core::visual::VisualParams* vparams) override; - void handleTopologyChange(); + void handleTopologyChange() override; /// Pre-construction check method called by ObjectFactory. /// Check that DataTypes matches the MeshTopology.