diff --git a/Sofa/Component/Visual/src/sofa/component/visual/VisualModelImpl.cpp b/Sofa/Component/Visual/src/sofa/component/visual/VisualModelImpl.cpp
index fc93fac1094..1b2ce730267 100644
--- a/Sofa/Component/Visual/src/sofa/component/visual/VisualModelImpl.cpp
+++ b/Sofa/Component/Visual/src/sofa/component/visual/VisualModelImpl.cpp
@@ -166,10 +166,15 @@ VisualModelImpl::VisualModelImpl() //const std::string &name, std::string filena
// add one identity matrix
xforms.resize(1);
+ addUpdateCallback("updateGeometry", {&d_triangles},
+ [&](const core::DataTracker&) -> sofa::core::objectmodel::ComponentState {
+ modified=true;
+ return sofa::core::objectmodel::ComponentState::Loading;
+ }, {&d_componentState});
+
addUpdateCallback("updateTextures", { &d_texturename },
- [&](const core::DataTracker& tracker) -> sofa::core::objectmodel::ComponentState
+ [&](const core::DataTracker&) -> sofa::core::objectmodel::ComponentState
{
- SOFA_UNUSED(tracker);
m_textureChanged = true;
return sofa::core::objectmodel::ComponentState::Loading;
}, { &d_componentState });
diff --git a/Sofa/framework/Core/src/sofa/core/fwd.h b/Sofa/framework/Core/src/sofa/core/fwd.h
index 622a30e1394..dc2c5109e47 100644
--- a/Sofa/framework/Core/src/sofa/core/fwd.h
+++ b/Sofa/framework/Core/src/sofa/core/fwd.h
@@ -30,7 +30,7 @@ namespace sofa::helper::visual { class DrawTool; }
namespace sofa::core
{
-
+class ObjectFactory;
class BaseState;
class BaseMapping;
class BehaviorModel;
diff --git a/Sofa/framework/Core/src/sofa/core/objectmodel/Base.h b/Sofa/framework/Core/src/sofa/core/objectmodel/Base.h
index ae3b2285b6c..330a3d7cc4b 100644
--- a/Sofa/framework/Core/src/sofa/core/objectmodel/Base.h
+++ b/Sofa/framework/Core/src/sofa/core/objectmodel/Base.h
@@ -352,7 +352,7 @@ class SOFA_CORE_API Base : public IntrusiveObject
/// @name componentstate
/// Methods related to component state
/// @{
-
+ int getRevisionCounter() const { return d_componentState.getCounter(); }
ComponentState getComponentState() const { return d_componentState.getValue() ; }
bool isComponentStateValid() const { return d_componentState.getValue() == ComponentState::Valid; }
bool isComponentStateInvalid() const { return d_componentState.getValue() == ComponentState::Invalid; }
diff --git a/applications/plugins/SofaImplicitField/CMakeLists.txt b/applications/plugins/SofaImplicitField/CMakeLists.txt
index f7cfab05c64..a3d74a87fa4 100644
--- a/applications/plugins/SofaImplicitField/CMakeLists.txt
+++ b/applications/plugins/SofaImplicitField/CMakeLists.txt
@@ -5,6 +5,7 @@ sofa_find_package(Sofa.Component.Topology.Container.Constant REQUIRED)
set(HEADER_FILES
config.h.in
+ fwd.h
initSofaImplicitField.h
# This is backward compatibility
@@ -12,6 +13,8 @@ set(HEADER_FILES
deprecated/ImplicitSurfaceContainer.h # This is a backward compatibility file toward ScalarField
deprecated/InterpolatedImplicitSurface.h # This is a backward compatibility file toward DiscreteGridField
+ components/engine/FieldToSurfaceMesh.h
+ components/engine/RayMarching.h
components/geometry/BottleField.h
components/geometry/DiscreteGridField.h
components/geometry/SphericalField.h
@@ -28,6 +31,8 @@ set(SOURCE_FILES
deprecated/SphereSurface.cpp
deprecated/InterpolatedImplicitSurface.cpp
+ components/engine/FieldToSurfaceMesh.cpp
+ components/engine/RayMarching.cpp
components/geometry/BottleField.cpp
components/geometry/ScalarField.cpp
components/geometry/DiscreteGridField.cpp
@@ -47,6 +52,10 @@ endif()
add_library(${PROJECT_NAME} SHARED ${HEADER_FILES} ${SOURCE_FILES} ${EXTRA_FILES})
target_link_libraries(${PROJECT_NAME} PRIVATE Sofa.Component.Topology.Container.Constant)
+find_package(SofaPython3 REQUIRED)
+if (SofaPython3_FOUND)
+ add_subdirectory(python)
+endif()
## Install rules for the library and headers; CMake package configurations files
sofa_create_package_with_targets(
diff --git a/applications/plugins/SofaImplicitField/components/engine/FieldToSurfaceMesh.cpp b/applications/plugins/SofaImplicitField/components/engine/FieldToSurfaceMesh.cpp
new file mode 100644
index 00000000000..a7a4ac9cf5b
--- /dev/null
+++ b/applications/plugins/SofaImplicitField/components/engine/FieldToSurfaceMesh.cpp
@@ -0,0 +1,356 @@
+/******************************************************************************
+* SOFA, Simulation Open-Framework Architecture, development version *
+* (c) 2006-2025 INRIA, USTL, UJF, CNRS, MGH *
+* *
+* This program is free software; you can redistribute it and/or modify it *
+* under the terms of the GNU Lesser General Public License as published by *
+* the Free Software Foundation; either version 2.1 of the License, or (at *
+* your option) any later version. *
+* *
+* This program is distributed in the hope that it will be useful, but WITHOUT *
+* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
+* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License *
+* for more details. *
+* *
+* You should have received a copy of the GNU Lesser General Public License *
+* along with this program. If not, see . *
+*******************************************************************************
+* Authors: The SOFA Team and external contributors (see Authors.txt) *
+* *
+* Contact information: contact@sofa-framework.org *
+******************************************************************************/
+#include
+
+#include
+using sofa::core::visual::VisualParams ;
+
+#include
+using sofa::core::RegisterObject ;
+
+#include
+
+#include "FieldToSurfaceMesh.h"
+
+#include
+
+namespace sofa::component::geometry
+{
+
+FieldToSurfaceMesh::FieldToSurfaceMesh()
+ : l_field(initLink("field", "The scalar field to generate a mesh from."))
+ , mStep(initData(&mStep,0.1,"step","Step"))
+ , mIsoValue(initData(&mIsoValue,0.0,"isoValue","Iso Value"))
+ , mGridMin(initData(&mGridMin, Vec3d(-1,-1,-1),"min","Grid Min"))
+ , mGridMax(initData(&mGridMax, Vec3d(1,1,1),"max","Grid Max"))
+ , d_out_points(initData(&d_out_points, "outputPoints", "position of the tiangles vertex"))
+ , d_out_triangles(initData(&d_out_triangles, "outputTriangles", "list of triangles"))
+ , d_debugDraw(initData(&d_debugDraw,true, "debugDraw","Display the extracted surface"))
+ , d_doAsync(initData(&d_doAsync,false, "doAsync","Extract the surface in an asynchronous thread"))
+{
+ addUpdateCallback("updateMesh", {&mStep, &mIsoValue, &mGridMin, &mGridMax}, [this](const core::DataTracker&){
+ checkInputs();
+
+ std::cout << " UPDATE MESH " << getRevisionCounter() << std::endl;
+ return core::objectmodel::ComponentState::Valid;
+ }, {});
+}
+
+FieldToSurfaceMesh::~FieldToSurfaceMesh()
+{
+}
+
+void FieldToSurfaceMesh::init()
+{
+ if(!l_field.get())
+ {
+ msg_error() << "Missing field to extract surface from";
+ d_componentState = core::objectmodel::ComponentState::Invalid;
+ }
+ d_componentState = core::objectmodel::ComponentState::Valid;
+}
+
+void FieldToSurfaceMesh::checkInputs(){
+
+ auto length = mGridMax.getValue()-mGridMin.getValue() ;
+ auto step = mStep.getValue();
+
+ // clamp the mStep value to avoid too large grids
+
+ if( step < 0.0001 || (length.x() / step > 256) || length.y() / step > 256 || length.z() / step > 256)
+ {
+ mStep.setValue( *std::max_element(length.begin(), length.end()) / 256.0 );
+ msg_warning() << "step exceeding grid size, clamped to " << mStep.getValue();
+ }
+}
+
+void FieldToSurfaceMesh::updateMeshIfNeeded(){
+ // Check if we are computing, if so, wait termination
+ if(workInProgress.load())
+ return;
+
+ // Check that the component state has not its revision counter changed.
+ if( !workFinished.load() &&
+ (l_field->getRevisionCounter() != lastGenerationFieldCounter || getRevisionCounter() != lastGenerationCounter) )
+ {
+ std::cout << "STARTING A MESH " << getName() << std::endl;
+ // First clean the two buffers.
+ sofa::helper::getWriteOnlyAccessor(d_out_points).clear();
+ sofa::helper::getWriteOnlyAccessor(d_out_triangles).clear();
+
+ double isoval = mIsoValue.getValue();
+ double mstep = mStep.getValue();
+ double invStep = 1.0/mStep.getValue();
+
+ Vec3d gridmin = mGridMin.getValue() ;
+ Vec3d gridmax = mGridMax.getValue() ;
+
+ auto field = l_field.get();
+ results = std::async(std::launch::async, [isoval, mstep, invStep, gridmin, gridmax, field,
+ this](){
+ workInProgress.store(true);
+ generateSurfaceMesh(isoval, mstep, invStep, gridmin, gridmax, field);
+ workFinished.store(true);
+ workInProgress.store(false);
+ return true;
+ });
+
+ if(!d_doAsync.getValue())
+ results.wait();
+ }
+
+ if(workFinished.load()){
+ /// Copy the surface to Sofa topology
+ d_out_points.setValue(tmpPoints);
+ d_out_triangles.setValue(tmpTriangles);
+
+ tmpPoints.clear();
+ tmpTriangles.clear();
+
+ lastGenerationFieldCounter = l_field->getRevisionCounter();
+ lastGenerationCounter = getRevisionCounter();
+ workFinished.store(false);
+ //results.wait();
+ //results.get();
+ std::cout << "FINISHING A MESH " << getName()
+ << " " << d_out_points.getValue().size() << " " << d_out_triangles.getValue().size() << std::endl;
+
+ return;
+ }
+}
+
+void FieldToSurfaceMesh::draw(const VisualParams* vparams)
+{
+ if(isComponentStateInvalid())
+ return;
+
+ if(!d_debugDraw.getValue())
+ return;
+
+ updateMeshIfNeeded();
+
+ auto dt = vparams->drawTool();
+
+ dt->drawBoundingBox(mGridMin.getValue(), mGridMax.getValue()) ;
+
+ sofa::helper::ReadAccessor< Data > x = d_out_points;
+ sofa::helper::ReadAccessor< Data > triangles = d_out_triangles;
+ dt->setLightingEnabled(true);
+
+ for(const Triangle& triangle : triangles)
+ {
+ int a = triangle[0];
+ int b = triangle[1];
+ int c = triangle[2];
+ Vec3d center = (x[a]+x[b]+x[c])*0.333333;
+ Vec3d pa = (0.9*x[a]+0.1*center) ;
+ Vec3d pb = (0.9*x[b]+0.1*center) ;
+ Vec3d pc = (0.9*x[c]+0.1*center) ;
+
+ Vec3d a1 = x[c]-x[b] ;
+ Vec3d a2 = x[a]-x[b] ;
+
+ vparams->drawTool()->drawTriangles({pa,pb,pc},
+ a1.cross(a2),
+ type::RGBAColor(0.0,0.0,1.0,1.0));
+ }
+
+ if(x.size()>10000){
+ dt->drawPoints(x, 1.0, type::RGBAColor(1.0,1.0,0.0,0.2)) ;
+ }else{
+ dt->drawSpheres(x, 0.01, type::RGBAColor(1.0,1.0,0.0,0.2)) ;
+ }
+}
+
+void FieldToSurfaceMesh::generateSurfaceMesh(double isoval, double mstep, double invStep,
+ Vec3d gridmin, Vec3d gridmax,
+ sofa::component::geometry::ScalarField* field)
+{
+ if(!field)
+ return;
+
+ std::cout << "1" << std::endl;
+ tmpPoints.clear();
+ tmpTriangles.clear();
+
+ int nx = floor((gridmax.x() - gridmin.x()) * invStep) + 1 ;
+ int ny = floor((gridmax.y() - gridmin.y()) * invStep) + 1 ;
+ int nz = floor((gridmax.z() - gridmin.z()) * invStep) + 1 ;
+
+ double cx,cy,cz;
+ int x,y,z,i,mk;
+ const int *tri;
+
+
+ std::cout << "2" << std::endl;
+ planes.resize(2*(nx)*(ny));
+ P0 = planes.begin()+0;
+ P1 = planes.begin()+nx*ny;
+
+ const int dx = 1;
+ const int dy = nx;
+
+ z = 0;
+ newPlane();
+
+ i = 0 ;
+ cz = gridmin.z() ;
+ for (int y = 0 ; y < ny ; ++y)
+ {
+ cy = gridmin.y() + mstep * y ;
+ for (int x = 0 ; x < nx ; ++x, ++i)
+ {
+ cx = gridmin.x() + mstep * x ;
+
+ Vec3d pos { cx, cy, cz } ;
+ double res = field->getValue(pos) ;
+ (P1+i)->data = res ;
+ }
+ }
+
+ for (z=1; z<=nz; ++z)
+ {
+ newPlane();
+
+ i = 0 ;
+ cz = gridmin.z() + mstep * z ;
+ for (int y = 0 ; y < ny ; ++y)
+ {
+ cy = gridmin.y() + mstep * y ;
+ for (int x = 0 ; x < nx ; ++x, ++i)
+ {
+ cx = gridmin.x() + mstep * x ;
+
+ Vec3d pos { cx, cy, cz } ;
+ double res = field->getValue(pos) ;
+ (P1+i)->data = res ;
+ }
+ }
+
+ unsigned int i=0;
+ int edgecube[12];
+ const int edgepts[12] = {0,1,0,1,0,1,0,1,2,2,2,2};
+ typename std::vector::iterator base = planes.begin();
+ int ip0 = P0-base;
+ int ip1 = P1-base;
+ edgecube[0] = (ip0 -dy);
+ edgecube[1] = (ip0 );
+ edgecube[2] = (ip0 );
+ edgecube[3] = (ip0-dx );
+ edgecube[4] = (ip1 -dy);
+ edgecube[5] = (ip1 );
+ edgecube[6] = (ip1 );
+ edgecube[7] = (ip1-dx );
+ edgecube[8] = (ip1-dx-dy);
+ edgecube[9] = (ip1-dy );
+ edgecube[10] = (ip1 );
+ edgecube[11] = (ip1-dx );
+
+ // First line is all zero
+ {
+ y=0;
+ x=0;
+ i+=nx;
+ }
+ for(y=1; ydata>isoval)^((P1+i-dx)->data>isoval))
+ {
+ (P1+i)->p[0] = addPoint(tmpPoints, 0, pos,gridmin, (P1+i)->data,(P1+i-dx)->data, mstep, isoval);
+ }
+ if (((P1+i)->data>isoval)^((P1+i-dy)->data>isoval))
+ {
+ (P1+i)->p[1] = addPoint(tmpPoints, 1, pos,gridmin,(P1+i)->data,(P1+i-dy)->data, mstep, isoval);
+ }
+ if (((P1+i)->data>isoval)^((P0+i)->data>isoval))
+ {
+ (P1+i)->p[2] = addPoint(tmpPoints, 2, pos,gridmin,(P1+i)->data,(P0+i)->data, mstep, isoval);
+ }
+
+ // All points should now be created
+
+ if ((P0+i-dx-dy)->data > isoval) mk = 1;
+ else mk=0;
+ if ((P0+i -dy)->data > isoval) mk|= 2;
+ if ((P0+i )->data > isoval) mk|= 4;
+ if ((P0+i-dx )->data > isoval) mk|= 8;
+ if ((P1+i-dx-dy)->data > isoval) mk|= 16;
+ if ((P1+i -dy)->data > isoval) mk|= 32;
+ if ((P1+i )->data > isoval) mk|= 64;
+ if ((P1+i-dx )->data > isoval) mk|= 128;
+
+
+ tri=sofa::helper::MarchingCubeTriTable[mk];
+ while (*tri>=0)
+ {
+ typename std::vector::iterator b = base+i;
+ if (addFace(tmpTriangles,
+ (b+edgecube[tri[0]])->p[edgepts[tri[0]]],
+ (b+edgecube[tri[1]])->p[edgepts[tri[1]]],
+ (b+edgecube[tri[2]])->p[edgepts[tri[2]]], tmpPoints.size())<0)
+ {
+ /*std::cout << " mk=0x"<p[edgepts[e]];
+ std::cout<::iterator P = P0;
+ P0 = P1;
+ P1 = P;
+ int n = planes.size()/2;
+ for (int i=0; i
+void registerToFactory(sofa::core::ObjectFactory* factory)
+{
+ factory->registerObjects(sofa::core::ObjectRegistrationData("Generates a surface mesh from a field function.")
+ .add< FieldToSurfaceMesh >());
+}
+
+}
diff --git a/applications/plugins/SofaImplicitField/components/engine/FieldToSurfaceMesh.h b/applications/plugins/SofaImplicitField/components/engine/FieldToSurfaceMesh.h
new file mode 100644
index 00000000000..28d57a40efe
--- /dev/null
+++ b/applications/plugins/SofaImplicitField/components/engine/FieldToSurfaceMesh.h
@@ -0,0 +1,135 @@
+/******************************************************************************
+* SOFA, Simulation Open-Framework Architecture, development version *
+* (c) 2006-2025 INRIA, USTL, UJF, CNRS, MGH *
+* *
+* This program is free software; you can redistribute it and/or modify it *
+* under the terms of the GNU Lesser General Public License as published by *
+* the Free Software Foundation; either version 2.1 of the License, or (at *
+* your option) any later version. *
+* *
+* This program is distributed in the hope that it will be useful, but WITHOUT *
+* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
+* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License *
+* for more details. *
+* *
+* You should have received a copy of the GNU Lesser General Public License *
+* along with this program. If not, see . *
+*******************************************************************************
+* Authors: The SOFA Team and external contributors (see Authors.txt) *
+* *
+* Contact information: contact@sofa-framework.org *
+******************************************************************************/
+#pragma once
+#include
+#include
+
+#include
+#include
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+namespace sofa::component::geometry
+{
+
+typedef sofa::core::topology::BaseMeshTopology::SeqTriangles SeqTriangles;
+typedef sofa::core::topology::BaseMeshTopology::Triangle Triangle;
+typedef sofa::type::vector VecCoord;
+
+using sofa::core::visual::VisualParams ;
+using sofa::core::objectmodel::BaseObject ;
+using sofa::type::Vec3d ;
+
+class FieldToSurfaceMesh : public BaseObject
+{
+public:
+ SOFA_CLASS(FieldToSurfaceMesh, BaseObject);
+
+ virtual void init() override ;
+ virtual void draw(const VisualParams*params) override ;
+
+ double getStep() const { return mStep.getValue(); }
+ void setStep(double val) { mStep.setValue(val); }
+
+ double getIsoValue() const { return mIsoValue.getValue(); }
+ void setIsoValue(double val) { mIsoValue.setValue(val); }
+
+ const Vec3d& getGridMin() const { return mGridMin.getValue(); }
+ void setGridMin(const Vec3d& val) { mGridMin.setValue(val); }
+ void setGridMin(double x, double y, double z) { mGridMin.setValue( Vec3d(x,y,z)); }
+
+ const Vec3d& getGridMax() const { return mGridMax.getValue(); }
+ void setGridMax(const Vec3d& val) { mGridMax.setValue(val); }
+ void setGridMax(double x, double y, double z) { mGridMax.setValue( Vec3d(x,y,z)); }
+
+protected:
+ SingleLink l_field ;
+
+ Data mStep;
+ Data mIsoValue;
+
+ Data< Vec3d > mGridMin;
+ Data< Vec3d > mGridMax;
+
+ /// For each cube, store the vertex indices on each 3 first edges, and the data value
+ struct CubeData
+ {
+ int p[3];
+ double data;
+ };
+
+ int addPoint(VecCoord& v, int i, Vec3d pos, const Vec3d& gridmin, double v0, double v1, double step, double iso)
+ {
+ pos[i] -= (iso-v0)/(v1-v0);
+ v.push_back( (pos * step)+gridmin ) ;
+ return v.size()-1;
+ }
+
+ int addFace(SeqTriangles& triangles, int p1, int p2, int p3, int nbp)
+ {
+ if ((unsigned)p1<(unsigned)nbp &&
+ (unsigned)p2<(unsigned)nbp &&
+ (unsigned)p3<(unsigned)nbp)
+ {
+ triangles.push_back(Triangle(p1, p3, p2));
+ return triangles.size()-1;
+ }
+ else
+ {
+ return -1;
+ }
+ }
+
+ /// Output
+ Data d_out_points;
+ Data d_out_triangles;
+ Data d_debugDraw;
+ Data d_doAsync;
+
+ sofa::type::vector planes;
+ typename sofa::type::vector::iterator P0; /// Pointer to first plane
+ typename sofa::type::vector::iterator P1; /// Pointer to second plane
+
+ void newPlane();
+ void generateSurfaceMesh(double isoval, double mstep, double invStep,
+ Vec3d gridmin, Vec3d gridmax,
+ sofa::component::geometry::ScalarField*);
+ void updateMeshIfNeeded();
+
+protected:
+ FieldToSurfaceMesh() ;
+ virtual ~FieldToSurfaceMesh() ;
+
+ int lastGenerationCounter {-1};
+ int lastGenerationFieldCounter {-1};
+
+private:
+ void checkInputs();
+ std::atomic workInProgress;
+ std::atomic workFinished;
+ std::future results;
+ VecCoord tmpPoints;
+ SeqTriangles tmpTriangles;
+};
+
+}
+
diff --git a/applications/plugins/SofaImplicitField/components/engine/RayMarching.cpp b/applications/plugins/SofaImplicitField/components/engine/RayMarching.cpp
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/applications/plugins/SofaImplicitField/components/engine/RayMarching.h b/applications/plugins/SofaImplicitField/components/engine/RayMarching.h
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/applications/plugins/SofaImplicitField/components/geometry/ScalarField.cpp b/applications/plugins/SofaImplicitField/components/geometry/ScalarField.cpp
index 178d034a6e6..9c0323bbfd8 100644
--- a/applications/plugins/SofaImplicitField/components/geometry/ScalarField.cpp
+++ b/applications/plugins/SofaImplicitField/components/geometry/ScalarField.cpp
@@ -40,6 +40,11 @@ namespace geometry
namespace _scalarfield_
{
+void ScalarField::init()
+{
+ d_componentState.setValue(core::objectmodel::ComponentState::Valid);
+}
+
Vec3d ScalarField::getGradientByFinitDifference(Vec3d& pos, int& i)
{
Vec3d Result;
diff --git a/applications/plugins/SofaImplicitField/examples/python/example-mesh-extraction-from-implicit.py b/applications/plugins/SofaImplicitField/examples/python/example-mesh-extraction-from-implicit.py
new file mode 100644
index 00000000000..d297f84683a
--- /dev/null
+++ b/applications/plugins/SofaImplicitField/examples/python/example-mesh-extraction-from-implicit.py
@@ -0,0 +1,23 @@
+import Sofa
+from shapes import Sphere
+
+def createScene(root : Sofa.Core.Node):
+ root.addObject("RequiredPlugin", name="SofaImplicitField")
+
+ s = root.addObject(Sphere(name="field1", center=[0,0,0]))
+ root.addObject("SphericalField", name="field2", center=[2,0,0])
+
+ root.addChild("Visual")
+
+ root.Visual.addObject("FieldToSurfaceMesh", name="polygonizer1",
+ field=s.linkpath, min=[-1,-1,-1], max=[1,1,1],
+ isoValue="0.0", step="0.1",doAsync=True)
+
+ root.Visual.addObject("FieldToSurfaceMesh", name="polygonizer2",
+ field=root.field2.linkpath, min=[1,-1,-1], max=[3,1,1],
+ isoValue="0.0", step="0.01",doAsync=True)
+
+ root.Visual.addObject("OglModel", name="renderer",
+ position=root.Visual.polygonizer2.outputPoints.linkpath,
+ triangles=root.Visual.polygonizer2.outputTriangles.linkpath)
+
\ No newline at end of file
diff --git a/applications/plugins/SofaImplicitField/examples/python/python-scalarfield.py b/applications/plugins/SofaImplicitField/examples/python/python-scalarfield.py
new file mode 100644
index 00000000000..4de1988e932
--- /dev/null
+++ b/applications/plugins/SofaImplicitField/examples/python/python-scalarfield.py
@@ -0,0 +1,27 @@
+import Sofa
+from Shapes import Sphere
+
+class FieldController(Sofa.Core.Controller):
+ def __init__(self, *args, **kwargs):
+ Sofa.Core.Controller.__init__(self, *args, **kwargs)
+ self.field = kwargs.get("target")
+
+ def onAnimateEndEvent(self, event):
+ print("Animation end event")
+ print("Field value at 0,0,0 is: ", self.field.getValue(0.0,0.0,0.0) )
+ print("Field value at 1,0,0 is: ", self.field.getValue(1.0,0.0,0.0) )
+ print("Field value at 2,0,0 is: ", self.field.getValue(2.0,0.0,0.0) )
+
+ print("Field gradient at 1.0,0.0,0.0 is: ", self.field.getGradient([2.0,0.0,0.0]) )
+ print("Field hessian at 1.0,0.0,0.0 is: ", self.field.getHessian([2.0,0.0,0.0]) )
+
+def createScene(root):
+ root.addObject(Sphere("field"))
+ root.addObject(FieldController(target=root.field))
+
+ root.addChild("Visual")
+ root.Visual.addObject("OglModel", name="renderer")
+ root.Visual.addObject("ImplicitSurfaceMapping", name="polygonizer",
+ input=root., output=root.Visual.renderer.linkpath,
+ isoValue="0.5", radius="0.75", step="0.25")
+
\ No newline at end of file
diff --git a/applications/plugins/SofaImplicitField/examples/python/shapes.py b/applications/plugins/SofaImplicitField/examples/python/shapes.py
new file mode 100644
index 00000000000..ce8593cee88
--- /dev/null
+++ b/applications/plugins/SofaImplicitField/examples/python/shapes.py
@@ -0,0 +1,25 @@
+import Sofa
+from SofaImplicitField import ScalarField
+import numpy
+import jax
+import jax.numpy as jnp
+
+class Sphere(ScalarField):
+ def __init__(self, *args, **kwargs):
+ ScalarField.__init__(self, *args, **kwargs)
+
+ self.addData("center", type="Vec3d",value=kwargs.get("center", [0.0,0.0,0.0]), default=[0.0,0.0,0.0], help="center of the sphere", group="Geometry")
+ self.addData("radius", type="double",value=kwargs.get("radius", 1.0), default=1, help="radius of the sphere", group="Geometry")
+
+ self._center = jnp.array(self.center.value)
+
+ self.fast_getValue = jax.jit(self.getValueFct)
+
+ #def getValue(self, x, y, z):
+ # return numpy.sqrt( numpy.sum((self.center.value - numpy.array([x,y,z]))**2) ) - self.radius.value
+
+ def getValueFct(self, x, y, z):
+ return jnp.sqrt( jnp.sum((self._center - jnp.array([x,y,z]))**2) ) - self.radius.value
+
+ def getValue(self, x, y, z):
+ return self.fast_getValue(x, y, z)
\ No newline at end of file
diff --git a/applications/plugins/SofaImplicitField/fwd.h b/applications/plugins/SofaImplicitField/fwd.h
new file mode 100644
index 00000000000..a026ff9ff3c
--- /dev/null
+++ b/applications/plugins/SofaImplicitField/fwd.h
@@ -0,0 +1,33 @@
+/******************************************************************************
+* SOFA, Simulation Open-Framework Architecture *
+* (c) 2006 INRIA, USTL, UJF, CNRS, MGH *
+* *
+* This program is free software; you can redistribute it and/or modify it *
+* under the terms of the GNU Lesser General Public License as published by *
+* the Free Software Foundation; either version 2.1 of the License, or (at *
+* your option) any later version. *
+* *
+* This program is distributed in the hope that it will be useful, but WITHOUT *
+* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
+* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License *
+* for more details. *
+* *
+* You should have received a copy of the GNU Lesser General Public License *
+* along with this program. If not, see . *
+*******************************************************************************
+* Authors: The SOFA Team and external contributors (see Authors.txt) *
+* *
+* Contact information: contact@sofa-framework.org *
+******************************************************************************/
+#pragma once
+
+#include
+#include
+
+namespace sofa::component::geometry
+{
+ class FieldToSurfaceMesh;
+
+ template void registerToFactory(sofa::core::ObjectFactory* factory);
+}
+
diff --git a/applications/plugins/SofaImplicitField/initSofaImplicitField.cpp b/applications/plugins/SofaImplicitField/initSofaImplicitField.cpp
index ca5f0f5732d..f9ee30d23e2 100644
--- a/applications/plugins/SofaImplicitField/initSofaImplicitField.cpp
+++ b/applications/plugins/SofaImplicitField/initSofaImplicitField.cpp
@@ -20,12 +20,15 @@
* Contact information: contact@sofa-framework.org *
******************************************************************************/
#include
+#include
#include
#include
#include
using sofa::helper::system::PluginManager ;
+using namespace sofa::component::geometry;
+
namespace sofa::component::geometry::_BottleField_
{
extern void registerBottleField(sofa::core::ObjectFactory* factory);
@@ -106,6 +109,7 @@ void registerObjects(sofa::core::ObjectFactory* factory)
sofa::component::mapping::registerImplicitSurfaceMapping(factory);
sofa::component::container::registerInterpolatedImplicitSurface(factory);
sofa::component::geometry::_discretegrid_::registerDiscreteGridField(factory);
+ sofa::component::geometry::registerToFactory(factory);
}
} /// sofaimplicitfield
diff --git a/applications/plugins/SofaImplicitField/python/CMakeLists.txt b/applications/plugins/SofaImplicitField/python/CMakeLists.txt
new file mode 100644
index 00000000000..bd66bd9546b
--- /dev/null
+++ b/applications/plugins/SofaImplicitField/python/CMakeLists.txt
@@ -0,0 +1,26 @@
+project(SofaImplicitField.Python)
+
+set(SOURCE_FILES
+ ${CMAKE_CURRENT_SOURCE_DIR}/src/Binding_ScalarField.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/src/Module_SofaImplicitField.cpp
+)
+
+set(HEADER_FILES
+ ${CMAKE_CURRENT_SOURCE_DIR}/src/Binding_ScalarField.h
+)
+
+if (NOT TARGET SofaPython3::Plugin)
+ find_package(SofaPython3 REQUIRED COMPONENTS Plugin Bindings.Sofa.Core)
+endif()
+
+sofa_find_package(SofaImplicitField REQUIRED)
+
+SP3_add_python_module(
+ TARGET ${PROJECT_NAME}
+ PACKAGE SofaImplicitField.Python
+ MODULE SofaImplicitField
+ DESTINATION .
+ SOURCES ${SOURCE_FILES}
+ HEADERS ${HEADER_FILES}
+ DEPENDS SofaImplicitField SofaPython3::Plugin SofaPython3::Bindings.Sofa.Core
+)
diff --git a/applications/plugins/SofaImplicitField/python/src/Binding_ScalarField.cpp b/applications/plugins/SofaImplicitField/python/src/Binding_ScalarField.cpp
new file mode 100644
index 00000000000..7a52fad43dc
--- /dev/null
+++ b/applications/plugins/SofaImplicitField/python/src/Binding_ScalarField.cpp
@@ -0,0 +1,96 @@
+/******************************************************************************
+* SofaImplicitField plugin *
+* (c) 2024 CNRS, University of Lille, INRIA *
+* *
+* This program is free software; you can redistribute it and/or modify it *
+* under the terms of the GNU Lesser General Public License as published by *
+* the Free Software Foundation; either version 2.1 of the License, or (at *
+* your option) any later version. *
+* *
+* This program is distributed in the hope that it will be useful, but WITHOUT *
+* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
+* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License *
+* for more details. *
+* *
+* You should have received a copy of the GNU Lesser General Public License *
+* along with this program. If not, see . *
+*******************************************************************************
+* Contact information: contact@sofa-framework.org *
+******************************************************************************/
+#include
+
+#include
+#include
+#include
+#include
+
+#include "Binding_ScalarField.h"
+
+/// Makes an alias for the pybind11 namespace to increase readability.
+namespace py { using namespace pybind11; }
+
+namespace sofaimplicitfield {
+using namespace sofapython3;
+using sofa::component::geometry::ScalarField;
+using sofa::core::objectmodel::BaseObject;
+using sofa::type::Vec3d;
+using sofa::type::Mat3x3;
+
+class ScalarField_Trampoline : public ScalarField {
+public:
+ SOFA_CLASS(ScalarField_Trampoline, ScalarField);
+
+ double getValue(Vec3d& pos, int& domain) override{
+ SOFA_UNUSED(domain);
+ PythonEnvironment::gil acquire;
+ PYBIND11_OVERLOAD_PURE(double, ScalarField, getValue, pos.x(), pos.y(), pos.z());
+ }
+
+ Vec3d getGradient(Vec3d& pos, int& domain) override {
+ PythonEnvironment::gil acquire;
+
+ PYBIND11_OVERLOAD(Vec3d, ScalarField, getGradient, pos);
+ }
+
+ void getHessian(Vec3d &pos, Mat3x3& h) override {
+ PythonEnvironment::gil acquire;
+
+ PYBIND11_OVERLOAD(void, ScalarField, getHessian, pos, h);
+ }
+
+};
+
+void moduleAddScalarField(py::module &m) {
+ py::class_> f(m, "ScalarField", py::dynamic_attr(), "");
+
+ f.def(py::init([](py::args &args, py::kwargs &kwargs) {
+ auto ff = sofa::core::sptr (new ScalarField_Trampoline());
+
+ ff->f_listening.setValue(true);
+
+ if (args.size() == 1) ff->setName(py::cast(args[0]));
+
+ py::object cc = py::cast(ff);
+ for (auto kv : kwargs) {
+ std::string key = py::cast(kv.first);
+ py::object value = py::reinterpret_borrow(kv.second);
+ if (key == "name") {
+ if (args.size() != 0) {
+ throw py::type_error("The name is set twice as a "
+ "named argument='" + py::cast(value) + "' and as a"
+ "positional argument='" +
+ py::cast(args[0]) + "'.");
+ }
+ }
+ //BindingBase::SetAttr(cc, key, value);
+ }
+ return ff;
+ }));
+
+ m.def("getValue", &ScalarField_Trampoline::getValue);
+ m.def("getGradient", &ScalarField_Trampoline::getGradient);
+ m.def("getHessian", &ScalarField_Trampoline::getHessian);
+}
+
+}
diff --git a/applications/plugins/SofaImplicitField/python/src/Binding_ScalarField.h b/applications/plugins/SofaImplicitField/python/src/Binding_ScalarField.h
new file mode 100644
index 00000000000..8ab31e32945
--- /dev/null
+++ b/applications/plugins/SofaImplicitField/python/src/Binding_ScalarField.h
@@ -0,0 +1,29 @@
+/******************************************************************************
+* SofaImplicitField plugin *
+* (c) 2021 CNRS, University of Lille, INRIA *
+* *
+* This program is free software; you can redistribute it and/or modify it *
+* under the terms of the GNU Lesser General Public License as published by *
+* the Free Software Foundation; either version 2.1 of the License, or (at *
+* your option) any later version. *
+* *
+* This program is distributed in the hope that it will be useful, but WITHOUT *
+* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
+* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License *
+* for more details. *
+* *
+* You should have received a copy of the GNU Lesser General Public License *
+* along with this program. If not, see . *
+*******************************************************************************
+* Contact information: contact@sofa-framework.org *
+******************************************************************************/
+
+#pragma once
+
+#include
+
+namespace sofaimplicitfield {
+
+void moduleAddScalarField(pybind11::module &m);
+
+}
diff --git a/applications/plugins/SofaImplicitField/python/src/Module_SofaImplicitField.cpp b/applications/plugins/SofaImplicitField/python/src/Module_SofaImplicitField.cpp
new file mode 100644
index 00000000000..bd7e9480856
--- /dev/null
+++ b/applications/plugins/SofaImplicitField/python/src/Module_SofaImplicitField.cpp
@@ -0,0 +1,38 @@
+/******************************************************************************
+* SofaImplicitField plugin *
+* (c) 2024 CNRS, University of Lille, INRIA *
+* *
+* This program is free software; you can redistribute it and/or modify it *
+* under the terms of the GNU Lesser General Public License as published by *
+* the Free Software Foundation; either version 2.1 of the License, or (at *
+* your option) any later version. *
+* *
+* This program is distributed in the hope that it will be useful, but WITHOUT *
+* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
+* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License *
+* for more details. *
+* *
+* You should have received a copy of the GNU Lesser General Public License *
+* along with this program. If not, see . *
+*******************************************************************************
+* Contact information: contact@sofa-framework.org *
+******************************************************************************/
+
+#include
+namespace py = pybind11;
+
+#include "Binding_ScalarField.h"
+
+namespace sofaimplicitfield
+{
+
+PYBIND11_MODULE(SofaImplicitField, m) {
+ m.doc() = R"doc(
+ Implement scalar field representation in python
+ )doc";
+
+ moduleAddScalarField(m);
+}
+
+}
+