diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_LinkPath.cpp b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_LinkPath.cpp index d08b0e57..a268a11a 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_LinkPath.cpp +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_LinkPath.cpp @@ -24,6 +24,8 @@ using sofa::core::objectmodel::BaseLink; #include using sofa::core::objectmodel::BaseObject; +#include + #include #include @@ -79,6 +81,13 @@ void moduleAddLinkPath(py::module& m) py::class_ link(m, "LinkPath", sofapython3::doc::linkpath::linkpath); link.def("__str__", &__str__); link.def("__repr__", &__repr__); + link.def("target", [](const LinkPath& entry) -> py::object{ + if(entry.targetData != nullptr) + return PythonFactory::toPython(entry.targetData); + else if(entry.targetBase.get() != nullptr) + return PythonFactory::toPython(entry.targetBase.get()); + return py::none(); + }); } }/// namespace sofapython3 diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Mapping.cpp b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Mapping.cpp new file mode 100644 index 00000000..9c8e8ac0 --- /dev/null +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Mapping.cpp @@ -0,0 +1,182 @@ +/****************************************************************************** +* SofaPython3 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 * +******************************************************************************/ + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include + +#include +using sofapython3::PythonEnvironment; + +/// Makes an alias for the pybind11 namespace to increase readability. +namespace py { using namespace pybind11; } +/// To bring in the `_a` literal +using namespace pybind11::literals; + +namespace sofapython3 +{ + using sofa::core::Mapping; + using sofa::core::objectmodel::BaseObject; + using sofa::core::objectmodel::ComponentState; + using sofa::core::behavior::MechanicalState; + using sofa::core::MechanicalParams; + using sofa::core::behavior::MultiMatrixAccessor; + using sofa::defaulttype::Vec3dTypes; + using sofa::defaulttype::Vec2dTypes; + using sofa::defaulttype::Vec1dTypes; + using sofa::defaulttype::Vec6dTypes; + using sofa::defaulttype::Rigid3dTypes; + using sofa::defaulttype::Rigid2dTypes; + + template + Mapping_Trampoline::Mapping_Trampoline() = default; + + template + Mapping_Trampoline::~Mapping_Trampoline() = default; + + template + std::string Mapping_Trampoline::getClassName() const + { + PythonEnvironment::gil acquire {"getClassName"}; + + // Get the actual class name from python. + return py::str(py::cast(this).get_type().attr("__name__")); + } + + template + void Mapping_Trampoline::init() + { + PythonEnvironment::gil acquire; + + Inherit1::init(); + } + + template + void Mapping_Trampoline::apply( const MechanicalParams* mparams, OutDataVecCoord& out, const InDataVecCoord& in){ + PythonEnvironment::gil acquire; + + // pass bFactor, kFactor, energy + py::dict mp = py::dict("time"_a=this->getContext()->getTime(), + "mFactor"_a=mparams->mFactor(), + "bFactor"_a=mparams->bFactor(), + "kFactor"_a=mparams->kFactor(), + "isImplicit"_a=mparams->implicit(), + "energy"_a=mparams->energy()); + + PYBIND11_OVERLOAD_PURE(void, Inherit1, apply, mp, + PythonFactory::toPython(&out), PythonFactory::toPython(&in)); + } + + template + void Mapping_Trampoline::applyJ( const MechanicalParams* mparams, OutDataVecDeriv& out, const InDataVecDeriv& in){ + PythonEnvironment::gil acquire; + + // pass bFactor, kFactor, energy + py::dict mp = py::dict("time"_a=getContext()->getTime(), + "mFactor"_a=mparams->mFactor(), + "bFactor"_a=mparams->bFactor(), + "kFactor"_a=mparams->kFactor(), + "isImplicit"_a=mparams->implicit(), + "energy"_a=mparams->energy()); + + PYBIND11_OVERLOAD_PURE(void, Inherit1, applyJ, mp, + PythonFactory::toPython(&out), PythonFactory::toPython(&in)); + } + + template + void Mapping_Trampoline::applyJT( const MechanicalParams* mparams, InDataVecDeriv& out, const OutDataVecDeriv& in){ + PythonEnvironment::gil acquire; + + py::dict mp = py::dict("time"_a=getContext()->getTime(), + "mFactor"_a=mparams->mFactor(), + "bFactor"_a=mparams->bFactor(), + "kFactor"_a=mparams->kFactor(), + "isImplicit"_a=mparams->implicit(), + "energy"_a=mparams->energy()); + + PYBIND11_OVERLOAD_PURE(void, Inherit1, applyJT, mp, + PythonFactory::toPython(&out), PythonFactory::toPython(&in)); + } + + template + void Mapping_Trampoline::applyJT( const ConstraintParams* cparams, + InDataMatrixDeriv& out, const OutDataMatrixDeriv& in) + { + PythonEnvironment::gil acquire; + + py::dict mp = py::dict("time"_a=getContext()->getTime()); + + PYBIND11_OVERLOAD_PURE(void, Inherit1, applyConstrainsJT, mp, + PythonFactory::toPython(&out), PythonFactory::toPython(&in)); + } + + template + void declareMapping(py::module &m) { + const std::string pyclass_name = std::string("Mapping_") + In::Name()+ "_" + Out::Name(); + + py::class_, BaseObject, Mapping_Trampoline, + py_shared_ptr>> f(m, pyclass_name.c_str(), py::dynamic_attr(), py::multiple_inheritance(), + sofapython3::doc::mapping::mappingClass); + + f.def(py::init([](py::args &args, py::kwargs &kwargs) { + auto ff = sofa::core::sptr> (new Mapping_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; + })); + } + + void moduleAddMapping(py::module &m) { + declareMapping(m); + declareMapping(m); + declareMapping(m); + } + +} // namespace sofapython3 diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Mapping.h b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Mapping.h new file mode 100644 index 00000000..362c2039 --- /dev/null +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Mapping.h @@ -0,0 +1,61 @@ +/****************************************************************************** +* SofaPython3 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 +#include +#include +#include +#include + +namespace sofapython3 { +using sofa::core::ConstraintParams; +using sofa::core::MechanicalParams; + +template +class Mapping_Trampoline : public sofa::core::Mapping { +public: + SOFA_CLASS(SOFA_TEMPLATE2(Mapping_Trampoline, In, Out) , + SOFA_TEMPLATE2(sofa::core::Mapping, In, Out)); + using sofa::core::Mapping::getContext; + using typename sofa::core::Mapping::OutDataVecCoord; + using typename sofa::core::Mapping::OutDataVecDeriv; + using typename sofa::core::Mapping::InDataVecCoord; + using typename sofa::core::Mapping::InDataVecDeriv; + using typename sofa::core::Mapping::OutDataMatrixDeriv; + using typename sofa::core::Mapping::InDataMatrixDeriv; + + Mapping_Trampoline(); + ~Mapping_Trampoline() override; + + void init() override; + std::string getClassName() const override; + + void apply( const MechanicalParams* mparams, OutDataVecCoord& out, const InDataVecCoord& in) override; + void applyJ( const MechanicalParams* mparams, OutDataVecDeriv& out, const InDataVecDeriv& in) override; + void applyJT( const MechanicalParams* mparams, InDataVecDeriv& out, const OutDataVecDeriv& in) override; + void applyJT( const ConstraintParams* mparams, InDataMatrixDeriv& out, const OutDataMatrixDeriv& in) override; + +}; + +void moduleAddMapping(pybind11::module &m); + +} /// namespace sofapython3 diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Mapping_doc.h b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Mapping_doc.h new file mode 100644 index 00000000..ba5f6da0 --- /dev/null +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Mapping_doc.h @@ -0,0 +1,29 @@ +/****************************************************************************** +* SofaPython3 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 + +namespace sofapython3::doc::mapping +{ +static auto mappingClass = R"( + Overridable class to create your own customized mapping + )"; + +} diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/CMakeLists.txt b/bindings/Sofa/src/SofaPython3/Sofa/Core/CMakeLists.txt index d7ff4fd7..f88a0e21 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/CMakeLists.txt +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/CMakeLists.txt @@ -27,6 +27,8 @@ set(HEADER_FILES ${CMAKE_CURRENT_SOURCE_DIR}/Binding_Mass_doc.h ${CMAKE_CURRENT_SOURCE_DIR}/Binding_ObjectFactory.h ${CMAKE_CURRENT_SOURCE_DIR}/Binding_ObjectFactory_doc.h + ${CMAKE_CURRENT_SOURCE_DIR}/Binding_Mapping.h + ${CMAKE_CURRENT_SOURCE_DIR}/Binding_Mapping_doc.h ${CMAKE_CURRENT_SOURCE_DIR}/Binding_Node.h ${CMAKE_CURRENT_SOURCE_DIR}/Binding_Node_doc.h ${CMAKE_CURRENT_SOURCE_DIR}/Binding_NodeIterator.h @@ -71,6 +73,7 @@ set(SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/Binding_LinkPath.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Binding_Mass.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Binding_ObjectFactory.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/Binding_Mapping.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Binding_Node.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Binding_NodeIterator.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Binding_PointSetTopologyModifier.cpp diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Submodule_Core.cpp b/bindings/Sofa/src/SofaPython3/Sofa/Core/Submodule_Core.cpp index 249fedc1..844e4c2d 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/Submodule_Core.cpp +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Submodule_Core.cpp @@ -37,6 +37,7 @@ using sofa::helper::logging::Message; #include #include #include +#include #include #include #include @@ -140,11 +141,12 @@ PYBIND11_MODULE(Core, core) moduleAddController(core); moduleAddDataEngine(core); moduleAddForceField(core); - moduleAddMass(core); - moduleAddObjectFactory(core); moduleAddLinkPath(core); + moduleAddMapping(core); + moduleAddMass(core); moduleAddNode(core); moduleAddNodeIterator(core); + moduleAddObjectFactory(core); moduleAddPrefab(core); moduleAddBaseLink(core); moduleAddTopology(core); diff --git a/examples/example-mapping.py b/examples/example-mapping.py new file mode 100644 index 00000000..9fe6f423 --- /dev/null +++ b/examples/example-mapping.py @@ -0,0 +1,100 @@ +"""Implementation of an identity mapping in python""" +# coding: utf8 +import Sofa +import numpy as np + +class IdentityMapping(Sofa.Core.Mapping_Vec3d_Vec3d): + """Implementation of a Mapping in python""" + def __init__(self, *args, **kwargs): + + ## This is just an ugly trick to transform pointer to object into sofa linkpath. + for k,v in kwargs.items(): + if k == "input": + kwargs[k] = v.linkpath + elif k == "output": + kwargs[k] = v.linkpath + + Sofa.Core.Mapping_Vec3d_Vec3d.__init__(self, *args, **kwargs) + + # let's compute the positions initial delta. + input_position = kwargs["input"].target().position.value.copy() + output_position = kwargs["output"].target().position.value.copy() + self.delta = output_position - input_position + + def apply(self, m, outCoord, inCoord): + print("PYTHON(🐍) APPLY ", outCoord, inCoord) + with outCoord.writeableArray() as wa: + wa[:] = inCoord.value + self.delta + + def applyJ(self, m, outDeriv, inDeriv): + print("PYTHON(🐍) APPLY-J", outDeriv, inDeriv) + + def applyJT(self, m, outDeriv, inDeriv): + print("PYTHON(🐍) APPLY-JT", outDeriv, inDeriv) + + def applyConstrainsJT(self, m, outDeriv, inDeriv): + print("PYTHON(🐍) APPLY-JT for constraints, data are [⋱]", m, outDeriv, inDeriv) + print("Constraints ", inDeriv.value) + + + + +def createScene(root): + root.addObject("RequiredPlugin", pluginName=["Sofa.GL.Component", + "Sofa.Component.ODESolver.Backward", + "Sofa.Component.LinearSolver.Direct", + "Sofa.Component.LinearSolver.Iterative", + "Sofa.Component.Mass", + "Sofa.Component.StateContainer", + "Sofa.Component.Visual" + ]) + + root.addObject("LineAxis") + root.addObject("DefaultAnimationLoop", name="loop") + + root.addChild("Modelling") + + ######### OBJECT ##################### + o = root.Modelling.addChild("Object") + c = o.addObject("MechanicalObject", name="mechanical", position=[0.0,0.0,0.0, 1.0,0.0,0.0]) + c.showObject = True + c.showColor = [1.0,0.0,0.0,1.0] + c.drawMode = 1 + o.addObject("UniformMass", name="mass", totalMass=1.0) + + ######### OBJECT ##################### + m = o.addChild("MappedDof") + sm = m.addObject("MechanicalObject", name="mapped_quantities", position=[0.0,0.5,0.0, 1.0, 0.5, 0.0]) + sm.showObject = True + sm.showColor = [1.0,0.0,1.0,0.7] + sm.drawMode = 1 + m.addObject( IdentityMapping(name="CPPObject", input=c, output=sm ) ) + + root.addChild("Simulation") + root.Simulation.addObject("EulerImplicitSolver") + root.Simulation.addObject("CGLinearSolver", tolerance=1e-12, threshold=1e-12, iterations=25) + root.Simulation.addChild(root.Modelling) + + return root + + +def main(): + import SofaRuntime + import Sofa.Gui + import SofaQt + + root=Sofa.Core.Node("root") + createScene(root) + Sofa.Simulation.initRoot(root) + + Sofa.Gui.GUIManager.Init("myscene", "qglviewer") + Sofa.Gui.GUIManager.createGUI(root, __file__) + Sofa.Gui.GUIManager.SetDimension(1080, 1080) + Sofa.Gui.GUIManager.MainLoop(root) + Sofa.Gui.GUIManager.closeGUI() + + print("End of simulation.") + + +if __name__ == '__main__': + main() diff --git a/examples/example-mapping_3_to_1.py b/examples/example-mapping_3_to_1.py new file mode 100644 index 00000000..96c7b6d0 --- /dev/null +++ b/examples/example-mapping_3_to_1.py @@ -0,0 +1,100 @@ +"""Implementation of an identity mapping in python""" +# coding: utf8 +import Sofa +import numpy as np + +class MSELossMapping(Sofa.Core.Mapping_Vec3d_Vec1d): + """Implementation of a Mapping in python""" + def __init__(self, *args, **kwargs): + + ## This is just an ugly trick to transform pointer to object into sofa linkpath. + for k,v in kwargs.items(): + if k == "input": + kwargs[k] = v.linkpath + elif k == "output": + kwargs[k] = v.linkpath + + Sofa.Core.Mapping_Vec3d_Vec1d.__init__(self, *args, **kwargs) + + # let's compute the positions initial delta. + input_position = kwargs["input"].target().position.value.copy() + output_position = kwargs["output"].target().position.value.copy() + self.delta = output_position - input_position + + self.target = kwargs.get("target", kwargs["input"].target().rest_position) + + def apply(self, m, outCoord, inCoord): + print("PYTHON() APPLY ", outCoord, inCoord) + with outCoord.writeableArray() as wa: + wa[:] = 1.0 + + def applyJ(self, m, outDeriv, inDeriv): + print("PYTHON() APPLY-J", outDeriv, inDeriv) + + def applyJT(self, m, outDeriv, inDeriv): + print("PYTHON() APPLY-JT", outDeriv, inDeriv) + + def applyConstrainsJT(self, m, outDeriv, inDeriv): + print("PYTHON() APPLY-JT for constraints, data are [⋱]", m, outDeriv, inDeriv) + print("Constraints ", inDeriv.value) + + + + +def createScene(root): + root.addObject("RequiredPlugin", pluginName=["Sofa.GL.Component", + "Sofa.Component.ODESolver.Backward", + "Sofa.Component.LinearSolver.Direct", + "Sofa.Component.LinearSolver.Iterative", + "Sofa.Component.Mass", + "Sofa.Component.StateContainer", + "Sofa.Component.Visual" + ]) + + root.addObject("LineAxis") + root.addObject("DefaultAnimationLoop", name="loop") + + root.addChild("Modelling") + + ######### OBJECT ##################### + o = root.Modelling.addChild("Object") + c = o.addObject("MechanicalObject", name="mechanical", template="Vec3d", + position=[0.0,0.0,0.0, 1.0,0.0,0.0]) + c.showObject = True + c.showColor = [1.0,0.0,0.0,1.0] + c.drawMode = 1 + o.addObject("UniformMass", name="mass", totalMass=1.0) + + ######### OBJECT ##################### + m = o.addChild("MappedDof") + sm = m.addObject("MechanicalObject", name="mapped_quantities", template="Vec1d", position=[0.0]) + m.addObject( MSELossMapping(name="MSELossObject", input=c, output=sm, target=c ) ) + + root.addChild("Simulation") + root.Simulation.addObject("EulerImplicitSolver") + root.Simulation.addObject("CGLinearSolver", tolerance=1e-12, threshold=1e-12, iterations=25) + root.Simulation.addChild(root.Modelling) + + return root + + +def main(): + import SofaRuntime + import Sofa.Gui + import SofaQt + + root=Sofa.Core.Node("root") + createScene(root) + Sofa.Simulation.initRoot(root) + + Sofa.Gui.GUIManager.Init("myscene", "qglviewer") + Sofa.Gui.GUIManager.createGUI(root, __file__) + Sofa.Gui.GUIManager.SetDimension(1080, 1080) + Sofa.Gui.GUIManager.MainLoop(root) + Sofa.Gui.GUIManager.closeGUI() + + print("End of simulation.") + + +if __name__ == '__main__': + main()