diff --git a/CMakeLists.txt b/CMakeLists.txt index 327412de..34b404ed 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -171,6 +171,7 @@ if (NOT SP3_COMPILED_AS_SUBPROJECT) message(STATUS "SOFA Framework:\n\tVersion: ${SofaFramework_VERSION}\n\tLocation: ${SOFA_ROOT_DIR}") endif() +add_subdirectory(Lifetime) add_subdirectory(Plugin) add_subdirectory(bindings) add_subdirectory(examples) diff --git a/Lifetime/CMakeLists.txt b/Lifetime/CMakeLists.txt new file mode 100644 index 00000000..a4f15266 --- /dev/null +++ b/Lifetime/CMakeLists.txt @@ -0,0 +1,27 @@ +project(Lifetime VERSION 1.0) + +set(HEADER_FILES + ${CMAKE_CURRENT_SOURCE_DIR}/src/SofaPython3/lifetime/features.h +) + +set(SOURCE_FILES + ${CMAKE_CURRENT_SOURCE_DIR}/src/SofaPython3/lifetime/features.cpp +) + +find_package(SofaFramework REQUIRED) + +add_library(${PROJECT_NAME} SHARED ${HEADER_FILES} ${SOURCE_FILES}) +add_library(SofaPython3::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) + +target_link_libraries(${PROJECT_NAME} PUBLIC SofaCore) + +sofa_create_component_in_package_with_targets( + COMPONENT_NAME ${PROJECT_NAME} + COMPONENT_VERSION ${SofaPython3_VERSION} + PACKAGE_NAME SofaPython3 + TARGETS ${PROJECT_NAME} AUTO_SET_TARGET_PROPERTIES + INCLUDE_SOURCE_DIR "src" + INCLUDE_INSTALL_DIR "." + OPTIMIZE_BUILD_DIR FALSE + RELOCATABLE ".." + ) diff --git a/Lifetime/LifetimeConfig.cmake.in b/Lifetime/LifetimeConfig.cmake.in new file mode 100644 index 00000000..0641dc00 --- /dev/null +++ b/Lifetime/LifetimeConfig.cmake.in @@ -0,0 +1,22 @@ +# CMake package configuration file for the @PROJECT_NAME@ module +@PACKAGE_GUARD@ +@PACKAGE_INIT@ + +set(SP3_BUILD_TEST @SP3_BUILD_TEST@) + +find_package(pybind11 CONFIG REQUIRED) +find_package(SofaFramework REQUIRED) +find_package(SofaSimulationGraph REQUIRED) + +if(SP3_BUILD_TEST) + find_package(Sofa.Testing REQUIRED) +endif() + +# If we are importing this config file and the target is not yet there this is indicating that +# target is an imported one. So we include it +if(NOT TARGET @PROJECT_NAME@) + include("${CMAKE_CURRENT_LIST_DIR}/PluginTargets.cmake") +endif() + +# Check that the component/target is there. +check_required_components(@PROJECT_NAME@) diff --git a/Lifetime/src/SofaPython3/lifetime/features.cpp b/Lifetime/src/SofaPython3/lifetime/features.cpp new file mode 100644 index 00000000..7bd70792 --- /dev/null +++ b/Lifetime/src/SofaPython3/lifetime/features.cpp @@ -0,0 +1,62 @@ +/****************************************************************************** +* 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 + +namespace sofapython3::lifetime::features +{ + +std::map features; + +bool get(const std::string& name) +{ + auto f = features.find(name); + if(f == features.end()) + throw std::runtime_error("Missing attribute '"+name+"'"); + + return (f->second); +} + +void set(const std::string& name, bool value) +{ + auto f = features.find(name); + if(f == features.end()) + throw std::runtime_error("Missing attribute '"+name+"'"); + + (f->second) = value; +} + +void init(const std::string& name, bool value) +{ + features[name] = value; +} + + +std::vector list_features() +{ + std::vector v; + for(auto& it : features) + v.push_back(it.first); + return v; +} + +} //namespace sofapython3::futurefeatures diff --git a/Lifetime/src/SofaPython3/lifetime/features.h b/Lifetime/src/SofaPython3/lifetime/features.h new file mode 100644 index 00000000..58fe89dd --- /dev/null +++ b/Lifetime/src/SofaPython3/lifetime/features.h @@ -0,0 +1,43 @@ +/****************************************************************************** +* 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 + +namespace sofapython3::lifetime::features +{ + +/// Retrieve the value associated with the feature "name" +/// raise an exception if "name" is not existing. +SOFA_EXPORT_DYNAMIC_LIBRARY bool get(const std::string& name); + +/// Change the value associated with the feature "name" +/// raise an exception if "name" is not existing. +SOFA_EXPORT_DYNAMIC_LIBRARY void set(const std::string& name, bool value); + +/// Create and set a new value for feature with "name" +SOFA_EXPORT_DYNAMIC_LIBRARY void init(const std::string& name, bool value); + +/// Returns the list of registered features names +SOFA_EXPORT_DYNAMIC_LIBRARY std::vector list_features(); + +} ///namespace sofapython3::futurefeatures diff --git a/Plugin/src/SofaPython3/PythonEnvironment.cpp b/Plugin/src/SofaPython3/PythonEnvironment.cpp index 2b1fa920..9d42c189 100644 --- a/Plugin/src/SofaPython3/PythonEnvironment.cpp +++ b/Plugin/src/SofaPython3/PythonEnvironment.cpp @@ -233,9 +233,8 @@ void PythonEnvironment::Init() // Lastly, we (try to) add modules from the root of SOFA addPythonModulePathsFromDirectory( Utils::getSofaPathPrefix() ); - py::module::import("SofaRuntime"); getStaticData()->m_sofamodule = py::module::import("Sofa"); - + PyRun_SimpleString("import SofaRuntime"); // python livecoding related PyRun_SimpleString("from Sofa.livecoding import onReimpAFile"); @@ -243,7 +242,7 @@ void PythonEnvironment::Init() // general sofa-python stuff // python modules are automatically reloaded at each scene loading - //setAutomaticModuleReload( true ); + setAutomaticModuleReload( true ); // Initialize pluginLibraryPath by reading PluginManager's map std::map& map = PluginManager::getInstance().getPluginMap(); diff --git a/bindings/Sofa/CMakeLists.txt b/bindings/Sofa/CMakeLists.txt index 371c13d1..f733f632 100644 --- a/bindings/Sofa/CMakeLists.txt +++ b/bindings/Sofa/CMakeLists.txt @@ -1,6 +1,7 @@ project(Bindings.Sofa) set(SOFABINDINGS_MODULE_LIST + Lifetime Components Core Helper diff --git a/bindings/Sofa/package/__init__.py b/bindings/Sofa/package/__init__.py index 0281d230..30fcacb9 100644 --- a/bindings/Sofa/package/__init__.py +++ b/bindings/Sofa/package/__init__.py @@ -37,14 +37,15 @@ import Sofa.constants import Sofa.Helper import Sofa.Core +import Sofa.Lifetime import Sofa.Simulation import Sofa.Types import Sofa.Components import SofaTypes - from .prefab import * +from .lifetime import __feature__ -__all__=["constants", "Helper", "Core", "Simulation", "Types", "SofaTypes", "prefab"] +__all__=["constants", "Helper", "Core", "Simulation", "Types", "SofaTypes", "prefab", "future"] # Keep a list of the modules always imported in the Sofa-PythonEnvironment try: @@ -56,7 +57,6 @@ # e.g. plugin's modules defined from c++ __SofaPythonEnvironment_modulesExcludedFromReload = [] - def unloadModules(): """ call this function to unload python modules and to force their reload (useful to take into account their eventual modifications since diff --git a/bindings/Sofa/package/lifetime.py b/bindings/Sofa/package/lifetime.py new file mode 100644 index 00000000..facbe409 --- /dev/null +++ b/bindings/Sofa/package/lifetime.py @@ -0,0 +1,46 @@ +""" +Activate/deactive some feature of sofa python + +Use that to control how some part of the binding should behave. + +Usage: + from Sofa.Lifetime import __feature__ + + with __feature__("feature_name", True): + do_something() + + with __feature__("feature_name", False): + do_something() +""" +import Sofa.Lifetime +from contextlib import ContextDecorator + +### Initialize the feature set. +# Add your own feature by un-commenting the following line +#Sofa.Lifetime.init("my_feature", False) +Sofa.Lifetime.init("object_auto_init", False) + +def has_feature(name): + return Sofa.Lifetime.get(name) + +def list_features(): + return Sofa.Lifetime.list_features() + +class __feature__(ContextDecorator): + @staticmethod + def list_features(): + return self.Config.list_feature() + + def __init__(self, name, value): + self.name=name + self.new_value=value + self.old_value=None + + def __enter__(self): + self.old_value=Sofa.Config.get(self.name) + Sofa.Lifetime.set(self.name, self.new_value) + return self + + def __exit__(self, *exc): + Sofa.Lifetime.set(self.name, self.old_value) + return False diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Node.cpp b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Node.cpp index 6bee5700..9d70b9b3 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Node.cpp +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Node.cpp @@ -18,7 +18,6 @@ * Contact information: contact@sofa-framework.org * ******************************************************************************/ - /// Neede to have automatic conversion from pybind types to stl container. #include #include @@ -53,18 +52,21 @@ using sofapython3::PythonEnvironment; #include #include #include - using sofa::core::objectmodel::BaseObjectDescription; #include #include +#include + +#include + /// Makes an alias for the pybind11 namespace to increase readability. namespace py { using namespace pybind11; } using sofa::simulation::Node; - -namespace sofapython3 { +namespace sofapython3 +{ bool checkParamUsage(BaseObjectDescription& desc) { @@ -186,12 +188,18 @@ py::object getObject(Node &n, const std::string &name, const py::kwargs& kwargs) /// Implement the addObject function. py::object addObjectKwargs(Node* self, const std::string& type, const py::kwargs& kwargs) { + bool doInit = true; if (kwargs.contains("name")) { std::string name = py::str(kwargs["name"]); if (sofapython3::isProtectedKeyword(name)) throw py::value_error("Cannot call addObject with name " + name + ": Protected keyword"); } + if (kwargs.contains("__noInit")) + { + doInit = !py::bool_(kwargs["__noInit"]); + kwargs.attr("pop")("__noInit"); + } /// Prepare the description to hold the different python attributes as data field's /// arguments then create the object. BaseObjectDescription desc {type.c_str(), type.c_str()}; @@ -226,6 +234,11 @@ py::object addObjectKwargs(Node* self, const std::string& type, const py::kwargs if(d) d->setPersistent(true); } + + if(doInit && sofapython3::lifetime::features::get("object_auto_init")) + { + object->init(); + } return PythonFactory::toPython(object.get()); } diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/CMakeLists.txt b/bindings/Sofa/src/SofaPython3/Sofa/Core/CMakeLists.txt index 14b41b24..d9b28887 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/CMakeLists.txt +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/CMakeLists.txt @@ -85,5 +85,5 @@ SP3_add_python_module( DESTINATION Sofa SOURCES ${SOURCE_FILES} HEADERS ${HEADER_FILES} - DEPENDS SofaBaseUtils SofaBaseCollision SofaCore SofaHelper SofaSimulationCore SofaDefaultType SofaBaseVisual SofaPython3::Plugin + DEPENDS SofaBaseUtils SofaBaseCollision SofaCore SofaHelper SofaSimulationCore SofaDefaultType SofaBaseVisual SofaPython3::Lifetime SofaPython3::Plugin ) diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Lifetime/CMakeLists.txt b/bindings/Sofa/src/SofaPython3/Sofa/Lifetime/CMakeLists.txt new file mode 100644 index 00000000..bba64cec --- /dev/null +++ b/bindings/Sofa/src/SofaPython3/Sofa/Lifetime/CMakeLists.txt @@ -0,0 +1,26 @@ +project(Bindings.Sofa.Lifetime) + +set(SOURCE_FILES + ${CMAKE_CURRENT_SOURCE_DIR}/Submodule_Lifetime.cpp + ) + +if (NOT TARGET SofaPython3::Plugin) + find_package(SofaPython3 REQUIRED) +endif() + +find_package(Sofa.Config REQUIRED) +find_package(SofaFramework REQUIRED) +find_package(SofaBaseCollision REQUIRED) +find_package(SofaBaseVisual REQUIRED) +find_package(SofaBaseUtils REQUIRED) + +SP3_add_python_module( + TARGET ${PROJECT_NAME} + PACKAGE Bindings + MODULE Lifetime + DESTINATION Sofa + SOURCES ${SOURCE_FILES} + HEADERS ${HEADER_FILES} + DEPENDS Sofa.Config SofaBaseUtils SofaBaseCollision SofaCore SofaHelper SofaSimulationCore SofaDefaultType SofaBaseVisual SofaPython3::Lifetime SofaPython3::Plugin +) + diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Lifetime/Submodule_Lifetime.cpp b/bindings/Sofa/src/SofaPython3/Sofa/Lifetime/Submodule_Lifetime.cpp new file mode 100644 index 00000000..73093f0e --- /dev/null +++ b/bindings/Sofa/src/SofaPython3/Sofa/Lifetime/Submodule_Lifetime.cpp @@ -0,0 +1,46 @@ +/****************************************************************************** +* 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 + +namespace py { using namespace pybind11; } + +namespace sofapython3 +{ + +/// The first parameter must be named the same as the module file to load. +PYBIND11_MODULE(Lifetime, ffmodule) +{ + ffmodule.doc() = R"doc( + Control the the activation of new features + ------------------------------------------ + Sofa.Lifetime.object_auto_init = True + )doc"; + ffmodule.def("init", sofapython3::lifetime::features::init); + ffmodule.def("set", sofapython3::lifetime::features::set); + ffmodule.def("get", sofapython3::lifetime::features::get); + ffmodule.def("list_features", sofapython3::lifetime::features::list_features); +} + +} ///namespace sofapython3 diff --git a/bindings/Sofa/tests/CMakeLists.txt b/bindings/Sofa/tests/CMakeLists.txt index 9d06a9bc..a4013fe2 100644 --- a/bindings/Sofa/tests/CMakeLists.txt +++ b/bindings/Sofa/tests/CMakeLists.txt @@ -11,6 +11,7 @@ set(SOURCE_FILES set(PYTHON_FILES ${CMAKE_CURRENT_SOURCE_DIR}/Components/Components.py + ${CMAKE_CURRENT_SOURCE_DIR}/Lifetime/Lifetime.py ${CMAKE_CURRENT_SOURCE_DIR}/Core/BaseData.py ${CMAKE_CURRENT_SOURCE_DIR}/Core/Base.py ${CMAKE_CURRENT_SOURCE_DIR}/Core/BaseObject.py @@ -51,6 +52,7 @@ add_test(NAME ${PROJECT_NAME} COMMAND ${PROJECT_NAME}) set(DIR_BINDING_LIST Components + Lifetime Core Helper Simulation diff --git a/bindings/Sofa/tests/Lifetime/Lifetime.py b/bindings/Sofa/tests/Lifetime/Lifetime.py new file mode 100644 index 00000000..30dd12c3 --- /dev/null +++ b/bindings/Sofa/tests/Lifetime/Lifetime.py @@ -0,0 +1,15 @@ +# coding: utf8 + +import Sofa.Lifetime +from Sofa.Lifetime import __feature__ +import unittest + +class Test(unittest.TestCase): + def test_init_feature(self): + Sofa.Lifetime.init("new_feature", False) + self.assertFalse(Sofa.Lifetimeg.get("new_feature"), False) + + with __feature__("new_feature", True): + self.assertEquals(Sofa.Lifetime.get("new_feature"), True) + + self.assertFalse(Sofa.Lifetime.get("new_feature"), False) diff --git a/bindings/Sofa/tests/Simulation/Node.py b/bindings/Sofa/tests/Simulation/Node.py index 7f21c14a..6671cf46 100644 --- a/bindings/Sofa/tests/Simulation/Node.py +++ b/bindings/Sofa/tests/Simulation/Node.py @@ -4,6 +4,7 @@ import Sofa.Types import Sofa.Simulation import SofaRuntime +from Sofa.future import __enable_feature__ class MyController(Sofa.Core.Controller): """This is my custom controller @@ -42,14 +43,33 @@ def test_GetAttr(self): self.assertTrue(o is not None) self.assertTrue(root.child1.mechanical is not None) - def test_init(self): + def test_future_addObject_noInit(self): + """With __enable_feature__("object_auto_init") the MechanicalObject + does not sets its rest_position if __noInit it passed as an argument""" + with __enable_feature__("object_auto_init", True): + root = Sofa.Core.Node("rootNode") + root.addObject("RequiredPlugin", name="SofaBaseMechanics") + c = root.addObject("MechanicalObject", name="MO", position=[0.0,1.0,2.0]*100, __noInit=True) + self.assertEqual(len(c.rest_position.value), 0) + c.init() + self.assertEqual(len(c.rest_position.value), 100) + + def test_future_addObject_defaultInit(self): + """With __enable_feature__("object_auto_init") the MechanicalObject + does sets its its rest_position.""" + with Sofa.future.__enable_feature__("object_auto_init", True): + root = Sofa.Core.Node("rootNode") + root.addObject("RequiredPlugin", name="SofaBaseMechanics") + c = root.addObject("MechanicalObject", name="MO", position=[0.0,1.0,2.0]*100) + self.assertEqual(len(c.rest_position.value), 100) + + def test_addObject_defaultInit(self): + """Without __enable_feature__("object_auto_init") the MechanicalObject + does not sets its rest_position""" root = Sofa.Core.Node("rootNode") root.addObject("RequiredPlugin", name="SofaBaseMechanics") - c = root.addChild("child1") - c = c.addObject("MechanicalObject", name="MO", position=[0.0,1.0,2.0]*100) - root.init() - print("TYPE: "+str(len(c.position.value))) - self.assertEqual(len(c.position.value), 100) + c = root.addObject("MechanicalObject", name="MO", position=[0.0,1.0,2.0]*100) + self.assertEqual(len(c.rest_position.value), 0) def test_createObjectInvalid(self): root = Sofa.Core.Node("rootNode")