diff --git a/CMakeLists.txt b/CMakeLists.txt index b36adb4ae..43ac28215 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,6 +34,8 @@ set(HEADER_FILES ${BEAMADAPTER_SRC}/component/controller/AdaptiveBeamController.h ${BEAMADAPTER_SRC}/component/controller/AdaptiveBeamController.inl + ${BEAMADAPTER_SRC}/component/controller/BeamAdapterActionController.h + ${BEAMADAPTER_SRC}/component/controller/BeamAdapterActionController.inl ${BEAMADAPTER_SRC}/component/controller/InterventionalRadiologyController.h ${BEAMADAPTER_SRC}/component/controller/InterventionalRadiologyController.inl ${BEAMADAPTER_SRC}/component/controller/SutureController.h @@ -55,6 +57,7 @@ set(HEADER_FILES ${BEAMADAPTER_SRC}/component/mapping/MultiAdaptiveBeamMapping.inl ${BEAMADAPTER_SRC}/utils/BeamSection.h + ${BEAMADAPTER_SRC}/utils/BeamActions.h ${BEAMADAPTER_SRC}/utils/deprecatedcomponent.h ) @@ -68,6 +71,7 @@ set(SOURCE_FILES ${BEAMADAPTER_SRC}/component/constraint/AdaptiveBeamLengthConstraint.cpp ${BEAMADAPTER_SRC}/component/controller/AdaptiveBeamController.cpp + ${BEAMADAPTER_SRC}/component/controller/BeamAdapterActionController.cpp ${BEAMADAPTER_SRC}/component/controller/InterventionalRadiologyController.cpp ${BEAMADAPTER_SRC}/component/controller/SutureController.cpp diff --git a/src/BeamAdapter/component/controller/BeamAdapterActionController.cpp b/src/BeamAdapter/component/controller/BeamAdapterActionController.cpp new file mode 100644 index 000000000..604c673c9 --- /dev/null +++ b/src/BeamAdapter/component/controller/BeamAdapterActionController.cpp @@ -0,0 +1,40 @@ +/****************************************************************************** +* BeamAdapter plugin * +* (c) 2006 Inria, University of Lille, CNRS * +* * +* This program is free software; you can redistribute it and/or modify it * +* under the terms of the GNU Lesser General Public License as published by * +* the Free Software Foundation; either version 2.1 of the License, or (at * +* your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, but WITHOUT * +* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * +* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * +* for more details. * +* * +* You should have received a copy of the GNU Lesser General Public License * +* along with this program. If not, see . * +******************************************************************************* +* Authors: see Authors.md * +* * +* Contact information: contact@sofa-framework.org * +******************************************************************************/ +#define SOFA_PLUGIN_BEAMADAPTER_ACTIONCONTROLLER_CPP + +#include +#include +#include + +#include +#include + +namespace sofa::component::controller +{ + +const static int BeamAdapterActionControllerClass = core::RegisterObject("BeamAdapterActionController") + .add< BeamAdapterActionController >() + ; + +template class SOFA_BEAMADAPTER_API BeamAdapterActionController; + +} // namespace sofa::component::controller diff --git a/src/BeamAdapter/component/controller/BeamAdapterActionController.h b/src/BeamAdapter/component/controller/BeamAdapterActionController.h new file mode 100644 index 000000000..115f37449 --- /dev/null +++ b/src/BeamAdapter/component/controller/BeamAdapterActionController.h @@ -0,0 +1,86 @@ +/****************************************************************************** +* BeamAdapter plugin * +* (c) 2006 Inria, University of Lille, CNRS * +* * +* This program is free software; you can redistribute it and/or modify it * +* under the terms of the GNU Lesser General Public License as published by * +* the Free Software Foundation; either version 2.1 of the License, or (at * +* your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, but WITHOUT * +* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * +* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * +* for more details. * +* * +* You should have received a copy of the GNU Lesser General Public License * +* along with this program. If not, see . * +******************************************************************************* +* Authors: see Authors.md * +* * +* Contact information: contact@sofa-framework.org * +******************************************************************************/ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace sofa::component::controller +{ + +/*** +* This class is a SOFA component inheriting from MechanicalStateController. +* It can be used to script the InterventionalRadiologyController using @sa BeamActions +* If @sa d_writeMode mode is on, each keyboard interaction used to control the Beam will be exported with key times. +* Otherwise, it will load a list of @sa BeamActions with their corresponding key times to replay a navigation. +*/ +template +class BeamAdapterActionController : public MechanicalStateController +{ +public: + SOFA_CLASS(SOFA_TEMPLATE(BeamAdapterActionController, DataTypes), SOFA_TEMPLATE(MechanicalStateController, DataTypes)); + + using BeamAdapterAction = sofa::beamadapter::BeamAdapterAction; + using interventionCtrl = InterventionalRadiologyController; + using Real = typename DataTypes::Real; + + BeamAdapterActionController(); + ~BeamAdapterActionController() override = default; + + /// Method to init the component. Will search for a InterventionalRadiologyController using link @sa l_interventionController + void init() override; + + /// Method called at each timestep. Will check if an action has to be read or write + void onBeginAnimationStep(const double dt) override; + + /// Method to control the Beam using keyboard and save the actions in @sa d_actions + void onKeyPressedEvent(core::objectmodel::KeypressedEvent* kev) override; + + /// Unused metho for mouse event + void onMouseEvent(core::objectmodel::MouseEvent* ev) override { SOFA_UNUSED(ev);} + + Data d_writeMode; ///< If true, will accumulate actions in @sa d_actions for export. Press key E for export. + Data > d_actions; ///< List of actions to import or export. + Data > d_actionString; ///< List of actions to import or export as string. + Data > d_timeSteps; ///< List of key times corresponding to BeamActions in @sa d_actions or @sa d_actionString + + /// Link to the InterventionalRadiologyController, controlling the Beam, to script. + SingleLink, InterventionalRadiologyController, BaseLink::FLAG_STOREPATH | BaseLink::FLAG_STRONGLINK> l_interventionController; + +private: + int m_readStep = 0; ///< counter to the current action to read in @sa d_actions + BeamAdapterAction m_currAction = BeamAdapterAction::NO_ACTION; ///< Current action imported or to export + BeamAdapterAction m_lastAction = BeamAdapterAction::NO_ACTION; ///< Previous action imported or to export + + bool m_exportActions = false; ///< Bool to dump actions, will be set to true if key 'E' is pressed +}; + +#if !defined(SOFA_PLUGIN_BEAMADAPTER_ACTIONCONTROLLER_CPP) +extern template class SOFA_BEAMADAPTER_API BeamAdapterActionController; +#endif + +} /// namespace sofa::component::controller diff --git a/src/BeamAdapter/component/controller/BeamAdapterActionController.inl b/src/BeamAdapter/component/controller/BeamAdapterActionController.inl new file mode 100644 index 000000000..312550be8 --- /dev/null +++ b/src/BeamAdapter/component/controller/BeamAdapterActionController.inl @@ -0,0 +1,177 @@ +/****************************************************************************** +* BeamAdapter plugin * +* (c) 2006 Inria, University of Lille, CNRS * +* * +* This program is free software; you can redistribute it and/or modify it * +* under the terms of the GNU Lesser General Public License as published by * +* the Free Software Foundation; either version 2.1 of the License, or (at * +* your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, but WITHOUT * +* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * +* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * +* for more details. * +* * +* You should have received a copy of the GNU Lesser General Public License * +* along with this program. If not, see . * +******************************************************************************* +* Authors: see Authors.md * +* * +* Contact information: contact@sofa-framework.org * +******************************************************************************/ +#pragma once +#include +#include +#include + +namespace sofa::component::controller +{ + +using namespace sofa::beamadapter; + +template +BeamAdapterActionController::BeamAdapterActionController() + : d_writeMode(initData(&d_writeMode, false, "writeMode", "If true, will accumulate actions from keyboard and dump the actions and times when key 'E' is pressed.")) + , d_actions(initData(&d_actions, "actions", "List of actions to script the BeamAdapter")) + , d_actionString(initData(&d_actionString, "actionString", "List of actions as string to script the BeamAdapter")) + , d_timeSteps(initData(&d_timeSteps, "timeSteps", "List of key times corresponding to the actions")) + , l_interventionController(initLink("interventionController", "Path to the InterventionalRadiologyController component on scene")) +{ + +} + +template +void BeamAdapterActionController::init() +{ + this->d_componentState.setValue(sofa::core::objectmodel::ComponentState::Loading); + if (!l_interventionController.get()) + { + msg_error() << "No l_interventionController given. Component will be set to Invalid."; + this->d_componentState.setValue(sofa::core::objectmodel::ComponentState::Invalid); + } + + // the controller must listen to the event (in particular BeginAnimationStep event) + this->f_listening.setValue(true); + + this->d_componentState.setValue(sofa::core::objectmodel::ComponentState::Valid); +} + + +template +void BeamAdapterActionController::onKeyPressedEvent(core::objectmodel::KeypressedEvent* kev) +{ + if (!d_writeMode.getValue()) + return; + + /// Control keys for interventonal Radiology simulations: + switch (kev->getKey()) + { + case 'E': + m_currAction = BeamAdapterAction::NO_ACTION; + m_exportActions = !m_exportActions; + break; + case 'D': + m_currAction = BeamAdapterAction::DROP_TOOL; + break; + case '2': + m_currAction = BeamAdapterAction::USE_TOOL_2; + break; + case '1': + m_currAction = BeamAdapterAction::USE_TOOL_1; + break; + case '0': + m_currAction = BeamAdapterAction::USE_TOOL_0; + break; + case 20: // droite = 20 + if (m_currAction == BeamAdapterAction::SPIN_RIGHT) + m_currAction = BeamAdapterAction::NO_ACTION; + else + m_currAction = BeamAdapterAction::SPIN_RIGHT; + + break; + case 18: // gauche = 18 + if (m_currAction == BeamAdapterAction::SPIN_LEFT) + m_currAction = BeamAdapterAction::NO_ACTION; + else + m_currAction = BeamAdapterAction::SPIN_LEFT; + + break; + case 19: // fleche haut = 19 + if (m_currAction == BeamAdapterAction::MOVE_FORWARD) + m_currAction = BeamAdapterAction::NO_ACTION; + else + m_currAction = BeamAdapterAction::MOVE_FORWARD; + + break; + case 21: // bas = 21 + if (m_currAction == BeamAdapterAction::MOVE_BACKWARD) + m_currAction = BeamAdapterAction::NO_ACTION; + else + m_currAction = BeamAdapterAction::MOVE_BACKWARD; + + break; + default: + m_currAction = BeamAdapterAction::NO_ACTION; + break; + } +} + + +template +void BeamAdapterActionController::onBeginAnimationStep(const double /*dt*/) +{ + const auto currentTime = this->getContext()->getTime(); + if (d_writeMode.getValue()) + { + interventionCtrl* ctrl = l_interventionController.get(); + ctrl->applyAction(m_currAction); + + if (m_lastAction != m_currAction) + { + auto times = sofa::helper::WriteAccessor(d_timeSteps); + auto actions = sofa::helper::WriteAccessor(d_actions); + times.push_back(currentTime); + actions.push_back(int(m_currAction)); + + if (m_exportActions) + { + std::cout << "timeSteps='" << times.wref() << "'" << std::endl; + std::cout << "actions='" << actions.wref() << "'" << std::endl; + } + + m_lastAction = m_currAction; + } + + if (m_currAction >= BeamAdapterAction::SWITCH_NEXT_TOOL) // action regarding tool needs only to be triggered once + { + m_currAction = BeamAdapterAction::NO_ACTION; + } + } + else + { + const type::vector& times = d_timeSteps.getValue(); + if (!times.empty()) + { + if (m_readStep < times.size()) + { + Real time = times[m_readStep]; + if (currentTime >= time) // check if another key time has been reached and change action + { + m_currAction = BeamAdapterAction(d_actions.getValue()[m_readStep]); + m_readStep++; + } + } + + interventionCtrl* ctrl = l_interventionController.get(); + ctrl->applyAction(m_currAction); + + if (m_currAction >= BeamAdapterAction::SWITCH_NEXT_TOOL) // action regarding tool needs only to be triggered once + { + m_currAction = BeamAdapterAction::NO_ACTION; + } + } + } +} + + +} // namespace sofa::component::controller diff --git a/src/BeamAdapter/component/controller/InterventionalRadiologyController.h b/src/BeamAdapter/component/controller/InterventionalRadiologyController.h index c3c824edc..0eeabc68d 100644 --- a/src/BeamAdapter/component/controller/InterventionalRadiologyController.h +++ b/src/BeamAdapter/component/controller/InterventionalRadiologyController.h @@ -41,6 +41,7 @@ #include #include +#include #include #include #include @@ -108,8 +109,11 @@ class InterventionalRadiologyController : public MechanicalStateController >& get_id_instrument_curvAbs_table()const; int getTotalNbEdges()const; + void applyAction(sofa::beamadapter::BeamAdapterAction action); + /// Getter to the tools curviline abscisses sorted @sa m_nodeCurvAbs at the current timestep. [[nodiscard]] const type::vector& getCurrentCurvAbscisses() const { return m_nodeCurvAbs; } + public: using Inherit1::f_printLog; diff --git a/src/BeamAdapter/component/controller/InterventionalRadiologyController.inl b/src/BeamAdapter/component/controller/InterventionalRadiologyController.inl index 31f36b58b..a38a3c144 100644 --- a/src/BeamAdapter/component/controller/InterventionalRadiologyController.inl +++ b/src/BeamAdapter/component/controller/InterventionalRadiologyController.inl @@ -55,6 +55,7 @@ using core::objectmodel::BaseContext; using helper::WriteAccessor; using core::objectmodel::KeypressedEvent; using core::objectmodel::MouseEvent; +using namespace sofa::beamadapter; template @@ -242,72 +243,29 @@ void InterventionalRadiologyController::onKeyPressedEvent(KeypressedE switch(kev->getKey()) { case 'D': - m_dropCall = true; + applyAction(BeamAdapterAction::DROP_TOOL); break; - case '2': - { - if (2 >= (int)m_instrumentsList.size() && f_printLog.getValue() ) - msg_warning()<<"Controlled Instument num 2 do not exist (size ="<< m_instrumentsList.size() <<") do not change the instrument id"; - else - d_controlledInstrument.setValue(2); - } + applyAction(BeamAdapterAction::USE_TOOL_2); break; - case '1': - { - if (1 >= (int)m_instrumentsList.size() && f_printLog.getValue() ) - msg_warning()<<"Controlled Instument num 1 do not exist (size ="<< m_instrumentsList.size() <<") do not change the instrument id"; - else - d_controlledInstrument.setValue(1); - } + applyAction(BeamAdapterAction::USE_TOOL_1); break; - case '0': - d_controlledInstrument.setValue(0); + applyAction(BeamAdapterAction::USE_TOOL_0); break; - case 20: // droite = 20 - { - auto rotInstrument = sofa::helper::getWriteOnlyAccessor(d_rotationInstrument); - int id = d_controlledInstrument.getValue(); - rotInstrument[id] += d_angularStep.getValue(); - } + applyAction(BeamAdapterAction::SPIN_RIGHT); break; case 18: // gauche = 18 - { - int id = d_controlledInstrument.getValue(); - auto rotInstrument = sofa::helper::getWriteOnlyAccessor(d_rotationInstrument); - rotInstrument[id] -= d_angularStep.getValue(); - } + applyAction(BeamAdapterAction::SPIN_LEFT); break; - case 19: // fleche haut = 19 - { - int id = d_controlledInstrument.getValue(); - auto xInstrTip = sofa::helper::getWriteOnlyAccessor(d_xTip); - if (id >= (int)xInstrTip.size()) - { - msg_warning()<<"Controlled Instument num "<= (int)xInstrTip.size()) - { - msg_warning()<<"Controlled Instument num "<::onKeyPressedEvent(KeypressedE } } break; - case '/': { if(m_FF) @@ -397,6 +354,89 @@ void InterventionalRadiologyController::onBeginAnimationStep(const do } +template +void InterventionalRadiologyController::applyAction(sofa::beamadapter::BeamAdapterAction action) +{ + int id = d_controlledInstrument.getValue(); + if (id >= m_instrumentsList.size()) + { + msg_warning() << "Controlled Instument num " << id << " does not exist (size =" << m_instrumentsList.size() << ")."; + return; + } + + switch (action) + { + case BeamAdapterAction::NO_ACTION: + break; + case BeamAdapterAction::MOVE_FORWARD: + { + auto xInstrTip = sofa::helper::getWriteOnlyAccessor(d_xTip); + xInstrTip[id] += d_step.getValue(); + break; + } + case BeamAdapterAction::MOVE_BACKWARD: + { + auto xInstrTip = sofa::helper::getWriteOnlyAccessor(d_xTip); + xInstrTip[id] -= d_step.getValue(); + break; + } + case BeamAdapterAction::SPIN_RIGHT: + { + auto rotInstrument = sofa::helper::getWriteOnlyAccessor(d_rotationInstrument); + rotInstrument[id] += d_angularStep.getValue(); + break; + } + case BeamAdapterAction::SPIN_LEFT: + { + auto rotInstrument = sofa::helper::getWriteOnlyAccessor(d_rotationInstrument); + rotInstrument[id] -= d_angularStep.getValue(); + break; + } + case BeamAdapterAction::SWITCH_NEXT_TOOL: + { + if (id + 1 >= m_instrumentsList.size()) + msg_warning() << "Switching to next tool is not possible, no more instrument in list."; + else + d_controlledInstrument.setValue(id + 1); + break; + } + case BeamAdapterAction::SWITCH_PREVIOUS_TOOL: + { + if (id == 0) + msg_warning() << "Switching to previous tool is not possible, already controlling first instrument."; + else + d_controlledInstrument.setValue(id - 1); + break; + } + case BeamAdapterAction::USE_TOOL_0: + { + d_controlledInstrument.setValue(0); + break; + } + case BeamAdapterAction::USE_TOOL_1: + { + if (1 >= m_instrumentsList.size()) + msg_warning() << "Controlled Instument num 1 do not exist (size =" << m_instrumentsList.size() << ") do not change the instrument id"; + else + d_controlledInstrument.setValue(1); + break; + } + case BeamAdapterAction::USE_TOOL_2: + { + if (2 >= m_instrumentsList.size()) + msg_warning() << "Controlled Instument num 2 do not exist (size =" << m_instrumentsList.size() << ") do not change the instrument id"; + else + d_controlledInstrument.setValue(2); + break; + } + case BeamAdapterAction::DROP_TOOL: + { + m_dropCall = true; + } + } +} + + template void InterventionalRadiologyController::processDrop(unsigned int &previousNumControlledNodes, unsigned int &segRemove) diff --git a/src/BeamAdapter/utils/BeamActions.h b/src/BeamAdapter/utils/BeamActions.h new file mode 100644 index 000000000..ea91418de --- /dev/null +++ b/src/BeamAdapter/utils/BeamActions.h @@ -0,0 +1,68 @@ +/****************************************************************************** +* BeamAdapter plugin * +* (c) 2006 Inria, University of Lille, CNRS * +* * +* This program is free software; you can redistribute it and/or modify it * +* under the terms of the GNU Lesser General Public License as published by * +* the Free Software Foundation; either version 2.1 of the License, or (at * +* your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, but WITHOUT * +* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * +* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * +* for more details. * +* * +* You should have received a copy of the GNU Lesser General Public License * +* along with this program. If not, see . * +******************************************************************************* +* Authors: see Authors.md * +* * +* Contact information: contact@sofa-framework.org * +******************************************************************************/ +#pragma once + +namespace sofa::beamadapter +{ + + /// \brief Enum class listing all possible actions during Beam navigation + enum class BeamAdapterAction + { + NO_ACTION = 0, + MOVE_FORWARD, + MOVE_BACKWARD, + SPIN_RIGHT, + SPIN_LEFT, + SWITCH_NEXT_TOOL, + SWITCH_PREVIOUS_TOOL, + DROP_TOOL, + USE_TOOL_0, + USE_TOOL_1, + USE_TOOL_2, + }; + + /// \brief map of action as string keyword instead of int for better clarity in scene scripting + const static std::map beamActionNames = { + {"stop", BeamAdapterAction::NO_ACTION}, + {"fwd", BeamAdapterAction::MOVE_FORWARD}, + {"bwd", BeamAdapterAction::MOVE_BACKWARD}, + {"right", BeamAdapterAction::SPIN_RIGHT}, + {"nextT", BeamAdapterAction::SWITCH_NEXT_TOOL}, + {"prevT", BeamAdapterAction::SWITCH_PREVIOUS_TOOL}, + {"dropT", BeamAdapterAction::DROP_TOOL}, + {"tool0", BeamAdapterAction::USE_TOOL_0}, + {"tool1", BeamAdapterAction::USE_TOOL_1}, + {"tool2", BeamAdapterAction::USE_TOOL_2} + }; + + /// static method to convert an action as string into enum class using @sa beamActionNames + static BeamAdapterAction convertBeamAdapterAction(const std::string& sAction) + { + auto bAction = beamActionNames.find(sAction); + + if (bAction != beamActionNames.end()) + return bAction->second; + else + return BeamAdapterAction::NO_ACTION; + } + +} // namespace sofa::beamAdapter