Skip to content

Commit 7e288d4

Browse files
Merge branch '25_04_work_on_new_prefabs' into pr_basemodelpydantic
2 parents 63966a5 + 09c1c67 commit 7e288d4

15 files changed

Lines changed: 506 additions & 227 deletions

bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseComponent.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
#include <SofaPython3/Sofa/Core/Binding_Base.h>
2222
#include <SofaPython3/Sofa/Core/Binding_BaseComponent.h>
2323
#include <SofaPython3/Sofa/Core/Binding_BaseComponent_doc.h>
24-
#include <SofaPython3/Sofa/Core/Binding_Controller.h>
2524
#include <SofaPython3/PythonFactory.h>
2625

2726
#include <sofa/core/ObjectFactory.h>
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/******************************************************************************
2+
* SofaPython3 plugin *
3+
* (c) 2021 CNRS, University of Lille, INRIA *
4+
* *
5+
* This program is free software; you can redistribute it and/or modify it *
6+
* under the terms of the GNU Lesser General Public License as published by *
7+
* the Free Software Foundation; either version 2.1 of the License, or (at *
8+
* your option) any later version. *
9+
* *
10+
* This program is distributed in the hope that it will be useful, but WITHOUT *
11+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
12+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License *
13+
* for more details. *
14+
* *
15+
* You should have received a copy of the GNU Lesser General Public License *
16+
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
17+
*******************************************************************************
18+
* Contact information: contact@sofa-framework.org *
19+
******************************************************************************/
20+
21+
#include <SofaPython3/Sofa/Core/Binding_Component.inl>
22+
23+
SOFAPYTHON3_BIND_ATTRIBUTE_ERROR()
24+
25+
/// Makes an alias for the pybind11 namespace to increase readability.
26+
namespace py { using namespace pybind11; }
27+
28+
namespace sofapython3
29+
{
30+
using sofa::core::objectmodel::Event;
31+
using sofa::core::objectmodel::BaseComponent;
32+
33+
// ---------------------------------------------------------------------------
34+
// Component_Trampoline
35+
// ---------------------------------------------------------------------------
36+
37+
Component_Trampoline::Component_Trampoline()
38+
: BasetTrampoline(this) // pass this as BaseComponent* — no CRTP needed
39+
{
40+
}
41+
42+
void Component_Trampoline::draw(const sofa::core::visual::VisualParams* params)
43+
{
44+
PythonEnvironment::executePython(this, [this, params](){
45+
PYBIND11_OVERLOAD(void, Component, draw, params);
46+
});
47+
}
48+
49+
void Component_Trampoline::init()
50+
{
51+
PythonEnvironment::executePython(this, [this](){
52+
initializePythonCache();
53+
PYBIND11_OVERLOAD(void, Component, init, );
54+
});
55+
}
56+
57+
void Component_Trampoline::reinit()
58+
{
59+
PythonEnvironment::executePython(this, [this](){
60+
PYBIND11_OVERLOAD(void, Component, reinit, );
61+
});
62+
}
63+
64+
void Component_Trampoline::handleEvent(sofa::core::objectmodel::Event* event)
65+
{
66+
trampoline_handleEvent(event);
67+
}
68+
69+
std::string Component_Trampoline::getClassName() const
70+
{
71+
return trampoline_getClassName();
72+
}
73+
74+
// ---------------------------------------------------------------------------
75+
// Module registration
76+
// ---------------------------------------------------------------------------
77+
78+
void moduleAddComponent(py::module &m) {
79+
py::class_<Component,
80+
Component_Trampoline,
81+
BaseComponent,
82+
py_shared_ptr<Component>> f(m, "Component",
83+
py::dynamic_attr(),
84+
sofapython3::doc::component::componentClass);
85+
86+
f.def(py::init(&trampoline_init<Component_Trampoline>));
87+
f.def("__setattr__", &trampoline_setattr<Component_Trampoline>);
88+
89+
f.def("init", &Component::init);
90+
f.def("reinit", &Component::reinit);
91+
f.def("draw", [](Component& self, sofa::core::visual::VisualParams* params){
92+
self.draw(params);
93+
}, pybind11::return_value_policy::reference);
94+
}
95+
96+
} // namespace sofapython3
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/******************************************************************************
2+
* SofaPython3 plugin *
3+
* (c) 2021 CNRS, University of Lille, INRIA *
4+
* *
5+
* This program is free software; you can redistribute it and/or modify it *
6+
* under the terms of the GNU Lesser General Public License as published by *
7+
* the Free Software Foundation; either version 2.1 of the License, or (at *
8+
* your option) any later version. *
9+
* *
10+
* This program is distributed in the hope that it will be useful, but WITHOUT *
11+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
12+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License *
13+
* for more details. *
14+
* *
15+
* You should have received a copy of the GNU Lesser General Public License *
16+
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
17+
*******************************************************************************
18+
* Contact information: contact@sofa-framework.org *
19+
******************************************************************************/
20+
21+
#pragma once
22+
23+
#include <pybind11/pybind11.h>
24+
#include <sofa/core/objectmodel/BaseComponent.h>
25+
#include <string>
26+
#include <unordered_map>
27+
28+
namespace sofapython3 {
29+
30+
31+
class BasetTrampoline
32+
{
33+
public:
34+
explicit BasetTrampoline(sofa::core::objectmodel::BaseComponent* self);
35+
~BasetTrampoline();
36+
37+
void trampoline_handleEvent(sofa::core::objectmodel::Event* event);
38+
std::string trampoline_getClassName() const;
39+
void invalidateMethodCache(const std::string& methodName);
40+
41+
protected:
42+
void initializePythonCache();
43+
pybind11::object getCachedMethod(const std::string& methodName);
44+
bool callCachedMethod(const pybind11::object& method, sofa::core::objectmodel::Event* event);
45+
bool callScriptMethod(const pybind11::object& self, sofa::core::objectmodel::Event* event,
46+
const std::string& methodName);
47+
48+
/// Raw non-owning pointer to the concrete trampoline as BaseComponent.
49+
/// Safe because BasetTrampoline is always embedded in the same object.
50+
sofa::core::objectmodel::BaseComponent* m_componentSelf { nullptr };
51+
52+
pybind11::object m_pySelf;
53+
std::unordered_map<std::string, pybind11::object> m_methodCache;
54+
bool m_cacheInitialized = false;
55+
};
56+
57+
58+
template<class T>
59+
sofa::core::sptr<T> trampoline_init(pybind11::args& /*args*/, pybind11::kwargs& kwargs);
60+
61+
template<class T>
62+
void trampoline_setattr(pybind11::object self, const std::string& s, pybind11::object value);
63+
64+
65+
66+
class Component : public sofa::core::objectmodel::BaseComponent {
67+
public:
68+
SOFA_CLASS(Component, sofa::core::objectmodel::BaseComponent);
69+
void init() override {}
70+
void reinit() override {}
71+
};
72+
73+
class Component_Trampoline : public Component, public BasetTrampoline
74+
{
75+
public:
76+
SOFA_CLASS(Component_Trampoline, Component);
77+
78+
Component_Trampoline();
79+
80+
void init() override;
81+
void reinit() override;
82+
void draw(const sofa::core::visual::VisualParams* params) override;
83+
void handleEvent(sofa::core::objectmodel::Event* event) override;
84+
std::string getClassName() const override;
85+
};
86+
87+
void moduleAddComponent(pybind11::module &m);
88+
89+
} /// namespace sofapython3
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
/******************************************************************************
2+
* SofaPython3 plugin *
3+
* (c) 2021 CNRS, University of Lille, INRIA *
4+
* *
5+
* This program is free software; you can redistribute it and/or modify it *
6+
* under the terms of the GNU Lesser General Public License as published by *
7+
* the Free Software Foundation; either version 2.1 of the License, or (at *
8+
* your option) any later version. *
9+
* *
10+
* This program is distributed in the hope that it will be useful, but WITHOUT *
11+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
12+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License *
13+
* for more details. *
14+
* *
15+
* You should have received a copy of the GNU Lesser General Public License *
16+
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
17+
*******************************************************************************
18+
* Contact information: contact@sofa-framework.org *
19+
******************************************************************************/
20+
#pragma once
21+
22+
#include <pybind11/pybind11.h>
23+
#include <pybind11/cast.h>
24+
#include <sofa/core/visual/VisualParams.h>
25+
#include <SofaPython3/Sofa/Core/Binding_Base.h>
26+
#include <SofaPython3/Sofa/Core/Binding_Component.h>
27+
#include <SofaPython3/Sofa/Core/Binding_Component_doc.h>
28+
#include <SofaPython3/PythonFactory.h>
29+
#include <SofaPython3/PythonEnvironment.h>
30+
31+
/// Makes an alias for the pybind11 namespace to increase readability.
32+
namespace py { using namespace pybind11; }
33+
34+
namespace sofapython3
35+
{
36+
using sofa::core::objectmodel::Event;
37+
using sofa::core::objectmodel::BaseComponent;
38+
39+
40+
inline BasetTrampoline::BasetTrampoline(BaseComponent* self)
41+
: m_componentSelf(self)
42+
{
43+
}
44+
45+
inline BasetTrampoline::~BasetTrampoline()
46+
{
47+
if (m_cacheInitialized)
48+
{
49+
PythonEnvironment::gil acquire {"~BasetTrampoline"};
50+
m_methodCache.clear();
51+
m_pySelf = py::object();
52+
}
53+
}
54+
55+
inline void BasetTrampoline::initializePythonCache()
56+
{
57+
if (m_cacheInitialized)
58+
return;
59+
60+
// py::cast on the BaseComponent* will find the most-derived registered
61+
// pybind11 type (e.g. the user's Python subclass), which is exactly what
62+
// we want — no need for static_cast<T*>(this) anymore.
63+
m_pySelf = py::cast(m_componentSelf);
64+
65+
getCachedMethod("onEvent");
66+
m_cacheInitialized = true;
67+
}
68+
69+
inline py::object BasetTrampoline::getCachedMethod(const std::string& methodName)
70+
{
71+
auto it = m_methodCache.find(methodName);
72+
if (it != m_methodCache.end())
73+
return it->second;
74+
75+
py::object method;
76+
if (py::hasattr(m_pySelf, methodName.c_str()))
77+
{
78+
py::object fct = m_pySelf.attr(methodName.c_str());
79+
if (PyCallable_Check(fct.ptr()))
80+
method = fct;
81+
}
82+
83+
m_methodCache[methodName] = method;
84+
return method;
85+
}
86+
87+
inline bool BasetTrampoline::callCachedMethod(const py::object& method, Event* event)
88+
{
89+
if (m_componentSelf->f_printLog.getValue())
90+
{
91+
std::string eventStr = py::str(PythonFactory::toPython(event));
92+
msg_info(m_componentSelf) << "on" << event->getClassName() << " " << eventStr;
93+
}
94+
95+
py::object result = method(PythonFactory::toPython(event));
96+
if (result.is_none())
97+
return false;
98+
99+
return py::cast<bool>(result);
100+
}
101+
102+
inline void BasetTrampoline::invalidateMethodCache(const std::string& methodName)
103+
{
104+
if (!m_cacheInitialized)
105+
return;
106+
m_methodCache.erase(methodName);
107+
}
108+
109+
inline std::string BasetTrampoline::trampoline_getClassName() const
110+
{
111+
PythonEnvironment::gil acquire {"getClassName"};
112+
113+
if (m_pySelf)
114+
return py::str(py::type::of(m_pySelf).attr("__name__"));
115+
116+
// Fallback before cache is initialized: cast via BaseComponent*
117+
return py::str(py::type::of(py::cast(m_componentSelf)).attr("__name__"));
118+
}
119+
120+
inline bool BasetTrampoline::callScriptMethod(
121+
const py::object& self, Event* event, const std::string& methodName)
122+
{
123+
if (m_componentSelf->f_printLog.getValue())
124+
{
125+
std::string name = std::string("on") + event->getClassName();
126+
std::string eventStr = py::str(PythonFactory::toPython(event));
127+
msg_info(m_componentSelf) << name << " " << eventStr;
128+
}
129+
130+
if (py::hasattr(self, methodName.c_str()))
131+
{
132+
py::object fct = self.attr(methodName.c_str());
133+
py::object result = fct(PythonFactory::toPython(event));
134+
if (result.is_none())
135+
return false;
136+
return py::cast<bool>(result);
137+
}
138+
return false;
139+
}
140+
141+
inline void BasetTrampoline::trampoline_handleEvent(Event* event)
142+
{
143+
PythonEnvironment::executePython(m_componentSelf, [this, event](){
144+
if (!m_cacheInitialized)
145+
initializePythonCache();
146+
147+
std::string methodName = std::string("on") + event->getClassName();
148+
149+
py::object method = getCachedMethod(methodName);
150+
if (!method)
151+
method = getCachedMethod("onEvent");
152+
153+
if (method)
154+
{
155+
bool isHandled = callCachedMethod(method, event);
156+
if (isHandled)
157+
event->setHandled();
158+
}
159+
});
160+
}
161+
162+
163+
template<class T>
164+
sofa::core::sptr<T> trampoline_init(py::args& /*args*/, py::kwargs& kwargs)
165+
{
166+
auto c = sofa::core::sptr<T>(new T());
167+
c->f_listening.setValue(true);
168+
169+
for (auto kv : kwargs)
170+
{
171+
std::string key = py::cast<std::string>(kv.first);
172+
py::object value = py::reinterpret_borrow<py::object>(kv.second);
173+
174+
if (key == "name")
175+
c->setName(py::cast<std::string>(kv.second));
176+
try {
177+
BindingBase::SetAttr(*c, key, value);
178+
} catch (py::attribute_error& /*e*/) {
179+
// kwargs may be plain Python attributes unrelated to SOFA — ignore
180+
}
181+
}
182+
return c;
183+
}
184+
185+
template<class T>
186+
void trampoline_setattr(py::object self, const std::string& s, py::object value)
187+
{
188+
if (s.rfind("on", 0) == 0 && PyCallable_Check(value.ptr()))
189+
{
190+
auto* trampoline = dynamic_cast<T*>(py::cast<BaseComponent*>(self));
191+
if (trampoline)
192+
trampoline->invalidateMethodCache(s);
193+
}
194+
BindingBase::__setattr__(self, s, value);
195+
}
196+
197+
} // namespace sofapython3

0 commit comments

Comments
 (0)