diff --git a/CMakeLists.txt b/CMakeLists.txt index 327412de..c0b74f4a 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(Lifecycle) add_subdirectory(Plugin) add_subdirectory(bindings) add_subdirectory(examples) diff --git a/Lifecycle/CMakeLists.txt b/Lifecycle/CMakeLists.txt new file mode 100644 index 00000000..4989c734 --- /dev/null +++ b/Lifecycle/CMakeLists.txt @@ -0,0 +1,27 @@ +project(Lifecycle VERSION 1.0) + +set(HEADER_FILES + ${CMAKE_CURRENT_SOURCE_DIR}/src/SofaPython3/lifecycle/features.h +) + +set(SOURCE_FILES + ${CMAKE_CURRENT_SOURCE_DIR}/src/SofaPython3/lifecycle/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/Lifecycle/LifecycleConfig.cmake.in b/Lifecycle/LifecycleConfig.cmake.in new file mode 100644 index 00000000..0641dc00 --- /dev/null +++ b/Lifecycle/LifecycleConfig.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/Lifecycle/src/SofaPython3/lifecycle/features.cpp b/Lifecycle/src/SofaPython3/lifecycle/features.cpp new file mode 100644 index 00000000..f7976f72 --- /dev/null +++ b/Lifecycle/src/SofaPython3/lifecycle/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::lifecycle::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/Lifecycle/src/SofaPython3/lifecycle/features.h b/Lifecycle/src/SofaPython3/lifecycle/features.h new file mode 100644 index 00000000..b59c7c6c --- /dev/null +++ b/Lifecycle/src/SofaPython3/lifecycle/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::lifecycle::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/bindings/Sofa/CMakeLists.txt b/bindings/Sofa/CMakeLists.txt index 371c13d1..23b185d8 100644 --- a/bindings/Sofa/CMakeLists.txt +++ b/bindings/Sofa/CMakeLists.txt @@ -1,6 +1,7 @@ project(Bindings.Sofa) set(SOFABINDINGS_MODULE_LIST + Lifecycle Components Core Helper diff --git a/bindings/Sofa/package/Lifecycle.py b/bindings/Sofa/package/Lifecycle.py new file mode 100644 index 00000000..5fe88ac8 --- /dev/null +++ b/bindings/Sofa/package/Lifecycle.py @@ -0,0 +1,44 @@ +""" +Activate/deactive some feature of sofa python + +Use that to control how some part of the binding should behave. + +Usage: + from Sofa.Lifecycle import __new_feature__ + + with __new_feature__("feature_name", True): + do_something() + + with __new_feature__("feature_name", False): + do_something() +""" +import Sofa.__Lifecycle__ +from contextlib import ContextDecorator + +### Initialize the feature set. +# Add your own feature by un-commenting the following line +Sofa.__Lifecycle__.init("my_feature", False) + +def register_feature(feature_name, default_state): + Sofa.__Lifecycle__.init(feature_name, default_state) + +def get_feature_status(name): + return Sofa.__Lifecycle__.get(name) + +def list_features(): + return Sofa.__Lifecycle__.list_features() + +class __new_feature__(ContextDecorator): + def __init__(self, name, value): + self.name=name + self.new_value=value + self.old_value=None + + def __enter__(self): + self.old_value=Sofa.__Lifecycle__.get(self.name) + Sofa.__Lifecycle__.set(self.name, self.new_value) + return self + + def __exit__(self, *exc): + Sofa.__Lifecycle__.set(self.name, self.old_value) + return False diff --git a/bindings/Sofa/package/__init__.py b/bindings/Sofa/package/__init__.py index 0281d230..fdcc20ce 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.Lifecycle import Sofa.Simulation import Sofa.Types import Sofa.Components import SofaTypes - from .prefab import * +from .Lifecycle import __new_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 @@ -109,7 +109,6 @@ def sendMessageFromException(e): exc_type, exc_value, exc_tb = sys.exc_info() sofaExceptHandler(exc_type, exc_value, exc_tb) - def sofaFormatHandler(type, value, tb): global oldexcepthook """This exception handler, convert python exceptions & traceback into more classical sofa error messages of the form: diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/CMakeLists.txt b/bindings/Sofa/src/SofaPython3/Sofa/Core/CMakeLists.txt index 2019180d..584faae7 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 Sofa.Core Sofa.Helper Sofa.SimulationCore Sofa.DefaultType SofaBaseVisual SofaPython3::Plugin + DEPENDS SofaBaseUtils SofaBaseCollision Sofa.Core Sofa.Helper Sofa.SimulationCore Sofa.DefaultType SofaBaseVisual SofaPython3::Lifecycle SofaPython3::Plugin ) diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Lifecycle/CMakeLists.txt b/bindings/Sofa/src/SofaPython3/Sofa/Lifecycle/CMakeLists.txt new file mode 100644 index 00000000..5185584d --- /dev/null +++ b/bindings/Sofa/src/SofaPython3/Sofa/Lifecycle/CMakeLists.txt @@ -0,0 +1,26 @@ +project(Bindings.Sofa.Lifecycle) + +set(SOURCE_FILES + ${CMAKE_CURRENT_SOURCE_DIR}/Submodule_Lifecycle.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 __Lifecycle__ + DESTINATION Sofa + SOURCES ${SOURCE_FILES} + HEADERS ${HEADER_FILES} + DEPENDS Sofa.Config SofaBaseUtils SofaBaseCollision SofaCore SofaHelper SofaSimulationCore SofaDefaultType SofaBaseVisual SofaPython3::Lifecycle SofaPython3::Plugin +) + diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Lifecycle/Submodule_Lifecycle.cpp b/bindings/Sofa/src/SofaPython3/Sofa/Lifecycle/Submodule_Lifecycle.cpp new file mode 100644 index 00000000..d2605c79 --- /dev/null +++ b/bindings/Sofa/src/SofaPython3/Sofa/Lifecycle/Submodule_Lifecycle.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(__Lifecycle__, ffmodule) +{ + ffmodule.doc() = R"doc( + Control the the activation of new features + ------------------------------------------ + Sofa.__Lifecycle__.object_auto_init = True + )doc"; + ffmodule.def("init", sofapython3::lifecycle::features::init); + ffmodule.def("set", sofapython3::lifecycle::features::set); + ffmodule.def("get", sofapython3::lifecycle::features::get); + ffmodule.def("list_features", sofapython3::lifecycle::features::list_features); +} + +} ///namespace sofapython3 diff --git a/bindings/Sofa/tests/CMakeLists.txt b/bindings/Sofa/tests/CMakeLists.txt index 5bacc9b3..35b5f770 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}/Lifecycle/Lifecycle.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 + Lifecycle Core Helper Simulation diff --git a/bindings/Sofa/tests/Lifecycle/Lifecycle.py b/bindings/Sofa/tests/Lifecycle/Lifecycle.py new file mode 100644 index 00000000..2492f8c8 --- /dev/null +++ b/bindings/Sofa/tests/Lifecycle/Lifecycle.py @@ -0,0 +1,15 @@ +# coding: utf8 + +import Sofa +from Sofa.Lifecycle import __new_feature__ +import unittest + +class Test(unittest.TestCase): + def test_init_feature(self): + Sofa.Lifecycle.register_feature("new_feature", False) + self.assertFalse(Sofa.Lifecycle.get_feature_status("new_feature"), False) + + with __new_feature__("new_feature", True): + self.assertEquals(Sofa.Lifecycle.get_feature_status("new_feature"), True) + + self.assertFalse(Sofa.Lifecycle.get_feature_status("new_feature"), False) diff --git a/bindings/Sofa/tests/PythonModule_Sofa_test.cpp b/bindings/Sofa/tests/PythonModule_Sofa_test.cpp index e8c35d6b..167a4fc8 100644 --- a/bindings/Sofa/tests/PythonModule_Sofa_test.cpp +++ b/bindings/Sofa/tests/PythonModule_Sofa_test.cpp @@ -54,11 +54,12 @@ static struct PythonModule_Sofa_tests : public PythonTestExtractor PythonModule_Sofa_tests() { const std::string executable_directory = sofa::helper::Utils::getExecutableDirectory(); + addTestDirectory(executable_directory+"/Bindings.Sofa.Tests.d/Components", "Sofa_Components_"); addTestDirectory(executable_directory+"/Bindings.Sofa.Tests.d/Core", "Sofa_Core_"); addTestDirectory(executable_directory+"/Bindings.Sofa.Tests.d/Helper", "Sofa_Helper_"); + addTestDirectory(executable_directory+"/Bindings.Sofa.Tests.d/Lifecycle", "Sofa_Core_"); addTestDirectory(executable_directory+"/Bindings.Sofa.Tests.d/Simulation", "Sofa_Simulation_"); addTestDirectory(executable_directory+"/Bindings.Sofa.Tests.d/Types", "Sofa_Types_"); - addTestDirectory(executable_directory+"/Bindings.Sofa.Tests.d/Components", "Sofa_Components_"); } } python_tests;