diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Base.cpp b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Base.cpp index 90641297..c7894df9 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Base.cpp +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_Base.cpp @@ -477,7 +477,7 @@ void moduleAddBase(py::module &m) base.def("getDefinitionSourceFilePos", &Base::getDefinitionSourceFilePos, sofapython3::doc::base::getDefinitionSourceFilePos); base.def("getDefinitionSourceFileName", &Base::getDefinitionSourceFileName, sofapython3::doc::base::getDefinitionSourceFileName); base.def("getInstanciationSourceFilePos", &Base::getInstanciationSourceFilePos, sofapython3::doc::base::getInstanciationSourceFilePos); - base.def("getInstanciationFileName", &Base::getInstanciationSourceFileName, sofapython3::doc::base::getInstanciationSourceFilePos); + base.def("getInstanciationSourceFileName", &Base::getInstanciationSourceFileName, sofapython3::doc::base::getInstanciationSourceFileName); base.def("setDefinitionSourceFilePos", &Base::setDefinitionSourceFilePos, sofapython3::doc::base::setDefinitionSourceFilePos); base.def("setDefinitionSourceFileName", &Base::setDefinitionSourceFileName, sofapython3::doc::base::setDefinitionSourceFileName); diff --git a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseObject.cpp b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseObject.cpp index 6740de72..2b25bf39 100644 --- a/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseObject.cpp +++ b/bindings/Sofa/src/SofaPython3/Sofa/Core/Binding_BaseObject.cpp @@ -56,6 +56,7 @@ #include #include +#include /// Makes an alias for the pybind11 namespace to increase readability. namespace py { using namespace pybind11; } @@ -99,11 +100,20 @@ py::list getSlaves(BaseObject &self) return slaveList; } -py::object getContext(const BaseObject &self) +py::object getContext(BaseObject &self) { - const sofa::core::objectmodel::BaseContext* context = self.getContext(); + sofa::core::objectmodel::BaseContext* context = self.getContext(); if (context){ - return PythonFactory::toPython(const_cast(context)); + return PythonFactory::toPython(context); + } + return py::none(); +} + +py::object getOwner(BaseObject &self) +{ + sofa::simulation::Node* node = dynamic_cast(self.getContext()); + if (node){ + return PythonFactory::toPython(node); } return py::none(); } @@ -218,6 +228,7 @@ void moduleAddBaseObject(py::module& m) p.def("getLinkPath", [](const BaseObject &self){ return std::string("@") + self.getPathName(); }, sofapython3::doc::baseObject::getLink); p.def("getSlaves", getSlaves, sofapython3::doc::baseObject::getSlaves); p.def("getContext", getContext, sofapython3::doc::baseObject::getContext); + p.def("getOwner", getOwner); p.def("getMaster", getMaster, sofapython3::doc::baseObject::getMaster); p.def("addSlave", &BaseObject::addSlave, sofapython3::doc::baseObject::addSlave); p.def("storeResetState", &BaseObject::storeResetState, sofapython3::doc::baseObject::storeResetState); diff --git a/examples/modifier-exp/test1.py b/examples/modifier-exp/test1.py new file mode 100644 index 00000000..2e62d0de --- /dev/null +++ b/examples/modifier-exp/test1.py @@ -0,0 +1,233 @@ +import Sofa + +## Pyggy patch ############################################################## +class Parameters(object): + name : str = "" + def __init__(self, **kwargs): + for k,v in kwargs.items(): + setattr(self, k, v) + + def toDict(self): + return {"name":self.name} + +class ModifierParameters(Parameters): + def __init__(self, **kwargs): + Parameters.__init__(self, **kwargs) + + def toDict(self): + return Parameters.toDict(self) + +def getpythonCallingPoint(depth=0): + import inspect + d = inspect.stack()[depth] + return (d.filename, d.positions.lineno) + +class Modifier(Sofa.Core.Controller): + def __init__(self, parameters : ModifierParameters = ModifierParameters()): + Sofa.Core.Controller.__init__(self, **parameters.toDict()) + + def getModifiedNode(self): + return self.getOwner().parents[0] + +Sofa.Core.Modifier = Modifier + +def addModifier(self, type, parameters:Parameters, **kwargs) -> Modifier: + if "Modifiers" not in self.children: + self.addChild("Modifiers") + o = self.Modifiers.addObject(type(parameters=parameters, **kwargs)) + o.apply() + return o +Sofa.Core.Node.addModifier = addModifier + +def add(self, type, parameters:Parameters) -> Sofa.Core.Base: + if issubclass(type, Sofa.Core.Node): + return self.addChild(type(parameters)) +Sofa.Core.Node.add = add + +############################################################################# +class PrefabParameters(Parameters): + def __init__(self, **kwargs): + Parameters.__init__(self, **kwargs) + + def toDict(self): + return Parameters.toDict(self) + +class Prefab(Sofa.Core.Node): + def __init__(self, parameters): + Sofa.Core.Node.__init__(self, name=parameters.name) + +class MyEntityParameters(PrefabParameters): + def __init__(self, **kwargs): + PrefabParameters.__init__(self, **kwargs) + + def toDict(self): + return PrefabParameters.toDict(self) + +class MyEntity(Prefab): + def __init__(self, parameters): + Prefab.__init__(self, PrefabParameters(name=parameters.name)) + + self.addChild("Material") + self.addChild("Geometry") + self.addChild("Collision") + self.addChild("Visual") + self.addChild("Modifiers") + + self.Geometry.addObject("MeshOBJLoader", filename="mesh/cube.obj", name="loader") + + self.Material.addObject("MechanicalObject", name="state", template="Vec3", position=self.Geometry.loader.position.linkpath) + self.Material.addObject("UniformMass", name="mass", totalMass=1.0) + + self.Visual.addObject("OglModel", src=self.Geometry.loader.linkpath) + + +class FixedPointBoundaryConditionParameters(ModifierParameters): + def __init__(self, **kwargs): + ModifierParameters.__init__(self, **kwargs) + + def toDict(self): + return ModifierParameters.toDict(self) + +class FixedPointBoundaryCondition(Modifier): + def __init__(self, parameters : FixedPointBoundaryConditionParameters = FixedPointBoundaryConditionParameters()): + Modifier.__init__(self, parameters=parameters) + + def apply(self): + self.getModifiedNode().Material.addObject("UnilateralInteractionConstraint") + +class BilateralInteractionModifierParameters(ModifierParameters): + object1: Sofa.Core.Object = None + object2: Sofa.Core.Object = None + + def __init__(self, **kwargs): + ModifierParameters.__init__(self, **kwargs) + + def toDict(self): + return ModifierParameters.toDict(self) | {"object1":self.object1,"object2":self.object2} + +class BilateralInteractionModifier(Modifier): + def __init__(self, parameters : BilateralInteractionModifierParameters = BilateralInteractionModifierParameters()): + Modifier.__init__(self, parameters=parameters) + + self.object1 = parameters.object1 + self.object2 = parameters.object2 + + if self.object1 is None: + raise Exception("It is mandatory to have 'object1'") + + if self.object2 is None: + raise Exception("It is mandatory to have 'object2'") + + h = getpythonCallingPoint(3) + self.setInstanciationSourceFileName(h[0]) + self.setInstanciationSourceFilePos(h[1]) + + def apply(self): + a = self.object1.Material.addObject("UnilateralInteractionConstraint") + b = self.object2.Material.addObject("UnilateralInteractionConstraint") + + a.setInstanciationSourceFileName(self.getInstanciationSourceFileName()) + a.setInstanciationSourceFilePos(self.getInstanciationSourceFilePos()) + + b.setInstanciationSourceFileName(self.getInstanciationSourceFileName()) + b.setInstanciationSourceFilePos(self.getInstanciationSourceFilePos()) + +class HeaderFromSceneModifierParameters(ModifierParameters): + root : Sofa.Core.Node = None + + def __init__(self, **kwargs): + ModifierParameters.__init__(self, **kwargs) + + def toDict(self): + return ModifierParameters.toDict(self) | {"root":self.root} + +def find_component(cond, node): + c = [component for component in node.objects if cond(component)] + for child in node.children: + c += find_component(cond, child) + return c + +class HeaderFromSceneModifier(Modifier): + def __init__(self, parameters : HeaderFromSceneModifierParameters = HeaderFromSceneModifierParameters()): + Modifier.__init__(self, parameters=parameters) + self.root = parameters.root + + if self.root is None: + raise Exception("It is mandatory to have 'root' parameter for this modifier") + + h = getpythonCallingPoint(3) + self.setInstanciationSourceFileName(h[0]) + self.setInstanciationSourceFilePos(h[1]) + + def apply(self): + # Traverse scene to deduce stuff + constraints = find_component(lambda x: True, self.root) + constraints = [constraint.getClassName() for constraint in constraints] + print(f"Lagrangian {constraints}") + if "UnilateralLagrangianConstraint" in constraints: + self.root.addObject("FreeMotionAnimationLoop") + +class HeaderToSceneModifierParameters(ModifierParameters): + root : Sofa.Core.Node = None + + def __init__(self, **kwargs): + ModifierParameters.__init__(self, **kwargs) + + def toDict(self): + return ModifierParameters.toDict(self) | {"root":self.root} + +class HeaderToSceneModifier(Modifier): + def __init__(self, parameters : HeaderFromSceneModifierParameters = HeaderFromSceneModifierParameters()): + Modifier.__init__(self, parameters=parameters) + + self.root = parameters.root + + if self.root is None: + raise Exception("It is mandatory to have 'root' parameter for this modifier") + + def apply(self): + # Traverse scene to deduce stuff + + def is_mechanical(object): + return object.getClassName() == "MechanicalObject" + + mechanicals = find_component(is_mechanical, self.root) + for mechanical in mechanicals: + mechanical.getOwner().addObject("GenericConstraintCorrection") + +def createScene(root): + # Add a modifier in Example1/prefab, it modify the node it is applied to + # The use case is to enrich an object + root.addChild("Example1") + root.Example1.add(MyEntity, parameters=MyEntityParameters(name="prefab")) + root.Example1.prefab.addModifier(FixedPointBoundaryCondition, parameters=FixedPointBoundaryConditionParameters(name="boundary1", stiffness=30.0)) + + # Add a modifier in Example2, it modify a specific pair of node + # The use case is to explicit an action that involve several possibly disconnected in the graph object + # Personnally I'm not sure I want to express BoundaryCondition as Modifiers. + root.addChild("Example2") + root.Example2.add(MyEntity, parameters=MyEntityParameters(name="prefab1")) + root.Example2.add(MyEntity, parameters=MyEntityParameters(name="prefab2")) + root.Example2.addModifier(BilateralInteractionModifier, parameters=BilateralInteractionModifierParameters(name="constraint", + object1=root.Example2.prefab1, + object2=root.Example2.prefab2)) + + # The same but with the parameters written before calling the modifier + root.addChild("Example2bis") + root.Example2bis.add(MyEntity, parameters=MyEntityParameters(name="prefab1")) + root.Example2bis.add(MyEntity, parameters=MyEntityParameters(name="prefab2")) + parameters = BilateralInteractionModifierParameters() + parameters.name="constraint" + parameters.object1=root.Example2bis.prefab1 + parameters.object2=root.Example2bis.prefab2 + root.Example2bis.addModifier(BilateralInteractionModifier, parameters=parameters) + + + # Add a modifier in the root node, this modifier deduce something from the scene and modify anywhere in the scene. + root.addModifier(HeaderFromSceneModifier, parameters=HeaderFromSceneModifierParameters(name="bottom-up-fix", + root=root)) + + # Add a modifier in the root node, this modifier deduce something from the scene and modify top down the scene. + root.addModifier(HeaderToSceneModifier, parameters=HeaderToSceneModifierParameters(name="top-down-fix", + root=root)) +