From fbdc1f078f6e9f933651cbc1f70b52314a2ca07b Mon Sep 17 00:00:00 2001 From: epernod Date: Thu, 29 Jun 2023 16:34:17 +0200 Subject: [PATCH 01/22] [WireRestShape] Remove optiond releaseWire or brokenIn2 --- .../component/engine/WireRestShape.h | 13 +++-- .../component/engine/WireRestShape.inl | 58 +------------------ 2 files changed, 9 insertions(+), 62 deletions(-) diff --git a/src/BeamAdapter/component/engine/WireRestShape.h b/src/BeamAdapter/component/engine/WireRestShape.h index 34598fcc1..819e80548 100644 --- a/src/BeamAdapter/component/engine/WireRestShape.h +++ b/src/BeamAdapter/component/engine/WireRestShape.h @@ -91,7 +91,10 @@ class WireRestShape : public core::objectmodel::BaseObject /////////////////////////// Methods of WireRestShape ////////////////////////////////////////// /// For coils: a part of the coil instrument can be brokenIn2 (by default the point of release is the end of the straight length) - Real getReleaseCurvAbs() const {return d_straightLength.getValue();} + Real getReleaseCurvAbs() const { + msg_warning() << "Releasing catheter or brokenIn2 mode is not anymore supported. Feature has been removed after release v23.06"; + return 0.0; + } /// This function is called by the force field to evaluate the rest position of each beam void getRestTransformOnX(Transform &global_H_local, const Real &x); @@ -121,10 +124,9 @@ class WireRestShape : public core::objectmodel::BaseObject void getCollisionSampling(Real &dx, const Real &x_curv) ; void getNumberOfCollisionSegment(Real &dx, unsigned int &numLines) ; - //TODO(dmarchal 2017-05-17) Please specify who and when it will be done either a time after wich - //we can remove the todo. - // todo => topological change ! - void releaseWirePart(); + void releaseWirePart() { + msg_warning() << "Releasing catheter or brokenIn2 mode is not anymore supported. Feature has been removed after release v23.06"; + } void rotateFrameForAlignX(const Quat &input, Vec3 &x, Quat &output); @@ -157,7 +159,6 @@ class WireRestShape : public core::objectmodel::BaseObject Data d_massDensity2; /// broken in 2 case - Data d_brokenIn2; Data d_drawRestShape; private: diff --git a/src/BeamAdapter/component/engine/WireRestShape.inl b/src/BeamAdapter/component/engine/WireRestShape.inl index c2c89726c..3a158cf10 100644 --- a/src/BeamAdapter/component/engine/WireRestShape.inl +++ b/src/BeamAdapter/component/engine/WireRestShape.inl @@ -79,7 +79,6 @@ WireRestShape::WireRestShape() : , d_innerRadius2(initData(&d_innerRadius2,(Real)0.0f,"innerRadiusExtremity","inner radius for beams at the extremity\nonly if not straight")) , d_massDensity1(initData(&d_massDensity1,(Real)1.0,"massDensity", "Density of the mass (usually in kg/m^3)" )) , d_massDensity2(initData(&d_massDensity2,(Real)1.0,"massDensityExtremity", "Density of the mass at the extremity\nonly if not straight" )) - , d_brokenIn2(initData(&d_brokenIn2, (bool)false, "brokenIn2", "")) , d_drawRestShape(initData(&d_drawRestShape, (bool)false, "draw", "draw rest shape")) , l_topology(initLink("topology", "link to the topology container")) , l_loader(initLink("loader", "link to the MeshLoader")) @@ -272,65 +271,12 @@ void WireRestShape::init() } -template -void WireRestShape::releaseWirePart(){ - - d_brokenIn2.setValue(true); - - if ( edgeMod == nullptr ) - { - msg_error() << "no edgeSetModifier in the node -> cannot do the topological change"; - return; - } - ///////// remove the edge that is cut ////// - for ( sofa::Size i=0; i<_topology->getNbPoints(); i++) - { - if( _topology->getPX(i) > this->getReleaseCurvAbs() + EPSILON ) - { - type::vector edge_remove; - edge_remove.push_back( i-1 ); - - msg_info() << "releaseWirePart() -> remove edge number "<< i ; - - edgeMod->removeEdges(edge_remove,false); // remove the single edge and do not remove any point... - - msg_info() << "WireRestShape _topology name="<<_topology->getName()<<" - numEdges ="<<_topology->getNbEdges() ; - - return; - } - } - - dmsg_info() <<" Wire Part is brokenIn2... should implement a topo change !" ; -} - - template void WireRestShape::getSamplingParameters(type::vector& xP_noticeable, type::vector& nbP_density) const { - - xP_noticeable.clear(); - nbP_density.clear(); - - if (d_brokenIn2.getValue()) - { - for (unsigned int i=0; i getReleaseCurvAbs() ) - break; - xP_noticeable.push_back(x); - nbP_density.push_back(d_density.getValue()[i]); - } - xP_noticeable.push_back( getReleaseCurvAbs()); - - dmsg_info() <<"getSamplingParameters brokenIn2 detected - return xP_noticeable ="<, TopologyContainer, BaseLink::FLAG_STOREPATH | BaseLink::FLAG_STRONGLINK> l_topology; /// Pointer to the topology container, should be set using @sa l_topology, otherwise will search for one in current Node. TopologyContainer* _topology{ nullptr }; - /// Pointer to the topology modifier. Will be set at init by searching one in @sa _topology context. - EdgeSetTopologyModifier* edgeMod{ nullptr }; /// Link to be set to the topology container in the component graph. SingleLink, MeshLoader, BaseLink::FLAG_STOREPATH | BaseLink::FLAG_STRONGLINK> l_loader; diff --git a/src/BeamAdapter/component/engine/WireRestShape.inl b/src/BeamAdapter/component/engine/WireRestShape.inl index 559abdee1..bd3f5706d 100644 --- a/src/BeamAdapter/component/engine/WireRestShape.inl +++ b/src/BeamAdapter/component/engine/WireRestShape.inl @@ -34,7 +34,6 @@ #include #include -#include #include #include @@ -187,16 +186,7 @@ void WireRestShape::init() return; } } - - // Get pointer to the topology Modifier (for topological changes) - _topology->getContext()->get(edgeMod); - - if (edgeMod == nullptr) - { - msg_warning() << "No EdgeSetTopologyModifier found in the same node as the topology container: " << _topology->getName() << ". This wire won't support topological changes."; - } - - + //////////////////////////////////////////////////////// ////////// keyPoint list and Density Assignement /////// @@ -720,10 +710,7 @@ void WireRestShape::getRestPosNonProcedural(Real& abs, Coord &p) template typename WireRestShape::Real WireRestShape::getLength() { - if(d_brokenIn2.getValue()) - return d_straightLength.getValue(); - else - return d_length.getValue(); + return d_length.getValue(); } template From 27fa7d4a55258dbc2334281ec892f5cabdf3545d Mon Sep 17 00:00:00 2001 From: epernod Date: Thu, 19 Oct 2023 21:55:33 +0200 Subject: [PATCH 04/22] Fix tests --- BeamAdapter_test/component/model/WireRestShape_test.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/BeamAdapter_test/component/model/WireRestShape_test.cpp b/BeamAdapter_test/component/model/WireRestShape_test.cpp index ef7eec3c7..0e81d0a0b 100644 --- a/BeamAdapter_test/component/model/WireRestShape_test.cpp +++ b/BeamAdapter_test/component/model/WireRestShape_test.cpp @@ -159,9 +159,7 @@ void WireRestShape_test::testParameterInit() Real fullLength = wire->getLength(); EXPECT_EQ(fullLength, 100.0); - Real straightLength = wire->getReleaseCurvAbs(); - EXPECT_EQ(straightLength, 95.0); - + Real straightLength = 95.0; vector keysPoints, keysPoints_ref = { 0, straightLength, fullLength }; Real ratio = straightLength / fullLength; vector nbP_density, nbP_density_ref = { int(floor(5.0 * ratio)), int(floor(20.0 * (1 - ratio))) }; @@ -243,7 +241,7 @@ void WireRestShape_test::testTransformMethods() EXPECT_NE(wire, nullptr); Real fullLength = wire->getLength(); - Real straightLength = wire->getReleaseCurvAbs(); + Real straightLength = 95.0; Real middHook = (fullLength + straightLength) / 2; Transform transfo_0, transfo_1, transfo_2, transfo_3; From 626b5b1cdbe5b59ed0e5ccc141c58b018aecf315 Mon Sep 17 00:00:00 2001 From: epernod Date: Tue, 4 Oct 2022 15:17:29 +0200 Subject: [PATCH 05/22] [doc] Add some comments --- src/BeamAdapter/component/engine/WireRestShape.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/BeamAdapter/component/engine/WireRestShape.h b/src/BeamAdapter/component/engine/WireRestShape.h index 5caa80d12..9905a6969 100644 --- a/src/BeamAdapter/component/engine/WireRestShape.h +++ b/src/BeamAdapter/component/engine/WireRestShape.h @@ -51,8 +51,9 @@ using sofa::core::loader::MeshLoader; /** * \class WireRestShape * \brief Describe the shape functions on multiple segments - * - * Describe the shape functions on multiple segments using curvilinear abscissa + * + * Describe the full shape of a Wire with a given length and radius. The wire is discretized by a set of beams (given by the keyPoints and the relatives Beam density) + * This component compute the beam discretization and the shape functions on multiple segments using curvilinear abscissa. */ template class WireRestShape : public core::objectmodel::BaseObject From 8277457c67dd53ffc2625ad20f3a8365586a4881 Mon Sep 17 00:00:00 2001 From: epernod Date: Sat, 24 Sep 2022 00:33:18 +0200 Subject: [PATCH 06/22] Update WireREstShape to use the new WireMaterialSection links --- .../component/engine/WireRestShape.h | 27 ++---- .../component/engine/WireRestShape.inl | 97 +++---------------- 2 files changed, 21 insertions(+), 103 deletions(-) diff --git a/src/BeamAdapter/component/engine/WireRestShape.h b/src/BeamAdapter/component/engine/WireRestShape.h index 9905a6969..f56ae56ef 100644 --- a/src/BeamAdapter/component/engine/WireRestShape.h +++ b/src/BeamAdapter/component/engine/WireRestShape.h @@ -34,6 +34,8 @@ #include #include +#include + #include #include #include @@ -48,6 +50,8 @@ namespace _wirerestshape_ using sofa::core::topology::TopologyContainer; using sofa::core::loader::MeshLoader; +using namespace sofa::beamadapter; + /** * \class WireRestShape * \brief Describe the shape functions on multiple segments @@ -144,23 +148,15 @@ class WireRestShape : public core::objectmodel::BaseObject Data< int > d_numEdges; Data > d_numEdgesCollis; - /// User Data about the Young modulus - Data d_poissonRatio; - Data d_youngModulus1; - Data d_youngModulus2; - - /// Radius - Data d_radius1; - Data d_radius2; - Data d_innerRadius1; - Data d_innerRadius2; - - Data d_massDensity1; - Data d_massDensity2; - /// broken in 2 case Data d_drawRestShape; + /// Link to be set to the topology container in the component graph. + SingleLink, WireSectionMaterial, BaseLink::FLAG_STOREPATH | BaseLink::FLAG_STRONGLINK> l_sectionMaterial1; + + /// Link to be set to the topology container in the component graph. + SingleLink, WireSectionMaterial, BaseLink::FLAG_STOREPATH | BaseLink::FLAG_STRONGLINK> l_sectionMaterial2; + private: /// Data required for the File loading type::vector m_localRestPositions; @@ -168,9 +164,6 @@ class WireRestShape : public core::objectmodel::BaseObject type::vector m_curvAbs ; double m_absOfGeometry {0}; - BeamSection beamSection1; - BeamSection beamSection2; - /// Link to be set to the topology container in the component graph. SingleLink, TopologyContainer, BaseLink::FLAG_STOREPATH | BaseLink::FLAG_STRONGLINK> l_topology; /// Pointer to the topology container, should be set using @sa l_topology, otherwise will search for one in current Node. diff --git a/src/BeamAdapter/component/engine/WireRestShape.inl b/src/BeamAdapter/component/engine/WireRestShape.inl index bd3f5706d..7e2058b6b 100644 --- a/src/BeamAdapter/component/engine/WireRestShape.inl +++ b/src/BeamAdapter/component/engine/WireRestShape.inl @@ -68,16 +68,9 @@ WireRestShape::WireRestShape() : , d_keyPoints(initData(&d_keyPoints,"keyPoints","key points of the shape (curv absc)")) , d_numEdges(initData(&d_numEdges, 10, "numEdges","number of Edges for the visual model")) , d_numEdgesCollis(initData(&d_numEdgesCollis,"numEdgesCollis", "number of Edges for the collision model" )) - , d_poissonRatio(initData(&d_poissonRatio,(Real)0.49,"poissonRatio","Poisson Ratio")) - , d_youngModulus1(initData(&d_youngModulus1,(Real)5000,"youngModulus","Young Modulus")) - , d_youngModulus2(initData(&d_youngModulus2,(Real)3000,"youngModulusExtremity","youngModulus for beams at the extremity\nonly if not straight")) - , d_radius1(initData(&d_radius1,(Real)1.0f,"radius","radius")) - , d_radius2(initData(&d_radius2,(Real)1.0f,"radiusExtremity","radius for beams at the extremity\nonly if not straight")) - , d_innerRadius1(initData(&d_innerRadius1,(Real)0.0f,"innerRadius","inner radius if it applies")) - , d_innerRadius2(initData(&d_innerRadius2,(Real)0.0f,"innerRadiusExtremity","inner radius for beams at the extremity\nonly if not straight")) - , d_massDensity1(initData(&d_massDensity1,(Real)1.0,"massDensity", "Density of the mass (usually in kg/m^3)" )) - , d_massDensity2(initData(&d_massDensity2,(Real)1.0,"massDensityExtremity", "Density of the mass at the extremity\nonly if not straight" )) , d_drawRestShape(initData(&d_drawRestShape, (bool)false, "draw", "draw rest shape")) + , l_sectionMaterial1(initLink("main_material", "link to the fist Wire Section Material")) + , l_sectionMaterial2(initLink("extremity_material", "link to the second Wire Section Material")) , l_topology(initLink("topology", "link to the topology container")) , l_loader(initLink("loader", "link to the MeshLoader")) { @@ -233,29 +226,6 @@ void WireRestShape::init() msg_info() <<"WireRestShape end init" ; - // Prepare beam sections - double r = this->d_radius1.getValue(); - double rInner = this->d_innerRadius1.getValue(); - this->beamSection1._r = r; - this->beamSection1._rInner = rInner; - this->beamSection1._Iz = M_PI*(r*r*r*r - rInner*rInner*rInner*rInner)/4.0; - this->beamSection1._Iy = this->beamSection1._Iz ; - this->beamSection1._J = this->beamSection1._Iz + this->beamSection1._Iy; - this->beamSection1._A = M_PI*(r*r - rInner*rInner); - this->beamSection1._Asy = 0.0; - this->beamSection1._Asz = 0.0; - - r = this->d_radius2.getValue(); - rInner = this->d_innerRadius2.getValue(); - this->beamSection2._r = r; - this->beamSection2._rInner = rInner; - this->beamSection2._Iz = M_PI*(r*r*r*r - rInner*rInner*rInner*rInner)/4.0; - this->beamSection2._Iy = this->beamSection2._Iz ; - this->beamSection2._J = this->beamSection2._Iz + this->beamSection2._Iy; - this->beamSection2._A = M_PI*(r*r - rInner*rInner); - this->beamSection2._Asy = 0.0; - this->beamSection2._Asz = 0.0; - this->d_componentState.setValue(sofa::core::objectmodel::ComponentState::Valid); } @@ -368,31 +338,11 @@ void WireRestShape::getRestTransformOnX(Transform &global_H_local, co template void WireRestShape::getYoungModulusAtX(const Real& x_curv, Real& youngModulus, Real& cPoisson) const { - //Initialization - Real _E1, _E2; - youngModulus = 0.0; - cPoisson = 0.0; - - //Get the two possible values of the Young modulus - _E1 = this->d_youngModulus1.getValue(); - _E2 = this->d_youngModulus2.getValue(); - - //Get User data - cPoisson = this->d_poissonRatio.getValue(); - //Depending on the position of the beam, determine the Young modulus - if(x_curv <= this->d_straightLength.getValue()) - { - youngModulus = _E1; - } + if (x_curv > this->d_straightLength.getValue() && l_sectionMaterial2.get()) // extremity of the wire + return l_sectionMaterial2.get()->getYoungModulusAtX(youngModulus, cPoisson); else - { - if(_E2 == 0.0) - youngModulus = _E1; - else - youngModulus = _E2; - } - return; + return l_sectionMaterial1.get()->getYoungModulusAtX(youngModulus, cPoisson); } @@ -401,43 +351,18 @@ void WireRestShape::getInterpolationParam(const Real& x_curv, Real &_ { if(x_curv <= this->d_straightLength.getValue()) { - if(d_massDensity1.isSet()) - _rho = d_massDensity1.getValue(); - - if(d_radius1.isSet()) - { - _A =beamSection1._A; - _Iy =beamSection1._Iy; - _Iz =beamSection1._Iz; - _Asy =beamSection1._Asy; - _Asz =beamSection1._Asz; - _J =beamSection1._J; - } + return l_sectionMaterial1.get()->getInterpolationParam(_rho, _A, _Iy, _Iz, _Asy, _Asz, _J); } else { - if(d_massDensity2.isSet()) - _rho = d_massDensity2.getValue(); - else if(d_massDensity1.isSet()) - _rho = d_massDensity1.getValue(); - - if(d_radius2.isSet()) + auto mat = l_sectionMaterial2.get(); + if (mat) { - _A =beamSection2._A; - _Iy =beamSection2._Iy; - _Iz =beamSection2._Iz; - _Asy =beamSection2._Asy; - _Asz =beamSection2._Asz; - _J =beamSection2._J; + return mat->getInterpolationParam(_rho, _A, _Iy, _Iz, _Asy, _Asz, _J); } - else if(d_radius1.isSet()) + else { - _A =beamSection1._A; - _Iy =beamSection1._Iy; - _Iz =beamSection1._Iz; - _Asy =beamSection1._Asy; - _Asz =beamSection1._Asz; - _J =beamSection1._J; + return l_sectionMaterial1.get()->getInterpolationParam(_rho, _A, _Iy, _Iz, _Asy, _Asz, _J); } } } From 13bda922b3eab56ad3073e94cc95e8a52b3b4eb3 Mon Sep 17 00:00:00 2001 From: epernod Date: Mon, 26 Sep 2022 09:15:47 +0200 Subject: [PATCH 07/22] Add check on BeamMAterial section --- src/BeamAdapter/component/engine/WireRestShape.inl | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/BeamAdapter/component/engine/WireRestShape.inl b/src/BeamAdapter/component/engine/WireRestShape.inl index 7e2058b6b..eb5dc0f4f 100644 --- a/src/BeamAdapter/component/engine/WireRestShape.inl +++ b/src/BeamAdapter/component/engine/WireRestShape.inl @@ -170,6 +170,13 @@ void WireRestShape::init() initFromLoader(); } } + if (!l_sectionMaterial1.get()) + { + msg_error() << "No WireSectionMaterial set. At least one material should be set and link using main_material"; + this->d_componentState.setValue(sofa::core::objectmodel::ComponentState::Invalid); + return; + } + else { if (!fillTopology()) From dfebce317ac7c9ed5313c78f4c9fb04bf073acd4 Mon Sep 17 00:00:00 2001 From: epernod Date: Sun, 2 Oct 2022 01:35:37 +0200 Subject: [PATCH 08/22] [src] Move numEdges and numEdgesCollision into WireSectionMaterial --- .../component/engine/WireRestShape.h | 5 +- .../component/engine/WireRestShape.inl | 114 +++++++++++++----- 2 files changed, 87 insertions(+), 32 deletions(-) diff --git a/src/BeamAdapter/component/engine/WireRestShape.h b/src/BeamAdapter/component/engine/WireRestShape.h index f56ae56ef..d15e76b70 100644 --- a/src/BeamAdapter/component/engine/WireRestShape.h +++ b/src/BeamAdapter/component/engine/WireRestShape.h @@ -86,7 +86,8 @@ class WireRestShape : public core::objectmodel::BaseObject /////////////////////////// Inherited from BaseObject ////////////////////////////////////////// void parse(core::objectmodel::BaseObjectDescription* arg) override; void init() override ; - + void initTopology(); + void draw(const core::visual::VisualParams * vparams) override ; @@ -145,8 +146,6 @@ class WireRestShape : public core::objectmodel::BaseObject Data d_spireHeight; Data > d_density; Data > d_keyPoints; - Data< int > d_numEdges; - Data > d_numEdgesCollis; /// broken in 2 case Data d_drawRestShape; diff --git a/src/BeamAdapter/component/engine/WireRestShape.inl b/src/BeamAdapter/component/engine/WireRestShape.inl index eb5dc0f4f..9d70d10f2 100644 --- a/src/BeamAdapter/component/engine/WireRestShape.inl +++ b/src/BeamAdapter/component/engine/WireRestShape.inl @@ -66,8 +66,6 @@ WireRestShape::WireRestShape() : , d_spireHeight(initData(&d_spireHeight, (Real)0.01, "spireHeight", "height between each spire")) , d_density(initData(&d_density, "densityOfBeams", "density of beams between key points")) , d_keyPoints(initData(&d_keyPoints,"keyPoints","key points of the shape (curv absc)")) - , d_numEdges(initData(&d_numEdges, 10, "numEdges","number of Edges for the visual model")) - , d_numEdgesCollis(initData(&d_numEdgesCollis,"numEdgesCollis", "number of Edges for the collision model" )) , d_drawRestShape(initData(&d_drawRestShape, (bool)false, "draw", "draw rest shape")) , l_sectionMaterial1(initLink("main_material", "link to the fist Wire Section Material")) , l_sectionMaterial2(initLink("extremity_material", "link to the second Wire Section Material")) @@ -187,6 +185,7 @@ void WireRestShape::init() } } + initTopology(); //////////////////////////////////////////////////////// ////////// keyPoint list and Density Assignement /////// @@ -223,20 +222,62 @@ void WireRestShape::init() } } - if(!d_numEdgesCollis.getValue().size()) - { - auto densityCol = sofa::helper::getWriteOnlyAccessor(d_numEdgesCollis); - densityCol.resize(keyPointList.size()-1); - for (unsigned int i=0; id_componentState.setValue(sofa::core::objectmodel::ComponentState::Valid); } +template +void WireRestShape::initTopology() +{ + /// fill topology : + _topology->clear(); + _topology->cleanup(); + + const Real& fullLength = this->d_length.getValue(); + const Real& straightLength = this->d_straightLength.getValue(); + + // Add topology of the first material + WireSectionMaterial* mat1 = l_sectionMaterial1.get(); + int nbrEdges1 = mat1->getNbVisualEdges(); + Real dx1 = straightLength / nbrEdges1; + + /// add points from main material + for (int i = 0; i < nbrEdges1 + 1; i++) { + _topology->addPoint(i * dx1, 0, 0); + //std::cout << "1. addPoint: " << i << " -> " << i * dx1 << std::endl; + } + + /// add segments from main material + for (int i = 0; i < nbrEdges1; i++) { + _topology->addEdge(i, i + 1); + //std::cout << "1. addEdge: " << i << " - " << i + 1 << std::endl; + } + + + // Add topology of the second material + if (WireSectionMaterial* mat2 = l_sectionMaterial2.get()) + { + Real lengthExtremity = fullLength - straightLength; + int nbrEdges2 = mat2->getNbVisualEdges(); + Real dx2 = lengthExtremity / nbrEdges2; + + /// add points from main material + for (int i = 1; i < nbrEdges2 + 1; i++) { + _topology->addPoint(straightLength + i * dx2, 0, 0); + //std::cout << "2. addPoint: " << i + nbrEdges1 << " -> " << straightLength + i * dx2 << std::endl; + } + + /// add segments from main material + for (int i = nbrEdges1; i < nbrEdges1 + nbrEdges2; i++) { + _topology->addEdge(i, i + 1); + //std::cout << "2. addEdge: " << i << " - " << i + 1 << std::endl; + } + } +} + + template void WireRestShape::getSamplingParameters(type::vector& xP_noticeable, type::vector& nbP_density) const @@ -257,27 +298,41 @@ void WireRestShape::getCollisionSampling(Real &dx, const Real &x_curv x_used=0.0; // verify that size of numEdgesCollis = size of keyPoints-1 - if( d_numEdgesCollis.getValue().size() != d_keyPoints.getValue().size()-1) + //if( d_numEdgesCollis.getValue().size() != d_keyPoints.getValue().size()-1) + //{ + // msg_error() << "Problem size of numEdgesCollis ()" << d_numEdgesCollis.getValue().size() + // << " != size of keyPoints-1 " << d_keyPoints.getValue().size()-1 ; + // numLines = (unsigned int)d_numEdgesCollis.getValue()[0]; + // dx=d_length.getValue()/numLines; + // return; + //} + + if (x_used <= d_straightLength.getValue()) { - msg_error() << "Problem size of numEdgesCollis ()" << d_numEdgesCollis.getValue().size() << " != size of keyPoints-1 " << d_keyPoints.getValue().size()-1 ; - numLines = (unsigned int)d_numEdgesCollis.getValue()[0]; - dx=d_length.getValue()/numLines; - return; + numLines = l_sectionMaterial1.get()->getNbCollisionEdges(); + dx = d_straightLength.getValue() / numLines; } - - - for (unsigned int i=1; id_keyPoints.getValue().size(); i++) + else if (x_used <= d_length.getValue()) { - if( x_used < this->d_keyPoints.getValue()[i] ) - { - numLines = (unsigned int)d_numEdgesCollis.getValue()[i-1]; - dx=(this->d_keyPoints.getValue()[i] - this->d_keyPoints.getValue()[i-1])/numLines; - return; - } + numLines = l_sectionMaterial2.get()->getNbCollisionEdges(); + dx = d_length.getValue() / numLines; + } + else + { + dx = d_length.getValue() / 20; + msg_error() << " problem is getCollisionSampling : x_curv " << x_used << " is not between keyPoints" << d_keyPoints.getValue(); } - dx=d_length.getValue()/20; - msg_error() << " problem is getCollisionSampling : x_curv "<d_keyPoints.getValue().size(); i++) + //{ + // if( x_used < this->d_keyPoints.getValue()[i] ) + // { + // numLines = (unsigned int)d_numEdgesCollis.getValue()[i - 1]; + // dx = (this->d_keyPoints.getValue()[i] - this->d_keyPoints.getValue()[i - 1]) / numLines; + // return; + // } + //} + } @@ -648,10 +703,11 @@ typename WireRestShape::Real WireRestShape::getLength() template void WireRestShape::getNumberOfCollisionSegment(Real &dx, unsigned int &numLines) { - numLines = 0; - for (unsigned i=0; igetNbCollisionEdges(); + + if (auto mat2 = l_sectionMaterial2.get()) { - numLines += (unsigned int)d_numEdgesCollis.getValue()[i]; + numLines += mat2->getNbCollisionEdges(); } dx=d_length.getValue()/numLines; } From 5d0fbb17286e20f0328d507c577031d48794f351 Mon Sep 17 00:00:00 2001 From: epernod Date: Tue, 4 Oct 2022 11:30:25 +0200 Subject: [PATCH 09/22] [src] Update WireRestShape to use a vector of Links instead of hardcoded material1 and material2 --- .../component/engine/WireRestShape.h | 16 +- .../component/engine/WireRestShape.inl | 233 +++++++++--------- 2 files changed, 126 insertions(+), 123 deletions(-) diff --git a/src/BeamAdapter/component/engine/WireRestShape.h b/src/BeamAdapter/component/engine/WireRestShape.h index d15e76b70..e19b7407b 100644 --- a/src/BeamAdapter/component/engine/WireRestShape.h +++ b/src/BeamAdapter/component/engine/WireRestShape.h @@ -86,7 +86,6 @@ class WireRestShape : public core::objectmodel::BaseObject /////////////////////////// Inherited from BaseObject ////////////////////////////////////////// void parse(core::objectmodel::BaseObjectDescription* arg) override; void init() override ; - void initTopology(); void draw(const core::visual::VisualParams * vparams) override ; @@ -123,6 +122,12 @@ class WireRestShape : public core::objectmodel::BaseObject void rotateFrameForAlignX(const Quat &input, Vec3 &x, Quat &output); +protected: + /// Internal method to init Lengths vector @sa d_keyPoints if not set using @sa d_length and @sa d_straightLength. Returns false if init can't be performed. + bool initLengths(); + /// Internal method to init Edge Topology @sa _topology using the list of materials @sa l_sectionMaterials. Returns false if init can't be performed. + bool initTopology(); + /////////////////////////// Deprecated Methods ////////////////////////////////////////// @@ -149,12 +154,9 @@ class WireRestShape : public core::objectmodel::BaseObject /// broken in 2 case Data d_drawRestShape; - - /// Link to be set to the topology container in the component graph. - SingleLink, WireSectionMaterial, BaseLink::FLAG_STOREPATH | BaseLink::FLAG_STRONGLINK> l_sectionMaterial1; - - /// Link to be set to the topology container in the component graph. - SingleLink, WireSectionMaterial, BaseLink::FLAG_STOREPATH | BaseLink::FLAG_STRONGLINK> l_sectionMaterial2; + + /// Vector or links to the Wire section material. The order of the linked material will define the WireShape structure. + MultiLink, WireSectionMaterial, BaseLink::FLAG_STOREPATH | BaseLink::FLAG_STRONGLINK> l_sectionMaterials; private: /// Data required for the File loading diff --git a/src/BeamAdapter/component/engine/WireRestShape.inl b/src/BeamAdapter/component/engine/WireRestShape.inl index 9d70d10f2..e39baa61c 100644 --- a/src/BeamAdapter/component/engine/WireRestShape.inl +++ b/src/BeamAdapter/component/engine/WireRestShape.inl @@ -67,8 +67,7 @@ WireRestShape::WireRestShape() : , d_density(initData(&d_density, "densityOfBeams", "density of beams between key points")) , d_keyPoints(initData(&d_keyPoints,"keyPoints","key points of the shape (curv absc)")) , d_drawRestShape(initData(&d_drawRestShape, (bool)false, "draw", "draw rest shape")) - , l_sectionMaterial1(initLink("main_material", "link to the fist Wire Section Material")) - , l_sectionMaterial2(initLink("extremity_material", "link to the second Wire Section Material")) + , l_sectionMaterials(initLink("wireMaterials", "link to Wire Section Materials (to be ordered according to the instrument, from handle to tip)")) , l_topology(initLink("topology", "link to the topology container")) , l_loader(initLink("loader", "link to the MeshLoader")) { @@ -168,9 +167,20 @@ void WireRestShape::init() initFromLoader(); } } - if (!l_sectionMaterial1.get()) + if (l_sectionMaterials.empty()) + { + msg_error() << "No WireSectionMaterial set. At least one material should be set and link using wireMaterials."; + this->d_componentState.setValue(sofa::core::objectmodel::ComponentState::Invalid); + return; + } + + + //////////////////////////////////////////////////////// + ////////// keyPoint list and Density Assignement /////// + //////////////////////////////////////////////////////// + + if (!initLengths()) { - msg_error() << "No WireSectionMaterial set. At least one material should be set and link using main_material"; this->d_componentState.setValue(sofa::core::objectmodel::ComponentState::Invalid); return; } @@ -187,94 +197,92 @@ void WireRestShape::init() initTopology(); - //////////////////////////////////////////////////////// - ////////// keyPoint list and Density Assignement /////// - //////////////////////////////////////////////////////// + + this->d_componentState.setValue(sofa::core::objectmodel::ComponentState::Valid); + msg_info() << "WireRestShape end init"; +} + + +template +bool WireRestShape::initLengths() +{ auto keyPointList = sofa::helper::getWriteOnlyAccessor(d_keyPoints); - if(!keyPointList.size()) + auto densityList = sofa::helper::getWriteOnlyAccessor(d_density); + + // In case use used length and straightLenght instead of keyPointList, create keyPointList + if (keyPointList.empty()) { keyPointList.push_back(0.0); - if(d_straightLength.getValue()>= 0.001*this->d_length.getValue() && d_straightLength.getValue() <= 0.999*d_length.getValue()) + if (d_straightLength.getValue() >= 0.001 * this->d_length.getValue() && d_straightLength.getValue() <= 0.999 * d_length.getValue()) keyPointList.push_back(d_straightLength.getValue()); keyPointList.push_back(d_length.getValue()); } - if( d_density.getValue().size() != keyPointList.size()-1) + // checking sizes between keypointList and number of input material + if (l_sectionMaterials.size() != keyPointList.size() - 1) { - auto densityList = sofa::helper::getWriteOnlyAccessor(d_density); + msg_error() << "Wrong number of inputs. Component can't be init. Number of input materials: " << l_sectionMaterials.size() << ", should be equal to keyPointList.size()-1. keyPointList.size() is equal to: " << keyPointList.size(); + return false; + } - if(densityList.size() > keyPointList.size()-1 ) - densityList.resize(keyPointList.size()-1); - else - { - densityList.clear(); + if (densityList.size() != keyPointList.size() - 1) + { + msg_warning() << "Wrong number of densityOfBeams. Given: " << densityList.size() << ", should be equal to keyPointList.size()-1: '" << keyPointList.size() + << "'. densityOfBeams will be recomputed using Wire material number of collision edges."; + densityList.clear(); - if(d_straightLength.getValue()>= 0.001*this->d_length.getValue() ) - { - int numNodes = (int) floor(5.0*d_straightLength.getValue() / d_length.getValue() ); - densityList.push_back(numNodes); - } - if( d_straightLength.getValue() <= 0.999*d_length.getValue()) - { - int numNodes = (int) floor(20.0*(1.0 - d_straightLength.getValue() / d_length.getValue()) ); - densityList.push_back(numNodes); - } + for (unsigned int i = 0; i < keyPointList.size() - 1; ++i) + { + auto mat = l_sectionMaterials.get(i); + int nbrCollEdges = mat->getNbCollisionEdges(); + densityList.push_back(nbrCollEdges); } } - msg_info() <<"WireRestShape end init" ; - - this->d_componentState.setValue(sofa::core::objectmodel::ComponentState::Valid); + return true; } template -void WireRestShape::initTopology() +bool WireRestShape::initTopology() { /// fill topology : _topology->clear(); _topology->cleanup(); - const Real& fullLength = this->d_length.getValue(); - const Real& straightLength = this->d_straightLength.getValue(); - - // Add topology of the first material - WireSectionMaterial* mat1 = l_sectionMaterial1.get(); - int nbrEdges1 = mat1->getNbVisualEdges(); - Real dx1 = straightLength / nbrEdges1; - - /// add points from main material - for (int i = 0; i < nbrEdges1 + 1; i++) { - _topology->addPoint(i * dx1, 0, 0); - //std::cout << "1. addPoint: " << i << " -> " << i * dx1 << std::endl; - } - - /// add segments from main material - for (int i = 0; i < nbrEdges1; i++) { - _topology->addEdge(i, i + 1); - //std::cout << "1. addEdge: " << i << " - " << i + 1 << std::endl; - } - - - // Add topology of the second material - if (WireSectionMaterial* mat2 = l_sectionMaterial2.get()) + const type::vector& keyPts = d_keyPoints.getValue(); + if (l_sectionMaterials.size() != keyPts.size() - 1) { - Real lengthExtremity = fullLength - straightLength; - int nbrEdges2 = mat2->getNbVisualEdges(); - Real dx2 = lengthExtremity / nbrEdges2; - - /// add points from main material - for (int i = 1; i < nbrEdges2 + 1; i++) { - _topology->addPoint(straightLength + i * dx2, 0, 0); - //std::cout << "2. addPoint: " << i + nbrEdges1 << " -> " << straightLength + i * dx2 << std::endl; + msg_error() << "Wrong number of inputs. Component can't be init. Number of input materials: " << l_sectionMaterials.size() << ", should be equal to keyPointList.size()-1. keyPointList.size() is equal to: " << keyPts.size(); + return false; + } + + Real prev_length = 0.0; + int prev_edges = 0; + int startPtId = 0; + for (auto i = 0; i < l_sectionMaterials.size(); ++i) + { + // Add topology of the material + int nbrVisuEdges = l_sectionMaterials.get(i)->getNbVisualEdges(); + Real length = fabs(keyPts[i + 1] - keyPts[i]); + Real dx = length / nbrVisuEdges; + + // add points from the material + for (int i = startPtId; i < nbrVisuEdges + 1; i++) { + _topology->addPoint(prev_length + i * dx, 0, 0); } - /// add segments from main material - for (int i = nbrEdges1; i < nbrEdges1 + nbrEdges2; i++) { + // add segments from the material + for (int i = prev_edges; i < prev_edges + nbrVisuEdges; i++) { _topology->addEdge(i, i + 1); - //std::cout << "2. addEdge: " << i << " - " << i + 1 << std::endl; } + + prev_length = length; + prev_edges = nbrVisuEdges; + startPtId = 1; // Assume the last point of mat[n] == first point of mat[n+1] } + + return true; } @@ -297,42 +305,34 @@ void WireRestShape::getCollisionSampling(Real &dx, const Real &x_curv if(x_used<0.0) x_used=0.0; - // verify that size of numEdgesCollis = size of keyPoints-1 - //if( d_numEdgesCollis.getValue().size() != d_keyPoints.getValue().size()-1) - //{ - // msg_error() << "Problem size of numEdgesCollis ()" << d_numEdgesCollis.getValue().size() - // << " != size of keyPoints-1 " << d_keyPoints.getValue().size()-1 ; - // numLines = (unsigned int)d_numEdgesCollis.getValue()[0]; - // dx=d_length.getValue()/numLines; - // return; - //} - - if (x_used <= d_straightLength.getValue()) - { - numLines = l_sectionMaterial1.get()->getNbCollisionEdges(); - dx = d_straightLength.getValue() / numLines; - } - else if (x_used <= d_length.getValue()) + const type::vector& keyPts = d_keyPoints.getValue(); + + // verify that size of number of materials == size of keyPoints-1 + if (l_sectionMaterials.size() != keyPts.size() - 1) { - numLines = l_sectionMaterial2.get()->getNbCollisionEdges(); + msg_error() << "Problem size of number of materials: " << l_sectionMaterials.size() + << " != size of keyPoints-1 " << keyPts.size()-1 + << ". Returning default values."; + numLines = 20; dx = d_length.getValue() / numLines; + return; } - else + + // Check in which section x_used belongs to and get access to this section material + for (auto i = 1; i< keyPts.size(); ++i) { - dx = d_length.getValue() / 20; - msg_error() << " problem is getCollisionSampling : x_curv " << x_used << " is not between keyPoints" << d_keyPoints.getValue(); + if (x_used <= keyPts[i]) + { + numLines = l_sectionMaterials.get(i-1)->getNbCollisionEdges(); + dx = d_straightLength.getValue() / numLines; + return; + } } - //for (unsigned int i=1; id_keyPoints.getValue().size(); i++) - //{ - // if( x_used < this->d_keyPoints.getValue()[i] ) - // { - // numLines = (unsigned int)d_numEdgesCollis.getValue()[i - 1]; - // dx = (this->d_keyPoints.getValue()[i] - this->d_keyPoints.getValue()[i - 1]) / numLines; - // return; - // } - //} - + // If x_used is out of bounds. Warn user and returns default value. + numLines = 20; + dx = d_length.getValue() / numLines; + msg_error() << " problem is getCollisionSampling : x_curv " << x_used << " is not between keyPoints" << d_keyPoints.getValue(); } @@ -400,35 +400,37 @@ void WireRestShape::getRestTransformOnX(Transform &global_H_local, co template void WireRestShape::getYoungModulusAtX(const Real& x_curv, Real& youngModulus, Real& cPoisson) const { - //Depending on the position of the beam, determine the Young modulus - if (x_curv > this->d_straightLength.getValue() && l_sectionMaterial2.get()) // extremity of the wire - return l_sectionMaterial2.get()->getYoungModulusAtX(youngModulus, cPoisson); - else - return l_sectionMaterial1.get()->getYoungModulusAtX(youngModulus, cPoisson); + const type::vector& keyPts = d_keyPoints.getValue(); + // Depending on the position of the beam, determine the corresponding section material and returning its Young modulus + for (auto i = 1; i < keyPts.size(); ++i) + { + if (x_curv <= keyPts[i]) + { + return l_sectionMaterials.get(i - 1)->getYoungModulusAtX(youngModulus, cPoisson); + } + } + + msg_error() << " problem in getYoungModulusAtX : x_curv " << x_curv << " is not between keyPoints" << keyPts; } template void WireRestShape::getInterpolationParam(const Real& x_curv, Real &_rho, Real &_A, Real &_Iy , Real &_Iz, Real &_Asy, Real &_Asz, Real &_J) const { - if(x_curv <= this->d_straightLength.getValue()) + const type::vector& keyPts = d_keyPoints.getValue(); + // Check in which section x_used belongs to and get access to this section material + for (auto i = 1; i < keyPts.size(); ++i) { - return l_sectionMaterial1.get()->getInterpolationParam(_rho, _A, _Iy, _Iz, _Asy, _Asz, _J); - } - else - { - auto mat = l_sectionMaterial2.get(); - if (mat) + if (x_curv <= keyPts[i]) { - return mat->getInterpolationParam(_rho, _A, _Iy, _Iz, _Asy, _Asz, _J); - } - else - { - return l_sectionMaterial1.get()->getInterpolationParam(_rho, _A, _Iy, _Iz, _Asy, _Asz, _J); + return l_sectionMaterials.get(i - 1)->getInterpolationParam(_rho, _A, _Iy, _Iz, _Asy, _Asz, _J); } } + + msg_error() << " problem in getInterpolationParam : x_curv " << x_curv << " is not between keyPoints" << keyPts; } + template bool WireRestShape::checkTopology() { @@ -703,13 +705,12 @@ typename WireRestShape::Real WireRestShape::getLength() template void WireRestShape::getNumberOfCollisionSegment(Real &dx, unsigned int &numLines) { - numLines = l_sectionMaterial1.get()->getNbCollisionEdges(); - - if (auto mat2 = l_sectionMaterial2.get()) + numLines = 0; + for (auto i = 0; i < l_sectionMaterials.size(); ++i) { - numLines += mat2->getNbCollisionEdges(); + numLines += l_sectionMaterials.get(i)->getNbCollisionEdges(); } - dx=d_length.getValue()/numLines; + dx = d_length.getValue() / numLines; } template From d6a1b6c75607204ff714ecf3c631f8eee0568bec Mon Sep 17 00:00:00 2001 From: epernod Date: Thu, 6 Oct 2022 00:25:07 +0200 Subject: [PATCH 10/22] [WireRestShape] Fix Collision edges sampling --- src/BeamAdapter/component/engine/WireRestShape.inl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/BeamAdapter/component/engine/WireRestShape.inl b/src/BeamAdapter/component/engine/WireRestShape.inl index e39baa61c..556a58def 100644 --- a/src/BeamAdapter/component/engine/WireRestShape.inl +++ b/src/BeamAdapter/component/engine/WireRestShape.inl @@ -324,7 +324,9 @@ void WireRestShape::getCollisionSampling(Real &dx, const Real &x_curv if (x_used <= keyPts[i]) { numLines = l_sectionMaterials.get(i-1)->getNbCollisionEdges(); - dx = d_straightLength.getValue() / numLines; + + Real length = fabs(keyPts[i] - keyPts[i-1]); + dx = length / numLines; return; } } From d55b3e2208ed06e9c78865cba04b723d89bc8882 Mon Sep 17 00:00:00 2001 From: epernod Date: Tue, 11 Oct 2022 14:05:48 +0200 Subject: [PATCH 11/22] [WRShape] Add epsilon on get youngModulus and interpolation --- src/BeamAdapter/component/engine/WireRestShape.inl | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/BeamAdapter/component/engine/WireRestShape.inl b/src/BeamAdapter/component/engine/WireRestShape.inl index 556a58def..628e9bcfb 100644 --- a/src/BeamAdapter/component/engine/WireRestShape.inl +++ b/src/BeamAdapter/component/engine/WireRestShape.inl @@ -38,7 +38,7 @@ #include #include -#define EPSILON 0.0000000001 +#define EPSILON 0.0001 #define VERIF 1 namespace sofa::component::engine @@ -402,11 +402,13 @@ void WireRestShape::getRestTransformOnX(Transform &global_H_local, co template void WireRestShape::getYoungModulusAtX(const Real& x_curv, Real& youngModulus, Real& cPoisson) const { + const Real x_used = x_curv - Real(EPSILON); const type::vector& keyPts = d_keyPoints.getValue(); + // Depending on the position of the beam, determine the corresponding section material and returning its Young modulus for (auto i = 1; i < keyPts.size(); ++i) { - if (x_curv <= keyPts[i]) + if (x_used <= keyPts[i]) { return l_sectionMaterials.get(i - 1)->getYoungModulusAtX(youngModulus, cPoisson); } @@ -419,11 +421,13 @@ void WireRestShape::getYoungModulusAtX(const Real& x_curv, Real& youn template void WireRestShape::getInterpolationParam(const Real& x_curv, Real &_rho, Real &_A, Real &_Iy , Real &_Iz, Real &_Asy, Real &_Asz, Real &_J) const { + const Real x_used = x_curv - Real(EPSILON); const type::vector& keyPts = d_keyPoints.getValue(); + // Check in which section x_used belongs to and get access to this section material for (auto i = 1; i < keyPts.size(); ++i) { - if (x_curv <= keyPts[i]) + if (x_used <= keyPts[i]) { return l_sectionMaterials.get(i - 1)->getInterpolationParam(_rho, _A, _Iy, _Iz, _Asy, _Asz, _J); } From 895fa0d0e5dc4a5a79d00b3b95ed711075d3c1b1 Mon Sep 17 00:00:00 2001 From: epernod Date: Tue, 27 Jun 2023 22:01:12 +0200 Subject: [PATCH 12/22] [WireSection] Update WireSectionMaterial to be templated --- src/BeamAdapter/component/engine/WireRestShape.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BeamAdapter/component/engine/WireRestShape.h b/src/BeamAdapter/component/engine/WireRestShape.h index e19b7407b..ba3c86e0b 100644 --- a/src/BeamAdapter/component/engine/WireRestShape.h +++ b/src/BeamAdapter/component/engine/WireRestShape.h @@ -156,7 +156,7 @@ class WireRestShape : public core::objectmodel::BaseObject Data d_drawRestShape; /// Vector or links to the Wire section material. The order of the linked material will define the WireShape structure. - MultiLink, WireSectionMaterial, BaseLink::FLAG_STOREPATH | BaseLink::FLAG_STRONGLINK> l_sectionMaterials; + MultiLink, WireSectionMaterial, BaseLink::FLAG_STOREPATH | BaseLink::FLAG_STRONGLINK> l_sectionMaterials; private: /// Data required for the File loading From 43a1e4fc8cba0f74ecdff6b2fb8b83f72d86a01c Mon Sep 17 00:00:00 2001 From: epernod Date: Thu, 19 Oct 2023 17:35:48 +0200 Subject: [PATCH 13/22] backup work on moving loader to material --- .../component/engine/WireRestShape.cpp | 2 +- .../component/engine/WireRestShape.h | 9 +- .../component/engine/WireRestShape.inl | 108 +++++++++--------- 3 files changed, 57 insertions(+), 62 deletions(-) diff --git a/src/BeamAdapter/component/engine/WireRestShape.cpp b/src/BeamAdapter/component/engine/WireRestShape.cpp index dacba394e..34572378a 100644 --- a/src/BeamAdapter/component/engine/WireRestShape.cpp +++ b/src/BeamAdapter/component/engine/WireRestShape.cpp @@ -52,7 +52,7 @@ using namespace sofa::defaulttype; /// //////////////////////////////////////////////////////////////////////////////////////////////////// -static int WireRestShapeClass = core::RegisterObject("Describe the shape functions on multiple segments using curvilinear abscissa") +const int WireRestShapeClass = core::RegisterObject("Describe the shape functions on multiple segments using curvilinear abscissa") .add< WireRestShape >(true) ; diff --git a/src/BeamAdapter/component/engine/WireRestShape.h b/src/BeamAdapter/component/engine/WireRestShape.h index ba3c86e0b..ab22f391c 100644 --- a/src/BeamAdapter/component/engine/WireRestShape.h +++ b/src/BeamAdapter/component/engine/WireRestShape.h @@ -115,7 +115,7 @@ class WireRestShape : public core::objectmodel::BaseObject void initFromLoader(); bool checkTopology(); - [[nodiscard]] bool fillTopology(); + //[[nodiscard]] bool fillTopology(); Real getLength() ; void getCollisionSampling(Real &dx, const Real &x_curv) ; void getNumberOfCollisionSegment(Real &dx, unsigned int &numLines) ; @@ -163,17 +163,12 @@ class WireRestShape : public core::objectmodel::BaseObject type::vector m_localRestPositions; type::vector m_localRestTransforms; type::vector m_curvAbs ; - double m_absOfGeometry {0}; + double m_absOfGeometry {0}; /// Link to be set to the topology container in the component graph. SingleLink, TopologyContainer, BaseLink::FLAG_STOREPATH | BaseLink::FLAG_STRONGLINK> l_topology; /// Pointer to the topology container, should be set using @sa l_topology, otherwise will search for one in current Node. TopologyContainer* _topology{ nullptr }; - - /// Link to be set to the topology container in the component graph. - SingleLink, MeshLoader, BaseLink::FLAG_STOREPATH | BaseLink::FLAG_STRONGLINK> l_loader; - /// Pointer to the MeshLoader, should be set using @sa l_loader, otherwise will search for one in current Node. - MeshLoader* loader{ nullptr }; }; diff --git a/src/BeamAdapter/component/engine/WireRestShape.inl b/src/BeamAdapter/component/engine/WireRestShape.inl index 628e9bcfb..471d0ed4f 100644 --- a/src/BeamAdapter/component/engine/WireRestShape.inl +++ b/src/BeamAdapter/component/engine/WireRestShape.inl @@ -185,15 +185,15 @@ void WireRestShape::init() return; } - else - { - if (!fillTopology()) - { - msg_error() << "Error while trying to fill the associated topology, setting the state to Invalid"; - this->d_componentState.setValue(sofa::core::objectmodel::ComponentState::Invalid); - return; - } - } + //else + //{ + // if (!fillTopology()) + // { + // msg_error() << "Error while trying to fill the associated topology, setting the state to Invalid"; + // this->d_componentState.setValue(sofa::core::objectmodel::ComponentState::Invalid); + // return; + // } + //} initTopology(); @@ -474,45 +474,45 @@ bool WireRestShape::checkTopology() } -template -bool WireRestShape::fillTopology() -{ - if (!_topology) - { - msg_error() << "Topology is null"; - return false; - } - - const auto length = this->d_length.getValue(); - if (length <= Real(0.0)) - { - msg_error() << "Length is 0 (or negative), check if d_length has been given or computed."; - return false; - } - - int nbrEdges = d_numEdges.getValue(); - if (nbrEdges <= 0) - { - msg_warning() << "Number of edges has been set to an invalid value: " << nbrEdges << ". Value should be a positive integer. Setting to default value: 10"; - nbrEdges = 10; - } - - /// fill topology : - _topology->clear(); - _topology->cleanup(); - - Real dx = this->d_length.getValue() / nbrEdges; - - /// add points - for (int i = 0; i < d_numEdges.getValue() + 1; i++) - _topology->addPoint(i * dx, 0, 0); - - /// add segments - for (int i = 0; i < d_numEdges.getValue(); i++) - _topology->addEdge(i, i + 1); - - return true; -} +//template +//bool WireRestShape::fillTopology() +//{ +// if (!_topology) +// { +// msg_error() << "Topology is null"; +// return false; +// } +// +// const auto length = this->d_length.getValue(); +// if (length <= Real(0.0)) +// { +// msg_error() << "Length is 0 (or negative), check if d_length has been given or computed."; +// return false; +// } +// +// int nbrEdges = d_numEdges.getValue(); +// if (nbrEdges <= 0) +// { +// msg_warning() << "Number of edges has been set to an invalid value: " << nbrEdges << ". Value should be a positive integer. Setting to default value: 10"; +// nbrEdges = 10; +// } +// +// /// fill topology : +// _topology->clear(); +// _topology->cleanup(); +// +// Real dx = this->d_length.getValue() / nbrEdges; +// +// /// add points +// for (int i = 0; i < d_numEdges.getValue() + 1; i++) +// _topology->addPoint(i * dx, 0, 0); +// +// /// add segments +// for (int i = 0; i < d_numEdges.getValue(); i++) +// _topology->addEdge(i, i + 1); +// +// return true; +//} @@ -655,12 +655,12 @@ void WireRestShape::initRestConfig() msg_info() <<"Length of the loaded shape = "<< m_absOfGeometry << ", total length with straight length = " << newLength ; - if (!fillTopology()) - { - msg_error() << "Error while trying to fill the associated topology, setting the state to Invalid"; - this->d_componentState.setValue(sofa::core::objectmodel::ComponentState::Invalid); - return; - } + //if (!fillTopology()) + //{ + // msg_error() << "Error while trying to fill the associated topology, setting the state to Invalid"; + // this->d_componentState.setValue(sofa::core::objectmodel::ComponentState::Invalid); + // return; + //} } From 6243a0dddcdad5e941b3d27f4d9fcc74cd00d969 Mon Sep 17 00:00:00 2001 From: epernod Date: Thu, 29 Jun 2023 12:08:15 +0200 Subject: [PATCH 14/22] Update WireRestShape to only use RodSectionMaterials --- .../component/engine/SteerableCatheter.h | 7 +- .../component/engine/WireRestShape.h | 35 +- .../component/engine/WireRestShape.inl | 502 ++---------------- 3 files changed, 54 insertions(+), 490 deletions(-) diff --git a/src/BeamAdapter/component/engine/SteerableCatheter.h b/src/BeamAdapter/component/engine/SteerableCatheter.h index 17ceebb98..27819a66a 100644 --- a/src/BeamAdapter/component/engine/SteerableCatheter.h +++ b/src/BeamAdapter/component/engine/SteerableCatheter.h @@ -104,9 +104,10 @@ class SteerableCatheter : public WireRestShape /// Bring inherited attributes and function in the current lookup context. /// otherwise any access to the base::attribute would require /// the "this->" approach. - using Inherit1::d_spireDiameter; - using Inherit1::d_length; - using Inherit1::d_straightLength; + Data d_length; + Data d_straightLength; + Data d_spireDiameter; + Data d_spireHeight; /////////////////////////////////////////////////////////////////////////// }; diff --git a/src/BeamAdapter/component/engine/WireRestShape.h b/src/BeamAdapter/component/engine/WireRestShape.h index ab22f391c..f646133ac 100644 --- a/src/BeamAdapter/component/engine/WireRestShape.h +++ b/src/BeamAdapter/component/engine/WireRestShape.h @@ -34,7 +34,7 @@ #include #include -#include +#include #include #include @@ -72,7 +72,7 @@ class WireRestShape : public core::objectmodel::BaseObject using Quat = sofa::type::Quat; using BeamSection = sofa::beamadapter::BeamSection; - + /** * @brief Default Constructor. */ @@ -84,11 +84,8 @@ class WireRestShape : public core::objectmodel::BaseObject virtual ~WireRestShape() = default; /////////////////////////// Inherited from BaseObject ////////////////////////////////////////// - void parse(core::objectmodel::BaseObjectDescription* arg) override; void init() override ; - void draw(const core::visual::VisualParams * vparams) override ; - /////////////////////////// Methods of WireRestShape ////////////////////////////////////////// @@ -109,19 +106,14 @@ class WireRestShape : public core::objectmodel::BaseObject /// Functions enabling to load and use a geometry given from OBJ external file - void initRestConfig(); - void getRestPosNonProcedural(Real& abs, Coord &p); void computeOrientation(const Vec3& AB, const Quat& Q, Quat &result); - void initFromLoader(); - bool checkTopology(); + //[[nodiscard]] bool fillTopology(); Real getLength() ; - void getCollisionSampling(Real &dx, const Real &x_curv) ; + void getCollisionSampling(Real &dx, const Real &x_curv); void getNumberOfCollisionSegment(Real &dx, unsigned int &numLines) ; - void rotateFrameForAlignX(const Quat &input, Vec3 &x, Quat &output); - protected: /// Internal method to init Lengths vector @sa d_keyPoints if not set using @sa d_length and @sa d_straightLength. Returns false if init can't be performed. bool initLengths(); @@ -142,13 +134,6 @@ class WireRestShape : public core::objectmodel::BaseObject } public: - /// Analitical creation of wire shape... - Data d_isAProceduralShape; - Data d_nonProceduralScale; - Data d_length; - Data d_straightLength; - Data d_spireDiameter; - Data d_spireHeight; Data > d_density; Data > d_keyPoints; @@ -156,15 +141,9 @@ class WireRestShape : public core::objectmodel::BaseObject Data d_drawRestShape; /// Vector or links to the Wire section material. The order of the linked material will define the WireShape structure. - MultiLink, WireSectionMaterial, BaseLink::FLAG_STOREPATH | BaseLink::FLAG_STRONGLINK> l_sectionMaterials; - -private: - /// Data required for the File loading - type::vector m_localRestPositions; - type::vector m_localRestTransforms; - type::vector m_curvAbs ; - double m_absOfGeometry {0}; - + MultiLink, BaseRodSectionMaterial, BaseLink::FLAG_STOREPATH | BaseLink::FLAG_STRONGLINK> l_sectionMaterials; + +private: /// Link to be set to the topology container in the component graph. SingleLink, TopologyContainer, BaseLink::FLAG_STOREPATH | BaseLink::FLAG_STRONGLINK> l_topology; /// Pointer to the topology container, should be set using @sa l_topology, otherwise will search for one in current Node. diff --git a/src/BeamAdapter/component/engine/WireRestShape.inl b/src/BeamAdapter/component/engine/WireRestShape.inl index 471d0ed4f..e66f78fc2 100644 --- a/src/BeamAdapter/component/engine/WireRestShape.inl +++ b/src/BeamAdapter/component/engine/WireRestShape.inl @@ -55,72 +55,16 @@ using sofa::core::objectmodel::BaseContext ; * @brief Default Constructor. */ template -WireRestShape::WireRestShape() : - //TODO(dmarchal 2017-05-17) not sure that procedural & nonProceduralScale are very understandable name...are they exclusives ? - //if so have look in my comment in the init section. - d_isAProceduralShape( initData(&d_isAProceduralShape,(bool)true,"isAProceduralShape","is the guidewire shape mathemetically defined ?") ) - , d_nonProceduralScale( initData ( &d_nonProceduralScale, (Real)1.0, "nonProceduralScale", "scale of the model defined by file" ) ) - , d_length(initData(&d_length, (Real)1.0, "length", "total length of the wire instrument")) - , d_straightLength(initData(&d_straightLength, (Real)0.0, "straightLength", "length of the initial straight shape")) - , d_spireDiameter(initData(&d_spireDiameter, (Real)0.1, "spireDiameter", "diameter of the spire")) - , d_spireHeight(initData(&d_spireHeight, (Real)0.01, "spireHeight", "height between each spire")) - , d_density(initData(&d_density, "densityOfBeams", "density of beams between key points")) - , d_keyPoints(initData(&d_keyPoints,"keyPoints","key points of the shape (curv absc)")) - , d_drawRestShape(initData(&d_drawRestShape, (bool)false, "draw", "draw rest shape")) - , l_sectionMaterials(initLink("wireMaterials", "link to Wire Section Materials (to be ordered according to the instrument, from handle to tip)")) - , l_topology(initLink("topology", "link to the topology container")) - , l_loader(initLink("loader", "link to the MeshLoader")) +WireRestShape::WireRestShape() + : d_density(initData(&d_density, "densityOfBeams", "density of beams between key points")) + , d_keyPoints(initData(&d_keyPoints,"keyPoints","key points of the shape (curv absc)")) + , d_drawRestShape(initData(&d_drawRestShape, (bool)false, "draw", "draw rest shape")) + , l_sectionMaterials(initLink("wireMaterials", "link to Wire Section Materials (to be ordered according to the instrument, from handle to tip)")) + , l_topology(initLink("topology", "link to the topology container")) { - d_spireDiameter.setGroup("Procedural"); - d_spireHeight.setGroup("Procedural"); -} - -template -void WireRestShape::rotateFrameForAlignX(const Quat &input, Vec3 &x, Quat &output) -{ - x.normalize(); - Vec3 x0=input.inverseRotate(x); - - Real cTheta=x0[0]; - Real theta; - if (cTheta>(1-EPSILON)) - { - output = input; - } - else - { - theta=acos(cTheta); - // axis of rotation - Vec3 dw(0,-x0[2],x0[1]); - dw.normalize(); - // computation of the rotation - Quat inputRoutput; - inputRoutput.axisToQuat(dw, theta); - - output=input*inputRoutput; - } } -template -void WireRestShape::parse(core::objectmodel::BaseObjectDescription* args) -{ - const char* arg = args->getAttribute("procedural") ; - if(arg) - { - msg_warning() << "The attribute 'procedural' has been renamed into 'isAProceduralShape'. " << msgendl - << "To remove this warning you need to update your scene and replace 'procedural' with 'isAProceduralShape'" ; - - /// As arg is owned by the "procedural" attribute it cannot be removed before - /// being copied in the "isAProceduralShape". So please keep the ordering of the - /// two following functions. - args->setAttribute("isAProceduralShape", arg) ; - args->removeAttribute("procedural") ; - - } - - Inherit1::parse(args) ; -} template void WireRestShape::init() @@ -148,28 +92,9 @@ void WireRestShape::init() return; } - if (!d_isAProceduralShape.getValue()) - { - // Get meshLoader, check first if loader has been set using link. Otherwise will search in current context. - loader = l_loader.get(); - - if (!loader) - this->getContext()->get(loader); - - if (!loader) { - msg_error() << "Cannot find a mesh loader. Please insert a MeshObjLoader in the same node or use l_loader to specify the path in the scene graph."; - this->d_componentState.setValue(sofa::core::objectmodel::ComponentState::Invalid); - return; - } - else - { - msg_info() << "Found a mesh with " << loader->d_edges.getValue().size() << " edges"; - initFromLoader(); - } - } if (l_sectionMaterials.empty()) { - msg_error() << "No WireSectionMaterial set. At least one material should be set and link using wireMaterials."; + msg_error() << "No BaseRodSectionMaterial set. At least one material should be set and link using wireMaterials."; this->d_componentState.setValue(sofa::core::objectmodel::ComponentState::Invalid); return; } @@ -185,15 +110,6 @@ void WireRestShape::init() return; } - //else - //{ - // if (!fillTopology()) - // { - // msg_error() << "Error while trying to fill the associated topology, setting the state to Invalid"; - // this->d_componentState.setValue(sofa::core::objectmodel::ComponentState::Invalid); - // return; - // } - //} initTopology(); @@ -209,34 +125,16 @@ bool WireRestShape::initLengths() auto keyPointList = sofa::helper::getWriteOnlyAccessor(d_keyPoints); auto densityList = sofa::helper::getWriteOnlyAccessor(d_density); - // In case use used length and straightLenght instead of keyPointList, create keyPointList - if (keyPointList.empty()) - { - keyPointList.push_back(0.0); - if (d_straightLength.getValue() >= 0.001 * this->d_length.getValue() && d_straightLength.getValue() <= 0.999 * d_length.getValue()) - keyPointList.push_back(d_straightLength.getValue()); - keyPointList.push_back(d_length.getValue()); - } + keyPointList.resize(l_sectionMaterials.size() + 1); + keyPointList[0] = Real(0.0); - // checking sizes between keypointList and number of input material - if (l_sectionMaterials.size() != keyPointList.size() - 1) - { - msg_error() << "Wrong number of inputs. Component can't be init. Number of input materials: " << l_sectionMaterials.size() << ", should be equal to keyPointList.size()-1. keyPointList.size() is equal to: " << keyPointList.size(); - return false; - } - - if (densityList.size() != keyPointList.size() - 1) + densityList.resize(l_sectionMaterials.size()); + + for (unsigned int i = 0; i < l_sectionMaterials.size(); ++i) { - msg_warning() << "Wrong number of densityOfBeams. Given: " << densityList.size() << ", should be equal to keyPointList.size()-1: '" << keyPointList.size() - << "'. densityOfBeams will be recomputed using Wire material number of collision edges."; - densityList.clear(); - - for (unsigned int i = 0; i < keyPointList.size() - 1; ++i) - { - auto mat = l_sectionMaterials.get(i); - int nbrCollEdges = mat->getNbCollisionEdges(); - densityList.push_back(nbrCollEdges); - } + auto rodSection = l_sectionMaterials.get(i); + keyPointList[i+1] = keyPointList[i] + rodSection->getLength(); + densityList[i] = rodSection->getNbCollisionEdges(); } return true; @@ -294,16 +192,20 @@ void WireRestShape::getSamplingParameters(type::vector& xP_noti nbP_density = d_density.getValue(); } + template void WireRestShape::getCollisionSampling(Real &dx, const Real &x_curv) { unsigned int numLines; Real x_used = x_curv - EPSILON; - if(x_used>d_length.getValue()) - x_used=d_length.getValue(); - if(x_used<0.0) - x_used=0.0; + const Real totalLength = this->getLength(); + if (x_used > totalLength) { + x_used = totalLength; + } + else if (x_used < 0.0) { + x_used = 0.0; + } const type::vector& keyPts = d_keyPoints.getValue(); @@ -314,7 +216,7 @@ void WireRestShape::getCollisionSampling(Real &dx, const Real &x_curv << " != size of keyPoints-1 " << keyPts.size()-1 << ". Returning default values."; numLines = 20; - dx = d_length.getValue() / numLines; + dx = totalLength / numLines; return; } @@ -333,7 +235,7 @@ void WireRestShape::getCollisionSampling(Real &dx, const Real &x_curv // If x_used is out of bounds. Warn user and returns default value. numLines = 20; - dx = d_length.getValue() / numLines; + dx = totalLength / numLines; msg_error() << " problem is getCollisionSampling : x_curv " << x_used << " is not between keyPoints" << d_keyPoints.getValue(); } @@ -343,59 +245,24 @@ void WireRestShape::getRestTransformOnX(Transform &global_H_local, co { Real x_used = x - EPSILON; - if(x_used>d_length.getValue()) - x_used=d_length.getValue(); - - if(x_used<0.0) - x_used=0.0; - - if( x_used < d_straightLength.getValue()) - { - global_H_local.set(Vec3(x_used, 0.0, 0.0 ), Quat()); - return; + const Real totalLength = this->getLength(); + if (x_used > totalLength) { + x_used = totalLength; } - - if(d_isAProceduralShape.getValue()) - { - Real projetedLength = d_spireDiameter.getValue()*M_PI; - Real lengthSpire=sqrt(d_spireHeight.getValue()*d_spireHeight.getValue() + projetedLength*projetedLength ); - // angle in the z direction - Real phi= atan(d_spireHeight.getValue()/projetedLength); - - Quat Qphi; - Qphi.axisToQuat(Vec3(0,0,1),phi); - - // spire angle (if theta=2*PI, there is a complete spire between startx and x_used) - Real lengthCurve= x_used-d_straightLength.getValue(); - Real numSpire=lengthCurve/lengthSpire; - Real theta= 2*M_PI*numSpire; - - // computation of the Quat - Quat Qtheta; - Qtheta.axisToQuat(Vec3(0,1,0),theta); - Quat newSpireQuat = Qtheta*Qphi; - - - // computation of the position - Real radius=d_spireDiameter.getValue()/2.0; - Vec3 PosEndCurve(radius*sin(theta), numSpire*d_spireHeight.getValue(), radius*(cos(theta)-1) ); - Vec3 SpirePos=PosEndCurve + Vec3(d_straightLength.getValue(),0,0); - - global_H_local.set(SpirePos,newSpireQuat); + else if (x_used < 0.0) { + x_used = 0.0; } - else + + const type::vector& keyPts = d_keyPoints.getValue(); + for (auto i = 1; i < keyPts.size(); ++i) { - x_used = x_used - d_straightLength.getValue(); - x_used = x_used/(d_length.getValue()-d_straightLength.getValue()) * m_absOfGeometry; - - Coord p; - this->getRestPosNonProcedural(x_used,p); - Vec3 PosEndCurve = p.getCenter(); - Quat ExtremityQuat = p.getOrientation(); - Vec3 ExtremityPos = PosEndCurve + Vec3(d_straightLength.getValue(),0,0); - - global_H_local.set(ExtremityPos,ExtremityQuat); + if (x_used <= keyPts[i]) + { + return l_sectionMaterials.get(i - 1)->getRestTransformOnX(global_H_local, x_used, keyPts[i - 1]); + } } + + msg_warning() << "You should not be still here"; } @@ -437,277 +304,13 @@ void WireRestShape::getInterpolationParam(const Real& x_curv, Real &_ } -template -bool WireRestShape::checkTopology() -{ - if (!loader->d_edges.getValue().size()) - { - msg_error() << "There is no edges in the topology loaded by " << loader->getName() ; - return false; - } - - if (loader->d_triangles.getValue().size()) - { - msg_error() << "There are triangles in the topology loaded by " << loader->getName() ; - return false; - } - - if (loader->d_quads.getValue().size()) - { - msg_error() << "There are quads in the topology loaded by " << loader->getName() ; - return false; - } - - if (loader->d_polygons.getValue().size()) - { - msg_error() << "There are polygons in the topology loaded by " << loader->getName() ; - return false; - } - - //TODO(dmarchal 2017-05-17) when writing a TODO please specify: - // who will do that - // when it will be done - /// \todo check if the topology is like a wire - - - return true; -} - - -//template -//bool WireRestShape::fillTopology() -//{ -// if (!_topology) -// { -// msg_error() << "Topology is null"; -// return false; -// } -// -// const auto length = this->d_length.getValue(); -// if (length <= Real(0.0)) -// { -// msg_error() << "Length is 0 (or negative), check if d_length has been given or computed."; -// return false; -// } -// -// int nbrEdges = d_numEdges.getValue(); -// if (nbrEdges <= 0) -// { -// msg_warning() << "Number of edges has been set to an invalid value: " << nbrEdges << ". Value should be a positive integer. Setting to default value: 10"; -// nbrEdges = 10; -// } -// -// /// fill topology : -// _topology->clear(); -// _topology->cleanup(); -// -// Real dx = this->d_length.getValue() / nbrEdges; -// -// /// add points -// for (int i = 0; i < d_numEdges.getValue() + 1; i++) -// _topology->addPoint(i * dx, 0, 0); -// -// /// add segments -// for (int i = 0; i < d_numEdges.getValue(); i++) -// _topology->addEdge(i, i + 1); -// -// return true; -//} - - - -template -void WireRestShape::initFromLoader() -{ - if (!checkTopology()) - { - this->d_componentState.setValue(sofa::core::objectmodel::ComponentState::Invalid); - return; - } - - // this is... dirty but d_straightLength itself is not relevant for loader-created wire - // but the code uses it actively and expects it to not be zero. - if (d_straightLength.getValue() <= 0.0) - { - msg_warning() << "straightLength cannot be 0 (or negative...). Setting a minimum value."; - d_straightLength.setValue(0.0001); - } - - type::vector vertices; - sofa::core::topology::BaseMeshTopology::SeqEdges edges; - - //get the topology position - auto topoVertices = sofa::helper::getReadAccessor(loader->d_positions); - - //copy the topology edges in a local vector - auto topoEdges = sofa::helper::getReadAccessor(loader->d_edges); - edges = topoEdges.ref(); - - /** renumber the vertices **/ - type::vector verticesConnexion; //gives the number of edges connected to a vertex - for(unsigned int i =0; i < topoVertices.size(); i++) - verticesConnexion.push_back(2); - - for(const auto& ed : edges) - { - verticesConnexion[ed[0]]--; - verticesConnexion[ed[1]]--; - } - - msg_info() << "Successfully compute the vertex connexion" ; - - // check for the first corner of the edge - unsigned int firstIndex = 0; - bool found = false; - while((firstIndex < verticesConnexion.size()) && !found) - { - if(verticesConnexion[firstIndex] == 1) - found = true; - else - firstIndex++; - } - - if(firstIndex == verticesConnexion.size()) - { - msg_error() << "The first vertex of the beam structure is not found, probably because of a closed structure" ; - this->d_componentState.setValue(sofa::core::objectmodel::ComponentState::Invalid); - return; - } - - vertices.push_back(topoVertices[firstIndex]); - - while(edges.size() > 0) - { - auto it = edges.begin(); - auto end = edges.end(); - - bool notFound = true; - while (notFound && (it != end)) - { - const auto& ed = (*it); - auto toDel = it; - it++; - if(ed[0] == firstIndex) - { - vertices.push_back(topoVertices[ed[1]]); - firstIndex = ed[1]; - edges.erase(toDel); - notFound = false; - - } - else if(ed[1] == firstIndex) - { - vertices.push_back(topoVertices[ed[0]]); - firstIndex = ed[0]; - edges.erase(toDel); - notFound = false; - } - } - } - - msg_info() << "Successfully computed the topology" ; - - m_localRestPositions = vertices; - - for(unsigned int i = 0; i < m_localRestPositions.size() - 1; i++) - m_localRestPositions[i] *= d_nonProceduralScale.getValue(); - - initRestConfig(); - // TODO epernod 2022-08-05: Init from loader seems quite buggy, need to check if this is still needed and working - this->d_componentState.setValue(sofa::core::objectmodel::ComponentState::Valid); -} - - -template -void WireRestShape::initRestConfig() -{ - m_curvAbs.clear(); - double tot = 0; - m_curvAbs.push_back(0); - Quat input, output; - input.identity(); - m_localRestTransforms.resize(m_localRestPositions.size()); - m_localRestTransforms[0].setOrigin(Vec3(0,0,0)); - m_localRestTransforms[0].setOrientation(input); - - for(unsigned int i = 0; i < m_localRestPositions.size() - 1; i++) - { - Vec3 vec = m_localRestPositions[i+1] - m_localRestPositions[i]; - double norm = vec.norm(); - tot += norm; - - this->rotateFrameForAlignX(input, vec, output); - - input = output; - - m_localRestTransforms[i+1].setOrientation(output); - - Vec3 localPos = m_localRestPositions[i+1] - m_localRestPositions[0]; - - m_localRestTransforms[i+1].setOrigin(localPos); - - m_curvAbs.push_back(tot); - } - m_absOfGeometry = tot; - - Real newLength = d_straightLength.getValue() + m_absOfGeometry; - d_length.setValue(newLength); - - msg_info() <<"Length of the loaded shape = "<< m_absOfGeometry << ", total length with straight length = " << newLength ; - - //if (!fillTopology()) - //{ - // msg_error() << "Error while trying to fill the associated topology, setting the state to Invalid"; - // this->d_componentState.setValue(sofa::core::objectmodel::ComponentState::Invalid); - // return; - //} -} - - -template -void WireRestShape::getRestPosNonProcedural(Real& abs, Coord &p) -{ - /*** find the range which includes the "requested" abs ***/ - double startingAbs = 0; unsigned int index = 0; - - while ((startingAbs < abs) && (index < m_localRestPositions.size())) - { - index++; - startingAbs = m_curvAbs[index]; - } - - /*** OOB ***/ - if(abs > startingAbs) - { - msg_error() << "abs = "< typename WireRestShape::Real WireRestShape::getLength() { - return d_length.getValue(); + return d_keyPoints.getValue().back(); } + template void WireRestShape::getNumberOfCollisionSegment(Real &dx, unsigned int &numLines) { @@ -716,9 +319,10 @@ void WireRestShape::getNumberOfCollisionSegment(Real &dx, unsigned in { numLines += l_sectionMaterials.get(i)->getNbCollisionEdges(); } - dx = d_length.getValue() / numLines; + dx = getLength() / numLines; } + template void WireRestShape::computeOrientation(const Vec3& AB, const Quat& Q, Quat &result) { @@ -751,26 +355,6 @@ void WireRestShape::computeOrientation(const Vec3& AB, const Quat& Q, } -template -void WireRestShape::draw(const core::visual::VisualParams* vparams) -{ - if (!d_drawRestShape.getValue()) - return; - - vparams->drawTool()->saveLastState(); - vparams->drawTool()->setLightingEnabled(false); - - std::vector< sofa::type::Vec3 > points; - points.reserve(m_localRestPositions.size()); - - for (unsigned int i = 0; i < m_localRestPositions.size(); i++) - { - points.emplace_back(m_localRestPositions[i][0], m_localRestPositions[i][1], m_localRestPositions[i][2]); - } - - vparams->drawTool()->drawPoints(points, 10, sofa::type::RGBAColor(1, 0.5, 0.5, 1)); - vparams->drawTool()->restoreLastState(); -} } // namespace _wirerestshape_ using _wirerestshape_::WireRestShape; From 1a38c8998dfb2002e29b17f06250a95fc148bd82 Mon Sep 17 00:00:00 2001 From: epernod Date: Fri, 30 Jun 2023 13:18:04 +0200 Subject: [PATCH 15/22] Clean and doc Rod section classes and WireRestShape --- .../component/engine/WireRestShape.h | 30 +++++++++++-------- .../component/engine/WireRestShape.inl | 15 +++------- 2 files changed, 21 insertions(+), 24 deletions(-) diff --git a/src/BeamAdapter/component/engine/WireRestShape.h b/src/BeamAdapter/component/engine/WireRestShape.h index f646133ac..222215710 100644 --- a/src/BeamAdapter/component/engine/WireRestShape.h +++ b/src/BeamAdapter/component/engine/WireRestShape.h @@ -33,7 +33,6 @@ #include -#include #include #include @@ -56,8 +55,9 @@ using namespace sofa::beamadapter; * \class WireRestShape * \brief Describe the shape functions on multiple segments * - * Describe the full shape of a Wire with a given length and radius. The wire is discretized by a set of beams (given by the keyPoints and the relatives Beam density) - * This component compute the beam discretization and the shape functions on multiple segments using curvilinear abscissa. + * Describe the full shape of a Wire with a given set of @sa BaseRodSectionMaterial. The wire is discretized by a set of beams (given by the keyPoints and the relatives Beam density) + * @sa d_keyPoints and @d_density are computed by method @sa initLengths using the set of rod sections description. + * This component compute the beam discretization and the shape functions on multiple segments using curvilinear abscissa. */ template class WireRestShape : public core::objectmodel::BaseObject @@ -71,8 +71,6 @@ class WireRestShape : public core::objectmodel::BaseObject using Vec3 = sofa::type::Vec<3, Real>; using Quat = sofa::type::Quat; - using BeamSection = sofa::beamadapter::BeamSection; - /** * @brief Default Constructor. */ @@ -87,7 +85,7 @@ class WireRestShape : public core::objectmodel::BaseObject void init() override ; - /////////////////////////// Methods of WireRestShape ////////////////////////////////////////// + /////////////////////////// Methods of WireRestShape ////////////////////////////////////////// /// This function is called by the force field to evaluate the rest position of each beam void getRestTransformOnX(Transform &global_H_local, const Real &x); @@ -108,15 +106,24 @@ class WireRestShape : public core::objectmodel::BaseObject /// Functions enabling to load and use a geometry given from OBJ external file void computeOrientation(const Vec3& AB, const Quat& Q, Quat &result); - - //[[nodiscard]] bool fillTopology(); + Real getLength() ; void getCollisionSampling(Real &dx, const Real &x_curv); void getNumberOfCollisionSegment(Real &dx, unsigned int &numLines) ; + + + /////////////////////////// Deprecated Methods ////////////////////////////////////////// + + /// For coils: a part of the coil instrument can be brokenIn2 (by default the point of release is the end of the straight length) + Real getReleaseCurvAbs() const { + msg_warning() << "Releasing catheter or brokenIn2 mode is not anymore supported. Feature has been removed after release v23.06"; + return 0.0; + } + protected: - /// Internal method to init Lengths vector @sa d_keyPoints if not set using @sa d_length and @sa d_straightLength. Returns false if init can't be performed. - bool initLengths(); + /// Internal method to init Lengths vector @sa d_keyPoints using the length of each materials @sa l_sectionMaterials. + void initLengths(); /// Internal method to init Edge Topology @sa _topology using the list of materials @sa l_sectionMaterials. Returns false if init can't be performed. bool initTopology(); @@ -136,9 +143,6 @@ class WireRestShape : public core::objectmodel::BaseObject public: Data > d_density; Data > d_keyPoints; - - /// broken in 2 case - Data d_drawRestShape; /// Vector or links to the Wire section material. The order of the linked material will define the WireShape structure. MultiLink, BaseRodSectionMaterial, BaseLink::FLAG_STOREPATH | BaseLink::FLAG_STRONGLINK> l_sectionMaterials; diff --git a/src/BeamAdapter/component/engine/WireRestShape.inl b/src/BeamAdapter/component/engine/WireRestShape.inl index e66f78fc2..251e98a89 100644 --- a/src/BeamAdapter/component/engine/WireRestShape.inl +++ b/src/BeamAdapter/component/engine/WireRestShape.inl @@ -58,7 +58,6 @@ template WireRestShape::WireRestShape() : d_density(initData(&d_density, "densityOfBeams", "density of beams between key points")) , d_keyPoints(initData(&d_keyPoints,"keyPoints","key points of the shape (curv absc)")) - , d_drawRestShape(initData(&d_drawRestShape, (bool)false, "draw", "draw rest shape")) , l_sectionMaterials(initLink("wireMaterials", "link to Wire Section Materials (to be ordered according to the instrument, from handle to tip)")) , l_topology(initLink("topology", "link to the topology container")) { @@ -104,11 +103,7 @@ void WireRestShape::init() ////////// keyPoint list and Density Assignement /////// //////////////////////////////////////////////////////// - if (!initLengths()) - { - this->d_componentState.setValue(sofa::core::objectmodel::ComponentState::Invalid); - return; - } + initLengths(); initTopology(); @@ -120,7 +115,7 @@ void WireRestShape::init() template -bool WireRestShape::initLengths() +void WireRestShape::initLengths() { auto keyPointList = sofa::helper::getWriteOnlyAccessor(d_keyPoints); auto densityList = sofa::helper::getWriteOnlyAccessor(d_density); @@ -136,8 +131,6 @@ bool WireRestShape::initLengths() keyPointList[i+1] = keyPointList[i] + rodSection->getLength(); densityList[i] = rodSection->getNbCollisionEdges(); } - - return true; } @@ -236,7 +229,7 @@ void WireRestShape::getCollisionSampling(Real &dx, const Real &x_curv // If x_used is out of bounds. Warn user and returns default value. numLines = 20; dx = totalLength / numLines; - msg_error() << " problem is getCollisionSampling : x_curv " << x_used << " is not between keyPoints" << d_keyPoints.getValue(); + msg_error() << " problem in getCollisionSampling : x_curv " << x_used << " is not between keyPoints" << d_keyPoints.getValue(); } @@ -262,7 +255,7 @@ void WireRestShape::getRestTransformOnX(Transform &global_H_local, co } } - msg_warning() << "You should not be still here"; + msg_error() << " problem in getRestTransformOnX : x_curv " << x_used << " is not between keyPoints" << d_keyPoints.getValue(); } From d9c7cc0743185d1cc4902c5dbf1e13e1c4b6af2f Mon Sep 17 00:00:00 2001 From: epernod Date: Thu, 19 Oct 2023 17:42:49 +0200 Subject: [PATCH 16/22] Fix bad cherry pick --- src/BeamAdapter/component/engine/WireRestShape.h | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/BeamAdapter/component/engine/WireRestShape.h b/src/BeamAdapter/component/engine/WireRestShape.h index 222215710..04aac38b1 100644 --- a/src/BeamAdapter/component/engine/WireRestShape.h +++ b/src/BeamAdapter/component/engine/WireRestShape.h @@ -121,6 +121,10 @@ class WireRestShape : public core::objectmodel::BaseObject return 0.0; } + void releaseWirePart() { + msg_warning() << "Releasing catheter or brokenIn2 mode is not anymore supported. Feature has been removed after release v23.06"; + } + protected: /// Internal method to init Lengths vector @sa d_keyPoints using the length of each materials @sa l_sectionMaterials. void initLengths(); @@ -128,18 +132,6 @@ class WireRestShape : public core::objectmodel::BaseObject bool initTopology(); - /////////////////////////// Deprecated Methods ////////////////////////////////////////// - - /// For coils: a part of the coil instrument can be brokenIn2 (by default the point of release is the end of the straight length) - Real getReleaseCurvAbs() const { - msg_warning() << "Releasing catheter or brokenIn2 mode is not anymore supported. Feature has been removed after release v23.06"; - return 0.0; - } - - void releaseWirePart() { - msg_warning() << "Releasing catheter or brokenIn2 mode is not anymore supported. Feature has been removed after release v23.06"; - } - public: Data > d_density; Data > d_keyPoints; From 3c61d514cc0feb8b3e056c8757e0bd195347a814 Mon Sep 17 00:00:00 2001 From: epernod Date: Thu, 19 Oct 2023 17:07:25 +0200 Subject: [PATCH 17/22] [scenes] Update all scenes with new rod section components --- examples/3instruments.scn | 23 +++++++++++++++------- examples/3instruments_collis.scn | 21 ++++++++++++++------ examples/SingleBeamDeployment.scn | 9 ++++----- examples/SingleBeamDeploymentCollision.scn | 10 +++++----- examples/Tool_from_loader.scn | 11 ++++++----- 5 files changed, 46 insertions(+), 28 deletions(-) diff --git a/examples/3instruments.scn b/examples/3instruments.scn index 2e1843149..8d876cdad 100644 --- a/examples/3instruments.scn +++ b/examples/3instruments.scn @@ -24,25 +24,34 @@ - + + + + + - + + + + + - - + + + + + + diff --git a/examples/3instruments_collis.scn b/examples/3instruments_collis.scn index 4a5511faa..5f0d0ad19 100644 --- a/examples/3instruments_collis.scn +++ b/examples/3instruments_collis.scn @@ -38,24 +38,33 @@ - + + + + + - + + + + + - + + + + + diff --git a/examples/SingleBeamDeployment.scn b/examples/SingleBeamDeployment.scn index ca9739617..f29e61142 100644 --- a/examples/SingleBeamDeployment.scn +++ b/examples/SingleBeamDeployment.scn @@ -15,11 +15,10 @@ - + + + + diff --git a/examples/SingleBeamDeploymentCollision.scn b/examples/SingleBeamDeploymentCollision.scn index b455d0f44..4797b11e3 100644 --- a/examples/SingleBeamDeploymentCollision.scn +++ b/examples/SingleBeamDeploymentCollision.scn @@ -32,11 +32,11 @@ - + + + + + diff --git a/examples/Tool_from_loader.scn b/examples/Tool_from_loader.scn index 544bcba57..e76e8fd76 100644 --- a/examples/Tool_from_loader.scn +++ b/examples/Tool_from_loader.scn @@ -23,11 +23,12 @@ - - + + + + + + From f7d71f3c0410ce6044d8ee3f25866196a2699a0e Mon Sep 17 00:00:00 2001 From: epernod Date: Fri, 20 Oct 2023 10:26:02 +0200 Subject: [PATCH 18/22] Fix more tests --- ...InterventionalRadiologyController_test.cpp | 3 +- .../component/model/WireRestShape_test.cpp | 69 +++++++++++-------- 2 files changed, 41 insertions(+), 31 deletions(-) diff --git a/BeamAdapter_test/component/controller/InterventionalRadiologyController_test.cpp b/BeamAdapter_test/component/controller/InterventionalRadiologyController_test.cpp index 0d78f9027..3fa2898ee 100644 --- a/BeamAdapter_test/component/controller/InterventionalRadiologyController_test.cpp +++ b/BeamAdapter_test/component/controller/InterventionalRadiologyController_test.cpp @@ -122,7 +122,8 @@ void InterventionalRadiologyController_test::testDefaultInit() " " " " " " - " " + " " + " " " " " " " " diff --git a/BeamAdapter_test/component/model/WireRestShape_test.cpp b/BeamAdapter_test/component/model/WireRestShape_test.cpp index 0e81d0a0b..c41d9f054 100644 --- a/BeamAdapter_test/component/model/WireRestShape_test.cpp +++ b/BeamAdapter_test/component/model/WireRestShape_test.cpp @@ -119,13 +119,14 @@ void WireRestShape_test::testDefaultInit() { std::string scene = "" - " " - " " - " " - " " - " " - " " - " "; + " " + " " + " " + " " + " " + " " + " " + " "; loadScene(scene); @@ -141,15 +142,17 @@ void WireRestShape_test::testParameterInit() { std::string scene = "" - " " - " " - " " - " " - " " - " " - " "; - + " " + " " + " " + " " + " " + " " + " " + " " + " "; + loadScene(scene); WireRestShapeRig3::SPtr wireRShape = this->m_root->get< WireRestShapeRig3 >(sofa::core::objectmodel::BaseContext::SearchDown); @@ -157,12 +160,13 @@ void WireRestShape_test::testParameterInit() EXPECT_NE(wire, nullptr); Real fullLength = wire->getLength(); + Real straightLength = 95.0; EXPECT_EQ(fullLength, 100.0); - Real straightLength = 95.0; + int nbrE0 = 50; + int nbrE1 = 10; vector keysPoints, keysPoints_ref = { 0, straightLength, fullLength }; - Real ratio = straightLength / fullLength; - vector nbP_density, nbP_density_ref = { int(floor(5.0 * ratio)), int(floor(20.0 * (1 - ratio))) }; + vector nbP_density, nbP_density_ref = { nbrE0, nbrE1 }; wire->getSamplingParameters(keysPoints, nbP_density); EXPECT_EQ(keysPoints.size(), 3); @@ -175,9 +179,9 @@ void WireRestShape_test::testParameterInit() wire->getCollisionSampling(dx1, 0.0); wire->getCollisionSampling(dx2, fullLength); wire->getCollisionSampling(dx3, 90.0); - EXPECT_EQ(dx1, straightLength / nbEdgesCol_ref); - EXPECT_EQ(dx2, (fullLength - straightLength) / nbEdgesCol_ref); - EXPECT_EQ(dx3, straightLength / nbEdgesCol_ref); + EXPECT_EQ(dx1, straightLength / nbrE0); + EXPECT_EQ(dx2, (fullLength - straightLength) / nbrE1); + EXPECT_EQ(dx3, straightLength / nbrE0); } @@ -187,8 +191,10 @@ void WireRestShape_test::testTopologyInit() "" " " " " - " " + " " + " " + " " " " " " " " @@ -205,11 +211,12 @@ void WireRestShape_test::testTopologyInit() EXPECT_NE(topo, nullptr); // checking topo created by WireRestShape - int numbEdgesVisu = 30; - EXPECT_EQ(topo->getNbPoints(), numbEdgesVisu + 1); - EXPECT_EQ(topo->getNbEdges(), numbEdgesVisu); + int numbEdgesVisu0 = 20; + int numbEdgesVisu1 = 10; + EXPECT_EQ(topo->getNbPoints(), numbEdgesVisu0 + numbEdgesVisu1 + 1); + EXPECT_EQ(topo->getNbEdges(), numbEdgesVisu0 + numbEdgesVisu1); - Real dx = 100.0 / Real(numbEdgesVisu); + Real dx = 95.0 / Real(numbEdgesVisu0); EXPECT_EQ(topo->getPX(0), 0.0); EXPECT_EQ(topo->getPX(1), dx); @@ -227,8 +234,10 @@ void WireRestShape_test::testTransformMethods() "" " " " " - " " + " " + " " + " " " " " " " " From 013700309a0b8894cb71236f8267a1aafe489462 Mon Sep 17 00:00:00 2001 From: epernod Date: Fri, 20 Oct 2023 18:04:42 +0200 Subject: [PATCH 19/22] restore epsilon value --- src/BeamAdapter/component/engine/WireRestShape.inl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BeamAdapter/component/engine/WireRestShape.inl b/src/BeamAdapter/component/engine/WireRestShape.inl index 251e98a89..b97aae504 100644 --- a/src/BeamAdapter/component/engine/WireRestShape.inl +++ b/src/BeamAdapter/component/engine/WireRestShape.inl @@ -38,7 +38,7 @@ #include #include -#define EPSILON 0.0001 +#define EPSILON 0.0000000001 #define VERIF 1 namespace sofa::component::engine From 0c6ba1e049afd2c353aa2e32cfa3b6c3d1f9abfa Mon Sep 17 00:00:00 2001 From: epernod Date: Tue, 28 Nov 2023 11:05:32 +0100 Subject: [PATCH 20/22] Update scenes behavior and add recorded motion to fully test the 3 instruments deployments --- examples/3instruments.html | 15 +- examples/3instruments.scn | 21 ++- examples/3instruments_collis.html | 46 ++++- examples/3instruments_collis.scn | 185 ++++++++++----------- examples/SingleBeamDeploymentCollision.scn | 26 +-- 5 files changed, 164 insertions(+), 129 deletions(-) diff --git a/examples/3instruments.html b/examples/3instruments.html index 1361ea6ba..f11bc3233 100644 --- a/examples/3instruments.html +++ b/examples/3instruments.html @@ -9,7 +9,10 @@
Instruments control

- You can use the keyboard to control the instrument. + Recorded motion has been recorded to demonstrate the 3 intruments behavior. +
+
To manually test it, set the component BeamAdapterActionController writeMode to true. +
Then you can use the keyboard to control the instrument:
  • To change instrument
      @@ -18,16 +21,16 @@
    • ctrl + 2
  • -
  • To progress instrument +
  • To move the instrument forward/backward
      -
    • ctrl + + -
    • ctrl + - +
    • ctrl + UP key +
    • ctrl + DOWN key
  • To turn instrument
      -
    • ctrl + A -
    • ctrl + E +
    • ctrl + LEFT key +
    • ctrl + RIGHT key
diff --git a/examples/3instruments.scn b/examples/3instruments.scn index 8d876cdad..e9e438d18 100644 --- a/examples/3instruments.scn +++ b/examples/3instruments.scn @@ -15,12 +15,10 @@ - - @@ -62,10 +60,10 @@ @@ -78,11 +76,16 @@ - + + + - + diff --git a/examples/3instruments_collis.html b/examples/3instruments_collis.html index 51a7b1b11..f11bc3233 100644 --- a/examples/3instruments_collis.html +++ b/examples/3instruments_collis.html @@ -1,12 +1,42 @@ + + + -

Scene controls

-After launching the simulation ("Animate" button), clic on the viewport to give it the focus.
-Ctrl+[UP/DOWN key] move the tool forward/backward.
-Ctrl+[LEFT/RIGHT key] rotate the tool.
-Ctrl+[0/1/2] switch tool.
- +
+

Beam Adapter 3 instruments interventional radiology

+
+
Instruments control
+
+
+ Recorded motion has been recorded to demonstrate the 3 intruments behavior. +
+
To manually test it, set the component BeamAdapterActionController writeMode to true. +
Then you can use the keyboard to control the instrument: +
    +
  • To change instrument +
      +
    • ctrl + 0 +
    • ctrl + 1 +
    • ctrl + 2 +
    +
  • +
  • To move the instrument forward/backward +
      +
    • ctrl + UP key +
    • ctrl + DOWN key +
    +
  • +
  • To turn instrument +
      +
    • ctrl + LEFT key +
    • ctrl + RIGHT key +
    +
  • +
+
+ +
+
- - diff --git a/examples/3instruments_collis.scn b/examples/3instruments_collis.scn index 5f0d0ad19..0b37c8b56 100644 --- a/examples/3instruments_collis.scn +++ b/examples/3instruments_collis.scn @@ -1,6 +1,6 @@ - - - + + + @@ -23,147 +23,146 @@ - + - + - + - - + + - + - - - - + + + + - + - - - - + + + + - + - - - - + + + + - - - - + + + - + - - + + - - + + - - + + - + + - + - - + + - - - - - - - + + + + + + + - - - - - - + + + + + + - + - - - + + + - - - - - - - - - - + + + + + + + + + + - - - - - - - - - - + + + + + + + + + + - - - - - - - - - - + + + + + + + + - - - diff --git a/examples/SingleBeamDeploymentCollision.scn b/examples/SingleBeamDeploymentCollision.scn index 4797b11e3..ed3a1b887 100644 --- a/examples/SingleBeamDeploymentCollision.scn +++ b/examples/SingleBeamDeploymentCollision.scn @@ -23,12 +23,12 @@ - - - + + + - - + + @@ -57,24 +57,24 @@ startingPos="0 0 0 0 0 0 1" xtip="0 0 0" printLog="1" rotationInstrument="0 0 0" step="5" speed="5" listening="1" controlledInstrument="0"/> - + - - - - + + + + - - - + + + From 71eaf13520ae1b127356dc1b998bb272630ab021 Mon Sep 17 00:00:00 2001 From: epernod Date: Tue, 28 Nov 2023 11:37:17 +0100 Subject: [PATCH 21/22] Update scenes regression references --- ..._collis.scn.reference_0_DOFs_mstate.txt.gz | Bin 536 -> 1954 bytes ...cn.reference_1_CollisionDOFs_mstate.txt.gz | Bin 431 -> 875 bytes ...collis.scn.reference_2_Quads_mstate.txt.gz | Bin 10506 -> 29812 bytes ...collis.scn.reference_3_Quads_mstate.txt.gz | Bin 457 -> 25107 bytes ...collis.scn.reference_4_Quads_mstate.txt.gz | Bin 745 -> 6238 bytes ...n.reference_0_DOFs Container_mstate.txt.gz | Bin 514 -> 842 bytes ...cn.reference_1_CollisionDOFs_mstate.txt.gz | Bin 435 -> 458 bytes 7 files changed, 0 insertions(+), 0 deletions(-) diff --git a/regression/references/3instruments_collis.scn.reference_0_DOFs_mstate.txt.gz b/regression/references/3instruments_collis.scn.reference_0_DOFs_mstate.txt.gz index 5ae65366a867472af1406ce6f481118a70e1f7f4..25287bcb631af56566bd624a7d56c4aee8dd38ad 100644 GIT binary patch literal 1954 zcmV;T2VM9diwFP!000003+&jj34l-xK*2s!WC0~sss0us?mvkNf>w5R$19R>8)O|> zk-4AaZmPMfm+I?!tq?*8A%qY@2qAAg&k8IAhcI1qYop{l-`uld>*c3 zytY}3LH<}&0#Pj6eYWcsSSaw(hOS!Mi7zs{#ri~6h4KyGRG|bMNu%1Q(^7BONx*Wa zJC*f4={`Y+rd1|DOW@aTGu>qbvof5!4faAVo^Rzb9BS>8fxISg?7rM5@(5zf zHY79PiPJBv-#XccG&nmpWlk(sl;?LlzAI2}sT8_=!>w451=~9_mZ5}*4nAyR$&%b? zWFyuLx+2Lh3sex|D5^-LhQoEp$$=xcCUX-7+rCTegl<(UNTti<#uAPXg^r^tas@f@ z^Z`3>-CECffLG>t=V_M>tkX*wvSOMG5q8N)sN6ptCp!EHpO=5^HiP&}10{n(Q5VFI zPuCGWvGm-wa&+LzsWvQ11-)IWieS8(T($(~9V6C8@=yB0=y)nBIx|WH-j)Pp1MiX` z4n1u-wjUjku4)4;5%P%Ou#pf5AVg>&k04 zCZMR)ZPVr8X0H5Cq-cBZE3BU*btcR~LtK(wvU&iEb(IAX;0YH<#ro9kisz1RU5+0OxEi#~G>#{s9_& z4A~%XyKQSYRtj!myWNFeoH=SxNcSxydzNg$HYALct~TgSC`2r+W2Q?u`w1Va`U12u z=*I8Ar+9zw@BO{M_xJz#Vbt6P?)LkK*2NfDi}2E#Aq^{v;n9l3nSt^4NQSw2@21L_ zPAOStp(jhW#d0P*r(9<=#PEW_z3C&U8_4k4U+A7|@E+V$e5P4^!!b+OYaxqlB1Wmv zGz%Pn4n@W@#+W^V!3&&cM8^D1!{~&-7`b`q+T5=|JBWs{fq7ukY<9iG)?sADN1`W| zg|R(X8CUooBuf8SN98TbO?&{~=0i*{;#(QjV|meK8F)5P1N%))JDeA+3|w(ubJ;Re zaIT@@U}q>Q|)XXVllV4jhJv4mHI{wP&ovV-62yZj0fThczw6L~J7zZI2+Gbxg zk;&pImdcn-gYO9)>?6~wr<^;osxh}Cz@FBDXwRJip&8{UzZJ%@TS$e=|-#K6uL1`Vr#+_!+ba-#0^>h=MMk?0RR630GV>Yg@r@_01cnG)&Kwi literal 536 zcmV+z0_Xi7iwFP!000003ktDSFyK;9hyYShsB55SZeVU`U=HFTF-IjwB}XMkB}XMk zB?q}AutEz%RA{4c6b=K(1poj5|Nrb($!)|i5c{VJH(+7zXV-*hJ;?rb45Mye=NCnt@vW0%B>X(k}m0 z0m4UO-Vsr{Nj50VJ+4(M5o140q;^(@5xuXg6~|aQ zs(QnFi;vVQUT7oR=qh<$S)c2VuUkfNCBg&_xD)Qe!;C*Geu!DqU?`XdotP-r(?^B^oMz;)V%ky7rF6%dlceGU1(Y(_F@| zUg(J;No0}jx;RB{!N{Y`of5_(6Ykx5noWE{kFoz=h%Li*z`0e~l+ohV2$T_!YX^+% z+5nJL@R1%!(v=ldihx>&MJW^P9D67r5q2j5v9*WpoaJWAg=Xv%b6QO1^Vge8Uf}L; atW@#~D-v&C00030{{sLSg$V1*6#xKU#Ppy5 diff --git a/regression/references/3instruments_collis.scn.reference_1_CollisionDOFs_mstate.txt.gz b/regression/references/3instruments_collis.scn.reference_1_CollisionDOFs_mstate.txt.gz index ad60ce24686f1a0e70c0929601a36636685a6998..84512cf853b7e25fd8f94ab1654f9b30c94f2719 100644 GIT binary patch literal 875 zcmV-x1C;z9iwFP!000003ktDSFyK;9h_F@AH5f&s2tNX*lrUg|8HMzNTmS$70RR8& zR>_rIHw?Tx3MMdo*q4K|PzugJ1s90cNg=4;%z+<;9c&hU{d>A^`{!56_`gM6^4|~D zp<7Za`~|+i-vrR@j-s3sc?4^nxS2@{rmDxBT-EvrU+MSV5#}{+IlSd)TuFX$NcaI( z!FC)bQ4e76H6dj~9GJudw3`%JMRu2+8mH`oq?aDMY?h}TpW|AtC~Kd-(bRgFaGDTu z6S3=XOy+!8ASvXlNGf4!bcI+ByI*yCv9%B)__m21Sl_DYkcf%INr!8*gY&iRSUCCx zdBfres<5eBuaZ@tNMx&dln_Go{5b5;_~Ui6S&y2bU;xuGaz3uN)nQR~39$i8`LA_U zZN~%}mE+1i&_XM84s)?kWGm;#ZYYf=4z9;ZZ(`1nyQSpFHBg>Ypl8p(3e`bQOvsJu zj^mL-(8o}Y^`wB&8xMf;5*C8w6CoH=Xdi$(&=AAW5<_rCPH6*BW*uz|MMv%!TTP?d zrKj9W0S}I8smlN*miexd_k=(z>sdqxE`TBljD6&ZVj%Zq^`pqi(;v{#17MEolu)&= zynIO)%}p2l4496f?-B1f*Pj5`-1e`^SLLhnRryn8&LK{sd#2!~VJdv!akF{^L3n*y z4I~1h^TJy<3taMMGFK}H6~<7o>f zf{lnX?SWD__EM|J>W9H%PKIh|dDjx8J|O7DUVsFR__GvuexOK`zl@qpmzUWdui}9f zmVP-Bp7mvSmY0qe6+NX=t7+d-#B-`lx{VWY{@nxH!n+fH=#VBjHWt$uT1EySs~AkV z^n*D$Bn+ZK{WJ@QyrzShV-^e9xX(4lut1r}}F7-UI<<&5)#>9)nd-w#p~T9*(k=8gqwFz?zG z3v{@5MFT6tWSOPJZyuNoZMd2I6kllhj<#ENdIuo;~003SW Bo}vH% literal 431 zcmV;g0Z{%QiwFP!000003ktDSFyK;9hyYRwx?nU)4{I72*^F4FrM+)MnXG(exDhdcz?7L?(o+Dx19%<8(B9~Ux|FH$Ad zv52`KNKjO6x2{BZmXjFfG&qvETunDSNvkSHm?`6mj5&r8r)=~QX=+9vg+^ndpirqJ zh5+x6QL>91D>5!9g-`{SaMBno?L#sNr7UXQd2c&Bm5q4_?vbhT} Z_TOiWwEp@F009600{}MVniH@Q008O}$ISo$ diff --git a/regression/references/3instruments_collis.scn.reference_2_Quads_mstate.txt.gz b/regression/references/3instruments_collis.scn.reference_2_Quads_mstate.txt.gz index be948c7c2785e0f1cbe5eb103e44e4242a7f0bfa..acd07db10175a4bbd62269c14d9c1981b1f18b55 100644 GIT binary patch literal 29812 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 D*IoPk literal 10506 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 diff --git a/regression/references/3instruments_collis.scn.reference_3_Quads_mstate.txt.gz b/regression/references/3instruments_collis.scn.reference_3_Quads_mstate.txt.gz index 1c5c8831aea59e4145d6ba85af2f8b1b8a56a5ff..10e5de4051c81edf50b6734fa4b80ec47484f0de 100644 GIT binary patch literal 25107 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 NWLhkpnjpx?007Pv@g)EN diff --git a/regression/references/3instruments_collis.scn.reference_4_Quads_mstate.txt.gz b/regression/references/3instruments_collis.scn.reference_4_Quads_mstate.txt.gz index e78fdc8ce4118c913e7f3b93c0ffa4d857b8b7cd..e18d1a11075f6bcb3a4050a976f855e843093400 100644 GIT binary patch literal 6238 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 R;C{cuUt}rK75uQ{zW{J2)J6aR literal 745 zcmb2|=3oE=?l%`U<{mZ>aJYCc_zshb#$1)C)@=q&IuHJabMUe>zP%B8%H~g-kmj5p zdhT0-k31J#>bhDoSLfB0?Pqdr&Y!+>TT1n{-P+Sdy21P3o_}t7eAU%$_rERIh(0Ux zejeYH@9O*9rQiL6+TLAL{30Lt99`K1_2*LVZ2+NDnU@s^>!^DVg&5=x{5wwfwzG zgH%k+*CQ60tKhYl+3uw__m{#*Y$EL^g_7@ZJtLz97vxK|HkQgMt=bS5cCy<)bpHx}W1Y ziRs*impE`iG?%YMxI#c9Ma$mIN(UWkBkec>zmwJ-j<~yDkV0qxamwaL29w%$vr^6d z(HQ1z3x(mx27j^1n0<#+)7NUS4+4MTBFa&6CEC^bb z8!6@b)Uf1TO)P|JvB|TaNW5wrA^}Lpfe6*bdRK|+!+&6lcb&0hA(OyxcHS7tpkZeX znZJdNZKQXCz>&6x1t7d`VATSD014{@Qqr-@Kg>HXA)2wGZTvESzJY%J|Mvg%;3=@s zYnj@OUgr6-B;MM42|3gg-N;+WZHi7q;iqcLh;+FMPpE8*Q=?5T(X-wAmM|npkB|`r zvTlEqaeq!dTm4Mx`v@94mRxk53BVAJYi`t6>j)7*7`Hj5$uURc;@)+?oo?bOS_c4w zCG`L}70bSViqw`2b%TC9zC`vq5T0=W1QGfLfMV?CWHhYyVWSI@@9Q?X03C7xAd)u( z7peWi3WeAn7-k0eng9VRj>bae55Uw#c>$VoYkv_?k7K6DLrs7=V~_otA;mq;*IrF^ zOR5>2iEOSeD*`b8IuLhe>Kw!002q=7AcZ$e7@?RP6F}$|0dg%j37By4QefP@M=vT* zc5bXO-FpGV%VFlN9GxZi+MDj=F!I^k-VuK<3RUE_ueXdy^kop=1$68&Rd!s+3kK=3 jFCKZvT;+TJROlA~9}52KF8}}l|NjF3abZAA;}HM==@^2B delta 474 zcmV<00VV#*27&~TGJjRe5gj=QygQ0X;HjYbADo3#aQ-P635^}X9`?;#kOU3ts&4)I z`P8cVz7*fp|7G>Dm2PIgKUB|$7o^;0;VV<67u<;p#<{cd`Opdqrg%PB3XeM5|8_f) z4AhR;0qBEVr6iq1L}d^^rvb2?h{#eM))`(LguRK#f}Ds<{C^RwEFTn37WaMTB+fY? z&fvhFr2?8gx0KmHn5*j}zKkjl1_BZ1P`!-!#SkU$giKTix|nm)r0>_aky^?&7}D&K zokIx6dzVD)IZIvGo><;@nkK>wltj_ojcN^e6bf+TZ`b{o6+zqQ_v`JP2OLUp}&QEA?Hps6200nDJPQ zENfYhJGENObbNwTBuZMTRiQjC)7qvh0}8c(&py~4Be(8RIslgLJOFkRJBzkCVYR{| zx~-s;Bx_MUzxa%+Ha6nGLmO)qvc0 z;$~@wx`Wq{oUmN`hQ-3;uqC;Shf+b2K~pOo%V9yT7DHub0)K-`4e5lD;gv^ihRKl0 zr1i=s8EWoB*EDAvS`94gIVK*JS8Y2|Yj0yb>g6T4WR=^2tL&2hc zeT=$kSqiTNlpQHpiklu;W@STF>2>d{9ckrc%DHT~D))iFt;e(q&JCH`Czw&6$Xs2{ zt7TO>u$&67hT%lAq0sHJCL-DpTGKgj-Flo74ZRZL)E}cF7naj$2jwD~=h^zPKR*Bf Q0RR630Jqtphu{DEr3UDtTD4X)Qr0@rmQe;!!c0_!5RJmxZ9Dl7BQrkcyTlgjX&CDYFw} zuC7ZQ44W{x5*?Ca%1aV3$d&?8#t|e>+OFk0EZIGE>sJ)|0dYe~;(t&Ptm`0nG%i^-EV_4MEoeng= z(Bm+VHZ(UdFT Date: Tue, 28 Nov 2023 11:47:22 +0100 Subject: [PATCH 22/22] Update scenes regression references --- ...scn.reference_0_DOFs Container_mstate.txt.gz | Bin 842 -> 799 bytes ....scn.reference_1_CollisionDOFs_mstate.txt.gz | Bin 458 -> 421 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/regression/references/SingleBeamDeploymentCollision.scn.reference_0_DOFs Container_mstate.txt.gz b/regression/references/SingleBeamDeploymentCollision.scn.reference_0_DOFs Container_mstate.txt.gz index 451a435409afeaa59989a2105de08d48113f0d4d..397f4ad15925436719b7fe42edc0974dcbf00622 100644 GIT binary patch delta 761 zcmV9yGJlrKk>ofGMDJ?_9Vi7p#P6_Pj0@wx3zH;B^?gorsH*4)Nku#X z2{75e{vY*JeNT$--M@eLp@-aDqc4R5AIz^Mdks7x<{6Re*Ps&J%ZLJh>SH~e6~3NI zP{_WbVn+OsdN|1%qa@xK<`QWRhp}vmi0xR`^g5KPR2#|<$A4_dRH~hVo4Ut^aO7GL z<`FAvrL^4Zmchg^5HqhGYQ^$q;_jSSkkEsC@IzMFDWM3dq4TML0KGb@^EH{3 zd>B@)E0HwDX9#L=-Y;MjZ!**$Pddd!h3-72p_+Lal)fa(B1ks$(PC)0rCBcRIq)-BkP>p7Y&+1-=peHCC+Y}0>h3C(B9T8x=qmsjynP33&?o^41;?U@RD{r7Gv)U=HAFF-i|xUgd5y&?8o>0p%rar z@SAMvTpLq&e8hb}9Ow!_F6l1-o_GLG)dHBf=mr#0xB!c|01>JSNUoL!M(c3^uu7Z* z3xEtS;OdL>BQ4$%*7?v!%UoS-K;YQf64Y|T19He8fX$Q_5OL0g892onfN9zpFxnX} z;MOe#BeoY%!3R)reA7_@4*dlve*vX-0XRh7CgSE;_FCQXx|+M<(e77*iW^QpGgxb) rPB_N9!_jaYmbOpow}(|%fGn^20Vvsje*gdg|NjF3Q@tbdk`Vv^S%Yi` delta 805 zcmV+=1KRwb2FeDIGJjUNwcIca%w2^VSU%iG;4a+4@4p2PFa6w+gTI?dBryaAz(9X~ zKRvpCCdJR&|M>QiL6+TLAL{30Lt99`K1_2*LVZ2+NDnU@s^>!^DVg&5=x{5wwfwzG zgH%k+*CQ60tKhYl+3uw__m{#*Y$EL^g_7@ZJtLz97vxK|HkQgMt=bS5cCy<)bpHx}W1Y ziRs*impE`iG?%YMxI#c9Ma$mIN(UWkBkec>zmwJ-j<~yDkV0qxamwaL29w%$vr^6d z(HQ1z3x(mx27j^1n0<#+)7NUS4+4MTBFa&6CEC^bb z8!6@b)Uf1TO)P|JvB|TaNW5wrA^}Lpfe6*bdRK|+!+&6lcb&0hA(OyxcHS7tpkZeX znZJdNZKQXCz>&6x1t7d`VATSD014{@Qqr-@Kg>HXA)2wGZTvESzJY%J|Mvg%;3=@s zYnj@OUgr6-B;MM42|3gg-N;+WZHi7q;iqcLh;+FMPpE8*Q=?5T(X-wAmM|npkB|`r zvTlEqaeq!dTm4Mx`v@94mRxk53BVAJYi`t6>j)7*7`Hj5$uURc;@)+?oo?bOS_c4w zCG`L}70bSViqw`2b%TC9zC`vq5T0=W1QGfLfMV?CWHhYyVWSI@@9Q?X03C7xAd)u( z7peWi3WeAn7-k0eng9VRj>bae55Uw#c>$VoYkv_?k7K6DLrs7=V~_otA;mq;*IrF^ zOR5>2iEOSeD*`b8IuLhe>Kw!002q=7AcZ$e7@?RP6F}$|0dg%j37By4QefP@M=vT* zc5bXO-FpGV%VFlN9GxZi+MDj=F!I^k-VuK<3RUE_ueXdy^kop=1$68&Rd!s+3kK=3 jFCKZvT;+TJROlA~9}52KF8}}l|NjF3abZAA;}HM=N}z(J diff --git a/regression/references/SingleBeamDeploymentCollision.scn.reference_1_CollisionDOFs_mstate.txt.gz b/regression/references/SingleBeamDeploymentCollision.scn.reference_1_CollisionDOFs_mstate.txt.gz index b9da64eff0c751d40956b6ce39261dd7aa3b6629..2886309b2a3106ebbe78cf29b8d0fff813e9fd53 100644 GIT binary patch delta 384 zcmV-`0e}9=1Em9yE`Q67-Y^V=@2Nr!`~rt>9@0fxNdGM?C3-8^LlD6B#nLxJNdyGX zpWmx?)m+klzMcQ#{m>>im%#mGihjC_mhd`L92D#q7SOYcYa_|d6ePVKSe0T+uo1dw z3#Fx85fu@Z$OZ!oo2>Ghx*l;Y(Oto^xT`q$681D+;@gvS$$yr@_malip4?2x)m9)b zP+3x2D{Qq5HH&Ly=~CJz>MOSf4KRz|uVQn$F3{Gq@k5-XS7;Im#oVILJs?EO|9MI8m z$CkKIp4d?xL3xIC9H<;4jgbvLRrUV=53>mdooO-#b=(QLEG9ZZoN5*{c9sjPt&>8? z+z_!sJOm eU3gmH+5-s;Bx_MUzxa%+Ha6nGLmO)qvc0 z;$~@wx`Wq{oUmN`hQ-3;uqC;Shf+b2K~pOo%V9yT7DHub0)K-`4e5lD;gv^ihRKl0 zr1i=s8EWoB*EDAvS`94gIVK*JS8Y2|Yj0yb>g6T4WR=^2tL&2hc zeT=$kSqiTNlpQHpiklu;W@STF>2>d{9ckrc%DHT~D))iFt;e(q&JCH`Czw&6$Xs2{ zt7TO>u$&67hT%lAq0sHJCL-DpTGKgj-Flo74ZRZL)E}cF7naj$2jwD~=h^zPKR*Bf Q0RR630Jqtphu{