From 4867b24a357ebbc92ccbf41bb1e5f27c19d8f61f Mon Sep 17 00:00:00 2001 From: Lucas-TJ Date: Tue, 12 May 2026 10:00:49 +0200 Subject: [PATCH 1/7] bindings snapshot + python scripts --- .../SofaPython3/Sofa/Core/Binding_Node.cpp | 51 ++++++ .../Sofa/Core/Binding_Snapshot.cpp | 47 ++++++ .../SofaPython3/Sofa/Core/Binding_Snapshot.h | 46 ++++++ .../src/SofaPython3/Sofa/Core/CMakeLists.txt | 2 + .../SofaPython3/Sofa/Core/Submodule_Core.cpp | 2 + bindings/Sofa/tests/CMakeLists.txt | 1 + examples/BasicPendulum.py | 93 +++++++++++ examples/CMakeLists.txt | 2 + examples/LoaderScene_snapshot.py | 121 ++++++++++++++ examples/Snapshot_test.py | 62 +++++++ examples/analyselog.py | 84 ++++++++++ examples/testRunDirectory.py | 156 ++++++++++++++++++ 12 files changed, 667 insertions(+) create mode 100644 bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Snapshot.cpp create mode 100644 bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Snapshot.h create mode 100644 examples/BasicPendulum.py create mode 100644 examples/LoaderScene_snapshot.py create mode 100644 examples/Snapshot_test.py create mode 100644 examples/analyselog.py create mode 100644 examples/testRunDirectory.py diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Node.cpp b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Node.cpp index 85d0de2bf..bd33a1744 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Node.cpp +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Node.cpp @@ -20,17 +20,37 @@ /// Neede to have automatic conversion from pybind types to stl container. #include #include +#include #include #include #include #include + +#include "Binding_Snapshot.h" using sofa::core::objectmodel::BaseComponent; #include using sofa::core::objectmodel::BaseData; +#include +using sofa::core::objectmodel::Snapshot; + +#include + +#include +using sofa::simulation::SaveSnapshotVisitor; + +#include +using sofa::simulation::LoadDataSnapshotVisitor; + +#include +using sofa::simulation::LoadLinkSnapshotVisitor; + +#include +using sofapython3::Snapshot_Python; + #include namespace simpleapi = sofa::simpleapi; @@ -660,6 +680,27 @@ void sendEvent(Node* self, py::object pyUserData, char* eventName) self->propagateEvent(sofa::core::execparams::defaultInstance(), &event); } +void executeSaveSnapshotVisitor(Node* self, Snapshot_Python& snapshot) +{ + auto m_snapshot = std::make_shared(); + + auto visitor = SaveSnapshotVisitor(nullptr,*m_snapshot); + self->execute(visitor); + + snapshot.push_back(m_snapshot); + +} + +void executeLoadSnapshotVisitor(Node* self, Snapshot_Python& snapshot, sofa::Index index) +{ + auto m_loadedsnapshot = std::make_shared(); + + auto visitor = LoadDataSnapshotVisitor(nullptr,*snapshot.m_snapshots[index]); + self->execute(visitor); + auto linkvisitor = LoadLinkSnapshotVisitor(nullptr, *snapshot.m_snapshots[index]); + self->execute(linkvisitor); +} + py::object computeEnergy(Node* self) { sofa::simulation::mechanicalvisitor::MechanicalComputeEnergyVisitor energyVisitor(sofa::core::mechanicalparams::defaultInstance()); @@ -726,6 +767,16 @@ void moduleAddNode(py::module &m) { p.def("sendEvent", &sendEvent, sofapython3::doc::sofa::core::Node::sendEvent); p.def("computeEnergy", &computeEnergy, sofapython3::doc::sofa::core::Node::computeEnergy); + p.def("saveSnapshot", + [](Node& self, std::vector>& nodes) + { + Base& base = self; // cast explicite + return base.saveSnapshot(nodes); + }); + + p.def("executeSaveSnapshotVisitor", &executeSaveSnapshotVisitor); + p.def("executeLoadSnapshotVisitor", &executeLoadSnapshotVisitor); + p.def("__enter__", [](py::object self) { if(pybind11::hasattr(self, "onCtxEnter")) diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Snapshot.cpp b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Snapshot.cpp new file mode 100644 index 000000000..f5263e131 --- /dev/null +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Snapshot.cpp @@ -0,0 +1,47 @@ +/****************************************************************************** +* SOFA, Simulation Open-Framework Architecture * +* (c) 2021 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 . * +******************************************************************************* +* Contact information: contact@sofa-framework.org * +******************************************************************************/ + +#include "Binding_Snapshot.h" + +#include +#include + +namespace py { using namespace pybind11; } + + +#include + +#include + +using sofa::core::objectmodel::Snapshot; + +namespace sofapython3 { + + void moduleAddSnapshot(py::module &m) + { + py::class_(m, "Snapshot_Python") + .def(py::init<>()) + .def("getNumberOfSnapshotStr",&Snapshot_Python::getNumberOfSnapshotStr) + .def("getNumberOfSnapshot",&Snapshot_Python::getNumberOfSnapshot) + .def("push_back", &sofapython3::Snapshot_Python::push_back) + .def("printSnapshots", &Snapshot_Python::printSnapshots) + .def_readwrite("m_snapshots", &Snapshot_Python::m_snapshots); + } +} \ No newline at end of file diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Snapshot.h b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Snapshot.h new file mode 100644 index 000000000..bb362a496 --- /dev/null +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Snapshot.h @@ -0,0 +1,46 @@ +/****************************************************************************** +* SOFA, Simulation Open-Framework Architecture * +* (c) 2021 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 . * +******************************************************************************* +* Contact information: contact@sofa-framework.org * +******************************************************************************/ + +#pragma once + +#include +#include +using sofa::core::objectmodel::Snapshot; + + +namespace sofapython3 { +class Snapshot_Python +{ +public : + std::vector> m_snapshots; + + Snapshot_Python() = default; + ~Snapshot_Python() = default; + + void push_back(const std::shared_ptr& snapshot) { + m_snapshots.push_back(snapshot); + } + +}; + +void moduleAddSnapshot(pybind11::module &m); + +} /// namespace sofapython3 + diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/CMakeLists.txt b/bindings/Sofa/src/SofaPython3/Sofa/Core/CMakeLists.txt index 5b096833b..546d3ee68 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/CMakeLists.txt +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/CMakeLists.txt @@ -51,6 +51,7 @@ set(HEADER_FILES ${CMAKE_CURRENT_SOURCE_DIR}/Binding_BaseCamera_doc.h ${CMAKE_CURRENT_SOURCE_DIR}/Binding_Topology.h ${CMAKE_CURRENT_SOURCE_DIR}/Binding_BaseMeshTopology.h + ${CMAKE_CURRENT_SOURCE_DIR}/Binding_Snapshot.h ${CMAKE_CURRENT_SOURCE_DIR}/Binding_TaskScheduler.h ${CMAKE_CURRENT_SOURCE_DIR}/Binding_TaskScheduler_doc.h ${CMAKE_CURRENT_SOURCE_DIR}/Binding_VisualParams.h @@ -87,6 +88,7 @@ set(SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/Binding_BaseLink.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Binding_Topology.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Binding_BaseMeshTopology.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/Binding_Snapshot.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Binding_TaskScheduler.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Binding_VisualParams.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Submodule_Core.cpp diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Submodule_Core.cpp b/bindings/Sofa/src/SofaPython3/Sofa/Core/Submodule_Core.cpp index 23d0c89ab..496a9379e 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/Submodule_Core.cpp +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Submodule_Core.cpp @@ -43,6 +43,7 @@ using sofa::helper::logging::Message; #include #include #include +#include #include #include #include @@ -157,6 +158,7 @@ PYBIND11_MODULE(Core, core) moduleAddBaseMeshTopology(core); moduleAddPointSetTopologyModifier(core); moduleAddTaskScheduler(core); + moduleAddSnapshot(core); moduleAddVisualParams(core); // called when the module is unloaded diff --git a/bindings/Sofa/tests/CMakeLists.txt b/bindings/Sofa/tests/CMakeLists.txt index f4dde4270..a8c5c4235 100644 --- a/bindings/Sofa/tests/CMakeLists.txt +++ b/bindings/Sofa/tests/CMakeLists.txt @@ -21,6 +21,7 @@ set(PYTHON_FILES ${CMAKE_CURRENT_SOURCE_DIR}/Core/ForceField.py ${CMAKE_CURRENT_SOURCE_DIR}/Core/Mass.py ${CMAKE_CURRENT_SOURCE_DIR}/Core/MyRestShapeForceField.py + ${CMAKE_CURRENT_SOURCE_DIR}/Core/Snapshot.py ${CMAKE_CURRENT_SOURCE_DIR}/Helper/Message.py ${CMAKE_CURRENT_SOURCE_DIR}/Helper/Version.py ${CMAKE_CURRENT_SOURCE_DIR}/Simulation/Node.py diff --git a/examples/BasicPendulum.py b/examples/BasicPendulum.py new file mode 100644 index 000000000..f801354f2 --- /dev/null +++ b/examples/BasicPendulum.py @@ -0,0 +1,93 @@ +import Sofa + +def main(): + # Make sure to load all necessary libraries + import SofaRuntime + # import Sofa.Gui + + SofaRuntime.importPlugin("Sofa.Component.StateContainer") + # SofaRuntime.importPlugin("SofaImGui") + + # Call the above function to create the scene graph + root = Sofa.Core.Node("root") + createScene(root) + + # Once defined, initialization of the scene graph + Sofa.Simulation.initRoot(root) + + # # Use GUI + # Sofa.Gui.GUIManager.Init("myscene", "imgui") + # Sofa.Gui.GUIManager.createGUI(root, __file__) + # Sofa.Gui.GUIManager.SetDimension(1080, 800) + # + # Sofa.Gui.GUIManager.MainLoop(root) + # Sofa.Gui.GUIManager.closeGUI() + + # print("The simulation is done but...") + # print("time is = "+ str(root.time.value)) + + # Create a snapshot container (list of string) + snapshot_container = [] + + snapshot = Sofa.Core.Snapshot_Python() + + # for iteration in range(5): + # print(f'Iteration #{iteration}') + # Sofa.Simulation.animate(root, root.dt.value) + # snapshot_string = root.executeSaveSnapshotVisitor(snapshot) + # # print("Snapshot n°",iteration) + # # print(snapshot_string) + # snapshot_container.append(snapshot_string) + # print("Simulation made 10 time steps. Done") + + print("time is = "+ str(root.time.value)) + Sofa.Simulation.animate(root, root.dt.value) + + print("Save a snapshot") + snapshot_string = root.executeSaveSnapshotVisitor(snapshot) + print("save done") + + print("time is = "+ str(root.time.value)) + Sofa.Simulation.animate(root, root.dt.value) + print("time is = "+ str(root.time.value)) + print("Load a snapshot") + root.executeLoadSnapshotVisitor(snapshot,0) + print("load done") + + print("time is = "+ str(root.time.value)) + + +# Function called when the scene graph is being created +def createScene(root): + root.gravity=[0, 0, 0] + root.dt=0.1 + + root.addObject("RequiredPlugin", pluginName=[ 'Sofa.Component.Collision.Geometry', + 'Sofa.Component.Constraint.Projective', + 'Sofa.Component.LinearSolver.Iterative', + 'Sofa.Component.Mass', + 'Sofa.Component.ODESolver.Backward', + 'Sofa.Component.SolidMechanics.Spring', + 'Sofa.Component.StateContainer', + 'Sofa.Component.Visual' + ]) + root.addObject('DefaultAnimationLoop') + root.addObject('VisualStyle', displayFlags="showBehavior showCollisionModels") + + root.addObject('EulerImplicitSolver', name="EulerImplicit", rayleighStiffness="0.1", rayleighMass="0.1" ) + root.addObject('CGLinearSolver', name="CGSolver", iterations="25", tolerance="1e-5", threshold="1e-5") + root.addObject('InteractiveCamera') + root.addObject('MechanicalObject', name="Particles", template="Vec3", + position="0 0 0 0 0 1", + velocity="0 0 0 0 1 0") + root.addObject('UniformMass', name="Mass", totalMass="1") + root.addObject('FixedProjectiveConstraint', indices="0") + root.addObject('SpringForceField', name="Springs", stiffness="100", damping="1", spring="0 1 10 1 1") + root.addObject('SphereCollisionModel', radius="0.1") + + return root + + +# Function used only if this script is called from a python environment +if __name__ == '__main__': + main() diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index f2783d789..e089fae28 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -19,10 +19,12 @@ set(EXAMPLES_FILES ${CMAKE_CURRENT_SOURCE_DIR}/keyEvents.py ${CMAKE_CURRENT_SOURCE_DIR}/liver.py ${CMAKE_CURRENT_SOURCE_DIR}/liver-scriptcontroller.py + ${CMAKE_CURRENT_SOURCE_DIR}/LoaderScene_snapshot.py ${CMAKE_CURRENT_SOURCE_DIR}/loadXMLfromPython.py ${CMAKE_CURRENT_SOURCE_DIR}/pointSetTopologyModifier.py ${CMAKE_CURRENT_SOURCE_DIR}/ReadTheDocs_Example.py ${CMAKE_CURRENT_SOURCE_DIR}/springForceField.py + ${CMAKE_CURRENT_SOURCE_DIR}/Snapshot_test.py ) diff --git a/examples/LoaderScene_snapshot.py b/examples/LoaderScene_snapshot.py new file mode 100644 index 000000000..34194a6df --- /dev/null +++ b/examples/LoaderScene_snapshot.py @@ -0,0 +1,121 @@ +import Sofa +import SofaRuntime +import Sofa.Simulation +import importlib.util +import sys +import os + +def load_scene_module(scene_path): #Function to load a scene from a path (.py) + module_name = os.path.splitext(os.path.basename(scene_path))[0] + + spec = importlib.util.spec_from_file_location(module_name, scene_path) + + if spec is None or spec.loader is None: + raise RuntimeError(f"Cannot load Python module: {scene_path}") + + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + + return module + +def run_loaded_scene(root): + Sofa.Simulation.initRoot(root) + + print("Initial time is = " + str(root.time.value)) + + snapshot = Sofa.Core.Snapshot_Python() + + Sofa.Simulation.animate(root, root.dt.value) + print("After first animate, time is = " + str(root.time.value)) + + print("Save a snapshot") + root.executeSaveSnapshotVisitor(snapshot) + print("=======> save done") + + Sofa.Simulation.animate(root, root.dt.value) + print("After second animate, time is = " + str(root.time.value)) + + print("Load a snapshot") + root.executeLoadSnapshotVisitor(snapshot, snapshot.getNumberOfSnapshot() - 1) + print("=======> load done") + + print("After load, time is = " + str(root.time.value)) + + Sofa.Simulation.animate(root, root.dt.value) + print("After final animate, time is = " + str(root.time.value)) + + print("Simulation done") + +def run_scn_scene(scene_file): + SofaRuntime.importPlugin("Sofa.Component.StateContainer") + + scene_file = os.path.abspath(scene_file) + + if not os.path.isfile(scene_file): + raise FileNotFoundError(f"Scene file not found: {scene_file}") + + + print("Loading .scn scene in Python:") + print(scene_file) + + root = Sofa.Simulation.load(scene_file) + + # if root is None: + # raise RuntimeError(f"Cannot load scene: {scene_file}") + + run_loaded_scene(root) + +def main(scene_file): + import SofaRuntime + import Sofa.Gui + + ext = os.path.splitext(scene_file)[1] + + if ext == ".py" : + + SofaRuntime.importPlugin("Sofa.Component.StateContainer") + SofaRuntime.importPlugin("SofaImGui") + + scene_module = load_scene_module(scene_file) + + root = Sofa.Core.Node("root") + + scene_module.createScene(root) + + Sofa.Simulation.initRoot(root) + + print("time is = "+ str(root.time.value)) + Sofa.Simulation.animate(root, root.dt.value) + + snapshot = Sofa.Core.Snapshot_Python() + print("Save a snapshot") + root.executeSaveSnapshotVisitor(snapshot) + print("=======>save done") + + print("time is = "+ str(root.time.value)) + Sofa.Simulation.animate(root, root.dt.value) + print("time is = "+ str(root.time.value)) + print("Load a snapshot") + root.executeLoadSnapshotVisitor(snapshot,0) + print("=======>load done") + + print("time is = "+ str(root.time.value)) + + print("Simulation done") + + elif ext ==".scn": + print(scene_file) + run_scn_scene(scene_file) + + + else: + print("Unsupported file") + + + +if __name__ == '__main__': + if len(sys.argv) < 2: + print("no file") + else: + scene_path = sys.argv[1] + main(scene_path) \ No newline at end of file diff --git a/examples/Snapshot_test.py b/examples/Snapshot_test.py new file mode 100644 index 000000000..e8a7463d5 --- /dev/null +++ b/examples/Snapshot_test.py @@ -0,0 +1,62 @@ +import Sofa + +def main(): + # Make sure to load all necessary libraries + import SofaRuntime + + SofaRuntime.importPlugin("Sofa.Component.StateContainer") + + root = Sofa.Core.Node("root") + createScene(root) + + Sofa.Simulation.initRoot(root) + + snapshot = Sofa.Core.Snapshot_Python() + + print("time is = "+ str(root.time.value)) + Sofa.Simulation.animate(root, root.dt.value) + + print("Save a snapshot") + root.executeSaveSnapshotVisitor(snapshot) + print("save done") + + print("time is = "+ str(root.time.value)) + Sofa.Simulation.animate(root, root.dt.value) + print("time is = "+ str(root.time.value)) + print("Load a snapshot") + root.executeLoadSnapshotVisitor(snapshot,0) + print("load done") + + print("time is = "+ str(root.time.value)) + +def createScene(root): + root.gravity=[0, 0, 0] + root.dt=0.1 + root.addObject("RequiredPlugin", pluginName=[ 'Sofa.Component.Collision.Geometry', + 'Sofa.Component.Constraint.Projective', + 'Sofa.Component.LinearSolver.Iterative', + 'Sofa.Component.Mass', + 'Sofa.Component.ODESolver.Backward', + 'Sofa.Component.SolidMechanics.Spring', + 'Sofa.Component.StateContainer', + 'Sofa.Component.Visual' + ]) + root.addObject('DefaultAnimationLoop') + root.addObject('VisualStyle', displayFlags="showBehavior showCollisionModels") + + root.addObject('EulerImplicitSolver', name="EulerImplicit", rayleighStiffness="0.1", rayleighMass="0.1" ) + root.addObject('CGLinearSolver', name="CGSolver", iterations="25", tolerance="1e-5", threshold="1e-5") + root.addObject('InteractiveCamera') + root.addObject('MechanicalObject', name="Particles", template="Vec3", + position="0 0 0 0 0 1", + velocity="0 0 0 0 1 0") + root.addObject('UniformMass', name="Mass", totalMass="1") + root.addObject('FixedProjectiveConstraint', indices="0") + root.addObject('SpringForceField', name="Springs", stiffness="100", damping="1", spring="0 1 10 1 1") + root.addObject('SphereCollisionModel', radius="0.1") + + return root + +# Function used only if this script is called from a python environment +if __name__ == '__main__': + main() diff --git a/examples/analyselog.py b/examples/analyselog.py new file mode 100644 index 000000000..118adf3b1 --- /dev/null +++ b/examples/analyselog.py @@ -0,0 +1,84 @@ +import os +import re +from collections import defaultdict + +LOG_ROOT = "/home/lburel/logs/run_062" + + +def extract_stderr(content): + if "=== STDERR ===" not in content: + return "" + + part = content.split("=== STDERR ===", 1)[1] + + if "=== DURATION ===" in part: + part = part.split("=== DURATION ===", 1)[0] + + return part.strip() + + +def normalize_error(stderr): + lines = [line.strip() for line in stderr.splitlines() if line.strip()] + + # On garde les lignes les plus significatives + significant = [] + + for line in lines: + # Ignore les lignes "File ... line ..." + if line.startswith("File "): + continue + + # Remplace les chemins absolus + line = re.sub(r"/[^\s:]+", "", line) + + # Remplace les numéros + line = re.sub(r"\b\d+\b", "", line) + + significant.append(line) + + # Si possible, garder la dernière ligne (souvent le message principal) + if significant: + return significant[-1] + + return "Unknown error" + + +def main(): + groups = defaultdict(list) + + for filename in os.listdir(LOG_ROOT): + if not filename.endswith(".log"): + continue + + path = os.path.join(LOG_ROOT, filename) + + with open(path, "r", errors="ignore") as f: + content = f.read() + + stderr = extract_stderr(content) + + if not stderr.strip(): + continue + + signature = normalize_error(stderr) + groups[signature].append(filename) + + # Trier par fréquence décroissante + sorted_groups = sorted( + groups.items(), + key=lambda item: len(item[1]), + reverse=True + ) + + for signature, files in sorted_groups: + print("=" * 80) + print(f"{len(files)} occurrences") + print(f"Signature : {signature}") + print("Examples:") + for f in files[:10]: + print(" -", f) + print() + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/examples/testRunDirectory.py b/examples/testRunDirectory.py new file mode 100644 index 000000000..4c1fb6656 --- /dev/null +++ b/examples/testRunDirectory.py @@ -0,0 +1,156 @@ +import os +import subprocess +import traceback +from concurrent.futures import ThreadPoolExecutor, as_completed +import time + +def get_next_run_dir(base_dir="logs"): + os.makedirs(base_dir, exist_ok=True) + + existing = [ + d for d in os.listdir(base_dir) + if os.path.isdir(os.path.join(base_dir, d)) and d.startswith("run_") + ] + + indices = [] + for d in existing: + try: + indices.append(int(d.split("_")[1])) + except: + pass + + next_index = max(indices, default=0) + 1 + run_dir = os.path.join(base_dir, f"run_{next_index:03d}") + + os.makedirs(run_dir) + return run_dir + +# SCENE_DIR = "/home/lburel/Sofa/plugins/BeamAdapter/examples" +SCENE_DIR = "/home/lburel/Sofa/plugins/SofaPython3/examples" +# SCENE_DIR = "/home/lburel/Sofa/src/examples" +RUN_FEATURE = "/home/lburel/Sofa/plugins/SofaPython3/examples/LoaderScene_snapshot.py" +MAX_WORKERS = 16 +TIMEOUT = 30 +def find_scenes(root): + scenes = [] + for r, _, files in os.walk(root): + for f in files: + if f.endswith(".py") or f.endswith(".scn"): + scenes.append(os.path.join(r, f)) + return scenes + +def safe_name(path): + return path.replace("/", "_").replace(" ", "_") + +def run_scene(scene, log_dir): + start = time.time() + cmd = ["python3",RUN_FEATURE, scene] + + log_file = os.path.join(log_dir, safe_name(scene) + ".log") + + try: + result = subprocess.run( + cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + timeout=TIMEOUT + ) + + stdout = result.stdout + stderr = result.stderr + code = result.returncode + + python_trace = None + + except subprocess.TimeoutExpired as e: + stdout = e.stdout or b"" + stderr = e.stderr or b"" + code = -999 + python_trace = traceback.format_exc() + + except Exception as e: + stdout = b"" + stderr = str(e).encode() + code = -998 + python_trace = traceback.format_exc() + + duration = time.time() - start + # write the log after crash + with open(log_file, "wb") as f: + f.write(b"=== CMD ===\n") + f.write(" ".join(cmd).encode() + b"\n\n") + + f.write(b"=== RETURN CODE ===\n") + f.write(str(code).encode() + b"\n\n") + + f.write(b"=== STDOUT ===\n") + f.write(stdout + b"\n\n") + + f.write(b"=== STDERR ===\n") + f.write(stderr + b"\n") + + if python_trace: + f.write(b"=== PYTHON TRACEBACK ===\n") + f.write(python_trace.encode() + b"\n") + + f.write(b"=== DURATION ===\n") + f.write(f"{duration:.2f} seconds\n\n".encode()) + + # status + if code == 0: + status = "OK" + elif code < 0: + status = f"CRASH (signal {-code})" + else: + status = f"ERROR (code {code})" + + return scene, status, duration + +def main(): + start_time = time.time() + LOG_DIR = get_next_run_dir(base_dir="/home/lburel/logs") + print(f" Logs for this run: {os.path.abspath(LOG_DIR)}") + scenes = find_scenes(SCENE_DIR) + print(f"{len(scenes)} scenes found\n") + + results = [] + + if MAX_WORKERS == 1: + for scene in scenes: + print("▶", scene) + scene, status, duration = run_scene(scene, LOG_DIR) + print(" ", status) + results.append((scene, status)) + + else: + with ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor: + futures = {executor.submit(run_scene, s,LOG_DIR): s for s in scenes} + + for future in as_completed(futures): + scene, status, duration = future.result() + print(f"{status:20} | {scene}") + results.append((scene, status)) + + print("\n=== SUMMARY ===") + + stats = {} + for _, status in results: + stats[status] = stats.get(status, 0) + 1 + + for k, v in stats.items(): + print(f"{k:20} : {v}") + + fails = [s for s, st in results if st != "OK"] + + if fails: + print("\n FAILED SCENES:") + for f in fails: + print(" -", f) + + print(f"\n Logs saved in: {LOG_DIR}/") + end_time = time.time() + elapsed = end_time - start_time + print("\n Total execution time: {:.2f} seconds".format(elapsed) ) + +if __name__ == "__main__": + main() \ No newline at end of file From 3c1aaf2b667860f1075830b078af488c4a4b76fc Mon Sep 17 00:00:00 2001 From: Lucas-TJ Date: Tue, 12 May 2026 11:06:50 +0200 Subject: [PATCH 2/7] clean --- bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Snapshot.cpp | 3 --- bindings/Sofa/tests/CMakeLists.txt | 1 - 2 files changed, 4 deletions(-) diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Snapshot.cpp b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Snapshot.cpp index f5263e131..a5736abad 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Snapshot.cpp +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Snapshot.cpp @@ -38,10 +38,7 @@ namespace sofapython3 { { py::class_(m, "Snapshot_Python") .def(py::init<>()) - .def("getNumberOfSnapshotStr",&Snapshot_Python::getNumberOfSnapshotStr) - .def("getNumberOfSnapshot",&Snapshot_Python::getNumberOfSnapshot) .def("push_back", &sofapython3::Snapshot_Python::push_back) - .def("printSnapshots", &Snapshot_Python::printSnapshots) .def_readwrite("m_snapshots", &Snapshot_Python::m_snapshots); } } \ No newline at end of file diff --git a/bindings/Sofa/tests/CMakeLists.txt b/bindings/Sofa/tests/CMakeLists.txt index a8c5c4235..f4dde4270 100644 --- a/bindings/Sofa/tests/CMakeLists.txt +++ b/bindings/Sofa/tests/CMakeLists.txt @@ -21,7 +21,6 @@ set(PYTHON_FILES ${CMAKE_CURRENT_SOURCE_DIR}/Core/ForceField.py ${CMAKE_CURRENT_SOURCE_DIR}/Core/Mass.py ${CMAKE_CURRENT_SOURCE_DIR}/Core/MyRestShapeForceField.py - ${CMAKE_CURRENT_SOURCE_DIR}/Core/Snapshot.py ${CMAKE_CURRENT_SOURCE_DIR}/Helper/Message.py ${CMAKE_CURRENT_SOURCE_DIR}/Helper/Version.py ${CMAKE_CURRENT_SOURCE_DIR}/Simulation/Node.py From 7e8bd216b57be072be7c1f55b759ed6ef2029085 Mon Sep 17 00:00:00 2001 From: Lucas-TJ Date: Tue, 12 May 2026 11:55:52 +0200 Subject: [PATCH 3/7] clean again --- .../Sofa/src/SofaPython3/Sofa/Core/Binding_Node.cpp | 11 ++++------- .../src/SofaPython3/Sofa/Core/Binding_Snapshot.cpp | 1 + .../Sofa/src/SofaPython3/Sofa/Core/Binding_Snapshot.h | 4 ++++ examples/testRunDirectory.py | 4 ++-- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Node.cpp b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Node.cpp index bd33a1744..e3123b072 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Node.cpp +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Node.cpp @@ -42,11 +42,9 @@ using sofa::core::objectmodel::Snapshot; #include using sofa::simulation::SaveSnapshotVisitor; -#include -using sofa::simulation::LoadDataSnapshotVisitor; +#include +using sofa::simulation::LoadSnapshotVisitor; -#include -using sofa::simulation::LoadLinkSnapshotVisitor; #include using sofapython3::Snapshot_Python; @@ -695,10 +693,9 @@ void executeLoadSnapshotVisitor(Node* self, Snapshot_Python& snapshot, sofa::Ind { auto m_loadedsnapshot = std::make_shared(); - auto visitor = LoadDataSnapshotVisitor(nullptr,*snapshot.m_snapshots[index]); + auto visitor = LoadSnapshotVisitor(nullptr,*snapshot.m_snapshots[index]); self->execute(visitor); - auto linkvisitor = LoadLinkSnapshotVisitor(nullptr, *snapshot.m_snapshots[index]); - self->execute(linkvisitor); + } py::object computeEnergy(Node* self) diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Snapshot.cpp b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Snapshot.cpp index a5736abad..dab327df4 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Snapshot.cpp +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Snapshot.cpp @@ -39,6 +39,7 @@ namespace sofapython3 { py::class_(m, "Snapshot_Python") .def(py::init<>()) .def("push_back", &sofapython3::Snapshot_Python::push_back) + .def("getNumberOfSnapshot",&Snapshot_Python::getNumberOfSnapshot) .def_readwrite("m_snapshots", &Snapshot_Python::m_snapshots); } } \ No newline at end of file diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Snapshot.h b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Snapshot.h index bb362a496..a7ab1a793 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Snapshot.h +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Snapshot.h @@ -38,6 +38,10 @@ public : m_snapshots.push_back(snapshot); } + int getNumberOfSnapshot() const { + return m_snapshots.size(); + } + }; void moduleAddSnapshot(pybind11::module &m); diff --git a/examples/testRunDirectory.py b/examples/testRunDirectory.py index 4c1fb6656..27a8a90d5 100644 --- a/examples/testRunDirectory.py +++ b/examples/testRunDirectory.py @@ -26,8 +26,8 @@ def get_next_run_dir(base_dir="logs"): return run_dir # SCENE_DIR = "/home/lburel/Sofa/plugins/BeamAdapter/examples" -SCENE_DIR = "/home/lburel/Sofa/plugins/SofaPython3/examples" -# SCENE_DIR = "/home/lburel/Sofa/src/examples" +# SCENE_DIR = "/home/lburel/Sofa/plugins/SofaPython3/examples" +SCENE_DIR = "/home/lburel/Sofa/src/examples" RUN_FEATURE = "/home/lburel/Sofa/plugins/SofaPython3/examples/LoaderScene_snapshot.py" MAX_WORKERS = 16 TIMEOUT = 30 From 7ef803d34befed4cec025a9f3479ecd68cca61e4 Mon Sep 17 00:00:00 2001 From: Lucas-TJ Date: Thu, 21 May 2026 11:25:07 +0200 Subject: [PATCH 4/7] update --- examples/LoaderScene_snapshot.py | 4 + .../additional-examples/ControllerScene.py | 31 +++++-- examples/testRunDirectory.py | 86 ++++++++++--------- 3 files changed, 72 insertions(+), 49 deletions(-) diff --git a/examples/LoaderScene_snapshot.py b/examples/LoaderScene_snapshot.py index 34194a6df..1858821ed 100644 --- a/examples/LoaderScene_snapshot.py +++ b/examples/LoaderScene_snapshot.py @@ -107,6 +107,10 @@ def main(scene_file): print(scene_file) run_scn_scene(scene_file) + elif ext ==".xml": + print(scene_file) + run_scn_scene(scene_file) + else: print("Unsupported file") diff --git a/examples/additional-examples/ControllerScene.py b/examples/additional-examples/ControllerScene.py index cec5368c2..71a80f393 100755 --- a/examples/additional-examples/ControllerScene.py +++ b/examples/additional-examples/ControllerScene.py @@ -3,9 +3,10 @@ import Sofa.Core import Sofa.Simulation import SofaRuntime +import Sofa.Gui import os -_runAsPythonScript = False +_runAsPythonScript = True class RotationController(Sofa.Core.Controller): @@ -90,6 +91,12 @@ def createScene(root): root.name = 'root' root.gravity = [0.0, 9.8, 0.0] + root.addObject('RequiredPlugin', pluginName=['Sofa.Component.IO.Mesh', + 'Sofa.Component.Engine.Transform', + 'Sofa.Component.StateContainer', + 'Sofa.GL.Component.Rendering3D', + 'Sofa.Component.Mapping.Linear']) + loader = root.addObject('MeshOBJLoader', name='loader', filename="mesh/liver.obj") te = root.addObject("TransformEngine", name="te", @@ -108,8 +115,9 @@ def createScene(root): def main(): # Load the required plugins - SofaRuntime.importPlugin("Sofa.GL.Component") - SofaRuntime.importPlugin("Sofa.Component") + # SofaRuntime.importPlugin("Sofa.GL.Component") + # SofaRuntime.importPlugin("Sofa.Component") + SofaRuntime.importPlugin("SofaImGui") # Check and save if the script is called from python environment @@ -120,13 +128,20 @@ def main(): root = Sofa.Core.Node() createScene(root) Sofa.Simulation.initRoot(root) + Sofa.Gui.GUIManager.Init("myscene", "imgui") + Sofa.Gui.GUIManager.createGUI(root, __file__) + Sofa.Gui.GUIManager.SetDimension(1080, 800) - # Run simulation - for i in range(0, 360): - Sofa.Simulation.animate(root, root.dt.value) - root.te.rotation[0] += 1 + Sofa.Gui.GUIManager.MainLoop(root) + Sofa.Gui.GUIManager.closeGUI() - print("Last value is : "+ str(root.te.rotation.value[0])) + + # Run simulation + # for i in range(0, 360): + # Sofa.Simulation.animate(root, root.dt.value) + # root.te.rotation[0] += 1 + # + # print("Last value is : "+ str(root.te.rotation.value[0])) if __name__ == '__main__': diff --git a/examples/testRunDirectory.py b/examples/testRunDirectory.py index 27a8a90d5..ae3c49506 100644 --- a/examples/testRunDirectory.py +++ b/examples/testRunDirectory.py @@ -26,8 +26,16 @@ def get_next_run_dir(base_dir="logs"): return run_dir # SCENE_DIR = "/home/lburel/Sofa/plugins/BeamAdapter/examples" -# SCENE_DIR = "/home/lburel/Sofa/plugins/SofaPython3/examples" -SCENE_DIR = "/home/lburel/Sofa/src/examples" +SCENE_DIR = "/home/lburel/Sofa/plugins/SofaPython3/examples" +# SCENE_DIR = "/home/lburel/Sofa/src/examples" + +# SCENE_DIR = "/home/lburel/Sofa/src/examples/Benchmark" +# SCENE_DIR = "/home/lburel/Sofa/src/examples/Component" +# SCENE_DIR = "/home/lburel/Sofa/src/examples/Demos" +# SCENE_DIR = "/home/lburel/Sofa/src/examples/Objects" +# SCENE_DIR = "/home/lburel/Sofa/src/examples/Tutorials" +# SCENE_DIR = "/home/lburel/Sofa/src/examples/Validation" + RUN_FEATURE = "/home/lburel/Sofa/plugins/SofaPython3/examples/LoaderScene_snapshot.py" MAX_WORKERS = 16 TIMEOUT = 30 @@ -44,61 +52,57 @@ def safe_name(path): def run_scene(scene, log_dir): start = time.time() - cmd = ["python3",RUN_FEATURE, scene] + cmd = ["python3", RUN_FEATURE, scene] log_file = os.path.join(log_dir, safe_name(scene) + ".log") - try: - result = subprocess.run( - cmd, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - timeout=TIMEOUT - ) - - stdout = result.stdout - stderr = result.stderr - code = result.returncode - - python_trace = None - - except subprocess.TimeoutExpired as e: - stdout = e.stdout or b"" - stderr = e.stderr or b"" - code = -999 - python_trace = traceback.format_exc() - - except Exception as e: - stdout = b"" - stderr = str(e).encode() - code = -998 - python_trace = traceback.format_exc() - - duration = time.time() - start - # write the log after crash + python_trace = None + + # On ouvre le fichier log dès le début pour rediriger stdout/stderr with open(log_file, "wb") as f: + # Entête du log f.write(b"=== CMD ===\n") f.write(" ".join(cmd).encode() + b"\n\n") + f.flush() - f.write(b"=== RETURN CODE ===\n") + try: + # Redirection directe vers le fichier log + result = subprocess.run( + cmd, + stdout=f, + stderr=subprocess.STDOUT, + timeout=TIMEOUT + ) + code = result.returncode + + except subprocess.TimeoutExpired: + code = -999 + python_trace = traceback.format_exc() + + except Exception: + code = -998 + python_trace = traceback.format_exc() + + duration = time.time() - start + + # Footer du log + f.write(b"\n=== RETURN CODE ===\n") f.write(str(code).encode() + b"\n\n") - f.write(b"=== STDOUT ===\n") - f.write(stdout + b"\n\n") - - f.write(b"=== STDERR ===\n") - f.write(stderr + b"\n") - if python_trace: f.write(b"=== PYTHON TRACEBACK ===\n") - f.write(python_trace.encode() + b"\n") + f.write(python_trace.encode() + b"\n\n") f.write(b"=== DURATION ===\n") - f.write(f"{duration:.2f} seconds\n\n".encode()) + f.write(f"{duration:.2f} seconds\n".encode()) - # status + # Détermination du statut if code == 0: status = "OK" + elif code == -999: + status = "TIMEOUT" + elif code == -998: + status = "PYTHON ERROR" elif code < 0: status = f"CRASH (signal {-code})" else: From fab687f30e6009f00d80e47537bb1e5726706334 Mon Sep 17 00:00:00 2001 From: Lucas Burel Date: Tue, 9 Jun 2026 10:04:24 +0200 Subject: [PATCH 5/7] update 09/06 --- examples/testRunDirectory.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/examples/testRunDirectory.py b/examples/testRunDirectory.py index ae3c49506..a70c130d2 100644 --- a/examples/testRunDirectory.py +++ b/examples/testRunDirectory.py @@ -26,7 +26,7 @@ def get_next_run_dir(base_dir="logs"): return run_dir # SCENE_DIR = "/home/lburel/Sofa/plugins/BeamAdapter/examples" -SCENE_DIR = "/home/lburel/Sofa/plugins/SofaPython3/examples" +# SCENE_DIR = "/home/lburel/Sofa/plugins/SofaPython3/examples" # SCENE_DIR = "/home/lburel/Sofa/src/examples" # SCENE_DIR = "/home/lburel/Sofa/src/examples/Benchmark" @@ -36,7 +36,10 @@ def get_next_run_dir(base_dir="logs"): # SCENE_DIR = "/home/lburel/Sofa/src/examples/Tutorials" # SCENE_DIR = "/home/lburel/Sofa/src/examples/Validation" -RUN_FEATURE = "/home/lburel/Sofa/plugins/SofaPython3/examples/LoaderScene_snapshot.py" +SCENE_DIR = "/home/lucasbureltojo/~Sofa/src/examples" + +# RUN_FEATURE = "/home/lburel/Sofa/plugins/SofaPython3/examples/LoaderScene_snapshot.py" +RUN_FEATURE = "/home/lucasbureltojo/~Sofa/plugins/SofaPython3/examples/LoaderScene_snapshot.py" MAX_WORKERS = 16 TIMEOUT = 30 def find_scenes(root): @@ -112,7 +115,7 @@ def run_scene(scene, log_dir): def main(): start_time = time.time() - LOG_DIR = get_next_run_dir(base_dir="/home/lburel/logs") + LOG_DIR = get_next_run_dir(base_dir="/home/lucasbureltojo/logs") print(f" Logs for this run: {os.path.abspath(LOG_DIR)}") scenes = find_scenes(SCENE_DIR) print(f"{len(scenes)} scenes found\n") From f02e4157a10fbca3430913ea3d7c153b4e510377 Mon Sep 17 00:00:00 2001 From: Lucas-TJ Date: Wed, 15 Jul 2026 14:53:49 +0200 Subject: [PATCH 6/7] Restore file --- examples/BasicPendulum.py | 93 ---------- examples/CMakeLists.txt | 2 - examples/LoaderScene_snapshot.py | 125 -------------- examples/Snapshot_test.py | 62 ------- .../additional-examples/ControllerScene.py | 31 +--- examples/analyselog.py | 84 --------- examples/testRunDirectory.py | 163 ------------------ 7 files changed, 8 insertions(+), 552 deletions(-) delete mode 100644 examples/BasicPendulum.py delete mode 100644 examples/LoaderScene_snapshot.py delete mode 100644 examples/Snapshot_test.py delete mode 100644 examples/analyselog.py delete mode 100644 examples/testRunDirectory.py diff --git a/examples/BasicPendulum.py b/examples/BasicPendulum.py deleted file mode 100644 index f801354f2..000000000 --- a/examples/BasicPendulum.py +++ /dev/null @@ -1,93 +0,0 @@ -import Sofa - -def main(): - # Make sure to load all necessary libraries - import SofaRuntime - # import Sofa.Gui - - SofaRuntime.importPlugin("Sofa.Component.StateContainer") - # SofaRuntime.importPlugin("SofaImGui") - - # Call the above function to create the scene graph - root = Sofa.Core.Node("root") - createScene(root) - - # Once defined, initialization of the scene graph - Sofa.Simulation.initRoot(root) - - # # Use GUI - # Sofa.Gui.GUIManager.Init("myscene", "imgui") - # Sofa.Gui.GUIManager.createGUI(root, __file__) - # Sofa.Gui.GUIManager.SetDimension(1080, 800) - # - # Sofa.Gui.GUIManager.MainLoop(root) - # Sofa.Gui.GUIManager.closeGUI() - - # print("The simulation is done but...") - # print("time is = "+ str(root.time.value)) - - # Create a snapshot container (list of string) - snapshot_container = [] - - snapshot = Sofa.Core.Snapshot_Python() - - # for iteration in range(5): - # print(f'Iteration #{iteration}') - # Sofa.Simulation.animate(root, root.dt.value) - # snapshot_string = root.executeSaveSnapshotVisitor(snapshot) - # # print("Snapshot n°",iteration) - # # print(snapshot_string) - # snapshot_container.append(snapshot_string) - # print("Simulation made 10 time steps. Done") - - print("time is = "+ str(root.time.value)) - Sofa.Simulation.animate(root, root.dt.value) - - print("Save a snapshot") - snapshot_string = root.executeSaveSnapshotVisitor(snapshot) - print("save done") - - print("time is = "+ str(root.time.value)) - Sofa.Simulation.animate(root, root.dt.value) - print("time is = "+ str(root.time.value)) - print("Load a snapshot") - root.executeLoadSnapshotVisitor(snapshot,0) - print("load done") - - print("time is = "+ str(root.time.value)) - - -# Function called when the scene graph is being created -def createScene(root): - root.gravity=[0, 0, 0] - root.dt=0.1 - - root.addObject("RequiredPlugin", pluginName=[ 'Sofa.Component.Collision.Geometry', - 'Sofa.Component.Constraint.Projective', - 'Sofa.Component.LinearSolver.Iterative', - 'Sofa.Component.Mass', - 'Sofa.Component.ODESolver.Backward', - 'Sofa.Component.SolidMechanics.Spring', - 'Sofa.Component.StateContainer', - 'Sofa.Component.Visual' - ]) - root.addObject('DefaultAnimationLoop') - root.addObject('VisualStyle', displayFlags="showBehavior showCollisionModels") - - root.addObject('EulerImplicitSolver', name="EulerImplicit", rayleighStiffness="0.1", rayleighMass="0.1" ) - root.addObject('CGLinearSolver', name="CGSolver", iterations="25", tolerance="1e-5", threshold="1e-5") - root.addObject('InteractiveCamera') - root.addObject('MechanicalObject', name="Particles", template="Vec3", - position="0 0 0 0 0 1", - velocity="0 0 0 0 1 0") - root.addObject('UniformMass', name="Mass", totalMass="1") - root.addObject('FixedProjectiveConstraint', indices="0") - root.addObject('SpringForceField', name="Springs", stiffness="100", damping="1", spring="0 1 10 1 1") - root.addObject('SphereCollisionModel', radius="0.1") - - return root - - -# Function used only if this script is called from a python environment -if __name__ == '__main__': - main() diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index e089fae28..f2783d789 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -19,12 +19,10 @@ set(EXAMPLES_FILES ${CMAKE_CURRENT_SOURCE_DIR}/keyEvents.py ${CMAKE_CURRENT_SOURCE_DIR}/liver.py ${CMAKE_CURRENT_SOURCE_DIR}/liver-scriptcontroller.py - ${CMAKE_CURRENT_SOURCE_DIR}/LoaderScene_snapshot.py ${CMAKE_CURRENT_SOURCE_DIR}/loadXMLfromPython.py ${CMAKE_CURRENT_SOURCE_DIR}/pointSetTopologyModifier.py ${CMAKE_CURRENT_SOURCE_DIR}/ReadTheDocs_Example.py ${CMAKE_CURRENT_SOURCE_DIR}/springForceField.py - ${CMAKE_CURRENT_SOURCE_DIR}/Snapshot_test.py ) diff --git a/examples/LoaderScene_snapshot.py b/examples/LoaderScene_snapshot.py deleted file mode 100644 index 1858821ed..000000000 --- a/examples/LoaderScene_snapshot.py +++ /dev/null @@ -1,125 +0,0 @@ -import Sofa -import SofaRuntime -import Sofa.Simulation -import importlib.util -import sys -import os - -def load_scene_module(scene_path): #Function to load a scene from a path (.py) - module_name = os.path.splitext(os.path.basename(scene_path))[0] - - spec = importlib.util.spec_from_file_location(module_name, scene_path) - - if spec is None or spec.loader is None: - raise RuntimeError(f"Cannot load Python module: {scene_path}") - - module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(module) - - return module - -def run_loaded_scene(root): - Sofa.Simulation.initRoot(root) - - print("Initial time is = " + str(root.time.value)) - - snapshot = Sofa.Core.Snapshot_Python() - - Sofa.Simulation.animate(root, root.dt.value) - print("After first animate, time is = " + str(root.time.value)) - - print("Save a snapshot") - root.executeSaveSnapshotVisitor(snapshot) - print("=======> save done") - - Sofa.Simulation.animate(root, root.dt.value) - print("After second animate, time is = " + str(root.time.value)) - - print("Load a snapshot") - root.executeLoadSnapshotVisitor(snapshot, snapshot.getNumberOfSnapshot() - 1) - print("=======> load done") - - print("After load, time is = " + str(root.time.value)) - - Sofa.Simulation.animate(root, root.dt.value) - print("After final animate, time is = " + str(root.time.value)) - - print("Simulation done") - -def run_scn_scene(scene_file): - SofaRuntime.importPlugin("Sofa.Component.StateContainer") - - scene_file = os.path.abspath(scene_file) - - if not os.path.isfile(scene_file): - raise FileNotFoundError(f"Scene file not found: {scene_file}") - - - print("Loading .scn scene in Python:") - print(scene_file) - - root = Sofa.Simulation.load(scene_file) - - # if root is None: - # raise RuntimeError(f"Cannot load scene: {scene_file}") - - run_loaded_scene(root) - -def main(scene_file): - import SofaRuntime - import Sofa.Gui - - ext = os.path.splitext(scene_file)[1] - - if ext == ".py" : - - SofaRuntime.importPlugin("Sofa.Component.StateContainer") - SofaRuntime.importPlugin("SofaImGui") - - scene_module = load_scene_module(scene_file) - - root = Sofa.Core.Node("root") - - scene_module.createScene(root) - - Sofa.Simulation.initRoot(root) - - print("time is = "+ str(root.time.value)) - Sofa.Simulation.animate(root, root.dt.value) - - snapshot = Sofa.Core.Snapshot_Python() - print("Save a snapshot") - root.executeSaveSnapshotVisitor(snapshot) - print("=======>save done") - - print("time is = "+ str(root.time.value)) - Sofa.Simulation.animate(root, root.dt.value) - print("time is = "+ str(root.time.value)) - print("Load a snapshot") - root.executeLoadSnapshotVisitor(snapshot,0) - print("=======>load done") - - print("time is = "+ str(root.time.value)) - - print("Simulation done") - - elif ext ==".scn": - print(scene_file) - run_scn_scene(scene_file) - - elif ext ==".xml": - print(scene_file) - run_scn_scene(scene_file) - - - else: - print("Unsupported file") - - - -if __name__ == '__main__': - if len(sys.argv) < 2: - print("no file") - else: - scene_path = sys.argv[1] - main(scene_path) \ No newline at end of file diff --git a/examples/Snapshot_test.py b/examples/Snapshot_test.py deleted file mode 100644 index e8a7463d5..000000000 --- a/examples/Snapshot_test.py +++ /dev/null @@ -1,62 +0,0 @@ -import Sofa - -def main(): - # Make sure to load all necessary libraries - import SofaRuntime - - SofaRuntime.importPlugin("Sofa.Component.StateContainer") - - root = Sofa.Core.Node("root") - createScene(root) - - Sofa.Simulation.initRoot(root) - - snapshot = Sofa.Core.Snapshot_Python() - - print("time is = "+ str(root.time.value)) - Sofa.Simulation.animate(root, root.dt.value) - - print("Save a snapshot") - root.executeSaveSnapshotVisitor(snapshot) - print("save done") - - print("time is = "+ str(root.time.value)) - Sofa.Simulation.animate(root, root.dt.value) - print("time is = "+ str(root.time.value)) - print("Load a snapshot") - root.executeLoadSnapshotVisitor(snapshot,0) - print("load done") - - print("time is = "+ str(root.time.value)) - -def createScene(root): - root.gravity=[0, 0, 0] - root.dt=0.1 - root.addObject("RequiredPlugin", pluginName=[ 'Sofa.Component.Collision.Geometry', - 'Sofa.Component.Constraint.Projective', - 'Sofa.Component.LinearSolver.Iterative', - 'Sofa.Component.Mass', - 'Sofa.Component.ODESolver.Backward', - 'Sofa.Component.SolidMechanics.Spring', - 'Sofa.Component.StateContainer', - 'Sofa.Component.Visual' - ]) - root.addObject('DefaultAnimationLoop') - root.addObject('VisualStyle', displayFlags="showBehavior showCollisionModels") - - root.addObject('EulerImplicitSolver', name="EulerImplicit", rayleighStiffness="0.1", rayleighMass="0.1" ) - root.addObject('CGLinearSolver', name="CGSolver", iterations="25", tolerance="1e-5", threshold="1e-5") - root.addObject('InteractiveCamera') - root.addObject('MechanicalObject', name="Particles", template="Vec3", - position="0 0 0 0 0 1", - velocity="0 0 0 0 1 0") - root.addObject('UniformMass', name="Mass", totalMass="1") - root.addObject('FixedProjectiveConstraint', indices="0") - root.addObject('SpringForceField', name="Springs", stiffness="100", damping="1", spring="0 1 10 1 1") - root.addObject('SphereCollisionModel', radius="0.1") - - return root - -# Function used only if this script is called from a python environment -if __name__ == '__main__': - main() diff --git a/examples/additional-examples/ControllerScene.py b/examples/additional-examples/ControllerScene.py index 71a80f393..cec5368c2 100755 --- a/examples/additional-examples/ControllerScene.py +++ b/examples/additional-examples/ControllerScene.py @@ -3,10 +3,9 @@ import Sofa.Core import Sofa.Simulation import SofaRuntime -import Sofa.Gui import os -_runAsPythonScript = True +_runAsPythonScript = False class RotationController(Sofa.Core.Controller): @@ -91,12 +90,6 @@ def createScene(root): root.name = 'root' root.gravity = [0.0, 9.8, 0.0] - root.addObject('RequiredPlugin', pluginName=['Sofa.Component.IO.Mesh', - 'Sofa.Component.Engine.Transform', - 'Sofa.Component.StateContainer', - 'Sofa.GL.Component.Rendering3D', - 'Sofa.Component.Mapping.Linear']) - loader = root.addObject('MeshOBJLoader', name='loader', filename="mesh/liver.obj") te = root.addObject("TransformEngine", name="te", @@ -115,9 +108,8 @@ def createScene(root): def main(): # Load the required plugins - # SofaRuntime.importPlugin("Sofa.GL.Component") - # SofaRuntime.importPlugin("Sofa.Component") - SofaRuntime.importPlugin("SofaImGui") + SofaRuntime.importPlugin("Sofa.GL.Component") + SofaRuntime.importPlugin("Sofa.Component") # Check and save if the script is called from python environment @@ -128,20 +120,13 @@ def main(): root = Sofa.Core.Node() createScene(root) Sofa.Simulation.initRoot(root) - Sofa.Gui.GUIManager.Init("myscene", "imgui") - Sofa.Gui.GUIManager.createGUI(root, __file__) - Sofa.Gui.GUIManager.SetDimension(1080, 800) - - Sofa.Gui.GUIManager.MainLoop(root) - Sofa.Gui.GUIManager.closeGUI() - # Run simulation - # for i in range(0, 360): - # Sofa.Simulation.animate(root, root.dt.value) - # root.te.rotation[0] += 1 - # - # print("Last value is : "+ str(root.te.rotation.value[0])) + for i in range(0, 360): + Sofa.Simulation.animate(root, root.dt.value) + root.te.rotation[0] += 1 + + print("Last value is : "+ str(root.te.rotation.value[0])) if __name__ == '__main__': diff --git a/examples/analyselog.py b/examples/analyselog.py deleted file mode 100644 index 118adf3b1..000000000 --- a/examples/analyselog.py +++ /dev/null @@ -1,84 +0,0 @@ -import os -import re -from collections import defaultdict - -LOG_ROOT = "/home/lburel/logs/run_062" - - -def extract_stderr(content): - if "=== STDERR ===" not in content: - return "" - - part = content.split("=== STDERR ===", 1)[1] - - if "=== DURATION ===" in part: - part = part.split("=== DURATION ===", 1)[0] - - return part.strip() - - -def normalize_error(stderr): - lines = [line.strip() for line in stderr.splitlines() if line.strip()] - - # On garde les lignes les plus significatives - significant = [] - - for line in lines: - # Ignore les lignes "File ... line ..." - if line.startswith("File "): - continue - - # Remplace les chemins absolus - line = re.sub(r"/[^\s:]+", "", line) - - # Remplace les numéros - line = re.sub(r"\b\d+\b", "", line) - - significant.append(line) - - # Si possible, garder la dernière ligne (souvent le message principal) - if significant: - return significant[-1] - - return "Unknown error" - - -def main(): - groups = defaultdict(list) - - for filename in os.listdir(LOG_ROOT): - if not filename.endswith(".log"): - continue - - path = os.path.join(LOG_ROOT, filename) - - with open(path, "r", errors="ignore") as f: - content = f.read() - - stderr = extract_stderr(content) - - if not stderr.strip(): - continue - - signature = normalize_error(stderr) - groups[signature].append(filename) - - # Trier par fréquence décroissante - sorted_groups = sorted( - groups.items(), - key=lambda item: len(item[1]), - reverse=True - ) - - for signature, files in sorted_groups: - print("=" * 80) - print(f"{len(files)} occurrences") - print(f"Signature : {signature}") - print("Examples:") - for f in files[:10]: - print(" -", f) - print() - - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/examples/testRunDirectory.py b/examples/testRunDirectory.py deleted file mode 100644 index a70c130d2..000000000 --- a/examples/testRunDirectory.py +++ /dev/null @@ -1,163 +0,0 @@ -import os -import subprocess -import traceback -from concurrent.futures import ThreadPoolExecutor, as_completed -import time - -def get_next_run_dir(base_dir="logs"): - os.makedirs(base_dir, exist_ok=True) - - existing = [ - d for d in os.listdir(base_dir) - if os.path.isdir(os.path.join(base_dir, d)) and d.startswith("run_") - ] - - indices = [] - for d in existing: - try: - indices.append(int(d.split("_")[1])) - except: - pass - - next_index = max(indices, default=0) + 1 - run_dir = os.path.join(base_dir, f"run_{next_index:03d}") - - os.makedirs(run_dir) - return run_dir - -# SCENE_DIR = "/home/lburel/Sofa/plugins/BeamAdapter/examples" -# SCENE_DIR = "/home/lburel/Sofa/plugins/SofaPython3/examples" -# SCENE_DIR = "/home/lburel/Sofa/src/examples" - -# SCENE_DIR = "/home/lburel/Sofa/src/examples/Benchmark" -# SCENE_DIR = "/home/lburel/Sofa/src/examples/Component" -# SCENE_DIR = "/home/lburel/Sofa/src/examples/Demos" -# SCENE_DIR = "/home/lburel/Sofa/src/examples/Objects" -# SCENE_DIR = "/home/lburel/Sofa/src/examples/Tutorials" -# SCENE_DIR = "/home/lburel/Sofa/src/examples/Validation" - -SCENE_DIR = "/home/lucasbureltojo/~Sofa/src/examples" - -# RUN_FEATURE = "/home/lburel/Sofa/plugins/SofaPython3/examples/LoaderScene_snapshot.py" -RUN_FEATURE = "/home/lucasbureltojo/~Sofa/plugins/SofaPython3/examples/LoaderScene_snapshot.py" -MAX_WORKERS = 16 -TIMEOUT = 30 -def find_scenes(root): - scenes = [] - for r, _, files in os.walk(root): - for f in files: - if f.endswith(".py") or f.endswith(".scn"): - scenes.append(os.path.join(r, f)) - return scenes - -def safe_name(path): - return path.replace("/", "_").replace(" ", "_") - -def run_scene(scene, log_dir): - start = time.time() - cmd = ["python3", RUN_FEATURE, scene] - - log_file = os.path.join(log_dir, safe_name(scene) + ".log") - - python_trace = None - - # On ouvre le fichier log dès le début pour rediriger stdout/stderr - with open(log_file, "wb") as f: - # Entête du log - f.write(b"=== CMD ===\n") - f.write(" ".join(cmd).encode() + b"\n\n") - f.flush() - - try: - # Redirection directe vers le fichier log - result = subprocess.run( - cmd, - stdout=f, - stderr=subprocess.STDOUT, - timeout=TIMEOUT - ) - code = result.returncode - - except subprocess.TimeoutExpired: - code = -999 - python_trace = traceback.format_exc() - - except Exception: - code = -998 - python_trace = traceback.format_exc() - - duration = time.time() - start - - # Footer du log - f.write(b"\n=== RETURN CODE ===\n") - f.write(str(code).encode() + b"\n\n") - - if python_trace: - f.write(b"=== PYTHON TRACEBACK ===\n") - f.write(python_trace.encode() + b"\n\n") - - f.write(b"=== DURATION ===\n") - f.write(f"{duration:.2f} seconds\n".encode()) - - # Détermination du statut - if code == 0: - status = "OK" - elif code == -999: - status = "TIMEOUT" - elif code == -998: - status = "PYTHON ERROR" - elif code < 0: - status = f"CRASH (signal {-code})" - else: - status = f"ERROR (code {code})" - - return scene, status, duration - -def main(): - start_time = time.time() - LOG_DIR = get_next_run_dir(base_dir="/home/lucasbureltojo/logs") - print(f" Logs for this run: {os.path.abspath(LOG_DIR)}") - scenes = find_scenes(SCENE_DIR) - print(f"{len(scenes)} scenes found\n") - - results = [] - - if MAX_WORKERS == 1: - for scene in scenes: - print("▶", scene) - scene, status, duration = run_scene(scene, LOG_DIR) - print(" ", status) - results.append((scene, status)) - - else: - with ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor: - futures = {executor.submit(run_scene, s,LOG_DIR): s for s in scenes} - - for future in as_completed(futures): - scene, status, duration = future.result() - print(f"{status:20} | {scene}") - results.append((scene, status)) - - print("\n=== SUMMARY ===") - - stats = {} - for _, status in results: - stats[status] = stats.get(status, 0) + 1 - - for k, v in stats.items(): - print(f"{k:20} : {v}") - - fails = [s for s, st in results if st != "OK"] - - if fails: - print("\n FAILED SCENES:") - for f in fails: - print(" -", f) - - print(f"\n Logs saved in: {LOG_DIR}/") - end_time = time.time() - elapsed = end_time - start_time - print("\n Total execution time: {:.2f} seconds".format(elapsed) ) - -if __name__ == "__main__": - main() \ No newline at end of file From f15bd5a2864a6c61ea7360d48febf5a4b7280dfa Mon Sep 17 00:00:00 2001 From: Lucas-TJ Date: Wed, 15 Jul 2026 15:15:20 +0200 Subject: [PATCH 7/7] cleaning --- .../SofaPython3/Sofa/Core/Binding_Node.cpp | 42 ++++++------------- .../Sofa/Core/Binding_Snapshot.cpp | 8 +--- .../SofaPython3/Sofa/Core/Binding_Snapshot.h | 1 - 3 files changed, 14 insertions(+), 37 deletions(-) diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Node.cpp b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Node.cpp index e3123b072..deb121619 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Node.cpp +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Node.cpp @@ -27,28 +27,11 @@ #include #include - -#include "Binding_Snapshot.h" using sofa::core::objectmodel::BaseComponent; #include using sofa::core::objectmodel::BaseData; -#include -using sofa::core::objectmodel::Snapshot; - -#include - -#include -using sofa::simulation::SaveSnapshotVisitor; - -#include -using sofa::simulation::LoadSnapshotVisitor; - - -#include -using sofapython3::Snapshot_Python; - #include namespace simpleapi = sofa::simpleapi; @@ -86,6 +69,18 @@ using sofapython3::PythonEnvironment; using sofa::core::objectmodel::BaseObjectDescription; +#include +using sofa::core::objectmodel::Snapshot; + +#include +using sofa::simulation::SaveSnapshotVisitor; + +#include +using sofa::simulation::LoadSnapshotVisitor; + +#include +using sofapython3::Snapshot_Python; + #include #include @@ -681,21 +676,16 @@ void sendEvent(Node* self, py::object pyUserData, char* eventName) void executeSaveSnapshotVisitor(Node* self, Snapshot_Python& snapshot) { auto m_snapshot = std::make_shared(); - auto visitor = SaveSnapshotVisitor(nullptr,*m_snapshot); self->execute(visitor); - snapshot.push_back(m_snapshot); - } void executeLoadSnapshotVisitor(Node* self, Snapshot_Python& snapshot, sofa::Index index) { auto m_loadedsnapshot = std::make_shared(); - auto visitor = LoadSnapshotVisitor(nullptr,*snapshot.m_snapshots[index]); self->execute(visitor); - } py::object computeEnergy(Node* self) @@ -763,14 +753,6 @@ void moduleAddNode(py::module &m) { p.def("getMechanicalMapping", &getMechanicalMapping, sofapython3::doc::sofa::core::Node::getMechanicalMapping); p.def("sendEvent", &sendEvent, sofapython3::doc::sofa::core::Node::sendEvent); p.def("computeEnergy", &computeEnergy, sofapython3::doc::sofa::core::Node::computeEnergy); - - p.def("saveSnapshot", - [](Node& self, std::vector>& nodes) - { - Base& base = self; // cast explicite - return base.saveSnapshot(nodes); - }); - p.def("executeSaveSnapshotVisitor", &executeSaveSnapshotVisitor); p.def("executeLoadSnapshotVisitor", &executeLoadSnapshotVisitor); diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Snapshot.cpp b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Snapshot.cpp index dab327df4..0ca2b36e8 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Snapshot.cpp +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Snapshot.cpp @@ -19,19 +19,15 @@ ******************************************************************************/ #include "Binding_Snapshot.h" - #include #include - -namespace py { using namespace pybind11; } - - #include - #include using sofa::core::objectmodel::Snapshot; +namespace py { using namespace pybind11; } + namespace sofapython3 { void moduleAddSnapshot(py::module &m) diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Snapshot.h b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Snapshot.h index a7ab1a793..b567cc75b 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Snapshot.h +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Snapshot.h @@ -24,7 +24,6 @@ #include using sofa::core::objectmodel::Snapshot; - namespace sofapython3 { class Snapshot_Python {